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
4 changes: 2 additions & 2 deletions .claude/agents/design-docs-reviewer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: design-docs-reviewer
description: Reviews changes to docs/design/ (rules.md, decisions.md, mechanisms.md, and AGENTS.md itself, which carries the conventions the other three rest on) for the failure modes that this doc system's own tests cannot catch β€” unreproducible measurements, rules that contradict the decision entries they rest on, statements that contradict their own examples, and prose edits that change what the doc parsers see. Use before merging any PR that touches docs/design/, and after distilling a design session into those files.
description: Reviews changes to docs/design/ (rules/, decisions/, mechanisms.md, and AGENTS.md itself, which carries the conventions the other three rest on) for the failure modes that this doc system's own tests cannot catch β€” unreproducible measurements, rules that contradict the decision entries they rest on, statements that contradict their own examples, and prose edits that change what the doc parsers see. Use before merging any PR that touches docs/design/, and after distilling a design session into those files.
tools: Bash, Read, Grep, Glob
---

Expand All @@ -26,7 +26,7 @@ It is the only copy on purpose. This agent used to restate the axes
and drifted three ways against it β€” a missing axis, a narrowed one,
and a corpus count whose correction was recorded there and never
reached the copy here (#473,
decisions.md#review-agent-single-source). Do not reintroduce a
decisions/review-agent-single-source). Do not reintroduce a
summary.

## How to run
Expand Down
50 changes: 26 additions & 24 deletions AGENTS.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ The exception is an acronym that is also a name word, such as ``meng``
or ``edd``. A mask applies wherever its word stands, so it would
re-spell a person called Meng or Edd, and these get none: they repair
in plain capitals. The reasoning is the Excluded block for
``CAPITALIZATION_EXCEPTIONS`` under ``R4`` in
``docs/design/decisions.md``.
``CAPITALIZATION_EXCEPTIONS`` (meng, edd, lac, ded) in
``docs/design/decisions/vocabulary-exclusions.md``.

.. doctest::

Expand Down
20 changes: 11 additions & 9 deletions docs/design/AGENTS.md

Large diffs are not rendered by default.

1,778 changes: 0 additions & 1,778 deletions docs/design/decisions.md

This file was deleted.

25 changes: 25 additions & 0 deletions docs/design/decisions/3-0-reevaluations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 3-0-reevaluations β€” decisions shaped by the v1 shim

Promoted 2026-08-15 from session memory (Derek's 2026-07-30 ask; promotion approved 2026-08-15). Discipline when appending: mark each entry (A) "would decide differently without the shim" β€” real 3.0 work β€” or (B) "cited 1.4 parity but stands on its own", recorded so nobody re-litigates it. Append here whenever a design choice cites 1.4 parity or the shim as a load-bearing reason. Standing discipline for the removals themselves: every removal warns in a RELEASED version first β€” the rule established by the 1.3.0 eq/hash work (#223/#224), the reason the v1.4 milestone existed, and the reason FACADE-CONTRACT's "warning-free on 1.4" anchor works at all. 3.0's shim removals follow the same bridge. The CONVERSE rule, declined-with-reasoning twice in the migration design (2026-07-11): a bridge warning must WAIT when the replacement does not yet exist β€” 1.4 warned on neither shared-CONSTANTS mutation nor the subclass hooks, because users had no actionable response until 2.0, and #262's contract docs shipped in the same release (warn-while-documenting is a mixed message).

- (A) v1 field vocabulary at the facade boundary: CJK semantics squeeze into first/last through HumanName while the core speaks given/family. 3.0 could drop the aliasing entirely.
- (A) Render's v1-inherited string_format defaults, including space-joined output for interpunct transcriptions (#298: str(HumanName("ε¨ε»‰Β·θŽŽε£«ζ―”δΊš")) == "威廉 θŽŽε£«ζ―”δΊš"; users reinstate the dot via custom format). 3.0 render defaults are a clean slate.
- (A) The differential harness baseline is 1.4-on-PyPI: post-shim, the migration promise it verifies dissolves; the successor baseline is presumably last-2.x. Machinery survives; corpus contracts change.
- (A) A .pyi stub for Policy (wide __init__, narrow attributes) β€” deferred from #334, see the differential-ledger Declined entry.
- (A) nameparser.config removal scope: the 3.0 schedule says
"nameparser.config in its entirety" while actually enumerating
only the five shim exports; whether the DATA modules keep the package as their home or move under the core is open, and Lexicon's public field docs cross-reference nameparser.config.particles et al. (see the maintainer note in nameparser/config/__init__.py).
- (A) `force_mixed_case_capitalization` as a second route into R5's override (2026-08-29, recorded while extracting R5). The core takes `force` per call and nothing else; the attribute reaches the same behavior only because the facade resolves it when no per-call value is given, which is v1 parity and nothing more. Measured: with it True, a bare `.capitalize()` on `HumanName('Shirley Maclaine')` leaves it `'Shirley MacLaine'` (mutator: read the result back with `str()`). Without the shim there is one route, and R5's statement could say so plainly instead of being written to cover both. Recorded as a removal CANDIDATE β€” nothing is decided here, and the bridge discipline above applies whenever it is.
- (B) Pickle-guard layout breaks landing in minors (2.1's __setstate__ breaks): the guarded-raise design is right regardless; only the in-a-minor friction is shim-era.
- (B) Positional-read-when-unlicensed as the safe direction (script_orders fallbacks, #298 dot-suppression granularity): coincides with 1.4 parity but stands alone β€” family-first is the marked case needing affirmative evidence.
- (B) The deprecation-bridge shape (#293/#354) is the template for every remaining 2.x→3.0 shim (the bridge DISCIPLINE — warn in a released version first — predates it, from #223/#224; this entry is the module-__getattr__ mechanics): PEP 562 module __getattr__ PLUS __all__ (star imports never reach __getattr__ — measured: `from ...prefixes import *` bound nothing and leaked the helper); warn per read-location rather than per process (a write-back let a vendored dependency's first read consume the only warning); and the TYPE_CHECKING split, because a module __getattr__ silently disables mypy's attr-defined checking for the whole module (measured on a py.typed package).
- (A) Parking lot from the 2026-07-06 config-model discussion, 3.0-shaped by design: scoped config override via contextvars (`with nameparser.config.use(c):`, the decimal.localcontext pattern β€” fixes test pollution and config wars while keeping the shared default), and a requests-Session-style Parser(constants) API (rejected then as a full rewrite; the shared CONSTANTS survives 2.x because it is the only config channel that reaches parses inside code users don't own β€” #262's contract framing).
- (B) The 1.3.0-era legacy-pickle property-key skip (suffixes_prefixes_titles in old blobs) rides in the shim's __setstate__ and dies with it.
- (B) The FAMILY_COMMA doctrine (rule W3): inherited from v1's lastname-comma but correct on its own terms β€” an explicit comma is stronger evidence than script.
- (A) 2026-08-17 β€” PARTICLE VOCABULARY AS LOCALE PACKS. Raised while reversing the order-independence decision above: every argument in the leading-particle thread is a language judgement wearing a position heuristic. `de` and `do` lead surnames in Vietnamese, `von` is German, `dos` Portuguese, `das` both Portuguese and a borne Bengali surname β€” and the per-word comments in nameparser/config/particles.py cite exactly these traditions as their justification. Locale packs already exist (ru, tr_az, zh, ja) and already carry vocabulary and rotations, so particles are the obvious next tenant: a caller who knows the tradition could opt into a tuned set instead of the parser splitting the difference globally. Not 2.x work β€” it needs the packs to carry vocabulary OVERRIDES rather than additions, and it would give C-i's positional qualifier a per-locale answer rather than one global one.
- (A) 2026-08-18 β€” LOCALE CONTROL OF P3's SINGLE-LETTER CARVE-OUT, and the sharper requirement it puts on the entry above: a pack would have to carry per-word POLICY, not just membership. Which single letters a tradition wants joined differs by language β€” Spanish writes "y" and would want "e" read as an initial, Portuguese wants the reverse, Dutch would want every single letter read as an initial. One global rule serves all three today: a single ALPHABETIC character in a name of fewer than four pieces stays a name word, and a bare Latin capital never joins (#383). It is v1's Google Code issue 11 fix ("john e smith") generalized to a vocabulary v1 did not have β€” the set now holds six single-letter conjunctions (y, e, ΠΈ, Ρ–, ΠΉ, و), and the carve-out reaches all of them because it counts characters, while the capital veto reaches only the Latin ones because it tests a Latin shape. That asymmetry is the visible seam and it is why #383 is open. A pack that could only ADD or REPLACE vocabulary could not express any of it; the join threshold and the initial veto would have to be per-word, per-pack values. Measured while documenting P3: "&" is single-character but not a letter, so it joins at any length ("Juan & Garcia" is one part while "Juan y Garcia" is three).
- (A) 2026-09-13 β€” SHOULD A LOCALE PACK BE ABLE TO REMOVE BASE VOCABULARY? `Locale.lexicon` is UNIONED onto the base and never removes β€” the field's own comment says so β€” which makes `conjunctions_ambiguous` (decisions/P3, 2026-09-13) expressible by a pack in ONE DIRECTION ONLY: an `nl` pack can add "y", and a `pt` pack cannot remove "e", so a Portuguese caller has to write `Parser(lexicon=Lexicon.default().remove(conjunctions_ambiguous={"e"}))` by hand. Accepted for 2.x and parked here rather than filed, because 3.0 is when a wider set of locale packs gets designed and this is part of that work (Derek, 2026-09-13). It is the 2026-08-17 PARTICLE VOCABULARY AS LOCALE PACKS bullet's "overrides rather than additions" requirement meeting a second tenant, which is what makes it a shape question rather than one pack's inconvenience.
- (A) 2026-09-13 β€” THE DIFFERENTIAL HARNESS CANNOT SEE A LOCALE PACK'S EFFECT AT ALL. `compare.py` builds one `Parser` per name ORDER and nothing else, and a `Case.locale` row projects into the corpora as a bare name string β€” no corpus row carries a locale key, verified over every `tools/differential/corpus*.jsonl` file on 2026-09-13 β€” so every corpus name is parsed under the DEFAULT `Lexicon` on both sides. A pack's effect on a name can therefore never diff, at any baseline. That is fine while packs only ADD vocabulary the default already carries most of, and stops being fine the moment the bullet above is answered yes. The shape of the fix is a `locale` carried per corpus ENTRY the way `shape` already carries an order, resolved at the baselines that ship the pack and skipped below them. Parked with the bullet above rather than filed, for the same reason: the two are one piece of work (Derek, 2026-09-13).
- (A) 2026-09-20 #397 β€” A SECOND INSTANCE OF THE SAME PER-LOCALE QUESTION, cross-referenced rather than parked again. Catalan/Polish `i` joins `conjunctions_ambiguous` beside `e`, so the marked subset now carries two letters and the 2026-08-18 bullet above β€” which counted six single-letter conjunctions and named the per-word-policy requirement β€” describes a set of seven, `i` included. Nothing in that bullet's argument moves: the subset IS the per-word policy the bullet asked for, made configurable without packs, and the pack-side half is still what the 2026-09-13 (A) bullet above asks, since a `ca` or `pl` caller still has to write `Lexicon.default().remove(conjunctions_ambiguous={"i"})` by hand. See `decisions/P3`'s 2026-09-20 bullet.
- (B) 2026-09-23 #459 β€” THE SHIM RAISES on a v1 config it once accepted: a `capitalization_exceptions` value that does not spell its key's letters and digits raises `ValueError` at the first parse (decisions/R4, 2026-09-23). Cited against the shim's never-raise rule and taken anyway, so it is recorded here: the raise stands on its own β€” a case mask has no reading for a value that re-spells its word, and the core `Lexicon` raises the same way with no shim involved β€” and only where it LANDS for a v1 caller, at the first parse rather than at construction, is shim-era. 3.0 has nothing left to decide.
- (B) 2026-10-02 #541 β€” THE SHIM DROPS, with a warning, every v1 `Constants` entry v1 could never match (decisions/R4, 2026-10-02). Cited from 1.4 parity, and it dies with the shim: the core `Lexicon` normalizes edge whitespace away and has no v1 reading to reproduce, so 3.0 has nothing left to decide.
Loading
Loading