NanoNets/Graft

GitHub: NanoNets/Graft

Graft 为大型代码库构建可持久化、可共享的上下文图谱,帮助 AI 编程 agent 减少重复探索、降低 token 消耗并提升响应速度。

Stars: 535 | Forks: 41

Graft — open-source context layer for large codebases ### 全面加速 Claude Code、Cursor、Codex、Gemini 及各类编程 agent:更快、更省,并具备针对您代码库的上下文理解能力。

### 最多可节省 **4倍** 成本,提升 **3倍** 速度,且准确率持平甚至更好。 | 对比无 Graft 的标准会话 | 使用 Graft | |---|---| | 工具调用 | **减少 46%** | | Tokens | **减少 42%** | | 耗时 | **减少 60%** | | 准确率 | **持平** | 上表来自一次包含 162 次运行的受控基准测试(相同的 agent、相同的文件工具,仅上下文不同)。“最多节省 4倍成本 / 提升 3倍速度”的数据来自于另一项针对真实代码库(PocketBase、ollama、Excalidraw)测试中单任务的最大优化结果。[完整方法与各代码库数据 ↓](#tested-on-your-popular-repos)

Two commands — npm install and graft init — then Graft rides along in a Claude Code session, statusline synced

## 目录 - [快速开始](#quick-start) - [问题背景](#the-problem) - [Graft 的作用](#what-graft-does) - [图是如何构建的](#how-the-graph-gets-built) - [节点的构成](#whats-in-a-node) - [运行机制](#what-runs-where) - [Agent 集成](#agent-integration) — [MCP 服务器](#mcp-server) · [Claude Code (深度集成)](#claude-code-deep-integration) - [命令行接口](#cli) - [搜索与定向](#search--orient-graft-grep--graft-map) (`graft grep` / `graft map`) - [Monorepos 与多代码库文件夹](#monorepos--multi-repo-folders) - [可视化](#visualize-it-graft-viz) (`graft viz`) - [基准测试](#benchmark) - [在热门代码库上的测试](#tested-on-your-popular-repos) - [开发指南](#development) - [许可证](#license) ## 快速开始 ``` npm install -g @nanonets/graft # install the CLI, once graft init # build the graph + wire it into Claude Code ``` 这就是设置的全部内容。`graft init` 会询问您要连接哪些编程 agent,根据您的代码构建 `graft/` 目录,并将状态栏和钩子注入到 `.claude/` 中。因此,从下一次会话开始,Graft 将在 Claude Code 中协同工作:它会将匹配的节点拉取到每个 prompt 中,并在每轮对话后在后台重建图。默认情况下无需 daemon,无需记住重新索引,也无需运行或维护任何东西——图仅仅是文件。 在您做出选择之前,不会写入任何内容。运行 `graft init --dry-run` 可以首先查看它会触及的每个文件,或者运行 `graft init --agents claude` 跳过提示并仅连接 Claude Code。 `graft build` 会自动将 `graft/` 添加到您的 `.gitignore` 中——图是一个本地、可重新生成的缓存(就像 `node_modules` 一样),而不是您需要提交的内容。您共享的是 `init` 注入到 `.claude/` 中的连接配置;每个团队成员运行 `graft build` 来生成他们自己的图: ``` git add .claude && git commit -m "wire in graft" ``` 不想全局安装?`npx @nanonets/graft init` 的效果是一样的。 ## 问题背景 每次任务开始时,您的编程 agent 都是盲目启动的。在它更改任何内容之前,它会重新探索代码库:grep 一个术语,打开一个文件,追踪一个 import,退回,重试。它正在重新构建一个小时前映射过但又丢弃的代码库全貌。这种重新发现过程消耗了运行过程中大部分的工具调用、tokens 和延迟,而且这纯粹是开销: - **不断重复。** 每次任务都要从零开始付出探索成本。 - **用后即弃。** agent 搞清楚的任何内容都会随会话一起消亡。 - **无法共享。** 下一个团队成员及其 agent 也要从零开始。 人类只需熟悉一次代码库。而 agent 每次都要重新熟悉。

A no-map agent's exploration trail wandering file to file before it finds what it needs

## Graft 的作用 Graft **一次性**构建起这种理解,并将其作为包含相互链接的 markdown 文件夹写入您的代码库中,每个系统、API 或概念对应一个节点。 - **真正的解释,而不是符号列表。** 每个节点都用通俗易懂的英文说明系统的某一部分是做什么的,以及它如何与其余部分连接,就像资深工程师讲解的那样。这正是 agent 真正需要的部分,以便它可以跳过探索。它绝不是函数名的简单堆砌。 - **可读的真实图谱。** 没有 embedding,没有相似性搜索,也没有需要保持活跃的索引。图是一组链接的文件,您的 agent 可以像读取代码库中的任何其他文件一样打开、grep 和追踪它们。 - **接入 git。** 图仅仅是 `graft/` 中的文件。提交它,任何 clone 该代码库的人都能拥有它。没有数据库,没有服务器,无需设置。git 负责同步,过时的图会在代码审查中显示为 diff,而不是在某个外部存储中逐渐腐坏。 - **diff 与代码同在。** 当更改引起文件变动时,您可以在同一个 pull request 的图 diff 中看到它,就在导致该变动的代码旁边。 - **您的 provider、您的密钥、您的模型。** 摘要由您选择的任何 provider 编写——OpenAI、Anthropic(原生)、OpenRouter、Fireworks、Groq、LiteLLM 代理或本地模型——并使用您自己的密钥。结构性代码图(`graft build`、`graft check`)是确定性的 tree-sitter,根本不会调用任何模型。 ## 图是如何构建的 Graft 通过两个步骤构建图,这两个步骤均由语言模型驱动: 1. **读取每个文件。** 每个源文件都会被概括成一段描述其功能的简短说明。 2. **分组成节点。** 这些摘要被分组成经过精选的节点集(子系统、关键文件和概念),并在它们之间建立带有类型的链接。Graft 会为您选择合适的详细程度,而不是每个文件生成一个节点,因此大型代码库会变成几十个可读的节点。 ``` flowchart LR S[Source files] --> T["Tier 1 — tree-sitter
no model, no key"] S --> P1["Pass 1 — LLM summarizes
each file (--deep)"] T --> W["graft/.graph/wiring.json
per-symbol code graph"] P1 --> P2["Pass 2 — group into nodes
+ typed links"] P2 --> N["graft/*.md
markdown node graph"] ``` 每次步骤都会根据内容哈希进行缓存——无论是 LLM 生成的部分还是 tree-sitter 解析的部分都是如此。重新运行只会触及已更改的文件,因此第二次构建既快又便宜(在这个代码库中,124 个文件:冷启动 0.74 秒,修改一个文件后 0.18 秒,无修改时 0.18 秒)。`graft build --no-reuse` 强制进行冷重新解析。 这种低成本使得 **每次查询都能在回答之前刷新图**。检索调用会将树结构与上次构建的指纹进行对比(约 3 毫秒),仅当发生变动时才重新构建——因此 `ask`/`grep`/`callers`/`skeleton`/`map` 描述的是当前的代码状态,包括尚未提交到 git 的编辑内容:未提交、未暂存或已暂存对 graft 来说都是一样的,它完全不读取 git。这种刷新是结构性的且花费 `$0`;它从不调用 LLM。可以使用 `--no-refresh` 针对特定命令关闭它,或者使用 `GRAFT_NO_REFRESH=1` 全局关闭。 除了 markdown 图谱,`graft build` 还会构建 `graft/.graph/wiring.json`——一个按符号划分的代码图——以及一个镜像您源代码树的按文件划分的连接卡片。第一层是纯粹的 tree-sitter(每一个函数、类和调用边;确定性、无模型、无网络),这就是为什么普通的 `graft build` 不需要密钥。`--deep` 步骤会为每个符号添加一行摘要和关键代码片段,并按函数体哈希进行缓存。 ## 节点的构成 节点是一个单一的 markdown 文件。大多数代码映射只停留在一个地址上:这个东西存在于那个文件、那一行。这只告诉 agent 去哪里找,而不是它会找到什么,所以它仍然需要打开源代码并阅读。Graft 节点将含义内嵌其中,因此 agent 可以预先学到所需知识,仅在需要更多内容时才打开文件。 每个节点包含: | 部分 | 包含内容 | |---|---| | **摘要** | 模型编写并缓存的关于代码功能的通俗英文解释。无论代码以前是否有文档记录,它都在那里,并且会在源代码更改时重新生成。 | | **核心** | 真正承载逻辑的那几行代码:守卫语句、跳过条件、状态改变。直接从源代码中提取并内嵌存储,因此 agent 可以看到它是*如何*工作的,而不仅仅是知道它的作用。 | | **来源** | 构建该节点的确切文件,每个文件都由内容哈希进行追踪,因此 Graft 可以精确判断节点何时过时。 | | **链接** | 到其他节点的类型化连接(`depends_on`、`part_of`、`uses`、`implements`、`produces`),编写为您的 agent 可以追踪的 `[[wikilinks]]`。 | | **备注** | 您在生成块下方编写的任何内容。它会在重新生成时保留,因此您自己的上下文永远不会被覆盖。 | 这就是在一个文件中包含的三个深度:摘要说明代码做*什么*,核心展示*如何*做,如果 agent 需要更多信息,来源指向其余部分。普通的索引会让它读取整个文件来学习一件事。而 Graft 节点将答案内嵌其中,后续的读取通常根本不需要发生。 核心被特意存储为代码本身,而不是行范围。每当它们上方的无关代码发生变动时,行号都会偏移,但重要的代码行不会。保留文本而不是数字,意味着即使周围的文件发生移动,核心依然保持正确。 _摘要、来源、链接和备注目前在 markdown 节点中可用。核心在代码图中按符号提供(`graft build --deep`);将其内联到 markdown 节点是接下来的计划。_ ## 运行机制 - **在您的机器上运行,无需密钥,无需网络:** 结构化代码图。`graft build`(连接图 + 按文件划分的卡片)、`graft check` 和 `graft ask` 都是确定性的 tree-sitter——它们从不调用模型。 - **通过您的 provider 密钥:** LLM 编写的部分——`graft build --deep` 会添加概念节点(文件摘要 + 节点合成)以及按符号划分的摘要和核心。graft 是供应商中立的:设置 `GRAFT_PROVIDER`(对于任何兼容 OpenAI 的 endpoint 设为 `openai`,或者原生 API 设为 `anthropic`)、您的 `GRAFT_API_KEY`、`GRAFT_MODEL`,以及——对于 `openai` 网络格式——设置 `GRAFT_BASE_URL` 指向 OpenRouter、Fireworks、Groq、LiteLLM 代理、本地服务器或 OpenAI 本身。或者在命令行中传入 `--provider/--model/--api-key/--base-url`。(`OPENROUTER_API_KEY` 仍然可以作为已弃用的回退方式使用。) - **无遥测**,无分析——唯一的网络调用就是您配置的 LLM 请求。 有关设置的完整列表(模型、base URL、图目录),请参见 [`.env.example`](.env.example)。 ## Agent 集成 只需一条命令即可将 Graft 接入您使用的编程 agent: ``` npx @nanonets/graft init # 检测你的 agents 并为每个写入其原生 instruction 文件; # Claude Code 还会获得下方的实时 statusline + hooks ``` 在终端中,`init` 会显示它了解的每一个 agent——标记出它检测到的那些(通过它们的配置目录),并列出每个 agent 将要写入的确切文件——并且仅连接您选择的那些。Claude Code 会被预选;其他则不会。被选中的 agent 会在其共享的指令文件中获得一个带有标记的 Graft 部分——`AGENTS.md`(Codex、OpenCode 和其他读取它的 CLI 工具)、`GEMINI.md`、`.github/copilot-instructions.md`——或者对于使用专属文件的 agent,会获得一个完全专属的规则/技能文件——`.cursor/rules/graft.mdc`、`.kiro/steering/graft.md`、`.windsurf/rules/graft.md`、[AdaL](https://adal.sylph.ai) 的 `.adal/skills/graft/SKILL.md`(渐进式披露技能,与下面的 Claude Code 技能形状相同)。重新运行只会更新 Graft 自己的部分(或替换专属文件),绝不会触及您其余的内容。 在没有 TTY 可供提示的情况下——例如 CI、Dockerfile、管道 shell——`init` 不会写入**任何内容**,而是打印出要运行的命令。传入 `--agents ` 或 `--yes` 以使脚本运行变得明确。 | 标志 | 效果 | |---|---| | `--agents ` | 仅连接这些,无提示——id 包括:`agents`、`cursor`、`gemini`、`copilot`、`kiro`、`windsurf`、`adal`、`claude` | | `--yes`、`-y` | 跳过提示并连接**检测到的** agent | | `--dry-run` | 打印 `init` 将触及的每个文件,然后退出而不写入 | | `--all-agents` | 为每个已知 agent 写入指令文件,无论是否检测到 | | `--no-agents` | 仅连接 Claude Code;跳过其他 agent | | `--list-agents` | 打印已知的 agent id 并退出 | | `--no-mcp` | 跳过 MCP 服务器注册 | | `--no-hooks` | 跳过钩子安装 | | `--no-global` | 跳过在此代码库之外的写入(即下方的 `~/.codex/` 条目) | #### 代码库之外的写入 选择 `agents` 主机时,如果存在 `~/.codex/`,还会触及您**用户级别**的 Codex 配置: | 路径 | 更改内容 | |---|---| | `~/.codex/config.toml` | 注册 Graft MCP 服务器 (`[mcp_servers.graft]`) | | `~/.codex/hooks/graft/graft-hooks.cjs` | 编辑后钩子垫片 | | `~/.codex/hooks.json` | 匹配 `Write\|Edit\|MultiEdit` 的 `PostToolUse` 条目 | 这两个配置都是用户级别的,因此它们适用于您用 Codex 打开的**每个**代码库,而不仅仅是这一个。选择器将这些标记为 `machine-wide`,`--dry-run` 会在它们自己的部分列出它们,而 `--no-global` 会跳过它们,但仍然会连接 `AGENTS.md`。 ### MCP 服务器 `graft init` 还会向支持它的 agent 注册 Graft 的 MCP 服务器,因此这六个工具会原生显示,不需要 shell。Claude Code 也会获得此功能:`graft init` 会将服务器写入项目的 `.mcp.json` 中(重启 Claude Code 即可加载)。使用 `--no-mcp` 跳过;使用 `graft mcp [dir]` 手动运行。 | 工具 | 接收参数 | 用途 | |---|---|---| | `graft_find_code` | 一个问题 | 带有文件:行号、内联源代码的排序节点——通常是完整的答案,无需后续读取。 | | `graft_file_api` | 文件路径 | 该文件中的所有签名,无函数体——以十分之一的 token 获取 API 接口面。 | | `graft_trace_calls` | 一个符号 | 谁依赖它,或者使用 `direction: out` 指定它依赖什么,N 层深度追踪影响范围。 | | `graft_find_all` | 一个 regex | 每一个匹配项,按封闭符号分组,按该符号的耦合程度排序。 | | `graft_repo_map` | 无 | 第一次查看陌生代码库:目录集群、枢纽、热点。 | | `graft_check_freshness` | 无 | 本地图是否与代码产生了偏差。 | 如果您的 agent 需要显式指定,可以手动注册它: ``` { "mcpServers": { "graft": { "command": "npx", "args": ["-y", "@nanonets/graft", "mcp"] } } } ``` 如果 CLI agent 支持用户级别的 `hooks.json`,`init` 还会安装 Graft 的编辑后钩子——影响范围警告和编辑后自动进行 `$0` 图重同步(使用 `--no-hooks` 跳过)。 ### Claude Code(深度集成) `graft init` 始终会连接 Claude Code,并且 Claude Code 获得的不仅是指令文件。从那时起,在该代码库中打开的任何 Claude Code 会话都将获得: - **实时状态栏** —— 图大小、% 丰富度,以及当代码领先于图时出现的 `⚠ N stale` 警告 - **自动同步** —— 每次 graft 查询都会首先将图更新到最新状态,因此答案总是描述当前的代码,包括未提交的编辑。查询仅刷新其读取的内容;`graft/` 下的 markdown 是由一轮对话结束后触及代码的后台重建来刷新的。两者都是结构性的且花费 `$0` —— 自动同步绝不会自行调用 LLM - **随时可用的上下文** —— 每个 prompt 都会将匹配的节点拉入会话;编辑文件会呈现依赖它的内容(“影响范围”);新会话从代码库映射开始

graft's post-edit hook: editing node-file.ts prints its blast radius (who depends on it) inline, the statusline flips stale → syncing → synced on its own, and the same dependents light up in graft viz
edit a file → blast radius appears inline → graph auto-resyncs → confirmed in graft viz

`graft init` 是幂等的,并且永远不会破坏您现有的 `.claude/settings.json`——它只会合并自己的块,其余部分保持不变。想要 LLM 摘要?可以在任何时候运行 `graft build --deep`(需提供密钥);自动同步永远不会替您做这件事。 ## 命令行接口 ``` graft build [dir] # build graft/ from the code at [dir]: wiring graph + per-file cards (no LLM, no key) graft build --deep # add the LLM layer: concept nodes + per-symbol summary/crux (cached) graft build --extensions .ts .py # only include these code extensions graft build --no-reuse # re-parse every file instead of replaying unchanged ones from cache graft ask "" [dir] # query the graph — ranked nodes + exact file:line (no LLM, no key) graft ask "" --json # machine-readable result graft ask "" --in # narrow to one sub-project of a monorepo/multi-repo folder (see below) graft skeleton [dir] # every signature in one file, no bodies — the API surface for ~1/10th the tokens (no LLM, no key) graft callers [dir] # who calls/references/imports/implements/extends a symbol (no LLM, no key) graft callers --direction out # the reverse: what the symbol itself calls/references (was `graft callees`) graft callers -d N # walk transitively out to depth N — full blast radius (was `graft impact`) graft grep "" [dir] # exhaustive regex search over indexed files, grouped by enclosing symbol (no LLM, no key) graft grep "" --in # narrow to files whose path contains this substring graft grep "" -i --fixed # case-insensitive; treat the pattern as a literal string, not a regex graft map [dir] # token-budgeted repo orientation — dir clusters, hubs, hotspots (no LLM, no key) graft map --max-dirs N # raise/lower the number of directories shown graft check [dir] # fail (exit 1) if graft/ has drifted from the code (never auto-refreshes — it's the drift report) graft check --json # print the drift report as JSON # 如果工作树发生了移动,ask / skeleton / callers / grep / map 都会首先刷新 graph: # --no-refresh # 直接根据磁盘上 graph 的原样给出答案 # GRAFT_NO_REFRESH=1 # 对于每条命令都一样 # GRAFT_REFRESH=hash # hash 每个文件,而不是信任 size+mtime graft viz [dir] # see the graph: serves an interactive viewer on localhost graft viz --port 5000 --no-open # pick a port; don't auto-open the browser graft init [dir] # pick which agents to wire (prompts on a terminal; writes nothing until you choose) graft init --dry-run # list every file it would touch, then exit graft init --agents cursor kiro # wire only these agents, no prompt (ids: agents, cursor, gemini, copilot, kiro, windsurf, adal, claude) graft init --yes # no prompt; wire every detected agent graft init --no-global # skip writes outside this repo (~/.codex/ config + hooks) graft init --no-build # wire the files only; don't build the graph graft init --all-agents # wire every known agent, detected or not graft init --list-agents # list known agent ids and exit graft version # print the installed + latest published npm version graft upgrade # npm install -g the latest published version # global graft --dir # use a context dir other than /graft graft --version, -v # print the installed version and exit ``` 方法调用通过接收者的类型进行解析——构造函数赋值 (`self.router = APIRouter()`)和类型注解,而不仅仅是调用处的 名称——因此 `callers`/`grep --in` 返回的是绑定到正确 类型的方法调用,这对于包含大量方法的代码尤其有效,而不是返回任何地方具有该名称的方法。 ## 搜索与定向 (`graft grep` / `graft map`) `graft grep ""` 会穷举每一个已索引的文件,并按封闭的符号对命中 结果进行分组,其排序方式与 `graft map` 使用的入边耦合度一致—— 专为“此模式的每一次出现”类任务而构建,此时 `graft ask` 的 排序前 N 个结果不足以满足需求: ``` "NEEDLE" — 2 hits in 2 symbols across 1 files (searched 1 indexed files) heavilyCalled · function · src/a.ts:L1-L3 · 3 in-edges L2: console.log("NEEDLE hit in heavilyCalled"); rarelyCalled · function · src/a.ts:L4-L6 · 0 in-edges L5: console.log("NEEDLE hit in rarelyCalled"); ``` `graft map` 是在 token 预算内对代码库的首次查看——带有 文件/符号计数的目录集群、每个目录的本地枢纽以及全局热点—— 全部按入度排序,无需 LLM,无需密钥: ``` repo map — 113 files · 687 symbols · 2186 edges · typescript src/ 63 files · 527 symbols hubs: contextDirFor (node-file.ts, 21←), wiringPath (write.ts, 14←), buildGraph (build.ts, 11←) test/ 43 files · 102 symbols hubs: edge (graph-traverse.test.ts, 4←), graphOf (graph-traverse.test.ts, 4←), fileNode (graph-map.test.ts, 3←) viewer/ 5 files · 58 symbols hubs: $ (main.ts, 9←), activeGraph (main.ts, 5←), cvar (data.ts, 5←) scripts/ 2 files · 0 symbols hotspots: contextDirFor · function · src/context/node-file.ts:L100-L103 · 21← wiringPath · function · src/graph/write.ts:L20-L22 · 14← buildGraph · function · src/graph/build.ts:L104-L218 · 11← ... ``` ## Monorepos 与多代码库文件夹 Graft 无需任何配置即可处理两种形式: - **带有单个 `.git` 的 monorepo**(一个 `pnpm-workspace.yaml`/`package.json` `workspaces`,或者每个包各自的 `go.mod`/`pyproject.toml`/`Cargo.toml`)—— `graft build` 将每个子项目发现为一个排名范围。`ask`/`map` 会根据每个范围自身的条件进行排名并融合结果,因此最大的 子项目不会淹没小的子项目;命中带有 `[scope/]` 标签,并且 `graft map` 首先按范围对其目录集群进行分组。 - **包含多个独立 git 仓库的文件夹**(顶层没有 `.git`)——`graft build` 自动拆分:每个子项目获得自己(被 git 忽略的)`graft/`,而父级 获得一个 `graft/workspace.json` 索引。从父级发起的查询会跨越 每个子项目进行联合搜索,并始终带有 `/` 标签。在子项目内部运行 `graft build` 以仅处理该代码库。 无论是哪种情况,一旦您知道您在哪里工作,就可以使用 `graft ask "" --in /` 缩小到特定的子项目。 ## 可视化 (`graft viz`) `graft viz` 会打开两个图的本地交互视图——无需安装,无需开发 服务器;查看器已预构建并打包在内。

graft viz — searching a symbol and jumping to it lights up its dependency graph: amber edges are what it depends on, teal is what depends on it
search → jump to a node → dependency graph lights up

- **Context** 标签卡 —— 来自 `graft/*.md` 的架构图。节点按 类型着色,按连接度调整大小。 - **Code** 标签卡 —— 来自 `graft/.graph/wiring.json` 的按符号划分的图(首先运行 `graft build`)。 - **Outline** 标签卡 —— 文件 → 类 → 方法的层次结构,以可折叠树的形式呈现。 边使用代码的语言。每个链接都是一组封闭动词中的一个,每个动词都回答了构建或审查代码的人实际会问的问题: | 动词 | 它回答的问题 | |---|---| | `part_of` / `contains` | 它存在于哪里? | | `uses` / `calls` / `imports` / `depends_on` | 如果我更改这个,什么会损坏? | | `produces` | 这个输出是从哪里来的? | | `configures` | 什么能在不更改代码的情况下改变其行为? | | `validates` | 什么检查或评判这个?(测试、偏差检查、评分) | | `extends` / `implements` | 它必须遵守什么契约? | 选择一个节点,它的边就会带有方向性:**琥珀色 = 它依赖什么, 青色 = 什么依赖它**,并且每个高亮的边上都写有动词。画布上方的标签可以按动词进行过滤;由 tree-sitter 提取的边绘制为实线, 而由 LLM 推断的边绘制为虚线。当 `graft/` 在磁盘上发生更改时,查看器会实时重新加载。带有模糊动词(`influences`、`supports`)的旧图在加载时会被 标准化——无需重新生成。 ## 基准测试 读取图的 agent 应该变得更便宜、更快速,而不会增加错误的答案。这就是我们的全部主张,因此我们对其进行了测量,而不是仅仅断言。 测试工具使用相同的文件工具运行了同一个 Claude Sonnet 5 agent 的三个变体:**cold**(从零开始探索)、**Graft**(预先注入 `graft ask --source` 包)和 **pull**(graft_find_code/graft_file_api 工具,不注入任何内容——仅在需要时为上下文付费)。一个 Opus 4.8 评判器使用必需的关键词阈值对准确度进行评分,因此快速但错误的答案不能因为速度快而获胜。成本是感知缓存的:读取 ≈0.1×,写入 1.25×,这正是 agent 实际运行的计费模型。 共进行了 162 次运行,涵盖两个代码库(graft 本身和一个真实的 Node/Express 身份验证服务),每个任务进行 3 次试验,任务分为单文件和多文件问题。 | 指标(平均/任务) | Cold | Graft | |---|---|---| | 成本 ($) | 0.0429 | **0.0292 (−32%)** | | 未缓存的输入 tokens | 8,070 | **4,650 (−42%)** | | 工具调用 | 4.2 | **2.3 (−46%)** | | 延迟 (s) | 39.8 | **15.8 (−60%)** | | 准确率 | 93% | 93% (持平) | 在任何语料库中,Graft 的回答都没有比 cold 差。pull 变体为了更大的收益牺牲了大部分速度:准确率跃升至 98%,比 cold 高出 5 个百分点,这是测试中最强的单一结果。当您需要速度时选择 push;当正确性更重要时选择 pull。 ## 在热门代码库上的测试 上面的测试衡量的是机制。真正的考验在于 graft 是否能帮助 agent 在人们实际运行的代码上**交付真正的更改**,而不仅仅是回答问题。因此,我们在流行的开源代码库上对其进行基准测试:**每个代码库 15 项任务**,包含 10 个真实的开发者问题以及 **5 项实际的实施任务**(真实合并的 pull request,每一项都从其基础 commit 重新实现,并根据维护者实际更改的文件进行评分)。相同的 agent (Claude Opus),相同的文件工具;唯一的区别在于是否接入了 graft。 在这些代码库中,graft 的运行成本**最多低 4 倍,速度快 3 倍**,且准确率更好或没有损失:它通过修改与维护者相同的文件来重现真实合并的 PR。下面是各代码库的详细数据。 ### PocketBase (Go, ~350 个文件) | 15 项任务的汇总 | 标准 Claude Code | 使用 graft | |---|---|---| | 成本 | $13.91 | **$11.02 (−21%)** | | 挂钟时间 | 2,044s | **1,762s (−14%)** | | 重现的 PR | 5 / 5 | **5 / 5 (与维护者修改的文件相同)** | 更便宜、更快速且没有准确度损失:graft 重现了所有五个合并的 PR,触及了与维护者相同的文件。这种差距在跨文件理解上最为明显——“auth 如何跨 OAuth2 提供商工作”的成本从 $2.19 下降到了 $0.84。
我们提出的 10 个问题 1. **定向** — 给我一张 PocketBase 的架构图:主要子系统以及 HTTP 请求是如何流转到数据库的。 2. **入口点追踪** — 端到端追踪当客户端通过 REST API 创建记录时发生的事情,从路由处理程序到数据库写入。 3. **功能定位** — 我想添加一个全新的集合字段类型。我应该在哪里接入,哪些部分必须更改? 4. **Bug 定位** — 实时订阅在一段时间后会无声无息地停止发送事件。你会从哪里开始查找,为什么? 5. **影响范围** — 如果我更改记录验证逻辑的签名,什么依赖于它,什么可能会损坏? 6. **跨文件综合** — auth跨 OAuth2 提供商工作:tokens 在哪里颁发、验证、存储和刷新? 7. **可扩展性** — 我如何使用 PocketBase 作为 Go 框架来注册自定义路由以及记录创建时的钩子? 8. **安全性发现** — 用户输入在哪里被验证?在查询运行之前,集合 API 访问规则在哪里被执行? 9. **公共 API** — 作为外部应用程序,我如何通过 REST API 进行身份验证,然后列出和筛选记录? 10. **测试验证** — 记录 CRUD API 的测试在哪里?它们对访问规则有什么断言?
我们重新实现的 5 个合并的 PR 每个 PR 都被重置到其基础 commit;graft 的 diff 根据合并的 PR 修改的文件进行评分。 | PR | 类型 | 功能描述 | 维护者修改的文件 | |---|---|---|---| | [#6744](https://github.com/pocketbase/pocketbase/pull/6744) | feat | 生成并提供 WebP 缩略图 | `apis/file.go`, `tools/filesystem/filesystem.go` | | [#6947](https://github.com/pocketbase/pocketbase/pull/6947) | fix | regex 随机字符串中的均匀字符分布 | `tools/security/random_by_regex.go` | | [#6690](https://github.com/pocketbase/pocketbase/pull/6690) | refactor | Patreon OAuth2 使用 `x/oauth2/endpoints` | `tools/auth/patreon.go` | | [#2726](https://github.com/pocketbase/pocketbase/pull/2726) | perf | 移除热点中间件路径上冗余的管理员计数查询 | `apis/middlewares.go` | | [#3192](https://github.com/pocketbase/pocketbase/pull/3192) | fix | 在自动迁移回滚时恢复之前的 API 规则 | `plugins/migratecmd/templates.go` |
方法 同一个 commit 下的两个 PocketBase 克隆:一个通过 `graft init` 接入,另一个保持原样并验证为无 graft。每个任务都在无头模式(`claude -p`,Claude Opus)下使用空的 MCP 配置运行。理解类问题根据答案是否指向正确的文件和函数来评分;PR 任务根据 agent 的 diff 是否触及与合并的 PR 相同的文件来评分。每份记录都经过审计,以确认在 graft 分支中确实使用了 graft,而在标准分支中则没有使用。
## 开发指南 ``` git clone https://github.com/NanoNets/context-graph-engine.git && cd context-graph-engine npm install npm run build npm test npm run cli -- build --deep . # run the CLI from source ``` ## 许可证 MIT。详见 [LICENSE](LICENSE)。
标签:AI编程助手, MITM代理, SOC Prime, 上下文管理, 代码索引, 优化工具, 大型代码库, 开发工具, 自动化攻击