Test impact analysis and code intelligence built for AI coding agents. Zero external dependencies, open source, MIT licensed.
Chisel maps tests to code, code to git history, and answers: what to run, what's risky, and who touched it. It runs as an MCP server alongside your agent — Claude Code, Cursor, Windsurf, Cline, or any MCP-compatible client.
Chisel builds a graph connecting your code, tests, and git history, then answers three questions:
You change a function in src/engine/analyze.rs. Chisel tells the agent which tests are impacted, through direct edges and the import graph, instead of running the whole suite or guessing from the filename.
Risk scores per file based on churn rate, coupling breadth, test coverage gaps, author concentration, and test instability. A file that changes often, has one author, and no tests? That's your highest risk.
Blame-based ownership (who wrote it) and commit-activity-based reviewer suggestions (who maintains it). Useful when multiple agents or developers work on the same codebase and you need to understand lineage.
When multiple LLM agents (or agents + humans) work on the same codebase, changes in one area can silently break another.
Chisel gives AI coding assistants the intelligence to understand the blast radius of their changes before they commit. One agent's refactor doesn't silently regress another agent's work — automated code quality checks that work at the speed of your agent.
The tool you run is the Rust binary. Build it from this repo. Result checks use the JSON under tests/fixtures/oracle. There is no Python package to install.
git clone https://github.com/IronAdamant/Chisel.git
cd Chisel
cargo build --releaseThe binary is target/release/chisel. chisel serve-mcp starts the stdio MCP server. A local build also writes chisel-mcp, which is the same program.
Storage is a snapshot file at .chisel/store. Pass --json and branch on the status field. no_data means run analyze. stale_db means run update or pass auto_update.
If you still have a SQLite chisel.db from the Python release, chisel migrate path/to/chisel.db copies the recorded test results into the snapshot. Other commands leave that file alone. The live store is not SQLite.
Add to your Claude Code MCP config (~/.claude/settings.json or project .mcp.json):
{
"mcpServers": {
"chisel": {
"command": "chisel",
"args": ["serve-mcp"],
"env": {
"CHISEL_PROJECT_DIR": "/path/to/your/project",
"CHISEL_STORAGE_DIR": "/path/to/your/project/.chisel"
}
}
}
}chisel serve-mcp speaks newline-delimited JSON-RPC on stdio, the same framing standard MCP clients send. A client that sends Content-Length headers still receives a Content-Length reply. initialize reports the crate version in serverInfo.version. The release binary is chisel. A copy named chisel-mcp or chisel-mcp.exe starts the same server. CHISEL_PROJECT_DIR and CHISEL_STORAGE_DIR are honoured when the CLI flags are absent.
Run analyze first to build the project graph, then diff_impact after edits to see which tests to run. analyze with force on more than 300 code files returns auto_queued and a job_id. Poll job_status. --working-tree reads the worktree. Leave it off to answer from the snapshot.
Monorepo sharding: CHISEL_SHARDS or .chisel/shards.toml gives each shard its own snapshot under .chisel/shards/<shard>/. Unscoped reads aggregate. Writes use the longest path prefix. A test in one shard that imports code in another is stored on the parent snapshot and included in impact and suggest_tests. An unknown shard returns status: "error".
After running tests, call record_result so Chisel can track failure rates and test instability over time. Or use chisel run -- pytest tests/ to run tests and record results automatically.
There is also an installable Claude Code skill with the distilled agent protocol: copy skills/SKILL.md to ~/.claude/skills/chisel/SKILL.md.
Stdio MCP for local clients:
chisel serve-mcpHTTP is a separate JSON API, not an MCP URL. chisel serve --port 8377 answers GET /health, GET /tools, and POST /call. There is no /mcp path.
# Analyze a project (builds the graph)
chisel analyze .
# What tests are impacted by my current changes?
chisel diff-impact
# What tests should I run for this file?
chisel suggest-tests src/engine/analyze.rs
# Risk heatmap (defaults: line-weighted coverage + proximity adjustment)
chisel risk-map
chisel risk-map --working-tree --auto-update
chisel risk-map --no-proximity --coverage-mode unit # override defaults
# Find code with no test coverage, sorted by risk
chisel test-gaps --working-tree
# Who owns this code?
chisel ownership src/engine/analyze.rs
# Run tests AND record pass/fail results in one step
chisel run -- pytest tests/
# Incremental update (near-instant when nothing changed)
chisel update
# Copy recorded results out of a Python-era chisel.db
chisel migrate path/to/chisel.db
# One command's flags
chisel churn --helpgit clone https://github.com/IronAdamant/Chisel.git
cd Chisel
cargo build --release
./target/release/chisel analyze .
chisel risk-map
chisel diff-impact
chisel test-gapsThe same binary has been run on flask, requests, cJSON, quixbugs, and the Probatio Separate language trees. Those snapshots were written outside the repos.
20 functional tools plus 6 advisory file-lock helpers for multi-agent coordination.
| Tool | What it does |
|---|---|
analyze |
Full project scan — builds the code/test/git graph. Optional shard param for sharded monorepos |
update |
Incremental re-analysis of changed files only. Optional shard param for sharded monorepos |
diff_impact |
Detects your changes from git diff and returns impacted tests. working_tree=true reads untracked files. auto_update=true refreshes a stale snapshot inline |
suggest_tests |
Ranks tests for one file (file_path) or every code file under directory. fallback_to_all ranks every known test when nothing else matches. A file that is not in the snapshot returns stale_db unless working_tree is set |
impact |
Which tests cover these files? Pass functions to limit the direct edges to those names |
risk_map |
Risk scores for all files (churn + coupling + coverage gaps). Defaults: line-weighted coverage + proximity. working_tree=true includes untracked files. exclude_new_file_boost=true suppresses the new-file boost. auto_update=true refreshes a stale snapshot inline |
test_gaps |
Code with zero test coverage, sorted by risk. working_tree=true includes uncommitted files. auto_update=true refreshes a stale snapshot inline |
triage |
Top risks + gaps + stale tests in one call. Supports exclude_new_file_boost and auto_update |
churn |
How often does this file or function change? |
coupling |
Files that change together or import each other |
ownership |
Blame-based — who wrote this code? |
who_reviews |
Commit-activity-based — who maintains this code? |
stale_tests |
Tests whose edges name code ids that are no longer in the snapshot |
history |
Commit history for a file |
record_result |
Log a pass or fail. duration_ms is stored and feeds the duration-variance part of test instability |
run |
CLI-only: stream a test command and record pytest or Jest results. Jest is invoked with --json and a relative test id. npx is treated as Jest. go test gets -json and cargo test gets --message-format=json |
stats |
Snapshot counts. It does not return the full result history |
start_job |
Run analyze/update in background (avoids MCP timeouts). Optional shard param |
job_status |
Poll a background job until complete |
cancel_job |
Request cooperative cancellation of a running background job |
optimize_storage |
Rewrite the snapshot file at .chisel/store |
- Zero dependencies — the Rust binary links only the standard library. No crates.io crates. System
gitis required at runtime. - Multi-language — Python, JavaScript/TypeScript, Go, Rust, C#, Java, Kotlin, C/C++, Swift, PHP, Ruby, Dart
- Framework-aware — pytest, Jest, Go test, Rust
#[test](including#[tokio::test]and#[rstest], intests/*.rsand in source files), Playwright, xUnit/NUnit/MSTest, JUnit (incl. parameterized), XCTest + Swift Testing, PHPUnit, RSpec, Minitest, gtest, Dart test - Incremental —
updaterecomputes changed files and newer commits. A no-op update setsedge_rebuild_skippedand does not rungit blameor per-functiongit log -L. Above 2000 code files, file-level churn still runs and per-functiongit log -Ldoes not (unit_churn_skipped). - gitignore-aware — ignored trees (vendored deps, build output, fixtures) are never scanned; untracked files still are (
CHISEL_INCLUDE_IGNORED=1to override) - Branch-aware —
diff_impactauto-detects feature branch vs main. Feature-branch co-change comes frommerge-base..HEAD.mainandmasterkeep that list empty. - Multi-agent safe — cross-process locks so parallel agents don't corrupt the snapshot
- MCP + CLI — stdio MCP (
chisel serve-mcp, newline JSON or Content-Length) and a JSON HTTP API (GET /health,GET /tools,POST /call).chisel <command> --helpprints that command. HTTPsuggest_testsadds a warning when the file usesevalornew Function - CI-friendly —
errorandgit_errorexit 1. Other statuses, includingno_dataandstale_db, exit 0.examples/github-actions/chisel.ymlbuilds this binary with Cargo. Do not pip-install Chisel. - Monorepo sharding — one snapshot per shard under
.chisel/shards/<shard>/(CHISEL_SHARDSor.chisel/shards.toml). Cross-shard test edges are overlaid from the parent snapshot.flakiness_trendappears when dated test results span two weeks. - Custom extractors —
CHISEL_EXTRACTORnames an executable that returns code units.CHISEL_DEP_EXTRACTOR, ordep_extractorin.chisel/extensions.toml, names an executable that returns import and call dependencies. That replaces the old Pythonregister_dep_extractorhook.CHISEL_BOOTSTRAPis not a Python import: if it points at an executable it is used as the dependency extractor, otherwise it is ignored with a warning. The binary does not load tree-sitter or plugins.
Chisel sits in the agent loop: impact -> tests -> record results -> refresh analysis. It works standalone or alongside Stele for semantic code context.
Docs for this binary: AGENTS.md | Claude Code skill
Checked-in oracle JSON lives in tests/fixtures/oracle. There is no Python package to install.
Timed on this machine: Linux 6.12.94+, 4 vCPUs, Intel Xeon, 15.6 GiB RAM, Rust 1.97.0, Python 3.12.3. Five runs per command. The figure is the median in milliseconds, with the minimum and maximum in parentheses. The small repo is a two-file Python sample. The large repo is the django package from django/django (about 1000 code files) plus 80 of its test modules, with history split into commits of at most 100 paths so a shallow-clone boundary commit does not pair every file with every other file. Peak RSS and the raw log are in scripts/benchmark_results.md. Reproduce with scripts/benchmark_parity.py.
| Command | Python small | Rust small | Python large | Rust large |
|---|---|---|---|---|
startup (--help) |
64.6 (63.4–73.4) | 0.3 (0.3–0.5) | 71.6 (64.7–76.1) | 0.4 (0.2–0.5) |
| cold analyse | 88.5 (86.7–93.9) | 8.0 (7.7–10.8) | 13692.5 (13542.5–14014.5) | 8034.2 (7627.3–8398.0) |
| warm analyse | 82.8 (80.7–89.8) | 2.3 (2.2–2.5) | 8646.5 (8604.2–8754.3) | 63.0 (61.3–77.9) |
| warm update | 71.0 (67.9–74.6) | 2.6 (2.2–2.6) | 146.4 (139.1–163.6) | 68.8 (65.3–71.2) |
| impact | 63.6 (63.0–66.2) | 0.6 (0.6–0.7) | 111.9 (106.5–113.3) | 45.4 (44.6–48.2) |
| suggest-tests | 64.3 (63.7–68.1) | 0.6 (0.6–0.7) | 125.9 (121.5–131.0) | 46.0 (44.6–51.9) |
| risk-map | 67.8 (65.9–75.4) | 0.7 (0.6–0.9) | 253.6 (233.9–261.0) | 57.5 (55.8–64.0) |
| test-gaps | 64.9 (63.2–69.7) | 0.6 (0.5–0.6) | 163.4 (161.9–180.5) | 73.1 (65.0–76.5) |
| triage | 65.8 (64.0–67.1) | 0.7 (0.6–0.8) | 254.4 (239.9–262.7) | 94.9 (93.3–97.5) |
| churn | 64.2 (62.9–66.6) | 0.6 (0.6–0.7) | 65.2 (64.4–69.0) | 48.8 (46.0–51.4) |
| coupling | 64.3 (63.3–65.5) | 0.6 (0.6–0.7) | 64.1 (63.3–66.1) | 47.7 (41.8–53.4) |
| stats | 64.8 (63.8–66.3) | 0.6 (0.5–0.6) | 67.4 (66.5–68.6) | 46.0 (45.0–51.0) |
On that large tree both snapshots have 829 test units, and the small sample has 1 on both sides. Rust is faster on every command in both trees.
MIT
