| name | version |
| description | Bump the project version in `package.json` via scripts/bump-version.mjs, summarize every commit since the last release into a new CHANGELOG.md section, commit with a `vX.Y.Z` message, and push an annotated tag. Use when cutting a release. |
| argument-hint | [major|minor|patch] |
| disable-model-invocation | true |
| allowed-tools | Read, Edit, Write, Bash(node scripts/bump-version.mjs*), Bash(npm run lint*), Bash(git log*), Bash(git diff*), Bash(git describe*), Bash(git status*), Bash(git add *), Bash(git commit *), Bash(git tag *), Bash(git push origin v*), Bash(git reset *), Bash(git restore *), Bash(git checkout *), Bash(node -e *), Bash(cat *) |
version
Bump the project version, prepend a CHANGELOG.md section summarizing every commit since the last release, and commit. The deterministic work — dirty-tree check, semver math, package.json rewrite — happens in scripts/bump-version.mjs before this skill begins reasoning. The skill itself only summarizes commits, drafts prose, and runs git.
Arguments
$ARGUMENTS — one of major, minor, or patch.
No default; fail fast if missing or anything else.
Preflight context
- Bump result: !
node scripts/bump-version.mjs $ARGUMENTS
The bump script runs first, before the skill reasons about anything. By the time you read this, one of two things is true on disk:
package.json has been rewritten to the new version (mode: "bump"),
- nothing was changed and the script emitted an error JSON (
mode: "error").
On a successful bump, the JSON carries everything the skill needs: new is the authoritative version (never recompute it), and since is the SHA of the previous release commit — the left boundary for the CHANGELOG commit range. The script recognizes both the current vX.Y.Z convention and the legacy chore: bump version to X.Y.Z convention, so the changeover to the new convention is seamless. If since is null, there is no prior release and this is an initial release.
Conventions
The skill's allowed-tools permissions match plain git <verb> invocations and Bash patterns like cat *. Stay inside those:
- Run git commands directly. The skill's working directory is already the repo root. Use
git status, git log, git commit, git restore, etc. without -C <path>. The git -C <path> <verb> form does not match patterns like Bash(git log*) and triggers a permission prompt for every command.
- Use Read or Glob for file existence checks. Don't shell out to
ls. If you need to inspect a file's content, use Read; if you only need to know whether it exists, Glob with the literal path.
- Quote-free commit messages. The commit message is always literally
vX.Y.Z (no body, no apostrophes), so git commit -m "vX.Y.Z" is safe inline — no scratch file needed.
Steps
-
Parse the bump-result JSON from preflight. Read the mode field and branch:
mode: "error" → go to "Steps — error". Do not proceed.
mode: "bump" → continue below.
-
Sanity-check the rewrite. The JSON's files[] lists package.json with changed: true|false. If changed_count is 0, something is wrong (the script claimed success but rewrote nothing) — stop and tell the user, don't continue to commit.
-
Fetch the commit range using the JSON's since SHA:
- If
since is a SHA: git log <since>..HEAD --no-merges --pretty=format:'%h %s'
- If
since is null: git log --no-merges --pretty=format:'%h %s' (initial release)
-
Draft the CHANGELOG section. Use the JSON's new field for the version heading (don't recompute):
## vX.Y.Z — YYYY-MM-DD
### Added
- <one line per user-visible addition>
### Changed
- <behavior changes, refactors that matter externally>
### Fixed
- <bug fixes>
### Internal
- <tooling, tests, docs — keep this section short or omit>
Rules for the summary:
- Group by impact, not by commit. Collapse three commits that together land one feature into one bullet.
- Omit any
Added/Changed/Fixed/Internal section that has no entries.
- Each bullet is one line. Reference commit hashes only if the line is genuinely ambiguous without one.
- Write for a reader who didn't follow the work. "Fixed flaky cache eviction under concurrent writes" beats "fixed bug in cache".
- If the JSON's
since is null, this is the first release — title the section "vX.Y.Z — initial release" instead of listing every commit in repo history.
Print the drafted section back to the user as a fenced markdown code block in your response text — the entire block, verbatim, exactly as it will be prepended to CHANGELOG.md. This is the user's one chance to see the prose in isolation before it's folded into the file, committed, and tagged. Do this before moving on to step 5; don't summarize or abbreviate — print the raw markdown. The skill continues automatically after printing (no wait for confirmation); if the user wants to change the prose, they'll interrupt.
-
Prepend the new section to CHANGELOG.md. First, note whether the file already exists — the unwind in step 6 needs this fact. Use Glob with the literal path CHANGELOG.md to check, not a shell ls. Then:
-
If it exists, prepend above the existing content (keep a single # Changelog heading at the very top). The existing file uses ## X.Y.Z (bare, no v prefix) for older entries with flat bullets — leave those untouched. The new format switches to ## vX.Y.Z — YYYY-MM-DD with the grouped subsections from step 4.
-
If it doesn't exist, create it with:
# Changelog
[new section here]
-
Verify with npm run lint:markdown before staging. The only thing that can fail on a (package.json version string + CHANGELOG prose) diff is markdown lint on the new CHANGELOG section — semver-string replacement in one package.json field can't break types, code lint, or tests, so running the full npm test pipeline is wasted CPU. If the narrow check exits 0, continue to step 7.
If it exits non-zero, the release must not happen. Roll the working tree back to its pre-bump state so the user can fix the issue and re-invoke the skill cleanly:
- Restore
package.json to its pre-bump content: git restore package.json. (The bump-script edits live in the working tree only — nothing has been staged yet — so git restore is the right tool.)
- For
CHANGELOG.md: if the file existed before this skill run, git restore CHANGELOG.md. If it was newly created in step 5, rm CHANGELOG.md. Note this in step 5 so you know which branch to take here.
- Tell the user the check failed, show the markdownlint output, and stop. Don't retry; the user decides whether to fix the CHANGELOG prose and re-invoke the skill or investigate first.
-
Stage exactly package.json and CHANGELOG.md, nothing else.
git add package.json CHANGELOG.md
Confirm via git status --short that no other files are staged. If anything unexpected is staged, stop and hand it back to the user — a release commit is not the place to sneak other changes in.
-
Commit. Use the JSON's new field literally:
git commit -m "vX.Y.Z"
No prefix, no body, no footer. That matches the marker convention the repo uses to find "the last release" on the next bump. Note: this is a change from the legacy chore: bump version to X.Y.Z convention. The script recognizes both, so old history keeps working as the boundary marker.
-
Create an annotated tag and push it. Use the JSON's new field literally:
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
Annotated (not lightweight) so the tag carries author, date, and message. The explicit tag-only push is intentional — branch merges and branch pushes are managed separately by the user; the skill only publishes the release marker. Pushing refs/tags/vX.Y.Z also sends the commit it points to, so the release commit reaches the remote even if the branch ref hasn't moved yet.
Error handling:
git tag fails because the tag already exists → stop and tell the user. Don't force-overwrite. A prior release at this version already exists and the user needs to resolve it by hand.
git push fails (network, auth, permissions) → the local tag is already created. Tell the user the commit + tag exist locally, show the push error, and suggest re-running git push origin vX.Y.Z once the issue is resolved. Do not delete the tag.
-
Report back. Tell the user: the old version, the new version (both from the JSON), the commit SHA, the tag name, the number of commits summarized, and whether the tag push succeeded. The user still pushes the release branch themselves when they're ready.
Steps — error
Triggered when the preflight JSON's mode is "error". The script made no changes (no version files touched, no commit). Branch on error_type:
dirty_tree → tell the user the working tree must be clean before a release commit; show the listed dirty files; suggest committing or stashing first; stop.
usage → relay the script's usage message verbatim; stop.
runtime → relay the script's message and stop. Don't speculate or retry.
In every error case: no edits, no git operations, no retry. The user decides what to do next.
When to use this skill
- Cutting a release, even an internal one (
patch/minor/major).
- User asks to "bump the version" or "tag a new version".
When NOT to use this skill
- There's no meaningful change since the last release (no commits between last release and HEAD). Tell the user and stop.
- The user wants to edit an existing CHANGELOG entry or retro-tag an older commit — that's a different workflow, not this skill.
Edge cases
- No prior release. When
since in the JSON is null, treat the entire history as the range and title the section vX.Y.Z — initial release. The script identifies prior releases by commit message (either vX.Y.Z or chore: bump version to X.Y.Z), not by git tag.
CHANGELOG.md exists but has no # Changelog heading. Prepend the new heading plus the new section; leave the old content below untouched.
- Commit list contains merge commits. Drop them from the summary unless they introduced something not present in the squashed commits.
--no-merges on the log is fine if the output is noisy.
- A commit is marked with
BREAKING: or !: but the user asked for patch or minor. Warn the user and ask if they meant major. Don't override silently. Note: by this point the bump has already happened on disk (the script ran in preflight); if the user wants major instead, they need to git restore package.json and re-invoke /version major.
npm run lint:markdown fails in step 6. The skill restores package.json with git restore and either removes a freshly-created CHANGELOG.md or restores a pre-existing one (per the existence check in step 5). No commit was created, so no reset is needed.