/ By 煎鱼 / #agent #ClaudeCode #context-engineering #Cookbook解读 / AI 入口

Cookbook解读|大型代码库里,Agent 为什么要先缩小世界

解读 Claude Code 的大型代码库指南:通过启动目录、分层规则、受限读取、代码智能、稀疏工作区与任务计划,把 Agent 的注意力收敛到真正会受影响的代码。

给 Agent 一个百万行仓库,再说一句“修复登录失败”,听上去很大方,实际有点像把整座图书馆的钥匙交给新同事,然后让他“先熟悉一下业务”。

他当然可以翻书;问题是,第一小时翻到的是支付、构建产物,还是十年前没人敢碰的 legacy 目录?如果他先打开了不相干的内容,后面真正要看的认证代码、约束和测试线索,反而更容易被挤出工作记忆。

Claude Code 的大型代码库指南给出的答案很朴素:随着仓库变大,和当前任务无关的指令与文件读取会占用上下文、增加 token 成本并降低效果;因此要把 Agent 的范围缩到任务实际触及的那部分代码。这不是“让模型一次记住更多代码”的技巧,而是给它安排一条更窄、更有路标的路。

先说结论:大型仓库的关键不是替 Agent 预装一张覆盖全图的记忆,而是让它在正确的目录启动,只加载此处的规则,只读取有用的文件,并把跨包改动留下一份能跨回合保存的计划。

数据快照

#先把误会拆开:大上下文不等于“应该全量读取”

对话里最容易出现的说法是:“仓库太大,所以要给 Agent 一份更完整的架构文档。”这只说对了一半。

架构信息当然有价值,但一份根目录的巨大说明文件会遇到两个相反的问题:要么把所有子系统的规则都塞进去,让当前任务带着无关说明前进;要么为了不膨胀而写得过于笼统,真正进入某个目录时又无法指导操作。官方文档把这个矛盾说得很直接:根目录单一的 CLAUDE.md 往往会在“覆盖所有子系统”和“过于泛化”之间摇摆,按目录拆分后,才可以把全仓规则与当前代码区域的约定叠在一起加载。

因此,“项目地图”不应该是一张巨大的海报,更像分区导览:你在入口先看到安全须知和总平面图;走进支付区,才看到支付区的操作规程。它们不是互相替代,而是按位置叠加。

这也修正了一个常见推论:搜索不是大型仓库的敌人,漫无目的的读取才是。 官方指南建议用语言服务器提供的跳转定义、查找引用与类型错误来替代大量文件扫描;它的目标不是禁止查找,而是让“找符号”不必先把一摞文件搬进上下文。代码智能插件正是在这一层降低文件读取。

#一次任务应怎样收窄:六道边界不是六份文档

大型仓库中的“范围”至少有六种。把它们混为一句“给 Agent 上下文”,后面就很容易用错工具。

图 1:从任务进入到跨包改动完成,范围在不同层次逐步收窄;虚线表示需要由人或任务描述决定,而不是配置自动推断

flowchart TD
  S([开始:收到修复任务]) --> A[任务:修复 API 登录失败]
  A --> B[启动目录\npackages/api]
  B --> C[规则范围\n根目录 + API 目录]
  C --> D[读取范围\n排除生成物与 vendor]
  D --> E[定位范围\n语言服务找定义与引用]
  E --> F[工作区范围\n稀疏 worktree]
  F --> G{是否影响 shared 包?}
  G -- 否 --> H[修改、测试、交付]
  G -- 是 --> I[显式扩展访问范围\n并把计划写入仓库]
  I --> H
  H --> E([结束:交付已验证改动])
  U[人:确认任务边界与验证标准] -.-> A
  U -.-> I

图里的重点不是顺序必须一模一样,而是每个边界回答不同问题:从哪里开始、哪些规则该出现、哪些文件不值得打开、怎样找符号、工作区放什么,以及跨出当前包时如何显式扩大范围。只新增一份“架构文档”,通常只回答了第二个问题。

#1. 启动目录先决定默认视野

官方文档把启动位置放在所有设置之前,是因为它同时影响文件访问、启动时加载的 CLAUDE.md,以及生效的项目设置。在仓库根目录启动时,Agent 默认可访问整个仓库;从 packages/api/ 启动时,默认只在该子树活动,同时获得该目录和祖先目录的 CLAUDE.md。这适合一个明确只影响 API 包的任务;反过来,跨多个包的改动从根目录启动会更自然。文档的启动位置对照给出了这两种边界。

这里没有“永远从子目录启动”的口诀。任务要修改 shared type 和所有调用点时,硬把它关在 API 子目录,只会让它反复请求额外权限。范围应该由任务的影响面决定,而不是由“上下文越小越好”决定。

#2. CLAUDE.md 是分区规则,不是百科全书

根目录的 CLAUDE.md 适合放仓库布局、提交约定和全局编码标准;子目录的 CLAUDE.md 适合放该区域的测试命令、迁移流程、框架约束和禁止事项。Claude Code 会在启动时加载工作目录及其祖先目录的文件,并在读取子目录文件时按需加载更深层的说明。官方的分层加载说明正是让规则和代码一起靠近。

这比“把所有知识放到根目录”多了一个重要好处:谁维护某条规则更清楚。API 目录的 owner 可以维护 API 约定,而不会为了改一条路由测试命令去改全仓员工手册。

不过,规则也有失控的时候。官方文档提供 claudeMdExcludes 跳过永远不工作的目录,但同时提醒:如果只是今天想专注一个包,优先从那个包启动,而不是频繁改排除配置。前者是任务范围,后者是长期策略;把短期选择写进长期配置,很容易留下明天看不懂的痕迹。

#3. 把“不该读”的内容变成真正的边界

.gitignore 能让常见的 node_modules/dist/build/ 默认不出现在内容搜索结果中。对于已经提交的 vendor SDK 或生成代码,官方建议在 permissions.deny 中设置 Read 规则,阻止 Agent 打开它们;这条规则覆盖内建文件工具和部分可识别的 shell 文件命令,但不会过滤递归搜索的输出,也不能限制任意子进程。这些限制在读取权限规则说明中讲得很明确。

例如,下面是官方示例的缩小版,意图是让构建输出和 vendor 代码不参与日常阅读,不是把它们从仓库历史中删除:

{
"permissions": {
"deny": ["Read(./**/dist/**)", "Read(./vendor/**)"]
}
}

这里的区别很关键:一份 Markdown 规则只能提醒 Agent“通常别看”;执行层的 Read deny 才会在打开文件时拦住它。前者影响判断,后者限制动作,两者不应互相冒充。

#4. 先问语言服务,再翻文件

当问题是“这个类型在哪里定义”“谁调用了它”,用文本搜索把整个仓库扫一遍非常浪费。官方指南建议接入 code intelligence plugin,让语言服务提供定义、引用与类型错误;claudeMdExcludesRead deny 负责移走不相干内容,语言服务负责更快地在剩余代码中定位。这两类能力的组合关系比“多写几个 grep 关键词”更接近可维护的方案。

但不要把 LSP 想成业务分析器。它能告诉你符号和调用关系,不能告诉你“退款时必须同步返还优惠券”这种跨系统约束。后者仍需要目录规则、领域文档、测试和人给出的验收条件。工具缩小检索范围,不会自动补齐缺失的业务知识。

#5. 让工作区也和任务一样小

上下文不是唯一会膨胀的东西。一次 worktree 默认会检出整个仓库;对于非常大的仓库,worktree.sparsePaths 可以只写出任务需要的目录与根文件,让 worktree 更快、更省空间。官方也说明:多个 subagent 共用同一组 sparsePaths,因此如果它们各自需要不同目录,路径必须一起列出。稀疏 worktree 的行为与限制都应该在真正依赖它前确认。

这是一个容易被忽略的对应关系:模型上下文要减少无关读取,磁盘工作区也要减少无关检出。两者的共同目标不是节省到极限,而是让“任务真正依赖什么”变得可见。

#6. 跨包改动要扩大范围,也要留下路线

范围收窄不等于把跨包任务切碎。官方指南的建议恰好相反:当共享类型和所有调用点必须一致时,把完整变更放在同一 session 中,可以避免每个包重新推导一次关键决定;再在编辑前把计划写入仓库,因为长 session 可能发生上下文压缩,而文件里的计划能够保留。跨包改动的 scope 与 plan 建议给出的,是“整体一致性”和“长期记忆”之间的折中。

这也是对“每一步都委派给独立 Agent”的边界提醒。只读探索可以隔离大量目录和搜索噪声;但需要共享设计决策的改动,过度切分会让每个子任务拿着不同的局部地图。官方页面把“在 subagent 中探索,让文件读取留在主对话之外”作为通用技巧,同时又要求跨包编辑保留同一变更的决策一致性。两条建议并不矛盾:隔离噪声,不要隔离必须共同维护的决定。

#一个退款需求,怎样从“全仓搜索”变成可执行路线

假设任务是“增加订单退款后返还优惠券”。最差的开场可能是搜索 coupon,然后在上千个命中里猜哪一个与退款有关。

按上面的边界,第一步应由人或任务说明确认影响面:订单、支付、优惠券和集成测试是否同仓、是否分包。若退款 API 在 packages/api/,可以从那里启动,先加载根规则和 API 规则;再由类型引用或路由入口定位退款处理链。读取生成 SDK、旧构建产物和 vendor 依赖不会增加答案的可信度,就应该排除。

如果发现退款事件定义在 packages/shared/,这不是“Agent 走出范围”的失败,而是范围必须扩大的证据。此时把 shared 包与调用点放进同一改动,并把“事件字段、优惠券回滚时机、幂等约束、集成测试”写入计划。下一轮即使上下文压缩,计划仍能告诉后来的 session:哪些决定已经确认,哪些假设尚待测试。

这里的退款场景是本文的分析性示例,不是 Claude Code 文档中的实测案例。它想说明的只有一点:先缩小世界,不是先假设世界只剩一个目录;而是让每一次扩大范围都有证据和记录。

#最小落地清单:先从一条真实任务开始

没有必要今天就给每个目录补齐规则、语言服务、插件和稀疏工作区。更可靠的做法是挑一条反复出现的任务,按影响面补齐最短路径:

  • 根目录保留真正全局的约定;把测试命令、框架限制等挪到对应子目录的 CLAUDE.md
  • 先用启动目录表达单包任务;只有稳定、长期无关的目录才使用 claudeMdExcludes
  • 对提交的生成物和 vendor 代码设置读取边界,再确认这些边界不会误伤排障任务。
  • 优先用语言服务回答“定义和引用在哪里”;领域规则仍由文档、测试与验收条件承担。
  • 跨包任务先写计划,记录影响目录、关键决定、风险和验证命令;不要把它拆成互不共享决策的碎片。

最后再回到开头的图书馆。大型代码库的难处不是书太多,而是没有书目、分区和借阅规则时,任何人都会先拿错书。模型的上下文再大,也不该被用来替代工程里的边界设计。

Claude Code 这篇指南最值得带走的,不是一串 .claude 配置项,而是一条工作原则:让任务决定 Agent 看什么;让证据决定何时扩大范围;让计划保存跨回合不能重新猜的决定。 这样它才更像一个知道该问谁、该去哪一层找资料的工程师,而不是一个被允许在仓库里随意翻页的阅读器。

#参考资料

Share this post

Mermaid 图表
100%