+++ title = "Add --version flag with X.Y.Z+GitSha format" priority = 5 status = "todo" ticket_type = "feature" dependencies = [] +++ ## Goal Add a `--version` / `-V` flag to `nbd` that prints the version in the format: ``` 0.1.0+7e311d6 ``` where `0.1.0` comes from `Cargo.toml` and `7e311d6` is the short git SHA of the commit the binary was built from, embedded at compile time via `build.rs`. ## Why Allows agents and users to confirm exactly which build of `nbd` is running without inspecting the binary separately. ## Implementation plan ### 1. Create `build.rs` at the crate root ```rust fn main() { // Capture the short git SHA at build time. // Falls back to "unknown" when git is unavailable (e.g. CI without repo). let sha = std::process::Command::new("git") .args(["rev-parse", "--short", "HEAD"]) .output() .ok() .and_then(|o| if o.status.success() { Some(o.stdout) } else { None }) .and_then(|b| String::from_utf8(b).ok()) .map(|s| s.trim().to_string()) .unwrap_or_else(|| "unknown".to_string()); println\!("cargo:rustc-env=GIT_SHORT_SHA={sha}"); // Re-run whenever HEAD changes (new commits). println\!("cargo:rerun-if-changed=.git/HEAD"); println\!("cargo:rerun-if-changed=.git/refs"); } ``` Key points: - Uses `std::process::Command` — no extra build dependencies. - Graceful fallback to `"unknown"` when git is absent (clean Nix sandbox builds may not have `.git/`). - `rerun-if-changed` directives ensure the SHA is refreshed on every commit without forcing a full rebuild every run. ### 2. Add a `VERSION` constant in `src/main.rs` Add near the top of `main.rs` after the existing `CLAUDE_MD_SNIPPET` constant: ```rust /// Full version string embedded at compile time: `"X.Y.Z+shortsha"`. const VERSION: &str = concat\!(env\!("CARGO_PKG_VERSION"), "+", env\!("GIT_SHORT_SHA")); ``` ### 3. Wire `VERSION` into the clap `#[command(...)]` attribute Change: ```rust #[command(name = "nbd", about = "Manage work tickets for agent workflows")] ``` to: ```rust #[command(name = "nbd", about = "Manage work tickets for agent workflows", version = VERSION)] ``` clap automatically handles `-V` / `--version` when `version` is set: it prints the string and exits 0. No manual subcommand or dispatch code needed. ### 4. Update `README.md` Add a brief note in the **Usage** section or **Installation** section: ```sh nbd --version # prints e.g. 0.1.0+7e311d6 ``` ### 5. Add an integration test in `tests/integration.rs` ```rust /// `nbd --version` exits 0 and stdout contains the semver. #[test] fn version_flag_exits_zero_with_semver() { let tmp = tempfile::tempdir().expect("tempdir"); let output = std::process::Command::new(env\!("CARGO_BIN_EXE_nbd")) .arg("--version") .current_dir(tmp.path()) .output() .expect("spawn nbd"); assert\!(output.status.success(), "--version should exit 0"); let stdout = String::from_utf8(output.stdout).unwrap(); // Should contain the package version (semver). assert\!(stdout.contains("0.1.0"), "--version should include semver: {stdout}"); // Should contain a '+' separator and at least one hex char after it. assert\!(stdout.contains('+'), "--version should contain '+': {stdout}"); } ``` ## Files to change | File | Change | |---|---| | `build.rs` | **Create** — emit `GIT_SHORT_SHA` env var | | `src/main.rs` | Add `VERSION` const; add `version = VERSION` to `#[command]` | | `README.md` | Add `nbd --version` example | | `tests/integration.rs` | Add `version_flag_exits_zero_with_semver` test | ## Edge cases - **No git available (Nix sandbox):** `build.rs` falls back to `"unknown"`, producing `0.1.0+unknown`. This is acceptable for packaged builds where the version is already pinned by the derivation. - **clap version output format:** clap 4 prints `nbd X.Y.Z+sha` (prefixed by the binary name) to stdout, then exits 0. This is standard behaviour — the test should check `stdout.contains("0.1.0")` rather than an exact match. - **No `--json` support for `--version`:** clap intercepts `--version` before dispatch reaches the `--json` handling. This is acceptable; version output is always plain text. ## Validation ```sh cargo fmt && cargo check && cargo clippy && cargo test cargo run -- --version # Expected output: nbd 0.1.0+ ```