前言:AI 不会「故意」改你的方向,它只是在填空
先描述一个你一定经历过的场景。
你让 AI 修一个登录的小 bug。它改完了——顺便把 useState 换成了 Zustand,把 components/ 下面的三个组件挪进了 features/,还把 axios 换成了 fetch。它说「已完成,并顺手优化了状态管理」。
你没让它做这些。而且它做得还不算错——在另一个项目里,这可能就是最优解。
这就是问题的本质:AI 不是叛逆,它是在填空。 你没写清楚的地方,它会用「训练数据里最常见的做法」补上,而那个「最常见」不属于你的项目。Stack Overflow 2025 年的调查里,45% 的开发者把「AI 给的方案几乎对、就差一点」列为最大的挫败,66% 的人说修这种「几乎对」的代码,比自己写还费时间。
上一篇《Vibe Coding 不是只能做 Demo》里我提了四条纪律,其中第二条是「把架构写成文件」。这篇把那条展开——具体写什么、写到哪、怎么写进各家工具的规则文件。
一句话立住全文的立场:
约束 AI 不是不信任它,是把「模型决定」变成「你决定,而且能被检查」。
规矩要提前立,而不是等它改乱了你再一条条往回拉。
第一章 · 先搞清规则文件在哪
同一个「规则」,各家工具的位置和格式都不一样。先上一张对照表——你用什么工具,就往对应的地方写。
| 工具 | 项目规则位置 | 个人/全局规则 | 备注 |
|---|---|---|---|
| Trae | .trae/rules/*.md | ~/.trae/user_rules.md | 四种生效方式:Always Apply / 按 globs 匹配文件 / 按 description 智能判断 / #Rule 手动引用;支持最多三级子目录 |
| CodeBuddy | .codebuddy/rules/*.md + CODEBUDDY.md | ~/.codebuddy/rules/*.md + ~/.codebuddy/CODEBUDDY.md | 规则带 frontmatter(description / alwaysApply / enabled);另有 CODEBUDDY.local.md 走本地、自动进 .gitignore |
| Cursor | .cursor/rules/*.mdc | User / Team Rules | frontmatter 里 alwaysApply / description / globs |
| Claude Code | CLAUDE.md(项目根)+ .claude/rules/*.md | ~/.claude/CLAUDE.md、CLAUDE.local.md | 支持 @路径 导入;2026-09 起,仓库没有 CLAUDE.md 时会回退读 AGENTS.md |
| Codex | AGENTS.md | ~/.codex/AGENTS.md、AGENTS.override.md | 从 git 根一路走到当前目录,越近的越优先;有 32KiB 上限 |
| Copilot | .github/copilot-instructions.md | — | 也读根目录 AGENTS.md;就近优先 |
| Gemini CLI | GEMINI.md | — | 要读 AGENTS.md 得手动在设置里配 context.fileName |
| Kiro | .kiro/steering/*.md | ~/.kiro/steering/ | workspace 覆盖 global |
抛开具体工具,有个跨厂商的标准值得单独说:AGENTS.md。
它 2025 年 8 月由 OpenAI 开源,2025 年 12 月被移交到 Linux Foundation 下的 Agentic AI Foundation(跟 Anthropic 的 MCP、Block 的 goose 并列成为创始项目),采用它开源项目已超过 6 万个。2026 年 9 月 18 日,Claude Code 也原生加了 AGENTS.md 回退支持。
它的价值很朴素:团队同时用 Codex、Cursor、Copilot、Trae 时,不需要给每个工具维护一份规则。一份文件,大家都读。
所以我的建议是:
AGENTS.md ← 唯一真源,所有规则写在这里
CLAUDE.md ← 只写一行:@AGENTS.md(外加 Claude 专属备注)
.trae/rules/... ← 同理,工具专属的才单独放
别把同一套规则在五个地方抄五遍。 下面第二章会讲为什么这是最常见的翻车原因。
第二章 · 为什么你写了规则,AI 还是不听
三种情况,几乎覆盖了我踩过的所有坑。
原因一:规则文件太多,AI 在「抽签」
这是最隐蔽的一种。你在 .cursorrules、CLAUDE.md、AGENTS.md 里各写了一份,内容还略有出入——AI 只会读它认识的那一份,或者按加载顺序读到哪份算哪份。你以为统一了,其实每次都在随机。
更糟的是,长文件里把「严禁 / 不要」写在了最后。模型读文件有注意力衰减,第一屏之后的内容它经常当没看见。
修法就一条原则——SSOT(单一真源):
- 一个仓库,一份权威规则,放根目录
AGENTS.md。 - 工具专属文件降级成指针,只写「见根目录
AGENTS.md」,不重复内容。 - 把禁止项提到前面,别埋在文档末尾。
- 详细设计(
DESIGN.md、架构说明)用链接引用、按需加载,别堆进入口文件变成一个「项目大百科」。
原因二:位置或加载时机错了
- 放错层级:团队规范写进了个人配置,队友看不见;个人偏好写进了共享配置,污染所有人。
- 不向上递归:注意 CodeBuddy 的项目规则
.codebuddy/rules/只扫当前工作目录,不会向上递归——子目录的规则得放进子目录自己的目录里。 - 改了不新开会话:CodeBuddy 明确说了,规则只在新会话开始时加载。你改完规则接着旧对话聊,它读的还是旧的。
原因三:frontmatter 把它「条件化」了
Trae / CodeBuddy / Cursor 都支持 frontmatter 控制生效条件。常见的自杀式写法:
---
alwaysApply: false
---
(然后没写 globs,也没写 description)
alwaysApply: false 又没给匹配条件——这条规则基本等于没写。要么 alwaysApply: true,要么把 globs / description 填准。
顺手一个反直觉的发现
有个 2026 年的研究(arXiv:2602.11988)值得记住:把规则文件的编写交给模型自己生成,反而会拉低任务成功率,还把推理成本推高 20% 以上;手写的规则才有小幅正向收益。而且研究发现——「架构总览」这类泛泛的描述基本没用,真正降低出错率的是:精确的命令、明确的版本约束、清晰的完成定义。
一句话:规则文件要短、要具体、要人写。
第三章 · 五类规则怎么写
下面是我现在每个项目都会落的一套。按管的东西分五类:
① 项目方案规则 做什么 / 不做什么 / 怎么算完成 ← 防它改方向
② 项目结构规则 目录 / 分层 / 依赖方向 / 命名 ← 防它乱搬文件
③ 数据库结构规则 schema 真源 / migration 闸门 ← 防它乱改表
④ 技术栈与依赖 版本钉死 / 不许凭空造包 ← 防它幻觉
⑤ 行为边界规则 改动范围 / 权限 / 收尾报告 ← 防它顺手扩大
① 项目方案规则:让「不做什么」比「做什么」更醒目
AI 最爱犯的错叫 scope creep(范围蔓延):你让它加个筛选,它顺手把列表页重构成通用组件、加了一层缓存、还引入了新依赖。
所以每个功能动手前,先落一份短 spec,放 docs/specs/YYYY-MM-DD-功能名.md。用四段式,缺一段都别开工:
Task 一句话说清可观察的行为(不是「优化一下登录」)
Context 该读哪些文件、跟项目已有的哪个 pattern 走
Scope 只许改哪几个目录;明确列出「不许动」的东西
Acceptance 怎么验证算完成(可执行的检查,不是「确保能用」)
关键是 Scope 那段要写成禁止句式:
Scope:
- 只修改 src/features/task-list/ 下的文件
- 不要改后端 API 契约
- 不要新增依赖(需要时先问我)
- 不要重构无关代码、不要顺手格式化整个文件
「不做什么」比「做什么」更能挡住 AI 的自由发挥——因为它每多改一处,都要先越过一道明确的「不许」。
② 项目结构规则:先把地图画好,再让它动手
规则文件里要有一小段目录地图,说清「东西住哪、依赖往哪个方向流」。不用长,但要有:
结构
- app/ 路由。 (marketing)=公开页,(app)=登录后
- components/ui/ 共享 UI 原子组件,复用,不许重复造
- lib/db/ 所有数据库访问都走这里,别的层不许直接连库
- db/schema.ts 数据库 schema 唯一真源(见③)
依赖方向
- ui 不许 import db;features 不许互相 import
命名
- 组件 PascalCase,函数/变量 camelCase,文件 kebab-case
- 所有请求走 lib/api/request,禁止在组件里直接写 fetch/axios
再加两条「先读后改」的硬要求:
- 动手前先读相关文件,跟随项目已有写法,不要自创一套
- 不要手动改生成文件(migrations、*.gen.ts、dist/)
光有规则还不够,要给它上物理围栏。 Claude Code 支持路径级权限(allowlist + deny),这类是工具强制执行的,比模型的「自觉」可靠得多:
Allow: Edit(/src/**)
Deny: Edit(/lib/db/migrations/**), Edit(/.github/**), Write(/.env*)
再配一条 diff-first 的提示词习惯——要补丁,不要整文件重写:
输出 unified diff,不要返回整个文件。
只改 [指定的函数/文件]。
如果修好它需要动到范围外的东西,停下来先说清楚为什么,等我确认。
整文件重写是最危险的动作:它会把模型上下文里没有的、但你代码里存在的逻辑一起删掉,还夹带风格漂移。
③ 数据库结构规则:schema 是唯一真源,migration 必须过闸
数据库是 AI 最容易「越权」的地方——因为改表结构不可逆。规则要写死三件事:
第一,schema 只有一个真源。 不管用 Prisma 的 schema.prisma 还是 Drizzle 的 schema.ts,表结构只在一个文件里定义,迁移从它生成。规则里写:
- 数据库结构以 db/schema.ts 为唯一真源,禁止直接手改生产库
- 任何 schema 变更必须同时生成 migration 文件,两者一起提交
- 禁止在 migration 里出现 DROP COLUMN / DROP TABLE / TRUNCATE,除非我明确批准
为什么要这么严?因为 AI 偶尔会生成破坏性迁移(直接 ALTER TABLE DROP COLUMN,或者改列类型但没有数据转换)。在 CI 里加一道闸,直接 grep 拦住:
# .github/workflows/db-migration-guard.yml
name: AI Database Migration Guard
on:
pull_request:
paths: ['migrations/**', 'db/schema.ts']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 拦截破坏性 SQL
run: |
if grep -iqE "DROP COLUMN|DROP TABLE|TRUNCATE" migrations/*.sql; then
echo "::error::检测到破坏性迁移,AI 生成的 SQL 需人工复核"
exit 1
fi
改表要用 expand-and-contract(先加列 → 回填 → 确认无碍 → 再删旧列),绝不一步到位。
第二,把 schema 写成「给 LLM 看的」。 这一条实测收益极大:有团队用同样的 100 个复杂 SQL 生成任务,对比「无类型传统 schema」和「带枚举 + 注释 + 外键的 agentic schema」——
| 指标 | 传统 schema | agentic schema |
|---|---|---|
| 生成有效 SQL 的比例 | 68.6% | 98.8% |
| 平均重试次数 | 2.8 | 0.05 |
| schema 自省 token 量 | 48,000 | 12,400(-74%) |
做法就是:外键建在数据库层、状态列用原生枚举、用 COMMENT ON 把字段含义写进 DDL——
COMMENT ON COLUMN memberships.role_mask IS
'位掩码:1=Viewer, 2=Editor, 4=Admin, 8=Owner。权限判断用按位与(&)';
当 AI 读到这句注释,它就不会拿 role_mask 去 join,也不会忘了租户隔离条件。
第三,记住一句心法:让 AI 起草 schema,别让它决定 schema。 它擅长给你一个像模像样的初稿,也擅长提示你没想到的表;但它不擅长判断「这个取舍两年后能不能反悔」。接受它生成的迁移前,一定去读 migration 的 SQL,而不是只看 schema 的 diff——删列、改类型这些,在 SQL 里最显眼。
④ 技术栈与依赖规则:防幻觉依赖(slopsquatting)
这是「防止 AI 幻想」最具体的一类。
模型会凭空发明不存在的包名。USENIX Security 2025 的研究生成了 223 万个代码样本、覆盖 16 个模型,19.7% 至少引用了一个不存在的包(共 20 万+ 个假名),而且43% 的假名在同样提示词重跑 10 次时每次都会出现。到 2026 年,前沿模型的幻觉率降到 4.6%–6.1%,但五个不同模型会发明出同一批假名——这就够了:攻击者提前把那些名字注册下来,等着 AI 建议你 npm install,装进去的就是别人的代码。这叫 slopsquatting。
还有个更阴的地方:光「检查包存不存在」挡不住。有研究者实测,模型常推荐的那几个错包(pil、faiss、tabula、haystack)在 PyPI 上全都返回 200——它们是同名但完全不同的包(比如 tabula 是个 2012 年的 ASCII 表格工具,你要的其实是 tabula-py)。
规则里先写死:
- 禁止引入未经确认的新依赖
- 需要新依赖时,先给出:包名、版本、用途、替代方案,等我确认
- 禁止使用训练记忆里的版本号,安装前先查注册表最新版本
但请记住一句最实在的话:规则是文字,模型可能忽略;钩子(hook)是代码,每次都会跑。 想要硬保障,就在工具的 pre-install 钩子上挂一个校验(有 ExactGround、slopguard 这类现成工具,能拦 npm / pip / uv 等的安装),或者最少拿一个脚本兜底:
# 装之前先问注册表:存在吗?多大年纪?谁维护?有没有仓库?
npm view "$PKG" name version maintainers repository.url time.created
⑤ 行为边界规则:哪些能自己干,哪些必须问
最后这类,是把「谁说了算」写清楚。直接照抄这张表进规则文件:
| 动作 | 是否放行 |
|---|---|
| 读任意文件、在分支上写代码、跑测试 | ✅ 自己干 |
| 改动约定范围之外的文件 | ⛔ 先问 |
| 新增/升级依赖 | ⛔ 先问 |
| 改配置(环境变量、CI/CD、部署脚本) | ⛔ 先问 |
| 改鉴权 / 支付逻辑 | ⛔ 先问(且要人工复核) |
| 动生产环境、跑部署 | ⛔ 先问 |
再配一条「停止规则」——什么时候让它停下来重新对齐,而不是硬着头皮往下做:
- 计划要动的文件超出约定清单 → 停下来说明
- 连续两次实现方案来回变 → 停下来说「可能上下文丢了」
- 同一个问题要问超过 3 次才能澄清 → 说明需求太糊,拆任务
还有一条小而好用的习惯:开工前先打个 git checkpoint。Codex 官方文档就是这么建议的——任务前后各留一个提交点,方便回滚。AI 改乱了,git reset 一下,比一条条往回改划算得多。
第四章 · 完成定义(Definition of Done)
规则里最后要钉一条:什么叫「做完了」。否则 AI 会在没跑测试的情况下,理直气壮地告诉你「测试通过」。
完成前必须报告:
1. 改了哪些文件
2. 具体改了什么
3. 实际执行了哪些验证命令,结果如何
4. 有哪些假设是你自己填的
5. 哪些没能验证
第 3、5 条最关键。没跑就是没跑——要求它说「无法验证」,而不是含糊地宣称通过。研究里也印证过:明确的完成定义,是少数几个真能降低出错率的规则类型之一(比泛泛的架构描述有用得多)。
结论:一份可以直接抄的骨架
把上面五类合成一份入口文件(AGENTS.md,工具专属文件只留指针):
# 项目规则
## 项目是什么
一句话:给谁用、解决什么问题。
## 命令(精确,别写「应该」)
- 安装:pnpm install
- 开发:pnpm dev
- 测试:pnpm test(Vitest)、pnpm test:e2e(Playwright)
- 检查:pnpm lint && pnpm typecheck
## 结构
- app/ 路由;components/ui/ 复用组件;lib/db/ 唯一数据库入口
- 依赖方向:ui 不许引 db;features 不许互引
- 命名:组件 PascalCase,文件 kebab-case
## 数据库
- db/schema.ts 是唯一真源,变更必须附带 migration
- 禁止破坏性 SQL,改表用 expand-and-contract
- 字段含义写进 COMMENT ON
## 依赖
- 不许凭空造包/版本;新增依赖先列出来等我确认
## 禁止(放前面,别埋后面)
- 不重写整个文件,只给 diff
- 不顺手重构、不顺手格式化、不擅自加依赖
- 不手改生成文件
## 完成定义
报告:改了什么 / 跑了什么命令 / 结果 / 假设 / 未验证项
三条心法,比模板本身更重要:
- 单一真源——一个仓库一份权威,其它文件只做指针。规则散落 = 没有规则。
- 先读后改——让它跟随项目已有写法,而不是从训练数据里搬一套。
- diff 优先——要补丁,不要整文件重写。
最后是边界:这套是给「会长期维护、别人也要接手」的项目准备的。 一次性脚本、周末小玩具,别写规则文件——那时候快就是唯一正确的选择。
AI 不会故意改你的方向。它只是把你没说的地方,填成了它的默认值。 规则文件的作用,就是让那些默认值不再有机会悄悄生效。