Claude Code 重构 scope 越做越大:6 个 creep 来源 + 「可编辑文件清单 + FOLLOWUPS.md」

你要小改,PR 一来 5 个文件、200 行——在 prompt 里固定可编辑文件清单,相邻问题写到 FOLLOWUPS.md,不要顺手改。

你让 Claude Code 改 billing.ts 里一个函数名。Diff 回来 12 个文件 200 行——4 个不相关模块”一致性 pass”、你没要的格式改动、8 个文件的 import 顺序”小改进”。原任务 5 分钟,review 现在 30 分钟。

没显式边界时,Claude 沿着它认为”相关”的代码路径走。Import 引向 caller、caller 引向其他模块、模块各有不一致——一个文件的活儿就变重构了。修法:钉死可编辑文件清单、相邻问题导向 FOLLOWUPS.md 不顺手改、review 时拒 scope creep。

常见原因

按命中率从高到低:

1. Prompt 没指文件边界

「把 getUser 改名 findUser」没说在哪儿。Claude 全局改名——包括你在迁移走的 legacy 路径,那里改名反而把迁移破了。

如何判断:回看 prompt——没列具体文件就是边界隐式、Claude 自由扩。

2. Agent 沿 import / call 链扩到邻居文件

Claude 在 service.ts 改名,然后更新 8 个 call site。每处都是合法触达,但累计 9 倍于你预期。

如何判断:diff 有「核心」文件改动 + 很多小 call site 更新。每条必要,但合起来就是扩 scope。

3. Agent 自发做了一致性 pass

Claude 注意到一半文件用 import type 一半没用,改名时顺便”协调”了 6 个文件——一句没问。

如何判断:diff 里有和改名无关的改动(import 顺序、格式、命名)——纯 creep。

4. CLAUDE.md / prompt 授权了「顺手清理」

# CLAUDE.md
- 看到机会就改进代码质量
- 相关工作中顺手修明显问题

看着无害的规则,是 scope 扩张的常设授权。

如何判断grep -i "improve\|clean up\|while you're\|opportunit" CLAUDE.md——每条匹配都是 creep 授权。

5. 重构本质就跨多文件

某些重构真的跨整 codebase(改公开 API、迁 state 库)。scope 是真的——bundle 进一个 PR 才是问题,应该拆。

如何判断:diff 本身真的必要——bug 是「一个巨型 PR」而不是「PR 链」。

6. Agent “修测试” 顺势级联

核心改动后测试挂了,Claude 在多个测试文件里更新 expectation、mock、fixture。有些合法(签名匹配),有些是 mask 失败。

如何判断:测试文件改动跟着改名——审一下哪些是签名更新、哪些是悄悄削弱断言。

最短修复路径

按收益从高到低。前 2 步在 prompt 层把 agent 关起来。

Step 1:在 prompt 里钉死可编辑文件清单

可编辑文件(其他都不许动):
- src/services/user.ts
- src/services/user.test.ts

任何其他文件:
- 必须动就停下来问。
- 不要做一致性 / 格式 / 清理改动。

「停下来问」让 Claude 能 flag 真级联而不私自扩。

Step 2:相邻问题导向 FOLLOWUPS.md

可编辑清单外发现问题:
- 不要修。
- 在 `FOLLOWUPS.md` 加一行描述问题 + file:line。
- 这些变 future task,不进本 PR。

Claude 仍能”有帮助”但不扩 scope——每个发现都成文档化的 future task。

Step 3:审 prompt + CLAUDE.md 找 creep 授权

grep -in "improve\|clean up\|while you're there\|opportunistically" \
  CLAUDE.md src/**/CLAUDE.md prompts/*.md

每条换成更紧的:

- task scope 外不要动。
- 相邻问题进 FOLLOWUPS.md,不进本 PR。

Step 4:收到过宽 PR 时回退 scope 外文件

不要原样 merge、也不要全拒——回退 scope 外文件:

git diff --stat HEAD
# 找出 scope 外的文件
git checkout HEAD -- <scope 外文>

# 重跑测试,确认核心改动还好
pnpm test

留要的、删不要的——PR 缩回预期 scope。

Step 5:真的多文件重构就拆 PR 链

改名真要更新 8 个 caller 就阶段发布:

PR 1:加 `findUser` 与 `getUser` 共存(不动 caller)。
PR 2:迁 `src/services/billing/` 的 3 个 caller。
PR 3:迁 `src/api/` 的 5 个 caller。
PR 4:删 `getUser`。

每个 PR 5 分钟 review。总 diff 同,但可 review 性高 10 倍。

Step 6:核查测试有没有被削弱

diff 里的每个测试文件:

# 单独 diff 测试文件
git diff -- src/services/user.test.ts

# 找:被删的 expect/assert、新加的 .skip、放宽的类型检查
git diff -- src/services/user.test.ts | grep -E '^-.*expect|^-.*assert|^\+.*skip'

断言被删 / 测试被 skip——“修测试”其实是回归被伪装。还原测试,重 prompt 让它修生产代码

预防建议

  • 每个代码 prompt 显式可编辑文件清单 + 越界「停下来问」
  • 维护 FOLLOWUPS.md 让 Claude 能浮 surface 邻近问题不动手
  • 审 CLAUDE.md 删「顺手改进」常设授权
  • 真多文件重构拆 PR 链,不要一个巨型 PR
  • 核查测试 diff——删断言 / 加 skip 是伪装成修法的 creep
  • review 时对照原 scope,不只看最终 diff——diff 本身可能看着合理

相关阅读

标签: #排查 #Claude Code #排查 #Scope 蔓延