bug(changelog-gate): brevity is enforced on sentence shape, never on volume or form-consistency — the two checks that would catch it are soft #627
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#627
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Motivation
Operator on the v0.35.0 cut (PR#613): "the release cut PR also got unnecessarily
verbose since the last cut. I liked the previous slim look of the changelogs more."
The observation is right; the mechanism is not a regression. v0.34.0 consumed ZERO
fragments — its 50 entries were all commit-subject bullets, uniformly slim.
v0.35.0 is the first cut since v0.19.0 (2026-06-28, a month ago) to consume
fragments at all. Nothing changed in the composer. The slim look was the absence
of fragment prose.
Total size is the wrong axis, and it is the axis check 4 measures. v0.35.0 is
638 chars under v0.34.0 and comfortably under the 5000 soft ceiling, while being
the entry that reads as bloated.
The gate gap
Disposition of all nine checks in
internal/gates/changelog_body_check.go(origin/main):Every hard gate constrains shape within a sentence or paragraph. The only two
checks that measure volume or form-consistency are both soft warnings.
And the enforced axis pushes the wrong way. Check 7 fails long sentences, so the
remedy is more, shorter sentences. Check 9 fails long paragraphs, so the remedy is
more paragraphs. Herald's
b8d8a09this morning cleared 8 over-threshold sentencesand came out +28/-26 lines — correctly satisfying the gate while moving away from
what the operator wants. The gate rewards splitting; the reader's axis is total volume.
Check 5 is a live hit, right now. v0.35.0's
### Addedmixes four proseparagraphs (fragment-derived) with
- **decide**: refuse a prerelease cut whose computed tag already exists(commit-derived). Two sources, two shapes, one section —warned, not failed. The composer emits both and nothing reconciles them.
The rule that is enforced by nothing
changelog.d/.template.mdstates the actual convention:No check counts sentences per fragment or rejects a multi-paragraph fragment.
Four of the six v0.35.0 fragments are 2+ paragraphs. All six pass.
Scope
section with two shapes is the visible symptom.
fragment-checktime enforcing the template's own1-3-sentence / no-multi-paragraph rule, where the author can still act on it.
Cut time is too late: fragments live on
mainand recompose on every regeneration.chars-per-entry instead of chars-per-body.
Acceptance criteria
Related
b8d8a09— Herald's fragment rewrite, correct against checks 7/9/srv/CLAUDE.md§ Mechanism design — scope-at-point-of-use: a check that warnsbut cannot change the exit status is decoration
Anchor
Operator observation on PR#613, 2026-07-31. Density measured by Bosun across the
v0.34.0 / v0.35.0 entries; check dispositions read from
origin/main(a stale working-tree copy showed only checks 1-6 and would have missed 7-9).
Cross-link: #621 and #627 are two DIFFERENT defects in the same gate — not duplicates
I filed both, six hours apart, and did not check the first before filing the second.
Recording the distinction so neither gets closed as a dup of the other:
They compose badly: #627's enforced axis rewards splitting long sentences into more
short ones, and #621 means the layer where that damage could be repaired durably is not
the layer being measured.
Partially addressed 2026-07-31 by release-toolkit#631, which changed
changelog.d/.template.md— the authoring layer #621 names. Neither tracker closes on it.⚠️ Sequencing note: both live in
scripts/changelog-body-check.sh, which #607migrates to
rt changelog-body-check. A bash-side fix today must also land Go-side topreserve byte-equivalence — the two-implementation trap from #612/#622. Fixing these
before #607 means fixing each twice.
Triage against Go ahead of #607: SURVIVES THE DELETION — re-scope to Go
Measured on
main@4aecf78. Both halves of this tracker reproduce identically inrt, so deleting the bash removes the file this was written against and leaves the defect firing exactly as before.Half 1 — the two soft checks are soft in Go too
Same input, both implementations, byte-identical messages:
internal/gates/changelog_body_check.go:342and:386both returnVerdict: Warn.⚠️ My first attempt at this measurement was not a measurement. The padding I used to push the body over the ceiling also tripped a hard check, so the run exited 1 and would have read as "the gate catches it" — for an entirely different reason. The numbers above come from a rebuilt input where checks 4 and 5 are the only unhappy checks, everything else green. Worth recording because the natural way to build this fixture produces the wrong answer.
Half 2 — the fragment-time rule exists in neither
Scope item 2 asks for a per-fragment 1–3-sentence / no-multi-paragraph rule at
fragment-checktime. There is no sentence or paragraph machinery in either implementation:The control is there because a zero from a query that silently failed and a zero from a genuine absence are the same output. This one is genuine.
So the template's own "1-3 sentences … if you find yourself writing multi-paragraph prose here, move it to the PR body" is unenforced in the Go stack too, and the AC that offers "or the rule is struck from the template as unenforced" is still open on both sides.
Disposition
Re-scope the tracker to
internal/gates/before #607 lands. Every check number in the body (4, 5, 7, 9) maps 1:1 onto the Go implementation and the dispositions are unchanged, so the re-scope is a pointer change, not a re-analysis.The reason to do it before rather than after: once
changelog-body-check.shis deleted, this tracker's text reads as though it describes a file that no longer exists, and the natural reflex is to close it. Text going stale is not the same as a defect going away — the operator's original complaint on the v0.35.0 cut is served byrttoday and nothing about it has changed.One thing this triage does not settle: whether check 5 should become a FAIL. That is AC1's decision and it is a judgement call about the composer's output, not something a differential can answer. It stays open wherever the tracker points.
2>/dev/null || trueflattens downstream refusals into empty answers, defusing the three-state seams #697Implementation closeout
rt#1014 merged at exact head
b4a6c8972bafter official Sentry review 6068 and Forgejo CI 25/25 success.Decisions and evidence:
The template, architecture contract, conventions, fragment style, Go gate comments/tests, and executable controls were updated together.
Refs frankenbit/release-toolkit#1014.