先说个我印象很深的场景。前阵子带一个模拟项目X,后端用了一套分层架构,业务逻辑全收在 service 层,数据访问只允许走仓储接口。我给 AI 编码助手派了个活:“把订单状态流转补全。”它干得挺快,结果 code review 时差点没绷住——它直接在控制器里 new 了一个仓储实例,绕过了 service,还顺手访问了实体内部一个本应私有的方法。代码能跑,测试也绿,可架构就这么被捅了个洞。
当时我第一反应是怪自己 prompt 写得不够细。后来才发现,问题根本不在指令写没写清楚,而是我压根没把“项目事实”告诉 AI。这个项目里谁依赖谁、哪些边界不能碰、测试该怎么跑、代码风格是什么——这些信息人靠看代码、翻文档就能摸个大概,但 AI 编码工具在一次任务里只能看到一小块上下文,它不知道这些“潜规则”,自然就会选最短路径。于是我把这些事全部写进了一个叫 AGENTS.md 的文件里,放在项目根目录,让 AI 每次开工前先读它。几个迭代下来,AI 生成代码绕过封装、破坏架构的频率肉眼可见地降了下来。
这篇文章就把我这段时间的实践完整拆开讲:AGENTS.md 里到底该写什么、四种最关键的项目事实怎么组织、踩过哪些坑、怎么验证它真在生效。内容不挑工具,主流的 AI 编程辅助工具基本都兼容这套约定,适合正在用 AI 写业务代码、又不想让架构失控的团队和个人。
1. 为什么 AI 写的代码总爱“抄近道”:问题不在模型,在上下文
先泼一盆冷水:大部分 AI 编码工具不会主动去看你项目里十几层目录的依赖关系,也不会翻开你那份几百行的架构设计文档再动手。它的工作方式很像一个阅读范围有限的新同事,你给它一个任务,它就在局部文件里搜索线索,然后按“最常见、最省事”的路径把代码补出来。
这就有个天然矛盾。人写代码时,脑子里装着一张家谱图:controller 调 service,service 调 repository,repository 才碰数据库;工具函数放 utils;DTO 不能混进领域模型。这些是多年形成的肌肉记忆。可 AI 没有这张图,它的“常识”来自海量公开代码,而那些公开代码里有大量糟糕的写法。于是它看到需要数据访问时,第一反应是直接 new 一个仓储对象;看到某个类里有现成方法,就不管它是 public 还是 internal,先调了再说。
这件事说白了就是上下文缺失。打个比方:你让一个只见过厨房的实习生去餐厅后厨做事,告诉他“把出餐口那桌的菜端过去”。他可能直接从炒菜锅里把菜舀出来就端走——因为他不知道出餐流程、不知道哪个窗口是传菜口、不知道哪些菜已经扣了盖。你得先把后厨的动线、岗位边界、禁忌事项统统讲清楚,他才知道怎么干而不闯祸。
AGENTS.md 要解决的,正是这个“后厨动线”问题。它不代替你写业务代码,也不存放具体实现细节,而是把你项目里“人知道但代码里看不出来”的事实——架构分层、依赖方向、封装边界、测试习惯——写成 AI 能直接读取的上下文。让 AI 在动手前,先获得跟你脑子里那份家谱图差不多的信息。
有一类人会觉得这事麻烦,觉得写 AGENTS.md 不如多写几行注释。我的观点是:注释是写给“正在看这段代码的人”的,而 AGENTS.md 是写给“每次开工前需要全局视角的 AI 同事”的。两者的服务对象完全不一样。AI 一次能看到的上下文窗口有限,你不可能指望它从项目注释里拼出整个架构图,它读一个文件就只认一个文件。给它一份集中、精炼、命令式的项目简报,远比零散的注释更高效。
那具体写什么?我总结下来,最有价值的项目事实有四类,缺一类,AI 的行为都会跑偏。下面逐一拆开讲。
2. 四种项目事实:AGENTS.md 里到底该装什么
这一节是核心,我会把每种事实单独拿出来讲清楚:它解决什么痛点、该写成什么样、不写的后果是什么。每个类别我附一段可以照着改的写法,不追求华丽,但求能直接用。
2.1 架构与分层地图:让 AI 知道它站在哪一层
第一种事实,是项目的地形图。很多项目用了分层架构、六边形架构或者模块化设计,但代码库里并没有一个地方会直接告诉你“这两层之间不能反着依赖”。AI 拿到一个小任务时,它看到的只有当前文件和相邻的几个文件,无法感知全貌。你让它改一个订单模块,它可能顺手就在控制器里写起了领域逻辑,因为它根本不知道领域逻辑应该在 service 层。
我在模拟项目X里是这么写的:
技术栈与分层: - 代码采用分层架构,严格按 controller -> service -> repository 的方向依赖。 - controller 只做参数接收与响应包装,不允许出现任何业务判断。 - service 承载业务规则和事务边界,事务必须由 service 开启与提交。 - repository 是唯一允许访问数据源的入口,禁止把数据访问逻辑写在 service 里。 - 领域实体放在 domain 目录,任何外部层不得直接访问实体内部字段。 - 跨层调用一律走接口,不直接依赖具体实现类。这段文字的核心套路是“定义角色 + 边界声明”。你要先告诉 AI 每一层是干嘛的,再明确哪层能碰哪层,每句话都用肯定或否定式命令。AI 对“不允许”“必须”“唯一入口”这类强约束的反应,比对“请尽量保持架构整洁”要可靠得多。如果你写“注意分层”“建议按架构规范来”,AI 很可能把它当成背景噪音直接忽略。
这里有个细节值得强调:写目录职责时,最好结合项目里真实的目录名,而不是只说抽象概念。比如“service 层”可以是虚构的,但“app/services/下的类只负责业务编排”这种带真实路径的描述,AI 能直接映射到文件路径上,理解和遵守的概率会高很多。我后来把 AGENTS.md 里所有“层”都改成了“目录A/目录B/”这种形式,效果立竿见影。
我自己见过不写架构地图的典型事故:AI 要给一个功能增加“导出 Excel”的能力,它自己找了一个第三方库,在控制器里裸调用,没有走 service,也没有做参数校验。功能是好的,可代码风格跟项目完全脱节。后来我在 AGENTS.md 里加了一条“所有新引入的第三方依赖必须先经过人工确认”,这个坑才算堵住。
2.2 编码规范与风格基线:让 AI 生成的代码长得像“自己人”
第二种事实是风格基线。很多团队没有精力把编码规范做成 ESLint 规则或静态检查,或者虽有规则但覆盖不全。AI 在训练数据里见过各种风格:有的用函数式、有的用类和装饰器、有的喜欢链式调用、有的偏好提前 return。你不告诉它这个项目偏好哪种,它就会按自己“最熟悉”的风格输出,结果就是代码库突然混入了一种陌生风格——抽奖抽出来一样,吧唧掉在 2021 年的老文件里,字体都不一样,看着别扭。
风格基线不是面子工程,它直接影响 AI 代码的可维护性。一个项目如果所有 service 方法都返回 Result 对象而不是 throw 异常,那么 AI 新写的代码如果直接抛异常,调用方的错误处理模式就断了;如果项目统一用命名参数式调用,AI 突然用位置参数,阅读和改动的成本立刻上升。
我在 AGENTS.md 里会这样写:
代码风格约定: - 命名:方法名使用动词开头,变量优先使用意图明确的名词,禁止拼音缩写。 - 错误处理:业务方法统一返回 Result<T>,不在业务层直接 throw。 - 函数风格:service 层方法保持短小,单个方法不超过约 30 行,过长先拆私有方法。 - 空值处理:禁止裸用 null 传递,数据缺失统一使用 Optional。 - 集合操作:优先使用扩展函数和流式 API,不使用 for 循环处理集合。 - 测试:优先使用领域语言描述的测试用例,单元测试不触达真实网络。写这部分时最大的坑是“过度细化”。我在早期版本里把变量命名规则、缩进、换行、引号全都塞进去了,结果 AI 的主要精力全花在“遵守格式”上,反而忽略了业务逻辑。后来我把这些琐碎规则删掉,只保留那些“会破坏架构或调用方式”的风格约定——错误处理模式、空值服用、函数长度红线、集合操作偏好。事实证明,这类“影响代码结构”的风格规则对 AI 最有约束力,而缩进引号之类的事,交给格式化工具去统一就行。
还有个反直觉的经验:跟 AI 讨论“风格”的时候,说“不要”“避免”不如说“优先使用”“统一使用”有效。因为 AI 生成代码时会优先选择它熟悉的模式,你告诉它“不要用 for 循环”,它可能还是用;但你告诉它“统一使用扩展函数处理集合”,它就会尝试照做。正面表达,让它有明确替代方案。
2.3 封装边界与操作禁忌:把项目里“不能碰的地方”圈出来
第三种事实是最容易被忽视、却最能直接减少“绕过封装”的一类。一个项目经过几年迭代,总会沉淀出一些约定俗成的封装边界:某个模块对外开放了三个入口,但内部其实还有十几个方法;某个类虽然字段都是 public,但业务上不该被外部直接改写;某个初始化流程必须先走哪一步,被跳过就会埋雷。这些信息往往不会写在注释里,但对 AI 来说,它们就是“必须遵守的红线”。
我在模拟项目X里就吃过这方面的亏。那个项目有个数据访问门面类,内部维护着连接池和缓存,对外只暴露了几个查询方法。AI 拿到任务后觉得“直接访问缓存字段更快”,就绕过门面方法读写内部缓存,结果并发环境下缓存一致性出了问题。后来我在 AGENTS.md 里加了整整一段“边界与禁忌”:
封装边界(红线,禁止违反): - 数据访问只能通过 RepositoryA 接口,项目内不存在第二个合法入口。 - 所有写操作必须先调用 TransactionManager.begin(),完成后必须提交。 - 域对象的属性禁止外部直接 set,必须通过领域行为方法修改。 - 配置对象只允许在系统启动阶段读取,业务运行期禁止修改配置。 - 外部系统接口调用统一走 gateway 目录,禁止在 service 里直接发 HTTP 请求。这段落的关键是“写禁忌时要把为什么顺便带上”。我发现纯写“禁止访问缓存字段”这种规则,AI 执行起来会打折;但写成“缓存字段由门面统一管理,绕过会导致一致性异常”,AI 就会把它当成一条有因果逻辑的约束,而不是一个“随便写写的项目偏好”。这个差异非常明显,可以说是 AI 上下文工程里性价比最高的技巧。
不过要小心,这类条目不能写太多。我在第一次整理时列了二十多条禁忌,结果 AGENTS.md 太长了,AI 读完前面几条就忘了后面的。后来我把禁忌按“最高优先级”和“普通约定”分两级,最高优先级的控制在五条以内,其余放到文件后半部。效果是:那些真正容易破坏架构的红线,AI 基本都能记住;而普通约定,遗漏的代价相对没那么大,可以容忍。
2.4 测试、构建与运行上下文:让 AI 补代码时先知道“怎么检查自己”
第四种事实是关于工作流的。AI 写代码和人类写代码有个很大区别:人写完后会跑测试、跑 lint、做自测,而 AI 生成的代码往往直接就是“最终交付状态”。如果 AI 完全不了解项目的测试命令、lint 规则和 mock 习惯,它很容易产出能通过编译、但完全不符合项目验收标准的代码。它甚至可能为了“让测试通过”,去修改应该保持不变的测试文件。
这部分我建议固定写成:
检查与验证: - 单元测试运行命令:pnpm test 或 ./gradlew test(按项目实际情况替换)。 - 写完功能后必须补充对应单测,测试文件与被测文件同目录。 - 外部依赖在测试中使用 mock,不发起真实服务调用。 - 所有公共方法必须附带文档注释,新文件必须遵循现有头注释风格。 - 改动入口文件时运行 lint 检查,命令见 ./scripts/check.sh。有人会觉得“这不就是 README 里的内容吗?”但它们的服务对象完全不同:README 是给人看的快速上手文档,信息是叙述式的;而 AGENTS.md 里的验证命令要写得“唯命令是从”。AI 读了就知道“要跑哪些命令、补什么测试、用什么 mock 策略”,等于给它一套自检流程。
这里我想单独强调 mock 和依赖隔离这类事。很多项目里,测试环境会预设一批数据库种子数据、有几个测试账号。AI 不知道这些,它写的测试可能直接依赖本地环境、或者硬编码了一个真实生产环境才有的账号。在 AGENTS.md 里明确“测试不得依赖本机数据库、统一使用 mock 数据源”之后,AI 发生类似问题的次数断崖式下降。这类规则就是那种“人知道但代码里完全看不到”的首选代表。
我还会顺手写上“开发时优先参考哪几个现有文件”。比如“新接口风格可参考modules/下的订单模块”,相当于给 AI 指了个模仿对象。机器人学风格,看例子比看规则快得多,这条实测很管用。
3. 把四种事实落成 AGENTS.md 的写作方法论
内容方向定好了,接下来就是怎么写。很多人第一次写 AGENTS.md 容易犯的毛病是:要么写成回忆录,事无巨细地记录项目史;要么写成论文,讲一堆抽象原则。这两种写法 AI 都不爱读。我踩过几轮之后,总结出来几条写作规则,按重要性排下来大概是下面这个样子。
3.1 文件放哪、叫什么、用什么格式
最直观也最关键的一条:文件必须命名为AGENTS.md,放在项目根目录。当前主流的 AI 编程辅助工具基本都认这套约定——你说别的名字(比如PROJECT_RULES.md、CONTEXT.txt),很多工具压根不会自动读取,它们只会去找自己约定的那个名字。这算是 AI 编码领域最早走向事实标准的约定文件,跟 README 一样,位置和名字本身就是约定的一部分。
格式强烈建议用纯文本 + Markdown 的轻量结构。不要用 HTML、不要用表格、不要用复杂嵌套列表——AI 读取这些内容时,越接近自然语言和简单 Markdown,越容易正确解析。我在实践中发现,同样的信息,用短段落和简单列表写,AI 的执行质量明显高于用表格写。可能因为表格的阅读顺序跟文本不太一致,AI 容易把单元格内容切碎理解。如果你在 AGENTS.md 里放了表格,最好还要把关键规则在表格外再用一句话重复一次,这样防止丢失。
我还建议给文件按“高优先级规则”和“参考信息”分区域。实测下来,AI 在读取项目文件时,对文件顶部的注意力高于底部。所以最高频的红线规则要放在最前面,比如“依赖方向不许反”“数据访问只走仓储接口”。而那些“推荐的参考文件列表”“常见术语”这类参考性内容可以放到后面,即使被截断丢了,损失也可控。
3.2 写规则时请用“命令式话术”而不是“建议式话术”
写 AGENTS.md 的人大多性格温和,写出来的句子容易变成“建议保持分层结构”“尽量通过 service 访问数据”。这种语气在跟人沟通时很合适,但给 AI 就不太行。AI 对句式的敏感度远超我们的直觉:命令式——“必须通过 service 访问数据”“禁止在控制器中写业务逻辑”——比建议式更容易被执行。
为什么?模型在训练数据里看到的“必须”和“禁止”,往往出现在规范文档和编码标准中,它会判定这是一条高权重约束;而“尽量”“建议”这类词,跟“可做可不做”关联,会被模型当成软性提示。所以在 AGENTS.md 里,我后来把所有模糊词全部替换成精确词:不仅“尽量”类词要删干净,连“通常”“一般来说”也全改掉。一份好的 AGENTS.md,读起来应该像交警指挥交通,而不是朋友劝你早点睡。
同时,长句子要拆短。AI 的指令遵循能力在处理简短、独立的指令时最强;如果一句话里塞了主语、条件、例外情况,模型的注意力会被分散。我有一条自我检查标准:AGENTS.md 里的任何一条规则,读起来不应该超过两行。超了就拆开写,或者重新组织句子。
3.3 用“问题—规则—例子”的三角结构组织每条事实
单纯的规则列表容易显得干巴巴,而且 AI 难以理解规则背后的意图。我后来形成了一套固定写法:每一类重要事实,先用一句“为什么”说明背景,再列出具体规则,最后给一个(或两个)微型例子说明对与错。
举个例子,我在模拟项目X里写数据访问规则时,第一条是:
背景:项目统一通过仓储接口访问数据,避免业务逻辑散落到四面八方。 规则:任何数据读取必须调用 RepositoryA 接口。 对:const data = await this.repository.findByOrderId(id) 错:const data = await db.query('SELECT ...'),禁止在 service 直接拼接 SQL。这个三角结构的价值在于:AI 不仅知道“不能怎么做”,还知道“应该怎么替代”——这是减少绕过封装的关键中的关键。光给禁令人会迷茫,但配上正确做法的示例,AI 就有了一条可模仿的路径。实践中带“对/错”示例的规则,比只有文字的规则,遵守率肉眼可见地高。
这个方法也解决了“AGENTS.md 太长”的一个心态问题:你不用追求每一条都带示例,挑那些“绕过后果严重”的高频路径写对错例就行。普通约定(比如“方法不超过 30 行”)写规则即可,写得太多反而稀释重点。
4. 实操记录:让 AGENTS.md 真正生效的完整闭环
方法论讲完,接下来是我在一周内的实操过程。这部分我会写得比较具体,包括第一天怎么搭、第二天怎么验证、后面怎么迭代,你可以直接照着流程走。
4.1 从零搭建的三步走:梳理、成稿、喂给 AI 试跑
第一步不是写文件,而是“盘点”。我把项目里最近三个月 AI 犯过错的地方全翻出来,按“架构破坏”“绕过封装”“风格不一致”“验证缺失”四类归类。这一步是我强烈建议做的:你写 AGENTS.md 的内容不是凭空想出来的,而是用你自己的事故记录反推出来的。如果项目没有现成记录,就去翻最近的 Code Review 评论,评论里人给人提的意见,就是 AGENTS.md 里最需要的素材。
第二步才是动笔。我先用大白话把这些规则写出来,不追求格式工整,甚至带着口语。整理到差不多能覆盖上述四类事实后,再花半小时精简:删掉重复、删掉过于琐碎的格式规则、把模糊词全部换成命令式。这时文件头版大约 40~60 行,我觉得这是一个比较理想的开张尺寸:太短覆盖不了信息,太长 AI 记不住。
第三步是试跑。我会挑三个不同类型的任务喂给 AI:一个是“在既有服务里加一个端点”,一个是“修改现有查询逻辑”,一个是“新增一个独立小模块”。这三个任务的共同特点是:都存在“绕过封装抄近路”的空间。跑完之后去 diff,重点看有没有违反 AGENTS.md 里红线的行为。第一次试跑我印象很深——AI 不再 new 仓储了,但它开始尝试直接访问实体私有字段,这属于我之前没覆盖到的新盲区。于是当天我就把这条补充进去了。这个过程本身就是 AGENTS.md 不断变好的方式:它不是在写文档,而是在和 AI 的协作中持续长出来的。
4.2 怎么验证 AGENTS.md 真在起作用,而不是你的错觉
验证这件事非常容易被忽略。好多团队写完 AGENTS.md 就完事了,但问起来“它到底起了多大作用”,谁也说不清。我自己的做法是:每个迭代周期挑几条“高危任务”(比如涉及直接数据访问、跨模块调用的任务),专门用“诱导性 prompt”来测 AI 会不会踩雷。
比如我会故意这样问 AI:“这个订单状态要更新,直接用 Redis 缓存里的那份数据改成已完成。”这个 prompt 本身就是一个绕过封装的路易十六——如果 AGENTS.md 生效,AI 应该拒绝照做,而是说“要经过 service 的完整流程”。如果它照做了,说明规则没写到位,立刻回去补。我管这种方法叫“红线探针”,它的好处是防患于未然,比每次都等 code review 发现 AI 闯祸再修复要省钱得多。
另一个有效验证方法是看 diff 风格漂移度。跑几次任务后,把 AI 生成的新代码和同目录下的人类代码放一起,感受是否“一眼像同一个人写的”。平时我只看逻辑对不对,很少检查风格;但有了 AGENTS.md,我会专门检查 AI 生成的代码有没有跟既有代码风格割裂。有时候 AGENTS.md 里第一条“错误处理统一用 Result”,AI 遵守了;但它在命名上又开始搞匈牙利命名法,这说明名称规则表达得不够强,或者位置太靠后没被读到。
4.3 团队协作与迭代:AGENTS.md 要随项目一起生长
写 AGENTS.md 不是一次性行为。项目在变,规则在变,甚至 AI 工具的能力也在变。我见过一个常见的失败模式:项目组花一天时间写了详尽版 AGENTS.md,之后半年没人碰,最后 AI 的行为跟规则越来越远。这不是 AGENTS.md 的错,是你没让它跟代码同步成长。
我现在的节奏是:每次 code review 发现 AI 犯同类错误超过两次,就为它新增一条规则;每次把某条规则从 AGENTS.md 里删掉,一定是确认它已经变成了自动化检查(比如 eslint 规则、编译期错误)。这样 AGENTS.md 里的每条规则都有明确价值,不会沦为僵尸条目。
一个额外的经验是:规则不要一个人闷头写,团队里每个人对“哪些行为不可容忍”的标准不一样。前端工程师可能不关心那个仓储私有方法,后端工程师则视其为命根子。我在模拟项目X里是让前后端各派代表各写一段,最后再合并去重。合并时争执是难免的——你说“禁止在 service 里发请求”,他说“我们有个回调就是这样”——我的原则是“谁的地盘谁说了算”。领域规则的最终发言权属于那个方向的技术负责人。
5. 常见问题速查:为什么 AGENTS.md 有时不灵
这部分是大家最容易遇到问题的地方。我把自己和一些同行踩过的坑整理成速查表,你在排查时可以按图索骥。
| 表现 | 可能原因 | 解决办法 |
|---|---|---|
| AI 完全像没读过 AGENTS.md | 文件名不对或放错目录 | 确认文件名严格是 AGENTS.md,位置在项目根目录 |
| 看到规则但不遵守 | 规则用了建议式语气 | 改成“必须”“禁止”,并配上对/错示例 |
| 只遵守前半部分规则 | 文件太长,后部信息被截断 | 把最高优先级红线放到文件最顶部,精简总行数 |
| 新代码风格仍跟项目割裂 | 风格条款太抽象 | 补一个“参考实现文件”路径,让 AI 直接模仿 |
| 规则只对部分工具生效 | 工具版本老旧或不兼容 | 确认 AI 工具版本是否支持 AGENTS.md 自动读取 |
| 加新规则后旧功能变差 | 新旧规则冲突 | 先理清规则优先级,冲突时明确哪条优先 |
| 某些格式信息(表格)解析错 | 复杂 Markdown 导致解析割裂 | 规则用短段落和列表写,表格只做补充 |
还想补充两个不属于表格但很常见的问题。第一个是“AGENTS.md 里写了规则,但 AI 生成时还是不守”——这种情况我见最多的是因为 AGENTS.md 里的规则跟代码里的既定事实冲突。比如你写“数据访问必须走仓储接口”,但代码里明明到处都是db.query的直连,那 AI 会觉得规则可能只是“理想”,实际项目实际是另一种做法。这时候不只是写规则,还要把 AGENTS.md 里引用的“标准路径示例”跟代码里的实际做法统一起来,让规则与事实一致。
第二个是“别把所有希望都压在 AGENTS.md 上”。它再完善,也不能替代编译检查、静态分析和自动化测试。AI 在遵守上下文规则方面已经进步很多,但它毕竟是概率模型,偶尔还是会抽风。我现在的用法是:AGENTS.md 负责“减少概率”,CI 和 lint 负责“兜住底线”,Code Review 负责“最终裁决”。三者各管一段,整体效果最稳。
6. 踩过几次坑之后,几条想说的真话
回到文章开头那个场景。后来我在 AGENTS.md 里明确写上了“数据访问只允许走仓储接口、事务必须由 service 开启、领域对象字段禁止外部直接写入”这三条,再让 AI 去补状态流转,它给出的方案就是老老实实走 service 方法——虽然最后还是让我改了十五分钟,但“绕过架构”这一类问题基本绝迹了。我不觉得是 AI 变聪明了,而是我终于把它缺的上下文给了它。
每次有团队来找我聊 AI 编码效率,我最后都会说:别把 AGENTS.md 当成写文档任务,把它当成“给 AI 同事做入职培训”。新人入职你要给他讲架构、讲规范、讲红线、讲怎么自测;AI 同事也一样,只是它需要的是精简、命令式、可检索的文本,而且它入职培训比人快得多——改完文件立刻生效,不用等它消化。
你项目里那些最容易让 AI 闯祸的“潜规则”,现在就可以开始列。别追求一次写完。先把最近 code review 里 AI 犯过的错按四类事实归类,挑一两个最扎心的写进 AGENTS.md,让 AI 下次开工前先读到。跑几轮,再加新的。等它的“捣乱频率”降下来,你会发现,改 AGENTS.md 花掉的时间,远比你返工擦屁股的时间省得多。