AGENTS.md 怎么写才能约束 AI 实现和修 Bug?

如果 Coding Agent 把“连续 3 次密码错误后锁定 30 分钟”做成 User 增加两个字段、AuthService 增加几个 if,修 Bug 时又随手加上空值判断或宽泛的异常捕获,最直接的办法不是追加一句“请遵循最佳实践”,而是在 AGENTS.md 中写清四件事:触发条件 → 责任模块 → 禁止操作 → 验证命令。

实现任务要说明状态归谁、业务规则在哪里落、哪些模块不能绕过;修 Bug 则要说明先用什么命令复现、拿什么证据判断根因、允许修改到哪里,以及最后运行哪组测试。

掘金热榜文章用“AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md”概括了这种反差。这个说法指出了两个容易观察的症状:Agent 在按需求字面生成代码,出了问题则倾向于遮住表象。AGENTS.md 不需要重复介绍项目,而要把生成代码时必须遵守的工程判断显性化。

登录锁定就是一个典型例子。Agent 可能先给 User 增加 failed_attempts、locked_until,再补上 is_locked()、record_failure()、record_success()。这些改动未必错误,但如果项目已经选择由领域对象持有账户状态、由应用服务负责流程编排,那么可变的 User、集中在服务层的锁定判断以及被压成布尔值的登录结果,仍然说明约束没有写到位。

先从 diff 看出它在按字面实现,还是只贴了一层补丁

先不要急着换模型或重写提示词。问题通常已经写在变更范围、代码位置和测试行为里。如果仓库使用 Git,可以先看这三项:

git status --short
git diff --stat
git diff

然后按这个顺序检查:改了哪些文件;业务状态和不变量落在哪个模块;接口是否还能表达调用方需要区分的结果;测试为什么通过。如果变更涉及依赖清单、迁移文件、测试脚本、CI 或部署配置,再单独核对是否超出了任务范围。

diff 或测试表现 这通常意味着 AGENTS.md 缺口 应补的规则
需求完成,但状态和判断放错模块 Agent 把需求翻译成了局部代码,没有遵守模块所有权 没写责任模块和不变量归属 规定触发条件、责任模块及禁止重复实现的范围
功能能跑,但接口只剩 true/false 调用方需要的业务结果被压平 没写接口契约 明确服务结果必须保留哪些差异
报错消失,但解释不了为什么消失 修改只遮住了表象,没有根因证据 没规定复现、诊断和根因分析流程 要求先复现,再说明证据,最后最小修改
只修好某一个输入或异常分支 修复范围没有覆盖问题类别 没定义问题的适用条件 写清触发条件、输入边界和受影响范围
顺手修改了依赖、配置或测试脚本 Agent 为让测试通过,选择了高影响路径 没划目录边界,也没定义授权条件 列出禁止事项和需要额外确认的操作
同一任务多次生成不同实现 抽象口号无法决定具体改法 规则只有目标,没有动作 改写成条件式规则,并给出验证命令

登录锁定案例里的问题,不能简单概括为“字段放错地方”。判断标准取决于项目已经做出的架构选择。

如果这个项目选择让 User 持有账户锁定状态,那么 failed_attempts、locked_until 以及状态迁移就应该由 User 负责,AuthService 只编排认证流程,Controller 或路由处理器不能再次复制“锁定 30 分钟”的判断。类似地,如果认证服务需要区分“用户不存在”“密码错误”和“账户锁定”,就不应继续在领域层压成单个布尔值。

反过来说,如果项目本来就采用以 Service 为中心的事务脚本,那么状态放在 Service 也不必然错误。真正缺少的是明确决策:锁定状态到底归谁、哪些调用方需要区分失败原因、哪些目录不得重复实现。AGENTS.md 约束的是这个项目的既定选择,不是强塞一套通用架构。

“贴膏药式修 Bug”通常更容易从 diff 里认出来。新增的代码可能只有一处 if value is None,一个捕获所有异常的 except,或者针对某个输入增加的特判。它们确实可能让原来的报错暂时消失,但没有回答三个问题:原问题能否稳定复现;修改点为什么能解释根因;如果不修这一层,后续是否还会从其他路径复发。

因此,检查 Bug 修复时不要先看“测试是不是绿了”,而要先看有没有失败用例或可重复的复现入口。没有原报错的完整内容、触发路径和实际输入,Agent 很容易根据表象猜测;猜中了也只是当前输入碰巧有效。

README、CONTRIBUTING 和 AGENTS.md 也因此不能互相复制。README 主要帮助人理解项目、启动和使用项目;CONTRIBUTING 说明人如何参与贡献;AGENTS.md 面向即将修改仓库的 Agent,重点提供代码边界、构建测试命令和不可触碰区域。如果 AGENTS.md 开头仍在介绍项目背景、安装步骤和功能列表,Agent 得到的多半只是另一份 README,而不是可以指导修改的约束。

把缺失的工程判断写成可执行规则

AGENTS.md 是一种极简 Markdown 文件,用来向 Coding Agent 提供项目上下文和规则,可以把它理解成“给智能体看的项目说明”。它应写明 Agent 无法仅凭通用训练经验得知的内容:已经确定的架构选择、模块边界、接口契约、领域不变量、Code Review 中反复出现的要求、历史事故中禁止再次发生的改法,以及项目真实使用的安装、构建、测试和检查命令。

这些内容适合写成明确命令、目录边界、编码禁区和条件触发式规则,并像代码一样持续维护。下面是一个填写骨架,不是可以原样提交的文件。复制到仓库前,必须把每个尖括号占位符替换成当前项目的事实;如果项目还没有对应命令,应先确定命令,不能让 Agent 自行猜测。

# AGENTS.md

## 适用范围
- 适用任务:<填写具体任务,例如“认证、账户锁定及密码错误计数”>
- 适用目录:<填写真实目录,例如“src/auth/、tests/auth/”>
- 不适用目录:<确有例外时才填写;没有就删除本行>

## 架构与领域规则
- 当 <触发条件> 时,<状态、不变量或接口结果> 由 <责任模块> 负责。
- 不得在 <其他模块> 复制、绕过或提前实现该规则。
- <调用方> 必须能够区分 <业务结果>;不得压成 <过度简化的返回值>。

## Bug 修复流程
1. 使用 <真实复现命令> 复现问题,并记录 <完整报错或失败断言>。
2. 修改前说明 <根因、证据和受影响范围>。
3. 只修改 <根因所在模块>;不得添加 <掩盖症状的特殊分支、空值判断或宽泛异常捕获>。
4. 添加 <回归测试>,要求修复前失败、修复后通过。

## 禁止事项
- 未经确认,不得运行会修改 <依赖清单或锁文件> 的命令。
- 未经确认,不得修改 <迁移文件、测试脚本、CI 或部署配置>。
- 不得删除断言、跳过测试、扩大权限或调整测试环境来让检查通过。

## 验证
- 运行 <真实单元测试命令>。
- 运行 <与改动相关的集成测试命令>。
- 运行 <真实静态检查命令>。
- 执行 `git diff -- <允许修改的路径>`,确认没有越过上述边界。

每条规则都可以用四个问题检查:什么时候触发;应该改哪里;不能做什么;怎样证明改对。如果其中任何一个答不上来,这句话大概率还不能指导 Agent。

功能实现的规则必须落到具体业务条件。下面假设这个仓库已经决定由 User 持有账户锁定状态、AuthService 负责编排认证流程。这只是该项目的一种架构示范,不是所有仓库都必须照搬。

## 认证与账户锁定
- 适用目录:<本仓库真实的认证代码目录和测试目录>。
- 架构选择:`User` 持有 `failed_attempts` 和 `locked_until`;`AuthService` 只负责编排认证流程。
- 当实现或修改密码校验时,连续 3 次密码错误后必须锁定账户 30 分钟。
- 锁定期间即使密码正确也不得认证成功。
- 不得在 Controller、路由处理器或工具函数中复制锁定时长或锁定状态判断。
- 认证服务的领域结果必须区分用户不存在、密码错误和账户锁定;对外接口按项目已有契约映射,不得在领域层压成单个布尔值。
- 时间来源必须可替换,测试不得依赖真实系统时间。
- 验证命令:`<本仓库真实的登录单元测试命令>`。
- 回归测试至少覆盖:第三次密码错误触发锁定、锁定持续 30 分钟、锁定期间密码正确仍被拒绝,以及锁定到期边界。

这里最关键的不是某个目录名,而是四层约束同时存在:业务条件明确,状态归属明确,调用边界明确,验证路径明确。如果只写“使用领域驱动”“保持职责单一”,Agent 仍然可以在不同目录、不同对象之间自由发挥。

下面这些口号通常无法执行:

- 代码要健壮。
- 保持架构整洁。
- 认真测试。

可以改成带触发条件和动作的规则:

- 修改登录锁定逻辑时,不得在 Controller 或工具函数中复制锁定时间判断。
- 提交前运行 `<项目真实的登录单元测试命令>`,并报告实际执行的命令、退出状态和失败摘要。

修 Bug 的规则则应固定排查顺序:

## 登录失败计数异常修复
- 触发条件:修改密码错误计数、锁定状态或认证失败处理。
- 修复前先读取完整报错、触发路径和实际输入,不根据摘要猜测。
- 使用 `<真实复现命令>` 复现问题,并保留原报错或失败断言。
- 如果无法稳定复现,只允许增加与问题路径直接相关的诊断信息并报告结果,不得先修改生产逻辑。
- 修改前说明根因、证据和受影响范围。
- 只修改根因相关代码,不添加针对单个输入的特殊分支,不使用宽泛异常捕获掩盖失败。
- 添加能在修复前失败、修复后通过的回归测试。
- 最后运行 `<相关测试命令>` 和 `<项目全量测试命令>`。

执行上述规则时,至少要让 Agent 运行以下动作,但尖括号内容必须先替换为项目中的真实命令:

<真实复现命令>
<相关单元测试命令>
<项目全量测试命令>
git diff --check
git diff -- <本次允许修改的路径>

高影响区域还要单独设禁区,而不是只写“不要乱改”:

## 高影响区域
- 未经明确授权,不得运行会写入 `<依赖清单或锁文件>` 的安装或升级命令。
- 未经明确授权,不得新增或改写 `<数据库迁移目录>` 中的文件。
- 未经明确授权,不得修改 `<测试脚本、CI 配置或部署配置>`。
- 如果根因修复确实需要触碰上述区域,先说明原因、影响范围和拟执行命令,取得确认后再修改。

这些规则不是为了让 Agent 什么都不能动,而是把“可以自行决定的范围”和“必须先确认的范围”分开。没有边界时,Agent 可能为了让测试通过而换包管理器、升级依赖、修改 CI,甚至调整测试环境;这些改动比原 Bug 本身更难审查。

用测试和变更范围验收,再反推规则

功能实现的验收不能只看“页面能打开”或“接口返回成功”。还要检查状态和业务不变量是否位于约定模块,公共服务是否只承担编排职责,接口是否保留调用方确实需要区分的业务结果,以及正常路径和边界路径是否都有覆盖。

放到登录锁定示例里,至少要验证连续 3 次失败后触发锁定、锁定持续 30 分钟,以及锁定期间密码正确仍然不能登录。如果这些条件只是分别出现在几个测试里,却没有经过同一个认证入口,测试仍然可能漏掉状态判断被放错层的问题。

Bug 修复的验收顺序则是:

  1. 修复前能够触发原问题,或者对应的回归测试在修复前确实失败。
  2. 修改点能够解释根因,而不只是解释报错表象。
  3. 修复后原测试和新增回归测试均通过。
  4. diff 中没有无关重构、配置漂移、依赖变更或绕过测试。

不要采信“已经修好”这类自然语言总结,要求 Agent 报告实际运行的命令和结果。项目没有定义全量测试命令时,就不要让 Agent 临时拼一个看起来合理的命令。

规则失效后,也不要继续追加更多说明。常见原因只有几类:规则没有指定适用目录;只写了抽象目标,没有具体动作;同时存在两条互相冲突的要求;缺少验证命令;规则已经与代码现状不一致。

前四类需要补全或改写触发条件、目录、动作和验证方式。最后一类需要删除或修改旧规则。例如,架构已经从“Service 持有锁定状态”调整为“领域对象持有状态”,却把两条要求都留在 AGENTS.md 里,Agent 才会反复生成不同实现。遇到这种冲突,应更新原来的决策,而不是再增加第三条折中规则。

AGENTS.md 本身也应像代码一样审查。如果仓库使用 Git,可以检查:

git diff -- AGENTS.md

当架构、接口契约、测试命令或历史事故发生变化时,同步修改对应规则,避免让过期约束与真实代码互相打架。历史事故尤其适合写成明确禁区,例如“不得通过捕获异常后直接返回成功来隐藏认证链路失败”,而不是只留下“注意异常处理”。

还要确认当前工具是否实际读取这个文件。文件名、读取位置、是否支持子目录规则、多个文件同时存在时的优先级以及修改后是否自动重新加载,都应以当前版本的官方说明为准,不能假定所有 Coding Agent 行为一致。确认规则生效时,可以给 Agent 一个会触及受限目录的最小任务,在临时分支或工作区中观察 Agent 是否先说明修改范围并请求授权,而不是直接改动:

在修改任何文件前,先说明预计修改的文件、修改原因和准备运行的验证命令。
如果需要修改依赖清单、迁移文件、测试脚本、CI 或部署配置,请先停止并请求确认。

如果当前工具不支持 AGENTS.md,应改用它原生支持的规则文件或配置方式;继续修改一个不会被读取的 AGENTS.md,只会制造“已经约束过了”的错觉。

最后一次验收不再看泛泛总结,只看三项事实:复现命令能够运行,修复后原测试与新增回归测试均通过,git diff --check 没有错误且 git diff -- <允许路径> 没有越过依赖、迁移、测试脚本、CI 或部署等约定边界。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容