把 Cursor 对准一个 12 包的 monorepo,让它在 apps/web 里加个功能。结果它跨 6 个包打开 47 个文件,顺手”重构”了 packages/utils,还留下另一个毫不相干的包编译失败。这就是 AI Agent 在 monorepo 里默认的崩盘方式:上下文太多、归属不清、耦合藏得深。这篇给 Turborepo、Nx、pnpm-workspace、Yarn-workspace 用户一套靠谱的按包工作流——让 Agent 守在自己车道里,同时也能安全地做跨包改动。
一句话总结
- 给每个包都配一份
AGENTS.md。 “就近文件优先”如今已是跨工具的事实标准(Cursor、Claude Code 经导入、Codex、Copilot 都认)。OpenAI 自家的 monorepo 就放了大约 88 份嵌套AGENTS.md,每个重要的包一份。 - 每条 prompt 都显式 scope 到目标包,并写明排除清单。“只在
packages/api改;不要碰packages/ui或apps/web。” - 跨包改动拆成两个 commit:先消费方(按尚不存在的契约写调用),后提供方。每个 commit 只是一个关注点。
- 用受影响图验证,而不是跑全世界。
turbo run build test --affected -- --base=origin/main(Turborepo 2.1+)或nx affected -t build test,只跑变更的包加它们的下游。 - 这套在 3 个以上包且存在跨边界耦合时才划算。低于这个量,默认工具就够。
为什么 monorepo 会把 AI Agent 搞炸
单 app 仓库给 Agent 一个清晰的爆炸半径:每个文件都能改,构建就一条命令。monorepo 把这个反过来了。Agent 看到几十个包,分不清哪个包拥有某个契约,也无法从文件夹名推断出依赖图。没结构的话它就干两件事之一:要么忽略真正该改的包,要么”优化”压根没让它碰的兄弟包。两种结果都会产出没人想审的 PR。
解法不是换个更聪明的模型,而是三块结构——用来收窄 Agent 的上下文、约束它的产出:按包的指令文件、显式的 prompt scope、跨边界改动走两阶段 commit。
这篇适合谁
Turborepo、Nx、pnpm-workspace、Yarn-workspace 用户,以及有清晰包边界的大单仓。给团队写 AI 规范的工程 lead 也受益——这些约定正是让 AI 产出的 PR 在规模上还能被审的前提。
什么时候该用这套流程:在 3 个以上包的 monorepo 里做任何非琐碎改动,尤其是改动可能跨边界时——新 API 端点被 web 消费、新共享组件、生成 client 更新。单包改动用默认工具就行;结构关键的时刻是”Agent 需要知道别碰兄弟包”。
对极小 monorepo(2-3 个包、什么都共享)和还在往 monorepo 拆分、结构未定型的仓库来说,它是过度工程——先让结构稳定。单 app 项目请参考用 AI 审计 React Native 项目。
现在的指令文件是 AGENTS.md
到 2026 年,项目级指令文件已经统一收敛到 AGENTS.md——同一份纯 Markdown 文件被 Cursor、Codex、GitHub Copilot 以及(经导入的)Claude Code 共同读取。让它在 monorepo 里管用的,是就近文件优先:Agent 在编辑 packages/api/src/route.ts 时,会读取从 git 根目录到这个目录沿途的 AGENTS.md 链,离得最近的那份优先级最高。这恰好就是 monorepo 需要的按包 scope。
各主流工具如何消费它(截至 2026 年 6 月):
| 工具 | 指令文件 | 按包行为 | scope 手柄 |
|---|---|---|---|
| Cursor | .cursor/rules/*.mdc(或 AGENTS.md) | 包内嵌套的 .cursor/rules/ 覆盖根目录;规则按 glob 限定范围 | 文件夹 scope + @ 提及文件 |
| Claude Code | CLAUDE.md(导入 AGENTS.md) | 从 CWD 向上走、增量合并;不加载兄弟包的文件 | 设工作目录;@file 引用 |
| Codex CLI | AGENTS.md | 拼接全局 + git 根到 CWD 沿途的每份 AGENTS.md | 只传目标文件 |
| GitHub Copilot | AGENTS.md | 编辑时就近文件优先 | 文件/选区 scope |
两个实操要点。Claude Code 不直接读 AGENTS.md——要么在 CLAUDE.md 里写 @AGENTS.md 导入,要么用 ln -s AGENTS.md CLAUDE.md 做软链。还有,每份文件都要小:健康的根文件大约 200-300 行,单份文件超过 150-200 行就该拆成嵌套的,既为了可读性也为了 token 预算。2026 年比较实在的默认做法是:先用 AGENTS.md,碰到真正的 scope 限制再加 Cursor .mdc 规则,团队统一用 Claude Code 时再加 CLAUDE.md。
开始前的准备
- 盘点包、写清边界。 哪个包拥有 API 契约?哪个拥有共享类型?哪个是内部包?对 Agent 来说,“禁止依赖谁”这条往往比”可以依赖谁”更有用。
- 为这个任务锁定一个工具。 Cursor、Claude Code、Codex、Aider 都行;任务中途换工具就是在丢上下文。
- 定好测试策略。 跨包工作单包测试不够。至少要一条跨包集成测试和一条能跑通的全量构建命令。
- 确认能构建单包或受影响集合。 单包用
turbo run build --filter=@acme/api,变更图用--affected。Agent 用它来验证,不必重建整个世界。
具体步骤
- 维护一份根
AGENTS.md加每个包一份简短的。 每个包条目写:名字、一句话用途、公共 API surface、归属、禁止依赖的对象。Agent 的上下文加载器会自动拣最近那份。 - 每条 prompt 都显式 scope。 目标和排除都要写明:“只在
packages/api改。不要碰packages/ui、apps/web、或packages/api以外的任何测试。” - 把检索限制在目标包内。 Cursor 用文件夹 scope;Claude Code 设工作目录;Codex/Aider 只传目标文件。这是防 Agent 读错包最有效的一根杠杆。
- 跨包改动分两遍做。 第一遍:在消费方按一份尚不存在的契约写调用。commit(构建会故意失败)。第二遍:在提供方实现该契约。commit。每个 commit 只是一个关注点,审起来很轻松。
- 显式追问下游消费方。 任何跨包改动后都问一句:“这次改动公共 API surface 了吗?有的话列出每个消费该 API 的包,以及各自需要改什么。“模型默认会忘掉下游消费方。
- commit 前用受影响图验证。
turbo run build test --affected -- --base=origin/main(Turborepo 2.1+,2024 年 8 月)或nx affected -t build test。Nx 会读真实的依赖图,因此能抓到只看变更文件抓不到的下游。跨包契约破裂只会在集成构建里浮出,单个包的测试永远抓不到。
一个真实的跨包例子
在 packages/api 加一个新的 GET /usage 端点,给 apps/web 里的仪表盘调用:
- scope 到
apps/web。按你想要的响应结构写调用方(fetchUsage()和渲染它的组件)。commit。构建会失败——packages/api还没这个端点,而这正是重点:消费方此刻定义了契约。 - scope 到
packages/api。实现GET /usage,匹配消费方期望的结构。如果有类型化 client 就重新生成。commit。 - 跑
turbo run build test --affected -- --base=origin/main,再跑 e2e 套件——它是唯一能同时验两个包的东西。 - 开 PR,2 个干净 commit。审的人按顺序读 commit 一里的契约、commit 二里的实现,不用去 bisect 一个跨 6 包的 diff。
容易踩的坑
- 在仓库根目录跑 Agent 不给 scope。 它会从错包里检索一堆无关文件。
- 一条 prompt 跨边界改动。 review 痛苦,diff 没法 bisect。
AGENTS.md里漏写每个包的约定(测试框架、lint 规则),Agent 就会用错的那套。- 跳过集成构建。 单包测试 pass,集成系统却挂;只有
--affected或全量构建才能抓到。 - 让 Agent 把生成代码放错包。 一定要写明哪个包拥有生成物(GraphQL 类型、protobuf、Prisma client),以及重新生成它的命令。
- 把
AGENTS.md当一次性写。 包会被加、被拆;过期的边界文档把 Agent 引向跟没文档一样的坑。每季度审一次。
进阶技巧
- 每个包一份简短
AGENTS.md,胜过根目录一份巨文件。 它让每份文件都低于 ~150-200 行的拆分阈值,也加载更少无关上下文。 - 用
@引用做条件式上下文。 Claude Code 里@docs/database-conventions.md只在需要时才把文档拉进来,而不是撑大每条 prompt。 - 把重新生成命令写进文件,比如”改完 schema 后从仓库根目录跑
pnpm gen:types”,让 Agent 知道要跑、在哪跑。 - 为超大 monorepo 维护一行式模块地图:每个包写它的输入、输出、Agent 应该先读的那个文件。
- 把近期跨包改动预加载给 Agent。 新工作往往跟随近期模式;把最近几个类似 PR 指给它,能改进它设计契约的水平。
完成后检查
- PR 里每个 commit 只动一个包(刻意走两阶段时是两个)。
- 受影响构建通过——用
--affected或全量turbo run build && turbo run test,不是只通过 Agent 改的那个包。 - PR 描述列出所有动过的包。没列说明 prompt 没要求。
- 生成代码重新生成并提交。Agent 忘了,说明
AGENTS.md缺重新生成命令。
常见问题
- 该用
AGENTS.md还是CLAUDE.md?:先用AGENTS.md——它是被 Cursor、Codex、Copilot 共同读取的跨工具标准。只有团队统一用 Claude Code 时才加CLAUDE.md,而且即便那样也用@AGENTS.md或软链导入同一份文件,保持单一信息源。 - 一份指令文件还是多份?:多份。每个包一份简短的,加一份小的根索引。就近文件优先意味着 Agent 读最近那份,小文件也让你保持在 ~150-200 行的拆分阈值和 token 预算之内。
- monorepo 里 Cursor 还是 Claude Code?:都行。Cursor 按文件夹和 glob 限定的
.mdc规则来 scope;Claude Code 从工作目录向上走、增量合并,所以一个会话里永远不会加载兄弟包的文件。挑你已经在用的那套 UX。 - 怎么只跑变更的包?:Turborepo 用
turbo run build test --affected -- --base=origin/main(2.1+),Nx 用nx affected -t build test。Nx 懂依赖图,所以它还会跑变更包的下游,而不只是变更包本身。 - 怎么防 Agent 顺手”优化”无关代码?:prompt 里显式排除:“不要改
packages/api以外的文件。不要跑会自动格式化其他包的 linter。” - 包之间没有清晰边界怎么办?:先把边界画出来。没边界 AI 没法在 monorepo 帮忙——审 diff 的人工程师也帮不上。