上一篇:Context Explorer:调试 Prompt 与 Token 使用
下一篇:Patch Editing:安全应用 Unified Diff
English version: Git Diff Tools for AI Coding Agents: Status, Parsed Patches, and Change Summaries
文章介绍
Day 09 我们实现了 Context Explorer,用来观察一次 agent run 里 system prompt、tool definitions、project snippets、conversation 和 tool results 分别占了多少 token。
今天开始把 agent 和真实工作区变化连接起来。
Coding agent 在修改代码前,至少要知道三件事:
- 当前分支和工作区是否干净。
- 哪些文件已经被修改、新增、删除或未跟踪。
- 当前 diff 具体改了什么,以及 diff 是否太大需要截断或摘要。
如果没有 Git 视角,agent 很容易误判用户已有改动,甚至在后续 patch 阶段覆盖不该碰的文件。Day 10 的目标是实现两个只读工具:git_status 和 git_diff。

今天要解决什么
今天完成五个交付:
- 新增只读
git_status工具,读取当前 Git working tree 状态。 - 新增只读
git_diff工具,读取 unstaged 或 staged diff。 - 使用成熟的
diffnpm 包解析 Git unified diff,而不是手写 patch parser。 - 给 diff 输出增加文件级统计:changed files、additions、deletions、hunks、binary/create/delete/rename。
- 更新 mock provider,让本地 demo 可以演示
git_status -> git_diff -> final工具链。
Demo 命令:
npm run dev -- "check git status and summarize current diff"
也可以单独调工具:
npm run dev -- --tool git_status --tool-input '{}'
npm run dev -- --tool git_diff --tool-input '{"maxBytes":4000}'
它在 Cursor/Codex 里对应哪一层
今天实现的是 workspace change inspection layer。
在 Cursor、Codex、Claude Code 这类 coding agent 中,Git 工具不是“附加功能”,而是 agent 做代码修改前的安全前提:
What files are already changed?
Are there untracked files?
What exactly is in the current diff?
Is this diff small enough to send to the model?
Did the user already modify a file I am about to edit?
这和 Day 08、Day 09 的上下文能力直接相关:
- Day 08 Context Builder 负责把必要信息放进模型请求。
- Day 09 Context Explorer 负责解释这些信息的 token 成本。
- Day 10 Git/Diff Tools 负责提供当前工作区变化这个关键信息源。
后续 Day 11 做 patch editing 时,agent 就可以先读 diff,再决定是否应用 patch。
设计思路
1. Git 工具必须先只读
Day 10 只实现 inspect,不做 mutate。
工具内部只调用:
git status --short --branch --untracked-files=all
git diff --no-ext-diff --no-color
不实现:
git checkout
git reset
git add
git commit
原因很简单:status 和 diff 是观察能力,checkout/reset/add/commit 是状态修改能力。它们应该进入 approval/sandbox 体系后再设计,而不是在 Day 10 混在一起。
2. 保留 raw diff,同时提供结构化摘要
git_diff 的输出分两层:
第一层是原始 unified diff:
{
"diff": "diff --git a/file.ts b/file.ts\n...",
"truncated": false,
"bytes": 1234,
"returnedBytes": 1234
}
第二层是解析后的文件摘要:
{
"fileCount": 2,
"additions": 18,
"deletions": 4,
"files": [
{
"oldPath": "apps/mini-harness/src/tools/registry.ts",
"newPath": "apps/mini-harness/src/tools/registry.ts",
"additions": 2,
"deletions": 0,
"hunks": 1,
"binary": false,
"created": false,
"deleted": false,
"renamed": false,
"copied": false
}
]
}
raw diff 给模型看细节,结构化摘要给 harness 做预算、排序和 UI 展示。
3. Diff parser 用成熟库
用户特别提到 “git diff 的工具应该有很成熟的,这个可以直接找一个现成的库来实现”。这里接入的是 npm 包 diff。
它提供的 parsePatch 能解析 unified diff,并且新版已经支持 Git 风格 patch headers、rename、copy、binary、new file、deleted file 等信息。这样我们不需要自己写 fragile parser。
工具里的职责变成:
git diff stdout
-> diff.parsePatch(stdout)
-> summarize patches
-> return raw diff + file summaries
4. 输出必须有截断信息
Diff 可能非常大,不能无上限塞进上下文。
git_diff 支持:
{
"maxBytes": 4000
}
返回:
{
"bytes": 190000,
"returnedBytes": 4000,
"truncated": true
}
这让 agent 和 Context Explorer 都能知道:当前看到的只是 diff 前半段,不应该把它当成完整事实。
5. 路径参数必须限制在 workspace 内
两个工具都支持可选 path:
{
"path": "apps/mini-harness/src/tools"
}
实现时会先 resolve 到 workspace root 内部,再把相对路径作为 Git pathspec 传给 git。这样可以避免工具被拿来读取 workspace 外部路径。
实现步骤
1. 安装 diff 包
文件:apps/mini-harness/package.json
新增依赖:
{
"dependencies": {
"@mini-harness/shared": "0.1.0",
"diff": "^9.0.0"
}
}
diff 包自带 TypeScript 类型,所以不需要额外安装 @types/diff。
2. 新增 Git tools
文件:apps/mini-harness/src/tools/git.ts
注册函数保持和 Day 05 local file tools 一样的风格:
export function registerGitTools(registry: ToolRegistry, workspaceRoot: string): void {
const root = resolve(workspaceRoot);
for (const tool of createGitTools(root)) {
registry.register(tool);
}
}
git_status 的 schema 很小:
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
},
}
它返回 branch、clean flag、status entries 和 raw status:
const stdout = await runGit(workspaceRoot, [
"status",
"--short",
"--branch",
"--untracked-files=all",
]);
3. 用 parsePatch 解析 git diff
git_diff 先读取 Git 输出:
const diff = await runGit(workspaceRoot, args);
再解析:
const patches = parsePatch(diff);
const files = patches.map(summarizePatch);
摘要函数只关心 agent 决策需要的字段:
return {
oldPath,
newPath,
additions,
deletions,
hunks,
binary,
created,
deleted,
renamed,
copied,
};
如果 parser 遇到异常,工具不会丢掉 raw diff,而是返回:
{
"parseError": "..."
}
这样模型仍然能读取原始 diff,harness 也能明确知道结构化解析失败了。
4. 注册到默认工具表
文件:apps/mini-harness/src/tools/registry.ts
默认 registry 现在包含:
registerLocalFileTools(registry, workspaceRoot);
registerGitTools(registry, workspaceRoot);
这意味着 agent loop、manual tool dispatch、transcript logging、context report 都能看到 Day 10 工具。
5. 更新 mock provider 的 demo 路径
文件:apps/mini-harness/src/model/provider.ts
当任务包含 git、diff、status、changes 等关键词时,mock provider 会依次返回:
TOOL_CALL {"name":"git_status","input":{}}
TOOL_CALL {"name":"git_diff","input":{"maxBytes":4000}}
这样即使本地没有配置真实模型 API key,Day 10 demo 也能验证工具链和 agent loop。
Demo
运行:
npm run dev -- "check git status and summarize current diff"
你会看到类似步骤:
[mini-harness] Step 1 [model]: Model requested tool: git_status
[mini-harness] Step 1 [tool]: Tool completed: git_status
[mini-harness] Step 2 [model]: Model requested tool: git_diff
[mini-harness] Step 2 [tool]: Tool completed: git_diff
[mini-harness] Step 3 [final]: Model returned a final answer.
单独调 git_diff 时,输出会包含:
{
"fileCount": 7,
"additions": 240,
"deletions": 18,
"truncated": true,
"files": [...]
}
具体数字会随着当前工作区变化而变化。
当前系统能力变化
Day 10 后,mini harness 已经能观察当前 Git 变化:
agent loop
-> git_status
-> git_diff
-> parsed file summaries
-> context report / final answer
这让后续能力更接近真实 coding agent:
- 修改前先检查用户已有改动。
- 把 diff 摘要加入上下文。
- 对大 diff 做截断和预算提示。
- 在 patch editing 前判断哪些文件已经处于 dirty 状态。
遇到的问题
1. 为什么不直接返回完整 diff
完整 diff 可能非常大。直接塞给模型会带来两个问题:
- token 成本不可控。
- 长 diff 里靠后的关键 hunks 可能被截断在模型上下文之外。
所以工具返回 bytes、returnedBytes 和 truncated,让调用者显式知道上下文是否完整。
2. 为什么既要 raw diff 又要 parsed summary
raw diff 适合模型读细节,parsed summary 适合程序做判断。
例如:
- UI 可以先展示
fileCount/additions/deletions。 - Context Explorer 可以按 diff 大小估算 token。
- Agent 可以先看文件列表,再决定是否读取某个文件。
这比只返回一大段字符串更容易扩展。
3. 非 Git 仓库怎么办
runGit 会把 Git stderr 标准化成工具错误:
git status --short --branch --untracked-files=all failed: ...
在 agent loop 中,这类异常会被包装为 ToolResult.error,模型可以向用户解释限制,而不是直接崩掉整个流程。
明天做什么
Day 11 会实现 Patch Editing。
有了 Day 10 的 Git/Diff 工具后,patch editing 不再是盲目写文件。更合理的流程是:
1. git_status 看工作区是否 dirty
2. git_diff 看用户已有改动
3. 生成 unified diff
4. apply_patch 应用修改
5. 再次 git_diff 验证结果
这也是 coding agent 从“会读代码”走向“能安全改代码”的关键一步。
Comments