Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ rules/ states intended parsing behavior and is NORMATIVE; decisions/ is the date

**Rethinking a rule that grew by patching.** Three signs together mean the next fix should be a rethink, not another patch: the rule works by scanning and stopping; each later fix added a stop, an exception or a check on one; and a guard exists whose job is to model what another stage would do. The third sign is the decisive one — a rule that must predict other rules' behavior to stay correct is defending an invariant it should be built from. #601 is the worked case. Five issues (#424, #533, #535, #538, #549) had each added a stop or a release check to M2, and its decisions.md section reached about 13,000 words. The rethink (mechanisms.md#READ-WITHOUT-THEN-BIND, #LICENSED-BY-NEIGHBOR) deleted the release machinery, cut the rule's statement to 883 words (counted 2026-10-04), and turned the invariant from a checked property into the construction. The rethink was drafted in rule shape and prototyped in seven variants behind one switch before anything landed (#601's comments); the variants that failed did so on measured counts, not on review opinion. Rule length alone is not the signal: S2 (before #682 split it into S2 and S4–S9) and R4 had each gathered 35 dated decisions entries, and R4's are mostly vocabulary and rendering cases rather than added stops (counted 2026-10-04).

**One rule per piece of evidence.** A rule states one signal in the written name — a count, a case contrast, a shape, a neighbor — and what that signal decides. A decision that weighs several signals is a hub: its rule states the decision, the precedence among its signals, and a one-line map naming the rule that states each one (C1 is the nearest to a worked hub: its map is one line, though it states no precedence among C4–C6 and C12 and still carries T4's clause on the strict knob; S2's one-sentence map names S4–S8 and leaves out S9). New evidence for a decision is therefore a new rule plus a line in the hub's map, never a new clause in the hub or in a sibling. Before adding a clause to a statement, name what it reads in the text: if the rule does not already read it, the clause is a new rule; if it is where this rule's own signal stops deciding, it belongs here, with a `· boundary` example. This is a different cause from the patching above. S2 and C1 did not grow by stops: each grew by clauses that were sound on their own, every review passing, until #682 split S2 into S2 and S4–S9 (PR #716) and #728 split C1 into C1 and C3–C11 (PR #734) — each retargeting every reference to the old ID by hand and leaving a note in each moved decisions entry that the old ID "means the rule as it then stood". A new rule costs an index line. Length is still not the signal; count the signals.

**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 entries; proposals rejected with evidence → Declined:; vocabulary that must stay out → Excluded:; behavior the design settled → rules/ (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.

**Moving text between files breaks every positional reference in it, and in what pointed at it.** "The bullet above", "this file's Excluded block", "the W2 section" are true only where the text stood. #713 split decisions.md by heading and then moved the vocabulary-keyed blocks into decisions/vocabulary-exclusions, and its last review found thirteen such references left pointing at nothing: three inside the moved blocks, ten in entries elsewhere that located a block by position. The citation check cannot see them, because none is a citation. After a move, grep the moved text and the whole folder for `above`, `below`, `this file`, `this section` and the moved block's name, and turn each hit into a key-form pointer (`decisions/P3's bullet of that date`). Splitting an ID is the same move in the other direction (#682, S2 into S2 and S4–S9): every reference to the old ID needs a reader's choice of new one, and the references come in more spellings than the citation check reads — `rules/S2` and `decisions/S2`, a bare `S2` in rule prose, and the short forms older entries use, `#S2` and "the #544 entry under S2". #682's first pass retargeted the first three and its review found nine of the last kind still pointing at entries that had left the file; grep all five before calling the retarget done. #728 (C1 into C1 and C3–C11) added two more. A blanket note that an old ID in a moved entry "means the rule as it then stood" is true only where the pointer meant the whole rule: "C1's two-dotted-groups sentence" means C5 now, so retarget each pointer that names a piece. And the prose a split writes fresh is new normative text that no guard reads against its statement. That covers each piece's Rationale, and a `· boundary` added so a piece has one. #728's first C4 Rationale called two words before the comma "a whole name", which C5's exception denies. Its first C10 boundary carried no policy annotation, so the rule never ran on it. Read each new Rationale against its piece's exceptions, and run each new boundary under the annotation its rule needs.
Expand All @@ -18,7 +20,7 @@ rules/ states intended parsing behavior and is NORMATIVE; decisions/ is the date

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 eleven axes.** Run these before merging anything that touches rules/, decisions/, 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-specific (they need rule statements, examples and `interacts:`); the other eight apply to any of the four — axis 11 included, since an invariant over two inputs is prompted by what a rule implies but is RUN without reading one, which is the whole of its value. Say which axes you ran and which you skipped as inapplicable — a checklist that asks for something meaningless teaches the reader to skim it. Axes 1–9 were each 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. Axes 10 and 11 were earned the same way on other arcs — #291's, which PREDATES this list, and #461/#466's, which follows it. The parenthetical is the case that earned the axis.
**Reviewing a docs/design change: the twelve axes.** Run these before merging anything that touches rules/, decisions/, mechanisms.md — or THIS FILE, which carries the conventions those three rest on and has the same failure modes. Axes 3, 5, 6 and 12 are rules-specific (they need rule statements, examples and `interacts:`); the other eight apply to any of the four — axis 11 included, since an invariant over two inputs is prompted by what a rule implies but is RUN without reading one, which is the whole of its value. Say which axes you ran and which you skipped as inapplicable — a checklist that asks for something meaningless teaches the reader to skim it. Axes 1–9 were each 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. Axes 10 and 11 were earned the same way on other arcs — #291's, which PREDATES this list, and #461/#466's, which follows it. Axis 12 was earned by no defect at all, which is its point: it covers the growth every other axis passes. The parenthetical is the case that earned the axis.

Skip what the suite already enforces. `tests/v2/test_rules_doc.py`, `tests/v2/test_doc_citations.py` and `tests/v2/test_design_docs.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, legacy citation forms (the pre-#713 file-anchor form among them), and that every rule and decisions file is indexed once, under a title matching its heading and file name. Assume they pass; a finding one of them would catch is a false positive. Read those three files rather than trusting this sentence — it named five of the nine guards when written, and goes stale as guards are added.

Expand All @@ -33,5 +35,6 @@ Skip what the suite already enforces. `tests/v2/test_rules_doc.py`, `tests/v2/te
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.
10. **Re-run every capability claim.** Axis 1 recomputes numbers; this one recomputes CAN and CANNOT. A statement that the parser never or cannot produce some reading is measured by a parse run written now, never derived from a mechanism — most of all when it justifies building something. (#291 derived "a multi-word credential is unparseable" from "vocabulary is matched one token at a time" — which `given_name_titles` had been an exception to since 2026-07-19, a month before that arc, and `maiden_markers` is a second since #434 — and specified a vocabulary set, a matching unit, a predicate and a `Lexicon` field on it; `parse("John Smith, MD PhD").suffix` had been `'MD PhD'` since 1.4.0.)
11. **State the invariant that relates two DIFFERENT inputs.** Axes 1–10 compare a document to another document, to its own examples, or to the implementation, so every one of them can only find code that DISAGREES with a rule — none of them can find a rule that is simply wrong. When a rule implies that two spellings of the same name must parse alike, write that as an invariant over both inputs and run it: it consults no rule statement, so it is not blind that way. Distinct from an invariant over two VIEWS of one parse, like `test_the_family_partitions_into_particles_and_base` in tests/v2/test_cases.py — that one holds however wrong the rule is. (Two review rounds a month apart found real defects and missed the premise under them: #461 was backed out after the rule turned out wrong, #466 closed unmerged for the same reason, and in both, every reviewer checked the code against the rule and THE RULE was at fault. The worked invariant: the family-comma writing under the default order and the comma-less writing under FAMILY_FIRST must parse alike. Measured, it holds for the great majority of the particle vocabulary and fails exactly on the words that are also suffix vocabulary — recovering P6's contested-membership set from behavior, having read no rule. Composition, measurement and recompute recipe: decisions/two-input-invariants.)
12. **Name the signal each new clause reads.** For every clause the diff adds to a rule's statement, say what in the written name it reads. A signal the rule did not already read is a new rule wanting its own ID and a line in the hub's map ("One rule per piece of evidence" above), and a clause that restates another rule's condition instead of citing it is the same finding from the other side. This is a structural finding, not a style one: report it under the usual format, the consequence being the next split and its hand retargeting. (S2 reached seven rules' worth of clauses and C1 ten, one sound clause at a time, every review passing, until #682 and #728 split them after the fact.)

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/P6 the population is the comma-bearing names that END in a particle — a small fraction of the comma-bearing names, which are themselves under a third of the corpus; decisions/P6 carries the dated measurement and the recompute. Do not restate a corpus count here. Axis 1 over this very sentence once caught it conflating the population with the movers, and the corrected figures that replaced it were superseded within the month as the corpus grew — a standing count in this file is wrong twice over, once for the class and once for the digits.
Loading
Loading