| name | version |
| description | Bump one target's version via scripts/bump-version.mjs, summarize every commit since that target's last release into a new section in its CHANGELOG.md, and commit with a `<short>-vX.Y.Z` message. Target is either a workspace package (apps/* or libs/*) or the root pseudo-package — the scaffold itself — when no name is passed. |
| argument-hint | [major|minor|patch] [<package-name>] |
| disable-model-invocation | true |
| allowed-tools | Read, Edit, Write, Glob, Bash(node scripts/bump-version.mjs*), Bash(pnpm check*), 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 one target's version, prepend a CHANGELOG.md section summarizing every commit since its last release, and commit. The deterministic work — dirty-tree check, skew check, semver math, file rewrites — happens in scripts/bump-version.mjs before this skill begins reasoning. The skill only summarizes commits, drafts prose, and runs git.
Two target shapes:
- Workspace package —
apps/* or libs/*. Each package's package.json version is the source of truth; an optional src/version.ts participates if present. CHANGELOG lives at <package_dir>/CHANGELOG.md. Commit log is path-filtered to that package's directory.
- Root (the scaffold itself) — selected by passing no package name. Source of truth is the root
package.json version. CHANGELOG lives at CHANGELOG.md at the repo root. Commit log is not path-filtered — a scaffold release summarizes everything since the last root release, including cross-cutting per-package work.
If a target has src/version.ts with an export const version = '...'; literal, the bump updates both — the skew check refuses to proceed if they're out of sync. The root never has a src/version.ts.
Arguments
$ARGUMENTS — one of these shapes:
<type> alone — bump the root (the scaffold itself)
<type> <name> (positional) — bump one workspace package, e.g. patch my-app, minor @repo/tools
<type> --package <name> (explicit) — same as above
<type> is major, minor, or patch. <name> matches either the workspace name (@repo/my-app) or the short name (my-app). When omitted, the target is the root. There is no root keyword — bare <type> is the only way to select root.
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:
- the package's version files have 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:
is_root — true when bumping the scaffold's root version; false for a workspace package. Drives the path-filter decision in step 3 and nothing else.
workspace_name, short_name, package_dir, changelog, tag — identity of the bumped target and where its CHANGELOG lives. package_dir is null when is_root is true.
new — authoritative new version (never recompute).
since — SHA of the previous <short>-vX.Y.Z release commit, or null for first release.
files — list of files the script rewrote, with changed: true|false per file.
Conventions
The skill's allowed-tools permissions match plain git <verb> invocations and 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>.
- Use Read or Glob for file existence checks. Don't shell out to
ls. Use Glob with the literal path to test existence; Read to inspect content.
- Quote-free commit messages. The commit message is always literally
<short>-vX.Y.Z (no body, no apostrophes), so git commit -m "my-app-v0.1.1" is safe inline.
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 every file the script touched with changed: true|false. If changed_count is 0, something is wrong (the script claimed success but rewrote nothing) — stop and tell the user.
-
Fetch the commit range using the JSON's since SHA. The shape depends on is_root:
-
Workspace package (is_root: false) — scope to the package's directory using the JSON's package_dir:
- If
since is a SHA: git log <since>..HEAD --no-merges --pretty=format:'%h %s' -- <package_dir>
- If
since is null: git log --no-merges --pretty=format:'%h %s' -- <package_dir> (initial release)
The path filter limits the log to commits that touched this package's files. If the package has cross-cutting dependencies on a sibling lib whose changes should also be summarized, mention them by hand in the CHANGELOG; the path filter is the default.
-
Root (is_root: true) — no path filter. A scaffold release summarizes everything since the last root release, including per-package work, tooling changes, docs, anything:
- 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 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.
- If the JSON's
since is null, title the section "vX.Y.Z — initial release" instead of listing every commit.
Print the drafted section back to the user as a fenced markdown code block. The entire block, verbatim, exactly as it will be prepended to the CHANGELOG. This is the user's one chance to see the prose in isolation before it's folded into the file, committed, and tagged. Print before moving to step 5; the skill continues automatically after printing (no wait for confirmation).
-
Prepend the new section to the target's CHANGELOG.md. Use the JSON's changelog field — apps/my-app/CHANGELOG.md for a workspace package, CHANGELOG.md for the root.
First, note whether the file already exists — the unwind in step 6 needs this fact. Use Glob with the literal path. Then:
-
If it exists, prepend above the existing content (keep a single # Changelog heading at the very top).
-
If it doesn't exist, create it with:
# Changelog
<new section here>
-
Verify with pnpm check --only docs,links,spell --bail before staging. Only these three checks can fail on a (version strings + CHANGELOG) diff — string replacement in a handful of files can't break types, lint, struct, dead, or tests. If the narrow check exits 0, continue to step 7.
If it exits non-zero, the release must not happen. Roll back the working tree so the user can fix the issue and re-invoke the skill:
- Restore each file from the JSON's
files[]: git restore <files[].rel>.
- For the CHANGELOG: if it existed before this run,
git restore <changelog>. If newly created in step 5, rm <changelog>.
- Tell the user the check failed, show the tail of the relevant
.check/*.txt or .check/*.json diagnostic, and stop.
-
Stage the bumped files + CHANGELOG, nothing else. Use the exact paths from the JSON:
git add <each files[].rel> <changelog>
Confirm via git status --short that nothing else is staged. If anything unexpected is staged, stop and hand it back to the user.
-
Commit. Use the JSON's tag field literally — but as the commit message, not as a git tag yet:
git commit -m "<tag>"
Example: git commit -m "my-app-v0.1.1" for a workspace package, git commit -m "ts-check-scaffold-v0.2.0" for the root. No prefix, no body, no footer. This matches the marker convention find_previous_release_sha uses to find this target's last release on the next bump.
-
Create an annotated tag and push it.
git tag -a <tag> -m "<tag>"
git push origin <tag>
Annotated (not lightweight) so the tag carries author, date, and message. The tag-only push is intentional — branch pushes are managed separately by the user.
Error handling:
git tag fails because the tag already exists → stop and tell the user. Don't force-overwrite.
git push fails (network, auth, permissions) → the local tag is created. Tell the user the commit + tag exist locally, show the push error, and suggest re-running git push origin <tag>. Do not delete the tag.
-
Report back. Tell the user: the target name (workspace name or <root-name> for root), the old version, the new version, the commit SHA, the tag, the number of commits summarized, and whether the tag push succeeded.
Steps — error
Triggered when the preflight JSON's mode is "error". The script made no changes. Branch on error_type:
dirty_tree → tell the user the working tree must be clean before a release; show the dirty files; suggest committing or stashing first; stop.
skew → tell the user the package has version skew between its package.json and src/version.ts; quote the script's diagnostic; tell them to align by hand; stop.
unknown_package → tell the user the named package wasn't found under apps/ or libs/; list available packages (read directory entries); 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 for one specific package —
/version patch my-app, /version minor @repo/tools.
- Cutting a release for the scaffold itself —
/version patch, /version minor (no package arg).
- User asks to "bump the version of X" or "tag a new version of X" or "release the scaffold".
When NOT to use this skill
- There's no meaningful change since this target's last release (no commits between last tag and HEAD that would land in the CHANGELOG). 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.
- The user wants to bump every workspace package together — workspace versions are independent per the project's design. Run the skill once per package. A root bump is a separate concern that releases the scaffold itself, not a fan-out over packages.
Edge cases
- No prior release. When
since in the JSON is null, this is the target's first release. Title the section vX.Y.Z — initial release and use the full history (filtered to the package directory for workspace packages; unfiltered for root).
- Root release shortly after
/scaffold-init. /scaffold-init deletes any prior CHANGELOG.md and resets the root version to 0.0.0, so the first /version <type> after init always behaves as an initial release for the renamed project. Old ts-check-scaffold-v* tags from before the rename are intentionally orphaned — they belong to the scaffold's release history, not the derived project's.
CHANGELOG.md exists in the package but has no # Changelog heading. Prepend the new heading + section; leave the old content below.
- Commit list contains merge commits. Drop them unless they introduced something not present in the squashed commits.
--no-merges on the log handles this.
- A commit is marked with
BREAKING: or !: but the user asked for patch or minor. Warn and ask if they meant major. By the time this runs, the bump has already happened on disk; if the user wants major instead, they need to git restore the files and re-invoke.
pnpm check --only docs,links,spell --bail fails in step 6. The skill restores the files via git restore and either removes a freshly-created CHANGELOG or restores a pre-existing one (per the existence check in step 5). Common cause: a word in the new CHANGELOG entry is missing from cspell.json's words list.
- Package newly added with no
version.ts. Fine — only package.json participates. If it later adds src/version.ts with the literal export const version = '<SEMVER>'; line, the discovery enumerator picks it up automatically on the next bump.
- Cross-package work in the release window. If a single feature touched multiple packages, you'll bump each separately. Each package's CHANGELOG reflects its own commits via the path filter. Mention shared work explicitly in each affected package's prose if it helps readers.