feat(tooling): changelog fragments - conflict-free parallel PRs - #806
Conversation
Release notes move from CHANGELOG.md's ## [Unreleased] section (where every concurrent PR prepended to the same lines and conflicted) to per-PR fragment files under changelog.d/, compiled into the version section at release time by the new stdlib-only .claude/scripts/changelog_compile.py (check + compile subcommands: pointer-only Unreleased invariant, fragment grammar, canonical semver/date validation, downgrade + dirty-fragment + empty-section guards, exit-4 idempotent re-run that certifies only a state the compiler could have produced - canonical date, predecessor-sourced comparison link, non-empty section - and v-prefixed comparison links). - Migrate the current Unreleased content (from #801/#803/#804/#805) into four fragments, byte-identity verified via a scratch-copy compile round-trip; Unreleased is now pointer-only. - Guard test tests/test_changelog_fragments.py (51 tests): live-repo invariant, README<->compiler category parity, check/compile behavior incl. the fragment-free committed-stub recovery flow, and self-pinning docs-tests.yml path filters + step. - CI: fragment/compiler/CHANGELOG paths trigger the docs-tests lane only (new doc-snippets step; the py39 smoke job also runs the compiler check so the 3.9 floor is continuously exercised); rust-test.yml excludes the guard test from its filters (test_doc_snippets precedent) so guard-only edits skip the heavy matrix. - Instruction surfaces updated: CONTRIBUTING (new CI-enforced convention section + rollback note), CLAUDE.md source-of-truth row, bump-version (compiler flow, exit-code contract, commit-the-stub recovery, five stale statements), docs-impact, pre-merge-check (checkbox + real `check` invocation), submit-pr (embedded template + always-run warning), push-pr-update (both blocks), the PR template, the CI reviewer prompt (P2 missing-fragment / P3 direct-Unreleased clauses with a comment-only carve-out), and docs/v4-design.md's two forward obligations. - DEFERRED.md row: hard fragment gate + fragment-izing TODO/DEFERRED/ ledger collisions parked pending demand. Intended behavior change: compiled release sections list entries oldest-first within a category (ascending fragment-filename order); releases now require fragments (git-log changelog generation removed).
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessmentExecutive summary
MethodologyNo findings. Method-related changelog text is relocated existing content; estimator code and Code Quality1. Ignored fragments bypass the committed-content guardSeverity: P1 Impact: Concrete fix: Require every discovered fragment to exist as a regular file in 2. EOF-only Unreleased headers raise a tracebackSeverity: P2 Impact: Concrete fix: Parse the header with horizontal whitespace only and handle a missing terminal newline explicitly; add an EOF-header test. PerformanceNo findings. MaintainabilityNo findings. Tech DebtTracked fragment-presence gateSeverity: P3 informational Concrete fix: No action required; tracked in SecurityNo secrets or security vulnerabilities found. Documentation/TestsEmpty bullets pass validationSeverity: P2 Impact: Concrete fix: Require at least one top-level bullet with non-whitespace content and add a negative test. Validation note: the live repository check passed. The pytest suite could not run locally because Path to Approval
|
…t); EOF-only Unreleased header is a finding not a traceback; empty bullets rejected (PR #806 review P1/P2s)
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessmentExecutive summary
MethodologyNo findings. The affected estimator names appear only in relocated changelog prose. Claims checked against Code Quality1. Dirty guard still does not prove fragments match HEADSeverity: P1 — previous finding only partially resolved Impact: Concrete fix: For every discovered fragment, reject symlinks/non-regular files, require a regular blob at the same path in 2. [Newly identified] Existing release header at EOF still raisesSeverity: P1 Impact: An existing target header at EOF without a trailing newline reaches Concrete fix: Treat a missing newline as an empty section and return the normal validation error. Add an exit-4-path test with the target header ending at EOF. PerformanceNo findings. MaintainabilityNo findings. Tech DebtFragment-presence enforcementSeverity: P3 informational Concrete fix: No action required; tracked in SecurityNo secrets or additional security vulnerabilities found. Documentation/TestsIgnored-fragment regression can pass for the wrong reasonSeverity: P2 Impact: The test leaves Concrete fix: Commit the baseline repository first, then create and ignore the sole fragment before invoking the compiler. Path to Approval
|
… match their HEAD blob (regular file, byte-identical; symlinks and status-hidden configs refused); _section_nonempty EOF-safe (PR #806 review round-2 P1s/P2)
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessmentExecutive summary
MethodologyNo findings. The changelog prose is relocation-only and agrees with Code Quality1. Symlinks and special files are read before validationSeverity: P1 — previous finding only partially resolved Impact: Concrete fix: In 2. Duplicate Unreleased headers bypass validationSeverity: P1 — [Newly identified] Impact: Concrete fix: Require exactly one exact Unreleased header before validating its body. Add check and compile regressions containing two Unreleased sections, with a bullet under the second. PerformanceNo additional findings. MaintainabilityNo additional findings. Tech DebtFragment-presence enforcementSeverity: P3 informational Concrete fix: No action required; tracked in SecurityNo secrets or privilege-related vulnerabilities found. Documentation/TestsNo additional findings. The standalone compiler check passes. The focused pytest suite could not run because Path to Approval
|
…ad; exactly one Unreleased header enforced (PR #806 review round-3 P1s)
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessmentExecutive summary
MethodologyRelocated methodology proseSeverity: P3 informational Code Quality1. Duplicate release headers pass exit-4 certificationSeverity: P1 — [Newly identified] Impact: Concrete fix: Before the existing-header loop, reject duplicate semantic-version headers—preferably any duplicated release version—with exit 1. Add a regression containing two target-version sections, no fragments, and an otherwise valid comparison link. The previous symlink/special-file and duplicate-Unreleased findings are resolved at PerformanceNo findings. MaintainabilityNo additional findings. Tech DebtFragment-presence enforcementSeverity: P3 informational SecurityNo secrets or security vulnerabilities found. Documentation/TestsNo independent findings. Path to Approval
|
… (PR #806 review round-4 P1)
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessment✅ Looks good — all prior P1 findings are resolved; no unmitigated P0/P1 issues remain. Executive summary
MethodologyRelocated methodology proseSeverity: P3 informational Code QualityPrior duplicate-header finding resolvedSeverity: P3 informational PerformanceNo findings. MaintainabilityNo findings. Tech DebtFragment-presence enforcementSeverity: P3 informational SecurityNo secrets or security vulnerabilities found. Documentation/TestsNo findings. |
Summary
changelog.d/(one file per PR,### <Category>blocks; format spec inchangelog.d/README.md), so concurrent PRs from separate worktrees no longer conflict onCHANGELOG.md. The## [Unreleased]section is pointer-only between releases, CI-enforced by the newtests/test_changelog_fragments.pyin the docs-tests lane..claude/scripts/changelog_compile.py(stdlib-only, py3.9-compatible, argv-safe):checkvalidates the fragment store + pointer invariant;compile --version --datemerges fragments into the release section (category order pinned, oldest-first within category, byte-preserving contiguous joins), writes thev-prefixed comparison link, deletes the fragments, and refuses everything it can't certify - downgrades, dirty/uncommitted fragments, empty sections, non-canonical dates, wrongly-sourced or missing links; exit 4 = idempotent already-compiled re-run for/bump-version.test_doc_snippetsprecedent) so guard-only edits skip the heavy matrix.checkinvocation), submit-pr (embedded template + always-run warning), push-pr-update (both blocks), PR template, the CI reviewer prompt (P2 missing-fragment / P3 direct-Unreleased clauses, comment-only carve-out keeping the frozen negative controls clean),docs/v4-design.md(fragment now carries the flipped-row-id obligation).Intended behavior changes, stated: compiled release sections list entries oldest-first within a category; releases now require fragments (the old git-log changelog generation is removed; fragment-free cycles use a committed
### Internalstub).Rebasing an in-flight branch that edited Unreleased: move the bullet into a fragment (documented in CONTRIBUTING + changelog.d/README).
Methodology references (required if estimator / math changes)
Validation
tests/test_changelog_fragments.py(51 tests: live-repo invariant, README↔compiler parity, check negatives/positives, compile behavior incl. exit-4 certification edge cases and the committed-stub recovery flow, workflow-filter + step pins, byte-stability).tests/test_openai_review.py(264) still green after the workflow edits.Security / privacy
Changelog
changelog.d/20260830-changelog-fragments.md)