Skip to content

Commit fe85bb3

Browse files
derek73claude
andauthored
Split T4: the written shape of an initial is T5; fix S10's README example (#756)
* Split rules/T4: the written shape of an initial is T5; fix S10's README example T4 (the bare letter as an initial) read two signals: the shape a one-letter word is written in, and a bare lower-case letter's case against the name's own words. T5 (the written shape of an initial) takes the shape: a one-letter word with its period, in a script that writes initials, or a bare unaccented Latin capital. Its examples are five maiden-marker names whose given-name reading shows the initial (J./J/Ж. initials, Ж and É name words). T4 keeps the case contrast, which every decisions/T4 entry decided. C9, O5 and S10 cite T5 where they mean the shape, and _vocab.is_initial quotes it. rules/README.md's example of a cited condition said "S10's count is S4's", which is not so: S4 states its count for ambiguous acronyms, and the two part in a maiden clause ('Jane Doe nee Smith V' takes the numeral, '... Smith Ma' keeps the acronym). The example is now S10's citation of T4, and S10's one-letter count is named on its Signal line as the size gate it is. No rule now names two signals. The five new example names join corpus_rules.jsonl and the snapshot. No reading moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the design-docs review findings on the T5 split - M4, S10, O5, H2 and P7 name T5 in interacts:, since each leans on the initial's shape (M4's exception is what every T5 example runs on); T4 drops P7, whose interaction is with the shape - P7 cites T5 for the period rather than restating the shape as its own - T5 records as Accepted that the period test reads one word character, as v1's did, so a digit with its period is an initial too ('1. née Smith' → given '1.'); the example joins the corpus and the snapshot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * P7's Signal line names what it decides, citing T5 for the period Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e9855df commit fe85bb3

17 files changed

Lines changed: 78 additions & 33 deletions

‎docs/design/decisions/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Entry conventions:
9999
- [T1 — separators, not joiners](T1-separators-not-joiners.md)
100100
- [T3 — the interpunct's flank guard](T3-interpuncts-flank-guard.md)
101101
- [T4 — the bare letter as an initial](T4-bare-letter-as-an-initial.md)
102+
- [T5 — the written shape of an initial](T5-written-shape-of-an-initial.md)
102103

103104
## Ambiguity & tie-breaking (A)
104105

‎docs/design/decisions/T4-bare-letter-as-an-initial.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
Declined, measured: passing the whole name's fact to the view as well. It closes both (0 field and 0 report differences on a grid with one-word, two-word and titled heads) but the view also asks S6's credential lean, which P8 says is asked of the own words P3's span holds, and moving it there fails `test_a_maiden_clause_does_not_change_how_a_trailing_word_reads` (tests/v2/test_properties.py) on 270 of its 20,412 pairs (with it, `J. nee Smith BA` would read the credential where `J. BA` reads a name). Closing the gap needs the peel to take the numeral's case fact apart from the lean's — a design change beyond this fix.
1616
Measured: the one-case grid gains `{a} {b} nee {n} {n}` and `{a} {b} nee j {n}`. ad486f66 disagreed on 36 of its 4,752 pairs, all in those shapes; this tree on none, and the fourth recorded control is that 36. 2.2.0 through 2.4.2 gave the lowercase reading above, and 2.0.0 and 2.1.0 the reading this restores (maiden 'x x'), so the release bullet for #723 carries this too. No corpus name moved.
1717
- 2026-10-18 (Derek), #731 reverted — A MAIDEN CLAUSE'S NUMERAL IS NOT HELD TO ITS CAPITALS. The #731 entry above is superseded and its change reverted. Derek: this rule and maiden names do not really interact, because nobody writes a maiden marker followed by an initial, or two. Asked whether settling the gap that entry left open would simplify the parser, the answer was the opposite: it needs the peel to carry two case facts, one for S6's lean and one for the numeral. And #731 itself had bought `john smith nee x x` and `john smith nee j x` — both that unwritten shape — a case argument on `trailing_candidates`, a whole-name case test in the take, a stated exception to the candidates' superset contract (decisions/M2's #731 entry), two grid shapes and a second invariant with their controls, all reverted. The readings go back to 2.2.0 through 2.4.2's (`john smith nee x x` suffix 'x', maiden 'x', reported), stated as an Accepted clause of this rule with both spellings as examples. Kept from that PR: M2 naming S10 among the trailing rules its take asks, which was true before #731 and stays true. What this rule still says of a clause's letters — classify reads one by the whole name's case (the #723 code-review entry) — stands: the shape that guards is a connective inside a clause (`MARIA GARCIA NEE LOPEZ y FERNANDEZ`), which names do take.
18+
- 2026-10-19 — RULE SPLIT: the written shape of an initial (its period, or a bare unaccented Latin capital) left rules/T4 for rules/T5, and T4 keeps the case contrast every entry here decided. Record: decisions/T5's entry of this date.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# T5 — the written shape of an initial
2+
3+
- 2026-10-19 — SPLIT FROM rules/T4, one rule per piece of evidence (docs/design/AGENTS.md): T4 read two signals, the shape a one-letter word is written in and a bare lower-case letter's case against the name's own words, and its `Signal:` line named both. The shape moved here and T4 keeps the case contrast, since every entry under decisions/T4 (#723, #727, #731) decided the case half. The shape itself is older than both rules and is recorded where it was decided: v1's `is_an_initial` (`_vocab._INITIAL`, a period after one word character or a bare A to Z capital), narrowed to scripts that write initials by #320 (decisions/initials-repertoire), and asked of the composed spelling since #665 (decisions/nfd-shapes). P7 (a one-letter particle against the initial it spells) is this shape against particle vocabulary.

‎docs/design/rules/C9-one-character-suffix-word-inside-a-comma-part.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ C9. Rationale: one letter is the shape a middle initial is written
99
run, written with a period or without, an initial being no shape
1010
anyone writes there; behind a name word it is the generation
1111
only when written bare, a period marking it the abbreviation of
12-
a name and so a middle initial (T4). Both branches then ask that the
12+
a name and so a middle initial (T5). Both branches then ask that the
1313
generation slot still be open: where a further comma has already
1414
named the suffix (C2), a single letter ending the GIVEN part has no
1515
generation left to be, and stays a middle initial — with a
@@ -40,4 +40,4 @@ C9. Rationale: one letter is the shape a middle initial is written
4040
while adding a suffix comma after either turns that same letter
4141
into the middle initial.
4242
Signal: a neighbor: what stands before a one-character suffix word inside a comma part
43-
history: decisions/C9 · interacts: C1, C2, C8, C13, S5, S10, T4 · implemented: nameparser/_pipeline/_assign.py
43+
history: decisions/C9 · interacts: C1, C2, C8, C13, S5, S10, T4, T5 · implemented: nameparser/_pipeline/_assign.py

‎docs/design/rules/H2-leading-abbreviation-title.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ H2. Rationale: before a name, an abbreviation is almost always a
5656
it is.
5757
"Smith, Esq." → suffix="Esq."
5858
Signal: a shape: an unlisted word of two or more letters ending in its one period, opening the given-name part
59-
history: decisions/H2 · interacts: C5, C6, C8, S3, P4, H5, H6, W3, W4, S9 · implemented: nameparser/_pipeline/_assign.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py
59+
history: decisions/H2 · interacts: C5, C6, C8, S3, P4, H5, H6, W3, W4, S9, T5 · implemented: nameparser/_pipeline/_assign.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py
6060

‎docs/design/rules/M4-maiden-marker-makes-the-lone-name-word-the-family.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,5 @@ M4. Rationale: a maiden name is a FORMER family name, and a former
3838
"J. née Jones Smith V" → given="J." · boundary
3939
"j nee smith" → given="j" · boundary
4040
Signal: a count: one name word left beside a maiden name
41-
history: decisions/M4 · interacts: M1, M2, M3, H1, N3, O5, T4 · implemented: nameparser/_pipeline/_post_rules.py
41+
history: decisions/M4 · interacts: M1, M2, M3, H1, N3, O5, T4, T5 · implemented: nameparser/_pipeline/_post_rules.py
4242

‎docs/design/rules/O5-lone-name-words-convention-and-reporting-it.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ O5. Rationale: O4 reads a name by comparing where its words stand,
2727
where it fires, a family comma — a comma with a name segment
2828
after it — silences it, a script whose own convention settles
2929
the order (W4) silences it, and so does the word's own claim — a
30-
particle, a bound given name, an initial (T4). A comma with
30+
particle, a bound given name, an initial (T5, T4). A comma with
3131
nothing after it names no family and silences nothing, so
3232
`سلمان،` and `Smith,` report where `Smith, Andrew` does not. A
3333
comma with only a TITLE after it names no family either, but the
@@ -58,5 +58,5 @@ O5. Rationale: O4 reads a name by comparing where its words stand,
5858
"abd née Jones" → ambiguities=()
5959
"j" → ambiguities=()
6060
Signal: a count: one name word that nothing else decided
61-
history: decisions/O5 · interacts: O4, H1, N3, M4, H4, W4, P3, A2, T4 · implemented: nameparser/_pipeline/_assign.py
61+
history: decisions/O5 · interacts: O4, H1, N3, M4, H4, W4, P3, A2, T4, T5 · implemented: nameparser/_pipeline/_assign.py
6262

‎docs/design/rules/P7-one-letter-particle-against-the-initial-it-spells.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ P7. Rationale: a one-letter particle is spelled with the same letter
66
a whole word and is not. The Irish Ó is never written with a
77
period, while Ó. is the initial of Óscar, Ólafur or Ólöf.
88
A one-letter word written with its period is an initial and not a
9-
particle, whatever particle the same letter spells; written bare,
9+
particle, whatever particle the same letter spells, the period
10+
being the shape T5 reads as an initial; written bare,
1011
it is the particle. It is an initial in every position, so it
1112
neither opens a surname, nor joins a chain, nor attaches to the
1213
family behind a comma.
@@ -19,6 +20,6 @@ P7. Rationale: a one-letter particle is spelled with the same letter
1920
the particle. Such initials normally carry the period, and the
2021
bare letter is exactly how the particle is written.
2122
"Juan Ó Pérez" → middle=""
22-
Signal: a shape: the period after a one-letter word the particle vocabulary holds
23-
history: decisions/P7 · interacts: P1, P2, P3, P6, R3, R4 · implemented: nameparser/_lexicon.py, nameparser/_pipeline/_classify.py
23+
Signal: a shape: a one-letter word the particle vocabulary holds, read by whether T5 makes it an initial
24+
history: decisions/P7 · interacts: P1, P2, P3, P6, R3, R4, T5 · implemented: nameparser/_lexicon.py, nameparser/_pipeline/_classify.py
2425

‎docs/design/rules/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Every example line is EXECUTABLE. The grammar (its executable definition is `tes
1313

1414
An `annotation` names a policy, locale (`[ru]`), or extras gate (`[ja+segmenter]`) in the registry beside the test. `· boundary` marks the non-firing example every rule must carry: an input shaped like the rule's subject where its effect does NOT occur. That is usually the rule's OWN stated exception — H1's given-name title, P3's single-letter carve-out — not an input the rule never reaches, so the exception is executable rather than merely asserted. Or the rule declares `no-boundary: <reason>` instead, so skipping the boundary is a recorded decision. `deviates:` states the INTENDED output on the example line while the marker records TODAY's output and the tracking issue; the runner asserts today's output strictly, so a parser change that closes the gap fails the suite until the marker is removed in the same PR. `grep -r deviates: docs/design/rules/` is the deviation backlog (deviations from statable rules — coverage gaps are a separate, larger category no grep can see, and contested vocabulary memberships a third, tracked as Open blocks keyed to the vocabulary set in decisions/).
1515

16-
Every rule also states its `Signal:` — what in the written name it reads, for example a count, a case contrast, a shape, a neighbor, a position or a script, or `none in the name: <what it reads instead>` for a rule that reads configuration or the parsed words. The line is one line, never wrapped, standing directly above the rule's pointer line, and the parser refuses one anywhere else, since a continuation line would be read as statement prose. It is metadata, not statement: a citation cannot quote it. A hub — a decision weighing several signals, each stated by a rule of its own — ends its line `hub of C3, C4, …`, after any signal it reads itself (`Signal: <its own>; hub of C3, C4`), and every rule a hub names must be in its `interacts:` and named in one sentence of its statement, the one-line map, which the doc tests check. A line naming two signals joins them with ` + `: that rule is a split candidate under docs/design/AGENTS.md's one rule per piece of evidence, and `grep -l '^ Signal:.* + ' docs/design/rules/*.md` lists them. A condition the rule cites to the rule stating it stays off the line — S10's count is S4's — and a gate on the name's size (exactly three name words, a word to spare) is part of the signal it gates rather than a second one.
16+
Every rule also states its `Signal:` — what in the written name it reads, for example a count, a case contrast, a shape, a neighbor, a position or a script, or `none in the name: <what it reads instead>` for a rule that reads configuration or the parsed words. The line is one line, never wrapped, standing directly above the rule's pointer line, and the parser refuses one anywhere else, since a continuation line would be read as statement prose. It is metadata, not statement: a citation cannot quote it. A hub — a decision weighing several signals, each stated by a rule of its own — ends its line `hub of C3, C4, …`, after any signal it reads itself (`Signal: <its own>; hub of C3, C4`), and every rule a hub names must be in its `interacts:` and named in one sentence of its statement, the one-line map, which the doc tests check. A line naming two signals joins them with ` + `: that rule is a split candidate under docs/design/AGENTS.md's one rule per piece of evidence, and `grep -l '^ Signal:.* + ' docs/design/rules/*.md` lists them. A condition the rule cites to the rule stating it stays off the line — S10 cites T4 for the lower-case numeral that is no initial — and a gate on the name's size (exactly three name words, a word to spare, as S10's one-letter numeral needs) is part of the signal it gates rather than a second one.
1717

1818
Each rule's size is recorded, as a guard rather than a cap (mechanisms.md#RECORDED-ROSTERS). `tests/v2/rules_budget.json` holds each rule's statement word count (the Rationale counted, the Accepted clauses not), the statement's `except` and `unless` (a proxy for the clauses that carve out a case), its count of Accepted clauses (a line opening `Accepted:` or `Accepted,` — the rules write `Accepted, and …:`) and their words; `tests/v2/test_rules_budget.py` fails when a rule moves against its record, shrinking included, so the record follows a rule down. A rule that has to grow re-records with `uv run python -m tests.v2.rules_budget --record`, and the PR says so: the record's diff is where a reviewer sees the growth and asks what the new words read.
1919

@@ -117,6 +117,7 @@ The marker's unit is the whole rule, so a clause that reaches past the tolerated
117117
- [T2 — the katakana middle dot divides like a space](T2-katakana-middle-dot-divides-like-a-space.md)
118118
- [T3 — the interpunct's flank guard](T3-interpuncts-flank-guard.md)
119119
- [T4 — the bare letter as an initial](T4-bare-letter-as-an-initial.md)
120+
- [T5 — the written shape of an initial](T5-written-shape-of-an-initial.md)
120121

121122
## [Ambiguity & tie-breaking (A)](A-ambiguity-and-tie-breaking.md)
122123

‎docs/design/rules/S10-trailing-roman-numeral.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ S10. Rationale: a roman numeral ending a name is its generation,
99
up to X it does not, by their shape alone — unless the word in
1010
front of it is written as an initial, which makes it a name word
1111
too; in a name written wholly in one case a bare letter in front
12-
is written as one (T4). A single letter written with a period is an initial, and a
12+
is written as one (T4). A single letter written with a period is an initial (T5), and a
1313
numeral past X is a name word. A numeral of one letter needs
1414
words to spare, as a bare ambiguous acronym does (S4): as the
1515
second of two name words it is the family name, a title in front
@@ -64,5 +64,5 @@ S10. Rationale: a roman numeral ending a name is its generation,
6464
as one with the numeral in front of it, the run taking both, as
6565
1.4.0 read it.
6666
"John Smith V Ph. D." → suffix="V Ph. D."
67-
Signal: a shape: a roman numeral ending the name, an initial in front of it being where the shape stops deciding
68-
history: decisions/S10 · interacts: S2, S4, S7, C1, C9, P2, P3, M2, H1, H3, T4, C12 · implemented: nameparser/_pipeline/_assign.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py
67+
Signal: a shape: a roman numeral ending the name, a one-letter one gated on a word to spare, an initial in front of it being where the shape stops deciding
68+
history: decisions/S10 · interacts: S2, S4, S7, C1, C9, P2, P3, M2, H1, H3, T4, T5, C12 · implemented: nameparser/_pipeline/_assign.py, nameparser/_pipeline/_group.py, nameparser/_pipeline/_pieces.py, nameparser/_pipeline/_vocab.py

0 commit comments

Comments
 (0)