This file is the single source of truth for how humans and coding agents work
in this repository. CLAUDE.md is a symlink to this file, so every agent reads
the same instructions.
When you generate a new project from this template, keep this file and adapt the project-specific parts (crate name, module map, feature flags, commands). Delete guidance that no longer applies rather than leaving it to rot.
Do this once, in a single commit, before writing feature code:
- Set
name,description,repository,keywords, andcategoriesinCargo.toml. - Rename the crate references in
README.md,src/lib.rs,examples/, andtests/(search forrust_templateandrust-template). - Replace the placeholder
greetingmodule with the first real feature area, keeping themod.rs/types.rs/test.rslayout. - Confirm
licenseandLICENSEmatch the project's intended license. - Update the security contact in
SECURITY.md. - Replace
ROADMAP.mdwith the real plan, or delete it. - Decide whether the project uses TinyBus. Keep
vendor/tinybuspinned and wire the required crate/features, or remove the submodule deliberately. - Rewrite the "Project Structure" section below to describe this crate.
This is a Rust 2024 library crate rooted at Cargo.toml.
src/
├── lib.rs # crate docs + the entire public re-export surface
├── error/mod.rs # crate-wide `Error` and `Result<T>`
└── <feature>/ # one directory per feature area
├── mod.rs # module docs, wiring, smallest useful public API
├── types.rs # substantial type definitions
└── test.rs # module-local unit tests
tests/ # integration tests against the public API only
examples/ # runnable, compiled-in-CI usage examples
crates/tinydocs-module/ # private TinyBus cdylib adapter
vendor/tinybus/ # pinned TinyBus source; optional until wired by a project
docs/
├── specs/ # behavior and architecture specifications
├── plans/ # test-first implementation plans
└── adr/ # immutable architecture decision records
Each feature area belongs in a focused module directory under src/. A module
root explains the module, wires its pieces together, and exposes the smallest
useful API. Move substantial type definitions into types.rs and put
module-local unit tests in a dedicated test.rs, wired from the bottom of the
module root with:
#[cfg(test)]
mod test;Do not accumulate inline mod tests blocks in implementation files, and do not
let a general-purpose utils.rs or helpers.rs grow — those are a symptom of a
missing module. Prefer many small modules that each do one thing well over few
broad ones.
Keep public exports centralized in src/lib.rs so downstream users have one
predictable surface. Put shared error variants in src/error/mod.rs and return
the crate-wide Result<T> from fallible public APIs.
Run every command from the repository root. These five are the contract; CI runs exactly them, so a green local run should mean a green CI run.
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo build --all-targets --all-features
cargo test --all-features
.github/scripts/check-file-coverage.sh 90 coverage.jsonSupporting commands:
cargo fmt --all— format before committing.cargo test <filter>— run a focused subset while iterating.cargo run --example basic— run the bundled example.cargo doc --no-deps --all-features— build the rustdoc CI also builds withRUSTDOCFLAGS="-D warnings".cargo test --doc— run doctests alone when editing documentation examples.cargo install cargo-llvm-cov— install the coverage tool required by the per-file coverage gate.
Never skip, ignore, or delete a failing test to make a command pass. Fix the root cause, or stop and report the blocker.
Use standard rustfmt output and Rust 2024 idioms. Do not hand-format around
rustfmt, and do not add #[rustfmt::skip] without a comment explaining why.
snake_casefor modules, files, functions, methods, fields, and locals.PascalCasefor types, traits, and enum variants;SCREAMING_SNAKE_CASEfor constants and statics.- Name things for what they are, not for their layer:
RetryPolicy, notRetryHelper. - Prefer small, typed APIs over stringly-typed ones. Accept
&strand genericimpl Into<String>at boundaries; return owned, concrete types. - Keep the public surface minimal: default to private, and export deliberately
from
src/lib.rs. unsafeis forbidden crate-wide by the lint configuration inCargo.toml. If a project genuinely needs it, relax the lint in its own commit and document every invariant with a// SAFETY:comment.
- One crate-wide
Errorenum insrc/error/mod.rs, built withthiserror. - Fallible public functions return
Result<T>, the crate alias. - Add a specific variant instead of stuffing context into a string; error messages are lowercase, without trailing punctuation.
- Do not
unwrap(),expect(), orpanic!in library code paths. They are fine in tests, examples, and genuinely unreachable states — whereexpectmust carry a message explaining the invariant. - Document a
# Errorssection on every public fallible function and a# Panicssection on anything that can panic.
Adding a dependency is a design decision. Before adding one, check whether the standard library or an existing dependency already covers the need. When you do add one:
- pin a caret range (
serde = "1"), not an exact version; - enable only the features you need, with
default-features = falsewhen that meaningfully trims the tree; - gate anything optional behind a Cargo feature, documented in
Cargo.toml; - leave a comment above the entry explaining why the crate is needed and what uses it — see the existing entries for the expected tone;
- prefer well-maintained crates with a compatible license.
Keep Cargo.lock committed; this crate ships a lockfile so CI and releases are
reproducible.
TinyBus is registered as the vendor/tinybus git submodule and pinned by its
gitlink. Initialize it after cloning with:
git submodule update --init --recursiveDo not edit vendored code from the parent repository. Make TinyBus changes in its own repository, push them there, then update this repository's gitlink in a separate commit. If the generated project consumes TinyBus, use the exact crate path and minimal features it needs; the template does not force that dependency on every generated crate.
- Module-local unit tests live in
src/<feature>/test.rsand may touch private items. - Integration tests live in
tests/and exercise only the public API — they are the regression suite for the crate's contract. - Use descriptive, behavioral test names:
rejects_an_empty_name, nottest_greet_2. - Cover the failure paths, not just the happy path. Every new error variant needs a test that produces it.
- For async behavior, standardize on one runtime (
tokioas a dev-dependency for tests) rather than mixing runtimes. - Tests must be deterministic and independent of network, wall-clock time, and
execution order. Gate any live/network test behind a feature or an env var and
name it
live_*so it is easy to exclude. - Maintain at least 90% line coverage in every source file under
src/. Add or update tests with every behavior change, and note any deliberately untested edge case in the pull request description.
Write the test first when fixing a bug: a failing test that reproduces the report, then the fix that turns it green.
Write documentation for the reader who has never seen the code.
- Every public item gets a rustdoc comment.
missing_docsis a warning that CI treats as an error. - Start every
mod.rsandtest.rswith a concise module-level//!description. src/lib.rscarries the crate-level overview: what the crate does, the primary entry points, and a short runnable example.- Prefer concrete examples over vague description. Doc examples are compiled and
run by
cargo test, so they cannot drift. - Complex modules must include a module-level
README.mdcovering their design, public surface, and important operational constraints. - Keep
README.md,docs/, and module docs aligned with code changes in the same commit that changes behavior. - Write accepted behavior and constraints in
docs/specs/before creating a linked, implementation-ordered plan indocs/plans/. Specs define what and why; plans define how and in what sequence. - Keep every Markdown file, including this one, at 500 lines or fewer. When a
topic outgrows that, split it into focused files and link them from the
nearest
README.md.
- Never commit directly to
main. Branch first, one branch per logical change. - Do feature work in a git worktree so the main checkout stays clean.
- Commit subjects are concise and imperative:
Add retry policy to the client. Keep the subject specific to the change and under ~72 characters. - Make small, focused commits. Each commit should cover one logical change, build independently, and avoid mixing formatting, refactors, and behavior changes unless they are inseparable.
- Never commit secrets.
.envis git-ignored; document new variables in.env.examplewith placeholder values. - Never force-push a shared branch, rewrite published history, or bypass hooks
with
--no-verify.
Open pull requests ready for review, not as drafts, unless the work genuinely must not merge yet. A pull request should:
- summarize what changed and why, in a few sentences;
- call out public API or behavior changes explicitly, or state "None";
- list the validation commands actually run, with their outcome;
- link the related issue;
- include updated tests, docs, and examples in the same change.
The template in .github/PULL_REQUEST_TEMPLATE.md encodes this checklist.
Address review feedback by fixing it, and reply on each thread describing what
changed. Do not resolve a thread whose feedback you have not addressed or
explicitly declined with a reason.
Releases run from .github/workflows/release.yml via a manual
workflow_dispatch with a patch / minor / major bump. The workflow
re-runs the full validation suite, computes the next version, updates
Cargo.toml and Cargo.lock, commits and tags vX.Y.Z, packages, pushes, and
publishes to crates.io using the CARGO_REGISTRY_TOKEN secret.
Consequently:
- Do not hand-edit the
versionfield inCargo.toml; the release workflow owns it. - Follow semantic versioning. Any change to the public surface that is not purely additive is a breaking change and needs a major bump (pre-1.0: a minor bump).
- The crate must be publishable at all times —
mainshould always be green.
For automated contributors specifically:
- Read before writing. Inspect the surrounding module and match its conventions, comment density, and idiom rather than importing a house style.
- Verify, do not assume. Run the four contract commands and read their output before reporting a task complete. Report failures with the output; never claim a check passed that you did not run.
- Stay in scope. Implement what was asked. Do not opportunistically refactor, reformat, upgrade dependencies, or "fix" unrelated code — raise it instead.
- No placeholders in delivered code. No
todo!(), no stubbed functions, no commented-out alternatives left behind. If something cannot be finished, say so explicitly. - Do not weaken the guardrails. Never add blanket
#[allow(...)], relax a lint, mark a test#[ignore], or loosen CI to get a green run. Fix the cause. - Secrets stay out. Never read, echo, or commit
.envcontents, tokens, or credentials, and never paste them into a pull request or issue. - Ask only when blocked. Make routine judgment calls yourself; escalate only irreversible decisions or genuine forks with no clear default.