Skip to content

feat(release): inject release notes from CHANGELOG.md into GitHub Releases#7

Merged
chocks merged 1 commit intomainfrom
feat/release-notes-from-changelog
Apr 11, 2026
Merged

feat(release): inject release notes from CHANGELOG.md into GitHub Releases#7
chocks merged 1 commit intomainfrom
feat/release-notes-from-changelog

Conversation

@chocks
Copy link
Copy Markdown
Owner

@chocks chocks commented Apr 11, 2026

Summary

  • Adds CHANGELOG.md (Keep-a-Changelog format) as the source of per-release narrative notes, backfilled with a [0.0.1] section covering what shipped in the first tagged release.
  • Wires goreleaser to prepend that narrative above its auto-generated commit changelog via release.header: {{ .Env.RELEASE_NOTES }}.
  • Groups the commit changelog by conventional-commit type (Features / Bug fixes / Other) so future release pages read cleanly instead of one flat list.
  • Adds a pre-goreleaser workflow step that extracts the section matching the pushed tag from CHANGELOG.md and exports it as RELEASE_NOTES. Fails fast with a clear error if no matching section exists — no more accidentally tagging a release with an empty summary.

Why

v0.0.1 was released with a commit changelog but no human-written "what's in this release" summary. This PR fixes that for all future releases while preserving the auto-generated commit list below the summary.

How the extractor works

The workflow step uses awk to extract the lines between the ## [X.Y.Z] heading and either the next ## heading or a reference-link definition line. Reference-link definitions ([text]: url) at the bottom of CHANGELOG.md are skipped rather than treated as terminators — verified against both the current single-section CHANGELOG and a synthetic multi-version file (newest, middle, oldest).

$GITHUB_REF_NAME is passed into the run: block via an explicit env: TAG_NAME: ${{ github.ref_name }} mapping, not interpolated via ${{ }} into the shell script — the safe pattern that keeps us out of the workflow injection footgun.

What this does NOT do

  • Does not retroactively edit the v0.0.1 GitHub Release page. That release already shipped with just the commit changelog. If we want to backfill it, gh release edit v0.0.1 --notes-file <extracted section> would do it in a follow-up.
  • Does not enforce conventional-commit prefixes — the groupings just degrade gracefully (everything without feat: or fix: falls into "Other").

Test plan

  • make ci passes locally (fmt, lint 0 issues, tests, build)
  • Dry-run of the awk extractor against the new CHANGELOG.md produces clean output (no leading blank line, no trailing reference links, stops correctly on ## boundaries)
  • Dry-run against a synthetic multi-version CHANGELOG extracts both the newest and a middle version correctly
  • First real-world exercise will be the next tag push (e.g. v0.0.2) — should produce a release page with the CHANGELOG narrative on top and the grouped commit list below

Follow-ups (not in this PR)

  • Update the local release skill's Go pre-flight to remind the user to add a ## [VERSION] section to CHANGELOG.md before tagging.
  • Consider whether to backfill the v0.0.1 GitHub Release page with the new summary.

🤖 Generated with Claude Code

…hangelog

Adds a Keep-a-Changelog-style CHANGELOG.md as the source of per-release
narrative notes, wires goreleaser to prepend that narrative above its
auto-generated commit changelog, and groups the commit changelog by
conventional-commit type so future releases read as Features / Bug fixes /
Other instead of one flat list.

- CHANGELOG.md: new file, backfilled with a 0.0.1 section covering the
  gate/trace/replay primitives, approval workflow, MCP server, VS Code
  extension, SDKs, and release pipeline that shipped in v0.0.1.
- .goreleaser.yaml: add release.header pulling from {{ .Env.RELEASE_NOTES }},
  expand changelog.groups with feat/fix/other buckets (existing docs/chore/ci
  filters preserved).
- .github/workflows/release.yml: add a pre-goreleaser step that extracts the
  section matching the pushed tag from CHANGELOG.md and exports it as
  RELEASE_NOTES. Fails fast with a clear error if no matching section exists,
  so we can't accidentally tag a release with an empty summary.

The extractor uses $GITHUB_REF_NAME via an explicit env: block (not
${{ }} interpolation into the run script) to stay out of the workflow
injection footgun.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chocks chocks merged commit 5888b2c into main Apr 11, 2026
1 check passed
@chocks chocks mentioned this pull request Apr 11, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant