
这是什么
一个 C++23 二进制,把仓库读成一个带排名、结果确定的图谱并打到标准输出,瞄准的正是编码 agent 准备花 token 去翻文件的那一刻。一次串行爬取加上每种语言一个 tree-sitter 查询引擎,喂给一次 Personalized PageRank;所有动词都只是对同一张图的不同读法——改动前用 --callers、--deps、--arch、--impact、--situ,改动后用 --edit-check 与 --quality-delta,而 --for=TASK 或 --pack-task 会按 token 预算给某个具名任务打包一份上下文。同一套计算也通过 MCP 暴露给 agent:33 个动词,用架构文档自己的话说,是「a thin front door onto the same computation and the same renderer as a CLI sibling — one output shape, two surfaces」。构建用 cmake -S . -B build && cmake --build build -j;把它注册成 {"type":"stdio","command":"ripwire","args":["--mcp"]};skills/install.sh --hook 会装上那些在工具调用前提醒 agent 的路由钩子与技能文件。输出是打在 stdout 上的压缩 XML,同样的仓库两次运行逐字节相同;爬取时丢掉的东西——体积上限、被 git 忽略的文件、没能解析的调用——都会写出来,而不是悄悄消失。
谁做的仓库归 GitHub 组织 redhat-et 所有,这个名字展开是 Red Hat Emerging Technologies。除此之外就没有旁的凭据了:仓库元数据里没有 owner 字段,README 全文(209,734 字符)里也不出现 "Red Hat" 字样——所以这层归属是靠组织名和仓库内部路径间接支持的,而不是靠项目自己说的任何一句话。报告里唯一直接的红帽标记在提交邮箱直方图里:dbrewste@redhat.com 出现在 4,135 次提交中的 1,651 次上。至于它出自红帽内部哪个团队,材料没有覆盖。干活的人高度集中:有关联账号的提交里 joyful-ii-V-I 占 3,554 次;25 人的贡献者名单由 barefootski(346)、quaterniondrift(93)与 lennix1337(33)领衔。
它是怎么搭起来的
组成 · 6一条五阶段、没有回边的流水线——ingest → graph → rank → serialize → cli / mcp——每一个阶段的输出都是普通数据结构,这正是让单个阶段可以独立测试、并让每个动词都只是对同一张图的另一种读法的原因。三个后果贯穿代码。爬取是串行的,并且在分配节点 ID 之前先把候选路径排好序,因为节点 ID 是那张表的下标,一旦依赖目录顺序,ID、top-K 截断和每一个与 diff 相关的动词都会在同一次树上抖动;线程都在解析池里。抽取是一个查询引擎跑每种语言一份 queries/<language>/tags.scm,而不是每种语言手写一遍遍历——架构文档点名否掉了另一条路。至于爬取时丢掉什么,都是有数字支撑的决定而不是默认值:通用 4 MB 上限;JSON 超过 256 KB 或嵌套超过 512 层就跳过;YAML 单独给 512 KB,因为套用 JSON 的那条线会把手工维护的配置丢掉;TOML 完全没有上限,因为一次测量显示 321 个文件里最大的一个是 57,759 B;而所有体积原因被丢下的文件都计进头部的 skipped_oversize=,不会凭空消失。
- src/ingest*.h
- ingest 阶段:一个编译单元,脊梁是
src/ingest.cpp(29,564 字节),各个家族放在带守卫的分节里——ingest_cache.h(255,885 字节)、ingest_relations.h(140,685)、ingest_names.h(130,361)、ingest_sidecap.h(130,077)、ingest_binds.h(123,635)、ingest_crawl.h(116,419)、ingest_astquery.h(108,592),再加上预热的 tags.scm、解析池、文档后处理与模型收尾几节。src/整体是 151 个文件、11,275 KB。 - src/graph.h 与 src/pagerank.cpp
- graph 与 rank 两个阶段。
graph.h有 453,160 字节;pagerank.cpp10,939 字节,配一个 2,174 字节的头文件——刻意保持小,因为确定性规则禁止在这个编译单元里对浮点做重结合。符号靠 Personalized PageRank 排名,而那个顺序是其余大部分动词赖以成立的东西。 - src/main.cpp、src/cli.h 与 src/verbs_*.h
- 动词家族与共用的输出机制。
serialize.h564,798 字节、cli.h523,742、quality.h541,224、main.cpp347,197。家族本身是verbs_for.h(287,589)、verbs_report.h(241,378)、verbs_navigate.h(175,046)、verbs_quality.h(160,168)、verbs_lint.h(126,375)、verbs_change.h(91,385)、verbs_grep.h(71,657)与verbs_doctor.h(66,917)。src/infra/有 39 个文件,其中有sortutil.h(9,889)——#342 之后五处比较器统一改走的就是它。 - src/mcp*.h
- MCP 面:33 个动词,按架构文档自己的说法,它们是通往「与 CLI 同源的那套计算与同一个渲染器」的一扇薄门。
mcpverbs.h352,422 字节、mcp.h201,355、mcprefusal.h96,085、mcpedit.h86,563、mcpindex.h77,824、mcpjson.h43,368、mcpserver.h30,626。仓库根还有一个 101 字节的.mcp.json,把这个服务注册给它自己。 - queries/ 与 third_party/
- 抽取用的数据与整棵 vendored 依赖树。
queries/下有 23 个<language>/tags.scm,从 1,367 字节(bash)到 28,032 字节(C++)。third_party/deps有 211 个文件、242,952 KB——tree-sitter 内核、doctest,以及 bash、C、C++、C#、CUDA、Dart、Elixir、GDScript、Go、Java、JavaScript、JSON、Kotlin、Lua、Markdown、Objective-C、PHP、Python、Ruby、Rust、Swift、TOML、TypeScript 与 YAML 的语法。third_party/patches/里是对这些 vendored 扫描器打的 14 个补丁,third_party/本身放着那些 header-only 的部件,CMakeLists.txt有 100,647 字节。 - test/、skills/ 与 hooks/
- 闸门套件与 agent 一侧的管线。
test/有 718 个文件、13,573 KB,其中 661 个是由test/pargates.py驱动的*check.sh闸门,登记在test/regression.sh里、由test/manifestcheck.sh稽查;光是test/fuzz/就有 90 个文件。skills/里放着install.sh(33,469 字节)与 17 份技能文档,hooks/里是五个 shell 钩子(ripwire-nudge.sh有 107,068 字节),而.claude/skills/、.codex-plugin/与.coderabbit.yaml装着按 agent 分别的配置。
取舍,以及它替代了什么
爬取保持串行,并在任何节点 ID 出现之前先把候选表排好 替代 按目录顺序分配 ID,并把遍历并行化
节点 ID 就是那张有序候选表的下标,所以文档写的是后果而不是偏好:按目录顺序会让节点 ID、top-K 截断和每一个与 diff 相关的动词在同一次树上抖动。它同样明确说了并行去了哪里——爬取「is the cheap half and is deliberately not parallelized; the parse pool is where the threads are」。
一个查询引擎读每种语言一份
tags.scm替代 每种语言手写一遍 AST 遍历架构文档点名并否掉了另一条路:「a bespoke AST traversal per language — is forbidden here」,理由是那条路等于「five fragile walkers that break on every grammar bump instead of one query loop that survives them」。于是每种语言要做的变成了数据——一份查询文件加一张 capture 名到角色的表——这也是仓库里能装下 23 份的原因。
Rails schema 里的列只是定义,不产生边 替代 让
create_table的列带上调用边与 PageRank 权重pull request 里的原话是「Definitions only (maintainer decision after review).」由
create_table块铸出的列是按内容而不是按路径识别的,能回答--uses、--whereis、--grep和地图,但buildGraph的byName会跳过Section && Lang::Ruby,于是--callers=id报的是defs="14" count="0"——定义找得到,边被拒绝。TOML 不给专属上限,YAML 给 512 KB 而不是 JSON 的 256 KB 替代 让所有配置格式共用同一条上限
这件事被写成「TOML has no lane-specific ceiling, and that is a measured decision rather than a missing sibling」:在 90 多个公开仓库、321 个
.toml文件上,最大的一个是 57,759 B,所以任何上限都不可能既高于实测最大值、又低于通用的 4 MB 跳过线。YAML 正好相反——套用 JSON 的 256 KB 会把真实手工维护的配置丢掉,因为 NeMo 的cicd-main.yml有 293 KB。目录符号链接一概不跟随 替代 跟随它们,再用 inode 记录来断开环
「There is no inode tracking, because with symlink-following off there is nothing for it to do.」遍历只用
skip_permission_denied打开,所以被软链的目录根本不会被进入,环也就无从产生。
依据docs/ARCHITECTURE.md(50,755 字节/50,456 字符——报告只打印前 12,000 字符)、AGENTS.md(2,930 字符)、架构文档候选清单、完整的 2,951 个文件树及其逐个文件体积,以及用自己话写下决定的那些 pull request(#339、#359、#363)。
制作过程
6 个阶段- 01
七月底出现,到九月已经发了二十个 release
仓库建于 2026-07-29,而它最老的一条提交落在 2026-07-31,标题是
import from internal development tree——材料里关于这段代码此前从哪来的唯一说法,就只有这一句。此后它的速度是本档案很少记到的:4,135 次提交,其中 7 月 17 次、8 月 1,294 次、9 月 2,824 次;对应 2,374 个星、151 个 fork、8 个 watcher、58 个待处理 issue,Apache-2.0,语言 C++。它发了 20 个 release,每一个都既不是 prerelease 也不是 draft,从 2026-08-02 的v0.1.0(标题为ripwire v0.1.0 — the ripgrep of AI context)到 2026-09-27 的v0.6.5;其中八个落在 29 小时之内:v0.3.0在 2026-08-11 的 23:31,v0.3.8在 2026-08-13 的 04:11。标签列表里有v0.3.7而它没有对应的 release,也没有v0.1.0;标签一共 20 条,和 release 列表一样长。提交里数出 3,205 条共同作者尾注,最多的是Claude Opus 5的 1,417 条,其后是Claude Fable 5的 698 条与Claude Fable 5.1的 543 条,另有Qwen3.8 Max的 2 条。三份提交直方图彼此并不一致——joyful-ii-V-I按作者名算是 3,993 次、按关联账号算是 3,554 次,而邮箱那一栏由dbrewste@redhat.com的 1,651 次领先。 - 02
五个阶段、没有回边,以及为每一句断言配一道闸
架构文档把流水线写成五个阶段——
ingest → graph → rank → serialize → cli / mcp——并用一句话把规矩定死:「Five stages, in that order, with no back edges. Each stage’s output is a plain data structure, so any stage can be tested in isolation and every verb is a different way of reading the same graph.」第一阶段里有两个选择担着大部分重量。爬取会先把每一个候选路径收齐,按字节字典序排序,然后才分配节点 ID,因为 ID 就是那张有序表的下标;文档说,若按目录顺序来,节点 ID、top-K 截断以及每一个与 diff 相关的动词都会在同一次树上产生抖动。而遍历本身保持串行——它「is the cheap half and is deliberately not parallelized; the parse pool is where the threads are」。抽取是一个查询引擎跑每种语言一份queries/<language>/tags.scm(树里 23 个),另一条路被点名否掉:「a bespoke AST traversal per language — is forbidden here」。同一套直觉也管着测试:AGENTS.md把规矩写成「Gate first, code second」,而机制是——新增一个test/*check.sh必须在同一次提交里登记进test/regression.sh,否则test/manifestcheck.sh会失败。test/有 718 个文件,其中 661 个是*check.sh闸门,而这个数字每周都在动:#337 里重建 deck 时是 649 道,#341 是 650 道,一位贡献者引的是 651 道,而某次在负载机器上跑的完整pargates.py -j 6返回的是 665 道闸、657 通过、4 个环境跳过、4 个失败。 - 03
那个花掉 67 GB 的家目录
#350 由
KilimcininKorOglu在 2026-09-27 提出,正是内存守卫存在的理由。当 MCP 服务以{"type":"stdio","command":"ripwire","args":["--mcp"]}注册进 Claude Code、而会话开在家目录里时,一次grep调用把--mcp进程在七小时里推到 67 GB 的phys_footprint;系统 swap 涨到 28 GB、只剩 458 MB 可用——机器是 Apple M1 Max、64 GB 内存、macOS 27.0。原因写在报告里并被维护者确认:在不是 git 仓库的根目录上无法套用.gitignore,于是爬取会把该目录下的一切都做成图,而没有任何东西给这块内存设上界。#363 分几层作答。没人主动选过的根会被拒绝——$HOME本身(即便它是 git 仓库)、文件系统或盘符根、各用户家目录的父目录、以及/System、/usr、/etc、/proc、/sys、%WINDIR%、Program Files 这类系统树——回一行「no project root: <dir> is a home/system directory; pass a project path」,同一条规则也管到 MCP 请求里的path=。为了证明它,还需要一个专门的测试接缝:RIPWIRE_TEST_MEMGUARD=hard:N让第 N 次硬上限读数算作越界,而 B14 这条臂用hard:1跑两个极小的根,断言只存在一个 ingest 缓存块、且 stderr 里点名的是「root 1 of 2」。B12 那条臂则关掉 AddressSanitizer 的隔离区来跑,因为隔离区会把这颗被测量的 footprint 抬大。 - 04
一条从来没跑通过的健康检查流水线
2026-09-27,
llvm-x86用 #342 报告「文档里写的 Linux sanitizer 仪式在main上根本跑不完」,并在同一分钟里拆出六个子 issue——#343 到 #348。根因是一个库的细节:libstdc++ 把string_view的比较算成size_type里的n1 - n2,当一个是另一个的前缀时这个减法会回绕;而 G1 那条线开着-fsanitize=integer与-fno-sanitize-recover=all,于是文档里写的自检会中止在一行「unsigned integer overflow: 4 - 16 cannot be represented in typesize_type」上。有五处用裸<比较std::string_view:src/ingest_crawl.h的pathInIgnoreSet、src/situ.h里--situ的词典序邻居索引两处、以及src/mention.h的finalizeNamedIdents两处。修法是把五处统一走rw::sortutil::svLess;维护者在那条评论里把「坏了什么、没坏什么」说得很准:「The ordering is still correct, so release binaries answer correctly.」另有两个子 issue 与 C++ 无关:两个闸门脚本以 100644 模式提交,而这套测试里其它每一个*check.sh都是 100755,直接调用会以 126 退出——而 CI 是隔着 shell 调它们的,所以从来没发现;以及文档里那条 GCC-DRIPWIRE_ASAN=ON路径根本构建不过,因为一个constexpr谓词把一个来自findByField的指针喂进了static_assert。#351 用一次提交关掉了 #342 连同六个子 issue,新加的 #6c 臂在main上是红的、打上修复后变绿。 - 05
一份报告要长成什么样,才配被信任
#353 由
toasterbook88在 2026-09-28 提出,是一份关于报告的报道。对一个 2,077 个文件的 skill 家目录跑--scan-skills返回了 156 条发现,其中 107 条是EXFILTRATE:net-exfil;报告者逐行读了每一条被标出的行,把 107 条按目标地址分了类,并把触发条件收敛到三行。维护者认同这个判断——那条规则是按「a network verb plus any$VARon the same line」触发的,对变量的值从哪里来一无所知,所以有文档的 API 调用和回环检测被当成 critical 报了出来。真正让这个 issue 值得留下的是第二条评论。报告者用「a single frozen script」重跑了一遍扫描,对它标出的三行逐一手工核对,然后发出更正后的直方图,并写道:第一版「was wrong in two places — one from over-claiming, one from a too-narrow var-trace — and I’d rather correct it explicitly than leave a number standing that a reader can’t reproduce」;更正之后,107 行里大约 89 行是有文档的服务 API 调用。维护者的回复是本档案可以直接引用的标准:「It’s exactly the kind of care that makes a report trustworthy. You withdrew the claims you couldn’t back, froze the classifier, and published the row-level TSV.」后续也走同一条路——#359 在docs/LINEAGE.md里给 NVIDIA 的 SkillSpector 补上署名,并且只取它基于代码的那部分检查,理由是「only the code checks come across. The agent’s LLM reads ripwire’s output and does the judging」——而 #360 把同一套推理推到 agent 会加载的指令文件上。 - 06
作者把数字连同它们自己的边界一起发出来
docs/EVALS.md有 1,132,024 字节,docs/COMMANDS.md有 720,784 字节——一个项目把「可测量的说法」当成交付物而不是事后补的东西,就长这样。README 里还放着一份现场报告,由 #361 加上,默认折叠在页面靠上的位置,署名是「Claude Fable 5.0, the frontier model orchestrating ~20 coding agents over two days on a ~1,500-file C++/Metal codebase」。它的数字很大,标注也一样明确:审计与研究阶段大约一半的 token 花费,原文称这是「the operator’s whole-phase estimate」;一次--callers查询返回了零个生产调用方;--edit-check标出了文本搜索漏掉的 6 个调用点中的 5 个;以及「a dozen-plus real code-quality defects fixed, not waived」。pull request 正文在同一口气里写明边界——这只是某一次合作、发生在更早的版本上,是「the orchestrating model’s own report and not a controlled measurement」——而报告的全文是一张点击展开的图片docs/assets/field-report-multi-agent.jpg,1144×1600、567 KB,图下另附一份为无障碍加的纯文本版。README 自己的那些数字也放在同一个框里:建索引 0.25–0.45 秒、strict file@10 为 58.3%(对照 40.0%)、签名比函数体少 74.7% 字节、profile-guided optimisation 带来 14–25% 的提升——全部出自作者,而且 README 自己说这些计时列没有被重新测过。
相关档案
全部档案 →第 091 号
OKF Agent Memory
把编码 agent 学到的东西以纯 Markdown 留在仓库里——一个用进程内 BM25 检索的 OKF v0.2 知识 bundle——于是这份记忆可以被 diff、被审阅,而不必住进数据库。
第 090 号
Reverify
一个校验 AI 关于二进制文件所下断言的东西:模型提出 claim,一套确定性工具拿真实字节去查,返回的是带证据的 VERIFIED、REFUTED 或 INCONCLUSIVE——能熬过一次上下文重置的,是这些判决,而不是模型的文字。
第 081 号
pgbot
一个静态 Go 二进制,只读地连上 PostgreSQL,读服务器自己的统计视图,打出一份以 finding 为先的体检报告——因为每次运行都会在本地存一份基线,它还能说出「与上次相比变了什么」;同一批确定性 finding 通过 MCP 交给 AI agent,而可选的 AI 层只被允许解释它们。