Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

git-std

From commit to release. One tool for conventional commits, versioning, changelog, and git hooks management.

Stop juggling five tools to enforce commit standards, bump versions, and manage hooks. git-std replaces commitizen, commitlint, standard-version, husky, and lefthook with a single, fast binary — zero runtime dependencies.

Features

  • Structured commits — interactive prompt for type, scope, and description, or non-interactive with --message
  • Commit validation — lint messages inline, from file, or across a revision range
  • Version bumping — semver, calver, and patch-only schemes, calculated automatically from commit history
  • Changelog generation — incremental or full, built from conventional commits
  • Git hooks — install, enable/disable, and auto-format staged files safely before commit
  • Lock file sync — automatically update Cargo, npm, yarn, pnpm, deno, uv, and poetry lock files after bump
  • Custom version files — update any file during bump using regex patterns
  • Configuration.git-std.toml with sensible defaults, inspectable via config list/get
  • Shell completions — bash, zsh, fish
  • AI agent skills — install commit and bump skills for your AI coding agent with npx skills add driftsys/git-std
  • CI-ready — JSON output, non-zero exit codes, no interactive prompts in pipelines

Quick start

git std hook install                # set up hooks
git std commit                       # interactive commit
git std bump                         # bump + changelog + tag
git push --follow-tags

Invoked as git std via git’s subcommand discovery.

Getting started

Install

Install script (recommended):

curl -fsSL https://raw.githubusercontent.com/driftsys/git-std/main/install.sh | bash

From source:

cargo install git-std

Shell completions:

# Bash (~/.bashrc)
eval "$(git-std --completions bash)"

# Zsh (~/.zshrc)
eval "$(git-std --completions zsh)"

# Fish (~/.config/fish/config.fish)
git-std --completions fish | source

Set up hooks

git std hook install

This sets core.hooksPath, writes shim scripts, and prompts which hooks to enable. Default: pre-commit and commit-msg.

AI agent skills

Install the std-commit and std-bump skills for your AI coding agent:

npx skills add driftsys/git-std

Make a commit

Stage your changes and run the interactive builder:

git add .
git std commit

Or use non-interactive mode:

git std commit -m "feat(auth): add OAuth2 PKCE flow"

Validate commits

git std lint "feat: add login"
git std lint --range main..HEAD

Use --strict to enforce types and scopes from .git-std.toml.

Preview changelog

git std changelog --stdout

Bump, changelog, and tag

git std bump
git push --follow-tags

This analyses commits since the last tag, calculates the next version, updates version files, generates the changelog, creates a release commit, and tags it.

Use --dry-run to preview without writing anything.

Commands

git std <command> [options]

Global Flags

FlagDescription
--help / -hPrint help
--version / -VPrint version
--color <when>auto (default), always, never
--completions <shell>Generate shell completions to stdout
--updateUpdate git-std to the latest release

git std lint

Validate commit messages against the Conventional Commits specification.

Input modes:

git std lint "feat: add login"                    # inline message
git std lint --file .git/COMMIT_EDITMSG           # from file (strips # comments)
git std lint --range main..HEAD                   # all commits in a range

Flags:

FlagDescription
--file <path>Read message from file
--range <range>Validate all commits in a git revision range
--strictEnforce types/scopes from .git-std.toml
--format <fmt>Output format: text (default) or json

Exit codes: 0 = valid, 1 = invalid, 2 = I/O or usage error.

Examples:

# Validate a single message
git std lint "feat(auth): add OAuth2 PKCE flow"

# Validate all commits on a branch
git std lint --range main..HEAD

# Strict mode — reject unknown types and scopes
git std lint --strict --range main..HEAD

# As a commit-msg hook
git std lint --file "$1"

git std commit

Interactive conventional commit builder. Prompts for type, scope, description, body, and breaking change, then runs git commit.

Flags:

FlagDescription
--type <type>Pre-fill type, skip prompt
--scope <scope>Pre-fill scope, skip prompt
--message <msg>Non-interactive mode
--body <text>Commit body paragraph
--breaking <msg>Add BREAKING CHANGE footer
--footer <text>Add a trailer footer (repeatable)
--signoff / -sAdd Signed-off-by trailer
--dry-runPrint message without committing
--amendPass --amend to git commit
--sign / -SGPG-sign the commit
--all / -aStage tracked changes

Exit codes: 0 = committed, 1 = validation/git error, 2 = usage error.

git std bump

Calculate the next version from conventional commits, update version files, generate changelog, commit, and tag.

Flags:

FlagDescription
--dry-runPrint plan without writing
--prerelease [tag]Bump as pre-release (e.g. 2.0.0-rc.1)
--release-as <ver>Force a specific version
--first-releaseInitial changelog, no bump
--no-tagSkip tag creation
--no-commitUpdate files only
--sign / -SGPG-sign commit and tag
--skip-changelogBump without changelog
--forceAllow breaking changes in patch-only scheme
--stable [branch]Create a stable branch for patch-only releases
--minorUse minor bump (instead of major) when advancing main after stable
--format <fmt>Output format: text (default) or json
--package <name>Filter bump to specific package(s) (monorepo only, repeatable)
--push [remote]Push commit and tags after release (default remote: origin)
--yes / -ySkip branch confirmation prompt

Exit codes: 0 = success, 1 = error.

Monorepo bump

When monorepo = true, each package is versioned independently based on commits touching its path.

# Bump all packages with changes
git std bump

# Preview monorepo bump
git std bump --dry-run

# Bump specific package(s)
git std bump -p core
git std bump -p core -p cli

# JSON output for CI
git std bump --dry-run --format json

Dependency cascade: when package A bumps and package B depends on A, B gets at least a patch bump. Use -p to skip cascade and bump only the named packages.

git std changelog

Generate or update the changelog from git history.

Flags:

FlagDescription
--fullRegenerate entire changelog
--range <range>Generate for a tag range (e.g. v1..v2)
-w, --write [path]Write to file (default: CHANGELOG.md)

Output goes to stdout by default. Pass -w / --write to write to CHANGELOG.md, or -w <path> for a custom path. --full and --range are mutually exclusive. Without either, generates an incremental changelog from unreleased commits since the last tag.

git std init

Scaffold hooks, bootstrap script, and README section in one step. Consolidates hook setup and bootstrap scaffolding for maintainers.

git std init              # scaffold everything interactively
git std init --force      # overwrite existing files
git std init --refresh    # update skills and merge config defaults

What it does:

  1. Creates .githooks/ directory.
  2. Sets core.hooksPath to .githooks.
  3. Writes .hooks templates (pre-commit, commit-msg, pre-push, etc.).
  4. Prompts which hooks to enable, writes shims.
  5. Generates ./bootstrap script.
  6. Generates .githooks/bootstrap.hooks.
  7. Creates .git-std.toml with taplo schema directive (if absent).
  8. Scaffolds agent skills in .agents/skills/ with .claude/skills/ symlinks.
  9. Appends post-clone section to README.md and AGENTS.md (if found).
  10. Stages all created files.

Flags:

FlagDescription
--forceOverwrite existing files
--refreshUpdate skills and merge config defaults (skip hooks)

Exit codes: 0 = success, 1 = error.

git std hook

Manage git hooks defined in .githooks/*.hooks files.

git std hook install          # set up hooks directory and shim scripts
git std hook run <hook>       # execute a hook manually
git std hook list             # display configured hooks
git std hook enable <hook>    # activate a hook (rename .off → shim)
git std hook disable <hook>   # deactivate a hook (rename shim → .off)

Subcommands:

SubcommandDescription
installWrite shim scripts and .hooks templates
run <hook>Execute a hook manually
listDisplay all hooks with enabled/disabled status
enable <hook>Activate a disabled hook
disable <hook>Deactivate an enabled hook

Known hook types: pre-commit, commit-msg, pre-push, post-commit, prepare-commit-msg, post-merge.

Flags (run and list):

FlagDescription
--format <fmt>Output format: text (default), json

git std bootstrap

Post-clone environment setup. Detects convention files and configures the local environment.

git std bootstrap              # run built-in checks + bootstrap.hooks
git std bootstrap --dry-run    # print what would be done

Built-in checks:

Convention fileAction
.githooks/git config core.hooksPath .githooks
.gitattributesgit lfs install + git lfs pull (if filter=lfs)
.git-blame-ignore-revsgit config blame.ignoreRevsFile .git-blame-ignore-revs

After built-in checks, runs .githooks/bootstrap.hooks if present.

Flags:

FlagDescription
--dry-runPrint what would be done without acting

git std doctor

Show everything about your local git-std setup in one command. Three sections: Status, Hooks, Configuration. Problems appear as hints at the bottom.

git std doctor              # show all sections, exit 0 (no problems) or 1
git std doctor --format json  # machine-readable JSON on stdout

Sections:

SectionContents
StatusTool versions: git, git-lfs (if .gitattributes needs it), git-std with update notice if available
HooksAll .githooks/*.hooks files with commands and sigils (! required, ? advisory), enabled/disabled state
ConfigurationAll .git-std.toml keys; explicit values in bold, defaults plain/dim

Example output:

  Status
    git 2.43.0
    git-lfs 3.4.1
    git-std 0.11.3 (update available: 0.12.0)

  Hooks
    commit-msg
      !  git std lint -f
    pre-commit
      !  cargo fmt --check
      !  cargo clippy
    pre-push (disabled)
      !  just verify

  Configuration
    scheme           semver
    strict           true
    ...

  hint: git-lfs not found — required by .gitattributes
  hint: .git-std.toml invalid: expected `=` at line 3

Flags:

FlagDescription
--format <fmt>Output format: text (default), json

Exit codes: 0 = no problems, 1 = one or more hints surfaced, 2 = not a git repository.

git std version

Lightweight, scriptable version queries.

git std version                  # 0.11.3
git std version --describe       # 0.11.3-dev.7+g3a2b1c.dirty
git std version --next           # 0.12.0
git std version --label          # minor
git std version --code           # 10299
git std version --format json    # all fields as JSON

Output goes to stdout. No v prefix.

Flags:

FlagDescription
--describeCargo-style describe: -dev.N pre-release + +hash[.dirty] metadata
--nextNext version from conventional commits since the last tag
--labelBump label (major/minor/patch/none), accounting for pre-1.0
--codeInteger version code
--format <fmt>Output format: text (default), json

Exit codes: 0 = success, 1 = error.

--completions <shell>

Generate shell completion scripts to stdout. The output includes wrappers that enable completion for both git-std and git std invocations. Works without a subcommand — usable regardless of install method.

git std --completions bash   # Bash
git std --completions zsh    # Zsh
git std --completions fish   # Fish

Add to your shell profile:

# Bash (~/.bashrc)
eval "$(git-std --completions bash)"

# Zsh (~/.zshrc)
eval "$(git-std --completions zsh)"

# Fish (~/.config/fish/config.fish)
git-std --completions fish | source

Update Check

git-std periodically checks for newer releases in the background and prints a hint after command output when an update is available.

  • Non-blocking — a detached background process fetches the latest release once every 24 hours.
  • Adapts to install method (cargo, install.sh, nix).
  • Opt-out: GIT_STD_NO_UPDATE_CHECK=1.

Recipes

Git aliases

Shorten common commands with git aliases:

git config --global alias.sc "std commit"
git config --global alias.sb "std bump"
git config --global alias.sl "std changelog"
git config --global alias.sk "std check"

Then use:

git sc           # interactive commit
git sc -am "fix(auth): handle expired tokens"
git sk --range main..HEAD
git sl --stdout  # preview changelog
git sb           # bump + changelog + tag

Semver workflow

Semver is the default scheme — no configuration needed.

Bump rules are inferred from conventional commits:

  • BREAKING CHANGE or ! suffix → major
  • featminor
  • Everything else → patch
git std bump               # auto-detect bump type
git std bump --dry-run     # preview without writing
git std bump --prerelease  # e.g. 2.0.0-rc.1
git std bump --release-as 3.0.0  # force a specific version

Calver workflow

Set the scheme in .git-std.toml:

scheme = "calver"

[versioning]
calver_format = "YYYY.0M.PATCH"

Commit types are ignored — the version is derived from the current date. The patch counter increments within the same period and resets when the period changes.

# March 2026, first release of the month
git std bump          # → 2026.03.0

# March 2026, second release
git std bump          # → 2026.03.1

# April 2026, first release
git std bump          # → 2026.04.0

Common formats:

FormatExampleUse case
YYYY.MM.PATCH2026.3.0Monthly releases
YYYY.0M.PATCH2026.03.0Zero-padded
YY.WW.DP26.12.30Weekly + day
YYYY.MM.DD.PATCH2026.3.18.0Daily releases

Stable branch

Use --stable to create a patch-only maintenance branch from the current version:

# On main at v2.3.0
git std bump --stable

This creates a stable/v2.3 branch where only patch bumps are allowed. Breaking changes are rejected unless --force is used.

# On stable/v2.3 — cherry-pick a fix, then:
git std bump          # → 2.3.1
git std bump          # → 2.3.2

Back on main, use --minor to advance without a major bump:

# On main after stable branch was cut
git std bump --minor  # → 2.4.0 (instead of 3.0.0)

Custom version files

By default, git std bump auto-detects and updates Cargo.toml version fields. For other files, add [[version_files]] entries to .git-std.toml. Each entry needs a file path and a regex whose first capture group matches the version string.

Plain text version file:

[[version_files]]
path = "VERSION"
regex = '(.+)'

Java (pom.xml):

[[version_files]]
path = "pom.xml"
regex = '<version>([^<]+)</version>'

Helm chart:

[[version_files]]
path = "Chart.yaml"
regex = 'version:\s*(.+)'

Python (pyproject.toml):

[[version_files]]
path = "pyproject.toml"
regex = 'version\s*=\s*"([^"]+)"'

Multiple files at once:

[[version_files]]
path = "package.json"
regex = '"version":\s*"([^"]+)"'

[[version_files]]
path = "src/version.h"
regex = '#define\s+VERSION\s+"([^"]+)"'

These are updated alongside auto-detected files during git std bump. Use --dry-run to preview which files would be updated.

Monorepo workflow

Enable per-package versioning for multi-package repositories:

monorepo = true
scheme = "semver"
scopes = "auto"

[versioning]
tag_template = "{name}@{version}"

# Optional — auto-discovered from workspace manifests if omitted
# [[packages]]
# name = "core"
# path = "crates/core"

Packages are auto-discovered from Cargo.toml workspace members, package.json workspaces, or deno.json workspace.

Bump all packages

git std bump                   # bumps all packages with changes
git std bump --dry-run         # preview what would change

Bump specific packages

git std bump -p core           # bump only core
git std bump -p core -p cli    # bump core and cli

The -p flag skips dependency cascade — only the named packages are bumped.

Per-package scheme override

Mix versioning schemes in the same monorepo:

monorepo = true
scheme = "semver"              # default for all packages

[versioning]
tag_template = "{name}@{version}"
calver_format = "YYYY.0M.PATCH"

[[packages]]
name = "core"
path = "crates/core"

[[packages]]
name = "api"
path = "crates/api"
scheme = "calver"              # this package uses calver

Per-package changelog config

Override changelog settings per package:

[[packages]]
name = "core"
path = "crates/core"

[packages.changelog]
hidden = ["chore"]             # show more commit types for core

Each package gets its own CHANGELOG.md in its root directory (e.g. crates/core/CHANGELOG.md). The root CHANGELOG.md includes all commits from all packages.

Git hooks

Setting up hooks

git std hook install

This writes .githooks/*.hooks template files and shim scripts, sets core.hooksPath, and prompts which hooks to enable.

Hook types

HookWhen it runsTypical use
pre-commitBefore a commit is createdLint, format, run fast tests
commit-msgAfter the message is writtenValidate conventional commit
prepare-commit-msgBefore the editor opensPre-fill commit template
post-commitAfter the commit is createdNotifications, stats
pre-pushBefore push sends data to remoteFull test suite, build validation
post-mergeAfter a merge completesReinstall deps, rebuild

Command prefixes

Each .hooks file contains one command per line with a prefix that controls behaviour:

PrefixNameBehaviour
!checkRun command, block on failure
~fixIsolate staged files, run, re-stage
?advisoryRun command, never block

The ~ prefix safely isolates staged content, runs the formatter, and re-stages the result. $@ is populated with the list of staged files.

pre-commit: lint only

Check formatting and lint without modifying files. The commit is blocked if any check fails.

.githooks/pre-commit.hooks:

! cargo fmt --check -- $@
! cargo clippy --workspace -- -D warnings

pre-commit: auto-format

Automatically format staged files and re-stage the result. The ~ prefix handles the stash dance so unstaged changes are preserved.

.githooks/pre-commit.hooks:

~ cargo fmt -- $@
! cargo clippy --workspace -- -D warnings
? cargo test --workspace

This formats first, then lints (blocking), then runs tests (advisory — never blocks the commit).

commit-msg: validate message

Reject commits that don’t follow conventional commits.

.githooks/commit-msg.hooks:

! git std lint --file {msg}

pre-push: PR readiness check

Run the full validation suite before pushing. Catches issues before CI does.

.githooks/pre-push.hooks:

! cargo test --workspace
! cargo clippy --workspace -- -D warnings
! git std lint --range origin/main..HEAD

Managing hooks

git std hook enable pre-push
git std hook disable post-commit
git std hook list              # see status of all hooks

git-std — Configuration

git-std reads .git-std.toml in the project root. All fields are optional — sensible defaults are used when the file is absent or a field is omitted.

Editor schema

A JSON Schema is available for validation and autocomplete in any JSON Schema-aware TOML editor.

Add the $schema key to your .git-std.toml:

"$schema" = "https://driftsys.github.io/git-std/schemas/v1/git-std.schema.json"

Or use a taplo inline directive (does not modify the file):

#:schema https://driftsys.github.io/git-std/schemas/v1/git-std.schema.json

Full schema

# ── Project ───────────────────────────────────────────────────────
scheme = "semver"                              # semver | calver | patch
types = ["feat", "fix", "docs", "style",
         "refactor", "perf", "test",
         "chore", "ci", "build", "revert"]
scopes = ["auth", "api", "ci", "deps"]         # "auto" | string[] | omit
strict = true                         # enforce types/scopes
monorepo = false                      # per-package versioning
release_branch = "main"               # expected branch for bumps
refs_required = ["feat", "fix"]       # types that require a footer ref

# ── Versioning ────────────────────────────────────────────────────
[versioning]
tag_prefix = "v"                               # git tag prefix
prerelease_tag = "rc"                          # default pre-release id
calver_format = "YYYY.MM.PATCH"                # only when scheme = "calver"
tag_template = "{name}@{version}"              # per-package tag format

# ── Changelog ─────────────────────────────────────────────────────
[changelog]
title = "Release Notes"                        # optional, custom heading
hidden = ["chore", "ci", "build", "style", "test"]
bug_url = "https://github.com/org/repo/issues" # optional, issue link base

[changelog.sections]
feat = "Features"
fix = "Bug Fixes"
perf = "Performance"
refactor = "Refactoring"
docs = "Documentation"

# ── Version files ────────────────────────────────────────────
[[version_files]]
path = "pom.xml"
regex = '<version>([^<]+)</version>'

# ── Packages (monorepo) ─────────────────────────────────────
[[packages]]
name = "core"
path = "crates/core"
# scheme = "patch"                             # optional override

Fields

Top-level

FieldTypeDefaultDescription
schemestring"semver"Versioning scheme (see below)
typesstring[]11 standard typesAllowed conventional commit types
scopes"auto" or string[]NoneScope discovery or explicit allowlist
strictboolfalseEnforce types/scopes validation without --strict flag
monorepoboolfalseEnable per-package versioning
release_branchstring(none)Branch that git std bump is expected to run on
refs_requiredstring[][]Commit types that require a footer reference

Default types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert.

Versioning schemes:

  • semverBREAKING CHANGE or ! → major, feat → minor, everything else → patch. Resets lower components (e.g. 1.2.31.3.0). Supports --prerelease.
  • calver — date-based, ignores commit types. Uses calver_format (default YYYY.MM.PATCH). Patch increments within the same period, resets on period change. No --prerelease.
  • patch — always increments patch only, never touches major/minor. Breaking changes rejected unless --force is used. Intended for maintenance/LTS branches.

Scopes behavior:

  • Not set (default) — no scope validation, any scope accepted
  • scopes = "auto" — discover scopes from workspace layout (crates/*, packages/*, modules/*)
  • scopes = ["auth", "api"] — explicit allowlist

When scopes is set (either "auto" or an array) and --strict is used, a scope is required and must be in the resolved list. For git std commit, the resolved scopes populate the interactive scope prompt.

release_branch: When set, bumping on a different branch triggers a confirmation prompt (or an error in non-interactive mode unless --yes is supplied). When unset, both main and master are accepted without prompting.

refs_required: Types listed here must include a footer reference (e.g. Refs: #123). Validated during git std lint with --strict.

[versioning]

FieldTypeDefaultDescription
tag_prefixstring"v"Git tag prefix (e.g., v1.0.0)
prerelease_tagstring"rc"Default pre-release identifier
calver_formatstring"YYYY.MM.PATCH"Calendar version format (only when scheme = "calver")
tag_templatestring"{name}@{version}"Per-package tag format (only when monorepo = true)

Calendar version format tokens:

TokenDescriptionExample
YYYYFull year2026
YYShort year26
0MZero-padded month03
MMMonth (no padding)3
WWISO week number11
DDDay of month13
PATCHAuto-incrementing patch counter, resets each period0, 1, 2
DPDay of week (1=Mon–7=Sun) concatenated with patch counter30, 31, 32

Common formats: YYYY.MM.PATCH (monthly releases), YYYY.0M.PATCH (zero-padded month), YY.WW.DP (weekly with day-of-week), YYYY.MM.DD.PATCH (daily releases).

Bump rules are inferred from the scheme — not configurable. For semver: BREAKING CHANGE or ! triggers major, feat triggers minor, everything else triggers patch.

[changelog]

FieldTypeDefaultDescription
titlestring(none)Custom changelog title
hiddenstring[]["chore", "ci", "build", "style", "test"]Types excluded from changelog
bug_urlstring(none)URL template for bug/issue links

[changelog.sections]

Maps commit types to changelog section headings. Types not listed here use the type name as the heading.

KeyDefault
feat"Features"
fix"Bug Fixes"
perf"Performance"
refactor"Refactoring"
docs"Documentation"

[[version_files]]

Optional array of custom version files to update during bump. Each entry specifies a file path and a regex whose first capture group contains the version string.

[[version_files]]
path = "pom.xml"
regex = '<version>([^<]+)</version>'

[[version_files]]
path = "Chart.yaml"
regex = 'version:\s*(.+)'
FieldTypeDescription
pathstringFile path relative to repo root
regexstringRegex with capture group containing version

Entries with missing path or regex are silently skipped. These are in addition to auto-detected version files (e.g. Cargo.toml).

[[packages]]

Explicit package definitions for monorepo workspaces. When monorepo = true and no packages are listed, git-std auto-discovers packages from workspace manifests (Cargo, npm, Deno) or subdirectories with version files.

[[packages]]
name = "core"
path = "crates/core"
scheme = "patch"                   # optional: override global scheme

[[packages.version_files]]         # optional: override version files
path = "version.txt"
regex = '(\d+\.\d+\.\d+)'

[packages.changelog]               # optional: override changelog config
title = "Core Changelog"
hidden = ["chore"]
FieldTypeDescription
namestringPackage name (used in tags and changelogs)
pathstringPackage root relative to repo root
schemestringOptional versioning scheme override
version_filesarrayOptional version files override
changelogtableOptional changelog config override

Entries with missing name or path are silently skipped.

Inferred settings

These are not configurable — git-std resolves them automatically:

ConcernResolution
Bump rulesInferred from scheme
Version filesAuto-detected (Cargo.toml)
URLsInferred from git remote get-url origin
Changelog outputCHANGELOG.md at root; {path}/CHANGELOG.md per package
Release commitchore(release): <version> (includes packages)
Package dependenciesResolved from workspace manifests (runtime only)

Minimal examples

No config needed — git-std works with zero configuration using conventional defaults.

Types and scopes only:

types = ["feat", "fix", "chore"]
scopes = ["auth", "api"]

Calver project:

scheme = "calver"

[versioning]
calver_format = "YYYY.0M.PATCH"

Custom changelog sections:

[changelog]
hidden = ["chore", "ci"]

[changelog.sections]
feat = "New Features"
fix = "Bug Fixes"
perf = "Performance Improvements"

Monorepo with per-package versioning:

monorepo = true
scheme = "semver"
scopes = "auto"

[versioning]
tag_template = "{name}@{version}"

[[packages]]
name = "core"
path = "crates/core"

[[packages]]
name = "cli"
path = "crates/cli"

When monorepo = true, each package is bumped independently based on commits touching its path. Packages are auto-discovered from workspace manifests if [[packages]] is omitted.

Dependency cascade: if package A bumps and package B depends on A (runtime dependency in Cargo.toml or package.json), B receives at least a patch bump. Use -p to skip cascade.

Per-package changelogs: each bumped package gets a CHANGELOG.md in its root directory. The root CHANGELOG.md includes all commits.

Mixed versioning schemes:

monorepo = true
scheme = "semver"

[versioning]
tag_template = "{name}@{version}"
calver_format = "YYYY.0M.PATCH"

[[packages]]
name = "core"
path = "crates/core"

[[packages]]
name = "api"
path = "crates/api"
scheme = "calver"              # override: date-based versioning

Skills

Agent skills for Claude Code, OpenCode, and GitHub Copilot.

Skills are stored in .agents/skills/ and symlinked into .claude/skills/ for Claude Code compatibility.

/std-commit

Author a conventional commit for staged changes.

Invoke with /std-commit in your agent.

The skill:

  1. Checks git std is installed — offers to install via ./bootstrap or the install script if not.
  2. Runs git std --context to read project config, valid types, scopes, and the staged diff.
  3. Proposes a git std commit --type X [--scope Y] --message Z command.
  4. For feat and fix commits, asks for a related issue number and pre-fills it from the branch name when available (e.g. feat/123-my-feature#123).
  5. Requires your approval before running.

/std-bump

Orchestrate a version bump.

Invoke with /std-bump in your agent.

The skill:

  1. Checks git std is installed — offers to install if not.
  2. Runs git std --context to assess stability, branch, and tag state.
  3. Runs git std bump --dry-run and shows the full plan.
  4. Asks for confirmation, package selection (monorepo), and whether to push.
  5. Requires your approval before running.

git-std — Specification

A single Rust binary for conventional commits, version bumping, changelog generation, and git hooks management.

Repository: driftsys/git-std Binary: git-std (invoked as git std via git’s subcommand discovery) Status: Draft


Part 1 — Architecture

1.1 Problem Statement

Modern git workflows require four distinct concerns currently served by separate tools across multiple runtimes:

ConcernCurrent toolsRuntime
Commit message conventionscommitizen, commitlintNode.js
Version bumpingstandard-version, release-pleaseNode.js
Changelog generationgit-cliff, conventional-changelogRust / Node.js
Git hooks managementpre-commit, husky, lefthookPython / Node.js / Go

Installing four tools across three runtimes to enforce git conventions is excessive. git-std consolidates all four into one statically-linked binary with zero runtime dependencies.

1.2 Design Principles

  1. One binary, four concerns. Commits, versions, changelogs, and hooks ship as a single tool. No Node.js runtime, no Python, no framework dependencies.

  2. Native hooks only. No pre-commit.com framework. Git hooks are plain shell commands in .githooks/*.hooks files, managed natively by git std hook.

  3. Config is TOML. .git-std.toml is the single config file for versioning, changelog, and commit conventions. Hooks config lives in the .githooks/*.hooks files themselves — plain text, not TOML or YAML.

  4. Install via curl. Precompiled static binaries per OS/arch. Not distributed through cargo install — avoids requiring the Rust toolchain on developer machines.

  5. Single responsibility. git-std owns everything between git commit and git tag — the developer’s git workflow. It does not touch repo structure, compliance, formatting, or linting.

  6. Zero-config sensible defaults. Every subcommand works without .git-std.toml. Missing config means conventional commit standard types, semver, v tag prefix, and default changelog sections.

1.3 Scope

git-std covers six concerns:

ConcernSubcommand
Conventional commit creationgit std commit
Commit message validationgit std lint
Version bump + changeloggit std bump, git std changelog
Maintainer setupgit std init
Git hooks managementgit std hook run, git std hook list
Post-clone bootstrapgit std bootstrap
Shell completionsgit std --completions <shell>

Out of scope: repo scaffolding, directory structure compliance, formatting, linting, CI pipeline generation, dependency management.

1.4 Configuration Files

FilePurpose
.git-std.tomlVersioning scheme, commit types, scopes, changelog sections
.githooks/*.hooksHook command definitions (one file per git hook)
.githooks/<hook>Shim scripts generated by git std hook install
Cargo.tomlVersion file — git-std reads/writes the version field only

git-std auto-detects version files and reads/writes only the version field during bump. See §2.3.1 for the full list of built-in version files.

1.5 Implementation

Language: Rust

Workspace crates:

CrateRole
git-stdCLI binary — orchestrates I/O, git, config, dispatch
standard-commitConventional commit parsing, linting, formatting
standard-versionSemantic version bump calculation, version file detection and update (with file I/O)
standard-changelogChangelog generation from conventional commits
standard-githooksHook file format parsing, shim generation

Library crates are pure — no I/O, no terminal output — except standard-version, which performs file I/O for version file detection and updates.

Key dependencies:

CratePurpose
clapCLI argument parsing with subcommand dispatch
clap_completeShell completion script generation (bash, zsh, fish)
inquireInteractive terminal prompts (type/scope/description selection)
yansiTerminal colours and --color flag support
toml.git-std.toml reading and Cargo.toml version updates
semverSemantic version parsing and bumping

Binary size target: ~5-8 MB statically linked (lto = true, strip = true, codegen-units = 1).

Static linking: musl on Linux for glibc-independent distribution. Separate x86_64 and aarch64 builds per platform.

1.6 Distribution

Precompiled binaries published as GitHub Release assets:

OSArchitectureTarget triple
Linuxx86_64x86_64-unknown-linux-musl
Linuxaarch64aarch64-unknown-linux-musl
macOSx86_64x86_64-apple-darwin
macOSaarch64 (Apple Silicon)aarch64-apple-darwin
Windowsx86_64x86_64-pc-windows-msvc

Install script at https://raw.githubusercontent.com/driftsys/git-std/main/install.sh:

  1. Detects OS and architecture
  2. Downloads the matching binary from the latest GitHub Release
  3. Installs to ~/.local/bin/git-std (Linux/macOS) or %LOCALAPPDATA%\bin\git-std.exe (Windows)
  4. Verifies with git std --version

Git discovers git-std as a subcommand automatically when it’s on $PATH — running git std invokes the git-std binary.

1.7 Dogfooding

The driftsys/git-std repository uses git-std for its own releases. CI builds via cross-compilation (GitHub Actions matrix), creates a GitHub Release with SHA-256 checksums, and publishes the install script. The .git-std.toml in the repo is the reference configuration.


Part 2 — Feature Specification

2.1 git std commit

Interactive conventional commit builder. Creates a well-formed conventional commit message and runs git commit.

Behaviour:

  1. If .git-std.toml exists, read types for the type list and scopes for scope suggestions. Otherwise, use the conventional commit standard types.
  2. Prompt for type (required), scope (optional), subject (required), body (optional), breaking change footer (optional), trailer footers (optional).
  3. Assemble the message: <type>(<scope>): <subject>\n\n<body>\n\n<trailers>.
  4. Validate the assembled message against the conventional commit spec (same rules as git std lint).
  5. Run git commit -m "<message>" with any passthrough flags (--sign, --amend, --all).

Flags:

FlagDescription
--type <type>Pre-fill type, skip type prompt
--scope <scope>Pre-fill scope, skip scope prompt
--message <msg>Non-interactive mode, full message provided. Validated before committing.
--body <text>Commit body paragraph (extended description), separated by blank line
--breakingAdd BREAKING CHANGE footer (prompts for description)
--footer <text>Add a trailer footer (repeatable, e.g. Co-authored-by: Name <email>)
--signoff / -sAdd Signed-off-by trailer from git user.name / user.email
--dry-runPrint the assembled message to stdout, do not commit
--amendPass --amend to git commit
--sign / -SPass --gpg-sign to git commit
--all / -aPass --all to git commit (stage tracked changes)

Exit codes: 0 = committed, 1 = validation failed or git error, 2 = usage error.

Example — interactive:

$ git std commit
? Type:       feat
? Scope:      auth
? Subject:    add OAuth2 PKCE flow
? Body:       (empty)
? Breaking:   (none)
? Footer:     (none)

→ feat(auth): add OAuth2 PKCE flow

Example — non-interactive:

git std commit -a --type fix --scope auth -m "fix(auth): handle expired refresh tokens"

# With trailers
git std commit --type feat -m "add login" --footer "Co-authored-by: Alice <a@b.com>"
git std commit --type fix -m "fix crash" -s
git std commit --type feat -m "new api" \
  --footer "Co-authored-by: Alice <a@b.com>" \
  --footer "Reviewed-by: Carol <c@d.com>"

# With commit body (subject ≤50 chars, body wrapped at 72 chars)
git std commit --type fix --scope cache \
  -m "prevent race condition in invalidation" \
  --body "The cache invalidation routine was checking stale entries
after acquiring the lock, creating a window where two threads
could invalidate the same entry. Wrap the check-and-clear in a
single lock acquisition instead of splitting into two operations."

Commit message guidelines:

  • Subject line: imperative mood, ≤50 characters, no period
  • Body (optional): explain what changed and why, not how — the diff shows that
  • Body wrapping: 72 characters per line for readability
  • Body length: 2–5 sentences typical

2.2 git std lint

Validate one or more commit messages against the conventional commit specification.

Validation rules:

  1. Message matches ^<type>(\(<scope>\))?!?: .+ where type is alphanumeric lowercase.
  2. Subject line (first line) does not exceed 100 characters.
  3. If --strict flag or strict = true in config: type must be in the known types list (from .git-std.toml or defaults). Scope must be in the known scopes list if scopes is defined.
  4. Body, if present, is separated from subject by a blank line.
  5. Footer tokens (BREAKING CHANGE:, Refs:, etc.) follow the conventional commit footer spec.
  6. In --range mode: process commits (merges, reverts, fixups, squashes, initial commits) are automatically skipped and excluded from the valid/total count.

Input modes:

ModeUsage
Positional argumentgit std lint "feat: add feature"
--file <path>git std lint --file .git/COMMIT_EDITMSG
--range <range>git std lint --range main..HEAD
stdinecho "feat: ok" | git std lint -

Flags:

FlagDescription
--file <path>Read message from file
--range <range>Validate all commits in a git revision range
--strictEnforce: type must be known, scope must be known (if defined), scope required
--format <fmt>Output format: text (default), json

Exit codes: 0 = all valid, 1 = one or more invalid.

Output on failure:

$ git std lint "added new feature"
✗ invalid: missing type prefix
  Expected: <type>(<scope>): <description>
  Got:      added new feature

Output with --range:

Process commits (merges, reverts, fixups) are automatically skipped and not counted as failures.

$ git std lint --range main..HEAD
  ✓ feat(auth): add OAuth2 PKCE flow
  ✓ fix(auth): handle expired refresh tokens
  ~ abc1234 Merge pull request #42 from owner/branch
  ✗ updated readme
    → missing type prefix

2/3 valid  (1 skipped)

2.3 git std bump

Calculate the next version from conventional commits since the last tag, update version files, generate changelog, commit, and tag.

Algorithm:

  1. Find the latest version tag matching <tag_prefix><semver> (default v*).

  2. Collect all commits between that tag and HEAD.

  3. Parse each commit as a conventional commit. Non-conforming commits are ignored (not an error).

  4. Apply bump rules inferred from scheme. The highest bump level wins (major > minor > patch):

    • BREAKING CHANGE footer or ! suffix → major
    • feat → minor
    • fix / perf / revert → patch

    Pre-1.0 convention (major == 0): bump levels are downshifted following the Rust/Cargo convention:

    Commit type>= 1.0.0< 1.0.0
    Breaking changemajorminor
    Featureminorpatch
    Fixpatchpatch

    Example: 0.10.2 + breaking → 0.11.0, 0.10.2 + feat → 0.10.3. To force a 1.0.0 release: git std bump --release-as 1.0.0.

  5. If no bump-worthy commits exist, print a message and exit 0 (not an error).

  6. Compute the new version string.

  7. Update all version files: a. Auto-detected built-in files (see §2.3.1). b. Custom files from .git-std.toml [[version_files]] (see §2.3.2). c. Report each file updated in the output.

  8. Sync ecosystem lock files. Only lock files that already exist on disk are synced. Missing tools emit a warning but never abort the bump. The version file name is matched with ends_with, so custom [[version_files]] entries pointing to subdirectory paths (e.g. crates/my-crate/Cargo.toml) are detected correctly.

    Version fileLock fileSync command
    Cargo.tomlCargo.lockcargo update --workspace
    package.jsonpackage-lock.jsonnpm install --package-lock-only
    package.jsonyarn.lockyarn install --mode update-lockfile
    package.jsonpnpm-lock.yamlpnpm install --lockfile-only
    deno.jsondeno.lockdeno install
    pyproject.tomluv.lockuv lock
    pyproject.tomlpoetry.lockpoetry lock --no-update
  9. Generate changelog section for this release via standard-changelog.

  10. Prepend the section to CHANGELOG.md.

  11. Create commit: chore(release): <version>. All updated version files and synced lock files are included in the release commit.

  12. Create annotated tag: <tag_prefix><version>.

Flags:

FlagDescription
--dry-runPrint the full plan without writing anything
--prerelease [tag]Bump as pre-release (e.g., 2.0.0-rc.1). Default tag from [versioning] prerelease_tag.
--release-as <version>Force a specific version, skip calculation
--first-releaseUse current version for initial changelog. No bump.
--no-tagUpdate files and commit, skip tag creation
--no-commitUpdate files only, no commit or tag
--sign / -SGPG-sign the release commit and annotated tag
--skip-changelogBump version files without changelog generation
--forceAllow breaking changes in patch-only scheme
--stable [branch]Create a stable branch for patch-only releases (optional custom branch name)
--push [remote]Push commit and tags after release. Without a remote, pushes to origin
--minorUse minor bump (instead of major) when advancing main after --stable

Exit codes: 0 = success (or no bump needed), 1 = error.

Output:

$ git std bump

  Analysing commits since v1.4.2...
    3 feat, 2 fix, 1 BREAKING CHANGE

  1.4.2 → 2.0.0 (major — breaking change detected)

  Updated:
    Cargo.toml           1.4.2 → 2.0.0
    package.json         1.4.2 → 2.0.0
    gradle.properties    1.4.2 → 2.0.0  (VERSION_CODE: 42 → 43)

  Changelog:
    CHANGELOG.md         prepended v2.0.0 section

  Committed: chore(release): 2.0.0
  Tagged:    v2.0.0

  Push with: git push --follow-tags

Version file resolution: git-std auto-detects built-in version files at the repository root (see §2.3.1). For Cargo.toml workspace manifests, it finds the binary crate member. Additional files can be declared via [[version_files]] in .git-std.toml (see §2.3.2).

Calver support: when scheme = "calver", date-based segments are computed from the current date. Default format YYYY.MM.PATCH. The PATCH counter increments if the date segments match the previous version, and resets to 0 when they change.

Calver format tokens:

TokenDescriptionExample
YYYYFull year2026
YYShort year26
0MZero-padded month03
MMMonth (no padding)3
WWISO week number11
DDDay of month13
PATCHAuto-incrementing patch counter, resets each period0, 1, 2
DPDay of week (1=Mon–7=Sun) concatenated with patch counter30, 31, 32

Common formats: YYYY.MM.PATCH (monthly), YYYY.0M.PATCH (zero-padded month), YY.WW.DP (weekly with day-of-week), YYYY.MM.DD.PATCH (daily).

Early validation: when scheme = "calver", the calver_format is validated at config parse time. If the format is invalid, a warning is printed and the default format (YYYY.MM.PATCH) is used instead.

2.3.1 Built-in Version Files

Auto-detected from root markers. No config needed.

FileEcosystemEngineDetection
Cargo.tomlRustTOML (toml)already implemented
package.jsonNode / TypeScriptJSONpackage.json at root
deno.json / deno.jsoncDenoJSON/JSONCdeno.json or deno.jsonc at root
pyproject.tomlPythonTOML (toml)pyproject.toml at root with [project] table
pubspec.yamlFlutter / Darttext (line-level)pubspec.yaml at root
gradle.propertiesAndroid / Gradletext (key=value)gradle.properties at root with VERSION_NAME key
VERSIONanytext (overwrite)VERSION file at root

Detection is best-effort: if the file exists and contains a recognisable version field, update it. If the file exists but has no version field (e.g. a package.json without "version"), skip silently. Never create files that don’t already exist.

Engine details:

TOML — reuse the existing toml crate. Cargo.toml already works. pyproject.toml reads [project] version. Preserves comments and formatting.

JSON / JSONCserde_json for package.json. JSONC support for deno.jsonc needs comment-preserving handling (line-level regex on the "version" field, or a JSONC-aware crate).

Text (line-level)pubspec.yaml: match ^version:\s*(.+) and replace the version part. gradle.properties: match ^VERSION_NAME=(.+) and replace. VERSION: overwrite entire file content with the version string + newline.

gradle.propertiesVERSION_CODE: Android projects use two version identifiers: VERSION_NAME (semver string) and VERSION_CODE (monotonic integer). git std bump updates VERSION_NAME to the new version. VERSION_CODE is incremented by 1 on every bump. If VERSION_CODE is not present, it is not added.

pubspec.yaml — build number: Flutter’s version field supports a build number suffix: 1.2.3+42. If the existing value has a +N suffix, increment N by 1 on every bump. If no suffix is present, leave it without one.

2.3.2 Custom Version Files

Users can declare additional files in .git-std.toml using regex. The first capture group is the version string to replace.

[[version_files]]
path = "pom.xml"
regex = '<version>(.*)</version>'

[[version_files]]
path = "CMakeLists.txt"
regex = 'project\(myapp VERSION ([^\)]+)'

[[version_files]]
path = "Directory.Build.props"
regex = '<Version>(.*)</Version>'

Rules:

  • path is relative to repo root.
  • regex uses RE2 syntax (Rust regex crate — linear time, no backtracking).
  • First capture group = version string to replace. No capture group = error at config parse time.
  • If the file doesn’t exist, warn and skip (not an error).
  • If the regex doesn’t match, warn and skip.
  • Multiple [[version_files]] entries are supported.

2.3.3 Monorepo Mode

When monorepo = true in .git-std.toml, git std bump performs per-package versioning in addition to the root version bump.

Package discovery:

  1. Explicit [[packages]] entries in config (highest priority).
  2. Auto-discovery from workspace manifests:
    • Cargo.toml [workspace] members
    • package.json workspaces
    • deno.json workspace
  3. Fallback: subdirectories containing version files.

Per-package bump algorithm:

  1. Collect all tags once.
  2. For each package: a. Find the latest tag matching tag_template for that package name. b. Walk commits between that tag and HEAD, filtered by the package path (git log --first-parent -- {path}). c. Parse conventional commits and determine bump level (same rules as root bump). d. Compute new version string.
  3. If scheme = "calver" for a package (via per-package scheme override or global), use date-based versioning: any commit touching the package path triggers a new version. Same date period increments the patch counter.

Dependency cascade:

After computing direct bumps, git-std resolves runtime dependencies from workspace manifests (Cargo, npm). If package A bumps and package B depends on A, B receives at least a patch bump. Dev-dependencies are excluded. The cascade iterates until stable (no new bumps).

The -p / --package flag skips cascade — only the named packages are bumped.

Apply phase:

  1. Write version files for each bumped package (respecting per-package [[packages.version_files]] overrides).
  2. Write per-package CHANGELOG.md in each package root (respecting per-package [packages.changelog] overrides).
  3. Write root CHANGELOG.md from all commits.
  4. Sync lock files once.
  5. Single commit: chore(release): v{root}, {pkg}@{ver}, ...
  6. Multiple tags: root v{version} + per-package tags from tag_template (default {name}@{version}).

Flags:

FlagDescription
--package <name>Filter bump to specific packages (repeatable)

All existing bump flags (--dry-run, --no-tag, --no-commit, --skip-changelog, --format json, etc.) work in monorepo mode.

Output (monorepo dry-run):

$ git std bump --dry-run

  core    0.2.0 → 0.3.0   minor — new feature
  cli     0.1.4 → 0.1.5   patch — dependency cascade (core)
  root    0.8.0 → 0.9.0   minor — new feature

  Tags:
    v0.9.0
    core@0.3.0
    cli@0.1.5

First release: when no tag and no version file exist for a package, the first semver release defaults to 0.1.0. Calver packages use the current date.

2.3.4 Lifecycle Hooks

git std bump supports four lifecycle hooks that run at defined points in the bump pipeline. Hook files live in .githooks/ with the .hooks extension, using the same format and sigils as git hooks (see §2.6).

Lifecycle positions:

                      ← pre-bump               (gate: validate, abort)
1. Detect version
2. Collect + parse commits
3. Compute new version
                      ← post-version {version} (build, generate artifacts)
4. Update version files
5. Sync lock files
6. Generate changelog
                      ← post-changelog         (lint, rewrite CHANGELOG.md)
7. Stage files
8. Create commit
9. Create tag
10. Push (if `--push`)
                      ← post-bump              (publish, notify — code is pushed)

Hook summary:

HookArgumentUse case
pre-bumpTests pass, clean tree, correct branch
post-version{version}Build with new version, stamp binaries
post-changelogLint/reformat CHANGELOG.md, rewrite URLs
post-bumpcargo publish, deploy, notify (after push)

Rules:

  • Missing hook file → silently skipped (not an error).
  • ! (required) commands: non-zero exit aborts the bump.
  • ? (advisory) commands: non-zero exit prints a warning, bump continues.
  • --dry-run skips all lifecycle hooks.
  • post-changelog is skipped when --skip-changelog is used.
  • post-version receives the computed version string as its first positional argument ($1).
  • Hook names do not collide with standard git hook names, so git std hook list / enable / disable work for bump hooks too.

Example:

# .githooks/pre-bump.hooks
!  just verify

# .githooks/post-version.hooks
!  cargo build --release
?  cp target/release/mybin dist/

# .githooks/post-changelog.hooks
?  dprint fmt CHANGELOG.md

# .githooks/post-bump.hooks
!  cargo publish
?  curl -X POST https://hooks.slack.com/...

2.4 git std changelog

Generate or update the changelog from git commit history using standard-changelog.

Behaviour:

  1. Read section mapping from .git-std.toml [changelog.sections]. Falls back to defaults if absent.
  2. Parse commits in the requested range as conventional commits.
  3. Group commits by type → changelog section. Hidden types excluded.
  4. Render as CommonMark with reference-style links.
  5. Print to stdout (default) or write to file when -w is given.

Flags:

FlagDescription
--fullRegenerate the entire changelog from the first commit
--range <range>Generate changelog for a tag range (e.g. v1.0..v2.0)
-w, --write [path]Write to file (default: CHANGELOG.md)

Output goes to stdout by default. Pass -w / --write to write to CHANGELOG.md, or -w <path> to write to a custom file. Without --full or --range, generates an incremental changelog (unreleased commits since the last tag) and prepends to the output file. --range and --full are mutually exclusive.

2.5 git std hook

Manage and execute git hooks defined in .githooks/*.hooks files.

2.5.1 git std hook run <hook>

Execute all commands in .githooks/<hook>.hooks.

Execution model:

  1. Read .githooks/<hook>.hooks, skip blank lines and # comments.
  2. Parse prefix, command, and optional trailing glob per line.
  3. If glob present: check staged files (pre-commit) or tracked files (other hooks) against glob. Skip silently if no match.
  4. Execute command via sh -c.
  5. Apply prefix rule to the exit code.

Arguments after -- are passed to each command. The {msg} token is substituted with the commit message file path.

Prefix rules:

PrefixNameOn non-zero exit
(none)DefaultUses the hook’s default mode
!Fail fastAbort immediately
?AdvisoryReport as warning, never fail

Default mode per hook:

HookDefaultRationale
pre-commitCollectShow all issues at once
pre-pushFail fastDon’t push broken code
commit-msgFail fastReject bad messages immediately
(other)CollectSafe default

Exit code: 0 if all non-advisory commands passed, 1 otherwise.

Output (collect mode):

$ git std hook run pre-commit

  ✓ dprint check
  ✗ shellcheck scripts/*.sh             (exit 1)
  ✓ cargo clippy --workspace            (*.rs)
  ⚠ detekt --input modules/             (advisory, 4 findings)

  1 failed, 1 advisory warning

2.5.3 git std hook list

Display all configured hooks and their commands:

$ git std hook list

  pre-commit (collect mode):
      dprint check
      shellcheck scripts/*.sh
      cargo clippy --workspace -- -D warnings    *.rs
    ? detekt --input modules/                    *.kt

  pre-push (fail-fast mode):
    ! cargo build --workspace
    ! cargo test --workspace

  commit-msg (fail-fast mode):
    ! git std lint --file {msg}

2.6 Hooks File Format

.githooks/<hook-name>.hooks — one command per line, optional prefix and glob.

# Comment
[prefix]command [arguments] [glob]

Prefixes: (none) = hook default, ! = fail fast, ? = advisory.

Globs (optional, end of line): restrict command to matching staged/tracked files. Git pathspec syntax. No match → skip silently.

Substitutions: {msg} → commit message file path.

Section markers: comments can be used to organise commands by concern. git-std ignores comment lines — they’re purely for human readability.

Example .githooks/pre-commit.hooks:

# ── Formatting ────────────────────────────
dprint check
prettier --check "**/*.md"

# ── Rust ──────────────────────────────────
cargo clippy --workspace -- -D warnings *.rs
cargo test --workspace --lib *.rs

# ── Android ───────────────────────────────
? detekt --input modules/ *.kt

Example .githooks/commit-msg.hooks:

! git std lint --file {msg}

2.7 git std bootstrap

Post-clone environment setup. Detects convention files in the repository and configures the local environment.

2.7.1 Built-in checks

Convention fileActionCondition
.githooks/git config core.hooksPath .githooksdirectory exists
.gitattributesgit lfs install + git lfs pullcontains filter=lfs
.git-blame-ignore-revsgit config blame.ignoreRevsFile .git-blame-ignore-revsfile exists

If LFS rules are detected but git-lfs is not installed, prints an error with install URL and exits 1.

2.7.2 Custom commands (.githooks/bootstrap.hooks)

After built-in checks, runs .githooks/bootstrap.hooks through the existing hooks runner if the file exists. Uses the standard .hooks file format:

  • ! prefix = required (fail bootstrap on failure)
  • ? prefix = advisory (best-effort)
  • No prefix = default mode

Flags:

FlagDescription
--dry-runPrint what would be done without acting

Exit codes: 0 success (including nothing to do), 1 failure.

2.8 git std init

Single maintainer setup command. Consolidates hook setup and bootstrap scaffolding into one idempotent command that project maintainers run once and commit.

Steps performed:

  1. Creates .githooks/ directory.
  2. Sets core.hooksPath to .githooks.
  3. Writes .hooks templates (pre-commit, commit-msg, pre-push, and others).
  4. Prompts which hooks to enable; writes active shims and .off shims for the rest.
  5. Generates ./bootstrap script that installs git-std and delegates to git std bootstrap.
  6. Generates .githooks/bootstrap.hooks template.
  7. Creates .git-std.toml with a taplo #:schema directive and commented-out common fields (skipped silently if the file already exists).
  8. Appends a post-clone reminder to AGENTS.md and README.md (idempotent, HTML comment marker).
  9. Stages all created/modified files.

Two personas:

  • Maintainer: git std init — scaffold everything, commit the result.
  • Contributor: ./bootstrap — post-clone setup (calls git std bootstrap).

Flags:

FlagDescription
--forceOverwrite existing files

Exit codes: 0 success, 1 failure.

All steps are idempotent — running twice produces the same result.

2.8 git std version

Lightweight, scriptable version queries without the overhead of bump --dry-run.

git std version                  # 0.10.2
git std version --describe       # 0.10.2-dev.7+g3a2b1c.dirty
git std version --next           # 0.11.0
git std version --label          # minor
git std version --code           # 10299
git std version --format json    # all fields as JSON

Output is always to stdout. No v prefix — the command outputs a version value, not a git tag name.

Flags

FlagDescription
--describeCargo-style describe: -dev.N pre-release + +hash[.dirty] metadata
--nextNext version computed from conventional commits since the last tag
--labelBump label (major/minor/patch/none) accounting for pre-1.0
--codeInteger version code (see §2.8.1)
--format <fmt>text (default) or json

Multiple flags may be combined. With --format json all fields are always included regardless of which other flags are set.

--describe format

<version>[-dev.<N>][+g<hash>[.dirty]]

  • -dev.N — appended when HEAD is N commits ahead of the version tag.
  • +g<hash> — the first 7 characters of the HEAD SHA, prefixed with g. Present when HEAD is ahead of the tag.
  • .dirty — appended when the working tree has uncommitted changes.

Example: 0.10.2-dev.7+g3a2b1c.dirty

--label and pre-1.0 rule

For versions < 1.0.0, bump levels are downshifted following the Cargo pre-1.0 convention (same as git std bump):

  • Breaking change → minor
  • New feature → patch
  • Bug fix → patch

When no bump-worthy commits exist, the label is none.

--format json schema

{
  "version": "0.10.2",
  "describe": "0.10.2-dev.3+g1a2b3c4",
  "next": "0.11.0",
  "label": "minor",
  "code": 100299
}

Fields describe, next, label, and code are always present in JSON output (computed unconditionally). Fields may be null only if computation fails (e.g., no tags).

§2.8.1 Version code (--code)

A monotonically increasing integer encoding of the version, suitable for Android versionCode, iOS CFBundleVersion, or any platform that requires an integer build number.

Semver formula:

code = ((MAJOR × 1 000 + MINOR) × 100 + PATCH) × 100 + stage

Calver formula:

code = days_since_epoch × 10 000 + MICRO × 100 + stage

Where days_since_epoch is the number of days since 1970-01-01 for the first day of the calver period, and MICRO is the PATCH component.

Stage table (shared):

Pre-releaseStage
(unknown)1
dev9
dev.0dev.2810–38
alpha39
alpha.0alpha.1840–58
beta59
beta.0beta.1860–78
rc79
rc.0rc.1880–98
(stable)99

The stage range ensures every pre-release build is numerically less than the final stable release while remaining monotonically increasing within each pre-release track.

2.9 Update Check

git-std periodically checks whether a newer release is available and prints a one-line hint after command output.

Behaviour (gh-style background check):

  1. On every invocation, read the cache file (~/.config/git-std/update-check.json, respects $XDG_CONFIG_HOME).
  2. If the cache is missing or stale (>24 h), spawn a detached background process that queries the GitHub releases API and writes the cache. No hint is printed on this run.
  3. On the next invocation (cache fresh, newer version exists), print a hint after command output.
  4. The update command adapts to the detected install method (cargo install, install.sh, nix profile upgrade, or a link to the releases page).

Properties:

  • Never blocks CLI startup — background subprocess is fire-and-forget.
  • 24 h throttle — at most one network request per day.
  • Stale or missing cache = silence.
  • Opt-out: set GIT_STD_NO_UPDATE_CHECK=1.

2.10 Global Flags

FlagDescription
--help / -hPrint help
--version / -VPrint git-std version
--color <when>auto (default), always, never
--completions <shell>Generate shell completions to stdout

--completions <shell>

Generate shell completion scripts for bash, zsh, or fish. Works without a subcommand — usable regardless of install method.

git std --completions bash   # Bash completions
git std --completions zsh    # Zsh completions
git std --completions fish   # Fish completions

The generated scripts include wrappers that enable completion for both git-std (standalone) and git std (git subcommand) invocations.

Example — add to shell profile:

# Bash (~/.bashrc)
eval "$(git-std --completions bash)"

# Zsh (~/.zshrc)
eval "$(git-std --completions zsh)"

# Fish (~/.config/fish/config.fish)
git-std --completions fish | source

Part 3 — Configuration Reference

3.1 .git-std.toml

TOML format. Optional — all fields have sensible defaults.

# ── Project ───────────────────────────────────────────────────────
scheme = "semver"                              # semver | calver | patch
types = ["feat", "fix", "docs", "style",
         "refactor", "perf", "test",
         "chore", "ci", "build"]
scopes = ["auth", "api", "ci", "deps"]         # "auto" | string[] | omit
# "auto" discovers directory names from crates/*/, packages/*/, modules/*/
strict = true                                  # enforce types/scopes without --strict flag
monorepo = false                               # per-package versioning

# ── Versioning ────────────────────────────────────────────────────
[versioning]
tag_prefix = "v"                               # git tag prefix
prerelease_tag = "rc"                          # default pre-release id
calver_format = "YYYY.MM.PATCH"                # only used when scheme = "calver"
tag_template = "{name}@{version}"              # per-package tag format (monorepo)

# ── Changelog ─────────────────────────────────────────────────────
[changelog]
hidden = ["chore", "ci", "build", "style", "test"]

[changelog.sections]
feat = "Features"
fix = "Bug Fixes"
perf = "Performance"
refactor = "Refactoring"
docs = "Documentation"

# ── Custom version files ─────────────────────────────────
[[version_files]]
path = "pom.xml"
regex = '<version>(.*)</version>'

# ── Packages (monorepo) ──────────────────────────────────
# [[packages]]
# name = "core"
# path = "crates/core"
# scheme = "patch"                             # optional override

Defaults when .git-std.toml is absent:

FieldDefault
schemesemver
typesfeat, fix, docs, style, refactor, perf, test, chore, ci, build, revert
scopesNone (no scope validation)
strictfalse
monorepofalse
versioning.tag_prefixv
versioning.prerelease_tagrc
versioning.calver_formatYYYY.MM.PATCH
versioning.tag_template{name}@{version}
changelog.hiddenchore, ci, build, style, test
changelog.sectionsSensible defaults for each type
version_files[] (empty — built-in files are always auto-detected)
packages[] (auto-discovered from workspace manifests when monorepo = true)

Inferred (not configurable):

ConcernHow it’s resolved
Bump rulesInferred from scheme (semver/calver/patch)
Version filesAuto-detected (see §2.3.1); extensible via [[version_files]]
URLsInferred from git remote get-url origin
Changelog outputCHANGELOG.md at root; {path}/CHANGELOG.md per package
Release commitchore(release): <version> (includes all packages)
Package dependenciesResolved from workspace manifests (runtime only)

Part 4 — Usage Manual

4.1 Installation

curl -fsSL https://raw.githubusercontent.com/driftsys/git-std/main/install.sh | bash
git std --version

After installing, run git std hook install in any repo that has .githooks/*.hooks files.

4.2 Making Commits

Interactive:

git add .
git std commit

Quick (non-interactive):

git std commit -a --type fix --scope auth -m "fix(auth): handle expired tokens"

Preview without committing:

git std commit --dry-run

4.3 Validating Commits

# Single message
git std lint "feat: add feature"

# All commits on your branch
git std lint --range main..HEAD

# Strict mode (enforce known types and scopes)
git std lint --strict --range main..HEAD

4.4 Releasing

Preview:

git std bump --dry-run

Execute:

git std bump
git push --follow-tags

Pre-release:

git std bump --prerelease        # → 2.0.0-rc.1
git std bump --prerelease        # → 2.0.0-rc.2
git std bump                     # → 2.0.0

Force a version:

git std bump --release-as 3.0.0

4.5 Changelog

# Preview unreleased changes
git std changelog --stdout

# Regenerate full history
git std changelog --full

4.6 Hooks

View hooks:

git std hook list

Run manually (debugging):

git std hook run pre-commit

Regenerate shims after editing .hooks files:

git std hook install

Add a custom command: edit .githooks/pre-commit.hooks and add a line.

4.7 CI Integration

# GitLab CI
lint:commits:
  stage: build
  script:
    - git std lint --range $CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD
# GitHub Actions
- name: Validate commits
  run: git std lint --range ${{ github.event.pull_request.base.sha }}..${{ github.sha }}

JSON output for scripting:

git std lint --range main..HEAD --format json

Appendix A — Conventional Commit Grammar

<message>  ::= <header> [<blank-line> <body>] [<blank-line> <footer>+]
<header>   ::= <type> ["(" <scope> ")"] ["!"] ":" " " <subject>
<type>     ::= [a-z]+
<scope>    ::= [a-zA-Z0-9_/.-]+
<subject>  ::= .+  (max 100 chars for header line)
<footer>   ::= <token> ":" " " <value>
             | "BREAKING CHANGE" ":" " " <value>

Appendix B — Existing Tool Comparison

Existing toolWhat git-std replaces
commitizen (cz)Interactive commit builder
commitlintCommit message validation
standard-version / commit-and-tag-versionVersion bump + changelog (.git-std.toml is the config file)
release-pleaseVersion bump + changelog (git-std is local-only, no PR creation)
git-cliffChangelog generation (uses git_cliff_core as a library)
pre-commit (framework)Hook management (native hooks, no framework)
huskyHook management (no Node.js dependency)
lefthookHook management (simpler syntax, glob concept kept)

Appendix C — Open Design Questions

  1. cliff.toml coexistence. .git-std.toml is primary config. cliff.toml is optional override for advanced git-cliff template customization. Right layering?

  2. Scope auto-discovery. Implemented. Three-way: not set (default, no validation), scopes = "auto" (discover from crates/*/, packages/*/, modules/*/ directory names), or scopes = ["auth", "api"] (explicit allowlist).

  3. Monorepo version sync. Post-bump, validate that workspace-inherited versions resolve correctly? Leaning: yes, warn on mismatch.

  4. Hook parallelism. Sequential by default. --parallel flag deferred to a future version.

API Reference

git-std is built on five crates. The four library crates implement domain logic only — no CLI, no git operations, no terminal output.

CrateDescriptiondocs.rs
git-stdCLI binary — orchestrates I/O, git, configdocs.rs/git-std
standard-commitConventional commit parsing, lintingdocs.rs/standard-commit
standard-versionVersion bump (semver + calver), file detectiondocs.rs/standard-version
standard-changelogChangelog generation from conventional commitsdocs.rs/standard-changelog
standard-githooksHook file format parsing, shim generationdocs.rs/standard-githooks