RE

redhat-et/ripwire

Developer tools
2.5K stars 158 forks Quality 84 Trend 48Apache-2.0

The 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:

  1. 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.
  2. 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 choice

In the middle, a few prompts hand your agent the whole repository and a job to do:

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.

help wanted · good first issue · all the prompts


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:

  1. Ingest. The tool crawls the tree, parses each file with tree-sitter, and extracts definitions and references.
  2. 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.
  3. Rank. The tool ranks the symbols with Personalized PageRank.
  4. Serialize. The tool writes a minified XML map. The output is deterministic. Section 8 states the determinism rules.
  5. 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

  1. Run the installer:

    RIPWIRE_REPO=redhat-et/ripwire bash -c "$(curl -fsSL https://raw.githubusercontent.com/redhat-et/ripwire/main/scripts/install.sh)"
    
  2. Add the install directory to the path if necessary:

    export PATH="$HOME/.local/bin:$PATH"
    
  3. 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

  1. Clone the repository:

    git clone https://github.com/redhat-et/ripwire.git
    cd ripwire
    
  2. 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.sh instruments, trains and re-optimizes in one command, into build_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.sh
    

    A 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 -j
    

    To work on the tool, build the development tree, with no build type at all:

    cmake -S . -B build
    cmake --build build -j
    
  3. 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 auto or 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 carries amb="K". The header totals the events in ambiguous=.
  • 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= and capped=. A document that a budget cut carries its own marker and a next= 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. --callers counts distinct symbols. --uses counts use sites. The two numbers differ by design.

Use these methods when a disclosure says the answer is incomplete:

  1. Use --skipped to see which files are missing from the index, and which indexed answers it therefore cannot vouch for.
  2. Use --expand=SYM to read the body.
  3. Use --uses=SYM and --impact=SYM to measure the blast radius.
  4. Use --verify="CLAIM" to test one claim about the code: confirmed, refuted, or not-established.
  5. Use --scip=FILE to supply a compiler-grade index. A precise index replaces the name-based edges and marks the affected edges with prov="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:

  1. The crawl sorts all candidate paths before it assigns symbol identifiers.
  2. Every global reduction folds fixed contiguous blocks in index order. No reduction uses an atomic floating-point add.
  3. The rank vector uses type double. Edges stay float; the multiplication and the accumulation promote to double.
  4. The PageRank translation unit compiles without floating-point reassociation: -fno-fast-math, set on src/pagerank.cpp alone.

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.

  1. Two runs over one tree are byte-identical — the determinism gate of section 8, run on every CI leg.

  2. A warm run equals a cold run — the cache-transparency gates.

  3. Every XML output is well-formed. The verification command is xmllint --noout.

  4. 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
    
  5. test/argvdiffcheck.sh runs 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 `–

View the full README on GitHub

Install

This server does not publish a one-line install command.

Open the repository installation guide

Configuration

{ "mcpServers": { "ripwire": { "command": "ripwire", "args": ["--mcp"] } } }