agent-calc is a contract-first CLI. Every design decision flows from three commitments:
- Exact-first — represent results as exact rationals when possible.
- Typed failures — every error path returns a stable
ErrorCode. - Bounded inputs — enforce runtime limits so the tool is safe to call from untrusted agents.
Before writing any code, decide: what are the algebraic properties this module must satisfy? Write those properties as proptest tests first. The implementation follows from the properties.
rustup toolchain install stable
rustup component add rustfmt clippy
cargo install cargo-mutants # for mutation testingmake install-hooksThis runs git config core.hooksPath hooks so git uses the hooks/ directory.
The hooks live in version control — no separate install script to maintain.
make test # after any edit — fast feedback
make ci # same as pre-commit, run manually any time
make full # before claiming a feature is done (ci + mutants)make ci = fmt-check + check + clippy + test + package
make full = ci + mutants
hooks/pre-commit fires on every git commit and runs the full gate:
fmt-check → check → clippy → test → package → 4 contract smoke checks.
hooks/pre-push fires when pushing to main and runs mutation testing
scoped to the src/ files changed since main. Feature branches skip it.
Force it on any branch: AGENT_CALC_MUTANTS=1 git push.
Emergency bypass (use sparingly): git commit --no-verify
Read the full checklist in CLAUDE.md before starting. The short version:
src/X.rs— types +evaluate()+x_schema_json()- Wire into
src/lib.rs,src/main.rs,src/protocol.rs tests/X_properties.rs— three or more proptest propertiestests/domain_error_codes.rs— every newErrorCodevarianttests/cli_contract.rs— schema, round-trip, bad-JSON golden testsmake full
Every module needs a tests/X_properties.rs file with proptest! blocks.
Each property must:
- Use a generator that covers zero, negative, boundary, and large values
- Assert an algebraic law (commutativity, identity, round-trip, etc.)
- Be tight enough that a relevant mutation kills it
A property that passes against a mutant is not finished.
Run make mutants before marking a feature complete. Surviving mutants are
bugs in the test suite, not in the implementation.
- Do not add
#[mutants::skip]to silence a surviving mutant. - Do not weaken an assertion to avoid a failure against a mutant.
- Do tighten the generator or assertion until the mutant is killed.
tests/domain_error_codes.rs must have at least one test for every
ErrorCode variant reachable from each command. If you add a new variant,
add a test before the PR is submitted.
| Result type | exactness field |
Required response fields |
|---|---|---|
| Exact rational | absent | numerator, denominator, display |
| Approximate f64 | "approximate_f64" |
value or data |
Omitting exactness on an f64 result is a contract violation. Callers rely
on its presence to decide whether to trust the result for exact downstream
arithmetic.
The contract_version field (calc1/0.1.0) governs breaking changes.
Breaking (bump version):
- Removing or renaming a response field
- Changing a field's type
- Removing an
intentvariant - Changing an
ErrorCodemeaning
Non-breaking (no bump needed):
- Adding a new optional response field
- Adding a new
intentor command - Adding a new
ErrorCodevariant
cargo fmtbefore every commit.cargo clippy -- -D warningsmust be clean.- No
unwrap()orexpect()outside of test code. - No
println!in library or handler code — onlyeprintln!for errors. - Errors go to stderr. JSON output goes to stdout. Always.
All gates are local git hooks — no remote CI.
| Hook | Fires | Runs |
|---|---|---|
pre-commit |
every git commit |
fmt + check + clippy + test + package + smoke |
pre-push |
push to main |
cargo mutants on changed src/ files |
Run make audit manually before any release to check supply-chain health.
<type>(<scope>): <short description>
<optional body>
Types: feat, fix, test, refactor, docs, chore
Scope: the command or module name (eval, finance, polynomial, etc.)
Examples:
feat(calculus): add definite integral with exact rational bounds
fix(matrix): standardize solve field type to match MatrixInput
test(stats): strengthen describe_sample properties to kill median mutant