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.
Files1
View on GitHubSKILL.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).