redhat-et/ripwire
GitHub: redhat-et/ripwire
一个零依赖的 C++23 CLI 工具,将代码库映射为确定性的排名调用图,为 AI 编码代理提供快速、可信的结构化上下文检索。
Stars: 0 | Forks: 0

[](https://github.com/redhat-et/ripwire/actions/workflows/ci.yml)
[](LICENSE)
[](CONTRIBUTING.md)
[](THIRD_PARTY.md)
# ripwire
**AI 上下文领域的 ripgrep。** 将它指向一个代码仓库,它就能在几十毫秒内从热索引中回答结构性问题——并为每一个它无法证明是完整总数的计数值打上标签。
[快速入门](#quickstart) · [它能回答什么](#what-it-answers) · [实际运行](#real-runs) ·
[性能度量](#measured) · [节省的 token](#what-it-saves-you-in-tokens) ·
[诚实契约](#the-honesty-contract) · [Agent 设置](#set-it-up-in-your-coding-agent) ·
[Prompt 循环](#improve-it-with-your-agent) · [文档](#documentation)
**只需十秒。** 无需索引服务器、无需 embeddings、无需 API key——直接当场构建解析和调用图:
```
$ ripwire . --callers=rankGraphTeleport
```
`counts_floor="1"` 是关键所在。调用边是根据名称从源码文本中提取的,因此动态分发、回调和宏生成的调用点不会产生边:`count="6"` 是一个**下限**,在你阅读任何一行数据之前,该元素本身就已经声明了这一点。(这是真实的输出,为了便于阅读进行了换行——实际输出是作为单行压缩文本发布的,其前缀包含一个完整说明此情况的图例注释。)
它的名字就是它的设计理念。**rip**grep 代表检索部分:这是一个零运行时依赖的 C++23 二进制程序,它会爬取文件树,使用 tree-sitter 提取符号,将引用解析为调用图,使用 Personalized PageRank 对该图进行排名,并将确定性的压缩 XML 映射流式传输到 stdout。
Trip**wire** 代表诚实部分:每一个它无法证明是完整总数的计数值都被标记为下限,每一次截断都会在 header 中披露,而零意味着*未找到*,绝不意味着*不存在*。
对同一棵文件树的两次运行在字节上是完全相同的,而且热运行等同于冷运行。这是一个契约,在每个 pull request 和每次推送到 main 分支时都会严格把关,而不是一种趋势。
在与另外三种上下文工具进行的 60 个实例的正面交锋中——使用相同的实例、相同的 gold 答案和相同的指标代码——它在 **36.7%** 的实例中将**所有** gold 文件置于前 10 名,而对手的比例分别为 26.7% / 21.7% / 13.3%,其中位数为 **0.074 秒**(热运行,使用预构建的索引)。[完整表格及相关注意事项 →](#against-other-tools)
## 快速入门
前置要求:CMake 3.24+ 和一个 C++23 编译器。仅此而已——tree-sitter 的核心、所有 15 个语法和测试框架都已内置于 `third_party/deps` 下,因此没有下载步骤,也不需要满足任何包管理器的要求。断开网络即可证明这一点:添加
`-DFETCHCONTENT_FULLY_DISCONNECTED=ON`,构建依然能够完成。
```
git clone https://github.com/redhat-et/ripwire.git
cd ripwire
cmake -S . -B build && cmake --build build -j
./build/ripwire --help
```
要将其加入 `PATH`,`./install.sh` 会将其构建并安装到检测到的 prefix 中(如果存在 Homebrew 则使用它,否则使用 `~/.local`;可以使用 `RIPWIRE_INSTALL_PREFIX` 进行覆盖)。
值得首先学习的四个命令:
```
ripwire . # the ranked map — start here on an unfamiliar repo
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
```
## 它能回答什么
在核心功能周围,有 123 个在 `--help` 中展示的长 flag,涵盖七个家族——此外还有一个 MCP server,因此编码 agent 可以在任务执行中途调用它们中的任何一个,而无需去 grep 和阅读整个文件。
`./build/ripwire --help` 是根据二进制文件自身的 flag 表生成的,始终具有权威性;
[`docs/COMMANDS.md`](docs/COMMANDS.md) 记录了其中 101 个 flag,并附带了实际的调用及其记录的输出。下面的每个家族都链接到了那里。
| 家族 | 问题描述 | 代表性 flag |
| --- | --- | --- |
| [**从零开始理解代码库**](docs/COMMANDS.md#understand-a-codebase-cold) | “这个仓库是什么,里面什么是重要的?” | `--for` · `--tree` · `--lego` · `--exemplar` · `--recall` · `--top-k` · `--token-budget` · `--max-tokens` |
| [**导航 / 回答问题**](docs/COMMANDS.md#navigate--answer-a-question) | “谁调用了这个?修改它安全吗?哪些测试相关?” | `--callers` · `--callees` · `--uses` · `--impact` · `--path` · `--connect` · `--affected` · `--situ` · `--test-gate` · `--grep` |
| [**缩放细节层级**](docs/COMMANDS.md#zoom-the-detail-ladder) | “给我看更多——但只在有价值的地方。” | `--detail` · `--pack-signatures` · `--outline` · `--expand` · `--compress` |
| [**评估质量 / 结构**](docs/COMMANDS.md#assess-quality--structure) | “风险在哪里,我刚才是不是增加了一些?” | `--hotspots` · `--clones` · `--metrics` · `--deps` · `--lint` · `--quality-delta` · `--edit-check` · `--pr-context` · `--merge-scout` |
| [**自检**](docs/COMMANDS.md#self-diagnosis) | “我的环境真的在工作吗?” | `--doctor` |
| [**安全性**](docs/COMMANDS.md#--scan-skillsdir) | “这个 agent skill 文件安装起来安全吗?” | `--scan-skill` · `--scan-skills` |
| [**旋钮 / 模式**](docs/COMMANDS.md#knobs--modes) | 形状、格式、缓存、预算 | `--json` · `--format` · `--mcp` |
值得刻入肌肉记忆的四个操作:针对手头错误的 `--from-trace=FILE`;编辑后立即执行的 `--edit-check=SYM`(契约是否改变了,哪些调用者现在被证明是不兼容的);在合并并行分支前执行的 `--merge-scout=REF1,REF2`;以及用于在单个受预算控制的捆绑包中完成排名、主体、调用者和测试的 `--pack-task="…"`。
## 实际运行
输出是压缩的——单行显示,标签之间没有空格——因此下面的摘录为了便于阅读进行了换行,并且省略了每个摘录前面的图例注释。除此之外没有进行任何编辑,只是语料库规模数字(文件/符号/边的数量、ranked-map header 中的 token/歧义统计数据、PageRank `k=` 值,以及 test-gate 示例中的 `script_gates_unmodelled=`——即递归计算 `test/` 下脚本运行器的数量)会随着代码仓库的增长而发生变化:**ranked map** 特意省略了这些内容,并在使用点再次进行了说明,而 **test gate** 额外地将其 `
` 行修剪为实际运行打印的 25 行中的 2 行,并在结尾用 `…` 代替。
**ranked map** —— 默认运行,限制为三个符号以便在此处完整显示:
```
$ ripwire . --top-k=3
```
`files=`/`symbols=`/`edges=` 以及 `k=` 排名值被省略了:这里是以本代码仓库为语料库,因此每次 README.md 自身增加或减少一行时,它们都会变动,而这并不是该示例要展示的内容。header 的其余部分衡量的是整个语料库,而不是摘录部分——`ambiguous=` 统计数据是调用图完整性的衡量标准,而某一行上的 `amb="2"` 表示该符号有两个调用命中了具有多个定义的名称,并且解析器进行了猜测。当“哪个目标”很重要时,请查阅源码。
**test gate** —— `--test-gate` 指出各项义务(obligations),并在仍有未完成项时以退出代码 4 退出。这是在代码树中存在未提交更改时捕获的:`changed="1"` 以及下面的行仅因为确实有待处理的内容才会出现。干净的克隆会以 0 退出,并且每个 changed/impacted/test 计数都为零——除了 `script_gates_unmodelled=`,它是结构性的(它计算的是调用图无法看到的脚本到二进制文件的测试运行器,而不是 git 状态),即使在这种情况下它也保持非零:
```
$ ripwire . --test-gate # exit code: 4
…
```
`run=` 属性只有在运行器可以从真实证据(一个词干与测试框架匹配的 test-dir 脚本,或者其文本指明了该框架的脚本)中推导出来时才会出现。没有 `run=` 意味着*无法推导*,绝不代表猜测出的测试套件命令。`script_gates_unmodelled="332"` 遵循同样的原则:脚本到二进制文件不是一条调用边,因此这些关卡在此次遍历中是不可见的,这个数字说明了这一点,而不是让 `tests="2"` 看起来像是完整的。`` 行是未经测试的影响范围(blast radius):即语料库中没有任何测试能够触及的受影响符号。
**基于错误本身,而非错误的转述** —— `--from-trace` 接收堆栈跟踪、sanitizer 报告或来自 stdin 或文件中编译器错误,将其帧由内而外映射到索引符号上,并返回带有这些帧的最内层语料库内主体:
```
./build/ripwire . --from-trace=asan_report.txt
cmake --build build 2>&1 | ./build/ripwire . --from-trace=-
```
## 性能度量
每一个公布的数字都记录在 **[`docs/EVALS.md`](docs/EVALS.md)** 中,连同生成它的工具、运行它的语料库,以及锁定它的树内文件——旁边还附有反例部分,以及本项目刻意*不*公布的声明列表。如果你来这里是为了检查该工具是否被夸大了,请先阅读这些内容。
### 与其他工具的对比
**N = 60 个配对实例,零排除。** 前 60 个评分的 LocBench 留出实例;相同的 gold 答案集和相同的指标代码,未加修改地导入,适用于每个测试组。*严格 file@10 = **所有** gold 文件均位于前 10 名内。*
| 测试组 | 严格 file@10 | any@10 | 中位运行时间 |
| --- | --- | --- | --- |
| **ripwire `--for`** | **36.7%** | 75.0% | **0.074 s** (热运行,预构建索引) |
| codebase-memory-mcp | 26.7% | 66.7% | 1.14 s |
| graphify | 21.7% | 41.7% | 5.8 s |
| Aider repo-map | 13.3% | 33.3% | 2.5 s |
在严格 file@10 下的配对胜负记录:对阵 Aider 16胜2负,对阵 codebase-memory-mcp 10胜4负,对阵 graphify 12胜3负。**关于速度的注意事项与该数字相伴:** 2.5 s ÷ 0.074 s 大约是 Aider 中位数的 34 倍,但 ripwire 的数据是在*带有预构建索引的热运行*下测得的,而 Aider 的是*每次运行均为冷启动*——这并非同等条件下的缓存状态对比。在引用两个中位数时请附带说明,或者在引用倍数时附加上这句话。
**LocBench 留出集,N = 243,涵盖 78 个代码仓库。** 严格 file@10 为 **60.9%**,而预路由基线为 **27.6%**——配对结果显示提升了 **+33.33pp**,其聚类自助法(clustered-bootstrap)95% 置信下限为 **+25.00pp**,而代价仅是热运行延迟增加了 3.4%,并且生产环境的 token 上限降低了 **−39.4%**。既更准确又更便宜,这就是它被发布的原因。
### 在 token 方面为你节省的开销
上下文是 agent 实际消耗的预算。三项测量结果,每一项都由相应的工具锁定:
| 节省来源 | 测量结果 | 锁定依据 |
| --- | --- | --- |
| `--pack-signatures` —— 移除主体后的声明骨架,而不是完整的函数体 | 在 top-50 处 **元素字节数减少 67.0%**(top-10 处为 46.7%,top-100 处为 66.2%) | `test/showcasecapturecheck.sh`,每次运行均从此代码仓库重新推导 |
| 查询结构路由,针对生产环境 token 上限 | **p50 下降 −39.4%**,同时严格 file@10 上升了 +33.33pp | `bench/locbench/`,[EVALS §3docs/EVALS.md) |
| 针对整个问题的捆绑包,对比 naive agent 的全盘读取 | **减少 96.0% 的 token (24.9倍)** —— 14,758 对比 367,192,tiktoken `cl100k_base`,六个现实问题 | `bench/BENCHMARK.md` —— *历史数据,私有语料库,无法从本代码树中重现* |
在引用第一行数据之前,请先阅读其方法论:元素字节的计算是**去除根目录影响(root-neutralised)**的,即从两边都减去了语料库根目录的前缀,因为根目录前缀在每个元素内部都会重复,在两种形式中都会被计费,而且它不是这个操作所移除的内容。请引用 top-50 的数据——无论 `--top-k` 怎么说,签名负载始终是 top-50 的,而 top-10 只是一个十个符号的样本,一个单行的访问器就能使其变动几个百分点。如果文档与二进制文件的偏差超过 1.5 个百分点,测试关卡就会失败。
第三行的注意事项并不小:它是在 2026-06-20 对一个大型私有 C++ 语料库进行测量的,无法从本代码树公开重现,而且它证明的是*更便宜、更快速*,而不是*结果更好*。
**失败案例与成功案例并列展示。** `--grep` 消耗的 token 比它节省的还要多(一项测量中为 **+19.7%**,另一项为 −11.2%)——它并不是一个减少 token 的工具。`--pack-signatures` 在处理短符号时效果会反转:303 字节的签名加文档注释对比 158 字节的主体。标题是对大型结果集属性的描述,[完整的反例列表](#in-the-numbers) 是契约的一部分,而不是附录。
## 诚实契约
差异化优势不在于某个数字,而在于一种自律:**一个你无法核查的测量结果仅仅是个声明,而这款工具交付了核查手段。**
### 输出中
- **零代表测量结果,而不是不存在。** `counts_floor="1"` 标记了所有基于名称解析无法证明其是完整总数的计数值。将零理解为*未找到*,绝对不要理解为*不存在*。
- **截断在发生处即被披露。** `shown_*`、`*_capped=` 和分页属性说明了保留了什么以及如何进行分页;引导每个文档的图例注释完整地定义了词汇表,因此输出无需依赖此 README 即可自我解释。
- **单位已明确标示,因为它们会根据具体的动词(verb)而变化。** 调用者计数(callers)是不同的符号,影响计数(impact)是一个可达集合(reach set),使用计数(uses)是调用点。图例说明了各自代表的含义,因此两个看起来矛盾的数字可以被理解为它们所回答的截然不同的问题。
### 数字中
评估标签是通过阅读源码并决定哪个符号*是*符合任务要求的答案来生成的——绝不是通过转录排名器自身的输出——因此评估允许指出排名器是错误的,而且它确实这么做过。以下是对此进行说明的结果,全部在代码树内,全部刻意予以发布:
- **`--grep` 消耗的 token 比它节省的还多**,并且 **`--pack-signatures` 可能会使输出变大**——两者都在[它们所限定的节省项目旁边](#what-it-saves-you-in-tokens)进行了量化,因为主动声明这一点比被发现要好得多。
- **公开的 C++ 数据明显低于之前的私有数据。** SFML:严格 file@10 为 31.3%,any@10 为 45.2%,首个命中结果的 MRR 为 0.22——而基于无法再从该代码树重现的私有语料库,any@10 约为 89%。公开的数据是今后采用的基准线。
- **PageRank 是一个糟糕的协同变更(co-change)排名器** —— recall@5 为 3.8%,而普通的词法匹配为 40.3%,将两者融合反而使结果变得更糟。相关性由词法决定;重要性由结构决定;该工具对这两者分别使用了不同的机制,因为测量结果是如此显示的。
- **严格的多文件定位既困难又将继续保持困难。** 留出的 LocBench 数据显示:单文件 gold 为 73.4%,多文件为 18.2%。每个语料库都显示出同样的断崖式下跌。
### 测试中
`test/regression.sh` 命名了 **312 个门控脚本**,并且是权威列表;
`python3 test/pargates.py . ./build/ripwire -j 6` 并行运行相同的脚本集。在此之上是不适合用单元测试来涵盖的契约:两次运行在字节上完全相同,热运行输出与冷运行一致,输出能通过 `xmllint --noout` 顺利解析,一个在 `-fno-sanitize-recover=all` 下构建的 sanitizer 版本,以及一个差异化的 argv 测试工具,它会针对每一个参数向量运行参考二进制文件和候选版本,并要求 stdout、stderr 和退出代码在每一个测试上都保持一致。
支撑这一切的内部规则是:**在编写它所测量的代码之前,先编写测试关卡。** 无论是否正确,排名、token 估算和调用图看起来都很合理。
## 在你的编码 agent 中进行设置
只需两步,都在一分钟以内:注册 MCP server 让 agent 能够在任务中途调用 ripwire,然后安装告诉它*何时*调用的 skills。
### 1. 注册服务器
`ripwire wrap ` 会为你指定的 agent 打印出配置方案(recipe)。它是**打印**;它绝不编辑你的配置——你阅读这行内容,然后自己运行它。
```
ripwire wrap claude # MCP: claude mcp add ripwire -- ripwire --mcp
ripwire wrap cursor # MCP: the mcpServers stanza for .cursor/mcp.json (or ~/.cursor/mcp.json)
ripwire wrap codex # MCP: the [mcp_servers.ripwire] stanza for ~/.codex/config.toml
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
```
这会注册一个 stdio server —— `ripwire --mcp` —— 并公开 **30 个动词**:15 个读取动词,12 个旗舰核心动词,以及 3 个基于 span 寻址的编辑动词。读取动词与 CLI 相对应(`analyze`、`for`、`grep`、`cochange`、`fetch_body`、`lego`、`mentions`、`owners`、`memory_recall`、`situational_awareness`、`batch` 等);`find_symbol` 和 `find_referencing_symbols` 附加的是一个稳定的 `handle` 而不是函数体,因此 agent 只在真正需要时才会去获取源码。编辑动词强制执行安全契约——拒绝陈旧数据、拒绝歧义、原子写入。完整参考文档:[`skills/ripwire-mcp/`](skills/ripwire-mcp/)。
在打印之前,`wrap` 会使用与 `--scan-skills` 相同的引擎对 `./skills` 和 `.agents/skills` 进行安全扫描:如果发现 CRITICAL 级别的问题将阻止生成方案,而警告则会打印出来并继续执行。
**如果你使用的客户端不是这六种之一**,该服务器是一个标准的 stdio MCP 进程,任何支持 MCP 的客户端都可以手动指向它。完整的配置如下:
```
{
"mcpServers": {
"ripwire": { "command": "ripwire", "args": ["--mcp"] }
}
}
```
如果 `ripwire` 不在 agent 的 `PATH` 中,请在 `command` 中使用绝对路径。对于需要 socket 而非 stdio 的客户端,`ripwire --listen=HOST:PORT` 可以提供相同的动词服务。
### 2. 安装 skills
`skills/` 提供了 **十七个基于任务形态的 skill**,用于告诉 agent 在当前情境下*哪个*动词能给出答案——从零开始的适应、追踪调用、评估重构规模、检查 diff、寻找 bug、编写测试、审查安全性。如果没有它们,agent 拥有 30 个动词,却不知道何时应用哪一个;skill 明确指出了每个动词适用的时机。以符号链接的形式安装回本代码仓库,这样此处的修改可以立即生效:
```
skills/install.sh # → ~/.claude/skills
skills/install.sh /some/path # → an explicit destination
ripwire --scan-skills=skills # read the security scanner's verdict first, if you would rather
```
该脚本自身的 header 文档记录了它的其他模式,包括 Codex 和一个可选的咨询性 PreToolUse 钩子。
## 与你的 agent 一起改进它
[`prompts/`](prompts/) 包含了十个**独立的编排器 prompt**:这是本项目构建时所使用的循环,编写时特意让编码 agent 能够直接运行。它们编码的是工作流,而不是描述工作流。
如何运行其中一个:
1. 首先构建该工具——大多数循环都需要一个二进制文件作为测量基准:
`cmake -S . -B build && cmake --build build -j`
2. 在 ripwire 检出目录的根目录下打开你的编码 agent。
3. 粘贴其中一个 prompt 文件的内容。它不需要该目录中的任何其他内容。
4. **它会写出一份计划,并在等待你的批准后继续。** 在你批准之前,什么都不会运行——阅读该计划,删掉你不同意的部分,然后说开始。
5. 该循环会在前台运行其自身的测试关卡,并报告它让哪些测试保持了绿色通过状态。
值得首先尝试的三个:
| Prompt | 产生什么 |
| --- | --- |
| [`full-audit.md`](prompts/full-audit.md) | 一份按严重程度排名的审计,涵盖 bug、性能测量、动词与情境的匹配度、token 效率,以及对拥有真实增长势头的论文和代码仓库的生态系统扫描。 |
| [`dogfood-gaps.md`](prompts/dogfood-gaps.md) | 仅使用 ripwire 进行导航来完成一项真实任务,并在回退到 grep 或读取整个文件的情况发生时,将其作为产品缺陷记录下来。 |
| [`capture-audit.md`](prompts/capture-audit.md) | 通过并行的对抗性视角读取一次全新的 showcase 捕获,并将发现的问题转化为整个家族通用的测试关卡。 |
另外七个——与竞争对手进行的配对正面交锋、从你自己的会话中挖掘真实检索失误的排名评估循环、针对每种语言的改进过程、零上下文的入职研究、同级 sweep、实时命令导览、showcase 构建——在 [`prompts/README.md`](prompts/README.md) 中列出了各自的受众群体。每一个都陈述了其自身的范围和诚实规则,并且大多数指明了它们必须保持绿色通过的测试关卡。
## 语言
C, C++, Objective-C / Objective-C++, **Metal** (Metal Shading Language, `.metal` —— 使用 C++ 语法进行索引,因为 MSL 是 C++14 的一个方言,所以双编译 header 中的符号可以从 GPU 和 CPU 两部分进行解析), Python, TypeScript, JavaScript, Java, Ruby, Bash, Go, Rust, Swift, C#, 以及 JSON (配置键)。内置了十五种 tree-sitter 语法。
Markdown、笔记本、HTML 和 CSV 被索引为*文档*,用于支持 `--recall` 以及 `--mentions` 背后的文档与代码间的边;Office 和 PDF 通过一个可选的桥接加入其中。
## 文档
| 需求 | 文件 |
| --- | --- |
| 包含实际调用及其记录的输出的每一个 flag | [`docs/COMMANDS.md`](docs/COMMANDS.md) |
| 具有权威性且始终最新的 flag 列表 | `./build/ripwire --help` |
| 管道(Pipeline)、数据模型、确定性契约、输出诚实契约 | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) |
| 所有已发布的数字、它们的测量工具以及*未*发布的内容 | [`docs/EVALS.md`](docs/EVALS.md) |
| 作为可迁移知识的方法论 | [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) |
| C++ 代码规范、G1–G5 防护栏、门控准则、提交检查清单 | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
| 针对*在本*代码仓库上工作的编码 agent 的导向说明 | [`CLAUDE.md`](CLAUDE.md) / [`AGENTS.md`](AGENTS.md) |
| 用户可见的功能、行为变更、已知限制 | [`CHANGELOG.md`](CHANGELOG.md) |
| 内置的依赖项及其许可证 | [`THIRD_PARTY.md`](THIRD_PARTY.md) |
如果文档与 `--help` 不一致,则以文档为 bug。
## 许可证
Apache License 2.0 —— 完整文本请见 [`LICENSE`](LICENSE)。
版权所有 2026 David Brewster
内置的第三方代码保留其原有的许可证;每一个依赖项都在
[`THIRD_PARTY.md`](THIRD_PARTY.md) 中连同其条款进行了列举。标签:AI编程助手, Bash脚本, C++23, SOC Prime, 云安全监控, 代码分析, 凭证管理, 开发工具, 调用图, 静态分析