张彦峰ZYF头像
读得懂代码不等于摸得清仓库:为 Java 仓库建立 AI 开发说明书的结构地图、构建命令、编码红线与禁改边界封面图

读得懂代码不等于摸得清仓库:为 Java 仓库建立 AI 开发说明书的结构地图、构建命令、编码红线与禁改边界

目录

一、为什么 AI 需要一本仓库说明书

(一)会写 Java 不等于会改这座仓库

(二)没有说明书时,AI 靠试探认识仓库——试探读不出禁令

(三)README 为什么不够

二、说明书放在哪里:载体、嵌套与单一事实源

(一)AGENTS.md:跨工具的开放约定

(二)各工具的专有通道

(三)单一事实源:一份内容,多处引用

三、第一部分怎么写:项目结构地图

(一)写职责与依赖方向,不写文件清单

(二)新增代码的落点规则

(三)路标:入口、配置与契约

四、第二部分怎么写:构建与验证命令

(一)命令必须可直接执行

(二)分级验证:把时间成本标在命令旁边

(三)失败的自解释:常见故障与处置预案

(四)验证闭环是代理自主性的地基

五、第三部分怎么写:编码约束

(一)红线与偏好分开,各自交给不同的执行者

(二)示例胜于形容词

(三)三层执行:AI 自觉、本地工具、CI 闸门

(四)业务红线:模型最缺的那部分知识

六、第四部分怎么写:禁止修改范围

(一)四类禁区

1、生成与派生的代码

1.1 构建期生成物

1.2 契约生成的桩与模型

2、数据库迁移历史

3、安全与合规配置

4、已发布的接口契约

(二)禁令必须配替代路径

(三)软禁令之外,给禁区上硬锁

七、写作纪律:说明书本身的工程化

(一)长度预算与渐进披露

(二)新鲜度:说明书与代码同一个评审流

(三)错误驱动迭代:一次事故,一条规则

(四)有效性验证:说明书也需要测试

八、一份可落地的样例

(一)样例全文

(二)逐段讲解

1、身份段:先立坐标系再谈规则

2、结构段:表格 + 落点 + 路标的三件套

3、命令段:阶梯 + 前提 + 故障对照

4、红线段:五条,每条都过了筛子

5、禁区段与维护段:不许 + 怎么办 + 谁来保鲜

九、常见反模式与上线自查

(一)五类反模式

(二)上线前自查清单

参考资料


干货分享,感谢您的阅读!

给一个训练有素的 AI 编码代理布置任务:在订单详情接口上加一个「预计送达时间」字段。它写出的每一行代码单看都是对的——DTO 加字段、Service 组装、单元测试齐全——但它把新类放进了早已冻结的公共模块,跑验证时凭直觉执行了要四十分钟的全量测试,顺手「修复」了一个由接口契约生成的桩类,还改动了一份三个月前已经上线的数据库迁移脚本。这些错误没有一个源于能力不足,全部源于信息不在场:模块边界、验证命令的分级、哪些文件是生成物、哪些历史不可触碰,这些事实不写在任何一行代码里,模型再强也推不出来。

这次我们讨论的就是把这些事实固化下来的那份文件——仓库级 AI 开发说明书:它为什么必要、放在哪里才会被读到、四个核心部分(结构地图、构建命令、编码约束、禁改范围)各自怎么写、以及说明书本身如何像代码一样被评审、迭代和验证。

一、为什么 AI 需要一本仓库说明书

(一)会写 Java 不等于会改这座仓库

大语言模型带进仓库的是通用知识:Java 语法与标准库、Spring 的装配惯例、Maven 的生命周期、主流的设计模式与测试写法。这部分知识在所有仓库之间是共享的,也是模型最可靠的部分。但一次真实的代码变更,依赖的绝大多数是另一类知识——只对这座仓库成立的局部事实。

把局部事实摊开来看,恰好落进四个筐里。第一是结构性事实:仓库分几个模块、各自的职责边界在哪、依赖方向允许怎么走、新增一个接口要在哪几层落几个文件。第二是操作性事实:用哪条命令编译、跑哪个级别的测试、验证要多久、失败了通常是什么原因。第三是规范性事实:这个团队把哪些写法定为红线、金额用什么类型、日志怎么脱敏、异常怎么处理。第四是边界性事实:哪些目录是生成物不能手改、哪些契约已经发布不能破坏、哪些配置动一下就是安全事件。

这四类事实有一个共同点:它们要么完全不体现在代码里(比如「不要改已上线的迁移脚本」——脚本自己不会说自己不可改),要么体现得极其分散(比如分层约定散落在几百个类的包名里,需要大量采样才能归纳)。模型的通用知识对它们毫无帮助,甚至有害——通用惯例与本仓库约定冲突时,模型会自信地选择前者。AI 开发说明书要填的就是这条缝:用两千字左右的篇幅,把四类局部事实一次性交代清楚。这四类事实,正好对应说明书的四个核心部分。

(二)没有说明书时,AI 靠试探认识仓库——试探读不出禁令

有人会说:代理有工具,可以自己探索仓库。确实可以,而且探索能力越来越强:列目录、全文检索、读 POM、抽样看几个典型类,几分钟内就能对结构建立大致印象。但靠试探认识仓库有三笔账要算。

第一笔是重复成本。探索发生在会话内,产出的认知随会话结束一起消失。十个任务就要探索十次,每次消耗数千到数万 token、数十秒到数分钟,而探索出来的结论——「这个仓库分五个模块、用 Maven 构建」——十次是完全相同的。说明书的本质就是把这份重复劳动做一次、存下来、每次会话直接注入。

第二笔是采样偏差。探索是抽样,抽样会抽到反例。仓库里总有历史遗留的坏代码——用了 double 存金额的老类、绕过分层直接查库的临时方案。代理抽到它们,会当成本仓库的惯例去模仿,坏样本就此复制。说明书的作用是给出规范口径:哪些是惯例、哪些是待清理的历史,让代理有能力区分「存在」与「应当」。

第三笔账最关键:有些事实原理上就探索不出来。代码只记录了「是什么」,不记录「为什么」与「不许」。一个由 OpenAPI 契约生成的类,和手写的类在文本上没有任何区别;一份不可修改的迁移脚本,和一份还在开发中的迁移脚本长得一模一样;一个「看起来没人用」的字段可能正被下游以序列化形式依赖。禁令、冻结、外部依赖,这些边界性事实只存在于团队的口头共识与事故记忆里——不写下来,任何智能都读不到。

(三)README 为什么不够

多数仓库已经有 README,为什么还要单写一份给 AI 的说明书?因为两者的读者模型完全不同,写法也就完全不同。

README 写给人。人类新人有导师带、有同事可以问、犯了错有代码评审兜底,所以 README 可以写得概览化、营销化——项目愿景、架构图、快速开始,细节留给口口相传。而 AI 代理在一次会话内自主决策,没有人可问,输出的字面意义就是它的全部理解。写给它的文字必须满足三个条件:可执行(命令能直接复制运行,而不是「运行测试即可」这种描述)、无歧义(「谨慎修改核心模块」是废话,「core 模块只允许新增类,修改既有类需要在 PR 里说明理由」才是指令)、含边界(人类靠常识知道别碰的东西,必须显式写出来)。

还有一个残酷的差别:README 过期了没人发现,因为老员工根本不看它;说明书过期了会立即造成事故,因为代理会一字不差地执行那条三年前就换掉的构建命令。这既是压力也是好处——写给 AI 的文档天然带着「必须保鲜」的约束,反过来逼着团队把它当代码维护。第七章会专门讨论这件事。

二、说明书放在哪里:载体、嵌套与单一事实源

(一)AGENTS.md:跨工具的开放约定

写好的内容要放在代理确定会读的位置。目前事实上的跨工具标准是仓库根目录的 AGENTS.md——一个由 OpenAI 发起、多家工具厂商跟进的开放格式,本质就是一个普通的 Markdown 文件,没有强制的章节结构,约定的只有文件名、位置和加载规则。Codex、Cursor、Copilot CLI、Gemini CLI 等主流代理都会自动读取它;截至撰稿时,OpenAI 自己的主仓库里放了 88 个。

复数个是因为它支持嵌套:大型仓库或多模块工程可以在子目录再放 AGENTS.md,代理读取「离被编辑文件最近」的那份,就近的优先于上层的,用户会话里的显式指令又高于一切文件。这个机制对 Java 多模块仓库非常合身:根文件写全局事实(构建命令、全局红线、禁区),order-service/AGENTS.md 写订单模块的局部约定,互不干扰。

但要注意一个现实的坑:「就近优先」在不同实现里语义不同。有的工具是「最近的一份独占生效」,有的是「从根到叶全部拼接、近者靠后」,拼接模式下如果根文件用命令式口吻写了一条、叶子文件用陈述式口吻写了相反的一条,模型未必听叶子的。写作对策是:不要让两级文件谈论同一件事。根文件只写真正全局的内容,叶子只写根没有覆盖的局部事实,从源头上让「优先级」无事可裁。

(二)各工具的专有通道

在 AGENTS.md 之外,各家工具还有自己的专有配置,能力略有差别,值得了解的是三家。

Claude Code 读取 CLAUDE.md,支持用户级、项目级、目录级三层作用域,特有的能力是 @路径 导入——一行 @AGENTS.md 就能把另一个文件的全文注入,这使它很适合做「薄壳」;配套的 .claude/rules/ 目录支持按路径通配符条件加载的规则文件。

Cursor 读取 .cursor/rules/ 下的 .mdc 规则文件,每个文件带 YAML 头,三个字段决定加载时机:alwaysApply: true 常驻注入;配 globs 则只在会话涉及匹配文件时自动附加;只配 description 则由代理自行判断相关性再拉取。这套条件加载机制是控制上下文成本的利器——Java 仓库可以把「数据库迁移规范」做成只在触碰 src/main/resources/db/migration/** 时才加载的规则,平时不占一个 token。Cursor 同时也读根目录与嵌套的 AGENTS.md。

GitHub Copilot 读取 .github/copilot-instructions.md 作为仓库级指令,另支持 .github/instructions/*.instructions.md 按 applyTo 通配符做路径级指令,机制与 Cursor 的 globs 同构。

三家的共同趋势很清楚:一个常驻的根文件,加一组按路径条件加载的规则文件。写作时可以把这当成目标形态来设计内容的分层。

(三)单一事实源:一份内容,多处引用

工具多了,最容易犯的错是把同一套约定复制进 AGENTS.md、CLAUDE.md、.cursor/rules、copilot-instructions.md 四个地方。复制的那天它们是一致的,三个月后必然漂移——有人改了构建命令只更新了其中一份,于是不同工具对同一仓库持有不同的「事实」,排查起来比没有说明书还痛苦。

正确的结构是单一事实源:AGENTS.md 是唯一正本,其余文件做薄壳。Claude Code 的壳只有一行 @AGENTS.md;Cursor 的壳是一个 alwaysApply: true 的 .mdc,正文引用正本再补充 Cursor 特有的 glob 规则;Copilot 的壳同理。改约定永远只改正本,一处生效、处处生效。如果团队工具链更复杂,可以用预提交钩子或同步脚本把正本内容分发到各工具的期望路径,把「一致性」交给机器保证。

三、第一部分怎么写:项目结构地图

(一)写职责与依赖方向,不写文件清单

结构地图最常见的写法错误,是把 tree 命令的输出贴进去——三百行目录树,看似详尽,实则三害俱全:占用大量 token;文件级信息代理自己一条 ls 就能拿到,写进说明书毫无增量;目录树每周都在变,说明书里的版本永远是过期的。

地图的正确颗粒度是模块级,而且要写代理探索不出来的两样东西:职责边界和依赖方向。一个 Maven 多模块电商单体,写成表格不过十行:

| 模块           | 职责(一句话)                     | 允许依赖           |
|----------------|-----------------------------------|--------------------|
| mall-api       | 对外 HTTP 接口层,只做参数校验与转发 | mall-service       |
| mall-service   | 业务编排与事务边界                  | mall-domain, mall-dao |
| mall-domain    | 领域模型与领域服务,禁止依赖框架     | 无(仅 JDK)        |
| mall-dao       | 持久化实现,MyBatis Mapper 落点     | mall-domain        |
| mall-common    | 跨模块工具类,已冻结,只减不增       | 无                 |

这张表交代的每一格都是探索不出来或探索代价极高的:mall-domain 禁止依赖框架,这是意图不是现状,代码里最多只能看到「目前没依赖」;mall-common 已冻结,这是决策不是事实,任何静态分析都读不出来。依赖方向一列尤其重要——它直接决定代理把新类放进哪个模块时会不会制造循环依赖或层级穿透。

(二)新增代码的落点规则

结构地图的第二个组成部分,是把「加东西放哪里」写成决策规则。代理最高频的动作不是重构,而是新增:新增一个对外接口、一张表的访问层、一个定时任务。每一种新增在成熟的 Java 仓库里都有固定的落点路径,把它写成清单,代理的文件落位准确率会直接从「靠模仿猜」变成「按图施工」:

新增一个对外 HTTP 接口,按顺序落四个文件:
1. mall-api    /controller/{领域}Controller.java   —— 只做校验与转发
2. mall-api    /request、/response 各一个 DTO      —— 后缀 Request / Response
3. mall-service/{领域}Service.java 加方法          —— 事务注解落在这一层
4. mall-dao    /mapper/{表名}Mapper.java 按需加方法 —— SQL 写在 XML,不用注解 SQL
禁止:Controller 直接调用 Mapper;DTO 与领域对象混用。

这一段的价值在于它同时回答了「放哪」「叫什么」「哪层负责什么」三个问题。命名后缀(Request/Response、Service、Mapper)看似小事,却是代理生成代码与仓库风格一致性的最大来源;事务注解落在哪一层、SQL 写在 XML 还是注解,都是模型通用知识里存在多个流派、必须由仓库明确指定的分歧点。

(三)路标:入口、配置与契约

地图的第三个组成部分是一组路标——那些代理迟早要找、但目录名未必自解释的关键位置:启动类在哪个模块;配置文件怎么分环境(application.yml 加 application-{env}.yml,本地起服务用哪个 profile);数据库迁移脚本在 db/migration,命名规则是什么;对外契约文件(OpenAPI 的 YAML、Protobuf 的 proto)在哪、由谁生成什么。路标不需要解释,只需要指路:一行一个「什么东西在哪、看什么文件」。

写路标时同样遵守颗粒度纪律:只写变化慢的锚点,不写变化快的细节。「迁移脚本在 db/migration」十年不变,值得写;「目前最新的迁移版本是 V137」下周就过期,不要写。判断一条信息该不该进说明书的通用标准是:它的变更频率是否显著低于说明书的评审频率。低于,写进去;高于,让代理现场探索,说明书只提供探索的起点。

四、第二部分怎么写:构建与验证命令

(一)命令必须可直接执行

操作性事实的写法只有一个标准:复制即可运行。这意味着三件事。

第一,写全命令行,不写意图描述。「运行相关测试」对代理是个谜语——它会猜一条命令,猜错的形式可能是跑了四十分钟全量,也可能是漏掉了集成测试。正确的写法是把命令一字不差地给出:./mvnw -pl mall-service -am test。

第二,用包装器锚定工具版本。命令统一写 ./mvnw、./gradlew 而不是裸的 mvn、gradle,让 Maven Wrapper 或 Gradle Wrapper 保证构建工具版本与仓库锁定的一致;JDK 版本用 toolchain 或在说明书里显式声明(「本仓库要求 JDK 17,./mvnw -v 确认后再动手」)。代理的执行环境千差万别,版本漂移导致的「我这里能跑」问题在无人盯着的自主会话里尤其致命。

第三,交代环境前提。首次构建要访问私服,凭证从哪来;集成测试用 Testcontainers,本机必须有 Docker 守护进程;某些测试依赖本地起 Redis——这些前提不写,代理会在失败信息里打转,把「Docker 没起」误诊为「代码有病」,然后开始错误的修复。

(二)分级验证:把时间成本标在命令旁边

验证命令不该只给一条,该给一个阶梯。Java 仓库的验证天然分级:编译最快,单模块测试次之,全量测试和集成测试最慢,静态检查独立一档。把阶梯连同时间成本写出来,代理才有依据在「改了两行想快速确认」和「提交前的完整验证」之间做出正确选择:

验证阶梯(由快到慢,按需选择):
1. 编译检查        ./mvnw -q compile                 约 30 秒
2. 单模块测试      ./mvnw -pl {模块} -am test        约 1–3 分钟
3. 全量测试        ./mvnw test                       约 15 分钟,提交前必须通过
4. 静态检查        ./mvnw spotless:check checkstyle:check   约 1 分钟
   格式问题直接运行 ./mvnw spotless:apply 自动修复,不要手工调格式。
5. 架构规则        全量测试中包含 ArchUnit 用例,分层违规会在这里报错。

时间标注不是装饰。没有它,代理面对十五分钟的全量测试有两种同样糟糕的倾向:要么每改一行就跑一次,把会话时间烧光;要么嫌慢干脆不跑,带着未验证的变更收工。标注了成本与「提交前必须通过哪一级」,两种倾向都被规则接住了。

(三)失败的自解释:常见故障与处置预案

命令会失败,而失败信息往往不自解释。说明书里值得维护一小节「常见失败对照」,把团队都知道、代理不知道的排障捷径写下来:Testcontainers 报 Could not find a valid Docker environment,先确认 Docker Desktop 已启动,不要去改测试代码;首次构建报依赖下载 401,是私服凭证未配置,参照某文档配置 settings.xml;OutOfMemoryError: Metaspace,用 MAVEN_OPTS=-Xmx2g 重试;已知的两个不稳定测试在 flaky-tests.md 列出,重跑一次通过即可,不要花时间修它们。

每一条的结构都是「症状 → 归因 → 处置」,且要点在于排除错误方向:代理面对陌生失败的默认反应是修改代码,而上面四种失败没有一种该用改代码来响应。一条对照能省下的,是代理沿错误方向走出去的整段弯路。

(四)验证闭环是代理自主性的地基

为什么操作性事实值得整整一章的篇幅?因为可执行的验证命令是代理自主工作循环的地基。代理的工作模式本质上是「修改 → 验证 → 依据反馈修正」的循环,循环的质量取决于验证信号的质量:一条能明确判对错的命令,价值超过十段描述性文字。说明书把验证阶梯写清楚,等于把这个循环的反馈通道接好了——代理犯的错会被测试当场拦住并自行修正,而不是流到评审阶段消耗人的注意力。反过来说,一个测试孱弱、验证全靠人眼的仓库,说明书写得再好,AI 产出的质量上限也被锁死了。让仓库对 AI 友好,第一优先级从来不是写文档,而是让 ./mvnw test 真正能判对错。

五、第三部分怎么写:编码约束

(一)红线与偏好分开,各自交给不同的执行者

编码约束最容易写砸的方式,是把团队 Wiki 上三十页的编码规范原样搬进说明书。三十页里混着两种性质完全不同的东西:红线——违反即阻塞合并的硬规则,比如禁止空 catch、金额必须 BigDecimal;偏好——影响可读性但不影响正确性的软规则,比如行宽、导入顺序、大括号风格。

两者的处理方式应该完全不同。偏好不要写给 AI,交给格式化工具:Spotless 或等价工具在本地和 CI 各挂一道,spotless:apply 一条命令统一全部格式问题。让代理在生成代码时惦记「行宽不超过 120」纯属浪费上下文——它记不牢,记牢了也不如工具改得快。说明书里关于格式只需要一句话:「格式由 Spotless 管,提交前跑 ./mvnw spotless:apply,不要手工调格式」。

红线才值得写进说明书,而且要少而硬。筛选标准:违反后果严重(资损、事故、安全合规)、工具难以完全拦截(语义级而非语法级)、模型通用知识里存在相反惯例(比如网上大量教程用 double 演示价格计算)。一个 Java 交易系统的红线清单大致是十条上下:异常必须处理或声明抛出、禁止用异常做流程控制、金额一律 BigDecimal 且指定舍入模式、禁止 Executors 工厂方法创建线程池、集合声明用接口类型、魔法值必须提取为常量、IO 与连接必须 try-with-resources。超过二十条,每一条的边际服从率都在下降——上下文里的规则不是越多越好,是越聚焦越好。

(二)示例胜于形容词

写红线的时候,有一个几乎无例外的经验规律:能写成正反例的,不要写成形容词。「妥善处理异常」「使用有意义的命名」这类句子对模型的行为几乎没有牵引力,因为它自认为一直在「妥善」和「有意义」。同一条约束换成代码对照,服从率立刻不同:

// 禁止:吞掉异常
try {
    doTransfer(order);
} catch (Exception e) {
    // 空 catch
}

// 要求:记录上下文并转译为业务异常
try {
    doTransfer(order);
} catch (TransferException e) {
    log.error("转账失败, orderId={}", order.getId(), e);
    throw new BizException(ErrorCode.TRANSFER_FAILED, e);
}

正反例的力量来自两方面:它消除了措辞的歧义(「处理」到底指什么,例子一给就清楚了);它同时提供了模仿的模板(代理生成代码时会直接套用正例的形状,连日志格式、错误码风格都一并对齐了)。每条红线配一组四到八行的对照,整个约束章节的篇幅依然可控,效果却是形容词写法的数倍。

(三)三层执行:AI 自觉、本地工具、CI 闸门

必须清醒的一点是:写进说明书的约束只是第一层,靠的是模型的「自觉」,而自觉是概率性的——上下文长了、任务复杂了,任何一条规则都可能被淡忘。工程上可靠的约束体系是三层递进:第一层,说明书告知,让代理多数时候第一次就写对;第二层,本地工具强制,Spotless 管格式、Checkstyle 管风格、Error Prone 在编译期捕捉缺陷模式,代理跑验证阶梯时被当场打回;第三层,CI 闸门兜底,同样的检查在流水线上再跑一遍,外加 ArchUnit 用例把分层依赖、包结构约定变成可执行断言——「domain 不得依赖 Spring」在这一层不是文字,是一个会红的测试。

三层的关系写进说明书本身,还有一个巧妙的收益:告诉代理「你写的代码会被哪些工具检查、自检命令是什么」,它就会在提交前主动运行这些工具并按报错修正,把第三层的拦截前移到第一层完成。说明书与自动化不是替代关系,是「告知」与「强制」的分工——只告知不强制,规则会被概率淡忘;只强制不告知,代理要靠反复碰壁来学习每一条规则,烧的是时间和 token。

(四)业务红线:模型最缺的那部分知识

技术性红线之外,别忘了业务与领域约束——这是模型通用知识里最空白的区域。敏感数据处理(手机号、身份证号落日志必须脱敏、API 响应必须掩码)、资金语义(金额字段的单位是分还是元、舍入规则统一用哪种)、时间口径(全系统时区约定、跨天结算的边界定义)、幂等要求(哪些写接口必须幂等、幂等键怎么取)。这些约束一旦违反就是合规事件或资损事故,而模型不但不知道,连「该问」的意识都没有——通用语料里没有任何东西提示它「这个仓库的金额单位是分」。业务红线的写法同样遵循正反例原则,且值得在说明书里独立成节,与技术红线分开,因为它们的审查者不同:技术红线靠工具,业务红线更多要靠评审时的人眼,写清楚是为了让代理少犯、评审者好查。

六、第四部分怎么写:禁止修改范围

(一)四类禁区

禁改范围是四个部分里最短的一章,却是防止最严重事故的一章。Java 仓库的禁区高度模式化,逐类过一遍。

1、生成与派生的代码

1.1 构建期生成物

target/、build/ 目录下的一切,以及注解处理器(Lombok、MapStruct)在编译期生成的类。这类禁区相对安全——手改了也会被下次构建覆盖——但代理不知道这一点时,会把「修复生成类」当成有效劳动,白白烧掉会话时间,还可能把生成物提交进版本库。说明书一句话就能封住:「target/ 与一切 generated-sources 是构建产物,发现其中有问题,修上游的源(注解、模板、契约),不要改产物」。

1.2 契约生成的桩与模型

由 OpenAPI、Protobuf、Avro 契约文件生成的客户端桩、服务端骨架与数据模型。这类更危险:它们往往被提交在版本库里,文本上与手写代码毫无区别,代理顺手「优化」一个字段名,编译还能通过,运行时序列化就与对端对不上了。禁令必须指名道姓:「*-api-client 模块与所有 *Stub、*Proto 类由契约生成,改契约请改 api-contracts/ 下的 YAML/proto 并重新生成,禁止手改生成类」。

2、数据库迁移历史

Flyway 或 Liquibase 的已执行迁移脚本是只追加的账本:每个脚本有校验和,已在任何环境执行过的脚本一旦被修改,校验和失配会让该环境的启动或部署直接失败。而在代理眼里,「上一个迁移里的字段类型不对,改那个文件」是再自然不过的修复路径。禁令要连带替代路径一起写:「db/migration 下已存在的脚本一律不可修改、不可删除、不可重排序号;要变更表结构,新建 V{下一个版本号}__描述.sql 往前走」。

3、安全与合规配置

CI 流水线定义、分支保护与 CODEOWNERS、依赖版本锁定(父 POM 的 dependencyManagement、版本锁文件)、密钥引用与权限清单。这一类的共同点是:改动的影响范围超出代码本身,波及构建安全、供应链与访问控制。代理在解决依赖冲突时「顺手升级一个版本」、在修 CI 失败时「注释掉一个检查步骤」,都是真实发生过的事故模式。禁令口径:「父 POM 的版本仲裁、.github/workflows、CODEOWNERS 的任何改动需要人工发起,代理只可以提出建议,不可以直接修改」。

4、已发布的接口契约

对外 HTTP 接口的路径、参数与响应结构,消息队列的 topic 与消息体结构,以及一切带 @Serial/serialVersionUID 语义的序列化类。它们的共同点是存在仓库之外的消费者:改动在本仓库内一切正常,下游在部署后才炸。禁令写法:「mall-api 中已发布接口的请求/响应字段只可新增、不可删除或改名、不可改语义;需要不兼容变更时走版本化路径(/v2),并在 PR 里显式标注 breaking change」。

(二)禁令必须配替代路径

单纯的「不许」是一半的禁令。代理接到任务是有目标压力的——它要完成那个字段变更、修好那个失败——只写「不要改迁移脚本」而不告诉它正确的路怎么走,结果通常不是收手,而是绕路:绕过迁移直接改 JPA 实体、在代码层做类型转换来「适配」错误的字段类型,绕出来的方案往往比直接违反禁令更隐蔽、更难收拾。所以上一节每条禁令的句式都是「不可 X;要达到目的,请走 Y」:不可改已有迁移,要新建下一版;不可手改生成类,要改上游契约;不可破坏已发布字段,要走版本化。替代路径把禁令从一堵墙变成一个改道标志——目标压力被疏导到正确的方向上,而不是憋成绕行的歪招。

(三)软禁令之外,给禁区上硬锁

与编码约束同理,说明书里的禁区是软约束,重要禁区必须再上硬锁。版本库层面:CODEOWNERS 把 db/migration、.github/workflows、父 POM 划给指定评审人,分支保护要求这些路径的变更必须有人工批准。CI 层面:加一个轻量守卫任务,检测 PR 的 diff 是否触碰禁区路径、已有迁移脚本是否被修改,触碰即失败并输出禁令原文——这个报错信息本身又成了代理的反馈,引导它自行撤销越界改动。代理工具层面:Claude Code 与 Cursor 都支持钩子(hooks),可以在文件写入前拦截匹配禁区路径的编辑操作,把「不可改」从事后检查提前到动作发生的瞬间。三道锁加上说明书的告知,禁区才算真正封闭:告知负责让代理不想越界,硬锁负责让越界不能成功。

七、写作纪律:说明书本身的工程化

(一)长度预算与渐进披露

说明书的每一个字都有持续成本:常驻注入意味着每次会话、每个任务都要为它付 token,而且它在上下文里的位置越靠前、篇幅越大,对模型注意力的稀释越明显。根说明书的合理预算是一千五百到两千五百字——四个部分各三五百字,足够覆盖高频事实。

超出预算的内容不是删掉,是分层。第一层常驻:全局红线、验证阶梯、禁区清单,任何任务都可能踩到的事实。第二层按需:数据库迁移规范、某个复杂模块的详细设计、性能调优指南,做成独立文档放进 docs/,说明书里只留一行索引——「动 db/migration 前必读 docs/migration-guide.md」。代理会在需要时自己去读,读的成本只在相关任务里发生。第三层条件加载:用 Cursor 的 globs、Claude 的 rules 或嵌套 AGENTS.md,让规则跟着文件路径自动出现。这套「常驻小核心 + 按需大外围」的结构,业内称为渐进披露,AGENTS.md 规范的演进提案也在把它标准化——说明书不是一本书,是一张带索引的地图。

(二)新鲜度:说明书与代码同一个评审流

过期的说明书比没有更糟——没有说明书,代理会去探索现状;有一份过期的,它会信心十足地执行错误命令、把新代码放进已经重构掉的目录。保鲜的办法只有一个:把说明书纳入与代码相同的变更流程。它是仓库里的一个受版本控制的文件,改它要走 PR、要有人评审;反过来,改代码的 PR 如果动了模块结构、构建命令、约束或禁区,必须同步更新说明书——把这一条写进 PR 模板的检查清单,甚至写进说明书自己的红线里(「变更构建命令时必须同步更新本文件」),让代理在做出这类变更时自动带上文档修改。

(三)错误驱动迭代:一次事故,一条规则

说明书的第一版不必追求完备,也不可能完备——你无法预知代理会在哪里跌倒。可持续的维护方式是错误驱动:每当代理犯了一个「不该犯」的错,先别急着骂模型,问一个固定的问题——说明书里哪一条缺失或含糊,使得这个错误成为可能?代理跑了全量测试烧掉半小时,是验证阶梯没标时间成本;代理改了已上线的迁移脚本,是禁区清单漏了这一条;代理把工具类塞进了冻结模块,是模块表没写「只减不增」。归因到条目,补一条,用同类任务回归验证一次,闭环完成。

这个回路需要配一个反向机制防膨胀:规则只进不出,两年后说明书又会变成三十页。定期(比如每季度)过一遍,合并同类项、删掉已经被硬锁(CI 守卫、hooks)接管的条目、删掉随着仓库演进已失效的条目。判断一条规则去留的标准和进入时一样:它还在拦截真实会发生的错误吗?

(四)有效性验证:说明书也需要测试

说明书写完了怎么知道有效?和代码一样——测试。准备一小组基准任务,都是仓库里最高频的变更模式:加一个对外接口、给某张表加一个字段并打通读写、修一个给定复现步骤的 bug。让代理在干净会话里执行,检查它的行为是否符合说明书:文件落点对不对、验证命令选的档位对不对、有没有触碰禁区、代码风格是否对齐。哪里不符合,回到上一节的回路里修文字。这组基准任务还有一个额外用途:换模型或换工具时跑一遍,就知道新环境对现有说明书的服从情况,相当于说明书的回归测试集。

八、一份可落地的样例

(一)样例全文

下面是为一个虚构的 Maven 多模块电商单体仓库写的根 AGENTS.md,约一百行,四个部分俱全,可以作为起点模板按需裁剪:

# mall 仓库 AI 开发说明书

## 你在哪里
Maven 多模块单体,JDK 17(用 ./mvnw,禁用系统 mvn),Spring Boot 3.x。

| 模块         | 职责                       | 允许依赖              |
|--------------|----------------------------|-----------------------|
| mall-api     | HTTP 接口,仅校验与转发     | mall-service          |
| mall-service | 业务编排,事务边界在此层    | mall-domain, mall-dao |
| mall-domain  | 领域模型,禁止依赖 Spring   | 无(仅 JDK)          |
| mall-dao     | MyBatis 持久化              | mall-domain           |
| mall-common  | 已冻结:只允许删除,不许新增 | 无                    |

新增对外接口的落点:Controller(api) → DTO(api, 后缀 Request/Response)
→ Service 方法(service, @Transactional 在此) → Mapper(dao, SQL 写 XML)。
禁止 Controller 直接调用 Mapper。

路标:启动类 mall-api/src/.../MallApplication.java;
配置 application.yml + application-{env}.yml,本地用 profile=local;
迁移脚本 mall-dao/src/main/resources/db/migration;
对外契约 api-contracts/openapi.yaml(生成代码见下方禁区)。

## 怎么验证(由快到慢)
1. ./mvnw -q compile                        # 约 30 秒
2. ./mvnw -pl {模块} -am test               # 1–3 分钟
3. ./mvnw test                              # 约 15 分钟,提交前必须通过
4. ./mvnw spotless:check checkstyle:check   # 格式问题跑 spotless:apply
集成测试用 Testcontainers,需要本机 Docker 已启动;
报 "Could not find a valid Docker environment" 先起 Docker,不要改测试。
已知不稳定测试见 docs/flaky-tests.md,重跑一次通过即可。

## 编码红线(违反即打回)
- 金额一律 BigDecimal,构造用字符串,舍入显式指定 RoundingMode。
- 禁止空 catch;捕获后记录上下文并转译为 BizException。
- 禁止 Executors 工厂方法;线程池用 ThreadPoolExecutor 显式配置。
- 手机号/身份证号落日志必须脱敏(MaskUtil),响应字段用 @Masked 注解。
- 格式不用记:提交前 ./mvnw spotless:apply。
分层依赖由 ArchUnit 用例强制(mall-arch-test),违规会在全量测试里报错。

## 禁改范围(每条都有替代路径)
- db/migration 下已有脚本不可改、不可删;变更表结构新建 V{n+1}__xxx.sql。
- generated-sources 与 *-client 模块由 api-contracts 生成;
  要改字段,改 openapi.yaml 后运行 ./mvnw -pl api-contracts generate-sources。
- 已发布接口字段只增不删不改名;不兼容变更走 /v2 并标注 breaking change。
- 父 POM 版本仲裁、.github/workflows、CODEOWNERS:只可建议,不可直接修改。

## 维护本文件
改动模块结构 / 构建命令 / 红线 / 禁区的 PR,必须同步更新本文件。

(二)逐段讲解

1、身份段:先立坐标系再谈规则

开头两行交代仓库形态、JDK 版本与包装器要求,成本三十个字,收益是代理后续所有命令与语法选择都有了坐标系。「禁用系统 mvn」这半句尤其值得:它预先封掉了一个高频的环境漂移来源。

2、结构段:表格 + 落点 + 路标的三件套

模块表十行写完职责与依赖方向,落点规则用一条箭头链交代四层文件的位置与命名,路标四行指清入口、配置、迁移、契约。注意整段没有一处会随日常开发过期——这是第三章颗粒度纪律的直接体现。冻结模块的表述用了最强的行为化写法「只允许删除,不许新增」,比「原则上不建议修改」这种含糊措辞可执行得多。

3、命令段:阶梯 + 前提 + 故障对照

四级阶梯每级带耗时与用途,Docker 前提与最高频的一条报错直接给出处置方向,不稳定测试外链到按需文档——常驻内容只留索引,正文细节放外围,渐进披露落在实处。

4、红线段:五条,每条都过了筛子

这里只留了五条,每一条都满足第五章的筛选标准:后果严重、工具难拦、模型通用惯例可能相反。格式类偏好整体让位给 Spotless,只留一行操作指引;最后一行把 ArchUnit 的存在告知代理,让第三层闸门的拦截前移。

5、禁区段与维护段:不许 + 怎么办 + 谁来保鲜

四条禁令全部带替代路径,句式统一「不可 X,要 Y 走 Z」;收尾的维护条款把说明书自己纳入变更红线,第七章的保鲜机制由此自举。

九、常见反模式与上线自查

(一)五类反模式

对照前文,把实践中最常见的五种写砸方式列出来,写完自查一遍。

第一类,README 复读机:把面向人类的项目简介、愿景与徽章原样搬进来,占了篇幅,四类局部事实一个没讲。第二类,愿望清单:通篇「写出高质量、可维护、健壮的代码」,全是形容词,没有一条可执行、可判定。第三类,百科全书:三百行目录树加三十页规范全文塞进常驻上下文,高价值红线被淹没在低价值细节里,每个任务都在为不相关的内容付 token。第四类,只有墙没有门:禁令写了一堆,替代路径一条没有,代理被目标压力顶着绕出更隐蔽的歪招。第五类,一次性工程:上线三个月从未更新,构建命令换了、模块重构了,说明书还在自信地描述旧世界——过期的地图比没有地图更危险。

(二)上线前自查清单

最后把全文收敛成一份可勾选的清单,新写或翻修说明书后逐项过一遍:

  • 结构:模块表是否写了职责与依赖方向?高频新增场景是否有落点规则?路标是否只含变化慢的锚点?
  • 命令:每条命令是否复制即可运行?是否分级并标注耗时?是否写明「提交前必须过哪级」?环境前提与高频故障是否有对照?
  • 约束:红线是否控制在二十条以内?是否每条都有正反例?格式类偏好是否已交给工具?业务红线(资金、脱敏、时间口径)是否单独成节?
  • 禁区:四类禁区(生成物、迁移历史、安全配置、已发布契约)是否覆盖?每条禁令是否带替代路径?重点禁区是否配了 CODEOWNERS/CI 守卫/hooks 硬锁?
  • 工程化:总长是否在预算内(常驻两千五百字以内)?低频内容是否外移并留索引?PR 模板是否含「同步更新说明书」检查项?是否有基准任务可以回归验证?

这份清单本身也回答了开篇的问题。AI 编码代理的能力在快速增长,但再强的通用能力也替代不了局部事实的供给——模块的边界、命令的档位、团队的红线、历史的禁区,这些只有你的团队知道的事情,值得用两千字认真写一次,然后像维护代码一样维护它。说明书不是给 AI 的施舍,它是团队工程知识的一次强制显式化——写完你会发现,最大的受益者往往不是代理,而是下一个入职的人。

参考资料

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/xiaofeng10330111/article/details/166735638

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--