--- name: changelog description: Regenerate the [Unreleased] section of every affected CHANGELOG.md in Keep a Changelog style. Reads commits since the last release tag plus any uncommitted or staged changes, classifies them by Conventional Commit prefix, and rewrites each [Unreleased] block. Works in single-package repos and monorepos (one CHANGELOG.md per package). Use when preparing a release or drafting changelog entries. Idempotent — safe to re-run as work lands. argument-hint: [--since ] allowed-tools: Bash(git *), Read, Edit --- # Generate CHANGELOG entries You are tasked with regenerating the `## [Unreleased]` section of every affected `CHANGELOG.md` in the repository so it reflects all change since the last release tag — committed and uncommitted alike. ## Range hint `$ARGUMENTS` (empty/literal → range starts at the last release tag from `git describe --tags --abbrev=0`) ## Workflow 1. Bail-out checks 2. Determine the change range 3. Determine each CHANGELOG's scope and collect commits + uncommitted hunks 4. Classify and draft entries 5. Preview and confirm 6. Apply ## Step 1: Bail-out checks 1. Run `git rev-parse --is-inside-work-tree`. If not a git repo, tell the user "This directory is not a git repository." and stop. 2. Run `git ls-files 'CHANGELOG.md' '**/CHANGELOG.md'` to discover every tracked changelog. If zero results, tell the user "No `CHANGELOG.md` found in the repository — create one (root or per-package) before running this skill." and stop. 3. Run `git describe --tags --abbrev=0` to confirm at least one release tag exists. If none, ask the user to supply `--since ` and stop until they do. ## Step 2: Determine the change range 1. Parse `$ARGUMENTS` for a `--since ` flag. If absent, set `SINCE=$(git describe --tags --abbrev=0)`. 2. The range is `$SINCE..HEAD` for committed changes, plus the current uncommitted+staged working tree. ## Step 3: Determine each CHANGELOG's scope, then collect commits + uncommitted hunks Each `CHANGELOG.md` discovered in Step 1.2 owns a path scope: - **Nested CHANGELOG** (e.g. `packages/foo/CHANGELOG.md`, `apps/web/CHANGELOG.md`): scope is its parent directory — `packages/foo/`, `apps/web/`. - **Root CHANGELOG** (`CHANGELOG.md` at repo root): - If no nested CHANGELOGs exist: scope is the entire repository. - If nested CHANGELOGs also exist: scope is the repository **excluding** every directory that owns a nested CHANGELOG. The root file captures repo-wide change (CI, build config, root README) that no per-package file would claim. For each scope: 1. Committed: `git log $SINCE..HEAD --pretty=format:"%H%x09%s%x09%b%x1e" -- `. For root-with-exclusions, pass `:(exclude)` pathspecs for every nested-CHANGELOG directory. Records are `\x1e`-delimited; parse subject (`%s`) and body (`%b`). 2. Uncommitted: `git diff HEAD -- ` and `git diff --cached -- ` with the same pathspec rules. Treat the union as a single virtual "pending" change set with no commit message — the model classifies it from the diff itself. 3. Skip CHANGELOGs whose scope has no committed and no uncommitted changes in range. ## Step 4: Classify and draft entries For each affected CHANGELOG, produce entries grouped under the Keep a Changelog 1.1.0 sections, in this order: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`, `Performance`. Append a `Breaking / Upgrade Notes` section only when a breaking change exists. ### Conventional Commit → section mapping - `feat:` → **Added** - `fix:` → **Fixed** - `perf:` → **Performance** - `refactor:`, `style:`, `build:`, `ci:`, `chore:` → **Changed** - `docs:` → **Changed** (only if user-facing docs; skip internal `thoughts/` or research notes) - `test:` → omit (not user-visible) - `revert:` → **Changed** (note what was reverted) ### Always-skip commits Skip any commit whose subject matches one of these — they are release pipeline housekeeping, not user-visible change: - `Release v` or `chore(release): v` (common release-bot patterns) - `Add [Unreleased] section for next cycle` - Version-only bumps with no other content (`` as the entire subject) - Merge commits with no diff content of their own ### Breaking change detection Flag a commit as breaking if any of these are true: - The type has a `!` suffix (`feat!:`, `refactor!:`, etc.) - The commit body contains a `BREAKING CHANGE:` footer - The diff removes or renames an exported symbol, removes a CLI flag, or removes a public file For each breaking change, add an entry to **Breaking / Upgrade Notes** in addition to the regular section, written as a one-line upgrade instruction. ### Style rules — match Keep a Changelog 1.1.0 prose - One short user-facing sentence per entry. Imperative mood ("Add", "Fix", "Remove"). - Write for the plugin's **users**, not its maintainers. No internal symbol names, file paths, regex literals, or precedent commit hashes inside entries. - If a feature has a user-visible name (a slash command, a CLI flag, a skill name), name it in backticks. Example: `` Added `--locale` flag for per-invocation language override. `` - Group entries by category, not by commit. Merge duplicate-topic commits into one entry. - If a commit reverses something earlier in the same `[Unreleased]` window (e.g. add → remove → add-back), reflect only the net effect. - Skip entries that have zero user-visible impact: dependency bumps with no behavior change, internal refactors invisible to users, test additions, type-only changes. ### Worked example Input commits in `packages/api/`: ``` abc1234 feat(api): add /v2/search endpoint with cursor pagination def5678 feat(api): support webhook retries with exponential backoff ghi9abc fix(api): rotate session secret on every JWT refresh jkl0def docs(api): document rate-limit headers in OpenAPI spec mno1234 chore(deps): bump @types/node to 20.11 pqr5678 test(api): coverage for cursor edge cases stu9abc refactor(api): inline httpClient factory (no behavior change) ``` Output `[Unreleased]`: ```markdown ## [Unreleased] ### Added - `/v2/search` endpoint with cursor-based pagination. - Webhook delivery retries with exponential backoff. ### Changed - OpenAPI spec documents rate-limit response headers. ### Fixed - JWT refresh rotates the session secret on every renewal. ``` What this example demonstrates: - Two `feat:` commits → two **Added** entries (one per user-visible feature). - `docs:` for a user-facing API spec → **Changed** (skip if the docs touched were internal notes). - `fix:` → **Fixed**, written as the corrected behavior in imperative mood, not as the bug. - `chore(deps):` with no behavior change → omitted. - `test:` → omitted (not user-visible). - `refactor:` flagged "no behavior change" → omitted (the rule is user-visible impact, not commit type). - Commit hashes never appear in entries. ## Step 5: Preview and confirm 1. Print a per-CHANGELOG summary: file path, count by section, breaking-change flag. 2. Print the proposed `[Unreleased]` body for each affected CHANGELOG, in full. 3. Call `ask_user_question`: - Question: "Apply regenerated `[Unreleased]` to {N} CHANGELOG(s)?" - Header: "Changelog" - Options: - "Apply (Recommended)" — Write the regenerated sections to disk. Refinement, if needed, happens afterward in normal chat (`Edit` tool) or via `git restore` to roll back. - "Show Preview" — For each affected CHANGELOG, render a unified diff between the **current** `[Unreleased]` body on disk and the **proposed** regenerated body. Lines marked `-` are about to be removed; lines marked `+` are about to be added. After printing, re-ask this same question. ## Step 6: Apply For each affected CHANGELOG: 1. Read the file. 2. Locate the `## [Unreleased]` heading. The block runs from that heading up to (but not including) the next `## [` heading — or end of file if no later version exists. If no `## [Unreleased]` heading exists, insert one above the first `## [` heading (or after the file's intro prose if no version sections exist yet). 3. Use `Edit` to replace the entire block with `## [Unreleased]\n\n` followed by the regenerated sections. 4. **Never** touch any heading below `[Unreleased]`. Released version sections are immutable. After all writes complete, print the list of modified files and remind the user to commit them before invoking their release pipeline — most release scripts require a clean working tree. ## Important Notes - ALWAYS preview before writing. Never apply without the user's `ask_user_question` confirmation. - ALWAYS replace the full `[Unreleased]` body, not append. The skill is idempotent regeneration, not accumulation. - NEVER modify released version sections (anything below the first `## [x.y.z]` heading). - NEVER write Conventional Commit prefixes (`feat:`, `fix:`, etc.) into the changelog body. They classify the entry; they don't appear in the prose. - NEVER include commit hashes, PR numbers, or author names in entries. The audience is end users, not git archaeologists. - NEVER pick or suggest a version number. The release pipeline owns the bump. - NEVER invoke a release script from this skill. Authoring is a separate step from releasing. - If a CHANGELOG has changes in the range but every commit is omit-worthy by the style rules (test-only, type-only, internal refactor), leave its `[Unreleased]` body empty — do not invent entries.