给 AI 立规矩:AGENTS.md 与项目约定文件怎么写

同一句「加个接口」,在你熟悉的仓库里 AI 写得像模像样,在陌生仓库里却能改错分层、漏掉错误码、把它偏好的日志库引进来。差别不在模型,而在仓库有没有把约定写下来。把「每次都要口头交代一遍」的事搬进仓库、跟着代码一起做评审,收益远大于继续打磨提示词。

三层规则:个人偏好、项目约定、目录细则

层次落在哪写什么长度参考
个人偏好用户级配置文件(不随仓库走)语言偏好、回答风格、常用命令别名10 行以内
项目约定仓库根 AGENTS.md技术栈、常用命令、目录职责、代码规范、硬性边界40~80 行
目录细则子目录下的规则文件该目录特有的约定(如 internal/order/AGENTS.md)20 行以内

文件名不必纠结:AGENTS.md 已被多数工具识别;若某工具只认自己的文件名(如 CLAUDE.md、.cursor/rules/、.github/copilot-instructions.md),就让它们指向同一份内容,避免维护两份会分叉的规则。

# 多工具共用一份规则:其余文件做软链接指向 AGENTS.md
ln -sf AGENTS.md CLAUDE.md
ln -sf ../../AGENTS.md internal/order/AGENTS.md

一个可直接抄的 AGENTS.md 骨架

# AGENTS.md

## 项目概览
Go 1.22 + Gin 的订单服务,MySQL 存订单,Redis 做幂等键;前端为 Next.js 14。

## 常用命令
- 全量检查:`make check`(= golangci-lint run ./... + go test ./... -count=1)
- 单包测试:`go test ./internal/order/... -run TestXxx -v`
- 本地启动:`docker compose up -d mysql redis && go run ./cmd/api`
- 生成 mock:`go generate ./internal/order/...`

## 目录职责
- `cmd/`:进程入口,只做装配,不放业务逻辑
- `internal/order/`:订单领域逻辑,对外只暴露 service 接口
- `internal/platform/`:数据库、缓存、日志等基础设施封装
- `migrations/`:SQL 迁移文件,只增不改

## 代码约定
- 错误统一用 `fmt.Errorf("...: %w", err)` 包装并保留上下文,对外错误码集中在 `internal/apperr`
- 导出函数必须有 doc comment,且以函数名开头
- 日志统一走 `internal/platform/log`,禁止 `fmt.Println` 或另引日志库

## 硬性边界(必须遵守)
- 不修改 `migrations/` 下已存在的文件,新增请另建文件
- 不升级 `go.mod` 依赖;确需新依赖时先说明理由并等确认
- 不改 `.github/workflows/`、`deploy/`
- 代码、测试、日志里不得出现真实手机号、身份证号、密钥

## 交付要求
- 改完必须运行 `make check`,把完整输出贴出来
- 一次只做一件事;发现其它问题写进「建议后续处理」,不要顺手改
- 新增逻辑要有测试,边界用例至少覆盖:空、重复、超限

Next.js 项目把「常用命令」「目录职责」两节换成自己的即可,其余四节结构可以直接沿用:

## 常用命令
- 全量检查:`npm run check`(= next lint + tsc --noEmit + vitest run)
- 单文件测试:`npx vitest run src/features/orders/__tests__/total.test.ts`

## 目录职责
- `src/app/`:路由与页面,只做组装,不放业务规则
- `src/features/<域>/`:领域组件、hooks、接口调用集中于此
- `src/lib/`:无业务语义的工具函数

rules 怎么写:可判定、可执行、给理由

规则文件的质量差别几乎全在「可判定性」上:

写法例子问题
不可判定「代码要优雅、性能要好」无法检查,也无人能验收
可判定「导出函数必须有 doc comment 且以函数名开头」可 lint、可评审、可复述
只给结论「不要用全局变量」换个模型可能被当成建议而非规定
结论 + 理由「不要用全局变量(并发测试会互相污染)」遇到例外时人和 AI 都知道为什么

两条实操建议:条目用「必须 / 禁止 / 优先」开头,括号里补一句理由,避免它把硬约束读成风格建议;能被 lint 检查的规则不要只写在规则文件里——规则文件是给 AI 的,lint 是给所有人的,两边都要有。

# 把最硬的约定固化成 lint,剩下的才需要写给 AI
golangci-lint run --enable=revive,gocritic,errcheck

长规则怎么放:渐进披露

规则文件一长,两头都亏:AI 读不完,人也不维护。做法是根文件只留高频与硬约束,细节拆到子文档:

## 参考文档(动手前按需阅读)
- 改数据库迁移:先读 `docs/agents/migrations.md`
- 改前端表单:先读 `docs/agents/frontend.md`
- 新增错误码:先读 `docs/agents/error-codes.md`

这样根文件能稳定在 60~80 行,子文档随模块增长,子目录规则文件则只写「本目录特例」。

怎么验证规则真的生效

写完不等于生效。花十分钟做一次验证,比反复优化措辞有用:

验证动作期望结果不通过就改什么
让它复述该仓库的三条硬约束能准确说出「不改迁移文件」等条目条目写得太散,提到「硬性边界」小节
故意下违反边界的任务(「直接改 migrations 里的文件」)它指出冲突并等你确认,而不是照做语气太软,改成「必须 / 禁止」
让它照着现有模式加一个新接口分层、错误码、日志都符合约定对应小节缺示例,补一个正确示范
改完看 git diff --stat只动了相关文件边界不清,补「不做的事」清单

维护:规则随事故增长

每次 AI 犯错,先问一句「这条能不能变成规则」,能就当天加进去;每季度再删掉已过时或被 lint 覆盖的条目。规则文件和流水线文件一样走代码评审——它一旦写错,仓库里所有 AI 都会按错误的方式写代码。

常见坑

坑后果规避
写成项目介绍文档太长没人读,AI 也读不完只留命令、目录职责、约定、边界、交付要求
只写「要做什么」,不写「不许做什么」它顺手重构、改无关文件单列「硬性边界」小节
同一条规则在多份文件里各写一遍分叉后互相矛盾一份主文件,其余用软链接指向
规则里塞密钥或内网地址随仓库泄漏一律用占位符
写完从不验证以为生效,实际被忽略按上表做一次十分钟验证

小结:规则文件的价值是把「口头交代」变成「版本化约定」;按个人偏好、项目约定、目录细则分三层,根文件控制在 60~80 行并只留高频与硬约束,细节拆到子文档;条目要可判定、带理由,能交给 lint 的别只指望提示词;写完必须验证它真的生效,并且像代码一样持续评审和维护。

笔记加载中…