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
6 changes: 3 additions & 3 deletions AGENTS.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ rules/ states intended parsing behavior and is NORMATIVE; decisions/ is the date

**Starting a design: pin the negative space first.** A spec's "X is not supported" is a claim about today's parser; hold it with a case row for X written BEFORE the implementation, so a row that passes unexpectedly kills the premise before any code exists. The 2026-07-30 comma-suffix spec did this for one item (`suffix_phrase_is_comma_scoped`) and not for its central premise, which is the half that cost a full plan (#291).

**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 and R4 have each gathered 35 dated decisions entries, and R4's are mostly vocabulary and rendering cases rather than added stops (counted 2026-10-04).
**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).

**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`).
**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.

**A count in a dated entry is evidence, not a live fact.** decisions 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.

Expand Down
4 changes: 2 additions & 2 deletions docs/design/decisions/C1-suffix-comma-decision.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/design/decisions/H5-trailing-title-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- **The inline frame-free gates at the two walk sites were REMOVED in review**, and this is the ONE-PREDICATE-PER-QUESTION half of the entry. Each site had a cheap inline test written to match the walk's own first condition; that is a second implementation of the question, and the measurement that justified it did not survive re-running. The band test runs early in a session where the facade sits at 453, so the "one frame of headroom" claim did not reproduce. With the gates gone the walk costs +1 frame on each entry point, inside the plan's target of two and inside `test_facade_cost_stays_within_its_band`. The walk's own cheapness is where the saving lives instead: the abbreviation-shape test is a compiled regex (a C call, no Python frame) and runs BEFORE the vocabulary call, and almost no name ends in a period-marked word, so the ordinary parse pays one match and stops.
- **A pre-existing detail mismatch, recorded and NOT fixed.** #H4's join shape reports `title-or-name` with a `detail` that says the unit was "read as a given name by convention", while under the default order H1 retags the unit to the family — so `Dr. John of Prince` reports that text with the unit in `family`. This rule adds a second input with the same mismatch, `John of Prince Prof.`, and fixes neither: the wording predates this bundle, the fork the kind reports is title-versus-name which no field answers either way, and #H4 already records why the detail names no field for the peel shape.
- 2026-09-09 (review round 2) — **A1's transparency holds for the ONE-name-word case too, and the scope written a day earlier was the defect rather than the boundary.** A1 was scoped that morning to inputs where a name word still stands on both sides of the chain, handing the one-word case to #H1 unconditionally: "the title behind that word decides its field as a title in front of it would". #H1 was then reading BOTH ends of the name as one title run, so `Sir John Prof.` read family `John` where `Sir John` reads given `John` — the trailing title changing what the leading one addressed by, which is exactly the non-transparency A1 denies. The clause is now conditioned on there being no run in FRONT of the word: where one stands it addresses and the chained trailing title only joins the title field, so `X Prof.` reads as `X` plus the title for one name word as for two. Where no run stands in front the trailing one still decides, unchanged — `Smith Prof.` reads family `Smith`, `Smith Sir.` given `Smith`. Nothing in the walk moved; the fix is entirely in which run H1 keys, and #H1's 2026-09-09 entry carries it, the measurement and the mutation. The #P5 clause below reads the same way afterwards and gains its second half: a bound given-name word behind a trailing title joins as it joins with the title absent AND lands in the same field. Said of the COMMA-LESS writing, and the review that found the H1 defect measured why the qualifier is needed: after a family comma the reserve reads no peel at all, so the join fires over the trailing title word and takes it into the given name — `Berg, abdul Prof.` reads given `abdul Prof.` where `Berg, John Prof.` reads title `Prof.`. That is PARITY (1.4.0 reads it the same, measured) and a gap in the segment path rather than a boundary of the rule, so it is recorded rather than fixed here: the clause carries the scope, and the case row `title_word_trailing_behind_a_bound_pair_after_a_comma` pins it without making it normative. Tracked as part of #316. (FIXED by #637, decisions/P5, 2026-10-11: the join declines the trailing title word, and the row pins the title.)
- 2026-09-09 (the /simplify round on the bundle's PR) — **the trailing peel and the title chain are ONE reading run to a FIXED POINT, and A1's "ONE peel runs over what stands" was a truncation of it.** Two contrived readings contradicted clauses this section states, both found by an altitude review reading the code against the rule rather than by a test. (1) THE SUBTRACTION MODEL. #P5's reserve modelled assign's reading as the peel's count minus the chain's take, on each of the two views the join compares. That arithmetic equals assign's answer only while the chain's take does not change what the peel would do, and at #S2's bare-ambiguous reserve it does: `abdul rahman MA` reads given `abdul`, family `rahman`, suffix `MA` — the join declining because peeling the acronym unjoined and not joined is a suffix reading the join would change — while `abdul rahman MA Prof.` read given `abdul rahman`, family `MA`, the subtraction having counted `MA` a name word to spare. The Accepted clause below, "a bound given-name word behind one joins exactly as it joins with the title absent", was false as stated. (2) THE TRUNCATED FIXED POINT. The re-peel ran ONCE, so a second title was read half way: `John Prof. MA` reads title `Prof.`, given `John`, family `MA`, and `John Prof. MA Prof.` read title `Prof.`, given `John`, family `Prof.`, suffix `MA` — the re-peel un-peeled the acronym and re-exposed the FIRST title, which nothing then took. Both measured on the branch tip, 2026-09-09.
- 2026-09-09 (the /simplify round on the bundle's PR) — **the trailing peel and the title chain are ONE reading run to a FIXED POINT, and A1's "ONE peel runs over what stands" was a truncation of it.** Two contrived readings contradicted clauses this section states, both found by an altitude review reading the code against the rule rather than by a test. (1) THE SUBTRACTION MODEL. #P5's reserve modelled assign's reading as the peel's count minus the chain's take, on each of the two views the join compares. That arithmetic equals assign's answer only while the chain's take does not change what the peel would do, and at rules/S4's bare-ambiguous reserve it does: `abdul rahman MA` reads given `abdul`, family `rahman`, suffix `MA` — the join declining because peeling the acronym unjoined and not joined is a suffix reading the join would change — while `abdul rahman MA Prof.` read given `abdul rahman`, family `MA`, the subtraction having counted `MA` a name word to spare. The Accepted clause below, "a bound given-name word behind one joins exactly as it joins with the title absent", was false as stated. (2) THE TRUNCATED FIXED POINT. The re-peel ran ONCE, so a second title was read half way: `John Prof. MA` reads title `Prof.`, given `John`, family `MA`, and `John Prof. MA Prof.` read title `Prof.`, given `John`, family `Prof.`, suffix `MA` — the re-peel un-peeled the acronym and re-exposed the FIRST title, which nothing then took. Both measured on the branch tip, 2026-09-09.
- **The fix is one function, `_pieces.tail_reading`:** peel, chain, splice the chained pieces out, peel again over what is left, until the chain takes nothing. Assign calls it in place of its inline sequence and all its reports still come from the final peel; the reserve calls it on both views and compares the suffix lists it returns, and both subtractions are deleted. `abdul rahman MA Prof.` now reads given `abdul`, family `rahman`, suffix `MA` under title `Prof.`, and `John Prof. MA Prof.` reads title `Prof. Prof.`, given `John`, family `MA`. One further clause had to be said in the code rather than left to the arithmetic: the join must not take a piece the chain takes (#P5 — "a trailing title word the run takes is no word to spare"), which the subtraction had enforced by accident, by leaving that piece in the tail it compared. Without it `Sir abdul Prof.` joins the title into the given name; with it the reading is title `Sir Prof.`, given `abdul`, as before. `trailing_titles` returns the count it LEAVES STANDING in the same edit, the way `peel_trailing` counts, so the two compose at the call site without arithmetic at all.
- **Cost, and what paid it.** The shared helper is one frame on the common path, where the inline sequence was none. It is paid by `_effective_order` taking the segment's pieces and the name-piece indices — the shape every piece-layer predicate takes — instead of a list built for it, which on 3.11 is a comprehension frame on every parse; the reference name stays at 416 parse / 453 facade. The helper returns a bare 3-tuple rather than a NamedTuple for the same budget: a NamedTuple's `__new__` is itself a frame, measured at +1 here.
- **How big each defect's class is, measured as the rule states it.** The two readings named above are the shapes that made the defects visible; the class both sit in is the TRANSPARENCY class A1 is stated over, and the two defects reach different parts of it — an earlier wording of this bullet flattened them into one width and named the wrong shape for it. Recipe: take the alphabet `John Smith abdul rahman Prof. Dr. Sir MA Jr. V Xyz. King de née`, form every 3- and 4-word arrangement ending in `Prof.` or `Dr.` whose spelling WITHOUT that last word still fills two of given/middle/family (3,210 of them — the two-or-more-name-words scope the A1 clause is stated for), and compare the seven fields other than `title` against that shorter spelling. rules/H5 says they must be equal. On the branch tip 786 differ; on this commit 698 do, none of them new, and all 698 are the P2/M2 residue the rule already accepts as a boundary — a particle chain or a maiden marker took the trailing word before the H5 chain could see it (`John de Prof.`, `Mary née Prof.`). The 88 that close PARTITION by defect, and that partition is what makes them two blast radii rather than one. 56 of them carry a SINGLE period-marked title word, and every one of those carries a bound given-name word as well — the subtraction model's reach, `abdul rahman MA Prof.` itself and `abdul John MA Prof.` beside it, where the arithmetic counted the acronym a name word to spare. The other 32 carry TWO, which is the truncated fixed point's reach: `John Prof. MA Prof.` and `John Dr. MA Dr.`, where the single re-peel re-exposed a title nothing then took. The two-title members of the 3,210 number 784, which is the population that could have witnessed the fixed point at all: 164 of them differed on the branch tip and 132 still do, every one of those 132 the same P2/M2 residue the 698 are, so the defect accounted for exactly the 32 that closed and no more. Over the same sweep's 82,712 inputs (both comma-less and family-comma spellings, 2 to 4 words) exactly 156 parses move, splitting the same way — 96 carry two period-marked title words, 60 carry one, and all 60 of those are bound-given — and not one mover is a comma spelling, the family-comma segment path reaching neither defect. Every figure in this bullet re-measured 2026-09-09 on this tree against `7ac2218`, the round's parent.
Expand Down
Loading
Loading