From 6e460aca8022d005cc1f897497b9a4283459af9a Mon Sep 17 00:00:00 2001 From: Wong Ding Feng Date: Tue, 11 Aug 2026 00:22:30 +0800 Subject: [PATCH] Add CLAUDE.md documenting cargo/nix workflows and architecture Covers both dev paths (plain cargo vs nix develop/build/flake check), LSP setup via the fenix toolchain's RUST_SRC_PATH, how nix/rust.nix is shared between the package build and dev shell, where to add unit vs integration tests, and how to use the rename script. Includes a note to keep it updated as the template grows into real functionality. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018ALEXkc7pTro7tF1WcUuKb --- CLAUDE.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0d93ee0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,97 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +A minimal Rust project template: a `clap`-based CLI (`src/main.rs`) backed by a library crate +(`src/lib.rs`), with unit tests, an integration test, and a `criterion` benchmark already wired +up. It also carries a full Nix flake (crane + fenix) so the same build/test/lint pipeline runs +identically with plain `cargo` or with `nix`. The intent is to eventually turn this into a `nix +flake init` template — see `scripts/rename-project.sh`. + +## Two ways to work in this repo + +**Cargo way** — fastest inner loop, needs Rust installed locally (or run inside `nix develop`): +```bash +cargo run -- Zeke # build + run the CLI, arg is the name to greet (default: "world") +cargo build # debug build +cargo build --release +cargo test # unit test (src/lib.rs) + integration test (tests/greet.rs) +cargo bench # criterion benchmark (benches/greet.rs) +cargo clippy --all-targets -- --deny warnings +cargo fmt +``` +To run a single test: `cargo test greets_by_name` (matches both the unit and integration test by +name; scope to one with `cargo test --test greet` for just the integration test, or +`cargo test --lib` for just the unit test). + +**Nix way** — reproducible, pins the exact toolchain via fenix, no local Rust install required: +```bash +nix develop # drops into a shell with the same toolchain/tools as CI, then use cargo as above +nix build # produces ./result/bin/rust_template +nix run # build + run the CLI +nix flake check # runs every check below in parallel, each cached separately by crane +nix fmt # formats Rust (rustfmt), Nix (nixfmt), and TOML (taplo) via treefmt +``` +`nix flake check` runs: `clippy` (deny warnings, all targets), `fmt` (rustfmt), `doc` (cargo doc, +deny warnings), `audit` (cargo-audit against the RustSec advisory DB), `nextest` (unit + +integration tests), and `formatting` (treefmt check). These are the same derivations +`nix develop`'s dev shell inherits, so a passing `nix flake check` is a reliable signal before +pushing. + +Both paths are meant to converge on the same result — the Nix devShell and the Nix package build +are built from one shared toolchain/build-args definition (see Architecture below), so what +compiles under `nix build` should also compile under plain `cargo build` inside `nix develop`. + +## IDE / LSP support + +`rust-analyzer` is included in the fenix toolchain (`nix/rust.nix`), and `nix develop`'s +`shellHook` exports `RUST_SRC_PATH` so `rust-analyzer` can resolve std-library sources. Point your +editor's rust-analyzer at the toolchain from inside `nix develop` (e.g. via `direnv` + `use flake`, +or by launching your editor from within `nix develop`) rather than relying on a system-wide Rust +install, so the LSP sees the same compiler version as the build. + +## Architecture + +- **`nix/rust.nix`** is the single source of truth for the Rust build environment: the fenix + toolchain, `craneLib`, `commonArgs` (source filtering, `strictDeps`, native/build inputs), and + the cached `cargoArtifacts` (deps built once via `craneLib.buildDepsOnly`, then reused by the + package build, clippy, doc, and nextest checks — this is what makes `nix build` incremental + instead of recompiling all dependencies on every source change). `packages.default` and + `devShells.default` both derive from this file's output, which is why they stay close to + identical. +- **`nix/devshell.nix`** builds the dev shell from that same `craneLib`/toolchain via + `craneLib.devShell`, adding dev-only tools (`cargo-nextest`, `cargo-criterion`, `cargo-audit`, + `cargo-expand`, `taplo`, `bacon`, `mold` on Linux) that aren't part of the package build itself. +- **`nix/treefmt.nix`** is a flake-parts module enabling rustfmt/nixfmt/deadnix/statix/taplo under + treefmt-nix, wired into both `nix fmt` and the `formatting` check in `flake.nix`. +- **`flake.nix`** ties it together with `flake-parts`: it imports the treefmt module, evaluates + `nix/rust.nix` per-system, and exposes `packages.default`, `checks`, `apps.default`, and + `devShells.default` from its outputs. +- **`src/lib.rs`** holds the actual logic (currently just `greet`); `src/main.rs` is a thin `clap` + wrapper around it. Keep new functionality in the lib crate, not `main.rs`, so it stays reachable + from `tests/` and `benches/` (both depend on the `rust_template` lib crate by name, not on the + binary). +- **`tests/greet.rs`** is the example integration test (tests the public lib API from outside the + crate); `src/lib.rs`'s `#[cfg(test)] mod tests` is the example unit test (tests internals + in-crate). Follow whichever pattern fits: internal/private logic → unit test in the same file; + public API surface → integration test in `tests/`. +- **`benches/greet.rs`** is a `criterion` benchmark against the lib crate; `Cargo.toml` disables + the default libtest harness for it (`harness = false`) since criterion supplies its own. + +## Renaming the template + +`scripts/rename-project.sh ` replaces every `rust_template` occurrence +(crate name in `Cargo.toml`, the `pname`/binary name in `nix/rust.nix` and `flake.nix`, and the +`use rust_template::...` imports in `src/main.rs`, `tests/greet.rs`, `benches/greet.rs`), then +regenerates `Cargo.lock`. It does not rename the project directory itself — that's a manual `mv` +afterward (the script prints the exact command). Run `nix flake check` after renaming to confirm +everything still resolves. + +## Keeping this file current + +This file describes the template in its current, minimal state. As real functionality gets added +(more crates, workspace layout, new checks, CI, actual product logic replacing `greet`), update +this file to match — stale architecture notes are worse than none. Don't let it drift from what +`scripts/rename-project.sh` actually touches, either, if the file list it edits changes.