Versioning Guide
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in BCP 14 RFC 2119 RFC 8174 when, and only when, they appear in all capitals, as shown here.
Libraries MUST use Semantic Versioning 2.0.01. A library is a package published for other code to depend on through a package manager or module system, including npm, crates.io, PyPI, RubyGems, NuGet, Maven, Go modules, and similar ecosystems. A project that ships both a command and importable packages is a library.
Everything else — applications, services, sites, command-line tools,
documentation standards such as crefft itself, and configuration — MUST
use calendar versioning (CalVer2) in the YYYY.M.N format.
Quick Reference
| Task | Rule | Example |
|---|---|---|
| Release a library in any ecosystem | Semantic Versioning 2.0.0 | 1.4.2 |
| Release anything else | Calendar versioning | 2026.10.0 |
| Ship both a command and importable packages | Treat the project as a library | 1.4.2 |
| First release of a month | Four-digit year, unpadded month, counter 0 | 2026.10.0 |
| Further release that month | Highest counter already released that month + 1 | 2026.10.1 |
| First release of a new month | Use the new year/month and reset the counter | 2026.11.0 |
| Tag a release | Prefix the version with v | v2026.10.0 |
| Record incompatibility | First changelog section: ### Breaking | Migration instructions |
| Mark a breaking commit | Conventional Commits ! or footer | feat!: remove legacy API |
| Release a Go library | SemVer; /vN module path suffix at major 2 or higher | v1.4.2 |
Semantic Versioning for Libraries
Libraries MUST use MAJOR.MINOR.PATCH: increment MAJOR for breaking
changes, MINOR for backward-compatible features, and PATCH for fixes.
Reset lower components to 0 when incrementing MAJOR or MINOR.
Breaking changes MUST still appear under ### Breaking, first in the
changelog release section, with migration instructions.
Why
Dependency resolvers in these ecosystems assume SemVer ranges. Under
CalVer the leading number is the year, so a caret range such as
^2026.10.0 would admit every later release that year, including breaking
ones. Library versions must communicate compatibility so dependency updates
do not silently admit incompatible code.
Do: library 1.4.2 -> 2.0.0 for a breaking change
library 1.4.2 -> 1.5.0 for a compatible feature
library 1.4.2 -> 1.4.3 for a fix
Don't: library 2026.10.0 -> 2026.10.1 for a breaking change
library 1.4.2 -> 1.5.0 after removing a public API
Go Modules
Go libraries use SemVer tags such as v0.x.y or v1.x.y, not
vYYYY.M.N. At major version 2 or higher, their module paths MUST
end in /vN with the matching major number.
Why
The Go module reference3 states: “If the module is released at major
version 2 or higher, the module path must end with a major version suffix
like /v2”. A calendar year in the leading position would therefore require
a path such as /v2026.
A Go command-line tool that publishes no importable packages MUST use
CalVer, with vYYYY.M.N tags. Such a tool MUST be built from its
checkout or distributed as release artifacts:
go install module@v2026.10.0 fails when the module path has no /v2026
suffix. A Go module that ships a command and importable packages is a
library and MUST use SemVer instead.
# Do: build a command-only tool from its checked-out release.
git checkout v2026.10.0
go build ./cmd/mytool
# Don't: request a year-major tag through an unsuffixed module path.
go install example.com/team/mytool/cmd/mytool@v2026.10.0
Calendar Versioning
Release numbers MUST have exactly three dot-separated components:
YYYY.M.N
YYYY is the four-digit release year. M is the release month, from 1 to
12. N is the release’s number within that month, counting from 0.
The month and counter MUST NOT contain leading zeros.
Why
A release number identifies when a release was made and its order within
that month. It does not encode a judgment about compatibility. The format
also has valid SemVer syntax1: npm, Cargo, PEP 440, and Homebrew accept
2026.10.0. npm, Cargo, and Go reject leading zeros, so 2026.03.001
is not a portable alternative.
Do: 2026.3.0, 2026.3.1, 2026.10.0
Don't: 2026.03.001, 26.10.0, 2026.13.0
Choosing the Next Number
For the release’s year and month, projects MUST inspect the versions already released. If none exists for that month, the counter MUST be 0; otherwise it MUST be one greater than the highest counter already released in that month. Commit types and breaking changes MUST NOT change this calculation.
Why
Using the release history prevents duplicate numbers. Resetting the counter at each new month makes the first release distinguishable without assigning compatibility meaning to any component.
| Release date | Change | Version |
|---|---|---|
| 2026-03-02 | First release of March | 2026.3.0 |
| 2026-03-09 | Bug fix | 2026.3.1 |
| 2026-03-27 | Breaking change | 2026.3.2 |
| 2026-04-01 | First release of April | 2026.4.0 |
| 2026-10-17 | First release of October | 2026.10.0 |
| 2026-10-20 | Next release of October | 2026.10.1 |
| 2027-01-03 | First release of January | 2027.1.0 |
Do: 2026.10.0 -> 2026.10.1 for any second October release
Don't: 2026.10.0 -> 2027.0.0 because a commit is breaking
Compatibility and Dependency Ranges
A CalVer number does not signal compatibility. Breaking changes MUST be documented under Breaking Changes, independently of the release number.
Why
Two releases in one month can be incompatible, while releases in different years can remain compatible. Users need the changelog and migration notes, not an inferred promise from the leading number.
Libraries use the SemVer rule instead of CalVer. Pre-release suffix policy for CalVer remains undecided.
Migration from Semantic Versioning
Non-library projects migrating to CalVer MUST simply follow their last SemVer release with the first CalVer release. Libraries MUST NOT migrate to CalVer. Previously published versions and tags MUST NOT be rewritten or re-tagged. Projects MUST update their version files, release templates, tag references, and changelog comparison links together.
Why
Existing tags identify immutable releases used by downstream consumers.
2026.10.0 sorts after 2.11.0, so a migration needs no intermediate
release and no alteration to history.
Do: v2.11.0 -> v2026.10.0; leave v2.11.0 pointing at its original commit
Don't: re-tag v2.11.0 or insert an intermediate compatibility-major release
Changelogs
Every project MUST maintain CHANGELOG.md following Keep a
Changelog4, extended with ### Breaking as the first subsection of
each release that contains breaking changes.
Why
A changelog describes the user-facing effect of a release. Putting breaking changes first makes the migration work visible even when a release number does not communicate compatibility.
Format
The example below is for a CalVer project. Library changelogs MUST declare Semantic Versioning 2.0.0 instead and use SemVer release headings and comparison links.
# Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to calendar versioning (`YYYY.M.N`).
## [Unreleased]
## [2026.10.1] - 2026-10-20
### Breaking
- Removed the legacy login endpoint; use `/api/v2/login`.
### Added
- Added profile editing.
### Changed
- Updated the dashboard layout.
### Deprecated
- Deprecated the old export format in favor of JSON.
### Removed
- Removed an unused example file.
### Fixed
- Fixed concurrent-session handling.
### Security
- Fixed an authentication bypass.
## [2026.10.0] - 2026-10-17
### Added
- First release of October.
[Unreleased]: https://github.com/user/repo/compare/v2026.10.1...HEAD
[2026.10.1]: https://github.com/user/repo/compare/v2026.10.0...v2026.10.1
Change Types
| Section | Content |
|---|---|
| Breaking | Incompatible changes, impact, and migration instructions; first when present |
| Added | New features |
| Changed | Changes to existing functionality |
| Deprecated | Features due for removal |
| Removed | Removed features |
| Fixed | Bug fixes |
| Security | Vulnerability fixes |
<!-- Do: make an incompatible change visible first. -->
### Breaking
- Renamed `getUserProfile()` to `getProfile()`; update callers.
<!-- Don't: bury an incompatible change among routine changes. -->
### Changed
- Renamed `getUserProfile()` to `getProfile()`.
Git Tags
Release tags MUST be the version prefixed with v:
# Do
git tag v2026.10.0
# Don't
git tag 2026.10.0
Why
One spelling lets release workflows, archives, and comparison links identify
the same release. The VERSION file and package metadata contain
2026.10.0; the tag contains v2026.10.0.
Conventional Commits
Projects SHOULD use Conventional Commits5 for commit messages.
Types classify changes for release notes. For libraries, feat selects
MINOR, fix selects PATCH, and a breaking marker takes precedence and
selects MAJOR. For CalVer projects, commit types MUST NOT affect
release-number arithmetic:
type(scope): description
[optional body]
[optional footer]
Why
Consistent commit metadata supports changelog generation and review. Libraries also use it to select a compatibility bump; CalVer projects calculate calendar order independently.
| Type | Description |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation only |
style | Formatting |
refactor | Code change without a feature or fix |
perf | Performance improvement |
test | Tests |
chore | Maintenance |
Breaking Changes
A breaking commit MUST retain the Conventional Commits ! marker or
BREAKING CHANGE: footer. Its changelog entry MUST appear under
### Breaking, first in the release section, and state the migration.
Why
The commit marker supports automated detection; the dedicated changelog heading gives consumers a human-readable migration path.
# Do
feat!: remove deprecated API endpoint
BREAKING CHANGE: The /api/v1/users endpoint has been removed.
Use /api/v2/users instead.
# Don't
feat: remove deprecated API endpoint
Release Automation
Release automation MUST first classify the project: a library uses a
SemVer bump from breaking, feature, or fix changes; anything else uses the
next YYYY.M.N from the release date and release history. It MUST
update version files, the changelog, and tag references consistently and
put breaking changes under ### Breaking in both cases.
Why
A commit analyzer that always maps feat to a minor bump or always selects
a calendar number cannot implement both policies. Project classification
determines the calculation; compatibility reporting is required for both.
Do: library + breaking/feature/fix changes -> next MAJOR.MINOR.PATCH
anything else + release date + released versions -> next YYYY.M.N
either policy + breaking changes -> ### Breaking and migration notes
Don't: use CalVer for a library or commit-driven bumps for an application
API and Package Versions
API URL or header versions identify contracts, not calendar releases:
/api/v1/users
/api/v2/users
Accept: application/vnd.myapi.v1+json
Projects SHOULD maintain at most two API contract versions simultaneously and MUST give at least six months’ notice before removing an old one.
Why
An API contract can survive many releases. Changing its URL every month would force migrations without any corresponding contract change.
Package metadata MUST carry the project’s release version: SemVer for libraries and CalVer for everything else. Publishing an application through a package manager does not make it a library unless other code can depend on its importable packages.
| Language | Version location |
|---|---|
| Python | pyproject.toml, __version__ |
| Ruby | version.rb, .gemspec |
| Go | Git tags; go.mod declares the module path |
| Rust | Cargo.toml |
| C# | .csproj, AssemblyInfo.cs |
| TypeScript | package.json |
Enforcement Tools
Conventional Commits
The pinned conventional-pre-commit hook6 checks commit messages:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.6.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
pre-commit install --hook-type commit-msg
Keep a Changelog
A changelog validator MUST accept the additional Breaking section
and the project’s SemVer or CalVer preamble. With kacl-cli7, commit
.kacl.yml alongside CHANGELOG.md; this example configures CalVer,
while libraries MUST use their Semantic Versioning 2.0.0 preamble:
# .kacl.yml
kacl:
allowed_header_titles: [Changelog]
allowed_version_sections: [Breaking, Added, Changed, Deprecated, Removed, Fixed, Security]
default_content:
- All notable changes to this project will be documented in this file.
- "The format is based on Keep a Changelog,"
- "and this project adheres to calendar versioning (`YYYY.M.N`)."
The validator image MUST be pinned, and local checks and CI MUST use the same command and working directory:
# Do: the tool reads CHANGELOG.md and .kacl.yml from /workdir.
# Exact release tag; verified from the container registry.
docker run --rm -v "$(pwd)":/workdir:ro -w /workdir mschmieder/kacl-cli:0.7.4 verify
# Don't: the tool cannot find CHANGELOG.md in its working directory.
# Exact release tag; verified from the container registry.
docker run --rm -v "$(pwd)":/src mschmieder/kacl-cli:0.7.4 verify
Why
The preamble and allowed sections are part of the validator’s grammar.
Without matching configuration, a valid changelog can fail a checker
that still expects a different preamble or rejects Breaking.
References
See Also
- CI/CD Guide - Release workflow checks
- Testing Guide - Testing strategy and best practices
- GitHub Templates - Issue and PR templates
- Release Manager - Release-number calculation and breaking flags
Footnotes
-
Semantic Versioning 2.0.0 - Library compatibility and version syntax ↩ ↩2
-
Calendar Versioning - Calendar-based version conventions ↩