22 Commits
Author SHA1 Message Date
tmu a865b466bd 👷 Add Gitea release page to CI
The publish job now opens a Gitea release for the tag, using the
matching Keep-a-Changelog section as the body via scripts/release-notes.sh.
It runs before npm publish so a broken page fails CI without burning an
npm version; npm publish stays the last step.

Uses the first-party gitea-release-action (the older actions/release-action
is deprecated and requires asset files) and contents: write for the run's
automatic token.
2026-09-11 22:06:05 +00:00
tmu 7216c418d0 📝 Add a few new ideas to backlog 2026-09-11 20:54:16 +00:00
tmu 1191f3c4d9 📝 Prune the backlog of closed tasks
All completed and cancelled setup-phase entries are resolved and reflected
in README/CONTRIBUTING/ci.yml; the only still-open items remain.
2026-09-11 20:30:16 +00:00
tmu eee73a151d 📝 Point feedback tiers at ci.yml as the source of truth
The CI build row and the publishing workflow restated a step list that
had already drifted from the workflow (build + publint in build, publish
consuming the build artifact). Reference .gitea/workflows/ci.yml instead
of duplicating the job graph.
2026-09-11 20:23:36 +00:00
tmu 4d92ce9f9a 📝 Check off dist-output v1.0 tasks
The emit work (rewriteRelativeImportExtensions, inlineSources, prebuild
clean) resolved the v1.0 dist/ output block, and attw is covered by the
CI publish tier rather than a standalone checklist item.
2026-09-11 20:23:32 +00:00
tmu b29f924416 🔧 Clean dist before each build
tsc does not prune orphaned emit output, so dropping declarationMap left
stale *.d.ts.map files in dist/ until a manual clean. A prebuild hook now
empties dist/ first, keeping the build reproducible. It deliberately leaves
coverage/ alone, since the manual `clean` resets both.
2026-09-11 20:04:33 +00:00
tmu a0dd187042 📝 Document the TypeScript 5.0 type floor
Consumers need TypeScript >= 5.0: the emitted declarations use const type
parameters and keep relative .ts specifiers, both of which resolve only on
TS >= 5.0. That also makes the emitted .ts specifiers a non-issue, so the
backlog item is closed without a .d.ts post-step: TS <= 4.9 cannot parse
the declarations anyway. The README/CONTRIBUTING wording now says the
rewrite applies to the JavaScript output, not to the declarations.
2026-09-11 19:58:36 +00:00
tmu e7a058d608 🔧 Inline sources and drop declaration maps
The JS source maps now embed their sources (inlineSources), so a debugger
resolves into src/ even though the package ships only dist/. declarationMap
is dropped: a .d.ts.map cannot embed source and would dangle against the
unshipped src/.

Also removes a duplicate backlog entry for the same task and closes the
sourcemap item.
2026-09-11 19:50:50 +00:00
tmu e96c3f89ac 📝 Hand over the two open emit items
Record what a following agent cannot cheaply reconstruct for the
remaining setup-phase items (.d.ts .ts extensions, sourcemap sources):
the verified current emit state, the fact that every existing gate is
already green so no red signal will confirm the fix, that the durable
proof is a consumer-resolution typecheck rather than exit 0, and that
the two items share the emit surface so they belong in one change.
2026-09-10 23:53:46 +00:00
tmu 78aa97d670 📝 Correct the pi-lsp false-positive finding
The TS1295/TS1287 errors were not an LSP misconfiguration: the server
was still running from an earlier local test that had emptied
package.json, and kept the corrupted project state alive. Driven with
pi-lsp's exact handshake, tsc --lsp --stdio reads tsconfig.json
correctly. Record the real caveat instead: no file watchers, so
tsconfig.json/package.json edits can leave diagnostics stale until the
server restarts.
2026-09-10 23:49:09 +00:00
tmu 13ea134d05 📝 Document the .pi extension install model
.pi/settings.json is the shared, committed declaration and .pi/npm/ is
a gitignored cache pi recreates on a trusted startup, so the generated
.gitignore is intentional and nothing needs tracking. Record this in
the README tooling notes and close the reproducibility item as
resolved by design.
2026-09-10 23:37:23 +00:00
tmu b18e03ff97 📝 Keep verify free of the emit path
verify is the local quick source-correctness check; the emit path is
already gated by CI's build job, so adding build to verify would only
add inner-loop friction for something CI covers.
2026-09-10 23:31:45 +00:00
tmu 0ceb5a586e 📝 Confirm pre-push stays a local quick check
Pre-push runs npm test only. That is intended: the staged-file checks
are pre-commit's job and whole-project correctness is CI's, which
checks the pushed commits' content. Dirty working-tree files are out
of scope for a local check that CI backs up, so no dirty-tree guard is
added.
2026-09-10 23:25:04 +00:00
tmu fe9d59f8d4 👷 Publish the dist built by the build job
The publish job re-ran npm ci + build from scratch, discarding the
output that check, test:ci and publint had already gated, and paying
the build twice on a tag push. Upload dist/ as an artifact from build
and download it in publish instead. npm ci stays for the publint/attw
binaries; only the redundant rebuild is gone.
2026-09-10 23:05:46 +00:00
tmu 303544796e 👷 Run publint in the CI build job
publish:publint and publish:attw only ran in the tag-triggered publish
job, so a packaging break stayed green until release. Add publint to
build (offline, fast, packs the built dist). attw stays in publish,
where the full resolution matrix is worth the cost.
2026-09-10 23:03:14 +00:00
tmu 968a478c93 ♻️ Use rm for the clean target
The node -e rmSync one-liner was carried over from the build-tool
switch and only existed for cross-platform shell safety. The repo is
POSIX-only (sh scripts, Node 26, Linux CI), so rm -rf is simpler and
consistent with the rest of the tooling.
2026-09-10 22:56:12 +00:00
tmu e86709f593 📝 Sweep doc and formatter drift
README named --experimental-strip-types, which was renamed to
--strip-types. Document the current flag only.

The extension recommendations list is unchanged, but the VSCode section
undercounted it and now matches extensions.json. The per-language
formatter blocks in .vscode/settings.json stay (they keep a user's
local formatter settings from overriding the project's) and gain the
JSX/TSX language ids to match what oxfmt and the lefthook glob cover.
2026-09-10 22:49:01 +00:00
tmu 1709fbe842 🔧 Ignore pubv in knip via knip.json
pubv is a CLI invoked by release.sh, never imported, so knip reported
it as an unused devDependency on every advisory run. Add a knip config
with an ignoreDependencies entry; verified removal of the entry
reproduces the report.
2026-09-10 22:43:04 +00:00
tmu 0b461c233f 🔥 Drop redundant main/types fields
exports already declares types + import for the root entry, so the
top-level main/types pair was duplicate metadata. publint and attw stay
green with exports alone, verified against a packed consumer install.
2026-09-10 22:42:32 +00:00
tmu 8d1a03f025 🔧 Remove coverage in clean target
clean only wiped dist, leaving the gitignored coverage/ tree to
accumulate across runs. Remove both so a clean checkout state is
actually reachable.
2026-09-10 22:42:14 +00:00
tmu b7d1dbdf06 📝 Track setup-phase review findings in the backlog
Record the tooling, CI and docs gaps found reviewing the setup phase
(src/ is placeholder code and was excluded from the review).

The entries cover pi-lsp install reproducibility, gating the emit path
in verify, .ts extensions left in emitted .d.ts, pi-lsp false-positive
diagnostics, CI packaging checks and artifact reuse, pre-push gate
strength, plus low-impact cleanups (clean target, redundant fields,
knip false positive, sourcemap sources, doc drift).
2026-09-10 22:37:59 +00:00
tmu 99bda289a6 📝 Document .editorconfig as oxfmt fallback
Recommend `EditorConfig.EditorConfig` in .vscode/extensions.json so editors
without native `.editorconfig` support pick it up, and add an "Editor
configuration" section to CONTRIBUTING.md: `.editorconfig` is an
editor-compatibility fallback for the file types oxfmt does not format
(shell scripts, dotfiles, the commit-message template, git's
`COMMIT_EDITMSG` buffer), with `.oxfmtrc.json` authoritative where both
apply. `.editorconfig` itself is unchanged.
2026-09-10 21:47:08 +00:00
12 changed files with 154 additions and 50 deletions

No files matched your search

+35 -1
View File
@@ -20,6 +20,18 @@ jobs:
- run: npm run build
- run: npm run check
- run: npm run test:ci
# Fast, offline packaging gate. `attw` stays in `publish` (it needs
# a pack + full resolution matrix); `publint` packs too but is cheap
# enough to run on every push so a packaging break fails here, not
# at release time.
- run: npm run publish:publint
# Persist the exact dist/ that `check`, `test:ci` and `publint` were
# run against, so `publish` ships those bytes instead of rebuilding
# (which could in principle differ and would pay the build twice).
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
# Advisory scans (dead code, dependency freshness). Non-blocking: surfaced in
# the Actions tab for visibility, but must never gate a merge — so
@@ -40,6 +52,10 @@ jobs:
if: startsWith(gitea.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
# The release page is created with the run's automatic Gitea token
# (`github.token`), not `NPM_TOKEN`, so it needs `contents: write`.
permissions:
contents: write
steps:
# Double-gate: publish only runs on a tag *and* aborts here if NPM_TOKEN
# is unset, so a tag push never silently no-ops (or half-publishes). Set
@@ -56,9 +72,27 @@ jobs:
node-version-file: .node-version
registry-url: "https://registry.npmjs.org/"
- run: npm ci
- run: npm run build
# Consume the dist/ that `build` produced and gated, instead of
# rebuilding here — `publish` must ship the tested artifact.
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: npm run publish:publint
- run: npm run publish:attw
# The Gitea release page is created *before* `npm publish` on
# purpose: a broken page then fails CI without burning an npm
# version. The page is cheap to retry, a published version is not.
# The body is the matching Keep-a-Changelog section; an unknown tag
# makes the extractor exit non-zero, so the page can never go up
# empty.
- name: Extract release notes from CHANGELOG.md
env:
TAG_REF: ${{ gitea.ref }}
run: ./scripts/release-notes.sh "${TAG_REF#refs/tags/}" > release-notes.md
- uses: https://gitea.com/actions/gitea-release-action@v1
with:
body_path: release-notes.md
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+2 -1
View File
@@ -3,6 +3,7 @@
"oxc.oxc-vscode",
"streetsidesoftware.code-spell-checker",
"typescriptteam.native-preview",
"sandy081.todotasks"
"sandy081.todotasks",
"EditorConfig.EditorConfig"
]
}
+8
View File
@@ -3,10 +3,18 @@
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
+1 -1
View File
@@ -9,7 +9,7 @@ first-action facts. Do not restate evolving prose here — it will drift.
- Project: F#-style pattern matching for TypeScript/ESM. Node `>=26` (pinned via `.node-version`), ESM-only (no CommonJS shim).
- **While iterating:** `npm run test` (`check:tsc` + the unit suite) for fast feedback on the files you changed.
- **Optional code intelligence:** this repo installs `@spences10/pi-lsp` (pinned in `.pi/settings.json`) as a project-local pi extension. It talks to the repo's own TypeScript 7 via `tsc --lsp --stdio` and exposes **read-only** tools — `lsp_hover`, `lsp_definition`, `lsp_references`, `lsp_find_symbol`, `lsp_document_symbols`, `lsp_diagnostics(_many)`. Prefer `lsp_references` over `grep -w` for widely-colliding identifiers (`matches`, `type`, …); use `lsp_hover` to read inferred types on generic-heavy code. It has no rename / code-action / apply-edit surface — the write side is pi's `edit` tool + `check:tsc`. Treat empty LSP output as _inconclusive_, not success: **`npm run test` / `npm run verify` remain the sole authoritative gate** (see the next bullet).
- **Optional code intelligence:** this repo installs `@spences10/pi-lsp` (pinned in `.pi/settings.json`) as a project-local pi extension. It talks to the repo's own TypeScript 7 via `tsc --lsp --stdio` and exposes **read-only** tools — `lsp_hover`, `lsp_definition`, `lsp_references`, `lsp_find_symbol`, `lsp_document_symbols`, `lsp_diagnostics(_many)`. Prefer `lsp_references` over `grep -w` for widely-colliding identifiers (`matches`, `type`, …); use `lsp_hover` to read inferred types on generic-heavy code. It has no rename / code-action / apply-edit surface — the write side is pi's `edit` tool + `check:tsc`. Treat empty LSP output as _inconclusive_, not success: **`npm run test` / `npm run verify` remain the sole authoritative gate** (see the next bullet). The server keeps running across that gate with a ~5 min idle timeout and registers no file watchers, so if you change `tsconfig.json` / `package.json` mid-session its diagnostics can be stale — when LSP output disagrees with `check:tsc`, trust `check:tsc` and restart pi (or wait out the idle timeout) before concluding the LSP is wrong.
- **Definition of done — run this before you call the work finished:** `npm run verify`. If all green, commit. If red, look at the output, fix the root cause, and re-run.
- **On commit:** write a good message (see [CONTRIBUTING.md § Commit messages](./CONTRIBUTING.md#commit-messages)). Lefthook's pre-commit hook already runs the fast, offline, staged-file checks — don't run them by hand. If the hook fails on style, `npm run fix`, restage, recommit.
- **`npm run maintain` is NOT part of the feature loop.** Its scans are advisory, never a gate; run them only on an explicit maintenance / update-deps branch.
+18 -14
View File
@@ -6,13 +6,17 @@ This document is for maintainers and contributors working on the project itself.
CI and review will bounce these even though `npm run check` and the linters don't catch them. They're the high-frequency things a contributor (or an agent) reaches for by default:
- **Source imports use `.ts` extensions, never `.js`.** `node --strip-types` resolves the `.ts` form at test time; `rewriteRelativeImportExtensions` emits `.js` in `dist/`. "Pre-fixing" an import to `.js` breaks the inner loop. (rationale: README § Tooling decisions)
- **Source imports use `.ts` extensions, never `.js`.** `node --strip-types` only resolves the `.ts` form at test time; "Pre-fixing" an import to `.js` breaks the inner loop. (rationale: README § Tooling decisions)
- **A new `npm run` script must reuse an existing prefix** (`create:` / `check:` / `fix:` / `test:` / `watch:` / `maintain:` / `publish:` / `setup:`). If none fits, that's a signal the script doesn't belong in the pipeline — not a reason to invent a new prefix. If it genuinely does belong, add the prefix to both lists here in the same commit; an undocumented prefix becomes invisible and quietly accrues members. (see [Script prefix convention](#script-prefix-convention))
- **`oxlint-disable` directives live in source, not `.oxlintrc.json`.** The trade-off must sit next to the code it silences. This is a _human_ last-resort convention; agents must not add these — see [AGENTS.md § Never do](./AGENTS.md#never-do). (rationale: README § Tooling decisions)
- **Don't put slow / network / whole-project scans in `check` or pre-commit.** Advisory scans are not correctness gates; they belong under `maintain:`. (see [Feedback tiers](#feedback-tiers) and [Script prefix convention](#script-prefix-convention))
- **New work starts with `npm run create:branch`, never a hand-written `git switch -c`/`git checkout -b`.** The command carries the branch precondition; branching around it skips the clean-tree, current-`main` and green-baseline checks, and the skip is invisible until a failure can no longer be attributed. (see [Branching model](#branching-model))
- **There is no local `npm run publish`, and `publish:publint` / `publish:attw` don't go in `check`.** (see [Publishing workflow](#publishing-workflow))
## Editor configuration
`.editorconfig` is for editor compatibility, not a gate — it is a sane fallback for the files oxfmt does not format (shell scripts, dotfiles, `LICENSE`, the commit-message template, and git's `COMMIT_EDITMSG` buffer). Where both apply, `.oxfmtrc.json` is authoritative: oxfmt is the formatter, and the overlapping `.editorconfig` keys only keep non-oxfmt editors close to the formatted result.
## Commit messages
Gitmoji subject, imperative mood, 50/72 wrapping. The template is `commit-message-template`; run `npm run setup:git-commit-message` once after cloning to register it as git's `commit.template` (or `npm run setup` to run every one-time clone step).
@@ -40,18 +44,18 @@ Separately, some top-level scripts are **bare** (no prefix): the entry points th
The tools are organized into a feedback ladder. Each tier catches different things at different costs; the rule of thumb is "earlier tiers fire more often, faster tiers catch less, slower tiers are more thorough":
| Tier | When | What it runs | Time |
| -------------------------------- | ---------------------- | --------------------------------------------------------------------- | ----- |
| `npm run watch` | manual | `watch:test` — re-runs tests on file save | ~0.1s |
| Pre-commit (auto) | on stage | tsc + oxlint + oxfmt + cspell (staged files only) | ~1.3s |
| Pre-push (auto) | on push | `npm test` (full tsc + unit tests) | ~3.5s |
| `npm run check` | manual | Correctness gates: tsc + oxlint + oxfmt + cspell (whole project) | ~3s |
| `npm run verify` | manual | Definition of done: `npm run check` + unit tests, one shot | ~6s |
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
| CI build (auto) | on push to `main` | `npm run check` + `npm run test:ci` | ~30s+ |
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
| CI publish (auto) | on tag | `publish:publint` + `publish:attw`, then `npm publish` | ~10s |
| Tier | When | What it runs | Time |
| -------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------- | ----- |
| `npm run watch` | manual | `watch:test` — re-runs tests on file save | ~0.1s |
| Pre-commit (auto) | on stage | tsc + oxlint + oxfmt + cspell (staged files only) | ~1.3s |
| Pre-push (auto) | on push | `npm test` (full tsc + unit tests) | ~3.5s |
| `npm run check` | manual | Correctness gates: tsc + oxlint + oxfmt + cspell (whole project) | ~3s |
| `npm run verify` | manual | Definition of done: `npm run check` + unit tests, one shot | ~6s |
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
| CI build (auto) | on push to `main` | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
| CI publish (auto) | on tag | Gitea release page (body from CHANGELOG) + `publish:publint` + `publish:attw`, then `npm publish` | ~15s |
### Why these splits?
@@ -92,4 +96,4 @@ Publishing is CI-only by policy. Local `npm publish` is not supported. The maint
1. All intended changes are merged to `main` and passing CI.
2. The maintainer runs `npm run create:release` — an interactive prompt suggests a version (based on the latest CHANGELOG entry); the maintainer confirms or edits it.
3. `scripts/release.sh` creates a single release commit (changelog + package.json bump, amended into one commit), tags it, and pushes everything to Gitea.
4. CI runs on the push: `npm run check` + `npm run test:ci` on the commit; then the `publish` job fires on the tag: `build` → `publish:publint` → `publish:attw` → `npm publish --access public`. The publish-tier checks must pass before the artifact is published.
4. CI runs on the push (the `build` and `maintain` jobs); the `publish` job then fires on the tag, consuming the `dist/` artifact the `build` job produced. The job graph lives in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) — keep that file, not this list, as the source of truth. The publish-tier checks must pass before the artifact is published. The `publish` job also creates the Gitea release page from the matching Keep-a-Changelog section (`scripts/release-notes.sh`); it runs _before_ `npm publish` so a broken page fails CI without consuming a version, and `npm publish` stays the last step.
+8 -6
View File
@@ -20,7 +20,7 @@ What each tier runs, when it fires and what it costs:
### Tooling
- **TypeScript 7** — type checker and build (`tsc`).
- **node --test** + `--experimental-strip-types` — test runner (Node 22.6+, flag dropped on Node 24).
- **node --test** + `--strip-types` — test runner.
- **c8** — code coverage for `test:ci`.
- **oxlint** — Rust-based linter, with type-aware rules powered by **oxlint-tsgolint** (typescript-go).
- **oxfmt** — Rust-based formatter (Prettier-compatible). Formats JS/TS, JSON/JSONC, YAML, Markdown, MDX, and more; built-in `package.json` key sorting replaces `sort-package-json`.
@@ -38,8 +38,9 @@ Each tool's configuration trade-off is recorded in [Tooling decisions](#tooling-
The choice and configuration of each tool above is the result of deliberate trade-offs, not defaults. The non-obvious ones:
- **`tsconfig.json` extends `@tsconfig/strictest` + `@tsconfig/node26`**; `tsconfig.build.json` extends it to add the emit-only options (`declaration`, `sourceMap`, `outDir`, `target: es2024`, `rewriteRelativeImportExtensions: true`) and to exclude test files. This separation lets the editor and CI type-check from one config while the build emits from the other.
- **Source imports use `.ts` extensions** so `node --strip-types` resolves them at test time. `rewriteRelativeImportExtensions: true` in `tsconfig.build.json` rewrites them to `.js` in the emitted `dist/*` output, so consumers see conventional ESM imports.
- **`tsconfig.json` extends `@tsconfig/strictest` + `@tsconfig/node26`**; `tsconfig.build.json` extends it to add the emit-only options (`declaration`, `sourceMap`, `inlineSources`, `outDir`, `target: es2024`, `rewriteRelativeImportExtensions: true`) and to exclude test files. `inlineSources` embeds the original TypeScript in `dist/*.js.map`, so debuggers can map into `src/` without it being shipped; `declarationMap` is intentionally off because a `.d.ts.map` cannot embed source and would dangle. This separation lets the editor and CI type-check from one config while the build emits from the other.
- **`npm run build` first runs a `prebuild` hook that empties `dist/`.** `tsc` does not prune orphaned emit output — dropping `declarationMap`, for example, left stale `*.d.ts.map` files behind — so the build must start from an empty `dist/` to be reproducible. `prebuild` removes only `dist`; the manual `clean` still resets `dist` + `coverage`, so a local coverage report survives a build.
- **Source imports use `.ts` extensions** so `node --strip-types` resolves them at test time. `rewriteRelativeImportExtensions: true` in `tsconfig.build.json` rewrites them to `.js` in the emitted JavaScript; the emitted `.d.ts` keep the `.ts` specifier, which TypeScript >= 5.0 resolves (see [Requirements](#requirements)), so no post-processing step is needed.
- **Type-aware oxlint is enabled declaratively** via `options.typeAware: true` in `.oxlintrc.json` (powered by `oxlint-tsgolint`). The script commands stay clean — no CLI flag — and type-aware mode is a property of the config, not the invocation.
- **Source-level `oxlint-disable` directives** are used for known type-aware false positives (see `src/pattern.ts`, `src/match.ts`, `src/index.test.ts`). The disable lives next to the code it silences, not in `.oxlintrc.json`, so the trade-off is visible to anyone reading the source.
- **`knip --include dependencies,exports,files`** intentionally omits the `types` category, which produces systematic false positives for libraries whose exported types are part of the public API. The targeted scope keeps the signal high without config-file boilerplate.
@@ -47,17 +48,18 @@ The choice and configuration of each tool above is the result of deliberate trad
- **`check:tsc` runs first** in the `npm run check` chain so a type error short-circuits the rest (faster feedback than letting oxlint/oxfmt run and then failing on tsc at the end).
- **The pre-commit hook sets the `LEFTHOOK_FILES` env var** to the staged-files list, and the affected scripts use `${LEFTHOOK_FILES:-<default>}` to default to the whole project when invoked manually. This keeps `package.json#scripts` as the single source of truth for the underlying commands — `lefthook.yml` only describes _what to run on which files_.
- **`tslib` and `type-fest` are deliberately not used.** `tslib` is a runtime helper for old ES3/ES5 targets (the project targets ES2024); `type-fest` was never imported. knip caught both.
- **`@spences10/pi-lsp` is pinned to `0.0.46` and is read-only by design.** The package inspects `node_modules/typescript`, sees major ≥ 7 with no `lib/tsserver.js` (true of the `typescript-go` / `tsgo` port), and spawns the repo's own `tsc --lsp --stdio` binary — no `typescript-language-server` dependency is required. Earlier releases (`≤ 0.0.10`) hard-wire to `typescript-language-server --stdio` and are TS6-only. The tool is _intermediate_ agent feedback (hover, references, definition, symbols, diagnostics); it has no rename / code-action / apply-edit surface, and never a correctness gate — `npm run check` / `verify` remain that.
- **`@spences10/pi-lsp` is pinned to `0.0.46` and is read-only by design.** The package inspects `node_modules/typescript`, sees major ≥ 7 with no `lib/tsserver.js` (true of the `typescript-go` / `tsgo` port), and spawns the repo's own `tsc --lsp --stdio` binary — no `typescript-language-server` dependency is required. Earlier releases (`≤ 0.0.10`) hard-wire to `typescript-language-server --stdio` and are TS6-only. The tool is _intermediate_ agent feedback (hover, references, definition, symbols, diagnostics); it has no rename / code-action / apply-edit surface, and never a correctness gate — `npm run check` / `verify` remain that. `.pi/settings.json` is the shared, committed declaration; `.pi/npm/` is a gitignored install cache that pi recreates automatically on a trusted startup (it runs `npm install` for any missing project package), so the cache is deliberately not tracked.
### Requirements
- Node.js >= 26 (engines field; pinned via `.node-version`).
- TypeScript >= 5.0 to consume the published declarations. The emitted `.d.ts` use `const` type parameters (TS 5.0) and keep their relative `.ts` specifiers; both resolve on TS >= 5.0 in `node10`/`node16`/`nodenext`/`bundler`.
## VSCode integration
- Recommended extensions: see `.vscode/extensions.json` (oxc, cspell).
- Recommended extensions: see `.vscode/extensions.json` (oxc, cspell, TypeScript native-preview, EditorConfig, todo-tasks).
- TypeScript 7 is used via the `typescriptteam.native-preview` extension.
- oxc extension provides oxlint squiggles and oxfmt format-on-save.
- oxc extension provides oxlint squiggles and oxfmt format-on-save; `.vscode/settings.json` pins it per language so a user's local `[language]` formatter settings cannot override the project's choice.
## Contributing
+10 -23
View File
@@ -5,22 +5,9 @@ Backlog and tracking for tiny-pattern-ts. Managed in vscode-todotasks format.
---
Setup:
☐ Establish release CI workflow: post-tag checks + npm publish after release tag push => pi --session 01a06e72-e61e-7327-b3e9-3e11749a523f @high
☐ Add gitea release page in CI @high
✔ Add gitea release page in CI @high @done
☐ Manually verify the Gitea release page on a real tag push (needs main) @high
☐ Split off template into separate package => pi --session 01a07dde-7050-7054-bb36-1606d7eb2bc3 @high
✔ Document branching model @done
✔ Describe branching model: when to branch, base branch, naming @done
✔ Clarify release workflow: who pushes `main`, CI as post-merge gate, drop PR usage @done (9/8/2026, 2:54:32 PM)
✔ Evaluate connecting the agent to lsp typescript server @done
- might be more difficult with TS7, since LSP Server has changed from 6
- but since vscode is running TS7, maybe it is possible to connect to the TS7 LSP server of vscode
- LSP servers in general?
- maybe skills?
→ resolved: adopted @spences10/pi-lsp@0.0.46 as a project-local pi extension (`.pi/settings.json`). It auto-detects the repo's TypeScript 7 (no `lib/tsserver.js`) and spawns `tsc --lsp --stdio` — the same tsgo binary VS Code's native-preview uses. Read-only: hover / definition / references / symbols / diagnostics. No skill; skills don't own persistent processes.
✔ Adopt a `setup:` prefix for one-time clone configuration @done
✔ Register `use:git-commit-message` as its first member (and retire `use:` — it is in no prefix list) @done
✔ Create an umbrella 'setup' task that runs all setup tasks - currently one @done
v1.0:
☐ API surface is stable and fully typed
@@ -31,10 +18,6 @@ v1.0:
☐ Achieve 100% branch coverage on `src/pattern.ts`
☐ Achieve 100% branch coverage on `src/match.ts`
☐ Achieve 100% branch coverage on `src/index.ts`
☐ `dist/` output is clean
☐ Verify `.d.ts` declarations match public exports
☐ Ensure `.js` files use `.js` extensions (not `.ts`)
☐ Test `attw --profile esm-only` passes
Bugs:
@@ -53,7 +36,11 @@ Maintenance:
☐ CI writes each run's `coverage/` into a shared volume keyed by project + tag (e.g. `/coverage/tiny-pattern-ts/<tag>/`)
☐ Browse to `…/coverage/<repo>/<tag>/index.html` in the browser; drop the `actions/upload-artifact` coverage step in favour of the shared-dir layout
→ design: no deploy step in CI; the webserver just exposes the shared directory (decided over Gitea Pages / Codecov — neither confirmed available/ wanted)
✔ Set up lefthook pre-commit hook @done (9/8/2026, 10:52:56 AM)
✔ Configure tsconfig strictest + node26 profiles @done (9/8/2026, 10:08:37 AM)
✔ Add check:tsc as first tier in `npm run check` @done (9/8/2026, 10:08:37 AM)
✔ Pin Node.js >= 26 via `.node-version` @done (9/8/2026, 10:08:38 AM)
☐ serve docs over self hosted server @low
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving docs (reuse existing reverse proxy)
☐ CI writes docs to a shared volume keyed by project + tag (e.g. `/docs/tiny-pattern-ts/<tag>/`)
☐ Browse to `…/docs/<repo>/<tag>/index.html` in the browser
☐ serve landing page over self hosted server @low
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving landing page (reuse existing reverse proxy)
☐ CI writes landing page to a shared volume keyed by project + tag (e.g. `/landing/tiny-pattern-ts/<tag>/`)
☐ Browse to `…/tiny-pattern-ts/index.html` in the browser
+1
View File
@@ -28,6 +28,7 @@
"runwisp",
"glab",
"postversion",
"prebuild",
"Zilla",
"kacl",
"bestikk",
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "./node_modules/knip/schema.json",
"ignoreDependencies": ["@runwisp/pubv"]
}
+2 -3
View File
@@ -27,8 +27,6 @@
],
"type": "module",
"sideEffects": false,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -40,12 +38,13 @@
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"prebuild": "rm -rf dist",
"check": "npm run check:tsc && npm run check:oxlint && npm run check:oxfmt && npm run check:cspell",
"check:cspell": "cspell lint ${LEFTHOOK_FILES:-.}",
"check:oxfmt": "oxfmt --check ${LEFTHOOK_FILES:-.}",
"check:oxlint": "oxlint ${LEFTHOOK_FILES:-src}",
"check:tsc": "tsc",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
"clean": "rm -rf dist coverage",
"fix": "npm run fix:oxlint && npm run fix:oxfmt",
"fix:oxfmt": "oxfmt ${LEFTHOOK_FILES:-.}",
"fix:oxlint": "oxlint --fix src",
+64
View File
@@ -0,0 +1,64 @@
#!/bin/sh
set -eu
# Print the Keep-a-Changelog section for a release tag, so CI can use it as the
# body of the Gitea release page without re-implementing CHANGELOG parsing.
#
# Run as `scripts/release-notes.sh <tag>`. A leading `v` is tolerated so both
# `v1.2.3` and `1.2.3` match the `## [1.2.3]` heading. Prints to stdout and
# exits non-zero when the tag has no section, so a release can never publish
# with an empty body.
TAG="${1:-}"
CHANGELOG="${CHANGELOG:-CHANGELOG.md}"
if [ -z "${TAG}" ]; then
echo "Error: usage: $0 <tag>" >&2
exit 1
fi
if [ ! -f "${CHANGELOG}" ]; then
echo "Error: ${CHANGELOG} not found." >&2
exit 1
fi
# `v1.2.3` and `1.2.3` are the same release; the heading only ever uses the
# bare version.
VERSION="${TAG#v}"
awk -v version="${VERSION}" '
# A new section ends the one we are printing (or is the one we want).
/^## \[/ {
if (found) exit
heading = $0
sub(/^## \[/, "", heading)
sub(/\].*$/, "", heading)
if (heading == version) found = 1
next
}
# Link-reference definitions live at the bottom of the file and are never
# part of the section body. Stopping here keeps the last release notes
# from picking them up (there is no following heading to stop at).
/^\[[^]]+\]:/ { exit }
found {
if ($0 ~ /^[[:space:]]*$/) {
# Buffer blank lines so trailing ones at the end of the section
# are dropped instead of leaking into the release body.
if (started) pending = pending $0 "\n"
} else {
printf "%s%s\n", pending, $0
pending = ""
started = 1
}
}
END {
if (!found) {
printf "Error: no CHANGELOG section for [%s].\n", version > "/dev/stderr"
exit 1
}
}
' "${CHANGELOG}"
+1 -1
View File
@@ -4,8 +4,8 @@
"noEmit": false,
"target": "es2024",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"inlineSources": true,
"outDir": "dist",
"rewriteRelativeImportExtensions": true,
"rootDir": "src"