Skip to content

Conflict-free CHANGELOG via CHANGELOG.d/ sub-pages + aggregator #222

Description

@Delqhi

Problem

When 5+ PRs land in a single day, every one touches CHANGELOG.md (added an entry to the [Unreleased] block), AGENTS.md ("Last verified against main"), and cmd/sin-code/main.go (subcommand registration). Every rebase produces a 3-way merge conflict in those files. The current mitigation (manual marker-stripping) works but is per-PR hand-work.

Why

The Linux kernel, Python, Rust, and Postgres all use a fragments/ or CHANGELOG.d/ directory with one file per PR, then an aggregator script builds the final CHANGELOG.md. The result:

  • No merge conflicts in CHANGELOG.md (every PR adds a new file, not edits)
  • The final output is deterministic (sort order = PR number or file name)
  • Reviewers can see one PR's changelog entry in isolation

Proposal

Layout

CHANGELOG.d/
├── _template.md        # the format spec
├── 218-lifecycle.md
├── 219-triage.md
├── 220-grill.md
└── ...
CHANGELOG.md           # generated, do not edit directly
scripts/build_changelog.sh

Per-PR fragment format

<!-- one file per PR, named <PR>-<slug>.md -->
### Added — <feature name> (PR #<n>)

- <bullet 1>
- <bullet 2>
- <bullet 3>

Aggregator

$ scripts/build_changelog.sh
# Concatenates CHANGELOG.d/*.md (sorted by PR number),
# prepends the static header from CHANGELOG.md, writes the result.

Pre-commit hook

$ cat .git/hooks/pre-commit
#!/bin/sh
exec scripts/build_changelog.sh --check
# Exit 1 if CHANGELOG.md is out of sync with CHANGELOG.d/.

Scope

  • v0: implement scripts/build_changelog.sh + pre-commit hook + migrate the existing [Unreleased] block to fragments
  • v1: extend to AGENTS.md (the "Last verified against main" line)
  • v1: extend to cmd/sin-code/main.go subcommand list (a static list + a generated test)

Acceptance criteria

  • scripts/build_changelog.sh produces byte-identical CHANGELOG.md from the existing [Unreleased] block + a few new fragments
  • Pre-commit hook refuses a PR that adds a CHANGELOG.d/ file but does not run the aggregator
  • Two simultaneous PRs each adding one fragment can be rebased without conflicts
  • The operator workflow is: echo '### foo' > CHANGELOG.d/222-foo.md && git add CHANGELOG.d/ (one file, no conflict possible)

Estimate

~2 hours for the spike + script + hook. Then a migration PR. Then a follow-up for AGENTS.md / main.go.

Backwards compatibility

The aggregator preserves the existing CHANGELOG.md format. Old PRs that edited CHANGELOG.md directly are grandfathered; new PRs use fragments. The migration PR converts the existing [Unreleased] block into one CHANGELOG.d/_unreleased.md so the initial state is clean.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions