fix(changelog): one fragment, one diagnosis, wherever the long entry sits

awk runs END on the way out of an exit from a main rule, so the length
row printed mid-file was followed by the citation row it outranks — the
internal protocol line landing inside the human-facing excerpt. Found by
claude-bot and kimi-bot in #262's first round, independently and with the
same reproduction.

The guard is the reported flag the empty-heading walk in this same
predicate already uses. The fixtures are the axis 57.md could not reach:
its over-bound entry is last, so only END's flush can print. 58.md puts
one before another bullet, 59.md before a heading and after a misplaced
cite. With lib/changelog.sh alone reverted they red, which is what the
green suite was hiding.

Refs #262.
This commit is contained in:
cndgrr 2026-08-04 12:38:13 +00:00
parent 84c73fe22e
commit 75a5b68c8a
3 changed files with 42 additions and 4 deletions

View file

@ -7,6 +7,9 @@
- The refusal distinguishes an entry carrying no reference at all from one
whose reference is present but not terminal, and names the shape to
write in both (#262).
- The 300-character bound still outranks the citation across the whole
fragment, and the outranked problem stays out of the message it lost
to: one fragment, one diagnosis, wherever in the file it sits (#262).
### Changed

View file

@ -208,6 +208,7 @@ changelog_fragment_problem() {
sub(/ $/, "", e)
len = length(e)
if (len > max) {
reported = 1
printf "long\t%d\t%s\n", len, excerpt(e)
return 1
}
@ -227,9 +228,15 @@ changelog_fragment_problem() {
}
/^[[:space:]]*$/ { next }
entry != "" { entry = entry " " $0 }
# An exit from a main rule still runs END, so a length row printed
# mid-file would be followed by the citation row it outranks — two
# lines spliced into one diagnosis, the internal protocol row landing
# inside the human-facing excerpt (#262 round 1). The reported flag is
# the same guard the empty-heading walk above uses, for the same
# reason: one diagnosis per fragment is the contract.
END {
if (flush()) exit
if (cite_kind != "") printf "%s\t\t%s\n", cite_kind, cite_excerpt
if (!reported && cite_kind != "") printf "%s\t\t%s\n", cite_kind, cite_excerpt
}
' "$file"
)"

View file

@ -420,16 +420,44 @@ check "cite: the reference need not match the filename" 0 "" \
check "cite: an over-bound entry outranks an earlier uncited one" 1 \
"57.md' has a 301-character entry" \
changelog_fragment_problem "$PF/57.md"
assert_one_diagnosis_57() {
assert_one_diagnosis() {
local count
count="$(changelog_fragment_problem "$PF/57.md" | wc -l)"
count="$(changelog_fragment_problem "$PF/$1.md" | wc -l)"
[ "$count" = 1 ] || {
printf 'wanted one diagnosis, got %s\n' "$count"
return 1
}
}
check "cite: the outranked citation problem is not reported beside it" 0 "" \
assert_one_diagnosis_57
assert_one_diagnosis 57
# The axis 57.md cannot test: its over-bound entry is LAST, so the only
# flush that can print is END's, which exits immediately. A flush from a
# main rule exits too — but awk runs END on the way out, so the citation
# row a mid-file length row outranks would print after it unless END is
# guarded. Both ways out of the walk, a bullet and a heading.
{
printf -- '- Uncited, and it comes first.\n'
printf -- '- %s\n' "$(mkchars 301)"
printf -- '- A later entry the walk never reaches (#57).\n'
} >"$PF/58.md"
check "cite: an over-bound entry that is not the last one still reports the bound" 1 \
"58.md' has a 301-character entry" \
changelog_fragment_problem "$PF/58.md"
check "cite: and it is still one diagnosis, not the protocol row spliced into it" 0 "" \
assert_one_diagnosis 58
{
printf '### Fixed\n'
printf -- '- Misplaced, and it comes first. (#59)\n'
printf -- '- %s\n' "$(mkchars 301)"
printf '### Changed\n'
printf -- '- The heading is the other way out of the walk (#59).\n'
} >"$PF/59.md"
check "cite: a heading after the over-bound entry is the same one diagnosis" 1 \
"59.md' has a 301-character entry" \
changelog_fragment_problem "$PF/59.md"
check "cite: the misplaced row does not ride along with it either" 0 "" \
assert_one_diagnosis 59
# Published sections keep their pre-rule prose (D4): reddening history is a
# wall, not a guard. Every shipped section predates the cite.