Claude Code 能在一次会话里改 50 个文件而保持类型与 import 同步,核心是把”理解全项目”做成 agent loop 的第一环:在写任何一行代码前先做项目级检索与依赖图推理。这与只看见当前打开文件的 inline 助手,落在完全不同的范式上。下面拆开它的实现路径,以及为什么这套范式对大规模重构是必要的。
一、Agent Loop 的前置检索阶段
Claude Code 接到任务后,不会直接进入”猜你要写什么”。它先调用本地 Read/Grep/Glob 工具组合,在工作树里做一次项目级扫描。下面是典型的前置检索步骤:
- 列出根目录与关键子目录,识别项目结构(单仓/多模块/package 边界);
- 用 Grep 锁定与目标相关的符号名、文件路径、配置项,得到候选文件集;
- 用 Glob 收口候选文件,过滤掉无关目录(构建产物、测试 fixture、依赖目录);
- 按依赖方向排序,优先读取底层类型与共享模块,再读取调用方;
- 把得到的”局部依赖切片”与 CLAUDE.md 一起装入上下文,进入规划阶段。
典型动作包括:列目录、定位入口文件、抓取共享类型定义、追溯 import 链。这一步把所有候选文件按”与目标的相关度”排序,挑出真正需要读取的那一批,塞进上下文窗口。
这一步的关键不是”读得多”,而是”读得准”。一个把登录模块从 session 切到 JWT 的请求,真正需要读的文件可能只有 auth.ts、middleware.ts、几个测试文件,而不是整个 src/。Claude Code 用语义检索 + 依赖图剪枝,避免上下文被无关代码稀释。
# 典型检索动作:按文件名 / 符号双轴锁定
rg -l "createSession|verifyToken" src/
# 命中后,Claude Code 逐个 Read 这几个文件,把符号定义、调用方、类型引用一起拉进上下文
上下文窗口里现在装的不再是单文件,而是一个”局部依赖切片”。这个切片会随着新需求动态扩张,但扩张是有边界的——窗口撑满后,自动 compaction 会优先丢早期工具输出,保留最近改动的文件,确保规划阶段建立的依赖图不丢。
二、跨文件编辑的执行机制
依赖切片建立之后,Claude Code 进入”计划-编辑-验证”循环。它不会把所有文件一次性改掉,而是按依赖方向顺序执行:先改底层类型,再改消费方,最后改测试。
执行跨文件编辑时,Claude Code 调用的是工具层的 Edit/Write,但工具只是”原语”,真正的难点是”改动一个文件后,其他文件是否仍然类型安全”。这一点是它与传统 inline 助手的分水岭——后者只看到当前文件,改完是”通过本地 Lint 就完事”;Claude Code 在每一步编辑后,会主动重读受影响的文件,核验符号引用、类型签名、import 路径,确认没有把别处打挂。
// auth/types.ts 改了 Session 接口
export interface Session {
userId: string;
token: string; // 新增字段
expiresAt: number; // 新增字段
}
上面的类型扩展落地后,Claude Code 不会停。它会主动 grep 整个 src/ 找 Session 的所有消费方,把缺字段的实例一并补全;再 grep 测试文件,把硬编码的 session fixture 同步更新。这一串动作被写进一个统一的 todo 列表里,任一步失败会触发重试,而不是把不一致的中间状态留下。
三、与 inline code assistant 的本质差异
把视角切到范式层,二者的差异不是”工具多与少”,而是”看世界的尺度”不同。
| 维度 | Claude Code | inline code assistant |
|---|---|---|
| 上下文范围 | 整个项目(经检索剪枝) | 当前文件 + 少量邻接文件 |
| 工作方式 | 读 → 规划 → 编辑 → 验证循环 | 在光标位置补全下一行/下一段 |
| 任务粒度 | 跨文件特性、重构、迁移 | 单文件内函数/语句级 |
| 工具组合 | Read/Edit/Bash/Grep/Glob 全套 | 主要靠 LSP 提示 |
| 失败处理 | 自动读报错、改代码、重跑 | 把错误抛回给开发者 |
| 自主性 | 可在无人值守下完成多步任务 | 完全由人类节奏驱动 |
inline code assistant 假设”开发者已经知道要写什么”,AI 只负责加快敲击速度;Claude Code 假设”开发者给出目标,执行路径由 AI 规划”,这就把编程的关键约束从”键盘速度”挪到了”任务规划能力”。后者在多文件协同场景下,优势是结构性的——人脑记不住 50 个文件之间的类型依赖,但 AI 在上下文窗口里能同时看到。
四、为什么 200K 上下文是硬约束
跨文件编辑要成立,光”知道有哪些文件”不够,必须把符号定义、调用链、当前 diff 一起装进窗口。200K token 是这一约束的工程解:它足以容纳一个中型项目的核心模块 + 完整 diff 历史 + 测试输出。窗口撑满后,Claude Code 不靠”挤掉所有上下文”硬扛,而是触发自动 compaction:压缩旧的工具输出、保留最近的代码改动,这样跨文件协调的”记忆”不会因为对话变长而彻底丢失。
超大项目(超过单窗口)走的是分批策略:把任务切成 50 个文件一组,每组跑完测试再切下一组。这种”批处理”模式在 OpenAI 的 Codex 与 Anthropic 的 Claude Code 上是共通的,但 Claude Code 的工程整合更紧——切批时它会把上一批的最终 commit 哈希记下,作为下一批的基线,避免依赖漂移。
五、CLAUDE.md 让跨文件上下文可继承
跨文件编辑要稳定,光靠运行时检索不够,还要把项目约定沉淀成”随时可加载的项目记忆”。Claude Code 在每个目录扫描 CLAUDE.md,从仓库根到子目录形成层级:根文件写总体约定(语言、测试命令、提交规范),子目录文件写该模块的局部规则(命名风格、禁止的 API)。这些文件加载优先级低于用户当前消息,但高于通用 system prompt,使每次跨文件编辑都遵守”项目自身规范”而不是”AI 通用建议”。
# packages/auth/CLAUDE.md
## 模块规范
- 禁止在 handler 里直接查 DB,统一走 Repository 层
- 所有新接口必须配套单元测试,覆盖率不低于既有模块均值
- 错误返回用 AppError 子类,不要 throw 原生 Error
打开 packages/auth/ 的会话,Claude Code 跨文件改这一模块时,会自动把这些规则纳入规划,改出来的东西与项目其他模块保持一致,而不是按通用最佳实践重新发明。
六、落地时的两个易错点
第一,把跨文件协调全押在自动 compaction 上。当上下文频繁撑满,关键依赖图被压缩掉后,Claude Code 会重复读文件甚至重做依赖推理,体感就是”反应变慢、问已问过的问题”;正确做法是把项目关键约定(构建命令、模块边界)写进 CLAUDE.md,减少每次会话都重新建立基础信息的成本。第二,混淆”多文件改动”与”批量替换”。跨文件编辑是带依赖推理的,批量替换是纯文本操作,后者用 sed 就够了,把后者塞给 Claude Code 反而浪费 token,还可能因为模型对细节的偏好改坏边界情况。
常见问题(FAQ)
Q1:Claude Code 会读完全部项目吗?
不会。它用 Grep/Glob 做语义检索,挑与任务相关的文件读,其他文件不进入上下文。窗口撑满时只压缩旧的工具输出,不丢当前依赖切片。
Q2:怎么避免它把改动限制在错误文件?
在 CLAUDE.md 显式写出模块边界,或者在请求里直接点名要改的目录;无明确指令时,Claude Code 会先列计划让你确认,而不是一上来就改。
Q3:跨文件编辑失败了怎么回退?
Claude Code 改文件前会快照受影响文件,失败可直接回到快照;已 commit 的改动用 git revert 或 git reset --hard HEAD~1,再让 Claude Code 重新规划。