AI Coding Agent 的代码库理解与补丁生成
- Published on
AI Coding Agent 的代码库理解与补丁生成
“帮我把这个 Bug 修一下。”这是人类工程师每天都会收到的任务,也是 AI Coding Agent 最容易被高估的地方。模型确实可以很快写出一段看起来合理的代码,但“看起来合理”离真正的修复还隔着很长一段路:它要知道项目用什么框架,入口在哪里,哪些文件是约定俗成的,哪些测试能证明行为,哪些改动会影响不在当前屏幕里的调用方。
我见过 Agent 最典型的失败,不是语法写错,而是改得太多。一个简单的空值问题,它顺手重构了整个模块;一个接口字段要改名,它只改了当前文件,没有更新调用方;测试没跑,最终补丁却被当成“完成”。
所以,好的 Coding Agent 不是代码生成器,而是一位遵守范围、能够检索证据、愿意验证结果的协作者。本文把它拆成一条完整链路:理解代码库、定位任务、选择上下文、生成最小补丁、执行验证,再把结果交给人审查。
一、代码库理解不是把所有文件塞进上下文
一个中型项目可能有几十万行代码。把所有文件都放进 Prompt,一方面超过上下文窗口,另一方面会把真正重要的信息淹没。Agent 需要的是“与当前任务相关的最小充分上下文”。
可以把上下文来源分成几层:
项目规则与 README
↓
目录结构与模块地图
↓
目标文件及其直接依赖
↓
符号引用、测试和配置
↓
最近相关提交与错误日志
第一层回答“这个项目怎么工作”,第二层回答“代码放在哪里”,第三层回答“这次具体改哪里”,第四层回答“改动会影响谁”。如果一开始就只把用户提到的文件扔给模型,Agent 很可能在错误的局部上下文里做出正确但无效的修改。
二、建立适合检索的代码库索引
代码索引不应该只有向量。代码有非常强的结构关系:函数调用函数,组件引用组件,类型被多个模块导入。单纯按文本相似度搜索,容易找到“说法相似但没有依赖关系”的文件。
建议同时保存三种信息:
type CodeSymbol = {
name: string
kind: 'function' | 'class' | 'component' | 'type' | 'constant'
filePath: string
startLine: number
endLine: number
signature?: string
}
type CodeChunk = {
chunkId: string
filePath: string
symbolName?: string
content: string
imports: string[]
exports: string[]
contentHash: string
}
文本向量适合找“处理登录错误的代码”,符号索引适合找 handleLogin 的所有引用,依赖图适合判断改一个类型会影响哪些模块。三者结合,Agent 才能从“相似内容”走向“真实关系”。
索引还要排除无关内容:构建产物、依赖目录、二进制文件、密钥和临时日志不应该默认进入模型上下文。.gitignore 是起点,不是全部规则;项目里可能还有包含内部配置的脚本和测试夹具,需要通过敏感文件清单进一步过滤。
三、先把自然语言任务变成变更计划
用户说“修复登录超时”,这个描述还不够执行。Agent 应该先提取目标、约束和验收条件:
type ChangePlan = {
goal: string
suspectedFiles: string[]
invariants: string[]
testsToRun: string[]
outOfScope: string[]
}
例如:
目标:登录请求超时后显示可重试提示
可能文件:请求封装、登录表单、错误提示组件
必须保持:成功登录流程、服务端错误提示、现有 API 签名
测试:登录组件测试、请求封装测试、类型检查
范围外:不重写认证状态管理,不修改后端接口
这一步看起来像在浪费时间,其实是在防止模型过早写代码。很多错误补丁,不是模型不会写,而是它没有先确认“什么不能改”。
如果任务信息不足,Agent 应该通过代码搜索和测试继续收集证据;只有当关键约束无法从仓库中确认时,才向人提问。一个会问“这个错误提示应该在哪个层处理”的 Agent,比一个不问就改五个文件的 Agent 更可靠。
四、上下文选择要有排序和预算
每次调用模型都应该有上下文预算。可以给候选代码片段打分:
相关度 = 语义相似度
+ 符号引用关系
+ 同模块加权
+ 测试关联加权
- 文件距离惩罚
用户明确提到的文件优先级最高;目标函数的调用方和测试通常比相似注释更重要;历史提交可以解释为什么代码这样写,但不应该无条件塞进上下文。
还要保留文件路径和行号。没有出处的代码片段,模型容易把两个同名函数混在一起,生成补丁时也无法准确定位。上下文不仅要告诉模型“内容是什么”,还要告诉它“内容在哪”。
对于超长文件,可以先提供符号摘要,再按需展开具体函数。这样既保持结构,也避免模型被一大段无关代码拖慢。
五、补丁生成:最小改动比聪明重构更重要
Coding Agent 的输出最好是补丁,而不是整文件重写。补丁有三个好处:人容易审查,冲突范围小,出错时容易回滚。
可以要求 Agent 遵循这些约束:
- 只修改计划中允许的文件。
- 不改变公开 API,除非任务明确要求。
- 不顺手格式化无关代码。
- 不删除失败测试来获得绿色结果。
- 每个修改都能对应一个问题或验收条件。
export async function request<T>(url: string) {
const response = await fetch(url)
if (!response.ok) {
- throw new Error('Request failed')
+ throw new RequestError(response.status, 'Request failed')
}
return response.json() as Promise<T>
}
这个补丁虽然小,但它可能影响所有调用方。Agent 不能因为改动只有一行就跳过引用搜索,而应该继续检查 RequestError 是否被正确处理、测试是否覆盖状态码、类型是否能通过。
六、工具调用必须有明确边界
一个实用的 Coding Agent 通常需要这些工具:读取文件、搜索文本、查找符号、查看 Git diff、运行测试和生成补丁。工具不是越多越好,每个工具都应有明确输入输出和权限。
type ToolPolicy = {
read: boolean
write: boolean
runTests: boolean
network: boolean
gitCommit: boolean
}
默认可以允许读和测试,把写入限制在当前工作区;网络访问、安装依赖、提交代码和发布部署需要更高权限或人工确认。尤其不能让模型通过一个“万能 Shell”随意删除文件、读取密钥或修改 CI 配置。
每次工具调用都要记录原因和结果。Agent 说“我已经检查了所有引用”不算证据,工具返回的搜索结果才是证据。审计记录可以帮助人判断模型是真的完成了探索,还是只是用一句话掩盖了没有检查。
七、验证不是最后点一下测试按钮
补丁生成以后,验证应该分层进行:
补丁语法与格式
↓
类型检查 / 静态检查
↓
目标模块单元测试
↓
相关集成测试
↓
完整测试与构建
↓
人工审查 diff
先跑便宜、针对性强的检查,可以更快发现明显错误;通过后再跑完整测试。测试失败时,Agent 要区分“补丁引入的失败”和“仓库原本就失败”,不能看到红色输出就盲目修改更多代码。
失败信息也应进入下一轮上下文,但要限制循环次数。一个典型的失控 Agent 会不断改代码、不断重跑同一个失败测试,最后把原本清楚的补丁改得面目全非。可以设置最大迭代次数,并在每次迭代前重新比较 diff,发现变更范围扩大时暂停请求人工确认。
八、用回归测试保护代码库记忆
每次线上修复都应该尽量留下一个能复现问题的测试。这样,未来模型再次修改相关代码时,测试会成为代码库的“长期记忆”。没有回归测试,Agent 只能依赖当前上下文推测历史经验,而推测很容易丢失。
测试不一定要很大。一个输入、一个错误条件、一个期望结果,就可能挡住下一次同类回归。对 Agent 来说,好的测试比长篇架构说明更直接:它告诉模型什么行为绝对不能破坏。
九、让人审查决策,而不是审查一团文字
最终交给人的不应该是一段“我已完成”的总结,而是一份清楚的交付报告:
修改文件:src/request.ts、src/login/LoginForm.tsx
解决问题:网络超时时展示可重试状态
未修改:认证状态管理和后端 API
验证结果:类型检查通过,12 个相关测试通过
已知风险:尚未覆盖弱网下的真实浏览器测试
人需要重点看行为和风险,而不是重新猜 Agent 做过什么。报告必须诚实地列出没有验证的部分,不能把“测试没有运行”写成“测试通过”。
总结:Agent 的能力上限取决于证据链
我对 Coding Agent 最终形成的判断是:写代码只是链路中最便宜的一步。真正有价值的能力,是从仓库里找到正确证据,理解变更边界,生成最小补丁,然后用测试和 diff 证明它没有越界。
代码库索引让 Agent 找得到,变更计划让它知道改什么,上下文排序让它看得准,补丁约束让它改得小,工具权限让它不至于失控,分层验证则让团队知道结果是否可信。
一个好的 Agent 不会把人排除在软件工程之外。它替人完成搜索、整理、重复修改和测试回放,把人的注意力释放到架构判断、风险取舍和最终审查上。我们不应该因为模型能写代码,就把仓库的钥匙全部交出去;应该让它在清楚的边界里工作,用证据换取信任,一次只提交自己能够解释的改变。