Drop the `pretest:ci` lifecycle hook and call `create:doc-tests` from the `build` job before `test:ci`. The hook hid the step from the job log and made `test:ci` behave differently under npm than when run directly.
73 lines
8.5 KiB
Markdown
73 lines
8.5 KiB
Markdown
# AGENTS.md
|
|
|
|
Machine entry point for AI coding agents working in this repo. The authoritative
|
|
guidance for humans lives in [CONTRIBUTING.md](./CONTRIBUTING.md) and
|
|
[README.md](./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 via `tsc --lsp --stdio` and 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 before `rg`/`grep`** — `lsp_references` / `lsp_find_symbol` / `lsp_definition` / `lsp_document_symbols` are semantic and cross-file, so they see shadowing, imports and overloads that a text search cannot; use `lsp_hover` to read inferred types on generic-heavy code. Use `rg` for 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 from `rg`). 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.
|
|
- **Touched a `ts`-tagged fence in `README.md` / `CONTRIBUTING.md`?** Run `npm run create:doc-tests` first: it regenerates the gitignored tests under `src/doc-test/__generated__/`, formats and type-checks them, so `verify` executes the documented example. CI runs it in an explicit step before `test:ci`; the fast local tiers do not. See [development/docs.md](./development/docs.md).
|
|
- **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.
|
|
- **Document decisions where the next maintainer will look:** rationale, rejected alternatives and known issues go in `development/<category>.md` (see [development/README.md](./development/README.md)); the actionable rule stays in [CONTRIBUTING.md](./CONTRIBUTING.md) and links to it. Write each fact once — never copy the rule into `development/` or the reason into `CONTRIBUTING.md` — and change both in the same commit when a rule changes.
|
|
- **`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.
|
|
|
|
```sh
|
|
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.json` to silence a finding (e.g. turning `typescript/no-floating-promises` off)
|
|
- `as` casts 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-write `git 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:
|
|
|
|
```md
|
|
## 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`, runs `npm run verify`, and deletes the branch). The branching model is documented in [CONTRIBUTING.md § Branching model](./CONTRIBUTING.md#branching-model).
|
|
|
|
Follow [CONTRIBUTING.md § Testing discipline (type-driven)](./CONTRIBUTING.md#testing-discipline-type-driven) throughout.
|
|
|
|
## Read these
|
|
|
|
- [CONTRIBUTING.md § Rules the tools don't enforce](./CONTRIBUTING.md#rules-the-tools-dont-enforce) — the constraints the linters don't catch; CI/review bounce these. **The most important section.**
|
|
- [CONTRIBUTING.md § Testing discipline (type-driven)](./CONTRIBUTING.md#testing-discipline-type-driven) — write the `expectTypeOf` (type) before the `assert` (red); the types are the feature.
|
|
- [CONTRIBUTING.md § Script prefix convention](./CONTRIBUTING.md#script-prefix-convention) — adding an `npm run` script? reuse an existing prefix or it doesn't belong.
|
|
- [CONTRIBUTING.md § Commit messages](./CONTRIBUTING.md#commit-messages) — gitmoji + imperative + 50/72.
|
|
- [CONTRIBUTING.md § Feedback tiers](./CONTRIBUTING.md#feedback-tiers) — what runs when and at what cost (`watch` / pre-commit / pre-push / `check` / `verify` / `fix` / `maintain` / CI).
|
|
- [development/](./development/README.md) — 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](./development/tooling.md) — the rationale behind each tool choice; read before changing tooling.
|
|
- [package.json `#scripts`](./package.json) — the source of truth for every command (the `LEFTHOOK_FILES` convention scopes them to staged files vs. the whole project).
|