Grubble
Automatic semantic versioning from conventional commits. Designed to be driven by AI-generated commit messages.
Why
Grubble reads your commit history, applies conventional commit rules, and bumps the version in one command:
feat:→ minor,fix:→ patch,!/BREAKING CHANGE→ major- Optionally writes the new version to
Cargo.tomlorpackage.json - Optionally generates a
CHANGELOG.md(Keep a Changelog format) - Cuts and pushes tags, including floating
v4/v4.1tags for GitHub Actions - Plays well with AI agents that emit conventional commits — see
.github/prompts/sc.prompt.md
Quick Start
# Install
# Make some conventional commits
# Preview the next version
# Release
For CI, jump to GitHub Actions.
Installation
Pre-built binaries
Download the latest release for your platform from GitHub Releases, or grab it directly:
# Linux x86_64
|
# Linux ARM64
|
# macOS Intel
|
# macOS Apple Silicon
|
# Windows (PowerShell)
Cargo
From source
# Binary at target/release/grubble
GitHub Action
- uses: davegarvey/grubble@v4
The Action exposes three outputs for downstream steps:
| Output | Description |
|---|---|
version |
The new version (e.g. 1.2.3). If no bump was needed, this matches previous-version. |
previous-version |
The version before the bump. |
bump-type |
One of major, minor, patch, none. |
When no bump is needed (e.g. only refactor: or chore: commits since the last tag), the Action exits cleanly with bump-type=none rather than failing.
Usage
Every flag has a corresponding option in .versionrc.json (see Configuration). CLI flags override file values.
Configuration
Grubble reads .versionrc.json from the project root. Flags and file values are merged, with flags winning.
| Option | CLI flag | Default | Description |
|---|---|---|---|
preset |
--preset |
git |
Versioning strategy: git, rust, or node. |
packageFiles |
--package-files |
[] |
Comma-separated files to update (for rust / node). |
tagPrefix |
--tag-prefix |
v |
Prefix for git tags. |
commitPrefix |
--commit-prefix |
chore: bump version |
Prefix for the bump commit message. |
tag |
--tag |
false |
Create a git tag for the new version. |
push |
--push |
false |
Push the commit (and tag) to the remote. |
releaseNotes |
--release-notes (-r) |
false |
Include release notes in the tag annotation. Requires tag: true. |
changelog |
--changelog |
false |
Generate or update CHANGELOG.md. |
updateMajorTag |
--update-major-tag |
false |
Maintain a floating v4 tag pointing to the latest v4.x.x. |
updateMinorTag |
--update-minor-tag |
false |
Maintain a floating v4.1 tag pointing to the latest v4.1.x. |
gitUserName |
--git-user-name |
github-actions[bot] |
Identity used for the bump commit when no local git user is configured. |
gitUserEmail |
--git-user-email |
41898282+github-actions[bot]@users.noreply.github.com |
Email used for the bump commit when no local git user is configured. |
types |
— | see Commit Types | Per-type bump behavior. Valid values: major, minor, patch, none. |
If your repo has a local user.name / user.email set, grubble uses those and ignores gitUserName / gitUserEmail. In CI, set these to match your bot user (e.g. github-actions[bot]).
Versioning Strategies
The preset option controls what files grubble writes.
git(default) — tracks versions via tags only. No files are modified. Use this for monorepos or projects with their own versioning scheme.rust— updates theversionfield inCargo.tomland refreshesCargo.lock. Pairs withcargo publish.node— updates theversionfield inpackage.jsonandpackage-lock.json. Pairs withnpm publish.
When switching from git to a file-based preset, or when a package file is behind the latest tag, grubble first syncs the file to the tag (with a chore: sync package version to v... commit) and then proceeds with the normal bump.
Major / Minor Tag Tracking
When updateMajorTag is enabled, grubble maintains a lightweight tag that follows the latest release in its range:
v4→ latestv4.x.xv4.1→ latestv4.1.x(additionally requiresupdateMinorTag)
This is the convention GitHub Actions use, so consumers can reference uses: owner/repo@v4 and automatically get the latest v4 release.
These tags are force-pushed. Anyone who has them checked out locally will need to re-fetch. Only enable this if you follow semver strictly — a breaking change bumps the major and consumers pinned to the old v4 will pick it up.
Changelog Generation
grubble --changelog writes a CHANGELOG.md in Keep a Changelog format:
feat:→ Addedfix:→ Fixedperf:,refactor:→ Changedrevert:→ Removedsecurity:→ Security- Breaking changes → Changed with a BREAKING: prefix
The changelog is committed as part of the release.
GitHub Actions
The Action downloads a pre-built binary, runs the bump, and exposes the new version as outputs. It is the simplest way to wire grubble into your release flow.
name: Release
on:
pull_request:
types:
branches:
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: davegarvey/grubble@v4
with:
push: true
tag: true
update-major-tag: true
The davegarvey/grubble@v4 pin floats to the latest v4.x.x release. Pin to a specific tag (e.g. @v4.9.4) if you need the release frozen.
Manual setup
Use this when you want to run tests and linting before the bump, or when self-hosted runners can't reach GitHub Releases.
name: Release
on:
pull_request:
types:
branches:
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- run: cargo test
- run: cargo clippy -- -D warnings
- name: Bump version and release
run: |
grubble \
--preset rust \
--push \
--tag \
--update-major-tag \
--git-user-name "github-actions[bot]" \
--git-user-email "41898282+github-actions[bot]@users.noreply.github.com"
CI checklist:
- Grant
contents: writeso the workflow can push commits and tags. - Use
fetch-depth: 0onactions/checkoutso grubble can see the full history. - Pin the major version (
@v4) unless you want the release frozen. - Use the GitHub Action unless you need custom logic between the lint and the bump.
CI/CD Patterns
Skip the bump when nothing changed
grubble --bump-type prints major, minor, patch, or none. Use it as a gate:
if [; then
fi
grubble --dry-run always exits 0 (success, including no-op). For the "would a bump happen?" signal, use --bump-type.
Read the bump type
grubble --bump-type prints major, minor, patch, or none. Use it to drive conditional logic:
Read the new version
grubble --raw prints the version that would be released without making changes. It honors --preset (e.g. --preset rust reads from Cargo.toml; --preset node reads from package.json; --preset git reads from the latest tag). Pair it with gh release create or a deploy step:
NEW_VERSION=
Machine-readable output
Both --bump-type and --raw accept --output json for stable, machine-readable output:
{
}
{
}
--output json is rejected when combined with the normal run mode or --dry-run (those modes always print human-readable text). Use --output json from CI scripts that need to parse the result instead of shell-substring matching.
How It Works
- Sync package files to the latest tag (for
rust/nodepresets). - Analyze commits since the last tag using conventional commit rules.
- Pick the highest bump (
major/minor/patch/none). - Update package files and
CHANGELOG.mdif configured. - Create the bump commit.
- Create tags, including
v4/v4.1floating tags if enabled. - Push to the remote if configured.
Commit Types
Default mappings (override with the types option in .versionrc.json):
| Commit | Bump |
|---|---|
! or BREAKING CHANGE |
major |
feat: |
minor |
fix: |
patch |
perf:, refactor: |
none by default; usually remapped to patch or minor |
docs:, test:, chore:, ci:, build:, style: |
none |
Custom mapping example:
Troubleshooting
"Author identity unknown"
Set a git identity in the workflow before running grubble:
- run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
The --git-user-name and --git-user-email flags are ignored when a local user.name / user.email is already set.
"grubble: command not found"
Make sure grubble is on PATH. If you used cargo install --root <dir>, add <dir>/bin to PATH.
No version bump on merge
- Confirm the merged commits include
feat:/fix:(or another type mapped to a bump). - Confirm the workflow has
contents: write. - Confirm
actions/checkoutusesfetch-depth: 0.
Invalid config file
Empty or invalid .versionrc.json falls back to defaults with a warning. Run grubble once to see the warning, then fix the JSON.
Package file not found
- Check that
--package-filespoints to files relative to the repo root. - For multi-package repos, pass each file:
--package-files "Cargo.toml,client/Cargo.toml".
"Invalid format" in $GITHUB_OUTPUT
Fixed in v4.9.4 (see #54). Bump the Action to davegarvey/grubble@v4 (movable) to pick up the fix, or pin to a release ≥ v4.9.4 once you have a chance to verify.
Migration from v4
v5.0.0 is a breaking release for the CLI's exit-code contract. The GitHub Action and version.yml workflow are unchanged in behavior; the v5 contract is what they always wanted.
What changed
grubbleexits 0 on success, including when no bump is needed. Previously, exit 1 meant "no bump" — now exit 1 means "error."grubble --rawhonors--preset.--raw --preset rustreads fromCargo.toml;--raw --preset nodereads frompackage.json;--raw --preset gitreads from the latest tag. Previously,--rawalways read from git tags regardless of preset.- New
--output text|jsonflag for--bump-typeand--raw. Use this from CI scripts that need to parse the result. - Action requires release checksums (was warn-and-continue on missing
.sha256). - The Action's "Get current version" step now uses a single
./grubble --raw --preset <preset>call instead of preset-specific shell branches.
If you script against the CLI
Replace exit-code gates with --bump-type checks:
# v4 (broken on v5)
if ; then
fi
# v5
if [; then
fi
Replace --raw exit-code handling with output parsing:
# v4
NEW_VERSION= || NEW_VERSION=
# v5 — exits 0 whenever a version is produced
NEW_VERSION=
Staying on v4
@v4 (the floating major tag) and all @v4.x.x specific tags remain available indefinitely. Pin to @v4 or @v4.9.4 to stay on the v4 contract. The default floating tag shifts to @v5 once v5 ships.
Contributing
See CONTRIBUTING.md for development setup, commit guidelines, and the release process.
License
MIT.
For AI Users
This tool is built for AI-generated commits that follow the conventional commit format. See .github/prompts/sc.prompt.md for a prompt that produces commits compatible with grubble.