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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ Add a dedicated `copy.deepcopy()` round-trip test for it too (see `test_regexes_

**A group-side reading of a family comma's given part that asks `tail_reading` disagrees with the given part's own walk, and the disagreement is accepted.** `tail_reading` reads the part as the comma-less name reads it, plus the bare salutations rules/H8 adds after a family comma, which a predictor of this part asks for with `salutations=True` (#835); assign's walk over the given part after a family comma reads by its own predicates -- a shape-only numeral VI to X stays a name word there (`Smith, John van der Berg VI` reads middle 'van der Berg VI'), V before a third comma part is read strictly (#144), and P6 attaches a period-marked title-particle (`St.`) the comma-less reading takes as a title. So a bound or a decline group computes with `tail_reading` acts where the walk then reads no title: #623's chain bound (`Smith, John Mc Prof. VI`) and #637's join decline (`Smith, abdul Prof. VI` → middle 'Prof. VI', `Smith, abdul St.` → family 'St. Smith'). Both were recorded rather than narrowed (Derek: garbage input; decisions/H5 and #P5, rules/P5's Accepted clause). Before writing another group-side predictor of the given part, sweep those three shapes and decide, rather than rediscovering the gap in review. And a change to the trailing peel ITSELF reaches the given part through those predictors without touching them: #718's words-to-spare count for a one-letter numeral, asked through `trailing_run_start`, made the chain take the V in `Smith, Freiherr von Berg V` (given 'von Berg V', unreported) while assign's walk, which never counts there (S4: "read without it"), read it as the suffix -- found by review on a grid of family-comma and empty-head shapes behind a title-particle; the predictor now passes `count_numeral=False`. When a change moves what the peel takes, put a family comma with a title-particle head in its grid.

**A comparison that runs both sides in one tree reports 0 differences, which is what success looks like.** `tools/differential/compare.py` has been hardened against this — it generates its baseline worker into a temp dir, strips `PYTHONPATH` from the child, and aborts unless both the version AND the resolved path check out on each side; its README's three invocation traps are the analysis behind that design, worth reading before changing the harness. **The exposure is ad-hoc comparisons you write yourself**, where the same collapse has a cause the harness cannot disarm for you: **the shell's working directory persists between tool calls**, so a two-tree comparison written as two `cd`s silently runs both halves in whichever tree it landed in. Pin absolute paths, and assert `nameparser.__file__` on both sides the way `compare.py` does. The general rule outlives any particular trap: before believing a null result, prove the harness can report a difference — a clean run and a broken harness are the same output. The proof has to come from the input class the null result is about: #683 moved `tools/name_usage.py` to a POST and checked it with `shehu`, which is ASCII and so cannot show that the server decodes a Cyrillic body. A misdecoded query still runs and matches nothing, so the Cyrillic zeros it was meant to vouch for proved nothing until `Иван` (17,119 given) went through the same path. The converse trap is a mutation that moves nothing: it shows the code is unexercised by your grids, not that it is dead. #638 was filed calling group's chain re-ask dead on a grid with no title-particle head before a particle run and a dotted acronym; the next review found 400 moves (`Smith, Freiherr van Berg X.Y.Z.`) and the issue closed the same day. Before calling code dead, look for the input that reaches it. A CACHE of readings is the same trap one step removed: a wrong key copies one tree's readings in as another's, and the check then reports nothing. #859 first keyed `snapshot.py --local`'s kept baselines by `git rev-parse HEAD:nameparser`, which answers for the enclosing checkout, so a gitignored copy inside one (`build/lib`) took the checkout's key; two reviewers found it. Key a cached reading by what the import reads -- the files on disk, hashed again after the read -- never by what version control says should be there.
**A comparison that runs both sides in one tree reports 0 differences, which is what success looks like.** `tools/differential/compare.py` has been hardened against this — it generates its baseline worker into a temp dir, strips `PYTHONPATH` from the child, and aborts unless both the version AND the resolved path check out on each side; its README's three invocation traps are the analysis behind that design, worth reading before changing the harness. **The exposure is ad-hoc comparisons you write yourself**, where the same collapse has a cause the harness cannot disarm for you: **the shell's working directory persists between tool calls**, so a two-tree comparison written as two `cd`s silently runs both halves in whichever tree it landed in. Pin absolute paths, and assert `nameparser.__file__` on both sides the way `compare.py` does. The general rule outlives any particular trap: before believing a null result, prove the harness can report a difference — a clean run and a broken harness are the same output. The proof has to come from the input class the null result is about: #683 moved `tools/name_usage.py` to a POST and checked it with `shehu`, which is ASCII and so cannot show that the server decodes a Cyrillic body. A misdecoded query still runs and matches nothing, so the Cyrillic zeros it was meant to vouch for proved nothing until `Иван` (17,119 given) went through the same path. The converse trap is a mutation that moves nothing: it shows the code is unexercised by your grids, not that it is dead. #638 was filed calling group's chain re-ask dead on a grid with no title-particle head before a particle run and a dotted acronym; the next review found 400 moves (`Smith, Freiherr van Berg X.Y.Z.`) and the issue closed the same day. Before calling code dead, look for the input that reaches it. A CACHE of readings is the same trap one step removed: a wrong key copies one tree's readings in as another's, and the check then reports nothing. #859 first keyed `snapshot.py --local`'s kept baselines by `git rev-parse HEAD:nameparser`, which answers for the enclosing checkout, so a gitignored copy inside one (`build/lib`) took the checkout's key; two reviewers found it. Key a cached reading by what the import reads -- the files on disk, hashed again after the read -- never by what version control says should be there. And a switch that changes no file defeats any file key: #752's spike set its options by environment variable, so after the first run every option copied that run's cached local readings until the variables joined the key (2026-10-24).

**`SUFFIX_ACRONYMS` and `SUFFIX_WORDS` are DISJOINT by construction (since 2026-10-18; an import-time assert from 2026-09-08) — a word has one suffix kind, and its reading is an acronym or a word (`acronym`), so the vocabulary build refuses a word classified twice; the fix is to choose, not to relax the check** — the two branches normalize differently (the word test strips only edge periods, the acronym test strips all of them), so a word in both is matched by two rules and which one fired is unreadable from outside. `esq` was the single overlap and left `SUFFIX_ACRONYMS`, which is what made the assert possible; the whole algebra that had made the overlap safe, and the criterion that decided which set a word belongs to, are in the `suffix-acronym-collisions` entry of `docs/design/decisions/` if anyone wants them.

Expand Down
Loading
Loading