diff --git a/AGENTS.md b/AGENTS.md index 45d6afd1..cdfb345a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,32 +22,10 @@ Three committed contributor docs carry the parser's normative rules and their re **Release-log claims.** Quantified or universal behavior claims in release bullets must come from the differential gate's classified summary or be verified against rules.md examples, never written from memory. Per-rule ledger toml comments asserting PARSER behavior cite rule IDs under the excerpt discipline; free prose is for ledger mechanics only (owned by tools/differential/README.md). -**Landing a design.** The gitignored spec (docs/superpowers/specs/) is the working medium; it dies with the branch, and the docs are the record. Before a design PR merges, walk its spec (including amendments) and distill the durable residue: decisions made or reversed → decisions.md entries; proposals rejected with evidence → Declined:; vocabulary that must stay out → Excluded:; behavior the design settled → rules.md (with a deviates: marker if unshipped); reusable patterns → mechanisms.md; options weighed → a weighing entry. Then check the spec cites nothing the docs don't now carry — a spec section with no committed home when the PR merges is lost, not deferred (a 2026-08-16 sweep of eight weeks of specs recovered nine such items). The same-PR amendment rule above covers code-driven changes; this covers the design-driven ones. +**Working on docs/design/ has its own AGENTS.md.** `docs/design/AGENTS.md` carries the landing-a-design distillation checklist, the primary-source review rule, the dated-count convention, and the nine review axes. Claude Code loads it automatically when a session reads or edits anything under docs/design/; if your tool does not do nested discovery, read it yourself before touching those files or reviewing a change to them. **Triaging a design backlog: shape before value.** When a pile of open questions has to be ordered, relatedness will not partition it — in a parser nearly everything touches particles or suffixes. The line that does is whether a decision changes WHAT THE MODEL CAN EXPRESS or fills in a value in a shape already fixed. Settle the shape questions first, as a batch: they are few, they are usually independent of each other (so their own ordering does not matter), and each one collapses or re-frames a run of the value questions below it. The rest are leaves needing one measurement and one answer apiece, in any order. Worked example, 2026-08-16 (#386): 30 open issues and ~13 open design questions reduced to three shape decisions, which decided or re-framed fourteen of them — and two of the three turned out smaller than their issues claimed, because the issues had gone stale. Check the age of an issue's premises before believing its cost estimate. -**A count in a dated entry is evidence, not a live fact.** decisions.md entries are snapshots by convention, so measurements belong in them — but a reader wanting TODAY's number must not have to trust the snapshot's date. Where an entry quotes something that drifts (vocabulary sizes, corpus counts, set compositions), give the one-liner that recomputes it, and phrase the argument so it survives the digits moving — "the two shares differ by orders of magnitude" outlives "58% vs 0.65%". A count that carries no argument is better deleted than dated: "over every name in the corpus, no prefilter" says what "over all 782 names" says, and cannot go stale. Do NOT reach for a test asserting the count — that is the constant-content pattern, and it fails on every legitimate vocabulary addition. #326 is the cautionary case: it quoted a vocabulary composition, carried a date, and was stale in five days. - -**Primary-source review.** When doc content is distilled from a session's work, have that session (or its transcript) review its own sections before or soon after landing — attribution flattening and inverted arguments are visible only to the source. Reviewers state which tree each measurement ran on (stale fetches produced three rounds of already-fixed findings), and landed corrections are re-verified here before committing. - -Two shapes, needing different instruments. CROSS-SESSION distillation is what the rule above is written for — the #381 arc harvested nine named sessions, and for each the source transcript is an artifact independent of the reviewing session, so "review your own sections" is a two-artifact comparison. SAME-SESSION design work (#386) has no such artifact: spec and docs were written in one context, so the same instruction degenerates into re-reading the working memory that produced any error, and the rationale inverts — when the source is NOW, the source is what got it wrong. There, compare against artifacts instead, in this measured order of yield: re-derive every number with a freshly written script (2 wrong claims on #386); re-read the written spec against a checklist for FIDELITY, not just presence (1); interrogate NAMED contested rule pairs one at a time (2). Unaided prose reading found none — so a general "coherence read" by the author is the one form to distrust, because a green report from the weakest instrument manufactures confidence rather than supplying it. - -**Reviewing a docs/design change: the nine axes.** Run these before merging anything that touches rules.md, decisions.md, mechanisms.md — or THIS FILE, which carries the conventions those three rest on and has the same failure modes. Axes 3, 5 and 6 are rules.md-specific (they need rule statements, examples and `interacts:`); the other six apply to any of the four. Say which axes you ran and which you skipped as inapplicable — a checklist that asks for something meaningless teaches the reader to skim it. Each was earned by a defect that survived every other check on #386, where the review passes surfaced ten issues in total — nine of them by these axes, one by the spec self-review above — and unaided prose reading surfaced none. The parenthetical is the case that earned the axis. - -Skip what the suite already enforces. `tests/v2/test_rules_doc.py` and `tests/v2/test_doc_citations.py` machine-check example lines and their `deviates:` today-values, boundaries, the one-of `implemented:`/`tracked:` rule, `implemented:` against the modules that actually cite the rule, `interacts:` and cross-doc anchor IDs, citation excerpts quoted verbatim, and legacy citation forms. Assume they pass; a finding one of them would catch is a false positive. Read those two files rather than trusting this sentence — it named five of the nine guards when written, and goes stale as guards are added. - -1. **Recompute every number.** Any count, ratio or "N names" in the diff gets recomputed by a script written now, never read. (A filter described as returning one name returned three; a rule's stated blast radius listed two names its own scope excluded.) -2. **Your detector is a second, unreviewed implementation.** It must read the same vocabulary and boundaries the rule reads. (A particle-run detector that walked only the never-given half — the rule chains through ANY particle — reported 50 false movers where the true count was 1, printing cleanly both times.) -3. **Rule vs. its own examples.** (P1 said the fold takes "the particle and the one name word"; its example `de la Vega` is two particles onto one word.) -4. **Any claim about a document vs. what that document says about itself.** Two directions, both bitten. Inward: follow every `history:` and read the entry (P6's rationale asserted what decisions.md#vocabulary-collisions denies, and the rule's own guard exists because the assertion is false). Outward: when prose anywhere — this file, a code comment, another design doc — describes what one of these files contains or does, read that file's preamble or entry conventions first (an AGENTS.md bullet claimed rules.md names the pipeline stages; its preamble forbids exactly that — "Statements are implementation-free: no stage names, no function names, no regexes"). Those declarations sit at the top of files people cite without reading. Mind their scope before calling a violation: rules.md's ban binds STATEMENTS, which `_statements()` in test_doc_citations.py defines as rule-block text up to the first example, so Backgrounds and Not-in-scope may name functions and do. -5. **Does the change move another rule's OUTPUT?** A marker lands on the rule whose STATEMENT changed; a rule can move another's output without touching its statement, and the runner cannot see it. Walk `interacts:`. (family_base moved initials(); R3 had no marker.) -6. **Contested inputs: is precedence stated?** `interacts:` is advisory and pins nothing. This document states precedence in the rule's own statement (H2, M1, W3). (P6 and S2 both claimed `Berg, Jan vd`.) -7. **General clause vs. adjudicated scope.** Enumerate the vocabulary a shape-clause reaches. (P6's precedence was argued for `vd` and swept in `do` and `mc`.) -8. **Guard docstring vs. what the guard enforces**, especially when the promise spans two test modules. -9. **Prose is input to the doc parsers.** A line starting with `"` inside a rule block is an example; a comment's quoted values join the citation block above them. - -Also: before reporting "N names move", report the size of the population that COULD move — a small count over a corpus blind to the shape is evidence about the corpus. For rules.md#P6: of 782 corpus names, 245 carry a comma, two of those end in a particle, and one of the two clears the words-to-spare guard. Running axis 1 over this very list caught an earlier wording of that sentence conflating the population with the movers, so the count above is the corrected one. - **Markdown line width: don't hard-wrap prose.** Measured 2026-08-16 — wrapped and unwrapped render identically, docs/design/ is not in the Sphinx build, and unwrapping is byte-identical (newline→space is 1:1), so the width buys nothing and costs diff churn (74% of #386's decisions.md deletions were reflow, not change). AGENTS.md and docs/design/ were unwrapped in one whitespace-only commit on 2026-08-16; keep new prose unwrapped rather than reformatting again. The blame cost was 43 lines of 1717, measured, not the wholesale reset predicted before doing it — git's diff maps most joined lines back to whichever commit wrote their opening text. `.git-blame-ignore-revs` lists the commit and is worth enabling (`git config blame.ignoreRevsFile .git-blame-ignore-revs`) for future format-only commits, but it recovered none of those 43: `--ignore-revs-file` cannot pass blame through a commit that MERGES lines, because the joined line has no counterpart in the parent to re-attribute to. Exceptions, both enforced: rules.md's example and pointer lines are parsed per line, so a wrapped example is a hard error naming its rule; and rules.md's rule BODIES stay wrapped because a rule block is a record format, aligning 4-space statement prose above 6-space examples, not a paragraph. **Guard tests** SHOULD carry a recorded negative control — the answer with the guard off, stored as data (the _EXCLUSION_EFFECT shape; see mechanisms.md's Verification shapes). diff --git a/docs/design/AGENTS.md b/docs/design/AGENTS.md new file mode 100644 index 00000000..5f24b6c9 --- /dev/null +++ b/docs/design/AGENTS.md @@ -0,0 +1,29 @@ +# Working on docs/design/ + +Loaded when a session touches these documents; the root AGENTS.md carries what fires from elsewhere (the same-PR amendment rule, prose counting claims, release-log claims, backlog triage, markdown line width, guard-test negative controls). + +rules.md states intended parsing behavior and is NORMATIVE; decisions.md is the dated ADR log behind it; mechanisms.md catalogs reusable patterns and verification shapes. Each declares its own conventions at the top — read those before describing what one of them contains (axis 4). + +**Landing a design.** The gitignored spec (docs/superpowers/specs/) is the working medium; it dies with the branch, and the docs are the record. Before a design PR merges, walk its spec (including amendments) and distill the durable residue: decisions made or reversed → decisions.md entries; proposals rejected with evidence → Declined:; vocabulary that must stay out → Excluded:; behavior the design settled → rules.md (with a deviates: marker if unshipped); reusable patterns → mechanisms.md; options weighed → a weighing entry. Then check the spec cites nothing the docs don't now carry — a spec section with no committed home when the PR merges is lost, not deferred (a 2026-08-16 sweep of eight weeks of specs recovered nine such items). The same-PR amendment rule above covers code-driven changes; this covers the design-driven ones. + +**A count in a dated entry is evidence, not a live fact.** decisions.md entries are snapshots by convention, so measurements belong in them — but a reader wanting TODAY's number must not have to trust the snapshot's date. Where an entry quotes something that drifts (vocabulary sizes, corpus counts, set compositions), give the one-liner that recomputes it, and phrase the argument so it survives the digits moving — "the two shares differ by orders of magnitude" outlives "58% vs 0.65%". A count that carries no argument is better deleted than dated: "over every name in the corpus, no prefilter" says what "over all 782 names" says, and cannot go stale. Do NOT reach for a test asserting the count — that is the constant-content pattern, and it fails on every legitimate vocabulary addition. #326 is the cautionary case: it quoted a vocabulary composition, carried a date, and was stale in five days. + +**Primary-source review.** When doc content is distilled from a session's work, have that session (or its transcript) review its own sections before or soon after landing — attribution flattening and inverted arguments are visible only to the source. Reviewers state which tree each measurement ran on (stale fetches produced three rounds of already-fixed findings), and landed corrections are re-verified here before committing. + +Two shapes, needing different instruments. CROSS-SESSION distillation is what the rule above is written for — the #381 arc harvested nine named sessions, and for each the source transcript is an artifact independent of the reviewing session, so "review your own sections" is a two-artifact comparison. SAME-SESSION design work (#386) has no such artifact: spec and docs were written in one context, so the same instruction degenerates into re-reading the working memory that produced any error, and the rationale inverts — when the source is NOW, the source is what got it wrong. There, compare against artifacts instead, in this measured order of yield: re-derive every number with a freshly written script (2 wrong claims on #386); re-read the written spec against a checklist for FIDELITY, not just presence (1); interrogate NAMED contested rule pairs one at a time (2). Unaided prose reading found none — so a general "coherence read" by the author is the one form to distrust, because a green report from the weakest instrument manufactures confidence rather than supplying it. + +**Reviewing a docs/design change: the nine axes.** Run these before merging anything that touches rules.md, decisions.md, mechanisms.md — or THIS FILE, which carries the conventions those three rest on and has the same failure modes. Axes 3, 5 and 6 are rules.md-specific (they need rule statements, examples and `interacts:`); the other six apply to any of the four. Say which axes you ran and which you skipped as inapplicable — a checklist that asks for something meaningless teaches the reader to skim it. Each was earned by a defect that survived every other check on #386, where the review passes surfaced ten issues in total — nine of them by these axes, one by the spec self-review above — and unaided prose reading surfaced none. The parenthetical is the case that earned the axis. + +Skip what the suite already enforces. `tests/v2/test_rules_doc.py` and `tests/v2/test_doc_citations.py` machine-check example lines and their `deviates:` today-values, boundaries, the one-of `implemented:`/`tracked:` rule, `implemented:` against the modules that actually cite the rule, `interacts:` and cross-doc anchor IDs, citation excerpts quoted verbatim, and legacy citation forms. Assume they pass; a finding one of them would catch is a false positive. Read those two files rather than trusting this sentence — it named five of the nine guards when written, and goes stale as guards are added. + +1. **Recompute every number.** Any count, ratio or "N names" in the diff gets recomputed by a script written now, never read. (A filter described as returning one name returned three; a rule's stated blast radius listed two names its own scope excluded.) +2. **Your detector is a second, unreviewed implementation.** It must read the same vocabulary and boundaries the rule reads. (A particle-run detector that walked only the never-given half — the rule chains through ANY particle — reported 50 false movers where the true count was 1, printing cleanly both times.) +3. **Rule vs. its own examples.** (P1 said the fold takes "the particle and the one name word"; its example `de la Vega` is two particles onto one word.) +4. **Any claim about a document vs. what that document says about itself.** Two directions, both bitten. Inward: follow every `history:` and read the entry (P6's rationale asserted what decisions.md#vocabulary-collisions denies, and the rule's own guard exists because the assertion is false). Outward: when prose anywhere — this file, a code comment, another design doc — describes what one of these files contains or does, read that file's preamble or entry conventions first (an AGENTS.md bullet claimed rules.md names the pipeline stages; its preamble forbids exactly that — "Statements are implementation-free: no stage names, no function names, no regexes"). Those declarations sit at the top of files people cite without reading. Mind their scope before calling a violation: rules.md's ban binds STATEMENTS, which `_statements()` in test_doc_citations.py defines as rule-block text up to the first example, so Backgrounds and Not-in-scope may name functions and do. +5. **Does the change move another rule's OUTPUT?** A marker lands on the rule whose STATEMENT changed; a rule can move another's output without touching its statement, and the runner cannot see it. Walk `interacts:`. (family_base moved initials(); R3 had no marker.) +6. **Contested inputs: is precedence stated?** `interacts:` is advisory and pins nothing. This document states precedence in the rule's own statement (H2, M1, W3). (P6 and S2 both claimed `Berg, Jan vd`.) +7. **General clause vs. adjudicated scope.** Enumerate the vocabulary a shape-clause reaches. (P6's precedence was argued for `vd` and swept in `do` and `mc`.) +8. **Guard docstring vs. what the guard enforces**, especially when the promise spans two test modules. +9. **Prose is input to the doc parsers.** A line starting with `"` inside a rule block is an example; a comment's quoted values join the citation block above them. + +Also: before reporting "N names move", report the size of the population that COULD move — a small count over a corpus blind to the shape is evidence about the corpus. For rules.md#P6: of 782 corpus names, 245 carry a comma, two of those end in a particle, and one of the two clears the words-to-spare guard. Running axis 1 over this very list caught an earlier wording of that sentence conflating the population with the movers, so the count above is the corrected one.