Cursor Composer 重构中途丢上下文

Composer 开头还行,做到第 9 个文件就开始改错文件、回滚之前的改动——上下文炸了;要把重构切块、每块重新对齐基线。

你在 Composer 里发起一个跨文件重构——重命名一个类型、传染到 15 个文件、把测试同步更新。前 6 个文件都很漂亮。到第 9 个就开始过不了编译:要么改错了文件,要么把前面改好的又回滚了,要么 import 路径完全是编出来的。模型没变笨,是上下文耗尽了。Composer 的有效窗口是有上限的,超过之后老的编辑会被驱逐,包括这次重构所依赖的类型定义本身。

修这个问题不是”换更大的模型”——而是把重构切块,每个块边界重新建立基线。

常见原因

1. 重构超出了有效上下文

哪怕模型支持 200K token,Composer 还要给工具和 system prompt 留位置,实际能用的代码空间只有 80-120K。20 个 TypeScript 文件的全文读取很容易就把这个空间吃完。

如何判断:统计 Composer 碰过的文件的 token 数。加起来超过 60K 就进入危险区间了。

2. 工具输出把老的编辑挤出去了

每次 tool call 的输出(文件读取、terminal 日志、grep 结果)都在上下文里。中途几次 npm run build 的报错栈就能挤掉前面的 diff。

如何判断:重构中途 Composer 跑过几次 build 或测试?这些输出现在在和真正的代码抢空间。

3. 一开始没给”基准定义”

第一条消息只说”把仓库里 UserId 重命名为 AccountId”,但没贴出标准定义。做到一半模型就忘了这个类型到底长什么样。

如何判断:往上翻——有没有一条消息里写着 interface AccountId 的完整定义?

4. 文件之间的隐式依赖没暴露出来

UserId 被 15 个文件 import,其中 3 个还在 re-export。模型只改了直接 import,漏了 re-export。

如何判断rg "export.*UserId|export \{.*UserId" --type ts 找 re-export。

5. Composer 工作时你又手动改了文件

你在 Composer 处理第 7 个文件的时候手动改了第 5 个文件的一个错字。模型认为第 5 个文件还是带错字的,下一轮编辑直接把你的修复回滚。

如何判断git status 有没有显示你做的、但 Composer 没读过的改动?

6. agent 长循环没打 checkpoint

agent 模式连跑 25 个 tool call,你一次 commit 都没。到第 20 个 call 时,模型已经在一个被截断到只有最后 10 个 call 的上下文里工作了。

如何判断:Composer 一条消息里挂了 20+ 个 tool call。

动手前先确认

  • 如果 Composer 还在跑就先停下来——让它做完当前文件再 reset。
  • 把已经过 lint 和类型检查的文件 commit 掉,留一个干净的回滚点。
  • 记下当前用的模型;Opus 4.7、Sonnet 4.6 在长上下文上明显比旧版本好。

需要收集的信息

  • git statusgit diff --stat 看重构走到哪一步了。
  • 当前状态下编译/类型检查的报错。
  • 触发这次重构的原始 Composer 消息。
  • 一份”已完成 / 已坏掉 / 还没动过”的文件清单。
  • 用的是 agent 模式还是普通 Composer。

一步步修复

Step 1:把能跑的部分 commit 掉

跑一遍类型检查 / build。能过的文件全部 stage + commit:

npm run typecheck
git add <files-that-pass>
git commit -m "refactor: rename UserId to AccountId (part 1, files 1-6)"

那些改了一半你不信的文件就丢掉或 stash:

git checkout -- src/api/users.ts src/services/auth.ts

现在你有了一个干净的基线。重构做到一半,但仓库能编译。

Step 2:用明确的基准把重构重新锚定

开一个新的 Composer chat。第一条消息必须钉死标准定义:

We are renaming `UserId` to `AccountId` across the repo.

Canonical definition (do not change this shape):

```ts
// src/types/account.ts
export interface AccountId \{
  value: string;
  scope: "internal" | "external";
\}

Files already migrated (do not touch):

  • src/types/account.ts
  • src/api/users.ts
  • src/services/auth.ts

Files still to migrate:

  • src/components/UserCard.tsx
  • src/components/UserList.tsx
  • src/hooks/useUser.ts

For each file: read it, plan the change, apply. Stop after each file. Do not move to the next without my OK.


钉死定义 + 已完成清单 + 待办清单,这种结构能把模型重置回一个干净的工作集。

### Step 3:每块控制在 3-5 个文件以内

哪怕开了新 chat,也别一次性贴 20 个文件。每个 Composer chat 上限 3-5 个文件。chat 之间 commit。块越小,模型在每块里都很宽裕,而且一个块爆掉也不会污染其它块。

### Step 4:与其靠记忆,不如重新读文件

要回头编辑 10 轮以前 Composer 碰过的文件时:

Before editing src/api/users.ts again, re-read the current contents. Do not rely on what you remember about this file.


这会强制重新读取,把模型对齐到磁盘上的真实状态。

### Step 5:类型重命名要做 re-export 扫描

```bash
# 找直接重命名遗漏的 re-export
rg "export \{[^}]*UserId[^}]*\}" --type ts
rg "export \* from.*types/user" --type ts

把结果丢给 Composer 让它专门改 re-export。这些点在扫描直接 import 时很容易漏掉。

Step 6:每块结束跑一次干净的类型检查

npm run typecheck 2>&1 | head -50

每块结束后报错数都是 0 就说明你站得稳。报错数往上爬就停手检查——别让 Composer 在污染了的上下文上继续修新的错误。

验证

  • npm run typechecknpm run build 全跑完整序列后都过。
  • rg "UserId" 在仓库里 0 命中(或只剩故意留的兼容别名)。
  • 最终 diff 和手工重构应该一致,没夹带其它无关改动。
  • 跑完整测试套件确认运行时没回归,不只是类型层面通过。

长期预防

  • 跨 5 个文件以上的重构,永远不要在一个 Composer chat 里做完——按目录或模块切。
  • 多文件重构的开头消息必须钉死标准的 type / interface 定义。
  • 块之间一定要 commit,每块都有干净的回滚点,模型也不必去记那些已经写进磁盘的东西。
  • agent 模式的重构里硬性限制每次回复最多 10 个 tool call,之后强制重新打 checkpoint。
  • 旁边开一个 npm run typecheck --watch,问题一冒头就发现,而不是漂移到第 5 个文件后才暴露。

常见坑

  • 编辑失败后用”接着干”这类 prompt——只会往一个已经溢出的 chat 里再塞内容。直接开新 chat。
  • 重构中途放任 Composer 反复跑命令;每次 tscnpm run build 输出都吃 1-5K token。
  • Composer 还在工作时你手动改文件——这些改动对模型不可见,下一轮就被回滚。
  • 信 Composer 那句”我已经更新了所有 15 个文件”——一定要 rg 验证。
  • 重命名同时存在 type 和 value 形态的符号(TypeScript class、enum)时,没告诉模型哪种形态在哪里。

FAQ

Q:换”长上下文”模型能解决吗? A:天花板会提高,但失败模式不变。够宽的重构终究会撞到上限;切块才是长久之计。

Q:大重构用 agent 模式还是普通 Composer 更好? A:要做大重构,普通 Composer 加每个文件人工确认更稳。agent 模式擅长孤立任务;多文件重构需要人工 checkpoint。

Q:能让 Composer 在 chat 之间”记住”东西吗? A:它会说能。别信。耐用事实写进 .cursorrules,或者在块边界再贴一次。

Q:要是重构本身就有顺序依赖(后一个文件依赖前一个)怎么办? A:每个文件 commit 后开新 chat,把上一个文件最终状态的关键片段贴进去。麻烦,但稳。

相关阅读

标签: #Cursor #ide #排查