Cursor 老是读错文件:分桶诊断 + 修复路径

Cursor 把旧 / 生成 / vendored 文件拉进 context 导致 patch 出错。靠 .cursorignore、重建索引和 @ 锁定来修。

你让 Cursor 修一个 React 组件,它却引用了 dist/components/Foo.js(已编译的旧版)里的 prop 名,导致 patch 完全不对。或者你问”我们怎么实现 rate limit 的?“,它回答的是 vendor/express-rate-limit/ 里的源码,而不是你自己写的中间件。这都是 Cursor 把不该读的文件塞进了 context:构建产物、vendored 第三方库、备份文件、生成代码。

最快修法: 把污染目录加进仓库根的 .cursorignore,在 Command Palette 里跑一次 Reindex Codebase,然后重问问题时用 @File / @Folder 把真正的源码钉进去。下面会帮你判断自己属于哪一桶,并把 Cursor 彻底锁到你的真实代码上。

先判断你属于哪一桶

展开上一条回复里的 context chips(回答上方的来源标签),或者跑下面 Step 5 的验证 prompt,看看出现了什么,对号入座:

context 里出现了什么所属桶主要修法
dist/build/.next/out/.astro/coverage/生成文件被索引.cursorignore 屏蔽构建目录 + 重建索引
vendor/public/vendor/third_party/、某个大 *.min.jsvendored 库压过源码.cursorignore 屏蔽 vendored 路径 + 重建索引
.env.localscratch.mdold-backup-2024.tsx(未追踪)只有 .gitignore,没有 .cursorignore.cursorignore(光靠 gitignore 不够)
Foo.tsx.origFoo.tsx~Foo-old.tsx备份 / merge 冲突遗留删垃圾文件 + ignore 对应模式
__snapshots__/*.snapfixtures/mocks/测试产物抢戏.cursorindexingignore(保留可 @ 引用)

常见原因

按出现频率从高到低排序。

1. 生成文件和源码混在一起

dist/build/.next/coverage/ 没被 ignore,Cursor 把它们一并索引。embedding 检索时,编译后的 JS 经常因为包含一样的关键字被判定相关,反而压过真正的 src/ 源码。

如何判断:展开 Cursor 回复上方的 context chips,里面出现 dist/build/.next/out/.astro/ 任意一个路径就是命中。

2. Vendored 第三方库放在仓库里

旧式 Node / PHP 项目里 vendor/ 直接 commit;前端项目偶尔有 public/vendor/jquery-3.5.1.min.js。这些代码体量大、命中率高,把 agent 的 context 挤满。

如何判断du -sh vendor/ public/vendor/ third_party/ 2>/dev/null,若任一被索引目录大于 5MB,就是元凶。

3. 完全没有 .cursorignore

很多人以为 .gitignore 就够了。Cursor 确实会遵守 .gitignore 外加一份内置默认 ignore 列表,但两者都不管那些从未提交过的本地文件:.env.localscratch.mdold-backup-2024.tsx。Cursor 看见就会索引。

如何判断:仓库根执行 ls -la | grep -E "(scratch|backup|old|\.bak|\.orig)",列出来的文件如果不在 .cursorignore 里,都是潜在污染源。

4. 备份与 .bak / .orig 文件

merge 冲突遗留的 Foo.tsx.orig、IDE 自动保存的 Foo.tsx~、或者你手动复制的 Foo-old.tsx。这些文件和当前版本只差几行,embedding 算它们高度相关,agent 经常把旧逻辑搬回来。

如何判断find . -type f \( -name "*.orig" -o -name "*.bak" -o -name "*~" -o -name "*-old.*" \) | head -20

5. 测试 fixtures / snapshot 抢戏

__snapshots__/fixtures/mocks/ 经常和真实代码语义雷同但行为不同。改业务逻辑时,agent 把 snapshot 里的旧字符串误认为是当前代码的真实状态。

如何判断:在 context chips 里看到 __snapshots__.snap 文件,且你的 prompt 跟测试无关,就是。

最短修复路径

Step 1:建一个能拦住常见污染源的 .cursorignore

在仓库根创建或扩充 .cursorignore。语法是 gitignore 风格(***?! 取反、# 注释):

# 构建产物
dist/
build/
out/
.next/
.astro/
.svelte-kit/
.nuxt/

# 依赖与 vendored
node_modules/
vendor/
public/vendor/
third_party/

# 缓存、覆盖率、日志
.cache/
.turbo/
.parcel-cache/
coverage/
*.log

# 备份与编辑器临时文件
*.bak
*.orig
*~
*-old.*
*.swp

# 压缩 / 打包文件
*.min.js
*.min.css
*.bundle.js
*.bundle.css

# 本地 scratch
scratch.md
TODO.local.md

根据栈微调,原则是 AI 看到没价值或会误导的,全屏蔽。

一个值得知道的细节(据 Cursor 官方文档,截至 2026 年 6 月):.cursorignore 会拦住该文件的 semantic search、Tab、Agent、Inline Edit 以及 @ mention 引用,但 Agent 调用的 terminal 和 MCP server 工具仍然能访问这些路径。也就是说,.cursorignore 管的是模型能不能读,管不了 shell 命令能不能看到。

Step 1b:想保留 @ 引用能力的文件,用 .cursorindexingignore

.cursorignore 是硬屏蔽。对于测试 fixtures、大 snapshot、或者偶尔确实想手动拉进来的生成文档,改用 .cursorindexingignore。它把文件从 codebase index 里去掉(这样不再污染自动检索),但你显式 @File 时仍能读到。

# 从索引里去掉,但保留可 @ 引用
**/__snapshots__/
**/*.snap
fixtures/
mocks/

记忆口诀:.cursorignore 用于”永远别读”,.cursorindexingignore 用于”别自动拉,但允许我按需引用”。

Step 2:强制 Cursor 重建索引

两个 ignore 文件改完都不会立刻生效,必须重建索引。最可靠的触发方式是 Command Palette:

Cmd+Shift+P(macOS)/ Ctrl+Shift+P(Windows/Linux)
→ "Reindex Codebase"

也可以走 Cursor Settings → Indexing(索引相关设置已从旧的 Features 页面挪了出来)。想确认清理后还剩哪些,打开 Cursor Settings → Indexing & Docs → View included files:污染目录应该已经不在列表里。

如果索引看起来卡住了,可以硬重置——退出 Cursor,删掉单仓库的索引缓存,再重启:

# 先退出 Cursor,然后:
rm -rf ~/Library/Application\ Support/Cursor/User/workspaceStorage/*/cursorIndex
# 重启 Cursor,它会从零重建索引

注意 Cursor 只会按大约 10 分钟一次的轮询周期自动重索引改动过的文件(用 Merkle 树做 diff),所以改完 ignore 文件后,手动重索引是唯一能立刻生效的办法。

Step 3:在 prompt 里用 @File / @Folder 锁定要参考的文件

即使 ignore 干净了,长 chat 仍会偶发误检索。最稳妥的做法是显式钉:

@Folder src/components/auth @File src/lib/auth.ts

修改 AuthForm 组件的 onSubmit。只读 / 改我 @ 出来的文件,
不要参考任何 dist/、build/ 或 *.min.js 的内容。

加一句反向约束(不要参考 X)通常比只加正向约束更有效。只 @ 你确定相关的文件,多 tag 无关文件反而会稀释 agent 对重点的判断。

Step 4:定期清理仓库里的考古层

每月(或加到 pre-commit hook)跑一次清扫:

# 列出所有可能被 agent 误读的文件
find . -type f \( \
  -name "*.bak" -o -name "*.orig" -o -name "*~" \
  -o -name "*-old.*" -o -name "*-backup.*" \
  -o -name "*.swp" \
\) -not -path "./node_modules/*" -not -path "./.git/*"

# 确认无用后删
find . -type f \( -name "*.bak" -o -name "*.orig" -o -name "*~" \) \
  -not -path "./node_modules/*" -delete

合并冲突后第一时间用 git status 看是否产生了 .orig,立刻删。

Step 5:验证 agent 现在读对了

让 Cursor 自己列读取的文件:

列出你为了回答上一个 prompt 读了哪些文件,完整路径,按读取
顺序。不要包含 dist / vendor / *.min.js。如果有,告诉我哪个 @ 把它们带进来了。

如何确认已修好

三条都成立才算修好:

  1. Cursor Settings → Indexing & Docs → View included files 里不再出现上面任何一桶的路径。
  2. 新回复上的 context chips 里只剩 src/(或你真正的源码根目录)下的文件。
  3. Step 5 的验证 prompt 返回的全是干净路径。若仍出现脏路径,说明 ignore 不完整:记下漏掉的目录,回 Step 1 补上,再重索引一次。

常见问题

为什么我都把文件加进 .cursorignore 了,Cursor 还是读它? 两个常见原因。第一,你没重索引:.cursorignore 的改动要跑过一次 Reindex Codebase 才生效。第二,这文件是被 terminal 命令或 MCP 工具读到的,不是模型自己检索到的。.cursorignore 能拦住读取、Tab、Agent、Inline Edit 和 @ mention,但拦不住 Agent 跑的 shell 命令去 list 或 cat 那个路径。

.cursorignore.cursorindexingignore 该用哪个? AI 永远不该读的(密钥、构建产物、vendored 代码)用 .cursorignore;想从自动检索里剔除、但仍想用 @File 手动引用的(测试 fixtures、大 snapshot)用 .cursorindexingignore。大约 90% 的仓库只需要 .cursorignore

已经有 .gitignore 了,还需要 .cursorignore 吗? 需要。Cursor 会遵守 .gitignore 外加一份默认 ignore 列表,所以提交进仓库的构建产物通常已被跳过。但未追踪的本地文件(.env.localscratch.md、手工备份)不在 .gitignore 里,只有 .cursorignore 能把它们挡在 context 外。

跑了 “Reindex Codebase” 却没反应怎么办? 退出 Cursor,删掉仓库的索引缓存(macOS 用 Step 2 的 rm -rf 那行,Windows 对应 %APPDATA%\Cursor 下的目录),再重启,Cursor 会从零重建。另外去 Cursor Settings → Indexing 确认该仓库的索引是开着的。

怎么让它以后不再复发?.cursorignore 随仓库一起 commit,构建产物和 vendored 代码放独立顶层目录,每次 merge 后立刻删 .orig。详见下面的预防建议。

预防建议

  • 新仓库初始化时就 commit .cursorignore,模板和 .gitignore 同步维护。
  • 生成产物(dist/build/coverage/)永远放仓库根独立目录,不要散落在 src/ 里。
  • vendored 第三方代码放 third_party/ 并加进 .cursorignore,不要直接放仓库根或 public/
  • 解决 merge 冲突后立刻 find . -name "*.orig" -delete,避免污染索引。
  • .cursorrules / CLAUDE.md 里写一句 “Treat dist/, build/, vendor/, and .min. as build artifacts. Never read them.”

相关阅读

外部参考:Cursor: Ignore FilesCursor: Codebase Indexing

标签: #AI 编程 #排查 #排查 #Cursor