Skip to content

Case repair reads the unjoined-particle mark, and P6 reports its fork (#407, #405) - #460

Merged
derek73 merged 6 commits into
masterfrom
fix/407-405-render-report
Aug 30, 2026
Merged

Case repair reads the unjoined-particle mark, and P6 reports its fork (#407, #405)#460
derek73 merged 6 commits into
masterfrom
fix/407-405-render-report

Conversation

@derek73

@derek73 derek73 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Bundle 4's head: two render/report fixes that share a review pass rather than any code.

Closes #407
Closes #405

Why R5 comes first

Writing #407's R4 examples exposed that rules.md never documented the case-repair gate, and that R4's statement was narrower than the parser's behavior — it promised "an already-correct name comes back unchanged" where the parser keeps any mixed-case name, correct or not.

Worse, R4's boundary example did not pin what it appeared to: "Juan McDonald" passes under force=True as well, so it was consistent with both idempotence and the gate and distinguished neither. Shirley Maclaine — mixed case, wrong, and kept — is the witness that separates them.

So the first commit extracts R5 and changes no behavior. Verified on the released 1.4.0 wheel: the refusal predates git (45a1539, 2011-02-03, the svn import), and the force override arrived in bf1e0a5 (2016-06-02, shipped in 0.4.0). capitalized_forced is a new example pseudo-field, because force is an argument rather than a policy and cannot ride an example's annotation slot.

#407 — case repair reads the unjoined-particle mark

post_rules marks a name part whose every word is particle vocabulary: nothing joins them to a name, so they are not doing a particle's work. Three views already read the mark. Case repair did not, so ANH DO capitalized to Anh do while family_base read DO as an ordinary name word.

Whole part, not standing-alone — rules.md#R2's test is "'Every word', not 'standing alone'", and anh van do has a two-word all-particle family that a narrower rule reads wrong on a real Vietnamese name.

This deviates from 1.4.0 deliberately and does not restore it: 1.4.0 returns Anh do. The accepted cost is that a degenerate all-particle family capitalizes too, so juan van der gives Juan Van Der.

How it is verified, given the gate cannot see it. compare.py compares the seven role fields plus _ambiguities; the string capitaliz does not occur in it. That is by design rather than a gap — the harness exists for parsing behavior, where role assignment is emergent across the stage fold and the blast radius cannot be read off a diff, while case repair is a four-branch pure function. The deviation is pinned by the rules.md#R4 examples and tests/test_capitalization.py, and decisions.md#R4 records the limit.

While rewriting R4's statement: McDonald was never a vocabulary exception. CAPITALIZATION_EXCEPTIONS is {ii, iii, iv, md, phd}; McDonald comes from the separate Mac/Mc convention. R4 had been illustrating its promise with the one mechanism it was not about, and now names both separately — each with a row that dies when its own mechanism is removed.

#405 — P6's attachment reports the fork it decides

rules.md#A1 requires a genuinely uncertain reading to carry a report. Van Johnson reports particle-or-given; Nguyen, Thi Van decided the same fork silently and lost the given name.

_assign's scoping — "after a comma that fixed the family, a leading given-position particle is not meaningfully ambiguous" — was true of the family, which the comma fixes, and not of the particle behind it, which P6 decides separately.

Two kinds, keyed on the reading P6 overrode rather than on the word's vocabulary:

what assign had read kind
a name word (middle) PARTICLE_OR_GIVEN
a post-nominal (suffixvd, mc) SUFFIX_OR_NAME
a never-given particle silent — nothing was declined

Keying on vocabulary instead is backwards for both collision words: Berg, Jan do reports particle-or-given, because do sits in the AMBIGUOUS acronym half and no credential reading was ever taken; and Berg, Jan de vd reports nothing, because behind de the run was read as name words. Both now have case rows.

The arms are ordered, suffix first, rather than asserted disjoint. A legal custom Lexicon adding a word to both particles_ambiguous and suffix_acronyms makes both true, and an assert there would raise on a parse — a totality violation of A1, whose only sanctioned exception is a caller's segmenter.

Beethoven, Ludwig van now carries a report. The textbook-correct Dutch listing gains one because the parser genuinely cannot distinguish it from Nguyen, Thi Van — the ASCII transliteration is the same string in both traditions. That is the most user-visible consequence of this PR.

post_rules gains ambiguities in the stage-ownership map, and the ParseState docstring twin moves with it.

Ledger

Seven names gain an ambiguity kind. The plan called for a new _ambiguities-only rule per ledger; that explains nothing, because classify matches on fields being a superset of the whole diff and these names already diff in middle/family from #379/#380. The existing rules were widened instead, each still satisfying #452's declared == union check. 1.4.0 needs nothing — _ambiguities cannot enter a diff below baseline 2.0.

Verification

  • 6116 passed, 156 skipped, 9 xfailed; mypy and ruff clean.
  • Gate green at all three baselines: corpus 1094, intentional diffs 229 / 194 / 102, unexplained: 0. Per-heading counts diffed before and after: identical.
  • Every example row mutation-checked. Two inert rows were caught in review and withdrawn rather than shipped.

Review round

A four-agent review (code, tests, comments, error handling) found no Critical issues and nine Important ones, all fixed in the final commit. Highlights: the emitted detail said the run is "read as the family's particle" where R2's later mark can make every other view read it as a name word — reworded to name the attachment instead; the detail text and reported token span were pinned by nothing (both mutations left the suite green) and now have assertions; the release log said the tussenvoegsel fix moved seven names where the gate says nine; and AGENTS.md had re-landed two stale claims in a line this PR rewrote.

The test pass ran 22 mutations and found no inert row — every new test, cases.py row and rules.md example is killed by a mutation of the exact expression it pins. Totality was checked across ~250,000 parses under adversarial lexicons, randomized vocabularies, four locale packs and 24 policy combinations: zero exceptions.

Three issues were spun off from the review: #459, #461 (both found while measuring), and #458 above.

Out of scope but adjacent, called out deliberately

Spun off, not fixed here

🤖 Generated with Claude Code

derek73 and others added 5 commits August 29, 2026 11:29
capitalized() acts only on a name written entirely in one case; a
mixed-case name is kept as written, and whether its casing is right
does not enter into the decision. rules.md never said so, and R4's
statement described the narrower 'an already-correct name comes back
unchanged'.

R4's boundary did not pin what it appeared to: 'Juan McDonald' passes
under force=True as well, so it distinguished the gate from
idempotence not at all. R5's witness is 'Shirley Maclaine' --
mixed-case and wrong, and kept -- and its boundary sits on the forced
row, where R5's effect is absent rather than present.

An earlier draft said the name is kept 'whether or not its casing is
correct'. That promises a branch nothing can witness: for a
mixed-case name repair would not alter, kept and repaired are the
same string, so no example separates a parser with the gate from one
without it. Two rows were tried and withdrawn -- one inert, one
redundant with the row above it -- before the statement was reworded
to claim only what is observable. decisions.md records the arc.

No parser change. The two halves of the rule have different ages: the
refusal predates the git history (45a1539, 2011-02-03, the initial
svn import, already opens capitalize() with the guard), and bf1e0a5
(2016-06-02, shipped in 0.4.0) added the force override around it, so
the rule as stated holds from 0.4.0 on. 1.4.0 is where it was
measured -- the last v1 release -- not where it came from.

The override has two routes, so the statement names neither: besides
the per-call argument, the facade honors the v1 constant
force_mixed_case_capitalization, and with it set a bare capitalize()
repairs mixed case. decisions.md carries the mechanism and a 3.0
removal-candidate line; rules.md stays implementation-free.

capitalized_forced is a new example pseudo-field: force is a per-call
argument, not a Policy, so it cannot ride the annotation slot.

decisions.md also records the debt this leaves. R4 keeps one
falsehood -- 'Juan Mcdonald' is refused despite the
vocabulary-exceptions promise -- and one ambiguity, 'already-correct'
meaning idempotence to the rule and bearer-correct to the reader.
The second wants disambiguation, not a carve-out for deliberately
single-cased names, which R5's rationale declines. R4 is amended in a
later commit; this one touches it only on its pointer line.

Three mechanical riders the doc guards and conventions require:
_render.py gains a rules.md#R5 citation comment on the gate line (no
code change -- implemented: is checked against the modules that cite
the rule); corpus_rules.jsonl gains 'Shirley Maclaine', 234 -> 235,
with compare.py's _CORPUS_FLOORS comment moved to match and the floor
itself untouched. That name is already in corpus.jsonl and the
harness dedupes across corpora, so the differential population holds
at 1090 and no ledger rule is needed: the gate exits 0 with 0
unexplained at the 1.4.0, 2.0.0 and 2.1.0 baselines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`capitalized()` lowercased the words of a family name made only of
particle vocabulary, while `family_base`, `family_particles` and
`initials()` have read them as ordinary name words since #404 --
`parse("ANH DO").capitalized()` gave 'Anh do' with family_base 'DO'
and initials 'A. D.'. _cap_word now sees the token's tags and skips
the particle lowercasing when UNJOINED_TAG is present.

The mark is consulted for the WHOLE PART, not for a particle standing
alone: `anh van do` has family `van do`, two particle words and
neither alone, and a standing-alone test would read the same surname
one way behind a given name and another way alone.

Only the PARTICLE conjunct is gated. Gating the conjunction conjunct
too would be a NO-OP, not a mistake: a part is marked only where every
word in it carries "particle", `particles` and `conjunctions` are
disjoint, so a part holding a conjunction is never marked and no
conjunction token can carry the mark. The code comment, the release
bullet and the new test all say so now -- an earlier draft of each
claimed R3 gives "a conjunction contributing nothing even in an
all-particle part", which describes a configuration the parser cannot
produce (`der, y van` keeps its lowercase `y` because the `y` is a
GIVEN-part conjunction, not a word of the all-particle family).

Deliberate 1.4.0 deviation, not a restoration: the released wheel
returns 'Anh do'. Accepted cost, agreed beforehand -- a degenerate
all-particle family capitalizes too, `juan van der` -> 'Juan Van Der'.

docs/design/rules.md#R4 is restated, discharging the two debts
decisions.md#R5 recorded: the unscoped vocabulary promise (the gate
refuses before any vocabulary is read) and the ambiguous
"already-correct", now disambiguated as correct-by-repair's-own-
conventions. A third error surfaced while rewriting those words and
is fixed too: McDonald is not a vocabulary exception at all -- the
shipped exceptions are II/III/IV/M.D./Ph.D. and Mc/Mac is a separate
convention -- so the statement now names one of each. R4's boundary
moves to `juan de la vega` and its inert `"Juan McDonald"` row becomes
a `capitalized_forced=` row that asserts what repair applies.

That correction then needed a witness of its own. Naming two
mechanisms where the rows only covered one reintroduced the very
defect the rewrite was repairing: emptying the exceptions map left all
nine R4/R5 rows green, none of their inputs holding a word the map
carries. R4 gains `"john smith phd" -> capitalized="John Smith Ph.D."`,
which is the only row of the ten that dies when the map is emptied
(`John Smith Phd`). Chosen for having the dullest possible parse and a
replacement no other rule can produce -- nothing else in the repair
inserts periods -- so the row pins the map and nothing else.

Reframing, on Derek's correction: case repair is not about
correctness. Mixed case is the writer making an explicit choice and
repair defers to it rather than judging it right or wrong. R4's
closing clause no longer says a bearer's spelling is unspared --
which contradicted R5 -- but that a SINGLE-CASE spelling is repaired
even where its bearer meant it, nothing in the text marking it as a
choice. decisions.md#R5's withdrawn-witness bullet retires the
bearer-correct frame it was written under, and records that needing a
contestable claim about Dutch orthography was the symptom of it.

R5's override clause is reworded from `unless repair regardless of how
the name is cased was asked for` to `unless repair was asked for
anyway`. The old nine words carried a second reading -- that the
repair disregards the input's casing -- which is exactly the property
the entry below measures and falsifies, in nearly its own vocabulary.
A reader taking it that way would run the re-casing test, land on
`Velasquez y Garcia, Dr. Juan Q.`, and conclude the rule was wrong
when only the phrasing was.

R5 also gains `"SHIRLEY MACLAINE" -> capitalized="Shirley MacLaine"`.
It is the only row in that block that fails when the gate is narrowed
to lowercase-only; until now R5 stated that repair acts on a name
written entirely in one case and witnessed only the lowercase half.

MEASUREMENT CORRECTION, recorded rather than shipped as proposed. The
invariant that forcing ignores the given casing does NOT hold. Over
the 1094 corpus names, `capitalize(force=True)` differs from
uppercasing then repairing for 63 names and from lowercasing for 16:
uppercase is the WORSE direction, and 38 of the 63 parse
byte-identically and diverge inside the repair, so they are not
parse-level. v1's initial carve-out is the mechanism (a one-letter
conjunction is initial-shaped once uppercased), and 1.4.0 does the
same. The property is pinned in tests over names with no single-letter
word whose class case decides, with `juan y garcia` beside it as the
recorded exception, and is deliberately NOT stated in rules.md: its
examples are keyed on input strings, so stating it would invite the
re-casing test that falsifies it. The repair-level half is illustrated
with `Velasquez y Garcia, Dr. Juan Q.`, whose partition is
byte-identical either side; the space-written form of that name is a
member of the 25 whose roles do move.

decisions.md#R4 records the scope choice, the deviation, the cost, and
the VERIFICATION LIMIT that matters most: compare.py compares the
seven role fields plus _ambiguities and never `capitalized()`, so no
ledger run can see this deviation at any baseline -- half of
mechanisms.md#FACADE-CONTRACT's promise cannot be kept here. The
rules.md examples and the v1-facade tests stand in its place; the ones
that pin the fix are mutation-checked against deletion of the tag
consult, and the `juan de la vega` control passes that mutation by
design, which is what makes it a control. A follow-up issue for a
capitalization column is left as a forward reference; the entry
records why one was not added now (1.4.0 and 2.1.0 already disagree on
`capitalize(force=True)` for 139 of 1090 corpus names, nearly all
echoes of parse diffs the field comparison explains, so it needs a
diff-of-diffs design rather than a column).

Gate: corpus 1094 (was 1090 -- `anh van do`, `juan de la vega`,
`SHIRLEY MACLAINE` and `john smith phd` are new), intentional diffs
229/194/102 at 1.4.0/2.0.0/2.1.0, unexplained 0 at all three --
unmoved, as expected for a change to an uncompared field.
corpus_rules.jsonl 235 -> 240; its floor comment updated, the floor
itself unchanged. Ledger guards pass with no reach recount.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rules.md#A1 requires a genuinely uncertain reading to carry a report.
P6's tussenvoegsel attachment creates such a fork and reported
nothing: 'Van Johnson' reports particle-or-given while 'Nguyen, Thi
Van' decided the same fork silently and lost the given name.

Two kinds, keyed on the reading the attachment OVERRODE rather than on
what the words are. Where assign had read the run as a post-nominal,
that is what was declined (suffix-or-name); otherwise, where the run
holds an ambiguous particle, the name-word reading was declined
(particle-or-given); otherwise nothing was decided and nothing is
reported. Keying on vocabulary instead gets both collision words
backwards: 'do' is suffix vocabulary yet reports particle-or-given,
having never been read as a credential, and 'Berg, Jan de vd' reports
nothing though 'vd' is. Both are pinned in cases.py.

The arms are ordered, not asserted disjoint. The plan's premise -- a
suffix-vocabulary particle arrives suffix-roled and IS the whole run
-- holds for the shipped vocabulary (0 both-arm runs over 29,400
generated names) but not for the rule: a caller's Lexicon can put one
word in both sets, and the assert fired on 'Berg, Jan zz'. A1 forbids
raising from a stage, so the suffix arm wins by statement.

assign's 'deliberately not emitted' scoping was true of the family --
which the comma fixes -- and not of the particle behind it.

post_rules gains ambiguities in the stage ownership map.

Ledger: no new rule. Against 2.0.0 and 2.1.0 these seven names already
diff in middle/family (or family/suffix) from #379 and #380, and
classify matches on `fields` being a superset of the whole diff, so an
_ambiguities-only rule explains none of them. The two existing rules
are widened instead, in both ledgers; each still passes #452's
declared == union check. 1.4.0 needs nothing. Gate output is
byte-identical before and after at all three baselines: 1094 names,
229/194/102 intentional, 0 unexplained, no per-heading count moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R4's verification-limit entry pointed forward to an unfiled issue for a
capitalization column in the differential harness. That issue is not
coming: the harness is scoped to parsing behavior, where a role is
emergent across the eight-stage fold and the reach of a change has to be
measured because it cannot be read off the diff. Case repair is a pure
function of (word, role, tags, lexicon) with four branches behind one
gate, so a change to it is legible in the change; a case column would
mostly re-report parse diffs the field comparison already explains, and
the residue belongs in unit tests. The entry now states that as a scope
rather than as a gap, and the following bullet is rewritten as a settled
decision instead of a deferral. The load-bearing fact is kept: compare.py
compares the seven role fields plus `_ambiguities` and never
`capitalized()`.

Stale prose the branch's two behavior changes left behind. The
AmbiguityKind docstrings are the API reference through autodoc and were
written for the pre-#405 world: PARTICLE_OR_GIVEN said the fork is
reported for a particle "at the head of a name" in "two shapes", where
P6's attachment now makes a third at the tail after a family comma, and
SUFFIX_OR_NAME's coverage list did not include the family-comma
abbreviation it now reports. docs/concepts.rst told readers that "the
comma paths stay quiet on purpose", which #405 made false; it now says
what a comma actually settles and names the listing that still reports.
No other site had prose to move -- AGENTS.md's ambiguity bullet was
already corrected by the #405 commit, and README.rst, docs/usage.rst,
docs/customize.rst, docs/migrate.rst and docs/modules.rst carry only
working-particle examples that this branch does not reach.

corpus_rules.jsonl's count in _CORPUS_FLOORS read 240; the #405 commit
added a rules.md example after it was written. It is 241.

OUT OF SCOPE FOR THIS BRANCH, but adjacent enough to fix while the files
are open: all three expected_since_*.toml headers said `fields` "narrows
further and is optional". That stopped being true when #456/#457 landed
on 2026-08-29 -- validate_rules already refused a rule with `fields` and
no `name_regex` (#451) and now refuses the mirror shape, so both keys are
required. Each header is corrected in its own words.

Verified at HEAD: 6114 passed / 156 skipped / 9 xfailed, mypy and ruff
clean, and the gate green at 1094 names with 229/194/102 intentional
diffs and 0 unexplained at all three baselines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'all 39 ambiguous particles' describes a past bug that fired for every
member of the set; the number adds nothing to that point and had
already drifted -- particles_ambiguous holds 37 today. Same edit in the
three places the phrase appears (AGENTS.md, mechanisms.md and the
cases.py row that quotes it), plus the 'only 9 of the 39' ratio in
decisions.md's Excluded block, which is the same standing-count shape.

decisions.md#vocabulary-collisions keeps its 39: that one is labelled
'Sizes as of 2.2.0dev' and followed by 'recompute before quoting them',
which is the dated-snapshot form AGENTS.md's counting rule permits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@derek73 derek73 added bug docs Documentation fixes and updates labels Aug 29, 2026
@derek73 derek73 self-assigned this Aug 29, 2026
@derek73 derek73 added the docs Documentation fixes and updates label Aug 29, 2026
@derek73 derek73 added this to the v2.2 milestone Aug 29, 2026
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.65%. Comparing base (0847ecf) to head (e555d21).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #460   +/-   ##
=======================================
  Coverage   98.64%   98.65%           
=======================================
  Files          45       45           
  Lines        3177     3186    +9     
=======================================
+ Hits         3134     3143    +9     
  Misses         43       43           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

P6's two ambiguity details said the trailing run "is read as the
family's particle", which every other view contradicts: R2's mark
runs later in the same stage and can make the whole attached part
ordinary name words -- `de la, Jan van` gives family_base 'van de
la', initials 'J. d. l. v.' and 'Jan Van De La' forced, while the
report said "particle". Both details now name the ATTACHMENT that P6
actually decides -- the run joins the family the comma named rather
than standing on its own -- which is true whether or not R2 later
marks the part. The emit stays at the decision site.

_render._cap_word's conjunction conjunct is correctly left ungated on
the unjoined mark; only the comment's reason was false. It claimed
`particles` and `conjunctions` are disjoint, so no conjunction could
ever carry the mark; both sets are public API, and under
`Lexicon.default().add(particles={'y'})` `anh y van` has an
all-particle family whose `y` carries the mark and gives 'Anh y Van',
where gating the conjunct gives 'Anh Y Van'. The real reason is R3's
own carve-out, now stated in rules.md#R4 as well, with the
disjointness claim scoped to the shipped vocabulary (measured empty
in the default lexicon and all four locale packs). Same claim fixed
at tests/test_capitalization.py.

Also, all re-measured:

- rules.md#R4: "Mac/Mc surname convention" -> "Mac/Mc convention"
  (_MAC has no role test; `mackenzie smith` capitalizes to
  'MacKenzie Smith' with `mackenzie` in the GIVEN role).
- release_log #405 bullet: the tussenvoegsel fix moved NINE names at
  2.1.0, not seven -- fix(#379) explains 7 and fix(#380) 2; the seven
  gaining a kind are five of #379's plus both of #380's, and the two
  left out are the `Jong` pair the same bullet already lists as
  unchanged. Standing population count dropped. Its "the fix above"
  pointer was also backwards; the #379 bullet sits below it.
- AGENTS.md: TITLES cap particles_ambiguous is {freiherr, st}, not
  {st, do, freiherr}; and `Dr. Do van Johnson` / `Do St Johnson`
  emit from `assign`, not `group`, so both worked examples were
  wrong. Replaced with `Dr. St van Johnson` / `St van Johnson`
  (reach it) and `Jan Freiherr von Richthofen` / `Jan St Johnson`
  (do not), measured by stepping STAGES. Dangling concepts.rst
  cross-reference dropped; "Structure also structure" typo fixed.
- tests/v2/pipeline/test_post_rules.py: both arms' detail text and
  the reported token span are now asserted. Mutation-checked outside
  the worktree -- constant details and `tuple(run[:1])` each left the
  full suite green before, and each kills a new test now.
- rules.md#R4 + decisions.md#R4: the replace()/revise() boundary,
  recorded as an Accepted boundary rather than a gap. `_cap_word`
  keys the particle TEST on lexicon membership and the REPAIR on the
  mark, and the mark is a parse product, so a family spliced in by
  `replace()` as 'de la' stays lowercase where a parsed one gives
  'De La'. `Parser.revise()` is the supported crossing and already
  promised it -- measured, it gives 'De La' -- so its enumeration of
  the tag-driven views gains `capitalized()`, the fourth since #407.
  Recorded too: for an all-particle part the other three tag-driven
  views converge across `replace()` and `revise()` (measured over
  `de la`, `van der`, `do`, `de`, `van de la`), and on a mixed part
  they diverge while case repair agrees -- which is why the boundary
  was invisible until #407 made case repair tag-driven.
- decisions.md blast radius: 4 and 23 movers over the 1090 names the
  corpora carried before this change, 5 and 24 over today's 1094,
  both now stated (`anh van do` is the added mover).
- decisions.md / tests/test_capitalization.py: the re-casing figures
  are 63/25/38 through the v2 core and 62/25/37 through HumanName,
  whose default render spec omits the maiden name and so cannot see
  `Jane van der Berg nee y Jones`. Each now names its surface.
- decisions.md: the 11 comma-plus-trailing-particle corpus names came
  from the #379/#380 arc alone; this change added four names and none
  carries a comma.
- _state.py and Parser.revise()'s docstrings reflowed; capitalized()
  now says it reads token tags, matching initials(); the R4/R5
  duplicate example row is kept and recorded as deliberate (five
  other rows already sit under two rules apiece).

Gate green at all three baselines: corpus 1094, intentional diffs
229 / 194 / 102, unexplained 0. corpus_rules.jsonl unchanged at 241.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@derek73
derek73 merged commit 82a7bd1 into master Aug 30, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug docs Documentation fixes and updates

Projects

None yet

1 participant