ripwire

redhat-etApache-2.0活跃维护

给编码智能体用的零依赖 C++23 命令行工具和 MCP 服务器,像 ripgrep 一样快速检索代码上下文,还能评估改动影响范围、该跑哪些测试和质量变化。

The ripgrep of AI context: a zero-dependency C++23 CLI + MCP server for coding agents. Find what you want without reading the repo, then check you built what you meant — blast radius, tests-to-run, quality deltas. Signatures at 74.7% fewer bytes than bodies; every guess labelled, every loss published. Paddle out with a map.

v0.6.10发布于 2026-11-08 08:24 UTC2,535star +5 / 7 天172fork同步于 2026-11-10 11:43 UTC

怎么上手

由 README 提炼,执行前请确认
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"
硬件要求按 README 说明

README 未说明显存或 GPU 要求;预编译二进制覆盖 macOS arm64 与 Linux arm64/x86-64(面向 RHEL 8+),Linux x64 与 Windows x64 需支持 AVX2 的 x86-64-v3 CPU。

需要 CUDA
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

给编码 agent 用的零依赖 C++23 CLI 与 MCP 服务器,先出仓库地图再校验改动

给编码智能体用的零依赖 C++23 命令行工具和 MCP 服务器,像 ripgrep 一样快速检索代码上下文,还能评估改动影响范围、该跑哪些测试和质量变化。

核心能力

  • 生成带排序的确定性调用图,指出改动影响面与需跑的测试
  • 用签名代替函数体,字节数比完整函数体少 74.7%
  • edit-check 校验改动是否破坏调用方契约
  • quality-delta 只报告变差的十二类质量指标
  • 安装时自动为检测到的 agent 激活十七个技能

容易踩的坑

安装后 ~/.local/bin 默认不在 PATH 上,需自行加入 shell 配置;Windows 版仍是预览且 exe 未签名,git 历史相关功能需另装 Git for Windows;Intel macOS 只有 0.6.1 及更早提供二进制,后续版本需自行从源码编译。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.6.10
  1. v0.6.10最新正式版2026-11-08 · 2026-11-08 08:24 UTC

    [0.6.10] — 2026-11-08

    Seventy-one merged pull requests since 0.6.9 (#433 to #570). What a user sees (each entry below states its own measurement and its floors):

    • More complete answers: on our fixed in-sample questions (the same 163 everyday, 39 multi-file and 56 large-repository questions, commands and grader as the README answers table, one call each, no cache), everyday answers complete 129 → 132 of 163 (27 partial, 4 none), multi-file 12 → 19 of 39, large repositories 23 → 30 of 56; required items delivered 1,142 → 1,152, 84 → 114 and 148 → 164; no everyday answer stated something wrong (read in source). Answers are larger: everyday answers total 2.37 MB against 1.95 MB (median 10.7 KB against 10.6 KB), multi-file 722 KB against 536 KB, large repositories 1.97 MB against 1.38 MB, so bytes per complete answer rise from 15.1 to 18.0 KB (everyday) and from 60 to 66 KB (large repositories). One everyday question that read complete in 0.6.9 reads none in 0.6.10 (textual-09; hono-17 stays complete under the rule for exports flagged as public API; the README footnote says why). A grader input lost in a cleanup was restored (GRADER-FIX-1107), so the 0.6.9 and 0.6.10 columns are graded by the same scripts. In-sample questions, so progress between releases, not held-out performance; measured on main just before #570 (the --for anchor-choice change), which these figures do not include.
    • Faster and lighter, as percent changes (each figure is that pull request's own measurement against the build just before it, on one machine; the changes were not measured together, and absolute seconds and megabytes come with the 0.7.0 table, measured on one other machine):
      • Warm --deps and --for on large trees use 35 to 67 % less CPU: vscode --deps -67 %, cockroach --for -57 %, envoy --deps -35 % (#542); a warm TypeScript --deps uses a further 22 % less CPU on vscode (#554). Cold runs gain less: instructions -14 % on vscode, -12 % on cockroach, -4 % on envoy.
      • Peak memory: on macOS the peak footprint of an index is about halved, -49 % on envoy, -47 % on cockroach, -48 % on vscode (#497); a warm run's peak is 30 to 40 % lower (-37 % on envoy, -40 % on terraform, -36 % on cpython) and sits at or under the cold run's (#556); the include closure of a large tree is held in an eighth of the memory, vscode's 32.5 MB of sorted vectors becoming 3.74 MB (-88 %, #505).
      • A file the nesting guard refuses no longer makes every warm run rewrite the whole cache: on envoy a 146 MB write per run is gone (#553; user CPU about 10 % lower, inside that measurement's noise).
    • Fewer false edges and more honest answers: --arch, --deps, --cochange, --impact and --export=cc.json stop reporting coupling from edges the index never had; --deps stops calling every deep graph tangled; a "not found" from an index the memory guard cut no longer reads as proof of absence; unused is printed only when the scan can prove it; a --path chain stitched from calls bound by name alone is no longer printed as the plain answer; a Rust prelude call, a macro with no in-tree binding, and a Python call to a builtin or third-party name no longer bind a same-named definition; --for reads confidence="high" on a name-exact route only when the anchor heads the rows, and hedges a second-hop caller reached only by name.
    • --for reaches more of what a task needs: registration rows and the registry key to what is registered under it (Python signals, TypeScript providers, computed-key registrars and their readers); the sites of a literal, a --flag and its default, a table or registration identifier; the family of an add-a-new-X task and the manifest lines that list a file; the doc sections for a prose question about the docs; re-export hops, alias chains and namespace re-exports; both sides of a missing path from A to B; the callers of the one function a sentence asks about; the implementations that can run at an interface call; the match arm that hold
  2. v0.6.92026-10-29 · 2026-10-29 16:28 UTC
    查看更新说明

    ripwire 0.6.9: better answers to the questions agents ask (a --path that prints the bodies it names, call chains and callers that earlier builds left out), and more hedged or declined calls instead of confident wrong ones. This release collects twelve merged batches of reviewed changes (pull requests #424 to #463). The next section shows real before and after output; section 1 is about honesty (hedged or declined calls, floors named, the bodies --path prints); section 2 is the new --defs verb and Owner.name selectors; section 3 is language specifics and callers for error-shaped --for questions. Every item is an entry in the changelog, which states what it was measured on and what it does not show; statements here are in-sample, and this page makes no comparison with any other tool.

    Better answers to the questions agents ask

    Each line is a question from the sets we developed ripwire against (in-sample, not held out), run with the same command on 0.6.8 and 0.6.9; sizes are the whole answer. Eight of these nine are questions whose answer spans several files or runs through indirection: an interface or trait call, a message queue, a renamed import.

    Across files: call chains, callers and the code on the route

    • TypeScript (hono): how does fetch reach compose? 0.6.8 named the hops and printed no code (1,005 B). 0.6.9 also prints the middle hop's body, where the compose( … ) call sits, and names each method's class (Hono.#dispatch; 3,973 B).
    • C (htop): ScreenManager_run to LinuxMachine_scanCPUTime. The hedged route was named without its code; now the three bodies come with it (3,444 to 10,699 B).
    • Go (terraform): the call chain from Context.Plan to Graph.walk. 0.6.8 printed no chain (22,049 B). 0.6.9 appends the six-hop chain, Context.PlanAndEval, plan, planWalk, walk, Graph.Walk, Graph.walk (24,389 B).
    • Rust (nushell): the call chain from main to eval_block_with_early_return when a script file runs. 0.6.8 printed no chain (23,599 B). 0.6.9 appends the five-hop chain through run_file (28,310 B).
    • Go (cockroach): which non-test functions call NewReplicaEvalContext? 0.6.8 named none of the four callers (21,812 B). 0.6.9 appends a callers answer that names all four, with their call lines (24,928 B).

    Through indirection: dispatch, message queues, renamed imports

    • Python (textual): how does post_message reach the on_* handlers? 0.6.8 said "not reached", with no route (3,480 B). 0.6.9 prints a route marked as a hedge (proven="0") through the message queue to _dispatch_message, with the four bodies (13,973 B).
    • Rust (mdbook): what does execute_build_process call? A call on a &dyn Renderer was bound by name to the test stub TestRenderer in the same file (render, name; 1,526 B). Now it is declined and says what could run: <dc n="render" l="207" cands="5" via="declined" gate="iface"/>, five renderers (2,000 B).
    • Python (cpython): who calls re.sub? Calls written from re import sub as re_sub were not counted; now TimeRE.pattern and LocaleTime.__calc_date_time are (73 to 75 callers).

    Unused code

    • Python (textual): which functions in _segment_tools.py have no caller? 0.6.8 printed count="0" (1,025 B). 0.6.9 adds <to n="align_lines">, a function only tests call (1,374 B).

    On the 163 questions we developed against, same command on both versions, graded against our answer key: complete answers went from 112 to 129, partial from 38 to 31, missing from 13 to 3; total bytes per complete answer went from 16,523 to 15,099 (all answers together are 5% larger). One answer moved from complete to partial (ninja-14): 0.6.8 reached it by guessing one of three implementations by name; 0.6.9 shows the route as a hedge instead. Proving which implementation runs is planned work. These are in-sample numbers, not a prediction for other code.

    Read this first

    • Caches are rebuilt once. Every ingest cache and quality
  3. v0.6.82026-10-19 · 2026-10-19 23:43 UTC
    查看更新说明

    ripwire 0.6.8: more complete first answers, and fewer false edges. This release collects four batches of reviewed changes (trains 28 to 31). --for stops when its answer is complete, --callers and --callees serve more rows whole, --impact of a type finds the code that uses it, and the call graph proves more receivers and hedges more guesses, with the guesses labelled. The numbers below say what was measured, on what, and what each one does not show.

    Read this first

    • Caches are rebuilt once. Every ingest cache and quality snapshot an earlier build wrote is refused and re-indexed on first run (parser version 206, cache version 36, snapshot scheme 22).
    • Go: a method's id is now file::Type::method. Quality-baseline rows, .ripwire_quality_acks rows and .ripwire_notes pinned to a Go method stop matching: re-pin them (--notes lists an old note with dangling="1"; --note-add with the same target stores the new id).
    • Scripts that read next=: on a cut --callers answer it is now the next page (the --uses follow-up moved to uses_next=); on a capped --callees page the --expand follow-up moved to expand_next=; on a --for answer with a body fold, <sigs next=> names the first skipped body and the signatures-only call moved to sigs_next=.
    • --for with no --token-budget now stops when it is complete (a 7,500 B target, a 30,000 B guard that says when it fires); an explicit --token-budget is a soft target whose cuts stay recoverable through next=.
    • Git: ripwire now pins repository-configured filter and diff programs to nothing for its own git commands (--doctor reports it). Files behind a repository-local filter (git-crypt, a per-repository LFS driver) are read as stored.
    • --quality-delta=A..B under a memory limit can answer with the kinds it can decide and exit 6 (new) when that partial answer has no gating row; --quality-kinds (new) runs only the kinds you name.

    Measured: the same 173 questions, 0.6.5 then 0.6.7 then 0.6.8

    Each answer is graded two ways: a stricter method (a rubric item counts only when the answer names the item's primary symbol, or at least two of its listed terms) and a more lenient one (any one listed term). A question counts as answered completely when its first answer covers the question's pre-written must-mention list. Only the first answer to each question counts.

    first answer complete, of 1730.6.50.6.70.6.8*
    stricter method50 (about 29%)83 (about 48%)98 (about 57%)
    more lenient method78109127
    the 10 questions from one repository we set aside before any build for this release ran (stricter)256
    answers that claimed a wrong target (mechanical check against pre-listed wrong targets)300

    * The 0.6.8 column was measured on the train-28 build of 0.6.8, the first of the four batches. The later changes in this release (trains 29 to 31) were measured only on their own target questions, in the next section, and are not in these counts. A re-run on the final 0.6.8 build of the 29 questions the train-28 build had answered completely only by the more lenient method: 9 are now complete by the stricter method too, and none of the 29 lost a matched item (the other 144 questions were not re-run). Grading is mechanical and the same machine ran all three builds. A hand read for false claims covered a sample of the 0.6.7 and 0.6.8 answers; the 0.6.5 answers were barely read, so their false-claim count is a lower bound.

    • Since 0.6.7, 15 questions got better and none got worse (stricter method), and the mechanical wrong-target check found no wrong-target answer in either build.
    • By language (stricter method), 0.6.5 then 0.6.7 then 0.6.8: C 5, 12, 13; C++ 6, 9, 10; JavaScript/TypeScript 13, 20, 23; Python 12, 20, 21; Go 4, 6, 8; Ruby 3, 6, 10; Rust 5, 5, 7; Java 2, 5, 6.
    • What it costs: the first answers to the 60 development questions total 356,443 B with 0.6.7 and 427,247 B
  4. v0.6.72026-10-11 · 2026-10-11 23:07 UTC
    查看更新说明

    ripwire 0.6.7: more complete answers, faster calls, fewer bytes. Caller rows now name their call sites, --lego walks the whole subclass tree, and --whereis labels other branches' definitions with the parser. A warm CLI call costs faster, and --whereis is about three times faster: --whereis=lego on this repository takes 4.7 s instead of 13.3 s and returns 18,616 bytes instead of 56,075 (0.6.5 vs 0.6.7, same checkout).

    Read this first: the routing default changed (#381)

    An install with no ~/.ripwire/meter.conf now splits sessions (arm=auto): about half get no routing hints and no SessionStart primer, so the treatment-versus-control comparison has a control group. Put arm=treatment in ~/.ripwire/meter.conf to keep the old behaviour. Prompts starting with <channel or <agent-message are now treated as harness events. Thanks @ahmedibrahim085.

    Complete answers

    • --callers / --safe-delete rows carry sites_at=, the call sites as file:line, so the first answer holds the lines to edit --callers=escapeXml on this repository: the 40 caller rows of its first answer, of 142 callers, carry 79 call lines (0.6.5: none).
    • --lego lists implementors below a direct one (via=, depth=). A two-level formatter hierarchy fixture: 5 of 5 implementors (was 2).
    • --whereis labels another ref's rows by parsing the blob. --whereis=escapeXml on this repository: 2 rows, both the declaration (was 60 rows, 54 of them call sites).
    • --for gives the function a question names its own call-hop row. On 32 --for questions over 8 public repos, 8 answers gain it and 23 are byte-identical (+1.4% bytes in total).
    • The default map lists directories and entry points it used to cut. tmux input.c, key-bindings.c and main now appear (+7.0% median bytes).
    • --impact rolls its reach up per file. tmux --impact=grid_set_cell names input.c and window-client.c (was neither).

    Faster

    • A warm --callers call on a 3,100-file snapshot went from 319 to 269 ms, every answer byte-identical (about 8–10% on this repository against 0.6.5).
    • --whereis on a checkout with 139 local branches: 12.3 s → 3.7 s; --whereis=lego on this repository's 596 refs: 13.3 s → 4.7 s (vs 0.6.5).
    • Memory, plainly: --whereis now parses other branches' files to tell definitions from call sites, so its peak is about 1.0 GB, up from 0.6 GB in 0.6.5 (--whereis=lego, 596 refs). Within this release it was cut from 1.42 GB by reading back only the files it parses.

    Smaller answers, same rows

    • --whereis=lego: 56,075 → 18,616 B against 0.6.5 (129,314 B mid-cycle), every row kept. A long line's t= is a 200-byte preview, and next= replays it whole.
    • --expand / --outline drop the ride-along map once every definition is served. --expand=subtokens: 26,791 → 5,317 B.
    • --grep=split-window on tmux: 16,108 → 5,453 B, with the defining line first.
    • A clean-tree --quality-delta prints its verdict, not its acks: 14,594 → 978 B on this repository.

    Honest answers

    • A C++ call named like a standard member (empty, size, find, …) binds no in-tree namesake unless a rule ties its receiver to one. On this repository's map, top-20 rows carrying such a name: 14 → 0.
    • A call bound by name alone keeps its rows and says so (via="name"), and typed receivers resolve. Cost: +2.04% bytes over 210 calls on eight public repositories.
    • The cross-branch verbs, --whereis and the git history walk refuse or disclose when git could not be read. They no longer answer hits="0" or a silent empty churn.
    • A compact --for answer fits an explicit --token-budget wherever the full dialect does, and says why where it cannot.
    • --seams no longer reports doc-to-doc links as untested seams. They are counted in doc_links= (53 on this repository).

    Imports and languages

    • JS/TS and Go import edges come from the shared @import.path capture (#358), with output byte-identical. The C-family slice is from @lennix1337 (#368).
    • Ruby calls r
  5. v0.6.52026-09-27 · 2026-09-27 12:20 UTC
    查看更新说明

    ripwire 0.6.5 — TypeScript alias imports resolve, and fixes from Windows testers. Imports through a tsconfig alias, baseUrl or a workspace package are now real edges, so --deps, --arch, --impact and the call graph see them, and a config ripwire could not read is disclosed rather than guessed (thanks @srinchow). On Windows, --doctor searches PATH as Windows does and no longer calls a byte-identical copy stale; two ripwire builds on one tree no longer re-parse on every run (thanks @elsRobin, @lennix1337 and @antoniojosedev for testing). --doctor's PATH hint can be pasted as printed. --for lifts a symbol the task names verbatim. In Python, JS/TS and Ruby, a method call on a dict or map binds to an in-repo method of that name only when the calling file names its class (or one in its inheritance cone); otherwise it is declined and counted (declined_calls=).

    Changelog and translated-README sections also rank after code-adjacent docs in --for.

    Full notes: CHANGELOG.md.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    CI Release Licence Standard Runtime dependencies Slides

    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.

    For a single developer, this tool is a good lookup accelerator. For an orchestrated fleet, it's load-bearing: it halved the research spend, twice redirected tasks before wasted work, prevented at least one silent-divergence shipped bug, and turned code-quality hygiene from a hope into a per-task mechanical gate. Whole-workflow ~2×; per-lookup 10–25×; and two moments where one call was worth more than the rest of the session's tooling combined.

    — the report's bottom line

    Field report: what ripwire contributed to a large multi-agent coding engagement

    Context, genericized: one orchestrating session directing ~20 sequential/parallel coding agents over a ~1,500-file C++/Metal codebase across two days — a deep architecture audit, then a 14-task feature wave (new subsystems, measurement infrastructure, a search-archive migration), ending in a verified all-on release flip. Every agent was instructed to lead with ripwire for orientation and to close with its quality gates.

    The headline numbers

    • Roughly half the total token spend of the audit/research phase, saved. This is the operator's whole-phase estimate — it includes everything the research agents did, ordinary file reads and all, which makes it conservative rather than cherry-picked. The per-call factors underneath are much steeper: a single doc-recall call served the relevant sections of a 164KB planning document in ~6K tokens (~25×), and agents that led with the tool ran ~30–40% leaner on tool-call counts than agents doing raw read fan-outs over comparable questions.
    • Two tasks had their direction changed by a single call. One task was chartered to activate a steering behavior the team believed was in production; one --callers query returned zero production callers, redirecting the task to the real gap (the data provider that behavior needed had never been wired anywhere). Another task refactored a widely-shared computation; --edit-check flagged 5 of 6 call sites as incompatible — sites a text search had missed — that would otherwise have silently diverged from the canonical path the day the feature was enabled, surfacing months later as an unexplainable visual bug.
    • A dozen-plus real code-quality defects fixed, not waived, across ~15 implementing agents — all caught by --quality-delta at each agent's "I think I'm done" moment: a 480-token duplicated routine, a fourth private copy of a shared RNG utility, a hand-duplicated cost function, a pair of near-identical functions with a flipped sign (correct at one boundary, quietly wrong at the other), test-fixture duplication across sibling suites, and functions that had quietly absorbed a second job. None of these would have failed a test; all of them are the sediment that rots a codebase under high-velocity multi-agent development. The gate made removing them routine instead of heroic.

    Where the value concentrated

    1. Orientation and recall (the token win). Ranked-signature task orientation and section-granular document recall meant agents started from answers, not file dumps. Recall over planning docs was the orchestrator's single most-used verb — the audit's entire framing came from two calls.
    2. The contract checker (the shipped-bug preventions). Beyond the 5-of-6 catch above, --edit-check gave near-free proof-of-contract on every public-symbol change across the wave — the kind of verification that otherwise simply doesn't happen at agent speed.
    3. The quality gate as a protocol. The measurable effect wasn't any single catch — it was that fifteen different agents, none sharing context, all converged on the same "fix or justify with a written reason" discipline, with an acknowledgments ledger that survived across tasks. Unattended orchestration usually leaks quality; here the leak-check was mechanized.
    4. Persistent notes. Agents left gotcha notes pinned to symbols mid-wave (a caller-wiring law, an arming-rule invariant), which later agents' lookups surfaced automatically — cheap institutional memory between contexts that never met.

    The honest boundary

    • The engagement's deepest findings did not come from the tool. A structural capacity ceiling, a mis-derived constant, a floating-point re-association drift of 1 ulp, and a seed-keying bug were all found by bespoke measurement the agents built — baseline worktree diffs over full data fills, multi-arm attribution sweeps, funnels. The tool is a floor for honesty and orientation, not a substitute for measurement design.
    • Known false-positive mode, handled by its own documentation: the contract checker's arity heuristic over-counts on defaulted trailing parameters (one task saw "incompatible=18" that were all fine). The tool's own trust-calibration notes say to verify by compiling in exactly this case, and agents that followed them lost nothing.
    • For broad, common-word conceptual queries, plain grep-and-read still occasionally won — consistent with the tool's own guidance that it shines on specific technical asks.

    Bottom line

    For a single developer, this tool is a good lookup accelerator. For an orchestrated fleet, it's load-bearing: it halved the research spend, twice redirected tasks before wasted work, prevented at least one silent-divergence shipped bug, and turned code-quality hygiene from a hope into a per-task mechanical gate. Whole-workflow ~2×; per-lookup 10–25×; and two moments where one call was worth more than the rest of the session's tooling combined.

    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.

    Beside those sits a labelled survey of 237 tools that contributed nothing and says so. The two sets are disjoint by construction, so they add rather than nest — a tool that gave a lesson is never counted twice.

    Both halves are load-bearing, and they are doing different jobs. The settled results are what make the quality lens trustworthy: McCabe on complexity (1976), Halstead on volume (1977), Spärck Jones on term specificity (1972), Nagappan & Ball on churn. Fifty years of replication means those are not opinions, and a tool that measures your code should be built on the ones that survived.

    The recent work is what makes it current: seventeen of the folded papers are from 2026, seven published in the last two months and three in the last thirty days (dates as of 2026-09-08; every row carries its arXiv id, so the claim is checkable rather than atmospheric). Retrieval for coding agents, context-compression cost, placebo-controlled localization — that literature is months old, not decades, and several rows were folded within weeks of the paper appearing.

    Neither half alone would be enough. A tool built only on the classics would not know what an agent needs; one built only on last month's preprints would have nothing underneath it. And the newest row is a result that failed when it was tested here — which is the point of writing them down. All three counts are re-derived from that document's own tables by test/readmedriftcheck.sh on every run, which fails if this page and those tables disagree, so the claim cannot quietly drift. The row-by-row ledger is docs/LINEAGE.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.10 — more complete --for answers, warm runs on large repositories that use 35 to 67 % less CPU, and about half the peak memory of an index on macOS. Release notes · the presentation · the changelog — with thanks to the contributors named there.


    Measured on 48 matched questions across django, webpack and this repository. Across all three, ripwire indexes in 0.25–0.45 s and 6.6–16.5 MB against that server's 23–52 s and 391–623 MB. The full method, the wins named one by one and the losses included, is in Against the leading graph-database code-context MCP server and docs/EVALS.md.

    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. Install it and ask it something before you finish reading this page:

    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="<the change you are about to make, in words>"
    

    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.

    Two things make that reachable in practice, and neither is the destination. Answers are honest about their own limits — a count that cannot be a total is labelled a floor, a zero means "none found" and never "none exists", every truncation is disclosed — so an answer never looks more complete than it is, and the map never degrades the code by guessing. And an answer can be given a token budget, so what one costs is something you ask for rather than discover; where a complete answer will not fit, it says it went over rather than silently dropping the row you needed.

    Those two are the stair-steps: honest about what is missing, priced in what it spends. The step they climb toward is a question fully answered in one call, which is not always trivial to reach — and where it is not, the output says so rather than pretending otherwise.

    How we measure the climb. A complete answer to every code question is genuinely hard, and it is reached over many releases rather than in one. Until an answer can be complete, it owes you three things: it stays bounded in size, it is fully honest about what it does not know, and it gives you a clue where to look next. So every change is judged on more than "was it right":

    • Complete answers: the share of questions answered so you can act without re-checking.
    • Honest-partial rate: how often an incomplete answer says what it is missing, instead of sounding confident.
    • False-confidence rate: wrong answers that claim to be complete — the worst case, held near zero.
    • Next-clue usefulness: for partial and wrong answers, whether following the answer's first suggested next step reaches what the question needed, within one hop. Graded blind.
    • Recoverability: for every cut answer, whether following its next= returns everything that was cut.
    • Tokens per complete answer: total tokens an agent spends across a scenario, divided by the questions it got fully answered.
    • Cut-row recoveries: how often a needed item arrived only through a continuation.

    Answering comes first; efficiency is how we get there. We aim for answers about as lean as the leanest tools, but a byte budget is never allowed to cost an answer. We have gone too far toward short answers before and cut information that was needed, so now every size limit has one of two jobs: a runaway guard far above typical answers (it catches an output bug, and says so when it trips), or a stair-step target where anything over it must be explained. A capped answer is always measured against the same answer uncapped, and the one that answers better wins.

    A cut is only honest if you can get the rest back. Disclosing a cut is the floor, not the bar: a capped answer plus the next= it prints must return everything the uncapped answer had. A test that checks this for every budget option on every command is being built, so that "the answer was cut" always comes with "and here is the rest".

    What a budget may spend first. When a budget is tight, ripwire drops bytes that carry no answer before it drops answers: repeated legend text, duplicated paths, bodies whose signature already says what you need. Only then the lowest-ranked rows, each counted. A whole section never disappears without a marker.

    Budgets are yours to set, never ours to impose. The defaults and the setups ripwire wrap writes carry no budget that cuts a typical answer; a budget is a choice the caller makes, with its cost disclosed.

    Efficiency is counted per complete answer, across the whole task. Bytes per answer can fall while the task gets more expensive, if the agent then has to ask again. So the efficiency figure is tokens per complete answer over a whole scenario — orient, then the questions that follow — not bytes per call.

    Ranking is the tool's best guess; the next= is the safety net — and we measure how often the net is needed. ripwire cannot always know which row answers the question; it goes by ranking. When a needed item appears only after following an answer's next=, the honesty layer saved the answer but the ranking put that item below the cut. We count those per round:

    • Recovered through next=: the item was cut but recoverable — one extra call. Driving this down is ranking work.
    • Lost: the item was cut with no marker or no working next= — a bug, fixed first.

    Same answer, a fraction of the tokens — read this table first if your agent is on a budget

    Ten everyday moments, re-measured on this repository, 2026-08-08. Figures are ~tokens (≈ bytes/4); every ratio comes from a real run, reproduced by the command in its row — raw byte counts and exact commands in docs/EVALS.md §5.

    Ordered understand → navigate → review-the-change:

    Ask itCommandripwirenaive readtoken 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 <term> src/, then read the file it points at2.3×–9.3×
    "What do I already know?"ripwire . --recall="…"~15K tok~445K tok — read all 119 markdown docs this repo carries29.2×
    "Set me up for this task"ripwire . --pack-task="…"~2.1K tok~16K–80K tok — read every relevant file, whole7.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 in2.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 mentions69.2×–89.1×
    "Is it safe to change this?"ripwire . --impact=SYM + --uses=SYM~1.3K tok~18K tok — open every direct-use file, whole14.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 <syms> test/, then open the candidates7.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 files2.6×–27.5×

    These aren't summaries that gamble with information. Each row is scored same-correct-answer-or-it-doesn't-count, and both sides were checked, not assumed: orient surfaces this repo's own pipeline files (ingest.cpp, graph.h, serialize.h) in the first screen, the same three docs/ARCHITECTURE.md names as central; the --for row lands mcpStale (src/mcpindex.h:633), the actual staleness check, 5th-ranked; --recall lands the container-rule doc (AGENTS.md) that states, verbatim, the same "no std::map" rule CONTRIBUTING.md explains in full; --pack-task names the same three touch points a human would — cachelint.h, mergeCachePack (src/main.cpp:1787), the lintrules.h helpers it reuses; --expand hands back the requested function's complete, unmodified body — the ranked-neighborhood addition costs the same ~22.6 KB regardless of which function you ask for, confirmed on two (a fixed floor, not per-function variance); --callers on langOfPath names its 2 real callers, the same ones a grep hit-list buries under 5 files of comment-only mentions; --impact+--uses on coversOrEquals names the same 2 direct call sites --uses alone would, plus (disclosed) a transitive reach --uses doesn't cover at all; --from-trace resolves all 7 frames of a real call chain by name to the same definitions a per-frame grep would eventually find, mixed with call sites and comments; --situ on a 2-file diff names the same 6 real test harnesses, 2 of which a filename grep across test/ cannot find even after opening every one of its 41 candidates — a completeness gap, not just a byte one; --pr-context surfaces co-change partners (test/regression.sh, src/main.cpp) a raw git diff has no way to know were usually touched and weren't this time — Fowler's Shotgun Surgery checked rather than merely named (the backtest is in docs/EVALS.md). The map ranks and discloses — it never paraphrases your code — and every truncation is disclosed in the header.

    The honesty line, made concrete: the same auto-selection behind the --expand row also runs the other way. On a small file (pageRankDouble in src/pagerank.cpp, 5,559 B) the ranked bundle would cost 27,916 B — nearly 5× more than the file — so ripwire serves the file itself instead, disclosed as mode="whole-file" on the response, not silently. docs/EVALS.md §7 lists that and the other counterexamples this project publishes against itself.

    The shape: plan the work, then run a loop that matches each task to the model that fits it. Every thread it spawns opens with an empty context on a repository it has never seen. Orienting an empty context to a large repository is the most repeated cost in the whole system, and the one this tool was built for — it is also the cost that grows with the size of the tree, which is why the pattern matters more the bigger the repository gets.

    --for and --pack-task answer it in a single call, at the per-call rates in the table above, instead of a grep-and-read tour that every lane pays over again from scratch.

    The return path matters as much. --quality-delta, --test-gate and --edit-check answer what did I make worse, which tests must run, did I change a contract — quantitative answers a lane can hand back as a verdict, rather than a transcript the orchestrator has to read to find out what happened.

    What is and is not claimed here. Every figure on this page is a single-agent measurement. That the saving compounds with the number of cold orientations follows from the fixed-cost mechanism, but it is pre-registered and unrun — the reason the pattern is worth trying, not a result this project has published.

    See the map — not just the numbers

    ripwire path/to/django/db/migrations --rank-by=rrf --top-k=120 --color-by=cx --html=map.html
    

    One self-contained HTML file (--html[=FILE]), no server, no CDN, no external asset. --color-by=lang|community|cx|churn|tested sets the initial colour; the page embeds all five and keeps a live selector, so switching lens costs no second run.

    Read from the figures above, which state their own rules in a sidecar saved beside each image:

    • arrow points caller → callee — the graph is directed, and the page draws it that way.
    • 31 of 183 shafts dashed in this view = the resolver could not choose between same-name definitions and split the call over all of them — per edge, not per symbol. A symbol-level "this function makes some ambiguous calls" would mark every one of its edges, which would be a lie about most of them.
    • labels: top 24 by in-view degree, one per name — one label per distinct name, so a picture of a container class stops crowding out the functions you asked about.
    • shapes: ● fn ■ cls ✚ var — kind is nominal data on a nominal channel; complexity never uses shape.
    • module outlines: 7 of 12 modules with 3+ nodes in view (cap 12; 3 dropped as too thin to read as a region; 2 dropped as enclosing mostly other modules) — three separate truncations, each with its own count and its own reason.

    The cx and churn ramps share one five-stop scale, ordered so lightness rises with the value — it survives greyscale printing, and every adjacent pair stays separable under protanopia, deuteranopia and tritanopia. Thresholds are fixed rather than per-corpus quantiles, so a hot node cannot be manufactured by a cold repository.

    churn needs real git history: a shallow clone reports every file as one commit, and a directory with no repository says churn unavailable rather than drawing zeros.

    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 ranked symbols, in rank order — the cache-header constant kCacheMagic first (with its doc comment quoted in place and the one next= call that opens it), then spanTierMemoPath (the cache-path composer), ingestCommitTree, … ingest — each row with its file, line, and signature.
    • Risk, annotated in place — complexity, git churn (ingest shows 128 recent edits), change amplification (touch ingest and 266 graph nodes feel it), purity and test coverage. The fragile spots are visible before anything touches them.
    • One-hop call context — spanTierMemoPath calls shaKeyedCachePath, headSnapRepoHex, exclConfigHex; no second query needed to see the neighbourhood.
    • Its own confidence — this answer says confidence="high" with the score margin attached; a flat ranking says low, so it reads as a starting point instead of masquerading as an answer. confidence= measures how clearly the ranking separates its head from the rest, not whether the head is what you meant: ask a repository about a concept it does not contain and the best lexical matches still rank, confidently. Phrase the task in your code's own words.
    <ctx task="incremental cache invalidation" confidence="high" margin_pct="20"
         bundle="compact" bodies="0" reason="compact-route" est_tokens="3995">
      <sigs shown="23" total="40" capped="1">
        <d l="106" n="kCacheMagic" p="src/ingest_cache.h" cx="0" in="0" churn="11" amp="71" pure="1" r="1"
           next="--expand=src/ingest_cache.h:kCacheMagic">
          <doc>incremental cache (--cache): per-file content hash + raw facts so a
               re-run re-parses ONLY …</doc>constexpr std::uint32_t kCacheMagic = …</d>
        <d l="1307" n="spanTierMemoPath" p="src/ingest_astquery.h" cx="1" in="2" churn="5" amp="44" r="2"> … </d>
        <d l="247" n="ingestCommitTree" p="src/dmm.h" cx="6" in="1" churn="6" amp="27" r="3"> … </d>
        …
        <d l="191" n="ingest" p="src/ingest.cpp" cx="4" in="14" churn="128" amp="266" tested="1" r="13"> … </d>
        … </sigs>
      <hops shown="2" total="6" capped="1" noedge="2">
        <h l="1307" p="src/ingest_astquery.h" n="spanTierMemoPath">
          <calls total="3"><c n="shaKeyedCachePath" l="1621"/> … </calls></h> … </hops>
    </ctx>
    

    cx= complexity, churn= git edit frequency, amp= change amplification, r= rank; <hops> rows carry the one-hop call context, caps disclosed. The one legend at the top of the real output defines them — tersely by default, every reading in full with --legend=full — and the root self-reports the bundle's cost — est_tokens="3995" here.

    The agent without a mapThe agent with ripwire
    greps a common word, gets hundreds of hits across dozens of filesone ranked answer — est_tokens="3995" on this repository (re-derived 2026-09-05, the run above)
    reads whole files to find the symbols that matterthose symbols, with complexity, churn and test coverage inline
    finds the callers only if it thinks to grep for them toocallers, blast radius and the tests to run, in the same bundle
    pays for every line it read, right or wrong5.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.

    And against five retrieval competitors on a held-out LocBench slice, it finds all gold files in the top 10 on 58.3% of instances — the best alternative lands 40.0% — while indexing in 0.31 s. The full leaderboard, losses included ↓

    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.

    The 48 questions span symbol lookup, conceptual search, blast radius, and one-call task orientation. ripwire indexes the same three repositories in 0.25–0.45 s and 6.6–16.5 MB, against that server's 23–52 s and 391–623 MB, and answers a warm query in a median 197 ms against its 1,082 ms. Its seven wins are real and named one by one in the method.

    Both arms warm with a pre-built index, median of 3 timed calls, stdout to a file rather than a pipe. The competitor ran in its stronger retrieval configuration; its numbers were recorded once and then frozen, and ripwire's side was re-run after the fixes the first pass produced. Per class, as a share of the competitor's bytes on totals: symbol lookup 1.35×, conceptual search 1.23×, blast radius 0.39×, task orientation 0.06×.

    Where it loses: on a plain one-symbol lookup the competitor answers in about a kilobyte carrying callers and callees, and ripwire spends roughly three times that to also hand back the body. It ranks a chunk-id plugin first on one webpack query where ripwire never surfaces the directory at all — a ranking miss, and a fix for it was built, met its pre-registered band, and was reverted anyway for failing a separate standing requirement. Its depth-labelled blast radius and its import edges are both better presentations than ripwire's flat reaching-set.

    Full method, pins, per-class tables, the carried-versus-re-judged ledger, and the complete list of what the competitor does better: docs/EVALS.md §2.

    Nothing it is unsure about reaches your agent unlabelled — and nothing it could not see goes unnamed. Every guess is marked in the output, and every mark has a next step — up to handing it a compiler-grade index. Point it at a repository whose main language it has no grammar for and the map's first line says so (unindexed="ml:793,mli:607,…" on a facebook/infer clone); a file it indexed but cannot vouch for carries a parse-health row; every file the crawl passed over is itemized with its reason. A confident-looking map that lies by omission is the failure mode this tool refuses.

    Graph-Ranked Retrieval: It finds the right files more often than the alternatives

    58.3% against 40.0% for the best tool tested — and it answers before they finish indexing. Every arm below was re-run in full on 2026-08-08 — one ripwire binary (the profile-guided release build that now ships), one evaluator, one 60-instance held-out LocBench slice: paired, zero exclusions, same gold set, and the metric code imported unmodified into every arm. Strict file@10 = all gold files inside the top 10, which is whether your agent starts in the right place at all.

    Round 4 — LocBench, Python-dominantstrict file@10any@10index (median)query (median)
    ripwire --for58.3%85.0%0.31 s0.108 s
    codebase-memory-mcp 0.9.040.0%63.3%1.24 s0.075 s
    repowise 0.37.033.3%53.3%34.0 s1.159 s
    graphify 0.9.3431.7%46.7%7.82 s0.614 s
    Aider repo-map 0.86.220.0%35.0%(inside query)2.920 s
    codeseek 0.1.31 (better of its two arms)15.0%20.0%3.37 s0.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.

    Ripwire leads every arm on both accuracy metrics and in both strata. Paired, the losses are small and they are published: 2 instances to codebase-memory-mcp, 2 to repowise, 1 each to graphify and aider. Cold from nothing to an answer — parse, rank, reply, no cache — ripwire takes 0.213 s, against a ~35 s index-then-query for repowise; its worst single index in this run was 352 s.

    Three things this table costs us, said plainly. codebase-memory-mcp is the real runner-up at 40.0%, not repowise — an earlier round credited it with 26.7%, and re-running it fairly raised it. The margin over the best competitor is therefore 1.46×, not the 1.75× two separately-dated tables used to imply. And multi-file gold is hard for everyone: ripwire leads the stratum at 21.4% strict, but its own any@10 there is 78.6% — it finds a gold file and misses the siblings, and no arm in this table solves that.

    Held out wider — 243 instances across 78 repositories — ripwire lands 60.9% against 27.6% for its own pre-routing baseline: +33.3pp paired, clustered-bootstrap 95% lower bound +25.0pp, bought for +3.4% warm latency and −39.4% tokens. Full provenance, the losing instances one by one, and a third round against a compression-layer competitor: Measured and bench/headtohead/r4-2026-08-06/, whose harness is committed so anyone can re-run the whole comparison.

    Test scaffolding does not pollute the ranking from inside source files either: #[cfg(test)] mod tests, describe() blocks, Test* classes and [Fact] attributes are detected syntactically, wherever they live — not by file path alone. On an astral-sh/ruff clone (5,945 files), --ignore-tests removes 23,907 test symbols where path rules alone caught 18,532 (ripwire <ruff> --ignore-tests, 2026-08-14; the per-language fixtures are pinned by test/testscopecheck.sh).


    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.

    A Survey of the Neighbourhood's Eligible Instruments — being an account of five gentlemen of retrieval, and one lady of no pretension whatsoever.

    It is a truth universally acknowledged, that an engineer in possession of a large repository must be in want of a map.

    Mrs. Codebase-Memory must be named first, for she has risen a great deal in the estimation of the neighbourhood — two-fifths of her answers entirely correct, which is more than any other caller can say, and she is ready in a second and a quarter. It must nevertheless be recorded that her card announces accomplishments in the semantic line; that upon enquiry the semantic line is not at home; and that the household denies all knowledge of it. One is left with the impression of a capable woman ill-served by whoever prints her cards.

    Mr. Repowise is by common consent the most substantial of the party, and no one who has waited upon him would dispute it. He is possessed of a handsome index and a manner of great thoroughness; but he must be seen to. One does not simply address Mr. Repowise. One sends word, and dresses, and waits — three-and-thirty seconds on an ordinary morning, and upon one memorable occasion in the country, seven minutes and four seconds — during which interval a less consequential neighbour has answered the question, taken her leave, and thought no more about it. He answers creditably when at last he arrives, one time in three; whether that is worth the toilette, each family must determine for itself.

    Mr. Graphify enjoys a great many admirers. He does not rank his acquaintances; he calls upon them in whatever order his walk happens to take him, and reports the order of the walk as though it were an opinion. He has been known to arrive carrying a hundred and thirty megabytes of correspondence. Pressed once for any answer at all, he replied that no matching nodes were found, and considered the matter closed.

    Mr. Aider is the most gentlemanly of the company and by far the most difficult to consult. He cannot be asked a question — the thing is simply not done. One may mention names in his hearing and hope he takes the hint; he does take it, and is fully ten points the better for it, which says more about the hint than about Mr. Aider. But he forms his view of the neighbourhood before you speak and retains it after, and one cannot escape the feeling that the conversation was never truly with you.

    Mr. Codeseek is a young gentleman of quick habits who suffers from an affliction of address. Speak to him plainly, in the language of ordinary complaint, and he will regard you with perfect composure and say nothing whatever — nothing, upon sixty occasions out of sixty. Name a person precisely as that person is named, and he grows animated directly. It is not stupidity; it is a want of imagination in the matter of introductions.

    And there is ripwire, of whom nothing is said in the drawing rooms, because she has already gone home. She was asked; she answered, in thirteen hundredths of a second; every gold file within the first ten, in eight-and-fifty cases of the hundred. She keeps no establishment, corresponds with no distant authority, and has never once been indexed at a party. Mr. Repowise finds her abrupt.

    She is.

    Every figure above is a measured number from the round-4 table on this page: the index medians (1.24 s, 34.0 s, 3.37 s) and repowise's 352 s worst case, the 40.0% and 33.3% strict file@10, the 0-results-on-60/60 fallback arm, the absent semantic_query tool, graphify's 129 MB largest graph and its 1-of-60 empty ranking, aider's +10 pp personalization delta, and ripwire's 0.108 s / 58.3%. Provenance in bench/headtohead/r4-2026-08-06/ and docs/EVALS.md; only the manners are editorial. These are other people's real work, and the joke is aimed at the trade-offs, never at the authors.

    The Long Index — in which a man asks six informants one simple question, and only one of them has the decency to answer it.

    It was a million lines of somebody else's mistakes, and I needed one file out of it before the coffee went cold. So I did what you do. I went and talked to the people who say they know the neighborhood.

    Codebase-Memory had the best record in the room and she knew it — two answers right out of every five, handed over in a second and a quarter, which in this business is practically a kindness. Trouble was the card. Right under her name it said semantic query, real classy, real expensive-looking. I asked to see it. She said it wasn't in. I asked the house. The house had never heard of it. I've known a lot of good people ruined by whoever printed their cards.

    Repowise was the heavyweight — everybody told me so before I got through the door. Big index, good tailoring, thorough as a tax man. Only you don't just ask Repowise a question. You send word. You wait. Thirty-three seconds on a good day, and one bad morning out in the country, seven minutes and four seconds — long enough to get the same answer somewhere else, drive home, and forget his name. He came through one time in three. For some outfits that's worth the wait. I've got a metabolism.

    Graphify never met a fact he wouldn't hand you. Ask him one thing and he turns up with a hundred and twenty-nine megabytes of everything, unsorted, in whatever order he tripped over it — and he'll report that order like it's a considered opinion. It isn't. Leaned on him once for anything at all; he looked me dead in the eye, said no matching nodes, and figured we were square.

    Aider was a gentleman, which is another way of saying you couldn't file a straight question into him in triplicate. Wouldn't be asked. You mention things, loud, and hope — and sure enough, drop the right names and he's ten points sharper, which tells you everything about the names and nothing about Aider. He'd made up his mind about the place before I opened mine, and kept it after. You never did feel the conversation was with you.

    Codeseek was young and had a condition. Talk to him like a human being — plain, tired, the way a man actually asks for help — and he'll look clean through you and say nothing. Sixty times out of sixty, nothing. But name the thing exactly, badge number and all, and the kid lights right up. It isn't that he's slow. He just never learned how people knock on a door.

    And ripwire. Nobody at the table brought her up, on account of she'd already left. Took the question, answered it in thirteen hundredths of a second — every file I needed inside the first ten, fifty-eight times out of a hundred — keeps no office, wires no head branch, never once got herself indexed at a party. Repowise says she's abrupt.

    She is. That's why I hired her.

    Every figure above is a measured number from the round-4 table on this page: the index medians (1.24 s, 3.37 s, 34.0 s), repowise's 352 s / six-minute worst case, the 40.0% and 33.3% strict file@10, codeseek's 0-of-60 plain-language arm, codebase-memory's advertised-but-absent semantic tool, graphify's 129 MB largest graph and its 1-in-60 empty return, aider's +10 pp name-drop delta, and ripwire's 0.108 s / 58.3%. Provenance in bench/headtohead/r4-2026-08-06/ and docs/EVALS.md; only the cynicism is editorial. These are other people's real work, and the joke is aimed at the trade-offs, never the authors.


    Name a symbol and it is the first hit — and it is never a mystery which ranker answered. Every --for query is served by one of three lanes; a confidence-gated router picks by reading the query's shape, discloses its choice on the output (route=), and --no-route overrides it. Routing lifts recall@1 on name-shaped queries 61.1% → 91.3% in src/, 59.2% → 85.5% at the repository root — and the gate is load-bearing in both directions: route everything to the name lane and prose queries collapse from 0.967 MRR to 0.016. Both numbers ship together. Reproduce with ripwire <dir> --eval-retrieval, which grades EVERY doc-commented symbol in the corpus and prints its own population=/scored=/rule=; the full per-ranker tables are in bench/ANSWERQUALITY.md.

    LaneBuilt forWhy it wins thereWhere it loses
    name-exactidentifier-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+bodyprose and task queries ("where is the content hash computed")the only lane that matches vocabulary living in doc comments and bodiesexact names drown in shared subtokens (61.1% recall@1 on name queries)
    mention anchora pasted path, Type.method, or issue URLa literal mention is lifted above any score — paste the ticket, don't paraphrase itadds nothing when the query names no artifact

    How the conceptual lane finds what you didn't name. The subtoken+body lane is why a query with no symbol name in it still lands:

    • Both sides are split into subtokens. SplitChunksPlugin becomes split+chunks+plugin, and so does your query — so words match pieces of names you never typed.
    • Three evidence fields, not one. A symbol is scored on its name subtokens, its doc comment, and its body — vocabulary that only exists in a comment or an implementation still finds its symbol.
    • BM25 with per-query IDF. Rare, discriminating words dominate the score; words the whole corpus shares contribute almost nothing. Type the three words only the right function uses and they carry the query.
    • Lookalikes are down-weighted, not hidden. Fixture, test-data, and generated paths score at a fraction, so a test vocabulary-twin cannot outrank the real source (adversarial-class pollution@5: 28% → 0%, docs/EVALS.md §4) — but they stay in the index and are still found when asked for.
    • The list ends at a cliff, not a quota. The cut is adaptive: output stops where the scores drop off, so a sharp answer is a short list and a diffuse one is disclosed as such, instead of a fixed top-k padding both.

    How the router picks. The gate is built on cheap, corpus-derived evidence, and its bias reflects an asymmetry the table above makes plain: a missed name-route costs a few ranks; a false one is catastrophic (0.016 MRR).

    1. Identifier shape is trusted outright. A camelCase/snake token — or a short query carrying one — routes name-exact. Someone who types chooseForRanker is naming, not describing.
    2. The all-words test. If every content word equals some symbol's whole name (pack task where both pack and task are real symbols), that is strong evidence of a name query.
    3. The plausibility test. Present is not enough — each matched name must be specific: few definitions, and not a subtoken carried by half the corpus's symbol names (thresholds derived from the index itself, not a hardcoded stdlib list). This is what catches split chunks: every word names a symbol, but split names a String method defined everywhere — so the route is declined and the conceptual lane runs, which finds SplitChunksPlugin easily.
    4. Every decision is disclosed. route= states the lane that ran; a decline names the anchor that failed and why. --no-route forces the conceptual lane when you disagree.

    The proof the gate earns its keep: the routed lane matches the best single lane on both query modes simultaneously — 0.960 MRR on names (equal to forced name-exact) and 0.967 on prose (within noise of pure subtoken+body, src/). No single lane does that.

    The honest boundary: the router classifies the query's shape — it cannot rescue vocabulary that is not in the index. A prose query whose concept lives only in a compound class name (SplitChunksPlugin contains no splits subtoken) is correctly sent to the conceptual lane, which then has little to grab; that gap is measured and recorded in docs/EVALS.md §7, not hidden. Numbers re-derived 2026-08-08 on this tree; per-lane table and history in docs/EVALS.md §4.

    Saves Tokens: It answers for a fraction of the context

    On mid-task questions it had never seen, ripwire answers at 5.0% of what a grep-and-read pass spends — 5.2% on the questions both arms fully answered. --pack-signatures returns 74.7% fewer bytes than full bodies at top-50 (re-derived on this tree, 2026-09-06). The output is already dense enough that running a dedicated context compressor over it saved exactly 0 tokens.

    Both of those first two figures moved when they were re-derived on 2026-08-23, and they moved in opposite directions — 7.3% → 5.0% overall, but 1.7% → 5.2% on the both-answered subset. Same frozen questions, same frozen verb ladders, same corpus pin, same tokenizer; the naive arm reproduced to the token. What changed is where ripwire spends: the compact conceptual route made its misses much cheaper, while richer default bundles made the questions it answers dearer. Both numbers are printed because printing only the one that improved would be the failure this project exists to not commit. The full per-question re-derivation is in the Round 3 note under Measured.

    It is also cheap enough to call on reflex: this repository parses in ~0.15 s cold and ~0.10 s warm (time ./build/ripwire . --no-cache), so the agent asks instead of guessing.

    Where it pays most, and where it does not

    A map is a fixed cost paid once per context, so it pays in proportion to what that context goes on to do with it. Two shapes get the most out of it, and one gets nothing.

    • Orienting a context that does not know the repository. This is the strongest case, and it is strongest of all under an orchestrator that matches tasks to models: every lane it spawns starts cold on the same tree, and each one would otherwise re-derive the same structure from scratch. The map is the one artifact that does not have to be rediscovered per agent.
    • The checking pass at the end. --quality-delta, --test-gate and --edit-check answer questions — what did I make worse, which tests must run, did I change a contract — that an agent cannot answer by reading more source, at any budget.
    • It does not pay on a question one grep already answers. The map is charged whether or not it was needed, so a narrow lookup in a file you can already name is cheaper without it. --help-task exists to make that call, and a flat ranking says confidence="low" rather than pretending.

    Getting the saving takes three things, and a bare --for is none of them.

    1. A budget. --token-budget=N caps the bundle; --top-k=N caps the rows of the default map, --query, --format=candidates, --recall and --graph-query — it does not shape plain --for. On --for, a positive, explicit --top-k is read by nothing: the run prints a stderr note (--top-k is not read by --for) and still emits the full bundle. Two neighbours of that shape are different and neither warns: --for --format=candidates --top-k=N does consume the flag (the candidate export composes with it and caps the rows), and --for --top-k=0 is refused outright by the payload-only guard (--top-k=0 needs a payload verb), not warned-and-emitted. Narrow plain --for with its own arguments instead — --signatures-only (drop the auto-bodies), --token-budget=N (shapes the bundle to fit), --detail=N (full bodies for just the top N). Unbudgeted --for returns a rich terminal bundle by design — right when it ends the question, wasteful when it does not.
    2. Routing. ripwire . --help-task="<task>" names the ONE command the task actually wants, and abstains when the evidence is thin. It is advice, it never runs anything. An answer of "just grep for it" is a correct answer and the tool will give it.
    3. The skills. skills/install.sh teaches an agent when to reach for which verb. Without them an agent has 175 flags and no map of which moment each is for, and it will reach for the map every time — including the times it should not.
    WRONGRIGHTwhy
    ripwire . --for="…" --top-k=5ripwire . --for="…" --signatures-only (or --token-budget=N, --detail=N)--top-k is inert on --for: the run warns on stderr and emits the full bundle anyway, so the agent believes it narrowed the output and did not.
    ripwire . --query="…" as the default lensripwire . --for="…"--query is the raw BM25 ranking — the binary's own help calls it debug and says "use --for". It is the right tool for hunting a vocabulary, the wrong default for a task.
    ripwire . --expand=SYM --top-k=0