SemVer Advisor
Decide the correct semantic-version bump — major, minor, or patch — by diffing a release range, mapping the changes onto the public API surface, and classifying each as breaking, additive, or a fix. Use before cutting a release when you are unsure whether changes are breaking, when a teammate proposes a bump you want to sanity-check, or when a behavior change has no signature change and you need to know if it is still breaking.
npx agentscamp add skills/semver-advisorInstall to ~/.claude/skills/semver-advisor/SKILL.md
A bump is wrong when you call a breaking change a minor — and consumers find out at runtime. This skill diffs the release range against the actual public API surface (exports, CLI flags, config, routes, file formats), classifies every change as breaking / additive / fix, and recommends major/minor/patch with the specific changes that forced it.
The wrong call here is silent until a consumer's build breaks. "It compiled, ship a minor" is how breaking changes escape — a tightened validation rule, a changed default, or a removed export looks small in the diff but breaks every downstream caller. This skill makes the bump a defensible decision: it pins down what your public API surface actually is, diffs the release range against it, classifies each change, and applies the SemVer rules — including the pre-1.0 exception people forget.
When to use this skill
- You are about to tag a release and are unsure whether the changes are breaking.
- Someone proposed
minororpatchand you want to verify it against the real diff. - You changed behavior without changing a signature and need to know if that is still breaking (it often is).
- You maintain a
0.xlibrary and keep forgetting that SemVer treats pre-1.0 differently. - A CI/release gate failed on version mismatch and you need the correct bump with a rationale.
Instructions
-
Define the public API surface first — it is narrower or wider than you think. Enumerate every contract a consumer can depend on, not just the language exports:
- Code exports: the package entry points (
exports/maininpackage.json,__all__,pub/publicsymbols). Anything reachable from the documented entry point is public; deep imports into internal paths usually are not (unless your docs/exportsmap expose them). - CLI: command names, flags, positional args, env vars they read, and exit codes.
- Config: accepted keys, their types, defaults, and required-ness in config files / schema.
- Wire contracts: HTTP routes, request/response shapes, status codes, GraphQL schema, event/message payloads.
- File formats: on-disk formats you read or write, serialization versions, migration outputs.
# entry points and public surface clues git show HEAD:package.json | grep -E '"(main|module|types|exports|bin)"' -A3 grep -rEn '__all__|^export (default |const |function |class |\{)' src | head -50 - Code exports: the package entry points (
-
Diff the release range, scoped to the surface. Use the last released tag as the lower bound; review only files that touch the surface from step 1.
LAST_TAG=$(git describe --tags --abbrev=0) git diff "$LAST_TAG"..HEAD --stat git diff "$LAST_TAG"..HEAD -- <surface paths: src/index.*, cli/, openapi.*, *.schema.json> -
Classify each surface change into exactly one bucket.
- Breaking (forces major): removed or renamed export/flag/route/config key; changed function signature, required arg added, or narrowed/changed return type; changed default behavior a consumer relied on; stricter validation that rejects previously-valid input; changed error type/exit code/status code; removed config default; changed file-format output that old readers can't parse.
- Additive (minor): new export, flag, optional config key with a safe default, new route, new optional response field — all 100% backward compatible.
- Fix (patch): bug fix that restores documented behavior with no API change, internal refactor, perf, docs, deps that don't change the public contract.
-
Apply the rule, then handle the pre-1.0 caveat. Take the highest-severity bucket present: any breaking → major; else any additive → minor; else patch. Then check the current version:
>= 1.0.0: apply the rule directly.0.y.z(pre-1.0): SemVer special-cases this. A breaking change bumps the minor (0.y→0.(y+1)), and additive/fix changes bump the patch. State explicitly that you are using pre-1.0 semantics.
-
Re-check every "no signature change" item before finalizing. A change with an identical signature can still be breaking — search the diff for default-value changes, validation tightening, altered side effects, and changed return values (not just types). These are the ones that get mislabeled as patches.
-
Output the recommendation with receipts. Give the bump, the resulting version number, the one-line rule that decided it, and the itemized changes per bucket — with each breaking change named explicitly so a reviewer can challenge it.
WARNING
A behavior change with an unchanged signature is still breaking. Tightening input validation, flipping a default (e.g. cache: false → true), changing rounding/sort order, or returning a different value for the same input all break consumers even though the API "didn't change." Grep the diff for changed literals and default arguments, not just modified declarations.
CAUTION
Pre-1.0 SemVer is not "anything goes" but it is not the 1.0 rule either: breaking changes go in the minor slot (0.4.x → 0.5.0), not the major. If you mechanically bump major for a 0.x package you will jump to 1.0.0 and signal stability you didn't intend. Confirm the current version before recommending.
Output
A bump recommendation, reproducible from the diff:
## SemVer recommendation: MAJOR (1.4.2 → 2.0.0)
Rule applied: contains ≥1 breaking change → major (current version ≥ 1.0.0).
### Breaking (forces major)
- Removed export `parseLegacy()` from package entry — consumers importing it will fail to resolve.
- `loadConfig()` now throws on unknown keys (was: ignored) — stricter validation rejects previously-valid config.
- Default of `--timeout` changed 0 (infinite) → 30000ms — changes runtime behavior for callers relying on the old default.
### Additive (would be minor on its own)
- New optional flag `--format json`.
### Fix (would be patch on its own)
- Fixed off-by-one in `splitRange()` matching documented behavior.
Note: if this were a 0.x package, the same set would be a MINOR bump (0.y → 0.(y+1)).Related
- Version BumperBump the project version everywhere it lives in one consistent pass — package.json, lockfile, nested/CLI package manifests, version constants, README badges, docs — then roll the changelog's Unreleased section under the new version and stage an annotated git tag. Use when you've already decided the new version (X.Y.Z or a pre-release like -rc.1) and need every artifact updated to the same value without drift, or before cutting a release.
- Changelog From PRsDraft a release changelog by summarizing merged pull requests since the last tag. Use when preparing a release or writing release notes.
- Migration WriterWrite a safe, reversible, zero-downtime database migration using expand-contract — add the new shape, backfill in batches, switch reads/writes, then drop the old — so every deploy stays compatible with the running app version. Use when adding or changing schema on a live system, renaming/dropping a column, adding NOT NULL or a foreign key on a large table, or when a migration risks locks, table rewrites, or an unrevertable step.
- Release Notes WriterWrite user-facing release notes — the curated 'what's new and what it means for you' — by starting from the real changes (git log / merged PRs / the changelog since the last release) and translating developer-speak into user impact, grouped by what the user cares about with breaking changes and required actions surfaced first. Use when shipping a release to users or customers and the raw commit log isn't something a user should read, when you need a published GitHub-release / blog / in-app announcement, or when a breaking change must be made unmissable so upgrades don't break.