From 436b49e3be3e2d395b6a103382f974b667bd0c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 5 Sep 2026 16:29:23 +0200 Subject: [PATCH] :memo: Add AGENTS.md as the agent entry point AI coding agents read AGENTS.md automatically, not README. Make AGENTS.md a thin pointer: the stable first-action facts plus links to CONTRIBUTING.md, README.md, and package.json#scripts. Keep the rules prose in CONTRIBUTING.md so humans and agents don't diverge (same anti-drift move as dropping project-specs.md). Clarify the agent's verification loop: npm run test is the mandatory gate; the pre-commit hook already runs the fast offline checks (tsc, oxlint, oxfmt, cspell) on staged files, and npm run check (knip + outdated) is repo-maintenance, not part of the feature loop. Forbid type-system escape hatches (@ts-nocheck, oxlint-disable, as-casts) as agent-only rules; a human may still add a review-visible disable as a last resort, so the location convention stays in CONTRIBUTING.md with a forward reference. --- AGENTS.md | 36 ++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 18 +++++++++++++++++- README.md | 2 +- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d2e3afc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# 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). +- **Mandatory while iterating:** `npm run test` (runs `check:tsc`, then the unit suite). This is the gate you are responsible for. +- **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 (`tsc` + `oxlint` + `oxfmt` + `cspell`) — don't run them by hand. If the hook fails on style, `npm run fix`, restage, recommit. +- **`npm run check` is NOT part of the feature loop.** It adds `knip` (dead-code/deps) and `check:outdated` (registry) — repo-maintenance scans. Run it only on an explicit maintenance / update-deps branch; CI runs it on the PR regardless. + + ```sh + npm run test # the bot's definition of done; commit normally after + ``` + +## 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` +- `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. The `oxlint-disable`-location rule in [CONTRIBUTING.md § Rules the tools don't enforce](./CONTRIBUTING.md#rules-the-tools-dont-enforce) is a **human** last-resort convention (so a reviewer can spot a deliberate suppression) — it is not permission for you to add one. If the types genuinely cannot express something, stop and surface the conflict (commit message / MR) rather than suppress it. + +## 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 § 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 (`watch` / pre-commit / pre-push / `check` / `fix` / CI). +- [README.md § Tooling decisions](./README.md#tooling-decisions) — 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). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85bdc03..08f41ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,22 @@ # Contributing -This document is for maintainers and contributors working on the project itself. End-user documentation is in [README.md](./README.md). +This document is for maintainers and contributors working on the project itself. End-user documentation is in [README.md](./README.md). The machine entry point for AI coding agents is [AGENTS.md](./AGENTS.md); keep this file as the prose home for the rules below so agents and humans don't diverge. + +## Rules the tools don't enforce + +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) +- **A new `npm run` script must reuse an existing prefix** (`check:` / `fix:` / `test:` / `watch:` / `publish:`). If none fits, that's a signal the script doesn't belong in the pipeline — not a reason to invent a new prefix. (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 checks in pre-commit.** `check:knip` (~4s) and `check:outdated` (~6.5s, network) are deliberately excluded from the hook to keep it fast and offline. (see [Why these splits](#why-these-splits)) +- **There is no local `npm run publish`, and `publish:publint` / `publish:attw` don't go in `check`.** They validate the publishable artifact (`dist/`) and run only in the CI `publish` job. (see [Publishing workflow](#publishing-workflow)) + +## Commit messages + +Gitmoji subject, imperative mood, 50/72 wrapping. The template is `commit-message-template`; `npm run use:git-commit-message` installs it into `.git/COMMIT_EDITMSG`. + +Examples from history: `:sparkles: Add watch tier with watch:test child`, `:recycle: Move type-aware config to .oxlintrc.json; use source-level disable directives`, `:memo: Restore unique maintainer content as CONTRIBUTING.md`. The body explains _what and why_, not _how_; link issues with `Resolves #...`. ## Script prefix convention diff --git a/README.md b/README.md index 34ef879..2efca56 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Script names follow a prefix convention that signals _when_ they run: ## Contributing -For maintainer and contributor docs — the script prefix convention, the feedback-tier system, and the publishing workflow — see [CONTRIBUTING.md](./CONTRIBUTING.md). +For maintainer and contributor docs — the script prefix convention, the feedback-tier system, the rules the tools don't enforce, and the publishing workflow — see [CONTRIBUTING.md](./CONTRIBUTING.md). AI coding agents: your entry point is [AGENTS.md](./AGENTS.md), which points back to CONTRIBUTING.md. - Commit signing (GPG). - Set up commit message template: `npm run use:git-commit-message`.