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.tomlwith sensible defaults, inspectable viaconfig 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
| Flag | Description |
|---|---|
--help / -h | Print help |
--version / -V | Print version |
--color <when> | auto (default), always, never |
--completions <shell> | Generate shell completions to stdout |
--update | Update 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:
| Flag | Description |
|---|---|
--file <path> | Read message from file |
--range <range> | Validate all commits in a git revision range |
--strict | Enforce 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:
| Flag | Description |
|---|---|
--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 / -s | Add Signed-off-by trailer |
--dry-run | Print message without committing |
--amend | Pass --amend to git commit |
--sign / -S | GPG-sign the commit |
--all / -a | Stage 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:
| Flag | Description |
|---|---|
--dry-run | Print plan without writing |
--prerelease [tag] | Bump as pre-release (e.g. 2.0.0-rc.1) |
--release-as <ver> | Force a specific version |
--first-release | Initial changelog, no bump |
--no-tag | Skip tag creation |
--no-commit | Update files only |
--sign / -S | GPG-sign commit and tag |
--skip-changelog | Bump without changelog |
--force | Allow breaking changes in patch-only scheme |
--stable [branch] | Create a stable branch for patch-only releases |
--minor | Use 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 / -y | Skip 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:
| Flag | Description |
|---|---|
--full | Regenerate 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:
- Creates
.githooks/directory. - Sets
core.hooksPathto.githooks. - Writes
.hookstemplates (pre-commit,commit-msg,pre-push, etc.). - Prompts which hooks to enable, writes shims.
- Generates
./bootstrapscript. - Generates
.githooks/bootstrap.hooks. - Creates
.git-std.tomlwith taplo schema directive (if absent). - Scaffolds agent skills in
.agents/skills/with.claude/skills/symlinks. - Appends post-clone section to
README.mdandAGENTS.md(if found). - Stages all created files.
Flags:
| Flag | Description |
|---|---|
--force | Overwrite existing files |
--refresh | Update 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:
| Subcommand | Description |
|---|---|
install | Write shim scripts and .hooks templates |
run <hook> | Execute a hook manually |
list | Display 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):
| Flag | Description |
|---|---|
--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 file | Action |
|---|---|
.githooks/ | git config core.hooksPath .githooks |
.gitattributes | git lfs install + git lfs pull (if filter=lfs) |
.git-blame-ignore-revs | git config blame.ignoreRevsFile .git-blame-ignore-revs |
After built-in checks, runs .githooks/bootstrap.hooks if present.
Flags:
| Flag | Description |
|---|---|
--dry-run | Print 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:
| Section | Contents |
|---|---|
Status | Tool versions: git, git-lfs (if .gitattributes needs it), git-std with update notice if available |
Hooks | All .githooks/*.hooks files with commands and sigils (! required, ? advisory), enabled/disabled state |
Configuration | All .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:
| Flag | Description |
|---|---|
--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:
| Flag | Description |
|---|---|
--describe | Cargo-style describe: -dev.N pre-release + +hash[.dirty] metadata |
--next | Next version from conventional commits since the last tag |
--label | Bump label (major/minor/patch/none), accounting for pre-1.0 |
--code | Integer 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 CHANGEor!suffix → majorfeat→ minor- 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:
| Format | Example | Use case |
|---|---|---|
YYYY.MM.PATCH | 2026.3.0 | Monthly releases |
YYYY.0M.PATCH | 2026.03.0 | Zero-padded |
YY.WW.DP | 26.12.30 | Weekly + day |
YYYY.MM.DD.PATCH | 2026.3.18.0 | Daily 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
| Hook | When it runs | Typical use |
|---|---|---|
pre-commit | Before a commit is created | Lint, format, run fast tests |
commit-msg | After the message is written | Validate conventional commit |
prepare-commit-msg | Before the editor opens | Pre-fill commit template |
post-commit | After the commit is created | Notifications, stats |
pre-push | Before push sends data to remote | Full test suite, build validation |
post-merge | After a merge completes | Reinstall deps, rebuild |
Command prefixes
Each .hooks file contains one command per line with a
prefix that controls behaviour:
| Prefix | Name | Behaviour |
|---|---|---|
! | check | Run command, block on failure |
~ | fix | Isolate staged files, run, re-stage |
? | advisory | Run 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
| Field | Type | Default | Description |
|---|---|---|---|
scheme | string | "semver" | Versioning scheme (see below) |
types | string[] | 11 standard types | Allowed conventional commit types |
scopes | "auto" or string[] | None | Scope discovery or explicit allowlist |
strict | bool | false | Enforce types/scopes validation without --strict flag |
monorepo | bool | false | Enable per-package versioning |
release_branch | string | (none) | Branch that git std bump is expected to run on |
refs_required | string[] | [] | Commit types that require a footer reference |
Default types: feat, fix, docs, style, refactor,
perf, test, chore, ci, build, revert.
Versioning schemes:
semver—BREAKING CHANGEor!→ major,feat→ minor, everything else → patch. Resets lower components (e.g.1.2.3→1.3.0). Supports--prerelease.calver— date-based, ignores commit types. Usescalver_format(defaultYYYY.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--forceis 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]
| Field | Type | Default | Description |
|---|---|---|---|
tag_prefix | string | "v" | Git tag prefix (e.g., v1.0.0) |
prerelease_tag | string | "rc" | Default pre-release identifier |
calver_format | string | "YYYY.MM.PATCH" | Calendar version format (only when scheme = "calver") |
tag_template | string | "{name}@{version}" | Per-package tag format (only when monorepo = true) |
Calendar version format tokens:
| Token | Description | Example |
|---|---|---|
YYYY | Full year | 2026 |
YY | Short year | 26 |
0M | Zero-padded month | 03 |
MM | Month (no padding) | 3 |
WW | ISO week number | 11 |
DD | Day of month | 13 |
PATCH | Auto-incrementing patch counter, resets each period | 0, 1, 2 |
DP | Day of week (1=Mon–7=Sun) concatenated with patch counter | 30, 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]
| Field | Type | Default | Description |
|---|---|---|---|
title | string | (none) | Custom changelog title |
hidden | string[] | ["chore", "ci", "build", "style", "test"] | Types excluded from changelog |
bug_url | string | (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.
| Key | Default |
|---|---|
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*(.+)'
| Field | Type | Description |
|---|---|---|
path | string | File path relative to repo root |
regex | string | Regex 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"]
| Field | Type | Description |
|---|---|---|
name | string | Package name (used in tags and changelogs) |
path | string | Package root relative to repo root |
scheme | string | Optional versioning scheme override |
version_files | array | Optional version files override |
changelog | table | Optional changelog config override |
Entries with missing name or path are silently skipped.
Inferred settings
These are not configurable — git-std resolves them automatically:
| Concern | Resolution |
|---|---|
| Bump rules | Inferred from scheme |
| Version files | Auto-detected (Cargo.toml) |
| URLs | Inferred from git remote get-url origin |
| Changelog output | CHANGELOG.md at root; {path}/CHANGELOG.md per package |
| Release commit | chore(release): <version> (includes packages) |
| Package dependencies | Resolved 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:
- Checks
git stdis installed — offers to install via./bootstrapor the install script if not. - Runs
git std --contextto read project config, valid types, scopes, and the staged diff. - Proposes a
git std commit --type X [--scope Y] --message Zcommand. - For
featandfixcommits, asks for a related issue number and pre-fills it from the branch name when available (e.g.feat/123-my-feature→#123). - Requires your approval before running.
/std-bump
Orchestrate a version bump.
Invoke with /std-bump in your agent.
The skill:
- Checks
git stdis installed — offers to install if not. - Runs
git std --contextto assess stability, branch, and tag state. - Runs
git std bump --dry-runand shows the full plan. - Asks for confirmation, package selection (monorepo), and whether to push.
- 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:
| Concern | Current tools | Runtime |
|---|---|---|
| Commit message conventions | commitizen, commitlint | Node.js |
| Version bumping | standard-version, release-please | Node.js |
| Changelog generation | git-cliff, conventional-changelog | Rust / Node.js |
| Git hooks management | pre-commit, husky, lefthook | Python / 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
-
One binary, four concerns. Commits, versions, changelogs, and hooks ship as a single tool. No Node.js runtime, no Python, no framework dependencies.
-
Native hooks only. No pre-commit.com framework. Git hooks are plain shell commands in
.githooks/*.hooksfiles, managed natively bygit std hook. -
Config is TOML.
.git-std.tomlis the single config file for versioning, changelog, and commit conventions. Hooks config lives in the.githooks/*.hooksfiles themselves — plain text, not TOML or YAML. -
Install via curl. Precompiled static binaries per OS/arch. Not distributed through
cargo install— avoids requiring the Rust toolchain on developer machines. -
Single responsibility. git-std owns everything between
git commitandgit tag— the developer’s git workflow. It does not touch repo structure, compliance, formatting, or linting. -
Zero-config sensible defaults. Every subcommand works without
.git-std.toml. Missing config means conventional commit standard types, semver,vtag prefix, and default changelog sections.
1.3 Scope
git-std covers six concerns:
| Concern | Subcommand |
|---|---|
| Conventional commit creation | git std commit |
| Commit message validation | git std lint |
| Version bump + changelog | git std bump, git std changelog |
| Maintainer setup | git std init |
| Git hooks management | git std hook run, git std hook list |
| Post-clone bootstrap | git std bootstrap |
| Shell completions | git std --completions <shell> |
Out of scope: repo scaffolding, directory structure compliance, formatting, linting, CI pipeline generation, dependency management.
1.4 Configuration Files
| File | Purpose |
|---|---|
.git-std.toml | Versioning scheme, commit types, scopes, changelog sections |
.githooks/*.hooks | Hook command definitions (one file per git hook) |
.githooks/<hook> | Shim scripts generated by git std hook install |
Cargo.toml | Version 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:
| Crate | Role |
|---|---|
git-std | CLI binary — orchestrates I/O, git, config, dispatch |
standard-commit | Conventional commit parsing, linting, formatting |
standard-version | Semantic version bump calculation, version file detection and update (with file I/O) |
standard-changelog | Changelog generation from conventional commits |
standard-githooks | Hook 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:
| Crate | Purpose |
|---|---|
clap | CLI argument parsing with subcommand dispatch |
clap_complete | Shell completion script generation (bash, zsh, fish) |
inquire | Interactive terminal prompts (type/scope/description selection) |
yansi | Terminal colours and --color flag support |
toml | .git-std.toml reading and Cargo.toml version updates |
semver | Semantic 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:
| OS | Architecture | Target triple |
|---|---|---|
| Linux | x86_64 | x86_64-unknown-linux-musl |
| Linux | aarch64 | aarch64-unknown-linux-musl |
| macOS | x86_64 | x86_64-apple-darwin |
| macOS | aarch64 (Apple Silicon) | aarch64-apple-darwin |
| Windows | x86_64 | x86_64-pc-windows-msvc |
Install script at
https://raw.githubusercontent.com/driftsys/git-std/main/install.sh:
- Detects OS and architecture
- Downloads the matching binary from the latest GitHub Release
- Installs to
~/.local/bin/git-std(Linux/macOS) or%LOCALAPPDATA%\bin\git-std.exe(Windows) - 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:
- If
.git-std.tomlexists, readtypesfor the type list andscopesfor scope suggestions. Otherwise, use the conventional commit standard types. - Prompt for type (required), scope (optional), subject (required), body (optional), breaking change footer (optional), trailer footers (optional).
- Assemble the message:
<type>(<scope>): <subject>\n\n<body>\n\n<trailers>. - Validate the assembled message against the
conventional commit spec (same rules as
git std lint). - Run
git commit -m "<message>"with any passthrough flags (--sign,--amend,--all).
Flags:
| Flag | Description |
|---|---|
--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 |
--breaking | Add BREAKING CHANGE footer (prompts for description) |
--footer <text> | Add a trailer footer (repeatable, e.g. Co-authored-by: Name <email>) |
--signoff / -s | Add Signed-off-by trailer from git user.name / user.email |
--dry-run | Print the assembled message to stdout, do not commit |
--amend | Pass --amend to git commit |
--sign / -S | Pass --gpg-sign to git commit |
--all / -a | Pass --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:
- Message matches
^<type>(\(<scope>\))?!?: .+where type is alphanumeric lowercase. - Subject line (first line) does not exceed 100 characters.
- If
--strictflag orstrict = truein config: type must be in the known types list (from.git-std.tomlor defaults). Scope must be in the known scopes list ifscopesis defined. - Body, if present, is separated from subject by a blank line.
- Footer tokens (
BREAKING CHANGE:,Refs:, etc.) follow the conventional commit footer spec. - In
--rangemode: process commits (merges, reverts, fixups, squashes, initial commits) are automatically skipped and excluded from the valid/total count.
Input modes:
| Mode | Usage |
|---|---|
| Positional argument | git std lint "feat: add feature" |
--file <path> | git std lint --file .git/COMMIT_EDITMSG |
--range <range> | git std lint --range main..HEAD |
| stdin | echo "feat: ok" | git std lint - |
Flags:
| Flag | Description |
|---|---|
--file <path> | Read message from file |
--range <range> | Validate all commits in a git revision range |
--strict | Enforce: 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:
-
Find the latest version tag matching
<tag_prefix><semver>(defaultv*). -
Collect all commits between that tag and
HEAD. -
Parse each commit as a conventional commit. Non-conforming commits are ignored (not an error).
-
Apply bump rules inferred from
scheme. The highest bump level wins (major>minor>patch):BREAKING CHANGEfooter or!suffix → majorfeat→ minorfix/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 change major minor Feature minor patch Fix patch patch 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. -
If no bump-worthy commits exist, print a message and exit
0(not an error). -
Compute the new version string.
-
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. -
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 file Lock file Sync command Cargo.tomlCargo.lockcargo update --workspacepackage.jsonpackage-lock.jsonnpm install --package-lock-onlypackage.jsonyarn.lockyarn install --mode update-lockfilepackage.jsonpnpm-lock.yamlpnpm install --lockfile-onlydeno.jsondeno.lockdeno installpyproject.tomluv.lockuv lockpyproject.tomlpoetry.lockpoetry lock --no-update -
Generate changelog section for this release via
standard-changelog. -
Prepend the section to
CHANGELOG.md. -
Create commit:
chore(release): <version>. All updated version files and synced lock files are included in the release commit. -
Create annotated tag:
<tag_prefix><version>.
Flags:
| Flag | Description |
|---|---|
--dry-run | Print 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-release | Use current version for initial changelog. No bump. |
--no-tag | Update files and commit, skip tag creation |
--no-commit | Update files only, no commit or tag |
--sign / -S | GPG-sign the release commit and annotated tag |
--skip-changelog | Bump version files without changelog generation |
--force | Allow 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 |
--minor | Use 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:
| Token | Description | Example |
|---|---|---|
YYYY | Full year | 2026 |
YY | Short year | 26 |
0M | Zero-padded month | 03 |
MM | Month (no padding) | 3 |
WW | ISO week number | 11 |
DD | Day of month | 13 |
PATCH | Auto-incrementing patch counter, resets each period | 0, 1, 2 |
DP | Day of week (1=Mon–7=Sun) concatenated with patch counter | 30, 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.
| File | Ecosystem | Engine | Detection |
|---|---|---|---|
Cargo.toml | Rust | TOML (toml) | already implemented |
package.json | Node / TypeScript | JSON | package.json at root |
deno.json / deno.jsonc | Deno | JSON/JSONC | deno.json or deno.jsonc at root |
pyproject.toml | Python | TOML (toml) | pyproject.toml at root with [project] table |
pubspec.yaml | Flutter / Dart | text (line-level) | pubspec.yaml at root |
gradle.properties | Android / Gradle | text (key=value) | gradle.properties at root with VERSION_NAME key |
VERSION | any | text (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 / JSONC — serde_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.properties — VERSION_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:
pathis relative to repo root.regexuses RE2 syntax (Rustregexcrate — 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:
- Explicit
[[packages]]entries in config (highest priority). - Auto-discovery from workspace manifests:
Cargo.toml[workspace] memberspackage.jsonworkspacesdeno.jsonworkspace
- Fallback: subdirectories containing version files.
Per-package bump algorithm:
- Collect all tags once.
- For each package:
a. Find the latest tag matching
tag_templatefor 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. - If
scheme = "calver"for a package (via per-packageschemeoverride 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:
- Write version files for each bumped package (respecting
per-package
[[packages.version_files]]overrides). - Write per-package
CHANGELOG.mdin each package root (respecting per-package[packages.changelog]overrides). - Write root
CHANGELOG.mdfrom all commits. - Sync lock files once.
- Single commit:
chore(release): v{root}, {pkg}@{ver}, ... - Multiple tags: root
v{version}+ per-package tags fromtag_template(default{name}@{version}).
Flags:
| Flag | Description |
|---|---|
--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:
| Hook | Argument | Use case |
|---|---|---|
pre-bump | — | Tests pass, clean tree, correct branch |
post-version | {version} | Build with new version, stamp binaries |
post-changelog | — | Lint/reformat CHANGELOG.md, rewrite URLs |
post-bump | — | cargo 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-runskips all lifecycle hooks.post-changelogis skipped when--skip-changelogis used.post-versionreceives 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/disablework 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:
- Read section mapping from
.git-std.toml[changelog.sections]. Falls back to defaults if absent. - Parse commits in the requested range as conventional commits.
- Group commits by type → changelog section. Hidden types excluded.
- Render as CommonMark with reference-style links.
- Print to stdout (default) or write to file when
-wis given.
Flags:
| Flag | Description |
|---|---|
--full | Regenerate 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:
- Read
.githooks/<hook>.hooks, skip blank lines and#comments. - Parse prefix, command, and optional trailing glob per line.
- If glob present: check staged files (pre-commit) or tracked files (other hooks) against glob. Skip silently if no match.
- Execute command via
sh -c. - 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:
| Prefix | Name | On non-zero exit |
|---|---|---|
| (none) | Default | Uses the hook’s default mode |
! | Fail fast | Abort immediately |
? | Advisory | Report as warning, never fail |
Default mode per hook:
| Hook | Default | Rationale |
|---|---|---|
pre-commit | Collect | Show all issues at once |
pre-push | Fail fast | Don’t push broken code |
commit-msg | Fail fast | Reject bad messages immediately |
| (other) | Collect | Safe 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 file | Action | Condition |
|---|---|---|
.githooks/ | git config core.hooksPath .githooks | directory exists |
.gitattributes | git lfs install + git lfs pull | contains filter=lfs |
.git-blame-ignore-revs | git config blame.ignoreRevsFile .git-blame-ignore-revs | file 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:
| Flag | Description |
|---|---|
--dry-run | Print 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:
- Creates
.githooks/directory. - Sets
core.hooksPathto.githooks. - Writes
.hookstemplates (pre-commit, commit-msg, pre-push, and others). - Prompts which hooks to enable; writes active shims
and
.offshims for the rest. - Generates
./bootstrapscript that installs git-std and delegates togit std bootstrap. - Generates
.githooks/bootstrap.hookstemplate. - Creates
.git-std.tomlwith a taplo#:schemadirective and commented-out common fields (skipped silently if the file already exists). - Appends a post-clone reminder to
AGENTS.mdandREADME.md(idempotent, HTML comment marker). - Stages all created/modified files.
Two personas:
- Maintainer:
git std init— scaffold everything, commit the result. - Contributor:
./bootstrap— post-clone setup (callsgit std bootstrap).
Flags:
| Flag | Description |
|---|---|
--force | Overwrite 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
| Flag | Description |
|---|---|
--describe | Cargo-style describe: -dev.N pre-release + +hash[.dirty] metadata |
--next | Next version computed from conventional commits since the last tag |
--label | Bump label (major/minor/patch/none) accounting for pre-1.0 |
--code | Integer 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 isNcommits ahead of the version tag.+g<hash>— the first 7 characters of the HEAD SHA, prefixed withg. 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-release | Stage |
|---|---|
| (unknown) | 1 |
dev | 9 |
dev.0–dev.28 | 10–38 |
alpha | 39 |
alpha.0–alpha.18 | 40–58 |
beta | 59 |
beta.0–beta.18 | 60–78 |
rc | 79 |
rc.0–rc.18 | 80–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):
- On every invocation, read the cache file
(
~/.config/git-std/update-check.json, respects$XDG_CONFIG_HOME). - 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.
- On the next invocation (cache fresh, newer version exists), print a hint after command output.
- 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
| Flag | Description |
|---|---|
--help / -h | Print help |
--version / -V | Print 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:
| Field | Default |
|---|---|
scheme | semver |
types | feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert |
scopes | None (no scope validation) |
strict | false |
monorepo | false |
versioning.tag_prefix | v |
versioning.prerelease_tag | rc |
versioning.calver_format | YYYY.MM.PATCH |
versioning.tag_template | {name}@{version} |
changelog.hidden | chore, ci, build, style, test |
changelog.sections | Sensible 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):
| Concern | How it’s resolved |
|---|---|
| Bump rules | Inferred from scheme (semver/calver/patch) |
| Version files | Auto-detected (see §2.3.1); extensible via [[version_files]] |
| URLs | Inferred from git remote get-url origin |
| Changelog output | CHANGELOG.md at root; {path}/CHANGELOG.md per package |
| Release commit | chore(release): <version> (includes all packages) |
| Package dependencies | Resolved 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 tool | What git-std replaces |
|---|---|
| commitizen (cz) | Interactive commit builder |
| commitlint | Commit message validation |
| standard-version / commit-and-tag-version | Version bump + changelog (.git-std.toml is the config file) |
| release-please | Version bump + changelog (git-std is local-only, no PR creation) |
| git-cliff | Changelog generation (uses git_cliff_core as a library) |
| pre-commit (framework) | Hook management (native hooks, no framework) |
| husky | Hook management (no Node.js dependency) |
| lefthook | Hook management (simpler syntax, glob concept kept) |
Appendix C — Open Design Questions
-
cliff.tomlcoexistence..git-std.tomlis primary config.cliff.tomlis optional override for advanced git-cliff template customization. Right layering? -
Scope auto-discovery.Implemented. Three-way: not set (default, no validation),scopes = "auto"(discover fromcrates/*/,packages/*/,modules/*/directory names), orscopes = ["auth", "api"](explicit allowlist). -
Monorepo version sync. Post-bump, validate that workspace-inherited versions resolve correctly? Leaning: yes, warn on mismatch.
-
Hook parallelism. Sequential by default.
--parallelflag 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.
| Crate | Description | docs.rs |
|---|---|---|
| git-std | CLI binary — orchestrates I/O, git, config | docs.rs/git-std |
| standard-commit | Conventional commit parsing, linting | docs.rs/standard-commit |
| standard-version | Version bump (semver + calver), file detection | docs.rs/standard-version |
| standard-changelog | Changelog generation from conventional commits | docs.rs/standard-changelog |
| standard-githooks | Hook file format parsing, shim generation | docs.rs/standard-githooks |