Skip to content

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-msg hook 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.md must 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-commit hook — bumps after the commit, creates a second commit for the bump. Causes recursive hook calls and dirty state.
  • Option C: commit-msg hook extension — reads the message type, bumps pyproject.toml, stages the file, then the original commit absorbs the bump.
  • Option D: prepare-commit-msg hook — too early; message not yet finalized.

Decision Outcome

Chosen option: Option C — extend the existing commit-msg hook.

Implementation:

  1. tools/bump_version.py — standalone script that:
  2. Reads the commit message from the file passed by git.
  3. Parses the Conventional Commit type.
  4. Applies bump rules (see table below).
  5. Updates version = "X.Y.Z" in pyproject.toml.
  6. Appends a changelog entry to CHANGELOG.md.
  7. Stages both files via git add pyproject.toml CHANGELOG.md.

  8. .git/hooks/commit-msg — extended to call bump_version.py after the atomic check passes.

  9. [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 --oneline maps directly to a CHANGELOG entry.
  • Agentic sessions no longer produce stale 0.1.0 versions.
  • The same tool ships in every generated project via post_gen_project.py.

Negative Consequences

  • commit-msg hook now modifies the working tree (stages files), which is unconventional. Mitigated: it only stages specific, known files.
  • Projects with non-standard pyproject.toml layouts need .cornerstone.toml override.
  • Breaking changes require the developer to add ! or a BREAKING CHANGE: footer — agent cannot auto-detect these from code diff.

  • Supersedes: nothing (new policy)
  • Related: ADR-0014 (atomic commit policy) — extends the same commit-msg hook
  • 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