From 3f66d910d685b1d9811f4ac5267a94c6b1369f5f Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 16 Aug 2026 19:34:51 -0700 Subject: [PATCH 1/3] docs(agents): the nine axes for reviewing a docs/design change #386 ran five review passes over its own work and found seven issues; unaided prose reading found none of them. The difference was that each pass named an axis and compared against an artifact. Record the axes so the next docs/design change gets the same treatment without rediscovering them. Each axis carries the case that earned it, because the abstract form ("check coherence") is what produced the zero. The list also states what NOT to report -- the suite already enforces example lines, deviates: today-values, boundaries, the one-of implemented:/tracked: rule and citation excerpts, so a finding those would catch is a false positive. Written into AGENTS.md rather than shipped as tooling: .gitignore's tools block keeps .claude/, .codex/ and .gemini/ personal, and AGENTS.md is the file every assistant reads. A Claude Code subagent and /docs-review command exist locally and are noted as a convenience, with the axes here as the shared record. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 9b127180..5d7cdd26 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,6 +134,61 @@ 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 or mechanisms.md. +Each was earned by a defect that survived every other check on #386, +where they found seven issues between them and unaided prose reading +found none. The parenthetical is the case that earned the axis. Skip +what the suite already enforces — example lines, `deviates:` today- +values, boundaries, the one-of `implemented:`/`tracked:` rule, and +citation excerpts are all machine-checked, so a finding those would +have caught is a false positive. + +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. **Rule vs. the decision entries it cites.** 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.) +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 (245 of 782 corpus names carry a +comma and exactly one ends in a particle). + +Claude Code users: `.claude/agents/design-docs-reviewer.md` packages +this as a subagent and `/docs-review` runs it. That directory is +untracked by design (see .gitignore's tools block, which keeps +assistant configs personal), so the axes above are the shared record +and the agent is a local convenience — keep them in sync by editing +here first. + **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). From 7e6211b8ebb27bc7a4f369460e4004a9d8a0c28a Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 16 Aug 2026 19:53:23 -0700 Subject: [PATCH 2/3] docs: drop the agent mention, and fix a count the axes caught Two changes, one of them the checklist catching itself. The agent and /docs-review references come out. .claude/ stays personal per .gitignore's tools block, so a committed pointer to an untracked file is a dangling reference for everyone else; the axes are the shared record and stand alone. Running axis 1 (recompute every number) over this PR falsified the corpus-blindness count, which was wrong in two files merged by #386 as well as here. Of 782 corpus names 245 carry a comma, TWO of those end in a particle, and ONE clears P6's words-to-spare guard and actually moves -- "Nguyen, Van" is the second, held back by the very guard the rule needed. "Exactly one ends in a particle" conflated the population with the movers, which is the distinction that field note exists to draw. Corrected in mechanisms.md, decisions.md#P6 and AGENTS.md, with the mechanisms note recording that it made its own error inside the correction. The finding count is corrected with it: #386's passes surfaced ten issues, nine by these axes and one by the spec self-review. "Seven" was a mid-session tally that stopped being true two passes later and was never re-counted. Neither error changed an argument -- the corpus is near-blind at two names as at one, and the axes earn their place at nine as at seven. That is what the count-durability note asks for: phrase the argument so it survives the digits moving, then get the digits right anyway. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 20 +++++++++----------- docs/design/decisions.md | 4 +++- docs/design/mechanisms.md | 12 +++++++++--- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5d7cdd26..764f8b5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,8 +137,10 @@ supplying it. **Reviewing a docs/design change: the nine axes.** Run these before merging anything that touches rules.md, decisions.md or mechanisms.md. Each was earned by a defect that survived every other check on #386, -where they found seven issues between them and unaided prose reading -found none. The parenthetical is the case that earned the axis. Skip +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 — example lines, `deviates:` today- values, boundaries, the one-of `implemented:`/`tracked:` rule, and citation excerpts are all machine-checked, so a finding those would @@ -179,15 +181,11 @@ have caught is a false positive. 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 (245 of 782 corpus names carry a -comma and exactly one ends in a particle). - -Claude Code users: `.claude/agents/design-docs-reviewer.md` packages -this as a subagent and `/docs-review` runs it. That directory is -untracked by design (see .gitignore's tools block, which keeps -assistant configs personal), so the axes above are the shared record -and the agent is a local convenience — keep them in sync by editing -here first. +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. **Guard tests** SHOULD carry a recorded negative control — the answer with the guard off, stored as data (the _EXCLUSION_EFFECT diff --git a/docs/design/decisions.md b/docs/design/decisions.md index c7f532a1..d44ad9f6 100644 --- a/docs/design/decisions.md +++ b/docs/design/decisions.md @@ -234,7 +234,9 @@ not). them; they were measured before the comma scoping was chosen and carried forward unfiltered. - What that number means is the opposite of reassuring. The corpus - holds 245 comma names and exactly ONE with a trailing particle, + holds 245 comma names, TWO of which end in a particle and one of + which clears the words-to-spare guard ("Nguyen, Van" is the other, + and the guard is what keeps it a given name), so it is very nearly blind to the shape this rule governs — Dutch and Flemish listings are barely represented. Treat the one as evidence about the corpus, not about the blast radius, and diff --git a/docs/design/mechanisms.md b/docs/design/mechanisms.md index 3f86b207..65d507b3 100644 --- a/docs/design/mechanisms.md +++ b/docs/design/mechanisms.md @@ -538,12 +538,18 @@ decline, not delete the test. such a rule. - The corpus can be near-blind to a WRITING CONVENTION even where the vocabulary is well covered, and a small honest count then - reads as a small blast radius. Measured for rules.md#P6: 245 of - 782 corpus names carry a comma and exactly ONE ends in a - particle, so the Dutch trailing-tussenvoegsel listing the rule + reads as a small blast radius. Measured 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 and actually + moves — so the Dutch trailing-tussenvoegsel listing the rule exists for is essentially unsampled. Before reporting "N names move", report the size of the population that COULD move; when that is ~1, the number is evidence about the corpus. + This note's first wording said "exactly ONE ends in a particle", + conflating the population with the movers — the very error it + exists to prevent, committed inside the correction. Caught by + re-running the count against AGENTS.md's axis 1 rather than by + re-reading it. - A detector that re-implements a rule's grouping will get the grouping wrong. Derive the boundary from the same vocabulary the rule reads, not from the half you happen to be thinking about: From fd81b8b40fcbd03c166c10cbf54a45a80fb65898 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 16 Aug 2026 19:58:38 -0700 Subject: [PATCH 3/3] docs(agents): put AGENTS.md itself in the axes' scope This file carries the conventions rules.md, decisions.md and mechanisms.md rest on, and it has the same failure modes -- demonstrated in the previous commit, where axis 1 falsified two counts in the axes note itself. So the trigger now names four files, not three. Not wholesale, though. Axes 3, 5 and 6 need rule statements, examples and interacts:, which only rules.md has; asking a reviewer to run rule-vs-its-own-examples against AGENTS.md is asking for something meaningless, and a checklist that does that teaches the reader to skim it. The note says which six apply and asks the reviewer to report what they skipped as inapplicable. Checking that also ran axis 7 (general clause vs actual scope) over the skip-list, which named five machine-checked things where there are nine tests -- a reviewer could have burned effort reporting a broken interacts: ID or an unresolved cross-doc anchor, both already enforced. The list is now complete and points at the two test files as the authority, saying in as many words that it goes stale as guards are added. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 764f8b5c..faab3984 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,16 +135,29 @@ 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 or mechanisms.md. +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 — example lines, `deviates:` today- -values, boundaries, the one-of `implemented:`/`tracked:` rule, and -citation excerpts are all machine-checked, so a finding those would -have caught is a false positive. +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