From 9ad5d0367af69754df4dc3718b87e5e40cc5ce9c Mon Sep 17 00:00:00 2001 From: Noah Schatz Date: Wed, 16 Sep 2026 21:12:17 +0000 Subject: [PATCH] S0308-submodule-agent-doc-budgets: split CLAUDE.md into documentation/ CLAUDE.md measured 23644 bytes against the 16200-byte ceiling the umbrella budget sets for a submodule agent doc. Three whole sections move, byte for byte and under their existing headings, into documentation/ beside agent-notes.md, which this file already names as its long-form record: documentation/required-checks-on-main.md documentation/engineering-guardrails.md documentation/standing-disciplines.md Each heading stays in CLAUDE.md above a pointer to the file its body moved to, so no heading is renamed and no text is deleted. CLAUDE.md is now 10364 bytes over 145 lines, which keeps both the five required headings and the 300-line ceiling the estate's engineering baseline sets for a package repo's agent doc. Markdown only: no generator, seed, fixture, emitted string or log line is touched, and nothing in the published file set is built from what moved. --- CLAUDE.md | 172 ++--------------------- documentation/engineering-guardrails.md | 59 ++++++++ documentation/required-checks-on-main.md | 49 +++++++ documentation/standing-disciplines.md | 70 +++++++++ 4 files changed, 187 insertions(+), 163 deletions(-) create mode 100644 documentation/engineering-guardrails.md create mode 100644 documentation/required-checks-on-main.md create mode 100644 documentation/standing-disciplines.md diff --git a/CLAUDE.md b/CLAUDE.md index 49225a2..0107aaf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,172 +128,18 @@ a summary. ## Required checks on `main` -One ruleset protects `main`: **`required-checks`, id `19913330`**. **Read the live set back rather -than trusting any list written down** (`gh api "repos/cosyte/synth/rulesets?includes_parents=true"`): -a hardcoded list goes stale and it is prose no test can check. Snapshot + why every context is pinned -to the GitHub Actions app: `notes#the-one-ruleset-that-protects-main`. - -- **Extend that one ruleset in place; never add a second for the next gate.** An unpinned required - context can be satisfied by any actor with write access posting a commit status of that name, - without the workflow ever running. `notes#extend-that-one-ruleset-in-place`. -- **Never require a context before its workflow has completed on `main`**: every future PR goes - PENDING and unmergeable with nothing saying why. `test-selection`, `smoke (22)`/`smoke (24)` and - `install (22)`/`install (24)` are deliberately not required yet; read real names off a live check - run, never off a workflow's `name:`. `notes#contexts-deliberately-not-required-yet`. -- **Never rename a required job, and never split a step out of one**: both silently un-require it - and leave PRs pending rather than red (why `build` and `smoke` are one job in `smoke.yml`). - `notes#things-that-silently-detach-or-hollow-out-a-required-check`. -- **Never narrow `include` in `vitest.config.ts`.** `pnpm test` takes no path arguments, so that one - glob selects everything `ci / verify` runs, and **coverage does not backstop it** (measured over - `src/**` only, so dropping every `synthetic-safety.property.test.ts` costs 0% and reds nothing). - **For a synthetic-data generator that is the whole safety story.** Gated by - `pnpm check:test-selection`. `notes#things-that-silently-detach-or-hollow-out-a-required-check`. -- **Never replace that gate's exact-match script rule with a parser, and never answer a hole in it - with one more spelling**: analysing a shell string is unbounded, and this is the half a refuter - broke **three times** in `ncpdp`, each time in the remedy for the last. Its four deliberate shapes, - and why **self-test C is NOT covered by self-test A**: - `notes#the-test-selection-gate-and-its-four-deliberate-shapes`. -- **READ THE COUNTS OFF THE GATE, NOT OFF PROSE, AND MOVE THEM WHEN YOU ADD A TEST FILE.** - `pnpm check:test-selection` prints watched / name-only / unwatched every run; they have gone stale - **twice**. The numbers, and what the gate does **not** reach: - `notes#know-the-denominator-and-what-the-gate-does-not-reach`. -- **Deleting a subpath from `exports` shrinks that gate's headline subject and it stays green** - (measured). Not a free escape: it is a breaking change, and `scripts/smoke.mjs` derives its subpath - set from the same map and **refuses** on disagreement. Two gates on one map, on purpose. - `notes#narrowing-the-published-surface`. -- **Never require a workflow with no `pull_request` trigger** (`fuzz`, `scorecard`, `release`): it - strands every PR forever. `notes#requiring-a-workflow-with-no-pull_request-trigger`. -- **RE-MEASURE EVERY PORTED SENTENCE AGAINST THIS REPO: the one failure that repeated.** Two of - three refuter passes found a false number or a false claim of reach carried over from `ncpdp`/`deid` - rather than a hole in the mechanism. **Where a number is asserted, name what produced it; where - reach is asserted, bound it to a route actually seeded.** - `notes#re-measure-every-ported-sentence-against-this-repo`. -- **Nothing inside this repository can observe its own ruleset.** Delete it and every test still - passes, every gate still prints OK, and this file still says `main` is protected. A ruleset makes a - red check block a merge; it does not make the check correct. - `notes#nothing-inside-this-repository-can-observe-its-own-ruleset`. +Moved whole and unchanged to +[documentation/required-checks-on-main.md](documentation/required-checks-on-main.md): read it +before you touch a workflow, a job name or a required context. ## Engineering Guardrails -- No `any`. No unjustified `as` casts. Use `unknown` and narrow. -- JSDoc (with `@example`) on every public export: the JSDoc lint rule is an **error** on public - exports, so this is enforced, not optional. -- Immutable by default. Mutation only via explicit methods. -- No `console.*` in library code. Throw typed errors or return results. -- Short, testable functions over big parsing blobs. -- Postel's Law: parser is liberal (lenient default + warnings), serializer is conservative (always - emits spec-clean output). -- Fatal errors only for unrecoverable structural corruption (Tier-3 codes). Everything else is a - warning with a stable code + positional context. -- **No diagnostic takes a value parameter.** `SynthError(code)` is the only error `src/` constructs - and its message is a fixed entry in the frozen `SYNTH_FATAL_MESSAGES` table, so a caller string has - no position through which to reach `message`, `stack` or a thrown field. **Adding a fatal means - adding a registry entry, never a template string.** `notes#no-diagnostic-takes-a-value-parameter`. -- **Resolve every caller-supplied selector against its closed set** (`resolveKind`/`resolveMix`, - `src/select.ts`) at the entry point, before anything is generated. **Not a style rule**: an - unresolved selector reached an `err.message`/`err.stack` via a peer builder's `TypeError`, became an - `Artifact.kind` and a `manifest.counts` key, and fell out of an exhaustive `switch` as `undefined`. - All three were live. `notes#resolve-every-caller-supplied-selector-against-its-closed-set`. -- **Never claim `SynthError` being the only constructor is the whole guarantee**: `src/` can still - surface a runtime `TypeError` or a peer parser's fatal on forwarded input. Harnesses keep - `String(w.code)`, never a `message` or snippet; `test/phi/diagnostic-surface.test.ts` carries the - proof. `notes#syntherror-being-the-only-constructor-is-not-the-whole-guarantee`. -- **Read the meta-repo's `documentation/repos/phi-audit.md` before touching that.** **A new safety - sentence is worth nothing without a slot in that table behind it.** - `notes#the-phi-free-diagnostics-claim`. -- **▶ `attw` SAYS "does not contain types" AND EXITS 0, SO THE `attw` SCRIPT IS A WRAPPER - (`node scripts/attw.mjs --profile node16`), NOT THE BARE CLI.** For a package that ships types that - sentence means the declarations were **not in the tarball** (a broken publish reported as a pass), - and no `--profile`, `--ignore-rules` or config setting reaches that early return. **The race only - supplies the condition**, so the answer is not a lock, a lease or a build queue: the gate must be - able to say its own inputs were missing, whatever removed them. `notes#the-attw-false-green-and-why-the-script-is-a-wrapper`. -- **Do NOT re-derive the exit-0 condition from the shape of the code: three refuter passes corrected - it, each in the same direction, and "attw misses subpaths" is the plausible, wrong story.** The - residual is a **known limit, filed rather than fixed; weaken the sentence rather than adding a - fifth arm.** All three corrections, each with its measurement, and why a correct red explained by a - falsehood is worse than it looks: `notes#the-false-green-needs-every-entry-point-untyped-at-once`, - `notes#the-first-correction-was-also-wrong-in-the-same-direction`, - `notes#missing-is-a-proxy-not-the-key-a-known-limit`. -- **`scripts/attw.mjs` carries TWO nets that catch different things** (a preflight over the relative - paths `package.json` promises, and a post-check on the untyped sentence), both pinned by - `test/scripts/attw-gate.test.ts` against the real binary plus a negative control. - `notes#the-two-nets-in-scriptsattwmjs`. -- **The post-check reads a string, so the argument guard is an ALLOW-LIST, NOT A DENY-LIST**: - `--profile` and `--no-definitely-typed` forwarded, everything else refused, "harmless" included. - **Do not answer a hole in it with a seventh spelling.** The six measured routes, why a deny-list - could not hold, and why the `.attw.json` refusal stays separate: - `notes#the-argument-guard-is-an-allow-list-not-a-deny-list`. -- **The seven `file:vendor/*.tgz` devDeps are NOT part of the `attw` story**, `npm pack` emits no - `vendor/` and attw does not resolve bare external specifiers. A **stale** vendored tarball makes - this gate neither red nor green; a **missing** one is a different thing this sentence does not - cover. `notes#the-vendored-tarballs-are-not-part-of-the-attw-story`. -- Coverage: per-directory >= 90% (lines/branches/functions/statements), enforced by - `pnpm test:coverage`. +Moved whole and unchanged to +[documentation/engineering-guardrails.md](documentation/engineering-guardrails.md): read it before +you touch the thing a guardrail guards. ## Standing disciplines (every change) -Mirrors the three disciplines in the meta-repo's `documentation/conventions.md`, and they bind here too: - -1. **Documentation follows code**: a change to the public surface/stack/status isn't done until the - docs are: this repo's docs content (`README.md`, `docs-content/`), the meta-repo - `documentation/repos/synth.md` (bump its "last verified" date), and the `ecosystem-map.md` - status table. -2. **Version + changelog**: a Changeset (`patch` on the `0.0.x` ladder) per meaningful change. - **The changeset summary IS the changelog entry**, and `CHANGELOG.md` is generated output above - `## Released before this file was generated`: `.changeset/config.json` names a `changelog` - generator, so the release writes the version heading and the entry itself. **Do not hand-edit - `CHANGELOG.md`, and never reintroduce a hand-maintained `[Unreleased]` heading**: one stood over - this package's whole published history and shipped a tarball calling its own contents unreleased; - with generation on, a release prepends ABOVE such a heading, which is worse. Only the H1 may sit - above the first heading, and Changesets' Prettier pass stays ON here (derived, not copied): - `notes#the-changelog-is-generated-by-the-release`. Gated by - `test/scripts/changelog-generation.test.ts`. Renaming a stable warning code is a **breaking - change**. -3. **Crew + knowledgebase loop**, if this parser's public API or warning codes change, flag/update - the matching `crew` healthcare skill + the KB product doc. -4. **No internal project bookkeeping on a public surface** (founder directive, 2026-07-27). What a - consumer reads (`README.md`, `docs-content/`, the npm `description`, a release body) says what the - software does and what changed; identifiers, phase/wave language, ADR numbers, meta-repo paths and - `roadmap §N` citations belong in the changeset, `CHANGELOG.md`, the commit, the PR and the - roadmap. It is a **translation** at the boundary, not a deletion: **repair the head of a line you - strip an identifier from**. Gated by `pnpm check:no-internal-refs`, which keys on known prefixes - (**a new programme prefix must be added by hand**) and catches identifiers, not English sentences - about our process, so the reviewer still owns half the rule. - `notes#no-internal-project-bookkeeping-on-a-public-surface`. - - **`SYNTH` is BOTH our item prefix AND the marker this package writes into what it generates** - (`SYNTH-FAC`, `SYNTH-LIS`, `SYNTH-ANALYZER`, the `SYNTH-`+10-digit NCPDP message id). The - separation is the explicit `SYNTHETIC_FIXTURE_TOKEN` list in the gate, never a shape rule. - **Never re-key the identifier rule on the `WORD-N` shape**, and never "resync" the prefix list - with a sibling's copy without re-reading why `SYNTH` is PRESENT here and ABSENT from `ncpdp`'s. - `notes#synth-is-both-our-item-prefix-and-a-marker-in-generated-output`. - - **Four surfaces, three different answers.** Browsable Markdown, npm metadata and `/** */` doc - comments are **gated**; string literals are **gated too, and here they matter most: they are the - bytes this package emits**; `//` and plain `/* */` comments are **not gated and identifiers are - welcome in them**, because the convention says source comments are a place identifiers belong. - **Do not justify this boundary from what reaches `dist/`**: effectively all of `src/` does, and - two attempts to argue it that way in a sibling repo were false and both caught by a refuter. The - line is not what reaches a consumer's disk but what a consumer is **shown**. - `notes#four-surfaces-three-different-answers`. - - Two consequences: **removing a doc comment to satisfy the gate is a regression**, not a fix; and - **when a stale claim is what carries the phase number, cut the sentence rather than reword it**: - rewording leaves a falsehood standing in cleaner clothes. The gate cannot read `dist/`, so it - gates the source of the published text, not the published text. - `notes#two-consequences-doc-comments-and-stale-phase-claims`. - -5. **No em dash, anywhere** (founder directive, 2026-07-24), **including commit messages**. Gated by - `pnpm check:no-emdash` over every tracked file, its name, the gate script, and on a PR the title, - body and commit messages. Rewrite with a comma, colon, period or **parentheses**; **never - re-encode it**. Landed 2026-08-06 with the sweep (1,167 rewrites over 135 files). - Full text and the sweep's own traps: `notes#no-em-dash-anywhere`. Two that gate what you change: - - **The job is deliberately NOT a required context**: it reads the PR body, and Dependabot pastes - the dependency's upstream release notes there. **Keep the exemption and its written reason, and - never answer it with an actor `if:` on a required context** (that leaves the check PENDING, not - red). **PR text is also the half no local hook can see, so check your own before you push.** - - **Do not simplify two deliberate shapes.** The binary partition is a **declaration** - (`git check-attr binary`, refused outside `vendor/`), never a NUL test and never `grep -I`, - because a real UTF-8 source here holds a NUL and carried 14. And `CHANGELOG.md` is scanned - **above** `## Released before this file was generated` rather than excluded, the archive below - being a dated ship-log (128 deliberate survivors, and it fails closed). +Moved whole and unchanged to +[documentation/standing-disciplines.md](documentation/standing-disciplines.md): read it before you +open a change. diff --git a/documentation/engineering-guardrails.md b/documentation/engineering-guardrails.md new file mode 100644 index 0000000..e2c9b1b --- /dev/null +++ b/documentation/engineering-guardrails.md @@ -0,0 +1,59 @@ +Split out of `CLAUDE.md` to keep that file inside its byte budget. The text below is unchanged. +A pointer written `notes#
` names a section of `agent-notes.md` beside this file. + +## Engineering Guardrails + +- No `any`. No unjustified `as` casts. Use `unknown` and narrow. +- JSDoc (with `@example`) on every public export: the JSDoc lint rule is an **error** on public + exports, so this is enforced, not optional. +- Immutable by default. Mutation only via explicit methods. +- No `console.*` in library code. Throw typed errors or return results. +- Short, testable functions over big parsing blobs. +- Postel's Law: parser is liberal (lenient default + warnings), serializer is conservative (always + emits spec-clean output). +- Fatal errors only for unrecoverable structural corruption (Tier-3 codes). Everything else is a + warning with a stable code + positional context. +- **No diagnostic takes a value parameter.** `SynthError(code)` is the only error `src/` constructs + and its message is a fixed entry in the frozen `SYNTH_FATAL_MESSAGES` table, so a caller string has + no position through which to reach `message`, `stack` or a thrown field. **Adding a fatal means + adding a registry entry, never a template string.** `notes#no-diagnostic-takes-a-value-parameter`. +- **Resolve every caller-supplied selector against its closed set** (`resolveKind`/`resolveMix`, + `src/select.ts`) at the entry point, before anything is generated. **Not a style rule**: an + unresolved selector reached an `err.message`/`err.stack` via a peer builder's `TypeError`, became an + `Artifact.kind` and a `manifest.counts` key, and fell out of an exhaustive `switch` as `undefined`. + All three were live. `notes#resolve-every-caller-supplied-selector-against-its-closed-set`. +- **Never claim `SynthError` being the only constructor is the whole guarantee**: `src/` can still + surface a runtime `TypeError` or a peer parser's fatal on forwarded input. Harnesses keep + `String(w.code)`, never a `message` or snippet; `test/phi/diagnostic-surface.test.ts` carries the + proof. `notes#syntherror-being-the-only-constructor-is-not-the-whole-guarantee`. +- **Read the meta-repo's `documentation/repos/phi-audit.md` before touching that.** **A new safety + sentence is worth nothing without a slot in that table behind it.** + `notes#the-phi-free-diagnostics-claim`. +- **▶ `attw` SAYS "does not contain types" AND EXITS 0, SO THE `attw` SCRIPT IS A WRAPPER + (`node scripts/attw.mjs --profile node16`), NOT THE BARE CLI.** For a package that ships types that + sentence means the declarations were **not in the tarball** (a broken publish reported as a pass), + and no `--profile`, `--ignore-rules` or config setting reaches that early return. **The race only + supplies the condition**, so the answer is not a lock, a lease or a build queue: the gate must be + able to say its own inputs were missing, whatever removed them. `notes#the-attw-false-green-and-why-the-script-is-a-wrapper`. +- **Do NOT re-derive the exit-0 condition from the shape of the code: three refuter passes corrected + it, each in the same direction, and "attw misses subpaths" is the plausible, wrong story.** The + residual is a **known limit, filed rather than fixed; weaken the sentence rather than adding a + fifth arm.** All three corrections, each with its measurement, and why a correct red explained by a + falsehood is worse than it looks: `notes#the-false-green-needs-every-entry-point-untyped-at-once`, + `notes#the-first-correction-was-also-wrong-in-the-same-direction`, + `notes#missing-is-a-proxy-not-the-key-a-known-limit`. +- **`scripts/attw.mjs` carries TWO nets that catch different things** (a preflight over the relative + paths `package.json` promises, and a post-check on the untyped sentence), both pinned by + `test/scripts/attw-gate.test.ts` against the real binary plus a negative control. + `notes#the-two-nets-in-scriptsattwmjs`. +- **The post-check reads a string, so the argument guard is an ALLOW-LIST, NOT A DENY-LIST**: + `--profile` and `--no-definitely-typed` forwarded, everything else refused, "harmless" included. + **Do not answer a hole in it with a seventh spelling.** The six measured routes, why a deny-list + could not hold, and why the `.attw.json` refusal stays separate: + `notes#the-argument-guard-is-an-allow-list-not-a-deny-list`. +- **The seven `file:vendor/*.tgz` devDeps are NOT part of the `attw` story**, `npm pack` emits no + `vendor/` and attw does not resolve bare external specifiers. A **stale** vendored tarball makes + this gate neither red nor green; a **missing** one is a different thing this sentence does not + cover. `notes#the-vendored-tarballs-are-not-part-of-the-attw-story`. +- Coverage: per-directory >= 90% (lines/branches/functions/statements), enforced by + `pnpm test:coverage`. diff --git a/documentation/required-checks-on-main.md b/documentation/required-checks-on-main.md new file mode 100644 index 0000000..051b873 --- /dev/null +++ b/documentation/required-checks-on-main.md @@ -0,0 +1,49 @@ +Split out of `CLAUDE.md` to keep that file inside its byte budget. The text below is unchanged. +A pointer written `notes#
` names a section of `agent-notes.md` beside this file. + +## Required checks on `main` + +One ruleset protects `main`: **`required-checks`, id `19913330`**. **Read the live set back rather +than trusting any list written down** (`gh api "repos/cosyte/synth/rulesets?includes_parents=true"`): +a hardcoded list goes stale and it is prose no test can check. Snapshot + why every context is pinned +to the GitHub Actions app: `notes#the-one-ruleset-that-protects-main`. + +- **Extend that one ruleset in place; never add a second for the next gate.** An unpinned required + context can be satisfied by any actor with write access posting a commit status of that name, + without the workflow ever running. `notes#extend-that-one-ruleset-in-place`. +- **Never require a context before its workflow has completed on `main`**: every future PR goes + PENDING and unmergeable with nothing saying why. `test-selection`, `smoke (22)`/`smoke (24)` and + `install (22)`/`install (24)` are deliberately not required yet; read real names off a live check + run, never off a workflow's `name:`. `notes#contexts-deliberately-not-required-yet`. +- **Never rename a required job, and never split a step out of one**: both silently un-require it + and leave PRs pending rather than red (why `build` and `smoke` are one job in `smoke.yml`). + `notes#things-that-silently-detach-or-hollow-out-a-required-check`. +- **Never narrow `include` in `vitest.config.ts`.** `pnpm test` takes no path arguments, so that one + glob selects everything `ci / verify` runs, and **coverage does not backstop it** (measured over + `src/**` only, so dropping every `synthetic-safety.property.test.ts` costs 0% and reds nothing). + **For a synthetic-data generator that is the whole safety story.** Gated by + `pnpm check:test-selection`. `notes#things-that-silently-detach-or-hollow-out-a-required-check`. +- **Never replace that gate's exact-match script rule with a parser, and never answer a hole in it + with one more spelling**: analysing a shell string is unbounded, and this is the half a refuter + broke **three times** in `ncpdp`, each time in the remedy for the last. Its four deliberate shapes, + and why **self-test C is NOT covered by self-test A**: + `notes#the-test-selection-gate-and-its-four-deliberate-shapes`. +- **READ THE COUNTS OFF THE GATE, NOT OFF PROSE, AND MOVE THEM WHEN YOU ADD A TEST FILE.** + `pnpm check:test-selection` prints watched / name-only / unwatched every run; they have gone stale + **twice**. The numbers, and what the gate does **not** reach: + `notes#know-the-denominator-and-what-the-gate-does-not-reach`. +- **Deleting a subpath from `exports` shrinks that gate's headline subject and it stays green** + (measured). Not a free escape: it is a breaking change, and `scripts/smoke.mjs` derives its subpath + set from the same map and **refuses** on disagreement. Two gates on one map, on purpose. + `notes#narrowing-the-published-surface`. +- **Never require a workflow with no `pull_request` trigger** (`fuzz`, `scorecard`, `release`): it + strands every PR forever. `notes#requiring-a-workflow-with-no-pull_request-trigger`. +- **RE-MEASURE EVERY PORTED SENTENCE AGAINST THIS REPO: the one failure that repeated.** Two of + three refuter passes found a false number or a false claim of reach carried over from `ncpdp`/`deid` + rather than a hole in the mechanism. **Where a number is asserted, name what produced it; where + reach is asserted, bound it to a route actually seeded.** + `notes#re-measure-every-ported-sentence-against-this-repo`. +- **Nothing inside this repository can observe its own ruleset.** Delete it and every test still + passes, every gate still prints OK, and this file still says `main` is protected. A ruleset makes a + red check block a merge; it does not make the check correct. + `notes#nothing-inside-this-repository-can-observe-its-own-ruleset`. diff --git a/documentation/standing-disciplines.md b/documentation/standing-disciplines.md new file mode 100644 index 0000000..1af360d --- /dev/null +++ b/documentation/standing-disciplines.md @@ -0,0 +1,70 @@ +Split out of `CLAUDE.md` to keep that file inside its byte budget. The text below is unchanged. +A pointer written `notes#
` names a section of `agent-notes.md` beside this file. + +## Standing disciplines (every change) + +Mirrors the three disciplines in the meta-repo's `documentation/conventions.md`, and they bind here too: + +1. **Documentation follows code**: a change to the public surface/stack/status isn't done until the + docs are: this repo's docs content (`README.md`, `docs-content/`), the meta-repo + `documentation/repos/synth.md` (bump its "last verified" date), and the `ecosystem-map.md` + status table. +2. **Version + changelog**: a Changeset (`patch` on the `0.0.x` ladder) per meaningful change. + **The changeset summary IS the changelog entry**, and `CHANGELOG.md` is generated output above + `## Released before this file was generated`: `.changeset/config.json` names a `changelog` + generator, so the release writes the version heading and the entry itself. **Do not hand-edit + `CHANGELOG.md`, and never reintroduce a hand-maintained `[Unreleased]` heading**: one stood over + this package's whole published history and shipped a tarball calling its own contents unreleased; + with generation on, a release prepends ABOVE such a heading, which is worse. Only the H1 may sit + above the first heading, and Changesets' Prettier pass stays ON here (derived, not copied): + `notes#the-changelog-is-generated-by-the-release`. Gated by + `test/scripts/changelog-generation.test.ts`. Renaming a stable warning code is a **breaking + change**. +3. **Crew + knowledgebase loop**, if this parser's public API or warning codes change, flag/update + the matching `crew` healthcare skill + the KB product doc. +4. **No internal project bookkeeping on a public surface** (founder directive, 2026-07-27). What a + consumer reads (`README.md`, `docs-content/`, the npm `description`, a release body) says what the + software does and what changed; identifiers, phase/wave language, ADR numbers, meta-repo paths and + `roadmap §N` citations belong in the changeset, `CHANGELOG.md`, the commit, the PR and the + roadmap. It is a **translation** at the boundary, not a deletion: **repair the head of a line you + strip an identifier from**. Gated by `pnpm check:no-internal-refs`, which keys on known prefixes + (**a new programme prefix must be added by hand**) and catches identifiers, not English sentences + about our process, so the reviewer still owns half the rule. + `notes#no-internal-project-bookkeeping-on-a-public-surface`. + + **`SYNTH` is BOTH our item prefix AND the marker this package writes into what it generates** + (`SYNTH-FAC`, `SYNTH-LIS`, `SYNTH-ANALYZER`, the `SYNTH-`+10-digit NCPDP message id). The + separation is the explicit `SYNTHETIC_FIXTURE_TOKEN` list in the gate, never a shape rule. + **Never re-key the identifier rule on the `WORD-N` shape**, and never "resync" the prefix list + with a sibling's copy without re-reading why `SYNTH` is PRESENT here and ABSENT from `ncpdp`'s. + `notes#synth-is-both-our-item-prefix-and-a-marker-in-generated-output`. + + **Four surfaces, three different answers.** Browsable Markdown, npm metadata and `/** */` doc + comments are **gated**; string literals are **gated too, and here they matter most: they are the + bytes this package emits**; `//` and plain `/* */` comments are **not gated and identifiers are + welcome in them**, because the convention says source comments are a place identifiers belong. + **Do not justify this boundary from what reaches `dist/`**: effectively all of `src/` does, and + two attempts to argue it that way in a sibling repo were false and both caught by a refuter. The + line is not what reaches a consumer's disk but what a consumer is **shown**. + `notes#four-surfaces-three-different-answers`. + + Two consequences: **removing a doc comment to satisfy the gate is a regression**, not a fix; and + **when a stale claim is what carries the phase number, cut the sentence rather than reword it**: + rewording leaves a falsehood standing in cleaner clothes. The gate cannot read `dist/`, so it + gates the source of the published text, not the published text. + `notes#two-consequences-doc-comments-and-stale-phase-claims`. + +5. **No em dash, anywhere** (founder directive, 2026-07-24), **including commit messages**. Gated by + `pnpm check:no-emdash` over every tracked file, its name, the gate script, and on a PR the title, + body and commit messages. Rewrite with a comma, colon, period or **parentheses**; **never + re-encode it**. Landed 2026-08-06 with the sweep (1,167 rewrites over 135 files). + Full text and the sweep's own traps: `notes#no-em-dash-anywhere`. Two that gate what you change: + - **The job is deliberately NOT a required context**: it reads the PR body, and Dependabot pastes + the dependency's upstream release notes there. **Keep the exemption and its written reason, and + never answer it with an actor `if:` on a required context** (that leaves the check PENDING, not + red). **PR text is also the half no local hook can see, so check your own before you push.** + - **Do not simplify two deliberate shapes.** The binary partition is a **declaration** + (`git check-attr binary`, refused outside `vendor/`), never a NUL test and never `grep -I`, + because a real UTF-8 source here holds a NUL and carried 14. And `CHANGELOG.md` is scanned + **above** `## Released before this file was generated` rather than excluded, the archive below + being a dated ship-log (128 deliberate survivors, and it fails closed).