Graft:面向编程代理的代码库地图
Graft 使用 tree-sitter 解析代码库,生成可查询的符号地图,并通过 MCP 提供检索工具,让代理无需每次会话重新搜索目录、定义和调用关系。
编程代理使用的代码库地图是什么
面向编程代理的代码库地图,是代码库的持久化索引。代理可以从中查找信息,而不必在每个新会话中重新使用 grep 从头搜索。Graft 是这种方案的一种实现。它使用 tree-sitter 解析代码,生成一组相互链接的 Markdown 节点和按符号组织的连接图,并通过 MCP(model context protocol,编程代理调用外部工具所使用的标准接口)提供检索工具。
Graft 不是代理,也不是网关。代理与模型 API 之间没有额外的中间层。该地图是磁盘上的一个文件夹,代理会读取其中的内容。这个区别决定了您要解决的问题:自托管令牌网关负责计量和路由您已经发送的请求,而代码库地图会改变您实际需要发送的请求数量。
这项技术早于该工具存在,也会在该工具之后继续使用。请先了解这项技术,再了解具体机制。
编码代理为何会因重新发现代码结构而消耗上下文
观察一个代理在已经处理过 50 次的代码库上开始工作。它先列出目录,再搜索某个符号。然后打开 3 个文件,查找函数的定义位置;接着打开第 4 个文件,确认哪些代码调用了该函数。这些都不是任务本身,而是定位代码所需的准备工作。每次会话都要为此消耗输入 token。
原因很简单。模型无法在会话之间保留记忆。代理对代码库布局的所有了解,都存放在会话结束时被丢弃的上下文窗口中。因此,每次都要从零开始,以完整成本重新执行相同的发现过程。在大型代码库中,定位代码的成本可能高于实际修改:需要调用 10 次工具来找到代码,却只需调用 1 次工具完成修改。定位和修改各占这笔成本的一半,因此,要求代理坚持采用可行的最小修改方案的技能值得与结构地图配合使用,而不是二选一。
结构地图通过将发现工作从模型转移到磁盘上,打破了这一循环。解析器遍历一次代码库,记录每个符号的定义位置,以及符号之间的调用关系;代码发生变化时,它会持续更新这些记录。代理只需提出一个问题,就能获得附带文件名和行号的答案。重复探索变成了低成本查询。
您已经在使用一种较弱的类似方案。说明代码规范的 AGENTS.md 文件可以避免代理每次重新推导这些规范。生成的结构地图则可以避免代理重新推导代码库结构。区别在于文件由谁编写。规范文件由您手动编写,因此内容可以保持精简。结构地图由解析器生成,因此可以覆盖 10000 个文件。至于会话中上下文预算的实际分配方式,Claude Code 如何使用其上下文窗口介绍了相关明细。
Graft 实际构建的内容
仓库根目录下的同一个 graft/ 文件夹中包含两个构件。
第一个是使用相互链接的 Markdown 编写的节点图,每个节点对应一个文件。每个节点包含以下内容:简明的英文摘要;从源代码中提取的重要逻辑行,即“核心逻辑”;带内容哈希的精确源文件;指向其他节点的类型化 wiki 链接(depends_on、part_of、uses、implements);以及一个可在重新生成时保留的备注部分,用于记录解析器无法推断的上下文。
第二个是 graft/.graph/wiring.json,即 tree-sitter 提取的按符号划分的结构图,其中包含定义、引用以及符号之间的调用边。
这种拆分很重要,因为只有其中一部分需要模型。graft build 完全由 tree-sitter 处理,从不调用 LLM(大型语言模型),因此结果具有确定性且不产生费用。graft build --deep 会添加书面摘要和按符号划分的核心逻辑,这些内容需要调用模型并产生费用。
语言支持分为多个级别,级别决定了您可以在多大程度上信任调用图。TypeScript、JavaScript、Python、Go 和 Java 支持感知作用域的跨文件解析。Rust、C、C++、C#、Ruby、PHP、Kotlin、Scala、Swift、Elixir、Solidity、OCaml、Zig 和 Dart 支持符号及通用调用边。这意味着某条边可能只是名称匹配,而不是已解析的引用。使用 --lsp 和 rust-analyzer 或 gopls 等语言服务器后,才可选择启用编译器级调用边。
安装 Graft 并固定版本
Graft 需要 Node.js 20 或更高版本,并采用 MIT 许可证。截至 2026 年 8 月,当前版本为 0.10.1;最早发布的版本 0.1.0 的发布日期为 2026 年 7 月。请将其视为尚处于早期阶段的软件。
npm install -g @nanonets/graft@0.10.1
npm ls -g @nanonets/graftnpm ls -g 应输出 @nanonets/graft@0.10.1。请有意固定该版本。直接使用 npm install -g @nanonets/graft 会在运行时解析 latest 标签;如果一个项目每月发布多个次要版本,那么您周二运行的工具可能会与同事周一安装的版本不同。固定版本可以让所有人的 CLI 标志和图格式保持一致,只有在您决定升级时才会变更。
然后,将它接入您拥有的代码仓库:
cd /path/to/your/repo
graft init --dry-run
graft initgraft init 会询问要接入哪些编码代理,然后构建图。请先运行 --dry-run,并查看它计划修改的文件列表,因为其中一些文件位于代码仓库之外。graft init 具有幂等性,不会覆盖现有配置,因此再次运行是安全的。
截至 2026 年 8 月,该接入功能支持 Claude Code、Cursor、Codex、GitHub Copilot、Google Gemini、Kiro、Windsurf 和 AdaL。Claude Code 的集成最深入:包括 MCP server 条目、显示图规模和陈旧状态的 statusline、用于重建图的编辑后 hook,以及位于 .claude/ 下的 skill 文件。其他代理会获得 instruction 或 rule 文件,用于告知代理这些工具可用。因此,“支持”表示 Graft 会写入接入配置;如果代理跳过自己的规则文件,也会跳过该映射。这是代理忽略您为其编写的指令的常见原因,在这里同样适用。
进入代码仓库的内容,以及不会进入 git 的内容
执行 graft init 后,预计会出现以下内容:
graft/:Markdown 节点图和graft/.graph/wiring.json。系统会将其添加到.gitignore。.mcp.json:注册 graft MCP 服务器,以便 Claude Code 启动它。.claude/settings.json:原位合并,添加状态栏和编辑后钩子。AGENTS.md、GEMINI.md、.github/copilot-instructions.md、.cursor/rules/graft.mdc、.kiro/steering/graft.md、.windsurf/rules/graft.md和.adal/skills/graft/SKILL.md:追加到与所选代理匹配的文件中,并使用标记分隔相应部分。~/.codex/config.toml、~/.codex/hooks.json和~/.codex/hooks/graft/graft-hooks.cjs:作用于整台计算机,仅在选择 Codex 时写入。graft init --no-global会跳过这些文件,graft init --no-hooks单独跳过钩子 shim。
该图是缓存,类似 node_modules。不要提交它。它会在几秒内根据代码重新生成,几乎每次编辑都会变化。提交它会把一行修复变成包含数百个文件的差异,审阅者通常不会查看这些内容。应提交连接配置,包括 AGENTS.md 和 .mcp.json。团队成员克隆仓库后,运行 graft build,即可生成自己的本地图。
首次提交前,请确认忽略规则已生效:
grep -n graft .gitignore
git status --shortgrep 应输出包含 graft/ 的行,git status --short 在 graft/ 下不应列出任何内容。如果输出中出现 graft/ 下的文件,说明忽略条目缺失,或已在其他位置被覆盖。请在提交前修复,因为 git 一旦开始跟踪某个文件,后续的 .gitignore 编辑不会取消跟踪。
如果您希望手动注册 MCP 服务器,或将其固定为已安装的同一版本,条目很短:
{
"mcpServers": {
"graft": {
"command": "npx",
"args": ["-y", "@nanonets/graft@0.10.1", "mcp"]
}
}
}代理调用的检索工具,而不是 grep
Graft 通过 MCP 提供六个工具。graft_find_code 根据任务描述返回排序后的节点,并附带文件和行号。graft_file_api 返回文件中的所有签名,不包含函数体。graft_trace_calls 逐层遍历调用者或被调用者。graft_find_all 返回按符号分组的正则表达式匹配结果。graft_repo_map 用于初步了解陌生代码仓库。graft_check_freshness 报告代码图是否仍与代码一致。
每个工具都有对应的 CLI 命令。通过这些命令,可以检查代理实际收到的内容:
graft map .
graft ask "where do we validate the refresh token"
graft skeleton src/auth/session.ts
graft callers validateRefreshToken
graft callers validateRefreshToken --direction out
graft grep "refresh_token" --jsongraft ask 应返回带有 file:line 引用的排序节点,而不是文件内容。整个机制就是这样:代理收到一个指针后只打开一个文件,而不是读取十个文件来找到正确文件。graft viz 会在 localhost 上打开交互式查看器,供您自行查看代码图。如果针对一个您能在三十秒内回答的问题,graft ask 没有返回有用结果,说明代码图已过时,或者您的语言属于宽泛层级。此时,这张图也无法帮助代理。
还有一项容易忽略的成本。每次请求都会将六个工具定义注入整个会话的系统提示词。无论代理是否使用代码图,您都要承担这项成本。对于足以放入上下文的较小代码仓库,这项固定开销可能高于它节省的探索成本。
代码发生变化时,图会怎样
结构刷新成本低,而且会自动进行。Graft 读取的是工作树,而不是 git,因此未提交的编辑和已暂存的编辑对它同样可见。查询只会重新解析 stat 发生变化的文件。项目文档称,这部分开销约为 3 ms。每轮结束时的重建只处理代码发生移动的文件。设置 GRAFT_NO_REFRESH=1 或传入 --no-refresh,即可直接根据磁盘上的图回答,而无需重新解析。传入 --no-reuse 可强制对所有内容执行冷重新解析。升级 Graft 本身后应使用此选项。
由模型生成的部分行为不同,也是最容易在没有明显提示的情况下出错的部分。摘要和关键点会被缓存。每个节点都会记录其源文件的内容哈希,因此源文件发生变化时,节点会被标记为过期,而不会继续显示为最新状态。只有实际执行相应操作时,这个标志才有用。使用 graft build --deep 刷新,这会再次消耗模型 token。
让过期状态可见:
graft check .
echo $?退出状态为 0 表示图与代码一致。退出状态为 1 表示存在偏差。可从 pre-push hook 中运行此命令,或在 CI 中针对该分支运行,这样一份六个月前的地图就不会再对 3 月重写的代码给出看似确定的回答。
仔细阅读已发布的基准测试数据
Graft 的核心宣传语是“成本最高降低 4 倍、速度最高提升 3 倍,同时正确性更高或不受影响”。这些数据来自项目自己的基准测试,已发布在其 README 中。以下是报告中的两次完整运行结果。
The data behind this chart
[
{
"label": "Controlled sweep",
"run_count": 162,
"token_saving_pct": 42,
"tool_call_saving_pct": 46,
"correctness_pct": 93,
"baseline_correctness_pct": 93
},
{
"label": "SWE-bench Verified",
"run_count": 50,
"token_saving_pct": 23,
"tool_call_saving_pct": 25,
"correctness_pct": 66,
"baseline_correctness_pct": 54
}
]受控测试在两个代码仓库上运行了 162 次,其中一个是 Graft 自身的代码仓库;每个任务进行了 3 次试验。结果显示,使用的 token 减少了 42%,工具调用减少了 46%。SWE-bench Verified 测试运行了 50 个实例,两组使用相同的模型;结果显示节省幅度较小:token 减少 23%,工具调用减少 25%。第三次测试复现了 5 个已合并的 PocketBase pull request,成本为 11.02 美元;基线成本为 13.91 美元。
请将这些结果全部视为厂商基准测试。两点因素限制了这些结果的适用范围。受控测试包含 Graft 自身的代码仓库,而该代码库正是其作者针对性调优的对象。SWE-bench Verified 是一个公开数据集,包含来自知名开源 Python 项目的问题;无论是否有人有意为之,工具通常都会针对公开数据集进行优化。两者都不能代表您的私有 monorepo,因为您的代码库有自己的命名习惯,也有自己的死代码。
正确性需要单独分析。在受控测试中,正确性没有变化:使用 map 时为 93%,不使用时为 93%。从 54% 提升到 66% 的结果只出现在 SWE-bench Verified 中。如果某个工具能降低 token 成本,同时保持质量不变,仍然是合理的选择。但不要把 SWE-bench 的正确性结果与受控测试的 token 结果拼接起来,当作一个结论引用。
在相信任何结论前,先测量您自己的 token 差值
唯一有意义的数字来自您的代码仓库。此方法需要一个下午。
选择一个可以完全重复的任务。问题比编辑更合适,因为编辑会修改代码仓库,第二次运行时实验条件就不再相同。“登录路由上由哪个模块实施速率限制”就是合适的问题形式。
启用遥测,并将其发送到您自己的终端:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
claude控制台导出器会在指标记录采集时打印这些记录。您需要关注的是 claude_code.token.usage,它包含一个 type 属性,值为 input、output、cacheRead 或 cacheCreation。定向信息会出现在 input 和 cacheRead 中,因为文件内容会写入这两个字段。将这两个值相加。
在启用映射的情况下,分别在全新会话中运行任务 3 次。然后从 .mcp.json 中移除 graft 条目,再运行 3 次。比较中位数,不要比较单次运行结果,因为 agent 运行的波动很大,某次异常运行可能会得出相反结论。同时记录工具调用次数:工具调用是机制,token 是结果。因此,如果 token 减少但工具调用次数没有下降,说明变化发生在其他地方。
然后扣除基准测试未显示的成本。graft build --deep 每次完整刷新都会消耗模型 token。每个请求都会携带 6 个工具 schema。如果您的 agent 运行在租用的服务器上,为 agent 支出设置硬上限 可以将意外支出转变为预算;启用导出器后,编码 agent 的遥测实际报告哪些内容 会说明哪些数据会离开机器。
代码库映射从哪里开始失去作用?
- 代码仓库已经可以放入上下文。 单个小型服务不需要映射,而且每次请求仍要为六个工具模式付费。如果代理今天通过一到两次工具调用就能找到任意文件,可以跳过映射。
- 所用语言属于广泛支持的层级。 通用调用边可能导致
graft callers漏掉调用方,或因名称冲突生成错误的调用方。在信任影响范围前,使用graft grep进行确认。 - 图已经过时,但没有人发现。
graft check在检测到漂移时以状态码 1 退出;只有实际运行它,这项检查才有用。应将其接入钩子或 CI 步骤,而不是依赖日常习惯。 - 单体仓库需要限定范围。 单个 Git 单体仓库会根据工作区文件、
go.mod、pyproject.toml或Cargo.toml自动拆分,graft ask "..." --in services/billing/可将查询限定到一个子项目。将映射按子项目拆分的思路,与为每个软件包设置嵌套 AGENTS.md 文件相同。 - 代理忽略了连接关系。 在真实会话中观察工具调用,再判断代理是否在使用映射。代理如果仍在运行
grep,说明它从未读取规则文件。
FAQ
是否应将 graft/ 文件夹提交到 git?
不应提交。graft build 会自动将 graft/ 添加到您的 .gitignore,因为该图是可重新生成的缓存,类似于 node_modules。它几乎会在每次编辑后发生变化,因此提交它会让数百个生成文件掩盖真正的差异。应提交用于告知代理该映射存在的配置,包括 AGENTS.md 和 .mcp.json,并让每位协作者在本地运行 graft build。首次提交前,请使用 grep -n graft .gitignore 和 git status --short 验证,因为文件一旦被添加,git 就会继续跟踪它;之后再编辑 .gitignore 不会取消跟踪。
运行 Graft 需要付费吗?
结构分析部分不需要。graft build、graft ask、graft check 和 6 个 MCP 检索工具都是 tree-sitter 操作,不会调用模型。graft build --deep 是付费部分:它通过 LLM 生成纯英文摘要和每个符号的关键说明,并使用 GRAFT_PROVIDER、GRAFT_API_KEY 和 GRAFT_MODEL 进行配置;对于任何兼容 OpenAI 的端点,还需使用 GRAFT_BASE_URL。您可以只运行 Graft 的结构分析部分,不为图本身消耗任何 token。
代码库映射实际能为我的仓库节省多少开销?
不进行测量,没人能告诉您。该项目报告称,在自身进行的 162 次运行测试中,token 数量减少了 42%;在 SWE-bench Verified 上减少了 23%,两者都以不使用映射的基线为对照。这些都是厂商基准测试,其中一项测试部分使用了 Graft 自身的仓库,且两项都不能代表您的私有代码。请使用已设置的 CLAUDE_CODE_ENABLE_TELEMETRY=1 和 OTEL_METRICS_EXPORTER=console,对同一个可重复的问题分别在启用和禁用映射的情况下运行 3 次,然后比较 claude_code.token.usage 在 input 和 cacheRead 类型中的中位数。
重构时图会发生什么变化?
结构会自动重新解析。Graft 会对工作树进行统计,并且只重新解析发生变化的文件,因此重命名会在下一次查询时被发现,额外开销约为 3 ms。它可以看到未提交的修改,因为它读取的是文件,而不是 git 历史。模型生成的摘要会变得过时:每个节点都会保存其源文件的内容哈希;源文件发生变化时,节点会被标记为过时,而不会立即重写。运行 graft check . 查看差异,然后运行 graft build --deep 刷新生成的内容。
目前哪些编码代理可以使用 Graft?
截至 2026 年 8 月,graft init 已接入 Claude Code、Cursor、Codex、GitHub Copilot、Google Gemini、Kiro、Windsurf 和 AdaL。Claude Code 获得的集成最完整:包括 .mcp.json 中的 MCP 服务器条目、状态栏、编辑后钩子,以及 .claude/ 下的 skill 文件。Codex 获得一个 AGENTS.md 部分,以及 ~/.codex/ 下的全局条目;graft init --no-global 不会处理这些条目。其他代理会获得 rules 文件或 steering 文件。任何其他 MCP 客户端都可以直接使用该服务器,只需注册命令 npx -y @nanonets/graft@0.10.1 mcp。