name: om-auto-update-changelog
description: Draft a CHANGELOG.md release entry in the house emoji-driven format for every PR merged since the last release, then delegate to om-auto-create-pr so it lands as a docs PR against develop. Honors the Supersede Credit Rule for carried-forward fork PRs. Use at release time.
Auto Update Changelog
Release-engineering skill. Compile a CHANGELOG.md entry for the unreleased window using the project's existing emoji-driven format, then hand the file edit off to om-auto-create-pr so it lands as a normal docs PR against develop.
When to use
- Preparing a release (
0.4.11,0.5.0, a release candidate). - After a batch of merges at the end of a sprint when the team wants a running changelog.
- Manually invoked by maintainers; NOT intended to run on a schedule — changelog entries benefit from human review of the Highlights paragraph.
Arguments
--version <x.y.z>(optional) — the release heading. Default: readversionfrom the rootpackage.json; if it matches the topmost heading already inCHANGELOG.md, bump the patch component and ask the user viaAskUserQuestionwhether to usemajor.minor.patch+1,major.minor+1.0, or a custom value.--since <value>(optional) — lower bound for merged PRs. Accepts an ISO date, a git ref, or the literallast-release(default).last-releaseresolves to the date in the topmost# X.Y.Z (YYYY-MM-DD)heading inCHANGELOG.md.--date <YYYY-MM-DD>(optional) — the date in the heading. Default: today.--dry-run(optional) — print the drafted entry to stdout; do not editCHANGELOG.mdand do not invokeom-auto-create-pr.--slug <kebab-case>(optional) — override the slugom-auto-create-pruses. Default:changelog-<version>.
Workflow
0. Resolve the window
PKG_VERSION=$(node -p "require('./package.json').version")
TOP_HEADING=$(grep -m1 -E '^# [0-9]+\.[0-9]+\.[0-9]+ \([0-9]{4}-[0-9]{2}-[0-9]{2}\)' CHANGELOG.md)
# parse "# 0.4.10 (2026-04-01)" → version=0.4.10, date=2026-04-01
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)
TODAY=$(date +%Y-%m-%d)
- If
--versionwas not passed andPKG_VERSIONequals the heading version, ask the user which bump type to use before proceeding. - If
--since last-releaseresolves to a date that disagrees withLAST_TAG's tagger date by more than 3 days, ask the user which boundary to use. - Print
Window: <since> → <date>andVersion: <version>before any file edits.
1. Enumerate merged PRs
gh pr list \
--state merged \
--search "merged:>=${SINCE_DATE} merged:<=${TODAY}" \
--json number,title,body,author,labels,mergedAt,url,baseRefName \
--limit 250
Filter to PRs whose baseRefName is the default branch (develop). Exclude PRs that touched only .ai/runs/ (these are execution-plan commits, not release work) unless the entire body says Update CHANGELOG.md for vX.Y.Z (those are prior runs of this skill — also exclude).
2. Categorize each PR
Per-PR category derivation, in priority order:
- Labels — pick the first match:
bug→fixsecurity→securityfeature→featrefactor→refactordependencies→choredocumentation→docs
- Conventional-commit prefix in PR title (
feat:,fix:,security:,refactor:,docs:,test:,chore:,ci:,build:,perf:,style:). Allow optional scope:fix(auth):. - Fallback →
chore.
Map category → section + emoji:
| Category | Section heading | Line emoji |
|---|---|---|
feat |
## ✨ Features |
✨ |
security |
## 🔒 Security |
🔒 |
fix |
## 🐛 Fixes |
🐛 |
refactor, perf, style, chore |
## 🛠️ Improvements |
🛠️ |
test |
## 🧪 Testing |
🧪 |
docs (including spec updates) |
## 📝 Specs & Documentation |
📝 |
ci, build |
## 🚀 CI/CD & Infrastructure |
🚀 |
For fix entries, replace the default 🐛 with a more specific emoji when the PR title clearly indicates one: 🔐 for auth/ACL, 💰 for pricing/orders, 🌍 for i18n/translations, 🖼️ for media, 🔄 for sync/refetch, 📦 for packaging, 🐳 for Docker, 🔧 for core/infrastructure. Match the style already in CHANGELOG.md; when unsure, keep 🐛.
3. Resolve the credited author (Supersede Credit Rule)
See the dedicated section below. For every merged PR, compute:
primaryAuthor— the GitHub handle that should appear in*(@...)*.viaAuthor— optional second handle to disclose the carry-forward path when it happened.
4. Build the line text
One-liner format matching the existing CHANGELOG style:
- <lineEmoji> <normalizedSummary>. (#<prNumber>) *(@<primaryAuthor>)*
When viaAuthor is present:
- <lineEmoji> <normalizedSummary> (supersedes #<oldPrNumber>). (#<prNumber>) *(@<primaryAuthor>, via @<viaAuthor>)*
normalizedSummary comes from the PR title with the conventional-commit prefix and scope stripped, first letter capitalized, no trailing period before the (#...) token. Keep it under 140 chars — truncate with an ellipsis only if absolutely necessary.
Multiple issue references (fixes #N) carry through — append (fixes #N) before the PR number when the PR body authoritatively closes an issue (closingIssuesReferences non-empty). Match the style on lines like … (fixes #982). (#1056) *(@mwardon)* in the current CHANGELOG.md.
5. Assemble the release entry
Prepend a new block to CHANGELOG.md above the topmost # X.Y.Z (YYYY-MM-DD) heading, preserving the --- separator:
# {version} ({date})
## Highlights
<!-- TODO: Highlights — auto-update-changelog leaves this blank for the human author to fill in. -->
## ✨ Features
- ✨ ... (#1234) *(@author)*
## 🔒 Security
- 🔒 ... (#1235) *(@author)*
## 🐛 Fixes
- 🐛 ... (#1236) *(@author)*
## 🛠️ Improvements
- 🛠️ ... (#1237) *(@author)*
## 🧪 Testing
- 🧪 ... (#1238) *(@author)*
## 📝 Specs & Documentation
- 📝 ... (#1239) *(@author)*
## 🚀 CI/CD & Infrastructure
- 🚀 ... (#1240) *(@author)*
## 👥 Contributors
- @author1
- @author2
---
# {previous-version} ({previous-date})
...
Omit empty sections entirely — the existing changelog does the same. When the entire release has a single dominant theme, optionally add subsection headers (### 👥 <Area>) inside ## ✨ Features or ## 🐛 Fixes — but prefer flat lists unless there are 5+ PRs in the same module.
6. Build the Contributors block
Deduplicated list of every handle that appears in *(@...)* lines — both primaryAuthor and viaAuthor. Order: primary authors first (by first appearance), then any via authors that did not already appear as a primary. One handle per line, leading - @.
Skip bot accounts: github-actions[bot], dependabot[bot], copilot, renovate[bot], etc.
7. Delegate to om-auto-create-pr
Stage the CHANGELOG.md edit locally, but do not commit or push yourself. Instead, invoke om-auto-create-pr with:
--slug changelog-{version}- A concrete brief:
Update CHANGELOG.md for {version} covering PRs merged between {sinceDate} and {date}.
Only CHANGELOG.md is modified. Do not change any other files.
Apply labels: documentation, skip-qa.
Let om-auto-create-pr handle branch creation (feat/changelog-{version}), the isolated worktree, the commit, the docs-only validation gate, the PR body, label normalization, the om-auto-review-pr autofix pass, and the comprehensive summary comment.
Important: this skill never runs the full validation gate itself. That is om-auto-create-pr's job, and a changelog edit is docs-only by definition.
8. Dry-run
When --dry-run is set:
- Compute the full entry in memory.
- Print it to stdout.
- Print the list of PRs consumed, the credited author for each, and any supersede detections.
- Do not edit
CHANGELOG.md. - Do not call
om-auto-create-pr.
9. Report
After om-auto-create-pr finishes, print:
auto-update-changelog: {version} ({sinceDate} → {date})
PRs consumed: {count}
Supersede detections: {count}
Contributors: {count}
CHANGELOG entry preview:
<first 10 lines of the new block>
PR: {auto-create-pr URL}
Supersede Credit Rule
The central problem this skill solves: when om-auto-review-pr carries a fork contributor's PR forward (because the fork author went quiet and the reviewer applied the fixes themselves), the merged PR's author field on GitHub is the reviewer, not the original contributor. A naive changelog generator would credit the reviewer. That is wrong — the original contributor did the work. This skill implements three detection paths, in priority order:
Path A: Supersedes #N in the PR body
om-auto-review-pr writes this template when it carries a fork PR forward. Regex (anchored to the first 20 lines of the body, case-insensitive):
^Supersedes\s+#(\d+)\b
When matched, resolve the superseded PR:
ORIG_AUTHOR=$(gh pr view {supersededPrNumber} --json author --jq '.author.login')
Set primaryAuthor = ORIG_AUTHOR and viaAuthor = mergedPrAuthor. Emit (supersedes #M) in the summary text.
Path B: Credit: original implementation by @user in the PR body
Same template, also written by om-auto-review-pr. Regex:
Credit:\s+original\s+implementation\s+by\s+@([A-Za-z0-9][A-Za-z0-9-]{0,38})
When matched, set primaryAuthor from the captured handle and viaAuthor = mergedPrAuthor. No supersedes #M suffix unless Path A also fires (it usually does).
Path C: Closing in favor of comment on the superseded PR
When neither body regex on the merged PR matches, om-auto-review-pr's carry-forward flow still leaves an authoritative trail on the original PR via the closing comment template (see .ai/skills/om-auto-review-pr/SKILL.md lines 471–477):
Closing in favor of #{newPrNumber} ({newPrUrl}).
Credit to @{originalAuthor} for the original implementation. ...
Detection is reversed compared to Paths A and B — you are walking candidate superseded PRs, not the merged PR itself. For each closed-unmerged PR in the same window (already enumerated by om-sync-merged-pr-issues), check its comments for a line matching:
^Closing in favor of #(\d+)\b
When the captured number equals the merged PR currently being credited, treat the merged PR as a carry-forward. Set primaryAuthor = mergedPrAuthor of the closed PR (i.e., the original contributor, looked up via gh pr view {closedPrNumber} --json author) and viaAuthor = mergedPrAuthor of the merged replacement.
Path C is a fallback only — Paths A and B cover the overwhelming majority of cases because om-auto-review-pr writes both the Supersedes #N body and the Credit: original implementation by @user line on the replacement PR.
Fallback
If none of A/B/C match, primaryAuthor = mergedPrAuthor and viaAuthor = null — no supersede. Most PRs fall here.
Worked example
Given merged PR #1555 with body:
Supersedes #1421
Credit: original implementation by @contributor-a. This follow-up PR carries that work forward with the requested fixes so it can merge without waiting on the original branch.
## Included work
- Original changes from #1421
- Follow-up fixes applied during re-review
...and PR author pkarw (the reviewer), the changelog entry becomes:
- 🐛 Validate event names against module registry (supersedes #1421). (#1555) *(@contributor-a, via @pkarw)*
The Contributors block lists @contributor-a first (primary author) and @pkarw once (only if they did not already appear as a primary author for some other PR in the same release).
Rules
- Never credit a bot account (
github-actions[bot],dependabot[bot],copilot,renovate[bot]). - Never credit the merge author when Path A, B, or C detects a supersede — always resolve to the original author.
- Never fabricate a Highlights paragraph. Leave the
<!-- TODO: Highlights -->marker for the human author to fill in;om-auto-create-pr's review pass will call it out. - Never modify files other than
CHANGELOG.md. If the run needs anything else (e.g., apackage.jsonversion bump), stop and ask the user — that is out of scope for this skill. - Never skip the
skip-qalabel on the resulting PR. Changelog edits are docs-only low-risk. - Never run the full validation gate directly. Delegate to
om-auto-create-prand let it decide. - Never pass
--forcetoom-auto-create-pr. If a changelog PR for the same version already exists, stop and ask the user. - Respect
--dry-runabsolutely: no file edits and noom-auto-create-prinvocation. - When multiple PRs share the exact same normalized summary (e.g., repeated "CR fixes"), coalesce them into a single bullet with
(#A, #B, #C)and merge the contributor credits — matches the style already inCHANGELOG.md. - When a PR body contains
fixes #Nthat points to a closed issue, keep the(fixes #N)suffix — it helps readers trace history even when the issue is long-closed. - When resolving a superseded PR author fails (deleted account, private fork), fall back to
mergedPrAuthorand add a<!-- supersede author unresolved for #N -->HTML comment immediately above the entry so a human reviewer can fix it.
Reporting
On success, output the preview + the om-auto-create-pr URL (see step 9). On --dry-run, output the full drafted entry plus a per-PR table:
| PR | Category | Line emoji | Primary author | Via | Notes |
|----|----------|-----------|----------------|-----|-------|
| #1555 | fix | 🐛 | @contributor-a | @pkarw | supersedes #1421 |
| #1550 | fix | 🔧 | @pkarw | — | — |
| #1546 | fix | 🐛 | @muhammadusman586 | — | fixes #1290 |
Notes
- Runs well after
om-sync-merged-pr-issues— the two skills consume the same window of merged PRs but mutate different surfaces (issue tracker vs CHANGELOG.md). - The generated entry is intentionally a draft. A human maintainer should still fill in the Highlights paragraph, possibly regroup subsections, and adjust the narrative.
om-auto-create-prwill open the PR inreviewso a maintainer reviews it before merge. - Because the work is delegated to
om-auto-create-pr, this skill inherits all of its guarantees: isolated worktree, incremental commits, BC self-review,om-auto-review-prautofix pass, and comprehensive summary comment.