--- name: changelog description: Bumps the project version based on conventional commits on the current branch, writes a new entry to CHANGELOG.md following Keep a Changelog 1.1.0, and updates package.json#version. Use when the user asks to cut a release, prepare a version bump, or refresh the changelog. license: MIT metadata: author: frytg agent: pi category: engineering --- # Changelog Bump the project version based on the conventional commits on the current branch and write a corresponding entry to `CHANGELOG.md`. Updates `package.json#version` to match. ## 1. Establish context - **Working tree must be clean.** If `git status --porcelain` is non-empty, stop and report. The only changes this skill makes are the version bump and changelog entry — anything else should be committed first. - **Identify the base branch.** Try in order: `main`, `master`, then `git symbolic-ref refs/remotes/origin/HEAD`. Strip `origin/` from the result. The base branch is the comparison point, not the merge target. - **Confirm there are commits ahead of base.** `git log ..HEAD --oneline | wc -l` must be `> 0`. If zero, stop with "no commits ahead of `` — nothing to release." - **Read `package.json#version`.** If the file is missing, stop. If the field is missing, treat the current version as `0.0.0` and warn the user that this is a fresh project. - **Skip if already up-to-date.** If a tag matching `v` (e.g. `v1.4.2`) already exists at HEAD, stop with "version `` already tagged at HEAD — nothing to do." ## 2. Gather commits Collect commit subjects and bodies for everything on the branch since base: ```bash git log ..HEAD --pretty=format:'%H%x1f%s%x1f%b%x1e' ``` Use a non-printable delimiter (`%x1f` = US, `%x1e` = RS) so commit subjects and bodies can be parsed reliably even when they contain colons, parens, or newlines. ## 3. Classify commits For each commit, extract the conventional-commit **type** and **breaking flag**: - **Type** — the first token of the subject before the first `(`, `!`, or `:`. Examples: `feat`, `fix`, `chore`, `docs`, `refactor`, `perf`, `style`, `test`, `build`, `ci`, `revert`. - **Breaking flag** — `true` if either: - The subject ends in `!` before the colon (e.g. `feat(api)!: ...`), or - The body (or footer lines) contains `BREAKING CHANGE:` / `BREAKING-CHANGE:`. Map to a semver bump level. Take the **highest** level across all commits: - **major** — breaking flag set. - **minor** — type is `feat`. - **patch** — type is `fix`, `perf`, `refactor`, `revert`, `style`, `docs`, `test`, `chore`, `build`, or `ci`. - **patch (warn in the report)** — no recognised type. `major > minor > patch`. A single `BREAKING CHANGE` always wins over a `feat:`. ## 4. Compute the new version Apply semver rules to the current version: - **major** — bump major, reset minor and patch to 0 (e.g. `1.4.2` → `2.0.0`). - **minor** — bump minor, reset patch to 0 (e.g. `1.4.2` → `1.5.0`). - **patch** — bump patch (e.g. `1.4.2` → `1.4.3`). For pre-`1.0.0` versions, standard semver still applies. Note in the report that some teams choose to treat breaking changes as minor on `0.x.y`; default to the strict rule and let the user override. ## 5. Categorise commits for the changelog Group the commits into Keep a Changelog sections. Drop non-user-facing types unless they are breaking. - **Added** — `feat`. - **Changed** — `refactor`, `style`, `perf`, `revert`. - **Fixed** — `fix`. - **Removed** — only when a commit body explicitly contains `Removed:` (case-sensitive) followed by a one-line description. A `feat!:` or `fix!:` is breaking but is not necessarily a removal — leave the breaking flag in the relevant section. - **Security** — `fix` commits whose body mentions `CVE-` or `security`. - _(omit)_ — `docs`, `test`, `chore`, `build`, `ci`. If a commit is breaking, prefix the bullet with `**BREAKING:**` in the relevant section. If nothing maps to a given section, omit the heading entirely. Format each entry as: `- `. Keep the full conventional-commit structure (`type`, optional `(scope)`, optional `!`, colon, subject) — do **not** strip the type/scope prefix. Do **not** append commit SHAs, short hashes, or remote commit URLs. The changelog lists the changes themselves, not git log links. Examples: - `- feat(api): add pagination cursors` - `- fix(auth): reject expired refresh tokens` - `- **BREAKING:** feat(cli)!: rename --out to --output` When several commits collapse into the same user-visible change, merge them into one bullet. Prefer the cleanest conventional-commit subject over listing every commit separately. ## 6. Write `CHANGELOG.md` Follow [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/): - If `CHANGELOG.md` does not exist, create it with the standard header and an `## [Unreleased]` section above the new entry. - If it exists, leave the existing `## [Unreleased]` section in place and prepend the new version section above it. New entries go at the top of the version list. Template for the new section: ```markdown ## [] - ### Added - feat(scope): add something ### Changed - **BREAKING:** feat(scope)!: rename the flag ### Fixed - fix(scope): correct the edge case ``` Use today's date in `YYYY-MM-DD` (local time). The date is the release date, not the commit date. ## 7. Update `package.json` - Read `package.json` and update the top-level `version` field to the new version. - Preserve all other fields, formatting, key order, and existing indentation (tabs vs spaces, 2 vs 4). Use a targeted edit, not a rewrite. - If the file is missing, stop — this skill does not create `package.json`. ## 8. Report and stop Print a summary, then stop. Do not stage, commit, tag, or push. ``` Current branch: Base branch: Commits ahead: Bump: Reason: Old: New: Files changed: M package.json M CHANGELOG.md ``` End with: "Run `git diff` to review. Stage and commit when you're ready." ## Safety - **Never commit, tag, push, or merge.** This skill edits files only. - **Never edit a working tree with uncommitted changes** — stop and tell the user to commit or stash first. - **Never invent commit descriptions.** Prefer the commit subject exactly, including its conventional-commit type/scope/`!`. If you must fall back to a body line, keep the subject's conventional-commit prefix on the bullet. Never paraphrase beyond that. - **Never rewrite history.** The skill reads `git log`; it does not run `git rebase`, `reset`, or `commit --amend`. - **Single-package only.** Monorepos with multiple `package.json` files are out of scope — stop and tell the user to run the skill per package. - **No `npm`/`bun`/`nub` version commands.** Edit `package.json` directly so the bump works the same regardless of package manager.