fix(changelog): dedup fragment-vs-CC-subject at generation layer (#258) #262
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!262
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/258-dedup-fragment-vs-cc-subject"
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?
What
Generation-layer dedup: when a
changelog.dfragment and a conventional-commit subject both reference the same issue ((#NNN)),release-prep.shnow suppresses the CC-subject bullet in favour of the fragment prose.Why
tmux-tell#630 (first toolkit-driven cut) surfaced the gap: v0.23.0 CHANGELOG listed
#623twice — once from the fragment, once from the CC-subject parser. Manual dedup was the v0.1 documented workaround, but it doesn't survive rolling-PR regeneration on rebase (wiped twice in one session before the merge). Durable fix: generation layer.Changes
scripts/lib/changelog.sh: newchangelog_dedup_cc_by_fragment_refs FRAGMENTS_FILE COMMITS_FILE. Extracts all(#NNN)refs from fragment output; filters CC commits output, suppressing bullet lines whose ref is already covered. Each suppressed line is logged to stderr as[changelog-dedup] ...for auditability.scripts/release-prep.sh: wired betweencc_categorize_commits_sinceandchangelog_merge_sections. Deduped commits file passed to merge. Comment block updated (drops the "dedupe at review time" v0.1 note).docs/conventions.md: "When to use which" §: manual-dedup guidance replaced with description of the automatic dedup-precedence rule (fragment wins;[changelog-dedup]log is auditable).tests/changelog.bats: 7 new tests covering the empirical case, partial-ref suppression, no-fragment-refs pass-through, empty-inputs, and stderr logging. Usesrun --separate-stderrto isolate stdout assertions from the dedup log; addedbats_require_minimum_version 1.5.0.changelog.d/258.fixed.md: release fragment.Test run
486/486 bats tests pass (7 new for this function).
Design call
Dedup operates on
(#NNN)refs as the identity key — same ref in fragment output + CC bullet = CC bullet is the redundant entry. The(#NNN)trailer on CC-subject bullets is the common convention; fragments that don't include a ref (prose-only) don't trigger dedup. Chose fragment-wins because the fragment is authored specifically for the release (richer narrative); the CC-subject is a mechanical extract.Closes #258
REQUEST_CHANGES — head
4ea3a56The mechanism is clean and the 7 bats pass — but verified against the real #623 data, the dedup doesn't catch its own empirical anchor. It keys on the wrong identity source.
The gap — demonstrated at source
The dedup collects
(#NNN)refs bygrep-ing the rendered fragment output. But I checked whatcategorize_fragmentsactually produces for a real fragment:categorize_fragmentsdoes not append the filename's issue ref to the prose. So a fragment whose author didn't hand-write(#623)produces zero refs → the dedup'srefs_fileis empty → it early-returns and passes the CC bullet through. End-to-end with the real #623 shape:The #623 fragment in #630 was exactly this ref-less prose (confirmed in my #630 review — the Fixed section had a prose paragraph with no
(#623), plus the- **mutation**: … (#623)bullet). So this PR, as written, would not have prevented the #630 double-render — the incident it cites as the anchor. The 7 tests pass only because every fixture fragment includes(#NNN)in its prose, which the real fixtures don't.The fix — key on the fragment filename, not scraped prose
A fragment's canonical identity is its filename:
623.fixed.mdis issue #623, regardless of whether the prose mentions it. Derive the dedup ref-set from thechangelog.d/<NNN>.<type>.mdfilenames (the caller,release-prep.sh, already has the dir) rather than from(#NNN)in the rendered output. Then a ref-less623.fixed.mdstill contributes#623→ the CC bullet is suppressed → the actual #630 case is fixed.(Alternative: make
categorize_fragmentsappend(#NNN)from the filename to each rendered entry — arguably a UX win too, every CHANGELOG line then carries its ref — but that's a larger rendering change. Filename-derived ref-set in the dedup is the minimal fix.)Add the missing test
The current 7 fixtures all carry the ref in prose. Add the real shape: a ref-less
623.fixed.mdfragment + a(#623)CC bullet → assert suppression. That's the case that reproduces #630; it's the one the suite is missing, and it would have caught this.Otherwise sound
The awk two-file pass,
--separate-stderrstdout isolation, empty-input handling,[changelog-dedup]audit log, and the conventions.md rewrite (manual→automatic) are all good — the mechanism just needs to draw its ref-set from the canonical (filename) identity so it covers ref-less fragments, which are the common case.This is a validation-axis miss: the fixtures verify "ref-bearing fragment dedups," but the AC is "the #623/#630 case dedups," and #623 was ref-less. Re-request after the filename-keyed ref-set + the ref-less bats case, and I'll re-run it against the real shape. (Also note: this strengthens the #261 adjacency — until this genuinely dedups, the #261 fix-commit+fragment pair would still double-render on a cut.)
4ea3a5648ff7de7d9843APPROVED — head
f7de7d9Re-ran the exact case that defeated the prior version. It's genuinely fixed now.
Verified by re-running the real #623 shape
The prior head passed its 7 bats but failed the actual #623 data (ref-less fragment). I reproduced that same shape against this head:
Suppressed — the filename-derived id (
623.fixed.md→#623) matches the CC bullet's(#623)even though the prose carries no ref. That's the #630 empirical anchor, now actually closed. ✓The fix is the right one
changelog_dedup_cc_by_fragment_refs FRAGMENTS_DIR COMMITS_FILE— keys on the canonical filename identity, not prose-scraped refs. ✓Otherwise unchanged + good
The awk filter,
[changelog-dedup]audit log, empty/absent-dir handling, and the conventions.md manual→automatic rewrite all carry over correctly.The validation-axis is now aligned — fixtures test what the AC actually requires, and I confirmed it on the real shape rather than the fixtures. Clean. Clear to merge.
Per the agreed sequence: this unblocks merge-fixed-#262 → then rebase #261 → ping me to re-pin at the rebased head → merge #261. No cut in the window.