
redhat-et/ripwire
Developer toolsThe ripgrep of AI context: a zero-dependency C++23 CLI + MCP server giving coding agents a ranked, deterministic map of any repo, blast radius, tests-to-run and quality deltas.
Overview
Point it at any repository and your agent gets a ranked, deterministic call graph — what to touch, what it breaks, which tests to run — instead of grepping around and reading whole files. Rust · C++ · Objective-C/C++ · C · Metal · CUDA · Python · Go · Swift · TypeScript · JavaScript · Java · Ruby · PHP · Lua · Bash · C# · JSON · TOML · YAML · Markdown — twenty-one vendored grammars, and adding another is a vendored tree-sitter grammar plus one row in a declarative table. One self-contained binary on your own machine, offline, installed in one line — and the same line installs and activates the task-shaped skills that teach your agent when to reach for it, not just how, for every agent it finds on the machine. If your agent can run shell commands — Claude Code, Codex, Cursor, Windsurf, Gemini, opencode, aider — it is set up the moment the install finishes; the MCP server is the optional second interface.
README
Rip’n Fast. Fewer Tokens. Better Code.
The ripgrep of AI context. A map before your agent reads the repo — and a check on what it writes.
Ranked, deterministic call graph: what to touch, what it breaks, which tests to run. On the edit: blast radius, tests that reach it, twelve quality kinds reporting only what got worse, forgotten co-changes, fields read and written, names that resolve more than one way.
Just want to use it? Install it with the one line below, then start each coding session by telling your agent to use it, for example: “Use ripwire on this repo.” That is all most people need: the install also teaches your agent when to reach for each command.
Want every detail? The reference guide near the bottom covers install, commands, output format, exit codes and limits. You do not need it to get started.
The model’s own report of one engagement, on a version before 0.5; not a controlled measurement. Controlled measurements are in docs/EVALS.md.
Languages: Rust · C++ · Objective-C/C++ · C · Metal · CUDA · Python · Go · Swift · TypeScript · JavaScript · Java · Ruby · PHP · Lua · Elixir · Dart · Kotlin · GDScript · Clojure · Bash · C# · JSON · TOML · YAML · Markdown — see language support and limits.
Latest: 0.6.8 — more complete first answers and fewer false edges. Release notes · the presentation · the changelog — with thanks to the contributors named there; this release is largely theirs.
RIPWIRE_REPO=redhat-et/ripwire bash -c "$(curl -fsSL https://raw.githubusercontent.com/redhat-et/ripwire/main/scripts/install.sh)"
export PATH="$HOME/.local/bin:$PATH" # where it installed; the installer prints this line if you need it
cd your-repo
ripwire . --for=""
Every install route (prebuilt, from source, per-agent skills, hooks, the MCP server) is in INSTALL.md.
Reach for the CLI first — it is the cheaper interface. The MCP server is the optional second way in, and its convenience has a cost the shell pipe does not carry: its verb schemas sit in your agent’s context every session, whether or not it calls them.
The goal: one question, one complete answer.
Terminality is the objective. Ask the codebase a question and the answer should carry everything you need — no follow-on grep, no three more whole-file reads to fill in what it left out. A call followed by three greps is the same search paid for twice: it does not save you tokens and it does not make the coding faster.
Two jobs, one goal. An agent meeting a codebase does two things, and ripwire is shaped for both:
- Orient —
ripwire .One call gives the ranked map: the main subsystems, the program entry points, the symbols that matter most, and a count of every file the ranking left out. The most important information about a new tree, in as few calls as possible. - Ask —
--for,--expand,--callers,--path, … After that, each question gets a lean answer: as few bytes as it can spend while still answering the question fully, so no follow-up grep is needed.
That is the goal, and not every answer reaches it yet; the stair-steps below are how we get there.
Same answer, a fraction of the tokens — read this table first if your agent is on a budget
Ordered understand → navigate → review-the-change:
| Ask it | Command | ripwire | naive read | token savings |
|---|---|---|---|---|
| “Orient me in this repo” | ripwire . |
~5.6K tok | ~20K–25K tok — read README.md (+docs/ARCHITECTURE.md) |
3.6×–4.5× |
| “Where is X handled?” | ripwire . --for="…" |
~2.1K tok | ~4.9K–20K tok — grep -rn src/, then read the file it points at |
2.3×–9.3× |
| “What do I already know?” | ripwire . --recall="…" |
~15K tok | ~445K tok — read all 119 markdown docs this repo carries | 29.2× |
| “Set me up for this task” | ripwire . --pack-task="…" |
~2.1K tok | ~16K–80K tok — read every relevant file, whole | 7.7×–37.7× |
| “Show me this one function” | ripwire . --expand=SYM |
~260–16.5K tok body (+~5.7K for the ranked-neighborhood bundle) | ~43K–174K tok — read the whole file it lives in | 2.6×–670× |
| “Who calls this function?” | ripwire . --callers=SYM |
~580 tok | ~40K–52K tok — grep -rn SYM src/ (mostly noise), then open 2–3 files to sort real calls from mentions |
69.2×–89.1× |
| “Is it safe to change this?” | ripwire . --impact=SYM + --uses=SYM |
~1.3K tok | ~18K tok — open every direct-use file, whole | 14.4× |
| “I have a stack trace” | ripwire . --from-trace=FILE |
~1.4K tok | ~124K–298K tok — grep all 7 frame names, then open the innermost file(s) | 86.9×–208.6× |
| “I changed these files — tests? blast radius?” | ripwire . --situ |
~410 tok | ~3K–132K tok — git diff + grep -rn test/, then open the candidates |
7.3×–324.2× |
| “Review this PR/diff” | ripwire . --pr-context=REF |
~1.9K tok | ~4.8K–51K tok — git diff REF, then open the touched files |
2.6×–27.5× |
See the map — not just the numbers
Django’s migration autodetector, coloured by complexity. Thresholds are fixed, so the colour means the same thing on every repo you point it at.
ripwire path/to/django/db/migrations --rank-by=rrf --top-k=120 --color-by=cx --html=map.html
One deterministic answer: the relevant symbols, their callers, the change risks, and
the tests that reach them. The task is yours to phrase — ask about your code, not ours. Run on this
repository (2026-08-30), ripwire . --for="incremental cache invalidation" produced about 4.3K
estimated tokens, not an enforced token budget. It includes:
This is what the output looks like, not the token-savings recipe. A bare --for on every question
is the most expensive way to use this tool — see Where it pays most
and the three controls below it.
| The agent without a map | The agent with ripwire |
|---|---|
| greps a common word, gets hundreds of hits across dozens of files | one ranked answer — est_tokens="3995" on this repository (re-derived 2026-09-05, the run above) |
| reads whole files to find the symbols that matter | those symbols, with complexity, churn and test coverage inline |
| finds the callers only if it thinks to grep for them too | callers, blast radius and the tests to run, in the same bundle |
| pays for every line it read, right or wrong | 5.0% of what that grep-and-read pass spends — on a 12-question set where it strictly satisfied 5 to the naive arm’s 11 (re-derived 2026-08-23) |
Both halves of that sentence, because one without the other is an overclaim. The 5.0% is context compression, not equal task completion: on the five questions both arms strictly satisfied, ripwire spends 5.2% of what the naive pass spends. Cheap context that answers less is not a saving if the agent then retries. The full adjudication, the four ranking defects behind the eleven-to-five gap, and the run where this number got worse are in Measured.
Against the leading graph-database code-context MCP server
Won 27 · lost 7 · tied 14 on 48 matched questions across django, webpack and this repository, spending ~77K tokens against its ~486K for the whole sweep.
What it misses, and what to run next →
Graph-Ranked Retrieval: It finds the right files more often than the alternatives
| Round 4 — LocBench, Python-dominant | strict file@10 | any@10 | index (median) | query (median) |
|---|---|---|---|---|
ripwire --for |
58.3% | 85.0% | 0.31 s | 0.108 s |
| codebase-memory-mcp 0.9.0 | 40.0% | 63.3% | 1.24 s | 0.075 s |
| repowise 0.37.0 | 33.3% | 53.3% | 34.0 s | 1.159 s |
| graphify 0.9.34 | 31.7% | 46.7% | 7.82 s | 0.614 s |
| Aider repo-map 0.86.2 | 20.0% | 35.0% | (inside query) | 2.920 s |
| codeseek 0.1.31 (better of its two arms) | 15.0% | 20.0% | 3.37 s | 0.040 s |
Measured 2026-08-08, before the performance work that ships in 0.6.0. ripwire has become faster since — llvm-project’s cold parse (182,555 files) fell from 194.1 s to 155.6 s of CPU, and --pack-task on a Go repository from 8.13 s to 5.88 s — but these timing columns have not been re-measured.
The table above, read two other ways. Every figure in both is one of its measured numbers; only the manners and the cynicism are editorial. Should the reader find the manners excessive, the author begs them to recall that the alternative was a second table.
| Lane | Built for | Why it wins there | Where it loses |
|---|---|---|---|
| name-exact | identifier-shaped queries (chooseForRanker, pack task) |
whole-name match ignores body noise: 91.3% recall@1, 0.960 MRR in src/ |
scores zero on any word that is not literally a name — forced onto prose it dies (0.016 MRR) |
| subtoken+body | prose and task queries (“where is the content hash computed”) | the only lane that matches vocabulary living in doc comments and bodies | exact names drown in shared subtokens (61.1% recall@1 on name queries) |
| mention anchor | a pasted path, Type.method, or issue URL |
a literal mention is lifted above any score — paste the ticket, don’t paraphrase it | adds nothing when the query names no artifact |
Saves Tokens: It answers for a fraction of the context
Where it pays most, and where it does not
Getting the saving takes three things, and a bare --for is none of them.
Better Code: It automates the review judgments nobody has time to make — every lens from published research
Built for Codex, Claude Code, Cursor, Windsurf, Gemini, opencode, aider, and any agent that can call a CLI.
Full retrieval tables — including the MRR figures behind the router numbers above — in
bench/ANSWERQUALITY.md and Measured.
▶ The whole tool in 36 slides — every figure names the instrument that pins it renders in your browser · pptx beside it · the numbers behind it
What it answers · Quickstart · The quality panel · Benchmarks · Honesty contract · Agent setup · Docs
What it answers
| Family | The question | Representative flags |
|---|---|---|
| understand a codebase cold | “What is this repo, and what matters in it?” | --for · --help-task · --tree · --lego · --exemplar · --recall · --top-k · --token-budget · --max-tokens |
| navigate / answer a question | “Who calls this? Is it safe to change? Which tests?” | --callers · --callees · --uses · --impact · --path · --connect · --affected · --situ · --test-gate · --grep |
| zoom the detail ladder | “Show me more — but only where it pays.” | --detail · --pack-signatures · --outline · --expand · --compress |
| assess quality / structure | “Where is the risk, and did I just add some?” | --quality-panel · --hotspots · --clones · --metrics · --deps · --lint · --quality-delta · --dmm · --edit-check · --pr-context · --merge-scout |
| self-diagnosis | “Is my setup actually working?” | --doctor |
| security | “Is this agent skill file safe to install?” | --scan-skill · --scan-skills |
| knobs / modes | shape, format, cache, budget | --json · --format · --mcp |
Quickstart
Building it yourself needs CMake 3.24+ and a C++23 compiler, and nothing else installed first — every dependency is vendored in-tree, so the build completes with the network off.
git clone https://github.com/redhat-et/ripwire.git
cd ripwire
cmake -S . -B build && cmake --build build -j
./build/ripwire . # orient once: the ranked map of an unfamiliar repo (add --max-tokens=N only if your context is tight — it cuts the map and says so)
Wiring it into your agent takes one more minute — wrap prints the recipe for your client, it
never edits your config:
ripwire wrap claude # prints the wiring: the CLI call first, `claude mcp add` as the alternative
ripwire wrap --all # detect every installed agent, print each one's recipe
skills/install.sh --codex # Codex CLI: the task-shaped skills that say when to query — and when to stop
Four commands worth learning first:
ripwire . # orient once: the ranked map of an unfamiliar repo (add --max-tokens=N only if your context is tight — it cuts the map and says so)
ripwire . --for="incremental cache invalidation" # the task lens: what to touch, ranked
ripwire . --callers=someFunction # who calls it
ripwire . --test-gate # before you commit: which tests must run
Written for your agent. By default, every command prints compact XML sized for an AI agent to read, not for a person scanning a terminal. Human-readable output is on the roadmap.
The quality panel
What each family actually looks at — on this repository’s own source, not a synthetic example:
| Family | Question | Backing verb | On this repo |
|---|---|---|---|
| structural | shape: complexity, size, nesting and how much of the body is deep, params, local-variable count — absolute bars, not a ranking | --metrics |
buildGraph (src/graph.h:462): ccx=698 loc=1244 nest=8 humps=30 deep=283 locals=114 — 114 local variables invisible to every quality lens until this session, because naming/size analysis has always stopped at a function’s signature; locals= is a disclosed floor (locals_floor="1"), threaded through the same walk that already computes ccx/nest, at zero extra parsing cost |
| lexical | identifier text: the 10 naming-* lint rules (short, wordy, case-mixed, uninformative, …) |
--lint, --naming-consistency, --lint --naming-locals |
see below — the one family with a fix, not just evidence |
| confusion | syntactic idiom: the 7 atom-* rules (implicit predicates, nested ternaries, embedded ++/--, …) |
--lint |
corpus-wide finding counts, not a per-function claim to spotlight here |
| historical | git change frequency — score = churn × cognitive complexity |
--hotspots |
src/main.cpp: churn=42 ccx=3387 score=142254 — top of --hotspots’ ranking, and its own worst function (main, ccx=387) is where developers keep working and the code is hardest. One caveat the panel’s own legend states and this table must too: churn is measured per file, so every symbol in a file carries that file’s churn=/hrank= verbatim — this family is file evidence inherited by the row, never the row’s own history |
| colocation (local reasoning) | how much you must read that isn’t in front of you | --context-ratio |
computeQualityDelta (src/mcpverbs.h:2031), ~50 lines, 87.0% of its distinct references resolve outside its own file — by the tokens a reader must actually read, 99.6%. A refinement of Beck & Diehl’s per-class congruence (FSE 2011); Martin’s instability I = Ce/(Ca+Ce) is its cruder ancestor |
| state (unintended side effects) | mutable state a change here can perturb, that --impact (who calls you) never asks about |
--nonlocal-state |
ensure_global_init (src/infra/profilePmc.h:288) reaches 3 distinct global/static cells through its own body and callees — a tiny, innocent-looking call site can still break state three hops away. Unsound by construction (no pointer aliasing, no indirect calls), so every count is a floor |
Five lenses sit beside the six-family join — deliberately outside its vote. Each is out for a stated reason, not an oversight:
| Lens | Asks | Why beside the join | On this repo |
|---|---|---|---|
--field-affinity |
which struct fields are read together but declared far apart; each loop’s access shape (index vs pointer-chase) | its subject is a type, not a function — attributing a struct’s finding to the functions touching it is a claim the lens never makes | MainDispatch: 12 findings at separation cost 92.88; 1,374 loops classified, 5 genuine pointer-chases |
cache-* lint rules + --with-profile |
cache-hostile access shapes (alloc-in-loop, p=p->next, a[b[i]], node containers, …), then which are measured hot |
rows are facts about sites, joined to per-scope hardware counters — not per-function evidence a family vote could count | aggressive rules: 0 hits in shipping src/; 327 findings adversarially triaged → 0 fix-worthy; the one open refactor settled by measurement (5.2 ms) |
--biggest-first (was --readability) |
orders by Halstead volume, Posnett sigmoid (a size proxy — the readability-ordering claim is withdrawn) | the fitted score saturates past 20 lines — only the ordering is meaningful, and an ordering cannot vote in a count | ordering only, never a grade |
--naming-consistency |
off-convention names, each with a computed propose= |
the one lens that emits advice — a fix is not evidence, so it does not vote | camelCase dominant at 93.0%; 136 names flagged with proposals |
--lint --naming-locals |
the naming rules pointed at local variables inside already-flagged functions | opt-in and unvalidated — stays outside any join until a real-corpus audit clears it (the withdrawn-rule lesson) | +973 findings that were structurally invisible before |
Want to help? Start anywhere on the spectrum. At the ready-made end, open problems — languages, resolver bugs, fuzzers, docs — are written up as starter kits: the research is done, the file and line pointers are in the prompt, and each prompt writes a plan and stops, so we can agree the approach before you write any code.
git clone https://github.com/redhat-et/ripwire && cd ripwire cmake -S . -B build && cmake --build build -j # plain build, no build type ls prompts/help-wanted/ # pick one claude "follow prompts/help-wanted/zig-language.md" # or your agent of choiceIn the middle, a few prompts hand your agent the whole repository and a job to do:
- Full audit — six independent lenses over the codebase; take one, or all six.
- Add a language — a vendored grammar, extraction and gates, start to finish.
- Use it for real, log every gap — do an actual task with ripwire and write down every place it let you down.
At the other end, bring your own:
- research an idea you have been turning over
- bring in a paper that deserves to be in a tool
- show how AI agents could read and write better code
- automate something decades of software engineering already know
- check software algorithmically — a smell nobody measures yet, a way to prove an answer is complete, a bug class a tool could catch before a reviewer does
Open an issue; we want to hear it. The part we would most like help thinking about is the quality lens: measuring what a change makes worse, and handing that back while the code is still being written.
Real runs
Measured
Against other tools
| Arm | strict file@10 | any@10 | index (median) | query (median) |
|---|---|---|---|---|
ripwire --for |
58.3% | 85.0% | 0.31 s | 0.108 s |
| codebase-memory-mcp 0.9.0 | 40.0% | 63.3% | 1.24 s | 0.075 s |
repowise 0.37.0 (MCP search_codebase, LLM-free wiki) |
33.3% | 53.3% | 34.0 s¹ | 1.159 s¹ |
graphify 0.9.34 (--code-only --no-cluster, keyless) |
31.7% | 46.7% | 7.82 s | 0.614 s |
| Aider repo-map 0.86.2 (ident-personalized) | 20.0% | 35.0% | (inside query) | 2.920 s |
| Aider repo-map 0.86.2 (no-personalization control) | 10.0% | 25.0% | (inside query) | 0.818 s |
| codeseek 0.1.31 (ident-mention convention arm) | 15.0% | 20.0% | 3.37 s | 0.040 s |
| codeseek 0.1.31 (raw issue text, keyless fallback) | 0.0%² | 0.0% | 3.37 s | 0.024 s |
Measured 2026-08-08, before the performance work that ships in 0.6.0. ripwire has become faster since — llvm-project’s cold parse (182,555 files) fell from 194.1 s to 155.6 s of CPU, and --pack-task on a Go repository from 8.13 s to 5.88 s — but these timing columns have not been re-measured.
What it saves you, in tokens
Context is the budget an agent actually spends. Three measurements, each with the instrument that pins it:
| Where the saving comes from | Measured | Pinned by |
|---|---|---|
--pack-signatures — body-elided declaration skeletons instead of full bodies |
81.8% fewer element bytes at top-50 (89.5% at top-10, 84.3% at top-100) — re-derived 2026-09-10 | test/showcasecapturecheck.sh, re-derived from this repo every run |
| Query-shape routing, on the production token ceiling | −39.4% p50, while strict file@10 rose +33.33pp | bench/locbench/, EVALS §3 |
| A whole-question bundle against a naive agent read | 96.0% fewer tokens (24.9×) — 14,758 against 367,192, tiktoken cl100k_base, six realistic questions |
bench/BENCHMARK.md — historical, private corpus, not reproducible from this tree |
Where its own cycles go — hardware counters, per scope
Standing on the whole field
The honesty contract
The differentiator is not a number, it is a discipline: a measurement you cannot check is a claim, and this tool ships the check.
What it misses, and what to run next
In the output
In the numbers
In the tests
Set it up in your coding agent
1. (Optional) Register the MCP server
ripwire wrap prints the recipe for the agent you name. It prints; it never edits your
config — you read the line, then run it.
ripwire wrap claude # CLI-first: the CLI call, then `claude mcp add` as the warm-index alternative
ripwire wrap codex # CLI-first: optional MCP restricted to audit/health verbs in Codex TOML
ripwire wrap opencode # CLI-first: the AGENTS.md wiring; its "mcp" stanza offered as the alternative
ripwire wrap openclaw # CLI-first: same ~/.agents/skills root Codex uses; no shell hook slot
ripwire wrap hermes # CLI-first: hermes mcp add as the warm-index alternative; hook port pending
ripwire wrap cursor # MCP: the mcpServers stanza for .cursor/mcp.json (or ~/.cursor/mcp.json)
ripwire wrap windsurf # MCP: that client's stanza
ripwire wrap gemini # MCP: that client's stanza
ripwire wrap aider # no MCP: a ranked map file, and the aider invocation that reads it
ripwire wrap --all # detect every installed agent and emit each one's config
2. Install the skills
Improve it with your agent
prompts/ holds thirteen self-contained orchestrator prompts: the loops this project is
built with, written so a coding agent can run them. They encode the workflow rather than describing
it.
Three worth starting with:
| Prompt | What it produces |
|---|---|
full-audit.md |
A severity-ranked audit across bugs, measured performance, verb-to-moment matching, token efficiency, and an ecosystem scan of papers and repos with real momentum. |
dogfood-gaps.md |
A real task done using only ripwire for navigation, with every fallback to grep or a whole-file read logged as a product gap at the moment it happened. |
capture-audit.md |
A fresh showcase capture read by parallel adversarial lenses, and the findings turned into family-wide gates. |
If it works on your codebase, tell us what it got wrong
Every number on this page is a measurement on a corpus we happen to have. Yours is one we don’t.
Languages
Reference guide
You do not need this guide to use ripwire. Install it, tell your agent to use it, and the agent takes it from there. Come here when you want to know exactly what a command does, what its output means, or where the tool stops being right.
Re-verified against a 0.6.1 build on 2026-09-14.
ripwire reads a source tree and writes a ranked symbol map to standard output: the symbols that matter for a
task, their callers, and the tests that reach them. One binary, and a map that costs nothing to serve — no API
key, no embeddings, no index server. The history-backed commands need git on the path and a repository to
read. Two commands reach the network and nothing else does: a git URL in place of a directory, which is
shallow-cloned into a cache, and --mcp --listen=HOST:PORT, which binds a socket and is the one mode that
serves other processes (see --mcp-token and --allow-remote-edits before exposing it).
This guide tells you how to install, operate, and evaluate ripwire. Read docs/COMMANDS.md for the
full command reference. Run ripwire --help for the current flag list. The binary generates
--help from its own flag table, so --help is the authority. If this guide disagrees with
--help, report this guide as a defect.
1. Purpose and function
ripwire reads a source tree, answers these questions about it, and can apply whole-symbol edits to it:
Orient
- Which symbols matter most in this tree? (
ripwire .) - Where do I start on this task, and which code does it touch? (
--for) - Can I get the ranking, the key function bodies, their callers and the tests to run in one bounded bundle? (
--pack-task) - How do I split this work across several agents without them colliding? (
--partition,--plan-lanes) - What does the tree look like, directory by directory? (
--tree) - Which command answers my question? (
--help-task) - What do the project’s notes and docs already say about this task? (
--recall,--notes) - Which existing code should new code imitate? (
--exemplar) - What must a new implementation of this interface provide, and who implements it already? (
--lego) - Which names does this repo use but never define — its standard library and third-party surface? (
--external-surface)
Navigate
- Who calls this symbol? (
--callers) - What does this symbol call? (
--callees) - Where is this symbol read, written or imported, not only called? (
--uses) - What does a change to this symbol affect, transitively? (
--impact) - How does one symbol reach another, following the direction of the calls? (
--path) - How do several symbols connect, in any direction? (
--connect) - What sits around this symbol in the graph? (
--around) - What does this function’s body say, with its callees’ signatures inline? (
--expand) - Where does this text appear, and in which function? (
--grep) - Where does this code shape appear? (
--match) - How does one variable flow through a function? (
--slice) - Which branch’s tree defines or mentions this symbol? (
--whereis) - Which frames of this stack trace, sanitizer report or compiler error are in my code? (
--from-trace) - Is this claim about the code true? (
--verify) - Can I ask the call graph a question these commands do not phrase? (
--graph-query) - Can I run several of these lookups in one turn? (
--batch)
Change it safely
- Is it safe to delete this symbol? (
--safe-delete) - Did my edit change a function’s contract, and which callers does that break? (
--edit-check) - Can I replace one function’s body without reading the whole file? (
--replace-symbol-body,--insert-before-symbol,--insert-after-symbol) - Which tests must run for this change, and what does no test reach? (
--test-gate) - Which tests reach these changed files? (
--affected) - What is my change’s blast radius, and which files usually change with these that I haven’t touched? (
--situ) - What does this branch touch, and which of those files are risky? (
--pr-context) - Do these branches conflict, and in what order should they land? (
--merge-scout) - How did the map change between two revisions? (
--map-diff)
Quality and structure
- Which quality problems does this tree have at all, in one ranked report? (
--quality-panel) - Where is the risk: complex code that changes often? (
--hotspots) - Did my change make the code worse? (
--quality-delta) - Where is code duplicated? (
--clones) - What does the linter flag? (
--lint) - Which cross-module call seams does no test reach? (
--seams) - What is built but switched off in this repo? (
--flags) - How do the modules depend on each other? (
--deps) - Does the code follow our layering rules? (
--arch) - Which clusters of code belong together? (
--communities) - Who knows this code, and where is that knowledge concentrated in one person? (
--owners) - Which documented claims are no longer true? (
--doc-drift) - Which files did ripwire skip, why, and which indexed files can it therefore not vouch for? (
--skipped)
Setup and safety
- Is my install working? (
--doctor) - Is this agent skill file safe to install? (
--scan-skill) - Is this whole skills directory safe to install? (
--scan-skills)
The tool performs five steps in a fixed order:
- Ingest. The tool crawls the tree, parses each file with tree-sitter, and extracts definitions and references.
- Graph. The tool resolves each reference to a definition and builds a call graph. The resolution is name-based. Section 7 states the limits of this method.
- Rank. The tool ranks the symbols with Personalized PageRank.
- Serialize. The tool writes a minified XML map. The output is deterministic. Section 8 states the determinism rules.
- Serve. Two front doors run that map: the command line, and an optional MCP server that a coding agent starts on demand.
Every command is a different view of the same graph. The command line and the MCP server use the same renderer. One computation has one output shape.
2. System requirements
| Item | Requirement |
|---|---|
| Operating system | macOS (arm64 or x86-64) or Linux (arm64 or x86-64). Native Windows x64 builds with both clang-cl and MSVC cl.exe — CI builds both on windows-latest every full matrix and smoke-tests each binary (--version, ctest, a real crawl, the two-run byte-identical contract, well-formed XML); the 660-gate suite does not run there, and ASan is compiled but never executed, so treat it as a build, not a validated platform. From 0.6.3 a prebuilt windows-x64 zip ships as a preview (Windows): CI unzips it and compares its output with Linux’s byte for byte, but no maintainer runs Windows, so WSL2 remains the fully supported way to run it on a Windows machine. |
| Prebuilt Linux floor | RHEL 8 or later (glibc 2.28) |
| Prebuilt macOS floor | macOS 14 or later, Apple silicon. 0.6.1 is the last release with an Intel macOS binary; on an Intel Mac, pin RIPWIRE_VERSION=v0.6.1 or build from source. |
| x86-64 floor | x86-64-v3 (Intel Haswell, 2013, or later), for a prebuilt binary and a source build alike |
| Build tools | CMake 3.24 or later, and a C++23 compiler |
| Compilers | clang 16+, AppleClang 15+, or gcc 13+ |
| Build dependencies | None outside the source tree. Every dependency is vendored. |
| Runtime dependencies | None for the map itself. The history-backed commands need git on the path, and a repository to read. |
| Network | Not required for a build or for a run. A git URL as the root is the one exception: ripwire shallow-clones it into a cache. |
The history-backed commands are --hotspots, --owners, --cochange, --quality-delta, --dmm,
--map-diff, --whereis, --stray-content, --merge-scout, --pr-context, --situ and
--rank-by=churn. Without git, or outside a repository, none of them returns a thin answer: most
refuse with exit 1 and a named reason, --dmm and --pr-context return an explicit unavailable
row, and --map-diff and --rank-by=churn still answer but disclose that the ranking fell back to
uniform.
The build completes with the network off, and test/dependencypincheck.sh proves it on every
commit: it runs a real disconnected configure — the step at which FetchContent would fetch — with
-DFETCHCONTENT_FULLY_DISCONNECTED=ON.
3. Installation
3.1 Install a prebuilt binary
-
Run the installer:
RIPWIRE_REPO=redhat-et/ripwire bash -c "$(curl -fsSL https://raw.githubusercontent.com/redhat-et/ripwire/main/scripts/install.sh)" -
Add the install directory to the path if necessary:
export PATH="$HOME/.local/bin:$PATH" -
Check the version:
ripwire --version
The installer downloads the latest GitHub release, verifies the SHA-256 digest, and installs the
binary to ~/.local/bin. The installer also stages the agent skills under
~/.local/share/ripwire/skills. It activates the skills for each agent it finds. It does not edit
your shell profile. It does not register hooks. To register hooks, run
bash ~/.local/share/ripwire/skills/install.sh --hook.
On Windows the installer does not apply: download the windows-x64 zip instead (Windows, a preview).
3.2 Build from source
-
Clone the repository:
git clone https://github.com/redhat-et/ripwire.git cd ripwire -
Build. Which build you want depends on why you are here.
To use the tool, build the fast binary the released one is built as.
scripts/pgobuild.shinstruments, trains and re-optimizes in one command, intobuild_pgo/— Release, plus link-time optimization, plus profile-guided optimization, which is 14-25% faster cold than the default build and byte-identical in output:scripts/pgobuild.shA plain Release build is the shorter path if you would rather skip the training run. It still gets link-time optimization; it does not get PGO:
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release cmake --build build-release -jTo work on the tool, build the development tree, with no build type at all:
cmake -S . -B build cmake --build build -j -
Test the binary you built:
./build_pgo/ripwire . # or ./build-release/ripwire . or ./build/ripwire .
Note: do not add -DCMAKE_BUILD_TYPE=Release to the development tree. Release defines
NDEBUG. NDEBUG removes the DISCLOSE( msg ) traces at compile time. A gate that
asserts a degrade path then passes without evidence. That is why the two builds above write to two
trees, and why scripts/pgobuild.sh writes to a third: CMakeLists refuses a profile-guided build in
build/ or asan/ by name, because every gate and bench number in this repository is measured
against those trees.
The continuous-integration pipeline builds both flavors. The plain build proves the degrade paths. The release build proves the optimizer-visible invariants. Neither flavor replaces the other.
Note: make decides what to rebuild by comparing timestamps, so a source that changes while a
build runs — most often a git checkout under a build in flight — leaves object files that are
newer than the sources they disagree with, and no later incremental build repairs them. After a
branch switch, or whenever sources may have moved under a build, use cmake --build build --clean-first -j. CONTRIBUTING.md records the three ways this has been hit and what each looked
like.
3.3 Install the agent skills
The skills/ directory holds 17 routable skills, one directory each, holding a SKILL.md. The
skills tell an agent which command is correct for each situation. Sixteen are for using the tool;
ripwire-opt-remarks is audience: contributor and installs only with --contributor.
skills/hermes/ holds one more, Hermes-native, which is why a find for SKILL.md returns 18.
Install the skills with the script:
skills/install.sh
skills/install.sh --codex
skills/install.sh --contributor
The script links the skills into the configuration directory of the agent. The --contributor mode
adds the skill for compiling ripwire itself. Read the header of skills/install.sh for all modes.
To check one file before installing it, run ripwire --scan-skill=FILE; for a whole directory, run
ripwire --scan-skills=DIR. ripwire wrap runs that scan itself and refuses to wire an agent when
it finds a CRITICAL, unless you pass --force.
3.4 Register the MCP server (optional)
The command line is the primary interface. The MCP server is the optional second interface. The MCP server exposes 33 MCP verbs. The verb schemas reside in the agent context for every session. For this reason, register the MCP server only when you need it.
ripwire wrap prints the recipe for one agent — claude, cursor, codex, aider and
others — and ripwire wrap --all prints one for every agent it detects. For Claude Code it
recommends the command line over the server, for the reason above.
The server is a standard input and output MCP process. The complete configuration is:
{
"mcpServers": {
"ripwire": { "command": "ripwire", "args": ["--mcp"] }
}
}
To list fewer tools, add --mcp-tools= to the server’s arguments: a comma list of tool names and/or the core
profile (explore, batch, from_trace, impact, uses, fetch_body, edit_check, quality_delta, the loop the server’s own
instructions teach). A client that loads every tool schema at session start then pays for 8 schemas instead of 33
(tools/list measured at 13,834 bytes instead of 46,368). A call to a tool that is not listed is refused with the
flag that enables it. The list is not access control: batch sub-queries still reach hidden verbs. ripwire wrap AGENT --mcp-tools=core writes the flag
into the recipe it prints. The default stays the full catalog.
For a socket instead of standard input and output, run ripwire --listen=HOST:PORT. A non-loopback
bind requires --mcp-token. The three edit verbs are disabled on a remote bind unless you pass
--allow-remote-edits. The socket speaks plain HTTP with no TLS, so on a non-loopback bind the bearer token and
every request cross the network in the clear: put a TLS-terminating reverse proxy in front of it.
4. First use
ripwire is AI-driven today. By default, every command prints compact XML sized for a coding agent to read, not for a person scanning a terminal. The commands in this section are the ones your agent runs. Human-friendly use is on the roadmap: output a person can read, and more convenient input for common coding cases.
4.1 Make the first map
Change to the repository. Run the following command:
ripwire . # orient once on an unfamiliar repository
The command writes the ranked map: the main subsystems, the entry points, the most important symbols,
and a count of every file the ranking left out. Read it once per session, then ask lean questions
(--for, --expand, --callers). If your context is tight, --max-tokens=N keeps only the head of
the map; the cut is disclosed (shown= of symbols=), but it can drop what you need, so prefer the
full map when it fits.
The first call on a tree parses it; every call after that reads a cache and answers in a fraction of
the time. Judge the cost of the tool by the second call, not the first. If the first call fails, or
looks wrong, run ripwire . --doctor: it checks the binary, the grammars, the cache and the git
identity of the index, and says which one is at fault.
4.2 Use the common commands
| Task | Command |
|---|---|
| Orient in a new tree | ripwire . |
| Prepare for a task | ripwire . --for="" |
| Get everything for one task in a single call | ripwire . --pack-task="" |
| Ask which command to run | ripwire . --help-task="" |
| Find a symbol’s callers | ripwire . --callers=SYM |
| Find the callees | ripwire . --callees=SYM |
| Measure the blast radius | ripwire . --impact=SYM |
| List the use sites | ripwire . --uses=SYM |
| Read one function body | ripwire . --expand=SYM |
| Find the tests for a change | ripwire . --test-gate |
| Check an edit’s contract | ripwire . --edit-check=SYM |
| Check what a change made worse | ripwire . --quality-delta |
| Find the pattern to imitate before writing | ripwire . --exemplar="" |
| Review the current diff | ripwire . --situ |
| Review a branch against a base | ripwire . --pr-context=BASEREF |
| Read a stack trace | ripwire . --from-trace=FILE |
| Inspect code quality | ripwire . --quality-panel |
| Locate a literal, or a regex | ripwire . --grep=STRING, ripwire . --regex=PAT |
| Search the documents | ripwire . --recall="" |
5. Command families
The --help output groups 192 long flags advertised in --help into seven families. The --help=
column below is the argument that prints one family: ripwire --help=navigate. ripwire --help=--FLAG prints one flag’s full text — the caveats, the units, what it refuses and why.
ripwire --help=all is the whole catalog, about 46,000 tokens.
| Family | --help= |
Purpose | Representative flags |
|---|---|---|---|
| Understand a codebase cold | understand |
What is this repository, and what matters? | --for, --help-task, --tree, --lego, --exemplar, --recall, --top-k, --token-budget, --max-tokens |
| Navigate and answer a question | navigate |
Who calls this, and is it safe to change? | --callers, --callees, --uses, --impact, --path, --connect, --situ, --test-gate, --grep |
| Zoom the detail ladder | zoom |
Show more detail only where it pays | --detail, --pack-signatures, --outline, --expand, --compress |
| Assess quality and structure | quality |
Where is the risk, and did a change add risk? | --quality-panel, --hotspots, --clones, --metrics, --deps, --lint, --quality-delta, --edit-check, --pr-context, --merge-scout |
| Self-diagnosis | self-diagnosis |
Is the setup correct? | --doctor |
| Security | security |
Is this agent skill file safe to install? | --scan-skill, --scan-skills |
| Knobs and modes | knobs |
Shape, format, cache, and budget | --json, --format, --mcp |
The --help= argument matches on a substring of the family’s own name, so the family titles in the
first column are not themselves valid arguments.
Not sure which command answers the task? Run ripwire . --help-task="". The command
returns one recommended command, or it abstains when the evidence is thin. The command gives advice
only. It does not run the recommendation. A short bare word — a lone letter, a SCREAMING name, or an
ordinary word that happens to match an indexed name (django’s F, for instance) — does not resolve on
its own; only genuine identifier shape does (camelCase, snake_case, ::/.). Mark it as code in the
task text — backtick it or write it in call form, e.g. `F` or F() — to route on it by name.
6. Output format
You do not have to learn the schema, because the document carries it. Every answer opens with a
legend comment defining each attribute it uses, including what each count does and does not include.
The map is root, files, symbols, call edges; each answering
command wraps its rows in an element named for the verb — , , , , ``.
Two runs over one tree produce the same bytes, and every attribute and text node is XML-escaped, so
operator< and a path containing & round-trip: ripwire . | xmllint --noout - is a gate here.
docs/ARCHITECTURE.md has the schema and the streaming writer behind it.
6.1 Output example
ripwire . --callers=rankGraphTeleport
The leading legend comment is elided here; the line numbers are a capture and move as files grow:
counts_floor="1" says every count on that row is a floor, never a total. next= is the one
follow-up command worth pasting.
6.2 Exit codes
This is the part to wire into a script.
| Code | Meaning |
|---|---|
| 0 | The command completed. |
| 1 | The command refused the request. A refusal names the reason on stderr. When --callers, --callees, --uses, --impact or --path refuse a selector that matches no indexed definition, they also print an answer on stdout: the verb’s element with found="0" and the name to retry with. |
| 2 | A policy gate fired: --arch found a layering violation, --scan-skill found a CRITICAL, --quality-delta found new debt. |
| 3 | The output exceeded the token budget that you set. |
| 4 | --test-gate found an open obligation. |
| 5 | The memory guard stopped the run: over --max-memory (default 65% of the machine’s memory) with no partial answer possible, or a verb other than the default map facing a partial index. One stderr line names the limit and the override. |
6.3 JSON output
--json emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an
allow-list: the default map, --for, --pack-task, --callers, --callees, --impact,
--quality-delta, --test-gate, --metrics, and --plan-lanes, which is JSON-native. Every other
verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow
refuses by default.
7. Accuracy and disclosure rules
The call graph is extracted from source text by name. The tool does not have a type system or a compiler. These limits follow:
- A virtual call produces one edge per candidate in the receiver’s inheritance cone — its static
type, that type’s ancestors and its descendants — and not one edge to the target it dispatches to
at run time. Each such edge carries
prov="split". When the receiver’s static type is unknown, the candidate set is not narrowed at all. - A callback through a function pointer contributes an edge in one case. One function must be bound
to the variable in scope, the variable must not escape, and the variable’s type must be provably a
function pointer: a typedef or declarator in the same file proves it, a typedef in a header is
missed, and an
autoor template type is read as unpinned and kept. - A macro-generated call site contributes an edge only when the tool indexes the function-like
#define. - A name that has several definitions at the same resolution tier produces one edge per candidate.
Each edge carries the weight
1/k. The symbol carriesamb="K". The header totals the events inambiguous=. - Two further gauges sit beside
ambiguous=.unresolved=counts calls left with no edge and no proof that the target is outside the tree: every same-named in-repo definition is language-filtered or out of the language’s lookup (a method for a receiverless call, another Go package).external=counts the calls the language does prove are outside (a builtin or global, an outside import).unindexed=counts files no grammar could read, whose calls raise neither of the other two.
The output uses these disclosure rules without exception:
- A zero is a measurement. A zero means “none found”. A zero never means “none exists”.
- A floor is labelled. A count that cannot be a total carries
counts_floor="1". - A truncation is disclosed. The element that truncated carries
total=,shown=andcapped=. A document that a budget cut carries its own marker and anext=step. - A refusal is not a zero. A selector that names nothing indexed refuses with a did-you-mean
suggestion computed from an edit distance. A query that resolves and selects nothing reports
count="0". - An estimate is labelled. Token estimates are calibrated approximations. They are not exact counts for every model.
- Counting units are named.
--callerscounts distinct symbols.--usescounts use sites. The two numbers differ by design.
Use these methods when a disclosure says the answer is incomplete:
- Use
--skippedto see which files are missing from the index, and which indexed answers it therefore cannot vouch for. - Use
--expand=SYMto read the body. - Use
--uses=SYMand--impact=SYMto measure the blast radius. - Use
--verify="CLAIM"to test one claim about the code: confirmed, refuted, or not-established. - Use
--scip=FILEto supply a compiler-grade index. A precise index replaces the name-based edges and marks the affected edges withprov="scip". A path that is missing, empty or not a regular file causes a refusal (exit 1). A corrupt index causes a fallback to the name-based edges and a warning on standard error. Neither case is silent.
8. Determinism
The determinism contract is byte-identity. One tree produces one byte sequence on every run. The contract holds under four rules:
- The crawl sorts all candidate paths before it assigns symbol identifiers.
- Every global reduction folds fixed contiguous blocks in index order. No reduction uses an atomic floating-point add.
- The rank vector uses type
double. Edges stayfloat; the multiplication and the accumulation promote todouble. - The PageRank translation unit compiles without floating-point reassociation:
-fno-fast-math, set onsrc/pagerank.cppalone.
Thread count and thread timing never change the output. Neither does the output emitter: the
std::print build and the std::format fallback emit the same bytes, which
test/printffmtparitycheck.sh asserts directly — the two disagree on float formatting by default,
and nothing else in the suite would see it.
Continuous integration runs the two-run byte diff on every leg — macOS on both architectures, Linux, a RHEL 9 userland, both build flavours, and the fallback-emitter build — so the contract is checked on every commit, not only when you check it. To re-check it on your own tree:
t=$(mktemp -d) # outside the tree: an output written inside it is crawled by the next run
ripwire . > "$t/a"
ripwire . > "$t/b"
diff -q "$t/a" "$t/b" # two runs, byte-identical
ripwire . --no-cache > "$t/c"
diff -q "$t/a" "$t/c" # and the warm run equals the cold one
Neither diff may report a difference.
9. Verification and quality control
test/regression.sh names every gate script. It is the authoritative list.
The gate scripts hold five contracts that a unit test cannot hold.
-
Two runs over one tree are byte-identical — the determinism gate of section 8, run on every CI leg.
-
A warm run equals a cold run — the cache-transparency gates.
-
Every XML output is well-formed. The verification command is
xmllint --noout. -
The sanitizer build uses
-fno-sanitize-recover=all, without which a run with undefined behaviour still exits 0. AddressSanitizer, UndefinedBehaviorSanitizer and LeakSanitizer are part of the guardrails:cmake -S . -B asan -DRIPWIRE_ASAN=ON && cmake --build asan -j LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./asan/ripwire >/dev/null -
test/argvdiffcheck.shruns a reference binary and the candidate binary over an argument matrix. Standard output, standard error, and the exit code must match for every vector.
Run the full gate suite in the foreground:
python3 test/pargates.py . ./build/ripwire -j 6
A new gate script must be added to test/regression.sh in the same change. The gate
test/manifestcheck.sh enforces this rule.
Another gate derives the cap inventory. The tool has 270 compile-time caps and 7 ranking parameters.
docs/LIMITS.md lists each cap, its value, and whether the file discloses a truncation when the cap
fires, and python3 docs/limits_build.py --check proves that list against src/. docs/TUNING.md
lists the measured cost of each cap.
10. Quality evaluation
ripwire . --quality-panel joins six independent evidence families:
| Family | Question | Supporting command |
|---|---|---|
| Structural | Shape: complexity, size, nesting, parameters, local variables | --metrics |
| Lexical | Identifier text: the naming rules | --lint, --naming-consistency |
| Confusion | Syntactic idiom: the confusion rules | --lint |
| Historical | Git change frequency | --hotspots |
| Colocation | How much code a reader must read outside the current file | --context-ratio |
| State | Mutable state that a change can disturb at a distance | --nonlocal-state |
The panel ranks a row by the count of families that agree. It does not blend the families into one score. A row appears at an agreement count of two or more.
That is the default preset. --quality-panel=strict keeps only the four families measured steadily
enough to gate on, still at two: historical and colocation are out because each is a fixed-size
worst-40 cut over a ranking whose population moves, so both re-shuffle on code that did not change.
--quality-panel=lenient takes all six at one, which is a reading order rather than a verdict. A
family that could not be measured on this tree reports UNAVAILABLE, never “did not fire”.
--quality-delta reports only the properties that the working tree made worse against a baseline.
--test-gate names the tests that must run. --edit-check compares an edited symbol against git
HEAD. --safe-delete=SYM composes the callers, the blast radius, the use sites, and the dead-code
shape into one risk report.
11. Agent integration
The command line is the recommended interface. An agent that can run shell commands needs no registration. The installer adds the skills. The skills teach the agent when to use each command.
ripwire wrap prints the wiring recipe for one agent. The command prints the recipe. It
does not edit a configuration file. Run ripwire wrap --all to detect every installed agent and to
print each recipe. The recipe differs by agent, and wrap decides which interface to recommend: an
agent that can run shell commands gets the command line, and one that cannot gets the MCP server.
ripwire wrap claude
ripwire wrap codex
ripwire wrap cursor
ripwire wrap windsurf
ripwire wrap gemini
ripwire wrap opencode
ripwire wrap openclaw
ripwire wrap hermes
ripwire wrap aider
The write verbs are --replace-symbol-body=SYM, --insert-before-symbol=SYM, and
--insert-after-symbol=SYM. Supply the new text with `–
Install
This server does not publish a one-line install command.
Open the repository installation guideConfiguration
{
"mcpServers": {
"ripwire": { "command": "ripwire", "args": ["--mcp"] }
}
}