
一次"小修改",为什么会变成大型Review?
给AI一个看起来很简单的任务:
把订单接口里的
user_name改成display_name,并完善相关代码。
几分钟后,功能可能已经跑通,但打开Git Diff会发现:
- 接口类型改了;
- 调用方改了;
- import顺序被重新整理;
- 几个旧函数被顺手重构;
- 无关文件被格式化;
- 依赖版本发生变化;
- 测试目录也被重新组织。
AI可能没有明显写错代码,Review成本却远远超过任务本身。
这类情况很难只靠一句"不要修改无关文件"解决。
因为在缺少明确边界时,AI需要自行判断"相关代码"包括哪些文件。它可能认为顺手统一风格、清理旧实现和补全类型,都是完成任务的一部分。
对开发者来说,这是越界。
对Agent来说,它可能只是在积极完成一个描述模糊的需求。
项目Rule解决不了所有任务边界
Cursor Rules、AGENTS.md和CLAUDE.md很适合保存长期规则:
diff
- 新代码统一使用 TypeScript
- 业务逻辑放在 service 层
- 数据库字段使用 snake_case
- 修改后运行 lint 和单元测试
- 不删除已有测试
这些规则能告诉AI项目长期采用什么风格,却无法完整描述某一次任务:
- 本次只允许修改哪两个文件?
- 哪些公共接口不能变化?
- 是否允许新增依赖?
- 修改超过多少文件时需要停止?
- 测试失败与本任务无关时应该怎么办?
长期规则负责回答"这个项目通常怎么开发"。
Task Contract负责回答"这一次具体允许做什么"。
二者不应该混在一起。
Task Contract应该包含什么?
我会把单次任务合同放在:
bash
docs/ai-tasks/TASK-2026-0702.md
一个够用的合同至少包含:
markdown
# Task Contract
## Goal
修复 coupon 为 null 时订单金额计算失败的问题。
## Current Behavior
calculateDiscount() 在 coupon 为 null 时读取 coupon.rate,
触发 TypeError,接口最终返回 500。
## Expected Behavior
coupon 为 null 时不应用优惠,返回原始订单金额。
## Allowed Files
- src/domain/order/discount.ts
- tests/domain/order/discount.test.ts
## Forbidden Changes
- 不修改数据库结构
- 不修改 API 返回格式
- 不升级或新增依赖
- 不修改其他订单模块
- 不执行全局格式化
- 不删除、跳过或弱化已有测试
## Acceptance Criteria
- coupon 为 null 时返回原始金额
- 有效优惠券逻辑保持不变
- 过期优惠券测试继续通过
- 新增 null coupon 测试
- 不出现 Allowed Files 之外的变更
## Required Validation
npm test -- tests/domain/order/discount.test.ts
npm run lint
npm run typecheck
## Diff Budget
- 最多修改 2 个文件
- 预计变更不超过 80 行
## Stop Conditions
出现以下情况时停止修改并请求确认:
- 必须修改允许列表之外的文件
- 需要调整公共接口
- 需要新增依赖
- 现有实现与任务描述不一致
- 测试失败原因与本任务无关
- 无法在不改变业务规则的情况下完成
这里最重要的并不是Goal。
普通Prompt通常已经写了目标,真正缺少的是允许范围、禁止改动和停止条件。

让AI先交计划,不要马上写代码
合同准备好后,先让Agent进入只读阶段:
markdown
阅读项目规则和 docs/ai-tasks/TASK-2026-0702.md。
当前阶段只做分析:
- 不修改文件;
- 不创建文件;
- 不运行具有写入效果的命令。
请输出:
1. 根因判断;
2. 计划修改的文件;
3. 每个文件的修改内容;
4. 需要新增或调整的测试;
5. 计划执行的验证命令;
6. 是否存在超出Task Contract范围的风险。
如果需要修改Allowed Files之外的文件,停止并说明原因。
这一步可以在代码产生之前发现范围问题。
假设AI的计划中突然出现:
css
src/api/order-controller.ts
src/shared/money.ts
package.json
说明实际任务与合同不一致。
此时应该重新判断:
- 原合同是否漏掉必要文件?
- 当前实现方案是否扩大了影响面?
- 是否应该把任务拆成两个PR?
- 有没有不修改公共模块的实现方式?
先讨论这些问题,比修改完成后再回滚更省时间。
确认计划后,再开放修改权限
计划通过后,再发第二条指令:
diff
按照已经确认的计划执行Task Contract。
要求:
- 只能修改Allowed Files;
- 不得突破Forbidden Changes;
- 保持最小变更;
- 完成后运行Required Validation;
- 输出实际变更文件、测试结果和剩余风险。
一旦触发Stop Conditions,立即停止,不要自行扩大范围。
这样做的核心不是把Prompt拆成两条,而是把"思考"和"执行"分开。
css
```mermaid
flowchart LR
A[读取任务合同] --> B[只读分析]
B --> C{计划是否越界}
C -->|是| D[停止并请求确认]
C -->|否| E[人工确认计划]
E --> F[执行最小修改]
F --> G[运行验证]
G --> H[检查Git Diff]
```
如果一开始就给Agent完整写权限,它可能在分析过程中同步修改文件。等开发者发现计划有问题时,工作区已经产生大量变化。
Diff Budget不是代码质量指标
合同中写了:
最多修改2个文件
预计变更不超过80行
这不是要求AI为了控制行数而写出压缩、难懂的代码。
Diff Budget更像一个告警阈值。
任务预计只修改两个文件,结果却出现十几个文件,就应该暂停并解释。确实需要扩大范围时,开发者可以更新合同,而不是让Agent静默继续。
以下情况适合突破原预算:
- 公共类型确实被多个模块引用;
- 原测试结构无法覆盖目标行为;
- 修复暴露了必须同步处理的编译错误;
- 原需求本身低估了影响范围。
突破预算不可怕。
没有解释地突破预算,才会让Review失控。

修改结束后,用Git检查真实范围
不要直接相信Agent给出的"仅修改了两个文件"。
先看工作区:
lua
git status --short
这一步可以发现已修改文件和新建但尚未跟踪的文件。
查看相对当前提交发生变化的已跟踪文件:
css
git diff HEAD --name-only
查看变更规模:
bash
git diff HEAD --stat
检查空白错误:
css
git diff --check
查看完整差异:
git diff HEAD
示例任务的预期结果应该只有:
css
src/domain/order/discount.ts
tests/domain/order/discount.test.ts
如果出现其他文件,让Agent逐个解释:
diff
列出Allowed Files之外的所有工作区变更。
对每个文件说明:
- 为什么发生修改;
- 不修改会导致什么问题;
- 是否可以安全撤销;
- 是否应该拆成独立任务。
当前阶段不要继续编辑。
可以撤销的无关修改应该撤销。
确实必要的修改,则更新Task Contract并重新确认,而不是把它悄悄混进原任务。
测试通过不代表没有越界
一次越界修改完全可能通过测试。
例如,AI为了修复折扣计算,顺手改变了接口错误码。现有单元测试没有覆盖接口协议,CI仍然全绿,但调用方可能已经受到影响。
所以验收需要同时检查:
diff
功能结果
+
原有行为
+
修改范围
示例中的验证命令是:
arduino
npm test -- tests/domain/order/discount.test.ts
npm run lint
npm run typecheck
具体项目应该替换成真实存在的命令。
如果项目需要数据库、私有API或特定测试环境,还要在合同中明确:
- 哪些验证可以本地完成;
- 哪些需要测试环境;
- 哪些结论依赖人工确认;
- 哪些代码只是示例结构。
Rule与Task Contract怎么分工?
可以把长期规则写成:
markdown
# AI Development Rules
- 修改前必须先输出计划
- 优先最小变更,不主动重构无关代码
- 不得删除测试或降低校验标准
- 每次任务必须读取对应Task Contract
- 触发Stop Conditions时必须停止
- 完成后必须报告实际修改文件和验证结果
工具对应位置可以按项目选择:
sql
Cursor -> .cursor/rules/*.mdc 或 AGENTS.md
Claude Code -> CLAUDE.md
其他Agent -> 项目支持的仓库指令文件
Cursor官方建议项目规则保持具体、可执行并合理限定范围;Claude Code也建议在CLAUDE.md中保存项目架构、代码规范和常用命令。
Task Contract则不要无限积累到全局规则中。
它只服务一次任务,完成后可以保留为Review依据,也可以在合并后归档。
这套方法适合哪些任务?
特别适合:
- 修复线上Bug;
- 修改接口字段;
- 小范围性能优化;
- 补单元测试;
- 调整业务校验;
- 修改遗留项目;
- 在不熟悉的仓库中使用AI Agent。
从零搭建原型时,边界可以放宽。
进入已有项目、多人协作仓库和生产代码后,边界应该收紧。
AI能力越强,越需要明确它什么时候必须停手。
结论
项目Rule解决的是长期规范,Task Contract解决的是单次任务边界。
一套比较稳的AI改码流程应该是:
定义任务合同
→
AI只读分析
→
人工确认计划
→
执行最小修改
→
运行验证
→
Git核对范围
→
人工Review
下一次让AI修改代码之前,可以先花两分钟写下:
- 允许改哪些文件;
- 哪些地方不能动;
- 如何验证结果;
- 什么情况下必须停止。
这两分钟不会让AI写得更快,却能让后面的Diff更容易看懂,也更容易安全合并。