Classify every PR merged since the last release tag into a Keep a Changelog bucket, then write the
section of
. The bar is exhaustive — every merged PR accounted for, none dropped, none doubled.
-
Find the boundary. LAST_TAG=$(git describe --tags --abbrev=0)
; if none, start from the initial commit (
git rev-list --max-parents=0 HEAD
).
TAG_DATE=$(git log -1 --format=%aI "$LAST_TAG")
.
Done when you hold the boundary commit and its date.
-
Gather merged PRs. gh pr list --state merged --base main --search "merged:>$TAG_DATE" --json number,title,labels,mergedAt
.
Done when every PR merged after the tag is in the list.
-
Classify each. Sort by Conventional Commits prefix and label:
| Prefix / label | Bucket |
|---|
| / | Added |
| / | Fixed |
| Security |
| , , , , | Changed |
| Deprecated |
| Removed |
| no match | Changed (default) |
Strip the prefix from the title; capitalize the first word (
→
).
Done when every PR sits in exactly one bucket.
-
Catch ungrouped commits. git log <last-tag>..HEAD --oneline --no-merges
; for each,
gh pr list --state merged --search "<sha>"
. Commits with no PR land under
Ungrouped commits.
Done when every non-merge commit since the tag is matched to a PR or listed.
-
Write . Keep a Changelog format: one section per non-empty bucket, each entry linking its PR (
). Replace the existing
block (between
and the next
) — ask before overwriting entries an operator already edited. Keep the
marker intact.
Done when carries every merged PR under the right bucket, empty buckets omitted, and the marker survives.
-
Present for review. Show the section; let the operator re-classify or reword. Do not proceed to a release until approved.
Safe to re-run: reads
fresh each time, asks before overwriting existing entries, fetches PR data live from GitHub.