rust-tooling
How to create Rust tools for the orchestrator, add crates to the workspace, and write shell wrappers. Use when building new tools or extending existing ones.
SKILL.md.claude/skills/rust-tooling/SKILL.md
GitHubCreating Rust Tools
The orchestrator can build its own compiled tools to automate repeated patterns. Tools live in a Cargo workspace at tools/rust/.
Workspace structure
tools/
check-field-inventory-rs # shell wrapper (extensionless or -rs suffix, executable)
check-field-inventory.jq # legacy jq version (kept for reference)
rust/
Cargo.toml # workspace root: members = ["crates/*"]
Cargo.lock # committed for reproducible builds
crates/
check-field-inventory/ # existing tool
Cargo.toml
src/
main.rs
your-new-tool/ # new crate goes here
Cargo.toml
src/
main.rs
Adding a new tool
1. Create the crate
mkdir -p tools/rust/crates/my-tool/src
2. Write Cargo.toml
Use the Write tool to create tools/rust/crates/my-tool/Cargo.toml:
[package]
name = "my-tool"
version = "0.1.0"
edition = "2021"
description = "What this tool does"
[dependencies]
clap = { version = "4", features = ["derive"] }
The workspace members = ["crates/*"] glob automatically picks up new crates.
3. Write the source
Use the Write tool to create tools/rust/crates/my-tool/src/main.rs with a clap CLI.
Convention: accept --repo-root <path> as the first argument for locating repo files.
4. Build
cargo build --release --manifest-path tools/rust/Cargo.toml
5. Create the shell wrapper
Use the Write tool to create tools/my-tool (extensionless, matching the audit repo convention):
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BINARY="$SCRIPT_DIR/rust/target/release/my-tool"
if [ ! -x "$BINARY" ]; then
echo "Pre-built binary not found, building..." >&2
cargo build --release --manifest-path "$SCRIPT_DIR/rust/Cargo.toml" >&2
fi
exec "$BINARY" --repo-root "$REPO_ROOT" "$@"
6. Test
bash tools/my-tool --help
Conventions
- Use
clapderive for CLI argument parsing - Accept
--repo-rootto locate repo files relative to the workspace root - Shell wrappers — invoked as
bash tools/my-tool - Commit
Cargo.lockfor reproducible CI builds - Pre-build in CI — the orchestrator workflow builds all tools before the session starts
- Keep dependencies minimal — each tool should be focused and fast to compile
serde_jsonis the standard crate for JSON parsing (already in workspace)
Shared state-schema crate
The workspace includes a shared library crate at tools/rust/crates/state-schema/ that provides strongly-typed serde structs for docs/state.json. All tools that read or write the state file should depend on this crate instead of parsing JSON manually.
Adding it as a dependency
In your tool's Cargo.toml:
[dependencies]
state-schema = { path = "../state-schema" }
Usage
use state_schema::StateJson;
let content = std::fs::read_to_string("docs/state.json")?;
let state: StateJson = serde_json::from_str(&content)?;
// Access typed fields: state.copilot_metrics, state.test_count, state.field_inventory, etc.
Key features:
#[serde(default)]on all structs — tolerates missing fields#[serde(flatten)] extra: BTreeMap<String, Value>— preserves unknown fields on round-tripSCHEMA_VERSIONconstant for validation- All tools that need state.json (metric-snapshot, cycle-status, housekeeping-scan, pipeline-check, check-field-inventory) use this crate
When to update state-schema
When you add a new top-level or nested field to docs/state.json that a Rust tool needs to read, add the corresponding struct field to state-schema/src/lib.rs. If no Rust tool needs it yet, the #[serde(flatten)] extra field will capture it automatically.
When to create a tool
Tools are the default. Create a tool when you notice:
- Any manual process you've performed more than once
- Complex JSON processing that's error-prone with manual jq queries
- Verification or validation checks that should be automated
- Data extraction patterns from state files or API responses
- Any task blocked by sandbox restrictions on existing tools (like
jq -f) - Groundwork for reasoning tasks — even tasks requiring judgment benefit from tools that gather and structure the inputs (e.g., a tool that presents a summary of what changed since last cycle, so the orchestrator can focus on deciding what to do rather than collecting data)
If in doubt, build the tool. A small Rust tool takes minutes to create and saves time on every future cycle. Dispatch non-trivial tools to the Copilot coding agent — describe the tool's purpose, inputs, outputs, and constraints in an issue spec.
Permissions
The orchestrator has Bash(cargo *) and Bash(bash *) permissions, so it can:
- Build tools:
cargo build --release --manifest-path tools/rust/Cargo.toml - Run tools via wrapper:
bash tools/my-tool --help - Run tools directly:
tools/rust/target/release/my-tool --repo-root .
These permissions are configured in both .claude/settings.local.json (local) and .github/workflows/orchestrator.yml (CI).