diff --git a/AGENTS.md b/AGENTS.md index 24c297de..92304477 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,14 +172,17 @@ uv run sphinx-build -b html docs dist/docs # covers those is _CORPUS_CLAIMS, which records what every rule claims # -- its regex's corpus reach, its roles, and which names -- and so # needs no notion of how a copy is spelled. -# FOUR rosters are keyed by FILENAME and checked by EQUALITY, so a new +# SIX rosters are keyed by FILENAME and checked by EQUALITY, so a new # ledger must be enrolled in every one of them on the day it lands, even -# empty -- each fails loudly and names itself, but that is four separate -# red runs if you add them one at a time. Three sit in the test module -# and one in tools/differential/compare.py; a fifth, compare.py's -# _RECORDED_DIFFS, is enrolled transitively, since the guard holds -# set(_RECORDED_DIFFS) == set(_CROSS_RULE_WINNERS). Two more are keyed -# by rule CONTENT and apply only where such a rule exists. +# empty -- each fails loudly and names itself, but that is six separate +# red runs if you add them one at a time. Four sit in the test module +# and two in tools/differential/compare.py. Faster than reading this +# list: create the ledger, move DEFAULT_BASELINE, and run +# tests/v2/test_ledger_guards.py once -- every roster still missing the +# file fails by name. (Opening the 2.4 cycle found _ORDER_EXEMPTION_EFFECT +# that way; it landed with f478fb0 after this list was last written.) +# Two more are keyed by rule CONTENT and apply only where such a rule +# exists. # Required, by filename: # - _SPAN_BEARING_RULES: add the filename, mapped to the set of issue # tags whose rules carry a script-span class (empty set if none). @@ -201,6 +204,11 @@ uv run sphinx-build -b html docs dist/docs # run itself refuses, pre-worker, a ledger missing from either # shape roster -- an empty section is a statement, a missing one is # nobody having looked -- so the guard and the run agree on it. +# - _ORDER_EXEMPTION_EFFECT: add the filename mapped to [] while the +# ledger has no order-decided contest. It is the recorded negative +# control for precedes_narrower -- every contest measured with +# exemptions ignored -- so a row appears when two rules land that +# file order alone arbitrates, whether or not one declares it. # Conditional, by rule content: # - _HONORIFIC_SOURCES: if the ledger has a CJK honorific rule, add a # substring of its issue (keyed that way, not by tag) mapped to the diff --git a/docs/release_log.rst b/docs/release_log.rst index e15e2f1a..63f70bc4 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -1,5 +1,9 @@ Release Log =========== +* 2.4.0 - Unreleased + + nameparser 2.4 is under development. + * 2.3.0 - September 12, 2026 nameparser 2.3 is parsing fixes and new honorific vocabulary; diff --git a/nameparser/_version.py b/nameparser/_version.py index d7df1fcc..7a45feb7 100644 --- a/nameparser/_version.py +++ b/nameparser/_version.py @@ -4,16 +4,16 @@ #: #: Bumped when a cycle OPENS, not when it ships, so the tree says what #: it is building. Note the cost of the tuple being purely numeric: it -#: cannot carry the dev marker, so `VERSION >= (2, 2, 0)` is already -#: true here while 2.2.0 is unreleased. Compare `__version__` instead +#: cannot carry the dev marker, so `VERSION >= (2, 4, 0)` is already +#: true here while 2.4.0 is unreleased. Compare `__version__` instead #: where that distinction matters. -VERSION = (2, 3, 0) +VERSION = (2, 4, 0) #: PEP 440 pre-release/dev segment appended to the numeric version, or #: "" for a final release. Joined WITHOUT a dot ("2.0.0rc1", not #: "2.0.0.rc1"); setuptools reads __version__ as the package version. #: #: "dev" through the cycle, cleared at release. It normalizes to -#: 2.3.0.dev0, which sorts above 2.2.0 and BELOW 2.3.0, so an install +#: 2.4.0.dev0, which sorts above 2.3.0 and BELOW 2.4.0, so an install #: from master can never masquerade as the release it precedes. -PRE_RELEASE = "" +PRE_RELEASE = "dev" __version__ = ".".join(map(str, VERSION)) + PRE_RELEASE diff --git a/tests/v2/test_ledger_guards.py b/tests/v2/test_ledger_guards.py index ccff26f9..9a24336a 100644 --- a/tests/v2/test_ledger_guards.py +++ b/tests/v2/test_ledger_guards.py @@ -354,9 +354,8 @@ def test_script_ranges_membership_is_decided() -> None: "fix(#298)", # the 间隔号 lookahead }), "expected_since_2.1.0.toml": frozenset(), # 2.2 cycle: no span-bearing rule - # open cycle: its one rule, fix(#462), is a Latin letter shape and - # copies no script range - "expected_since_2.2.0.toml": frozenset(), + "expected_since_2.2.0.toml": frozenset(), # 2.3 cycle: no span-bearing rule + "expected_since_2.3.0.toml": frozenset(), # open cycle, no rules yet } #: The leading `fix(...)`/`feat(...)` tag of a rule's `issue`, which is @@ -3367,6 +3366,7 @@ def _claim(rule: dict) -> _Claim: _Claim(17, ('_ambiguities', 'family', 'given', 'middle', 'suffix', 'title'), "ef4a7afe791a", None), }, + "expected_since_2.3.0.toml": {}, # open cycle, no rules yet } @@ -3467,8 +3467,9 @@ def test_every_rule_claims_the_recorded_share_of_the_corpus() -> None: #: default-order only, the blind spot `orders` (#468) opened. One copy #: of each fact, since two means one of them goes quietly stale. _CROSS_RULE_WINNERS: dict[str, dict[str, str]] = { - # open cycle: one rule, so nothing for a second one to contest "expected_since_2.2.0.toml": {}, + # open cycle: no rules, so no contest + "expected_since_2.3.0.toml": {}, "expected_since_1.4.0.toml": { # Spelled out since #508: the bare `fix(comma-family)` this row # carried is a prefix of THREE rules in this ledger (measured @@ -4978,6 +4979,7 @@ def test_a_rule_reaching_no_corpus_name_says_why_it_is_kept() -> None: ], "expected_since_2.1.0.toml": [], "expected_since_2.2.0.toml": [], + "expected_since_2.3.0.toml": [], # open cycle, no rules yet } diff --git a/tools/differential/compare.py b/tools/differential/compare.py index 549a344d..9c2e62fc 100644 --- a/tools/differential/compare.py +++ b/tools/differential/compare.py @@ -31,7 +31,7 @@ FIELDS = ("title", "first", "middle", "last", "suffix", "nickname", "maiden") -DEFAULT_BASELINE = "2.2.0" +DEFAULT_BASELINE = "2.3.0" REPO_ROOT = HERE.parents[1] #: The v2 API's names for the same seven roles FIELDS names in v1 #: vocabulary. Both are compared from baseline 2.0 on. @@ -1746,8 +1746,9 @@ class _ShapeMismatch(NamedTuple): #: every run. 'Nguyen, Van' is classified by nothing only because it #: diffs from nothing. _RECORDED_DIFFS: dict[str, dict[str, tuple[str, ...]]] = { - # open cycle: one rule, so nothing for a second one to contest "expected_since_2.2.0.toml": {}, + # open cycle: no rules, so nothing for a second one to contest + "expected_since_2.3.0.toml": {}, "expected_since_1.4.0.toml": { "Andrews, M.D.": ("given", "suffix"), "田中, 太郎さん": ("given", "suffix"), @@ -2151,6 +2152,7 @@ class _ShapeMismatch(NamedTuple): "Lala Lajpat Rai": ("family", "middle", "suffix"), "Smith, John E, III, Jr": ("_initials",), }, + "expected_since_2.3.0.toml": {}, # open cycle, no watched name yet } diff --git a/tools/differential/expected_since_2.3.0.toml b/tools/differential/expected_since_2.3.0.toml new file mode 100644 index 00000000..952867fa --- /dev/null +++ b/tools/differential/expected_since_2.3.0.toml @@ -0,0 +1,34 @@ +# Ledger for baseline 2.3.0 -- what changes for a user upgrading from +# the previous minor. Same rule grammar as the other ledgers: `issue`, +# `name_regex` and `fields` are every one of them required. Neither +# narrowing key may stand on its own -- #451 banned a rule with +# `fields` and no regex, #456 banned the reverse -- so a rule here +# narrows by name AND by role, never by just one. +# +# Opened empty the day 2.3.0 shipped (AGENTS.md release step 8), and it +# is opened before it has anything to say because DEFAULT_BASELINE and +# the ledger are coupled: _allowlist_for treats a missing file as a +# hard error, on the reasoning that an absent ledger classifies nothing +# and would make every diff report as unexplained. So the baseline +# could not advance to 2.3.0 without this file. +# +# Do not copy rules across from expected_since_2.2.0.toml: that ledger +# classifies what 2.3 changed on top of 2.2, and a rule copied without +# checking either over-matches -- hiding a real 2.4 regression behind a +# 2.3-era label -- or never fires at all. Where a 2.4 change is visible +# from several baselines the rule may legitimately appear in more than +# one file, but each copy is checked against its own run rather than +# assumed. +# +# tests/v2/test_differential.py permits exactly one empty ledger, the +# one DEFAULT_BASELINE names -- which is this file. +# +# tests/v2/test_ledger_guards.py and tools/differential/compare.py +# enroll this file, empty, in every roster keyed by ledger filename +# (AGENTS.md release step 8 lists them). The first rule that copies a +# script range, a CJK honorific or a Latin vocabulary has to be +# recorded in its roster or the sweep fails. +# +# There is deliberately no `change = []` line. TOML forbids appending a +# [[change]] table to a statically defined array, so that line would +# block the first entry.