Semantic Versioning skill for Codex
Major, minor or patch, decided against the real API. Codex pins down what the public API actually is, classifies each change since the last tag, and proposes the version bump with reasons, including subtle breaks like narrowed TypeScript types, changed defaults and dropped runtimes. It knows 0.x caret rules, pre-release ordering, PEP 440 and Go module paths, runs API-diff tools where they exist, and writes a deprecation policy with warnings in code.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- semantic-versioning Installs to ~/.agents/skills/semantic-versioning/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install semantic-versioning The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project semantic-versioning Installs to .agents/skills/semantic-versioning/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/semantic-versioning.zipunzip -o semantic-versioning.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) semantic-versioning PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $semantic-versioning, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Decides the correct SemVer 2.0.0 bump for a library, CLI, API or package by classifying every change against the public API - including the edge cases (new required parameters, narrowed types, changed defaults, dropped runtimes, 0.x rules), pre-release identifiers such as 2.0.0-rc.1, npm dist-tags, PEP 440 and Go module /v2 paths - and writes a deprecation policy with warnings and removal windows. Use when asking "is this a breaking change", "major or minor", choosing the next version, publishing a beta, deprecating an API, or checking a diff with cargo-semver-checks or API Extractor.
Use it when
- You are choosing the next version number for a library or CLI
- A diff might break consumers and you need a clear answer
- You need alpha, beta or rc versions that sort correctly
- Your project has no written deprecation policy
Use something else when
- You are versioning an HTTP API contract for clients: use api-versioning
- You want commit messages that drive automatic version bumps: use conventional-commits
How to check the result
Every commit since the last tag appears in the classification table, and the ecosystem's API-diff tool agrees with the proposed bump or the difference is explained.
The mistake it prevents
Calling a change minor because runtime behaviour is unchanged, when a narrowed type or new required field breaks every consumer's build.
A version number is a promise to consumers. The bump is right when every consumer who followed your documented public API can take a MINOR or PATCH upgrade without changing their code or behaviour, and when anything that could break them waited for a MAJOR with a deprecation period first.
Define the public API first
SemVer is meaningless until the public surface is written down. Check README, docs, the
package exports map, __all__, exported Go identifiers, pub items, OpenAPI spec, CLI
flags, config keys, environment variables, file formats, and wire or database schemas
that consumers read. If the project has no statement, propose one to the user before
deciding a bump.
The decision
Classify each change since the last tag, then take the highest bump:
git log --oneline $(git describe --tags --abbrev=0)..HEAD
git diff $(git describe --tags --abbrev=0)..HEAD --stat
| Change | Bump |
|---|---|
| Bug fix that restores documented behaviour | PATCH |
| Performance improvement, internal refactor, docs | PATCH |
| New function, class, endpoint, optional parameter, CLI flag | MINOR |
| New deprecation warning (old thing still works) | MINOR |
| New optional field in a response or config | MINOR |
| Removing or renaming anything public | MAJOR |
| New required parameter, field or env var | MAJOR |
| Narrowing an input type or widening an output type (TypeScript, typed APIs) | MAJOR |
| Changing a default value consumers rely on | MAJOR |
| Changing error types or codes consumers catch | MAJOR |
| Dropping a supported runtime, OS or language version | MAJOR (state it in the changelog) |
| Raising a peer dependency’s major range | MAJOR |
| Fixing a bug that consumers demonstrably depend on | Judgement: MAJOR if widely relied on, else PATCH with a loud note |
| Security fix that must break a behaviour | Ship it; MAJOR if possible, otherwise MINOR or PATCH with explicit advisory |
Hyrum’s law means any observable behaviour gets depended on. The table is about the documented contract; use judgement where the observable behaviour is popular.
TypeScript and typed APIs
Type changes break builds even when runtime behaviour is identical:
- Adding a member to a union in a return type can break exhaustive
switchstatements: MAJOR unless documented as open. - Making an optional property required in an input type: MAJOR.
- Making an interface property optional in an output type: MAJOR (readers must now handle
undefined). - Raising the minimum TypeScript version required by your
.d.ts: treat as MAJOR, or document a TS support window explicitly.
Before 1.0.0
0.y.z signals an unstable API. Common conventions, which package managers encode:
- npm caret:
^0.4.2means>=0.4.2 <0.5.0, so a minor bump in 0.x is treated as breaking. - Cargo:
0.4.2likewise allows only0.4.x. - Treat
0.MINORas the breaking digit and0.x.PATCHas fixes and features.
Ship 1.0.0 once real users depend on the package in production. Staying on 0.x for years does not remove the obligation; it only hides it.
Pre-releases and build metadata
2.0.0-alpha.1 < 2.0.0-alpha.2 < 2.0.0-beta.1 < 2.0.0-rc.1 < 2.0.0
2.0.0-beta.2 < 2.0.0-beta.11 # numeric identifiers compare numerically
2.0.0+build.5 # build metadata: ignored for precedence
- Use dot-separated numeric counters (
rc.1, notrc1) so ordering is correct. - npm:
npm version prerelease --preid rcproduces2.0.0-rc.0, then-rc.1. Publish withnpm publish --tag nextsolatestis untouched (publishing is outward-facing: confirm with the user first). - Ranges skip pre-releases unless the range itself names one; consumers opt in deliberately.
Ecosystem rules that override plain SemVer
| Ecosystem | Rule |
|---|---|
| Python (PEP 440) | Pre-releases are 2.0.0a1, 2.0.0b2, 2.0.0rc1; post-releases 2.0.0.post1. -rc.1 is normalised, so write the PEP 440 form |
| Go modules | From v2 on, the module path must end in /v2 (module example.com/lib/v2) and imports change; tags are v2.0.0 |
| Rust / Cargo | Cargo treats 0.x minors and any major as incompatible; check with cargo semver-checks |
| npm | peerDependencies ranges are part of your contract; engines changes are user-visible |
| REST / GraphQL APIs | Version the contract (URL, header or schema evolution), not the server build; see api-versioning |
| Apps (not libraries) | SemVer adds little; calendar versions or build numbers are fine |
Tooling to check a bump
These compare the public API between two versions. Run them in CI on release PRs when the project is a library (installs need network):
- Rust:
cargo semver-checks(compares against the last published crate version). - TypeScript: API Extractor (
api-extractor run) keeps an API report file under review;@arethetypeswrong/clichecks packageexportsand types resolution. - Go:
gorelease(golang.org/x/exp/cmd/gorelease) suggests the next version from API diff. - Python:
griffe check <package>reports API breakages against a git ref. - OpenAPI:
oasdiff breaking old.yaml new.yaml.
A clean tool report does not prove behaviour compatibility; it only covers signatures.
Deprecation policy
Write a policy once and follow it every time:
## Deprecation policy
- Deprecations are announced in a MINOR release, never a PATCH.
- Deprecated APIs keep working for at least one further MINOR release and 3 months,
and are removed only in the next MAJOR.
- Each deprecation emits a runtime warning once per process and names the replacement.
- The changelog lists deprecations under "Deprecated" and removals under "Removed".
Mark deprecations in code so tools surface them:
/** @deprecated Since 3.4.0. Use `createClient({ retries })` instead. Removed in 4.0.0. */
export function createRetryingClient(opts: Options) { /* ... */ }
import warnings
def load_config(path):
warnings.warn(
"load_config() is deprecated since 3.4.0; use Config.from_file(). Removal in 4.0.0.",
DeprecationWarning, stacklevel=2,
)
return Config.from_file(path)
Gotchas
- Bumping MAJOR for every trivial break trains users to ignore majors; batch breaking changes into planned majors with a migration guide.
- A PATCH that changes behaviour “to match the docs” can still break production users. Search issues and dependents first.
- Version in code, tag and package metadata must match; derive one from the other (for example setuptools-scm or a release tool) instead of editing three places.
- Yanked or unpublished versions are never reused. If
2.1.0was bad, the fix is2.1.1.
Output
**Last release:** v3.3.1 **Proposed:** v4.0.0 (MAJOR)
| Change | Classification | Why |
|---|---|---|
| Removed `createRetryingClient` | MAJOR | public export removed (deprecated since 3.4.0) |
| New `timeoutMs` option | MINOR | optional, default unchanged |
| Fix off-by-one in pagination | PATCH | restores documented behaviour |
**Tool check:** api-extractor report diff attached; 1 removal, 1 addition
**Migration notes needed:** yes - see changelog draft
Verification
- Every change since the last tag appears in the classification table.
- The API-diff tool for the ecosystem agrees with the proposed bump, or the disagreement is explained.
- Deprecated items carry the version, the replacement and the planned removal version.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Semantic Versioning"
short_description: "Major, minor or patch, decided against the real API"
default_prompt: "$semantic-versioning Is this change breaking? Tell me whether the next version is major, minor or patch" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.