# AGENTS.md
Guide for agents/contributors working in this repository. For a user-facing overview see the
[README](README.md) (translations: [ko](README.ko.md) · [ja](README.ja.md) · [zh](README.zh.md)).
## Project overview
A Rust port of GitVersion (.NET) as a single native binary. It computes a SemVer from Git
history. Pure Rust (gix), verified against the real GitVersion 6.x binary via differential tests.
## Development workflow
```bash
cargo build # build
cargo test # unit + fixture integration tests
cargo fmt --all # format
cargo clippy --all-targets -- -D warnings # lint (keep zero warnings)
```
- **lefthook** (install: `lefthook install`)
- pre-commit: `cargo fmt` (auto-format + re-stage), `clippy -D warnings` on staged `*.rs`, `git-warden diff`
- commit-msg: `git-warden msg` (language + policy check)
- prepare-commit-msg: `git-warden prepare-msg`
- pre-push: `cargo test`
- **CI** (`.github/workflows/ci.yml`): fmt --check, clippy -D warnings, build/test on 3 OSes,
MSRV build (currently 1.88, the transitive-dependency floor). Runs on pushes to main and PRs.
## Commit conventions
- **Conventional Commits** type prefix required: `feat|fix|ci|chore|test|docs|refactor|perf|style|build|revert`.
- Commit messages must be written in **English** (enforced by `git-warden` via the commit-msg hook).
- **No AI co-author trailers** (e.g. Co-Authored-By), and no special characters such as arrows or
emoji (the commit-msg hook rejects them).
- For throwaway test repos, `git commit --no-verify` is fine.
## i18n
- Crate: [`rust-i18n`](https://github.com/longbridge/rust-i18n). Default language is **English**,
source keys are English. Supported: en/ko/ja/zh.
- Translations live in `locales/app.yml` (`_version: 2`) as `key: { en, ko, ja, zh }` or block form.
Placeholders use `%{name}`.
- In code: `rust_i18n::t!("key", name = value)`. **`t!` only works inside the lib crate
(`src/lib.rs`) that invokes the `i18n!` macro**, so the entry logic lives in `src/app.rs`
(`main.rs` is a thin shim).
- Pass runtime variable keys with `t!(*k)` (double-ref deref). CLI help is injected before parsing
by `cli::localized_command()` via the `cli.about` / `cli.help.<arg_id>` keys.
- **When you add a user-facing string, always add all four language values to `locales/app.yml`.**
On a missing key, rust-i18n prints the key string verbatim.
## Versioning and releases
### Version management
- **Source of truth: `Cargo.toml`** — managed by `cargo-release`. Do not bump by hand.
- **Version policy: `.gitversion.yml`** — `ManualDeployment` mode (GitHubFlow/v1). Versions are
set explicitly by the developer; gitversion-rs computes metadata (PreReleaseTag,
InformationalVersion) from git history.
- **Bump rules** (used by `cargo-release` to infer the level from the commit log):
- `feat:` → minor
- `fix:` / `perf:` → patch
- `!` suffix or `BREAKING CHANGE:` → major
### Justfile commands
```bash
just version # show current FullSemVer (gitversion-rs)
just check # dry-run: see what cargo-release would do (patch)
just check minor # dry-run for a minor bump
just bump # bump patch: updates Cargo.toml, commits, tags, pushes
just bump minor # bump minor
just bump major # bump major
just publish # publish to crates.io (run after bump)
```
### Release procedure
1. Ensure `main` is green.
2. Bump the version and push:
```bash
just bump minor ```
This runs `cargo release minor --execute` which:
- Updates `version` in `Cargo.toml`
- Commits `"chore: release 0.2.0"`
- Creates annotated tag `v0.2.0`
- Pushes commit + tag to origin
3. The tag push triggers `.github/workflows/release-draft.yml`:
- Builds 6 cross-compiled targets (version read directly from `Cargo.toml` via `CARGO_PKG_VERSION`)
- Generates changelog with git-cliff, signs artifacts with cosign
- Creates a GitHub draft release
4. Trigger `.github/workflows/release-publish.yml` (manual dispatch, input: tag):
```bash
just publish ```
- Publishes to crates.io
- Updates the Homebrew tap formula
- Marks the GitHub release as published
### Homebrew tap
- Required secret: **`HOMEBREW_TAP_TOKEN`** — PAT with `contents:write` on `zcube/homebrew-tap`.
- Pre-releases (version containing `-`) do not update the tap.
- Install: `brew install zcube/tap/gitversion-rs`
## Recommended pattern for consuming Rust projects
This section documents the recommended way to integrate gitversion-rs into a Rust project.
### Tool roles
| **cargo-release** | Version number management — bumps `Cargo.toml`, commits, tags |
| **gitversion-rs `--exec`** | Build-time metadata injection — sets `CARGO_PKG_VERSION_PRE`, `GitVersion_*` env vars |
`Cargo.toml` is the source of truth for the version number. `gitversion-rs` provides the
`PreReleaseTag` and `InformationalVersion` (SHA + branch) at build time without touching the commit
history.
### How `--exec` works
`gitversion-rs --exec "cargo build"` (or the `exec.prepare` hook in `GitVersion.yml`) runs the
given command with the following environment variables pre-set in the child process:
```
CARGO_PKG_VERSION = SemVer (e.g. "0.2.0" or "0.2.0-alpha.1")
CARGO_PKG_VERSION_MAJOR = Major
CARGO_PKG_VERSION_MINOR = Minor
CARGO_PKG_VERSION_PATCH = Patch
CARGO_PKG_VERSION_PRE = PreReleaseTag (e.g. "" on a release tag, "5" on an untagged commit)
GitVersion_SemVer = SemVer
GitVersion_InformationalVersion = "0.2.0+Branch.main.Sha.abc1234"
... (all GitVersion_* variables)
```
`CARGO_PKG_VERSION_PRE` is the key variable: it carries the `PreReleaseTag` computed from git
history, so a release build gets `""` and a dev build gets the commit distance (`"5"`) or branch
tag (`"alpha.1"`).
### build.rs pattern
```rust
fn main() {
// gitversion-rs --exec sets GitVersion_InformationalVersion; fall back to CARGO_PKG_VERSION.
let info = std::env::var("GitVersion_InformationalVersion")
.unwrap_or_else(|_| std::env::var("CARGO_PKG_VERSION").unwrap_or_default());
println!("cargo:rustc-env=APP_INFO_VERSION={info}");
println!("cargo:rerun-if-env-changed=GitVersion_InformationalVersion");
}
```
`CARGO_PKG_VERSION_PRE` is available in source files directly without a `build.rs`:
```rust
const PRE: &str = env!("CARGO_PKG_VERSION_PRE"); // "" on release, "5" on dev, "alpha.1" on pre-release
```
### Release workflow
```bash
# 1. Bump version, commit, and tag — Cargo.toml becomes the record
cargo release minor # 0.1.x → 0.2.0: updates Cargo.toml, commits "chore: release v0.2.0", tags v0.2.0
# 2. Build with gitversion-rs injecting CARGO_PKG_VERSION_PRE and InformationalVersion
gitversion-rs --exec "cargo build --release"
```
In CI:
```yaml
- run: gitversion-rs --exec "cargo build --release"
```
No `--allow-dirty` or separate version-injection step is needed: `cargo-release` has already set
the correct version in `Cargo.toml` before the build starts.
### Fallback hierarchy (no git / no gitversion-rs)
`crates.io` installs have no git repository. In that case:
- `GitVersion_*` env vars are absent → `build.rs` falls back to `CARGO_PKG_VERSION`
- `CARGO_PKG_VERSION_PRE` is set by Cargo from `Cargo.toml` (e.g. `""` for a release)
- The binary version is always correct because `cargo-release` stamped the right version into
`Cargo.toml` before publishing
## Dependency updates (Renovate)
- `.github/renovate.json` drives Renovate. Schedule: before 9am on Monday; concurrent PR limit 5.
- Groups: gix (gitoxide) crates, dev-dependencies (automerge), GitHub Actions (automerge);
`lockFileMaintenance` refreshes `Cargo.lock`.
- Major bumps may break the build; fix them locally, verify green, and push. PRs merge only after
CI passes.
## Code layout
| Module | Role |
|---|---|
| `src/git` | gix-based repository access |
| `src/config` | config model / defaults / loader / effective |
| `src/version` | SemanticVersion and the calculation engine |
| `src/output` | output variables / formatters / file output |
| `src/cli` | clap arguments + `localized_command()` |
| `src/app.rs` | entry logic (kept inside the lib so `t!` works) |
| `src/tui` | ratatui TUI |
| `src/i18n.rs` + `locales/` | rust-i18n locale handling |
> `refs/gitversion` is the .NET source this port was based on; it is excluded from tracking via
> `.gitignore`.