修复 Cursor Composer 在大型 monorepo 里丢上下文

小仓库 Composer 很聪明,进了 5 万文件 monorepo 就丢线索。靠 @Files、MDC 规则和单包任务逼它聚焦,别赌索引重跑。

Composer 从来不读整个仓库,它只用嵌入检索递给它的那几十块片段干活。小仓库里 top-K 几乎覆盖全部;进到 1 万文件以上的 monorepo,同一个概念有十几个版本,检索只抽出 1-2 个,剩下的模型靠猜补。表现很一致:改了 A 模块,引用 A 的 B 模块原封不动;推荐的 import 路径根本不存在;packages/old-app 的旧写法渗进 packages/new-app

这不是改一次设置就解决的事,而是要换一种给 Composer 喂上下文的方式。

一句话总结

  • 写 prompt 之前先用 @Files 手动挂 5-10 个相关文件,这一步就能解决大多数情况。
  • 打开包目录(apps/web),别开 monorepo 根——检索面和索引体积都能砍掉约一个量级。
  • 把边界规则搬进 .cursor/rules/*.mdc。老的 .cursorrules 在 2026 年的 Agent 模式下会被静默忽略
  • 每条 prompt 只锁定一个包。“统一所有微服务的 logger” 这种说法必然漏包。
  • Composer 2.5(默认 agent 模型,基于 Kimi K2.5)很强,但再强的模型喂错上下文照样瞎猜。赢在上下文,不在模型。

先搞清楚 Composer 跑的是哪个模型

不同模型对长上下文的处理差异很大,所以排查前先看一眼模型选择器。截至 2026 年 6 月,Cursor 给 agent 任务提供这几个:

模型上下文窗口大仓库表现
Composer 2.5(默认 agent)大 agent 上下文,文件树级别改动多文件重构快;SWE-Bench Multilingual 79.8%(2026 年 5 月 18 日发布)
Claude Sonnet 4.61M tokens稳定主力,长上下文撑得住
Claude Opus 4.71M tokens横跨大段代码推理最强;SWE-Bench Multilingual 80.5%
GPT-5.5~1Magent 工具调用强
Gemini 3.1 Pro1M tokens大上下文读取

窗口大不代表 Composer 会把整个 monorepo 塞进去。窗口只决定模型能装下多少检索到的上下文,进什么内容仍由检索说了算。这也正是下面这些招都在讲你挂了什么文件,而不是选哪个模型的原因。

常见原因

1. 检索只命中 top-N,覆盖率不够

Composer 把仓库切成大约 200-500 行的块再做嵌入。一句 “fix auth” 只取 top 10-20 最相关的块。10 万行的 auth 子系统里,这点信息量等于透过钥匙孔看屋子。

如何判断:让 Composer 答完后追问 List exactly which files you read。如果只列了 3-5 个、还漏掉你明知相关的文件,就是覆盖不够。

2. Monorepo 里多个同名模式互相干扰

packages/web/utils/fetch.tspackages/admin/utils/fetch.ts 同名不同实现。相似度排序可能把 admin 版塞进 web 的 prompt,模型照抄。

如何判断:检查它给的代码里 import 路径有没有穿越包边界,比如 packages/web/... 下的文件 import 了 packages/admin/internal/...

3. Working directory 设到了仓库根

Cursor 默认把 workspace 根当 working directory,于是所有文件都在搜索面里。收窄到 apps/web 后,检索面缩小约一个量级,质量立刻跳一档。官方说法很直接:直接打开 apps/web 而不是仓库根,索引体积能砍掉一个量级,agent 也更聚焦。

如何判断:看左下角的 workspace 路径。如果在 monorepo 里却指向仓库根,就是太宽。

4. 没有 MDC 规则给出”包边界”地图

没有规则,模型就不知道哪条路径属于哪个包、哪个是公开 API、哪个是内部,只能从文件路径瞎猜。注意格式已经变了:单个根目录 .cursorrules 在某些场景还能加载,但在 Agent 模式下会被静默忽略——而 Composer 正是跑在 Agent 模式里。现在的格式是 .cursor/rules/*.mdc

如何判断:跑 ls -la .cursor/rules/。里面没有 .mdc 文件,就说明 agent 拿不到边界地图。

5. 一条 prompt 横跨多个包

“统一所有微服务的 logger” 没法保证检索覆盖到每个包。Composer 改它看得见的几个,剩下的静静漏掉。

如何判断:改完后跑 grep -r "old_logger" .,还有残留命中就是漏包了。

6. 索引过期或 .cursorignore 排得太狠

Cursor 每 5 分钟自动同步一次索引、只处理改动过的文件;但索引时被排除、或在索引暂停期间新增的目录,在检索里就根本不存在。.cursorignore 太激进(比如 **/*.test.ts)会把 Composer 本该参考的测试文件藏掉。还要注意:语义检索要到索引完成 80% 才上线,所以刚打开超大 monorepo 时,最初几条 prompt 跑的是不完整的索引。没调好的 monorepo 索引在打开后会把磁盘 IO 占满好几分钟;配上精简的 .cursorignore 调好后会快得多(有公开实测把 100 万文件的索引从约 12 分钟压到 3 分钟以内)。

如何判断:Cursor Settings → Indexing,看索引进度/状态;再打开 .cursorignore 核对它排除了什么。

动手前先确认

  • 确认问题出在 Composer 还是 Cmd+K。Cmd+K 完全不走全仓检索,行为差很多。
  • 复现前先 commit 或开 branch,免得一次 Apply 把未保存的改动覆盖掉。
  • 记下 Cursor 版本和当前模型。Opus 4.7 和 Sonnet 4.6 都是 1M token 上下文;Composer 2.5 快,但和别的一样靠检索取料。

需要收集的信息

  • 仓库的文件数和行数,以及是 monorepo 还是单包。
  • 是否有 .cursor/rules/*.mdc.cursorignore,以及上次完整索引的时间。
  • 当前模型、是否在 Agent 模式、Composer 输入框里 @Files 的截图。
  • 模型对 “Which files did you actually read?” 的回答。

最短修复路径

按收益排序,前两步通常下一条 prompt 就见效。

Step 1:用 @Files 手挂 5-10 个最相关文件

最大的杠杆。任务前在 Composer 输入框里写:

@target-file.ts
@reference-pattern-1.ts
@reference-pattern-2.ts
@target-file.test.ts
@types/relevant.ts

然后加一句:“follow the pattern shown in reference-pattern-1.ts exactly”。手动挂的文件绕过检索排序,直接进上下文,这就是它在乱套的 prompt 上比任何招都管用的原因。

Step 2:把 working directory 设小

Cursor → File → Open Folder → 选 apps/web,别选 monorepo 根。Cursor 会把这个子目录当成项目根、只在它内部建索引,检索面和索引都缩到一个包以内,跨包污染基本消失。整个仓库大到没法好好索引时,这就是官方推荐的 monorepo 做法。

Step 3:给每个包写 MDC 规则

新建 packages/web/.cursor/rules/boundaries.mdc

---
description: Web app package boundaries and conventions
globs: packages/web/**
alwaysApply: false
---

This is the customer-facing web app.
- Imports MUST stay inside packages/web/* or packages/shared/*.
- Never import from packages/admin/* or packages/internal-tools/*.
- Use the Logger from packages/shared/logger, not console.log.
- API calls go through src/api/client.ts; do not call fetch() directly.

globs 字段会在匹配文件进上下文时自动挂上这条规则,包与包之间就不再串味。四种激活模式要按需选:Always ApplyalwaysApply: true)、Auto Attached(写 globs)、Agent Requested(写 description、不写 globs)、Manual@rule-name)。边界规则用 globs 的 Auto Attached 最合适。不想写 frontmatter 的话,在每个包目录放一份嵌套 AGENTS.md 也行。

Step 4:让 Composer 先回报上下文,再写代码

两步式 prompt:

Step 1: List exactly which files you've loaded into context and explain how each relates to the task. Do NOT write code yet.
Step 2: After I confirm, implement the change.

它列的文件不对就停手补 @Files,对了再放行。这一步花十秒钟,却能在它往错包写下 200 行之前抓住那次静默漏读。

Step 5:把跨包任务拆成单包子任务

别说”统一所有微服务”,改成:

Task scope: only packages/auth-service for now.
Goal: replace all usage of old_logger with the shared logger.

做完一个包就重开一个 Composer turn 再做下一个,避免上下文在包之间串。

Step 6:重跑索引并调 .cursorignore

先提交一份更紧的 .cursorignore,再重建索引,这样重建出来就是干净的。打开 Cursor Settings → Indexing & Docs → Clear Workspace Index,然后彻底重启 Cursor,让它重新索引(语义检索会在完成 80% 时恢复)。该排除的重目录:

node_modules/
.next/
dist/
build/
vendor/
target/
**/*.snap

正是这些排除项,把大型 monorepo 上动辄好几分钟的索引压成很快就能跑完。(.cursorignore 语法和 .gitignore 一样,每个开发者还能用各自的 ignore 把索引收窄到自己实际改动的目录。)

怎么确认已经修好

  • 同一条 prompt 在 working dir 收窄前后各跑一遍,跨包污染应当消失。
  • 让队友打开同一个 workspace 用同样的 prompt 复现,应当看到同样改善的行为。
  • grep 和 linter 实际验一遍,确认 import 路径不再穿越包边界。

如果还是没修好

  • 把 prompt 缩到最小:只改一个函数、只 @ 一个文件、写死明确路径。
  • 回滚最近一次 Cursor 升级或规则改动。
  • forum.cursor.com 搜 “monorepo composer context”,附上你的版本号和仓库规模数字。
  • 抓 View → Output → Cursor 日志贴到 Bug Reports。

预防建议

  • 每个包都放自己的 .cursor/rules/*.mdc,写清边界、约定、禁用 import。
  • 每个包的 README 里写一节”改了 X 就要同步改 Y”,给模型做 anchoring。
  • 把 5-10 个核心参考文件固定成 workspace 标签页,Composer 检索会优先打开的文件。
  • 大重构交给 Claude Code 这类 CLI agent 做全仓扫描,Cursor 只做编辑器内的局部修改。别强求 Cursor 一个人硬扛——真要交接出去,也要把重构范围卡紧
  • 把”先列上下文再写代码”定为团队的标准 prompt 模板。

常见问题

换个 1M token 的模型能自动解决吗?

不能。Sonnet 4.6、Opus 4.7、Gemini 3.1 Pro 都是 1M token,但窗口只决定能装下多少检索到的上下文,进什么仍由检索说了算。模型再大,喂错上下文照样自信地瞎猜。挂对文件比窗口大小重要得多。

我的 .cursorrules 为什么不生效了?

截至 2026 年,单个根目录 .cursorrules 在 Agent 模式下会被静默忽略——而 Composer 正运行在 Agent 模式。把边界迁到带 globs.cursor/rules/*.mdc,让它们按包自动挂载,或者每个包用嵌套 AGENTS.md

大仓库里 Composer 2.5 比 Sonnet 强吗?

Composer 2.5 于 2026 年 5 月 18 日发布(基于 Moonshot 的开源 Kimi K2.5),在 SWE-Bench Multilingual 上拿到 79.8%,和 Opus 4.7 的 80.5% 基本打平,而且又快又便宜,支持文件树级别的多文件改动。但要在庞杂、含糊的代码库里做持续推理,Opus 4.7 仍是更稳的选择;而当你已经挂好了正确的文件、只需要快速多文件改动时,Composer 2.5 性价比很高。无论哪个,上下文纪律都比模型选择更关键。

怎么知道 Composer 漏读了某个文件?

直接问它:List exactly which files you read。如果它列的清单漏了你明知相关的文件,就是检索没命中——用 @Files 把那些文件挂上再重跑。别信一个从不说明出处的自信回答。

超大 monorepo 要不要干脆关掉索引?

不要——关掉 Composer 就彻底没了语义检索(也就是帮你自动找相关文件的代码库 @ 搜索和检索)。该做的是调 .cursorignore,排除 node_modulesdistbuildvendortarget,再打开子包目录、让 Cursor 只在它内部建索引。这样既保持索引快,又不会让 agent 变瞎。

相关阅读

标签: #排查 #Cursor #排查 #Composer 大项目