Skip to content

Versioning Guide

Automated semantic versioning for BoB-managed projects — conventional-commit-driven version bumps, git tags, CHANGELOG.md, and GitHub Releases. This is the encyclopedic reference for the BoB-wide versioning capability (capability #275).

TL;DR — Add "versioning": { "enabled": true } to your project manifest and run cdprov refresh. Commit with Conventional Commit messages. Merges to your main branch now cut versioned releases automatically.

Before this, releases across the fleet were manual or absent: bigbrain itself had a single stale v1.0.0 tag and a 0.0.0 package.json. Versioning makes releases declarative, reproducible, and consistent across every project — provisioned from one source of truth rather than hand-copied CI.

BoB standardizes on commit-and-tag-version (the maintained fork of the deprecated standard-version):

  • Minimal dependencies, language-agnostic, runs anywhere Node runs.
  • Reads Conventional Commits → computes the next semver → bumps package.json, regenerates CHANGELOG.md, and creates a git tag.

Alternative considered — release-please (Google): a release-PR model that avoids pushing release commits directly to the main branch (better when the main branch is protected). Deferred because it is less “automatic” (requires merging a release PR) and heavier to set up. Revisit if branch protection on the main branch becomes a hard fleet-wide requirement. See capability #275 for the full decision.

  1. You merge a commit to the main branch (e.g. feat: add X).
  2. .github/workflows/auto-release.yml triggers (scoped to the project’s source paths — see Trigger paths).
  3. commit-and-tag-version bumps the version from the commits since the last tag, rewrites CHANGELOG.md, commits chore(release): X.Y.Z [skip ci], and tags vX.Y.Z.
  4. The workflow pushes the commit + tag with --follow-tags.
  5. A GitHub Release is published from the new CHANGELOG.md section.

The chore(release) commit is loop-guarded twice over, so it re-triggers nothing:

  • auto-release.yml skips its own output via an if: guard on the head-commit subject — that stops the release loop.
  • The subject also carries [skip ci], set by releaseCommitMessageFormat in .versionrc.json. GitHub honours that marker for every workflow on the push, which is what keeps the release commit from re-running CI (#1693). The commit only bumps package.json and regenerates CHANGELOG.md — no source surface, so a full suite run decides nothing.

The subject still begins with chore(release), which is the prefix the commit-msg hook and scripts/release/local-release.sh match on, so the marker is invisible to both. A manual workflow_dispatch lets you force a patch/minor/major bump.

Commit type Bump Appears in changelog under
fix: patch Bug Fixes
feat: minor Features
feat!: / BREAKING CHANGE: major ⚠ Breaking
perf: patch Performance
refactor:, docs:, test:, build:, ci: patch* their sections
chore:, style: none hidden

* Non-feat/fix types contribute to the changelog but only bump when present with other changes; a release with only chore: commits is a no-op.

Versioning is an opt-in manifest capability (it is not a symlinked registry item — cdprov only symlinks; versioning copies template files). It follows the same pattern as the gh_project block.

provisions/<project>.json:

{
"_meta": { "project": "my-app" },
"skills": [],
"commands": [],
"agents": [],
"versioning": {
"enabled": true,
"tool": "commit-and-tag-version",
"main_branch": "main",
"node_version": "22",
"trigger_paths": ["src/**", "package.json"]
}
}

All fields except enabled are optional:

Field Default Meaning
enabled — (required) true provisions versioning; false (or omitted block) is a no-op
tool commit-and-tag-version version-bump engine (only value supported today)
main_branch main branch the workflow triggers on
node_version 22 Node version in the release workflow
trigger_paths (none) push path filter; omitted ⇒ any push to main_branch

The orchestrator populates this block for you (see below); you rarely hand-write it.

Reconcile the project from its manifest — both forms do the same thing:

Slash command:

/provision

Terminal:

Terminal window
cdprov refresh

provision_versioning() copies the template set into the project, never clobbering existing files, and idempotently merges package.json:

Template Destination
templates/versioning/auto-release.yml .github/workflows/auto-release.yml
templates/versioning/.versionrc.json .versionrc.json
templates/versioning/commitlint.config.cjs commitlint.config.cjs

package.json gains a release script + the commit-and-tag-version devDependency, and a 1.0.0 version if it was absent or 0.0.0.

Slash command:

/provision status
/provision diff

Terminal:

Terminal window
cdprov status # shows which versioning files are present / missing
cdprov diff # dry run — what a refresh would create (writes nothing)

versioning is recommended by default for every project (the registry command versioning is default: true) — fulfilling the “all BoB projects should include versioning” goal of capability #275. When you run the orchestrator interview (or scripts/orchestrator/recommend.js), the generated manifest includes a versioning block with trigger_paths chosen for your project type.

The push path filter is parameterized per project type so docs-only or unrelated changes don’t cut releases:

Project type trigger_paths
cms (Drupal, …) docroot/**
api, backend, web-app, library src/**, package.json
cli src/**, bin/**, package.json
framework (BoB-like) skills/**, commands/**, agents/**, registry/**, runbooks/**, scripts/**, hooks/**, bin/**, templates/**, schemas/**, profiles/**, Makefile, package.json
research, mixed, any (none — any push to the main branch)

The mapping lives in VERSIONING_TRIGGER_PATHS in scripts/orchestrator/recommend.js. An existing versioning block in a manifest is always preserved on re-run, so opt-outs and customizations survive.

Automated bumping only works if commits follow the convention — a non-conventional subject makes the release a silent no-op or a mis-bump. Enforcement is built into BoB’s commit-msg git hook (templates/git/hooks/commit-msg, installed by cdi), which does two jobs in order (git allows only one commit-msg hook):

  1. Strip AI boilerplate (Co-Authored-By: Claude, 🤖 Generated, …) — always.
  2. Enforce Conventional Commitsonly when the project has versioning config (.versionrc.json or commitlint.config.* in the repo root). Projects without versioning keep the strip-only behavior, so nothing else is affected.

The enforcement step prefers node_modules/.bin/commitlint (using the provisioned commitlint.config.cjs) when installed, and otherwise falls back to a built-in, dependency-free check — so the convention is enforced even before npm install. Issue-ref scopes (feat(#274): …), feat!: breaking markers, and merge/revert/chore(release) subjects are all accepted; the chore(release) commit that commit-and-tag-version creates passes cleanly and is loop-guarded in CI.

The commit skill already drafts Conventional Commit messages, so commits made through /commit (or warp-drive) pass enforcement by construction.

When Actions cannot run (billing cap, outage), bob-release (or make release-local) cuts the identical release from a workstation: the same npm run release bump, the same atomic --follow-tags push with bounded retry, the same GitHub Release from the new CHANGELOG.md section. It refuses on a dirty tree, a non-main branch, or an out-of-sync HEAD, and is idempotent — re-running resumes a half-landed cut instead of double-bumping. The chore(release) loop guard means auto-release picks up cleanly when Actions returns. The full local delivery path (CI gate, release cut, docs deploy) is the local delivery runbook (#1691).

The workflow pushes the release commit and tag directly to the main branch using the default GITHUB_TOKEN. If branch protection is enabled on the main branch without an exception for github-actions[bot], the push will fail. This is the trade-off behind deferring release-please (the PR-model alternative). No protection ⇒ it just works.

bigbrain itself is the reference implementation (#274): see its .github/workflows/auto-release.yml, .versionrc.json, and package.json release script. The capability was then generalized into the provisionable template set (#276) and wired into the orchestrator (#277).