Every `ts`-tagged fence in README.md / CONTRIBUTING.md now becomes an executed `node:test` case in a gitignored generated file, so a documented example cannot drift from the API. The generator hoists and merges the leading imports, rewrites the library specifier to the `#test-tiny-pattern-ts` alias, and rejects a fence with no describing paragraph or one that re-imports a prelude module. CI regenerates via `pretest:ci`; `create:doc-tests` runs the generator, formats, then typechecks the output against a scoped tsconfig that relaxes `noUnusedLocals`. c8's default excludes already omit the generated `*.test.ts`, so `test:ci` is unchanged.
8.5 KiB
AGENTS.md
Machine entry point for AI coding agents working in this repo. The authoritative guidance for humans lives in CONTRIBUTING.md and README.md; this file only points at it and states the stable first-action facts. Do not restate evolving prose here — it will drift.
First action
-
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 viatsc --lsp --stdioand exposes read-only tools —lsp_hover,lsp_definition,lsp_references,lsp_find_symbol,lsp_document_symbols,lsp_diagnostics(_many). When you are looking for a symbol, reach for the LSP beforerg/grep—lsp_references/lsp_find_symbol/lsp_definition/lsp_document_symbolsare semantic and cross-file, so they see shadowing, imports and overloads that a text search cannot; uselsp_hoverto read inferred types on generic-heavy code. Usergfor what the LSP cannot see — doc prose, string literals, config, task lists, file discovery — and reconcile the two sets before editing (symbols from the LSP, strings and prose fromrg). It has no rename / code-action / apply-edit surface — the write side is pi'sedittool +check:tsc. Treat empty LSP output as inconclusive, not success:npm run test/npm run verifyremain 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 changetsconfig.json/package.jsonmid-session its diagnostics can be stale — when LSP output disagrees withcheck:tsc, trustcheck:tscand 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. -
Touched a
ts-tagged fence inREADME.md/CONTRIBUTING.md? Runnpm run create:doc-testsfirst: it regenerates the gitignored tests undersrc/doc-test/__generated__/, formats and type-checks them, soverifyexecutes the documented example. CI does this automatically viapretest:ci; the fast local tiers do not. See development/docs.md. -
On commit: write a good message (see 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. -
Document decisions where the next maintainer will look: rationale, rejected alternatives and known issues go in
development/<category>.md(see development/README.md); the actionable rule stays in CONTRIBUTING.md and links to it. Write each fact once — never copy the rule intodevelopment/or the reason intoCONTRIBUTING.md— and change both in the same commit when a rule changes. -
npm run maintainis NOT part of the feature loop. Its scans are advisory, never a gate; run them only on an explicit maintenance / update-deps branch.npm run test # while iterating (fast feedback) npm run verify # definition of done: whole-project correctness, one shot
Never do
Don't silence the type system to force a green run. As an agent these are forbidden:
// @ts-nocheck,// @ts-ignore,// @ts-expect-error// oxlint-disable/// oxlint-disable-next-line- editing
.oxlintrc.jsonto silence a finding (e.g. turningtypescript/no-floating-promisesoff) ascasts used to push an expression through (type-aware oxlint already flags unsafe assertions)
Fix the root cause with the type system instead — narrowing, generics, satisfies, conditional / mapped types, utility types (NonNullable, Exclude, …). TypeScript can express it; that's the intended tool. Suppressions — a source oxlint-disable or a .oxlintrc.json entry — are a human last resort, not a tool for you. If the types genuinely cannot express something, stop and surface the conflict (commit message / handover) rather than suppress it.
The same applies to the checks themselves: never git commit --no-verify (or otherwise skip a pre-commit / pre-push hook). The checks are fast and offline, so a redundant run is fine — bypassing a hook to get green is the identical anti-pattern. If a commit already skipped a hook, redo it through one: git reset --soft HEAD~1 && git commit -C <skipped-sha>.
Never start a long-lived / blocking process such as npm run watch. It runs until a human stops it with Ctrl-C, so in an agent turn it hangs forever and floods the context with continuous output. Reach for a one-shot command instead — npm run test (or npm run check) — to get feedback.
Backlog
backlog.tasks uses the vscode-todotasks format (not Markdown). A line ending in : is a project; every other line is a task. Status glyphs: ☐ open, ✔ done, ✘ cancelled; subtasks nest by indentation. Inline @tags carry metadata — @done / @cancelled mark completion, @critical / @high / @low / @today set priority. The (…) timestamp after @done is editor-generated: omit it when checking off by hand.
Required coupling: a ✔ line must also carry @done, and a ✘ line must carry @cancelled. The sandy081.todotasks extension treats the glyph as the completion signal, then unconditionally searches for the matching tag to decorate; a bare ✔/✘ with no tag makes it compute an illegal Range (negative character offset) that throws and kills all highlighting/decoration for the document. A ☐ may stand alone. So check off by hand as ✔ … @done (optionally @done (timestamp)), never a lone ✔.
Working on tasks
Every task — with or without subtasks — goes through the complete branching model:
-
Create the branch with
npm run create:branch -- <prefix>/<desc>, inferring the prefix from the task content (feature/…/fix/…/chore/…) — do not hand-writegit switch -c, the command enforces the clean-tree / current-main/ green-baseline precondition. Work with commits (each subtask gets one or more), then present a concise handover for the user to review. Use this fixed shape:## Handover — <branch> **Implemented:** <what was built, and how> **Judgement calls:** <where the task was unclear, and what you assumed> **Known problems:** <open issues, caveats, follow-ups>Once the user has no further objections, merge back:
npm run create:finish(on the branch — it merges--no-ff, runsnpm run verify, and deletes the branch). The branching model is documented in CONTRIBUTING.md § Branching model.
Follow CONTRIBUTING.md § Testing discipline (type-driven) throughout.
Read these
- CONTRIBUTING.md § Rules the tools don't enforce — the constraints the linters don't catch; CI/review bounce these. The most important section.
- CONTRIBUTING.md § Testing discipline (type-driven) — write the
expectTypeOf(type) before theassert(red); the types are the feature. - CONTRIBUTING.md § Script prefix convention — adding an
npm runscript? reuse an existing prefix or it doesn't belong. - CONTRIBUTING.md § Commit messages — gitmoji + imperative + 50/72.
- CONTRIBUTING.md § Feedback tiers — what runs when and at what cost (
watch/ pre-commit / pre-push /check/verify/fix/maintain/ CI). - development/ — the decisions, rejected alternatives and known issues behind the rules; the “why” that CONTRIBUTING.md links to. Read the relevant file before changing an area.
- development/tooling.md — the rationale behind each tool choice; read before changing tooling.
- package.json
#scripts— the source of truth for every command (theLEFTHOOK_FILESconvention scopes them to staged files vs. the whole project).