Rust CLI Project AGENTS.md
Claude CodeAGENTS.mdCoding
AGENTS.md for a Rust command-line tool: clap derive conventions, anyhow vs thiserror boundaries, exit-code discipline, and integration tests with assert_cmd.
Use when: Use in a Rust CLI crate so agents put error types in the right layer, keep main thin, and verify behavior with end-to-end binary tests rather than only unit tests.
Get this skill
Add the skill file with the CLI, by hand, or over MCP.Save this to your project or home directory so Claude Code can load it.
npx goodprompts add rust-cli-agents-mdSaves to
./AGENTS.mdnpx goodprompts add rust-cli-agents-mdSaves to
./AGENTS.mdSkill file
AGENTS.md3,252 characters# AGENTS.md — Rust CLI
## Commands
```bash
cargo build
cargo test # unit + integration tests
cargo test --test cli # just the end-to-end binary tests
cargo clippy --all-targets -- -D warnings # warnings are errors; fix, don't allow
cargo fmt --check # must be clean before commit
cargo run -- <args> # try the tool locally
```
MSRV is pinned in `Cargo.toml` (`rust-version`). Do not use features newer than it. Add dependencies with `cargo add <crate>`; keep default features off (`--no-default-features`) unless needed and say why in the commit message.
## Structure
```
src/main.rs # thin: parse args, call run(), map error to exit code
src/cli.rs # clap derive structs — the entire CLI surface in one file
src/lib.rs # everything else lives behind the library crate
tests/cli.rs # integration tests running the real binary
```
- `main.rs` stays under ~40 lines. All logic is in the library so it can be unit-tested without spawning processes.
- The library never prints directly. It returns data; rendering to stdout/stderr happens in the binary layer, writing through a `&mut impl Write` so tests can capture output.
## CLI conventions (clap)
- Derive API only (`#[derive(Parser)]`), no builder calls. Subcommands are an enum with `#[derive(Subcommand)]`.
- Every flag has long form (`--output`), doc comment (becomes `--help` text), and where sensible a short form. Help text is a sentence fragment, no trailing period.
- Machine-readable output behind `--format json`; human output is the default. Never mix logs into stdout — diagnostics go to stderr.
- Respect conventions: `-q/--quiet`, `-v/--verbose` (counted), read from stdin when the input arg is `-`.
## Error handling
- **Library code (`src/lib.rs` and below): `thiserror`.** Each module defines a specific error enum; variants carry context (`#[error("config file {path} is not valid TOML")]`). No `anyhow` in the library — callers need to match on failures.
- **Binary code (`main.rs`): `anyhow`.** `run()` returns `anyhow::Result<ExitCode>`; use `.context("reading config")?` to add user-facing breadcrumbs at each step.
- Exit codes: 0 success, 1 expected failure (bad input, not found), 2 usage error (clap handles this). Map them in one place in `main`.
- Error messages: lowercase start, name the thing that failed and the path/value involved, suggest a fix when one is obvious. Never show a raw `Debug` print to users.
- No `.unwrap()`/`.expect()` outside tests and provably-infallible cases — and provably-infallible gets a comment proving it.
## Tests
- Integration tests in `tests/cli.rs` use `assert_cmd` + `predicates`: run the real binary, assert on exit code, stdout, stderr.
- Filesystem-touching tests use `tempfile::TempDir`; never write into the repo or rely on the user's home directory.
- Every bug fix adds a regression test that fails before the fix.
- Snapshot-test help output (`insta`) so CLI surface changes show up in review diffs.
## Style
- `cargo fmt` defaults — no rustfmt.toml debates.
- Prefer borrowing (`&str`, `&Path`) in signatures; take `impl AsRef<Path>` for path parameters.
- No `unsafe`. If you believe it is required, stop and ask first.