ADR-0020: Agentic Semantic Versioning via commit-msg Hook
Status: Accepted Deciders: elcubonegro, Claude (Sonnet 4.6) Date: 2026-03-27
Context and Problem Statement
Cornerstone enforces Conventional Commits (ADR-0014). The information needed to determine the next semantic version (MAJOR.MINOR.PATCH) is already present in every commit message. However, version bumping is currently a manual step: a developer must remember to edit pyproject.toml and maintain CHANGELOG.md. Agentic sessions routinely skip this step, leaving version = "0.1.0" stale indefinitely.
How can version bumping become automatic, machine-enforced, and zero-friction — the same way ADR-0014 enforced atomic commits?
Decision Drivers
- The Conventional Commit type already encodes the required SemVer bump signal.
- The
commit-msghook is already the right layer for policy enforcement (ADR-0014). - Manual version bumping is a recurring omission in agentic sessions.
- Version must be bumped inside the same commit that introduces the change, not in a follow-up "chore: bump version" commit, to keep history clean.
CHANGELOG.mdmust be auto-generated and committed alongside the version bump.
Considered Options
- Option A: External tool only (
commitizen cz bump,semantic-release) — runs in CI, not local. - Option B:
post-commithook — bumps after the commit, creates a second commit for the bump. Causes recursive hook calls and dirty state. - Option C:
commit-msghook extension — reads the message type, bumpspyproject.toml, stages the file, then the original commit absorbs the bump. - Option D:
prepare-commit-msghook — too early; message not yet finalized.
Decision Outcome
Chosen option: Option C — extend the existing commit-msg hook.
Implementation:
tools/bump_version.py— standalone script that:- Reads the commit message from the file passed by git.
- Parses the Conventional Commit type.
- Applies bump rules (see table below).
- Updates
version = "X.Y.Z"inpyproject.toml. - Appends a changelog entry to
CHANGELOG.md. -
Stages both files via
git add pyproject.toml CHANGELOG.md. -
.git/hooks/commit-msg— extended to callbump_version.pyafter the atomic check passes. -
[skip-semver]bypass token — same pattern as[skip-atomic]; logs bypass to.semver-bypasses.log. Use for initial scaffold commits, CI meta-commits, and docs-only changes.
Bump Rules
| Commit type | SemVer bump |
|---|---|
feat! / BREAKING CHANGE in footer |
MAJOR |
feat |
MINOR |
fix, perf |
PATCH |
refactor, test, ci, build |
PATCH |
docs, style, chore |
No bump (skipped) |
Version file
pyproject.toml is the canonical version source for Python projects. Non-Python projects may override the version file path via .cornerstone.toml (key: version_file).
Positive Consequences
- Every commit touching functional code carries its own version bump.
git log --onelinemaps directly to a CHANGELOG entry.- Agentic sessions no longer produce stale
0.1.0versions. - The same tool ships in every generated project via
post_gen_project.py.
Negative Consequences
commit-msghook now modifies the working tree (stages files), which is unconventional. Mitigated: it only stages specific, known files.- Projects with non-standard
pyproject.tomllayouts need.cornerstone.tomloverride. - Breaking changes require the developer to add
!or aBREAKING CHANGE:footer — agent cannot auto-detect these from code diff.
Links
- Supersedes: nothing (new policy)
- Related: ADR-0014 (atomic commit policy) — extends the same
commit-msghook - Related: ADR-0007 (OWASP CI/CD) — signing and release integrity remain the CI layer's responsibility
- Implementation:
tools/bump_version.py,.git/hooks/commit-msg