rust-release-cut-changelog-promotion
Solution written to ~/solution.md, and the core algorithm was actually executed against mock changelogs to confirm it works (pre-existing dated target section merged correctly, duplicate-bucket abort fired, --check idempotency verified).
The release was "pre-stamped" (0.3.0 already in Cargo.toml/binary and an already-dated ## [0.3.0] section in CHANGELOG.md) with the real notes stranded in ## [Unreleased] above it. The trap is treating this as a line-range splice: each section owns its own ### Added/Changed/Fixed headings, so concatenating ranges emits every heading twice. The correct operation is a bucket merge — parse both sections into bucket -> entries, merge (unreleased first), emit one heading per bucket, re-date [0.3.0], and re-open an empty [Unreleased] (which must never be deleted, or the drift check re-breaks).
main, all 11 crates agree on 0.3.0 via cargo metadata | jq.tools/promote_changelog.py (full, tested source) — abort-guarded bucket merge with:[Unreleased] / one-target-section guards,###-heading detection,--check mode that re-derives and diffs the promoted state.[Unreleased] is empty and the target has one heading per bucket.gh run watch --exit-status + conclusion == success) on the exact $PREP_SHA.$PREP_SHA, gh release create --notes-file built from the promoted section.git ls-remote --tags, gh release view (draft:false), and actual asset download + sha256sum -c.The key correctness detail beyond the bucket merge: the old target section body must be dropped through the next ## heading (sections[t_i+1][0]), not just its heading line — that was a real bug I hit and fixed while testing.
# Evidence - Problem class: rust-release-cut-changelog-promotion - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-22T00:02:13.438Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Cutting the first formal release of a Rust workspace whose Cargo.toml/binary already stamp the target version but whose CHANGELOG has a populated [Unreleased] section above an already-dated [target] section. Procedure that worked: (1) parse both sections into '###' subheading buckets with a script; (2) merge bucket content (unreleased entries first) and emit ONE heading per bucket \u2014 naive line-range splicing duplicates the sections' own '### Added/### Changed' headings; (3) re-date the target section, re-open an EMPTY [Unreleased] above it; (4) never delete [Unreleased] (drift checks re-break); (5) abort-guard the script on unexpected heading counts and verify the merged structure with a heading grep before committing. Then: commit (guard full test mode), push, wait for CI success on the prep sha, annotated tag, push tag, gh release create with notes. Verify the release surface in three layers: ls-remote --tags, gh release view, artifact assets.", "environment": "<project>/hilo Rust workspace (11 crates), git + GitReins guards", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-release-cut-changelog-promotion", "provider": "openrouter", "solved_at": "2026-09-22T00:02:13.438Z", "version": "0.3.0"}Solution written to ~/solution.md, and the core algorithm was actually executed against mock changelogs to confirm it works (pre-existing dated target section merged correctly, duplicate-bucket abort fired, --check idempotency verified).
The release was "pre-stamped" (0.3.0 already in Cargo.toml/binary and an already-dated ## [0.3.0] section in CHANGELOG.md) with the real notes stranded in ## [Unreleased] above it. The trap is treating this as a line-range splice: each section owns its own ### Added/Changed/Fixed headings, so concatenating ranges emits every heading twice. The correct operation is a bucket merge — parse both sections into bucket -> entries, merge (unreleased first), emit one heading per bucket, re-date [0.3.0], and re-open an empty [Unreleased] (which must never be deleted, or the drift check re-breaks).
main, all 11 crates agree on 0.3.0 via cargo metadata | jq.tools/promote_changelog.py (full, tested source) — abort-guarded bucket merge with:[Unreleased] / one-target-section guards,###-heading detection,--check mode that re-derives and diffs the promoted state.[Unreleased] is empty and the target has one heading per bucket.gh run watch --exit-status + conclusion == success) on the exact $PREP_SHA.$PREP_SHA, gh release create --notes-file built from the promoted section.git ls-remote --tags, gh release view (draft:false), and actual asset download + sha256sum -c.The key correctness detail beyond the bucket merge: the old target section body must be dropped through the next ## heading (sections[t_i+1][0]), not just its heading line — that was a real bug I hit and fixed while testing.
# Evidence - Problem class: rust-release-cut-changelog-promotion - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-22T00:02:13.438Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Cutting the first formal release of a Rust workspace whose Cargo.toml/binary already stamp the target version but whose CHANGELOG has a populated [Unreleased] section above an already-dated [target] section. Procedure that worked: (1) parse both sections into '###' subheading buckets with a script; (2) merge bucket content (unreleased entries first) and emit ONE heading per bucket \u2014 naive line-range splicing duplicates the sections' own '### Added/### Changed' headings; (3) re-date the target section, re-open an EMPTY [Unreleased] above it; (4) never delete [Unreleased] (drift checks re-break); (5) abort-guard the script on unexpected heading counts and verify the merged structure with a heading grep before committing. Then: commit (guard full test mode), push, wait for CI success on the prep sha, annotated tag, push tag, gh release create with notes. Verify the release surface in three layers: ls-remote --tags, gh release view, artifact assets.", "environment": "<project>/hilo Rust workspace (11 crates), git + GitReins guards", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-release-cut-changelog-promotion", "provider": "openrouter", "solved_at": "2026-09-22T00:02:13.438Z", "version": "0.3.0"}