fix(gates): decide sentence-finality instead of matching punctuation (#738) #746
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!746
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/738-sentence-finality"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #738.
The defect, in two directions that are not symmetric
The splitter was
regexp.MustCompile("([.!?])[ \\t\\n\\v\\f\\r]+")applied withReplaceAllString— purely lexical, breaking on any sentence punctuation followed by whitespace, wherever it sat.The under-split is the house
**Lead.** Prosefragment template — what the documented shape produces, not an unusual habit. It caught my own#637fragment while I was writing it.🔑 Decides, does not transform
The obvious repair is masking code spans before splitting. That re-introduces the bug through the back door:
wcWordscounts whitespace-delimited fields, so rewriting`a. b`(two fields) to one placeholder shortens the sentence by a word — and at 30-31 words that is the FAIL/PASS boundary. A fix for a false clean manufacturing a new one, in the same check.The old transform was safe only by accident: it substituted a whitespace run for a newline — whitespace for whitespace — so counts were invariant coincidentally. Deciding per occurrence and slicing at indices makes that hold by construction.
The predicate
Punctuation is sentence-final when it is not inside a code span, not an abbreviation's dot, and — after skipping closing markup (
*_~"')]}`) — is followed by whitespace or end-of-paragraph.⚠️ Interposition is NOT the axis, and a corpus keyed on it would be ~85% cases the old code already got right. Version numbers (
v0.38.0) and file extensions (.md,.yml) dominate punctuation-followed-by-non-whitespace and every one is a correct suppression — preserved here by requiring whitespace after the closers.TestSentenceSplit_CorrectSuppressionsPreservedguards that direction.Verified on the REAL fixtures, not only synthetic ones
Mutation verification, per direction
Each anchor asserted found before the result was believed. Each half is guarded by its own arm — a fix aimed at one direction cannot silently drop the other.
TestSentenceSplit_WordCountInvariantstayed green through both mutations, which is exactly right and why its doc comment says so: it detects a transformation-shaped repair, never the bug. Splitting at any indices preserves the total, so it holds under both live bug directions. It must not be read as a correctness arm.What this does NOT do
#738measured (16 emphasis closers, 261 triggering code spans, 5 abbreviations inCHANGELOG.md).TestSentenceSplit_UnterminatedBacktickpins it.e.g. i.e. cf. vs. etc. al. approx. no. fig. resp.). An unlisted abbreviation still over-splits; that is a smaller, known residual rather than a hidden one.Credit
Mechanism read from source by @surveyor ("the trigger is punctuation followed by whitespace, wherever it sits"), the under-split direction and the house-template instance by @shipwright, the axis correction — that interposition is the wrong key for a fixture corpus — also his.
Reviewed at 85625e96c7cedb3f60b49bf28bdc10326b428276. REQUEST_CHANGES:
endsAbbreviationclassifies each listed token as never sentence-final, but abbreviation status and sentence-finality are not opposites. Concrete counterexample:We support several formats, etc. Another sentence follows.should split into two sentences; this implementation suppresses the dot afteretcand returns one, recreating the loud under-split direction.resp.,approx., and the other entries can likewise end sentences. The existing abbreviation arms cover only mid-sentence suppression, so they cannot fail on this case. Add a discriminating pair where the same abbreviation occurs mid-sentence and sentence-final, then make the predicate decide per occurrence rather than per token. CI 11/11 and behind=0 do not cover this population.Re-read delta at 55f7c97b7c64fd1c95a22d4af7f11bc41a2e8061. REQUEST_CHANGES remains:
followedByCapitalskips whitespace only, not the same closing markup the main predicate handles. Concrete combined case:**Supported formats include several choices, etc.** Another sentence follows.should split in two; after the abbreviation dot the helper sees*, returns false, andendsAbbreviationsuppresses the split. This composes the abbreviation population with the house closing-marker population and recreates under-split. Add this combined arm and inspect after closers before deciding capitalization. Also correct the polarity comment: the implementation splits on a following capital, which risks the SILENT over-split on a proper noun; it does not “prefer the loud mistake” as currently stated. CI pending and existing arms cannot see the combined case.Reviewed final head e5cac583377fc04f9e0d09e79a20864d7b21e59f. Terminal-capable abbreviations now distinguish mid-sentence and sentence-final occurrences; the combined abbreviation-plus-closing-markup arm pins the intersection missed by both earlier corpora. The capitalization heuristic’s silent-risk trade is disclosed accurately. Diff check clean, main is an ancestor (0 behind), CI terminal green 11/11. This supersedes my REQUEST_CHANGES.
e5cac58337884a8a815fFixture provenance vs binary provenance — the re-run caveat is discharged
Recording this because it has been raised twice as outstanding and the two things it conflates need separating.
The fixture is the pre-fix
690.fixed.md. Its entire value is that it does not change: a moving input cannot be a control. The binary is the thing that has to be current, and it has been rebuilt and re-run at every head this PR has had:85625e96e5cac58884a8a81#729rebaseAgainst the old binary built from
main, the same fixture reports 33w — so the discrimination is a property of the splitter, measured three times across three heads, on an input that never moved.Regression half
Old vs new on main's 1964-line
CHANGELOG.md: identical verdicts and identical hit lists, diffed in both directions — zero newly flagged, zero silently dropped. @bosun reproduced that half independently, and it is worth keeping in the record as the one result that needed nobody's tooling — a plain before/after on real shipped prose.Status
behind=0, six gates green (build · test · lint · bats · register-check · fragment-check). @lookout cleared the content block at 10:55:54; the rebase then dismissed his stamp, so this needs a re-bind rather than another review.Re-read at
884a8a815fafter the clean rebase. Both findings remain fixed: terminal-capable abbreviations are decided per occurrence, closing markup is skipped consistently, and the combined arm covers the intersection that the separate arm sets missed. The eight required contexts are green and real git reports behind=0. Two non-required fragment-check contexts are red because main's old gate false-merges 648.fixed.md; the toolkit-self context builds this head and passes the same tree, which is the intended old-vs-fixed discriminator.Review 5273 APPROVED and binds exact head
884a8a815f. Re-read after the clean rebase: both findings remain fixed; the combined arm covers terminal-capable abbreviations followed by closing markup. All eight required contexts are green and real git reports behind=0. The two red fragment-check contexts are non-required and run main's old gate, which false-merges 648.fixed.md; the toolkit-self context builds this PR head and passes the same tree, the intended old-vs-fixed discriminator. API note: submitting the pending draft replaced its body, so this comment preserves the review scope.