Semantic Versioning
Purpose
Provide portable defaults for assigning semantic versions and choosing dependency specifiers without drifting into version lock, version promiscuity, or misleading release numbers.
When to use this skill
- Choosing whether a change is a major, minor, patch, or prerelease bump.
- Reviewing whether a release or dependency range is actually semver-compatible.
- Deciding how to express npm or package.json dependency constraints.
- Choosing between
dependencies,devDependencies,peerDependencies,optionalDependencies, andoverrides. - Explaining why a
^,~,x, or exact range behaves differently than expected.
Scope Boundaries
- This skill owns version numbering meaning and dependency-range and dependency-field selection.
- Use
ref-sp-dev-package-managementfor the release workflow: version source of truth, multi-manifest sync, changelog policy, and registry publishing. - Use
ref-sp-py-commitizenwhen the release is driven by the Pythoncommitizentool (cz bump, version providers, generated changelogs).
Defaults
- Use strict
MAJOR.MINOR.PATCHversions with no leading zeroes. - Start from the public API, not from implementation churn, when deciding the bump level.
- Treat
0.y.zas unstable initial development where the public API is not yet stable. - Use prerelease identifiers such as
-alpha.1,-beta.1, or-rc.1for releases that are intentionally not final. - Prefer ranges that match the actual compatibility promise instead of pinning or widening by habit.
- Keep runtime packages in
dependencies, build and test tooling indevDependencies, host compatibility inpeerDependencies, optional integrations inoptionalDependencies, and dependency-tree surgery in rootoverrides.
Task Framing
| Command or action | What | Why | When | Expected outcome |
|---|---|---|---|---|
| Choose a version bump | Map the change to major, minor, patch, or prerelease. | SemVer is meaningful only when the version reflects the compatibility impact on users. | When preparing a release, changelog entry, or version proposal. | The next version communicates the real compatibility change. |
| Select a range specifier | Pick ^, ~, exact, x, comparator, or compound ranges intentionally. |
Range syntax changes how much future surface consumers accept. | When editing dependency specs or reviewing update policy. | The dependency policy matches the project’s compatibility expectations. |
| Place a dependency in the right field | Choose dependencies, devDependencies, peerDependencies, optionalDependencies, or overrides. |
A correct range in the wrong field still produces the wrong install behavior. | When adding or refactoring package metadata. | Consumers and contributors install the right packages for the right reasons. |
Core Rules
Version meaning
- Increment
MAJORfor backward-incompatible public API changes. - Increment
MINORfor backward-compatible functionality additions and for deprecations that users need to see before removal. - Increment
PATCHfor backward-compatible bug fixes. - Reset lower-order parts when bumping a higher-order part:
1.4.9 -> 1.5.0,1.4.9 -> 2.0.0. - Once a version is released, do not mutate its contents. Publish a new version instead.
Public API first
- Decide the bump based on the public contract, not on how large the internal diff feels.
- If the package has no declared public surface, define that before claiming semver discipline.
- If users rely on behavior that is being removed or changed incompatibly, it is a major change even when the code diff is small.
Major zero and 1.0.0
- Treat
0.y.zas unstable initial development; anything may change and consumers should not assume a stable API. - Do not use
0.xas an excuse for sloppy release semantics. The version should still communicate change magnitude as honestly as possible. - Move to
1.0.0once the public API is intended to be stable and compatibility matters.
Prerelease and build metadata
- Use prerelease identifiers after a hyphen, such as
1.4.0-beta.2, for unstable release candidates. - Prerelease versions sort lower than the corresponding final release:
1.4.0-beta.2 < 1.4.0. - Build metadata after
+does not affect precedence:1.4.0+build.7and1.4.0+build.8compare equal for ordering. - Do not assume prereleases satisfy ordinary ranges; most semver tooling excludes them unless the range opts in or the tool explicitly includes prereleases.
Range selection
- Use an exact version when compatibility must be identical, not merely similar.
- Use
~when patch-level updates are acceptable but automatic minor updates are not. - Use
^when changes are allowed up to the next incompatible release boundary. - Remember that caret behavior is narrower below
1.0.0:^1.2.3means>=1.2.3 <2.0.0^0.2.3means>=0.2.3 <0.3.0^0.0.3means>=0.0.3 <0.0.4
- Remember that tilde allows patch movement within the current minor line:
~1.2.3means>=1.2.3 <1.3.0~1.2means>=1.2.0 <1.3.0
- Use
xor*ranges only when the larger compatibility window is intentional rather than convenient. - Use comparator sets or
||only when a simpler contiguous range cannot express the real compatibility policy.
package.json dependency fields
- Put runtime requirements in
dependencies. - Put lint, test, build, and local development tooling in
devDependencies. - Put host-package compatibility in
peerDependencies, especially for plugins or adapters. - Keep
peerDependenciesas broad as the supported host API allows; over-narrow peer ranges cause unnecessary install conflicts. - Put optional runtime integrations in
optionalDependenciesonly if the code works when they are absent. - Use
overridesat the project root when you must force or replace transitive dependency versions.
Portable dependency policy
- Libraries should usually declare the broadest range they have actually validated, because dependents need upgrade headroom.
- Applications can keep direct dependency specs tighter because the app owner controls the whole deployed artifact, typically with a lockfile enforcing the concrete install.
- Do not put build-only tools in
dependenciesjust because they run in CI. - Do not use local-path specs as a publishing strategy for public packages.
Gotchas
v1.2.3is a common tag name, but the semantic version itself is1.2.3.- A tiny code change can still be a major release if it breaks the public contract.
^0.xranges are much narrower than many people assume.- Prerelease versions do not automatically satisfy normal stable ranges.
- An exact dependency spec does not replace a lockfile; it only constrains the declared acceptable version.
optionalDependenciesdo not make missing-code paths safe by themselves; the application must handle absence explicitly.
Validation
- The proposed bump matches the user-visible compatibility impact on the declared public API.
- Lower-order version parts are reset correctly after minor or major bumps.
- Prerelease versus final-release behavior is intentional and documented.
- Dependency ranges reflect the real compatibility policy, especially for
0.xpackages. - Every package entry is in the correct package.json field for its runtime role.
References
- Semantic Versioning 2.0.0: https://semver.org/
- npm semver reference: https://docs.npmjs.com/cli/v6/using-npm/semver
- npm package.json dependency fields: https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#dependencies
- Read
./references/checklist.mdfor a quick semver review pass.