tool-creation-guidelines
Best practices for creating developer tools and scripts in this project. Use when planning or reviewing tools that parse, analyze, or transform code.
Files1
View on GitHubSKILL.md.claude/skills/tool-creation-guidelines/SKILL.md
GitHubTool Creation Guidelines
Best practices for creating developer tools and scripts in this project. These apply to tools in tools/ and scripts/ directories.
Tool-first philosophy
Tools are the default, not the exception. If you perform a task manually more than once, build a tool. Even for tasks that require thought and judgment, build "groundwork" tools that gather data, structure inputs, and present summaries — so the orchestrator can spend its effort on reasoning rather than mechanical data collection.
Examples of groundwork tools:
- A tool that scans Eva's recent comments and presents structured output (saves API query construction)
- A tool that diffs Google docs properties against codebase implementations (saves manual comparison)
- A tool that summarizes PR changes with relevant context (saves reading raw diffs)
- A tool that checks all startup checklist steps and reports only anomalies (saves running each step manually)
Do not gate tool creation on frequency alone. A tool that saves 2 minutes per cycle pays for itself within a few cycles. The cost of building a small Rust tool is low; the cost of not building it compounds across every future cycle.
Use AST parsers, not regex
When parsing source code (PHP, TypeScript, or any language), always use established AST parsing libraries instead of regex-based string matching. This is a firm project convention (Eva directive #378).
Why
- Regex is fragile against formatting changes (whitespace, comments, multiline constructs)
- Regex cannot handle nested structures reliably (brackets, generics, unions)
- Regex reinvents parsing that battle-tested libraries already solve
- AST parsers handle edge cases (e.g.,
extendsclauses, complex type annotations) that regex misses
Available parsers in this project
| Language | Parser | Package | Notes |
|---|---|---|---|
| TypeScript | TypeScript Compiler API | typescript (already installed) |
Use ts.createSourceFile() to parse TS/JS files |
| PHP | php-parser (glayzzle) | php-parser (dev dependency) |
Pure JS PHP 8.x parser |
What NOT to do
- Do NOT use regex to extract class declarations, constructor parameters, interface properties, enum cases, or type annotations from source files
- Do NOT write custom bracket-depth-tracking string splitters (e.g.,
splitTopLevel()) — the AST handles nesting correctly - Do NOT parse
PROPERTY_MAPorA_SCHEMA_TYPEconstants with regex when an AST gives you structured constant declarations
When regex is acceptable
Regex is fine for:
- Simple pattern matching in output/log text (not source code)
- Filtering file names or paths
- Post-processing already-extracted string values (e.g., normalising type names)
Rust tools
For tools that don't need source code parsing (JSON processing, state verification, report generation, API query automation), Rust is the preferred language. Rust tools:
- Run in the orchestrator sandbox (
bash tools/<name>andcargo *are both allowed) - Avoid the sandbox restrictions that block tools like
jq -f - Compile to fast, standalone binaries with no runtime dependencies
- Live in the
tools/rust/Cargo workspace
See the rust-tooling skill for the full workflow (workspace structure, shell wrappers, conventions).
Tool quality assurance (per Eva directive #516)
Tools we rely on must be high quality. PR #514 demonstrated that a fail-open bug in the cycle-status tool's commit-freeze check went undetected through initial development and review. If we rely on a tool to enforce a safety invariant, the tool itself must be built and reviewed to a higher standard than ad-hoc scripts.
Fail-safe by default
Safety-critical tools (those that gate decisions like "is it safe to publish?" or "is the commit freeze intact?") MUST fail-closed:
- If a check cannot be performed, report failure — not success. A tool that says "all clear" when it can't actually verify anything is worse than no tool at all.
- Distinguish "check passed" from "check could not run". Use explicit status fields (e.g.,
check_failed: bool) or error states, not justpass: true/false. - Validate inputs before using them. If a tool reads a commit SHA from state.json and passes it to
git, validate the format first. If a tool reads a file path, verify it exists.
Adversarial testing requirements
Every tool issue spec MUST include error-path test cases. The happy path is easy; bugs hide in error paths.
Minimum test coverage for dispatched tool work:
- Happy path — the tool works correctly with valid inputs
- Invalid input — malformed data, missing fields, empty strings, unexpected types
- External command failure — if the tool shells out to
git,gh, etc., test what happens when those commands fail (non-zero exit, no output, timeout) - Edge cases — boundary values, empty collections, single-element collections
- Safety invariant — for safety-critical tools, explicitly test that failure modes produce the conservative (fail-closed) result
Include these in every agent issue spec:
### Required test cases
1. Happy path: [describe expected behavior]
2. Invalid input: [describe what should happen with bad input]
3. Command failure: [describe fail-closed behavior]
4. Edge case: [describe boundary conditions]
Tool review checklist
When reviewing a tool PR (whether from the coding agent or self-authored), verify:
- Error paths fail-closed — every
Err, non-zero exit, or missing data case produces the conservative result - No silent swallowing — errors are logged/reported, not silently ignored
- Input validation — external inputs (state.json values, CLI args, API responses) are validated before use
- Test coverage — unit tests exist for both happy and error paths
- Output format — both
--jsonand human-readable output reflect the new behavior - Action items — if the tool produces action items, error states generate appropriate (different) action items from success states
- Deprecation warnings — no use of deprecated APIs (e.g.,
chrono::Duration::hours()→TimeDelta::try_hours())
Tool maintenance
Existing tools accumulate debt as their environment changes. Every 10 cycles (during the tool audit):
- Run each tool's test suite —
cargo test -p <tool-name> --manifest-path tools/rust/Cargo.toml - Check for deprecation warnings —
cargo build 2>&1 | grep -i deprecat - Review error paths — are they still fail-closed? Has any refactoring introduced fail-open behavior?
- Check for new edge cases — has the data the tool processes changed in ways the tool doesn't handle?
Verification anti-pattern: tools must not compensate for what they verify
Verification and validation tools must check outcomes, not create them. A verification tool must never contain workarounds, fallbacks, or compensating logic for the conditions it is testing. If a verification tool needs a workaround to pass, the root cause must be fixed instead.
This anti-pattern was discovered in scripts/verify-build.mjs, which contained logic to copy ../dist into dist before checking the build output — masking a broken tsup.config.ts for 20+ cycles. The tool reported PASS despite the build configuration being incorrect.
When reviewing verification/validation tools, flag:
- File copy/move operations that "fix" the layout before checking it
- Default values substituted for things being verified
- Fallback paths that compensate for misconfiguration
- Any logic that creates the conditions it then checks
This applies to all tools in scripts/ and tools/ that perform verification, validation, or assertion.
General tool principles
- Use existing dependencies first. Check
package.jsonandcomposer.jsonbefore adding new packages. TypeScript is already installed — its compiler API is available for free. - Rust for non-AST tools. For JSON processing, state verification, or report generation, prefer Rust (
tools/rust/crates/) over jq or shell scripts. Rust tools can run in the orchestrator sandbox;jq -fand complex shell constructs cannot. - Ask Eva for help. If you need new dependencies installed, workflow changes, or permissions — create an issue. Eva is part of the team.
- Keep tools focused. One tool, one purpose. Don't combine unrelated analyses into a single script.
- CI-ready exit codes. Tools should exit 0 on success, non-zero on failure. This enables CI integration.
- Preserve output format. When rewriting a tool's internals, keep the same output format unless there's a specific reason to change it.
- Dispatch tool-building to the coding agent. For non-trivial tools, create an issue spec describing the tool's purpose, inputs, outputs, and constraints, then assign it to the Copilot agent. The agent can build Rust tools, TypeScript analysis scripts, and pipeline components.
Pipeline composition
Tools should be designed to compose into pipelines:
- Structured output: Tools should support
--jsonoutput for machine consumption alongside human-readable output. - Exit codes: Exit 0 on success, non-zero on failure. Pipelines can short-circuit on failure.
- Consistent
--repo-rootconvention: All tools accept--repo-root <path>to locate repo files. - Idempotent: Running a tool twice with the same inputs should produce the same output.
- Composable: A tool's output should be usable as input to another tool or to the orchestrator's reasoning.