Cursor 生成重复逻辑,而不是复用已有 helper

Cursor 又写了一个 formatDate,但 src/utils 里早就有——已有 helper 根本没进检索。教你让它先 grep 再写、把现成的复用起来。

你让 Composer 写一段格式化日期的逻辑,它给了你一个 function formatDate(d) {...}。打开 src/utils/date.ts,里面早就有一份成熟的 formatDate,连 timezone / locale 都处理好了。问题不是模型偷懒,而是它的检索压根没把已有 helper 拉进上下文——它”诚实”地以为仓库没有,于是从头写了一个。

最快的修法:在让它写 helper 之前,先要求它搜一遍。把这一句贴在 prompt 最前面:

First run a codebase search and grep for existing "date format" helpers, list what you find with file paths, and reuse one if it fits. Only write new code if nothing suitable exists.

光这一句就能在一轮之内挡掉大部分重复。下面的内容是把”复用”变成默认行为,这样你不必每次都手动提醒。

为什么会这样(30 秒版)

Cursor 不会在每次请求时读完整个仓库。每一轮它只用两种机制检索有限的代码块:基于向量索引的 semantic search,加上精确匹配的 grep(Cursor 自带的 Instant Grep 引擎,官方称在大型仓库上比 ripgrep 更快)。模型只能基于检索回来的内容推理。如果你的 formatDate 在排名很低的路径里、命名风格不同、或者 agent 干脆没去搜,那它在动笔那一刻就是看不见的。截至 2026 年 6 月,Cursor 公布的数据是:semantic search 与 grep 结合,相比只用 grep,在代码库问答上的准确率约提升 12.5%,而且仓库越大(超过 1000 文件)差距越明显——所以这个问题在大仓库里尤其集中。

先判断你属于哪一类

改任何东西之前先做这几步检查,它们直接对应下面的修复步骤。

你观察到的现象大概率原因最快的修法
agent 没有任何搜索步骤就直接写代码写之前没搜Step 1(强制先搜)
rg "function formatDate" 找得到,agent 找不到helper 在检索里排名太低 / 埋太深Step 2-3(清单 + 规则)
仓库叫 renderDate / displayDate,agent 写成 formatDate命名风格不一致Step 1 + 6
rg 已经能找到 2 处以上现成实现仓库本来就有重复Step 5(去重扫描)
你用的是 Cmd+K,不是 Composer/AgentCmd+K 不走全仓搜改用 Composer/Agent

常见原因

1. 已有 helper 不在 retrieval 上下文里

仓库 1 万文件,检索只取有限的 top-K 块。src/utils/date.ts 没匹配上当前 prompt 的关键词或 embedding,根本没进上下文。

如何判断:让它 “List every file you read for this.”;如果 src/utils/date.ts 不在列表里,就是没看到。

2. helper 放在不明显的位置

packages/feature-a/internal/helpers/date.ts 这种深层路径相似度排名低,而且 date.ts 这个文件名到处都是,毫无辨识度。

如何判断:你自己用 rg "function formatDate" 搜一下,看找到几处、分别在哪。

3. 命名风格和模型默认起名不一致

仓库里叫 toLocaleDateStringrenderDatedisplayDate,模型默认偏向起 formatDate。semantic search 多少能补一点,但 ranking 仍偏低,而精确 grep formatDate 直接落空。

如何判断:把仓库现有命名和模型给的新命名并排列出来,看是不是风格不同。

4. 没有中心化 utility 索引

没有 src/utils/README.md,也没有 rules 文件列出 helper 清单。模型每一轮都得从 0 重新摸索目录结构。

如何判断ls src/utils/ 看有没有 README.mdindex.ts 这种 entry point。

5. 仓库本来就已经有重复

不只是模型在造重复,仓库里本来就有好几份。packages/web 一个 formatDatepackages/admin 又一个,模型只看到其中一个、又造了第三个。

如何判断rg "function formatDate" --type ts,看整个仓库里出现几次。

6. Agent 没 grep 就动笔

agent 模式能调用 codebase 搜索和 grep,但模型不一定真去调。它经常直接写代码、从不先搜。

如何判断:打开这条消息的 tool call 链(回复下方那个折叠的”思考 / 工具”面板)。如果在 edit 之前没有任何 search / grep / read 步骤,就是盲写。

动手前先确认

  • 确认入口是 Composer/Agent、Cmd+K 还是 chat。 Cmd+K(行内编辑)只针对当前选区,不会跑全仓搜,所以必然重造。任何需要复用共享代码的事都用 Composer/Agent。
  • 先 commit,这样后续合并 helper 时有追踪,也能干净地回滚。
  • 记下 Cursor 版本和当前模型。 截至 2026 年 6 月,agent 模型选择器里有 Claude Sonnet 4.6、Claude Opus 4.7、GPT-5.5、Gemini 3.1 Pro,以及 Cursor 自家的 Composer 2.5。推理更重的模型(比如 Opus 4.7)比快模型更倾向于先搜再写;如果重复一直出现,换个模型用同一 prompt 再跑一次。

需要收集的信息

  • Composer 给出的新 helper 函数全文。
  • 仓库里已有相似 helper 的路径和实现。
  • 这条消息 tool call 链的截图。
  • 你的规则文件:.cursor/rules/*.mdc、根目录的 AGENTS.md,以及(如果还有)旧版 .cursorrules,看有没有”复用优先”条款。
  • src/utils/ 或对应 utility 目录的结构。

最短修复路径

顺序是”先修这一次,再修系统层面的可发现性”。

Step 1:写之前强制先搜

把这段当成可复用的 prompt 模板:

Before writing new code:
1. Run a codebase search AND grep for existing functions related to "date formatting" / "format date" / "render date".
2. List what you found with file paths.
3. If a suitable one exists, reuse it and explain why.
4. Only if nothing suitable exists, write new code — and propose a location.

在 agent 模式里你还可以让它 “use the Explore subagent first”——Cursor 能开一个独立的 Explore subagent,在自己的上下文窗口里并行跑大量搜索,只把相关结果带回来,特别适合在大仓库里回答”这个是不是已经有了”。

Step 2:给模型一份能读的 utility 清单

加一个 src/utils/README.md 当一眼就能看懂的索引:

# Utilities

| Function | File | Purpose |
|---|---|---|
| formatDate | date.ts | Locale-aware date formatting |
| formatCurrency | money.ts | Currency formatting with i18n |
| parseInvoice | invoice.ts | Parse invoice payloads |
| isValidEmail | validation.ts | RFC 5322 email check |

然后在相关 prompt 里用 @src/utils/README.md 引用它,让清单必然进上下文,而不是指望检索碰巧把它捞上来。

Step 3:加一条”复用优先”规则——用当前的规则格式

2026 年的重要变化:根目录那个单文件 .cursorrules 已经是旧版、被弃用的格式,而且在 Agent 模式里加载不稳定。Cursor 现在的格式是根目录的 AGENTS.md(纯 markdown),或者要更精细作用域时用 .cursor/rules/*.mdc 项目规则。如果你的 .cursorrules “不起作用”,迁移过去往往就修好了。

最简单的做法——根目录 AGENTS.md

# Project conventions

- Before writing a new utility function, search src/utils/ and packages/shared/ for an existing implementation. Reuse it if one fits.
- Common helpers:
  - src/utils/date.ts (date / time formatting)
  - src/utils/money.ts (currency)
  - src/utils/validation.ts (input validation)
- New helpers go in src/utils/ and must be added to src/utils/README.md.
- Do not inline helpers inside component files; extract to src/utils/.

更精细的做法——在 .cursor/rules/reuse-helpers.mdc 写一条项目规则。frontmatter 键是 descriptionglobsalwaysApply,三者的组合决定规则类型(Always / Auto Attached / Agent Requested / Manual):

---
description: Reuse existing utility helpers instead of writing new ones
alwaysApply: true
---

- Before writing a new utility function, search src/utils/ and packages/shared/ first.
- Reuse an existing helper if one fits; only create new code when nothing matches.
- New helpers go in src/utils/ and must be listed in src/utils/README.md.

alwaysApply: true 的规则要写短(200 词以内)——它每次请求都会发送,token 成本会累积。你可以在 chat 里用 /create-rule 直接生成一条骨架。(注意 .cursor/rules/ 里的纯 .md 文件会被忽略,因为没有 frontmatter——文件必须是 .mdc。)

Step 4:发现 diff 里有重复立刻回怼

You wrote a new `formatDate` function, but src/utils/date.ts already has one with timezone handling.
Replace your implementation with `import { formatDate } from "@/utils/date"`.
Find and fix any other helpers you duplicated the same way.

Step 5:仓库级去重扫描

# 列出所有函数名并统计重复
rg "^(export )?(async )?function (\w+)" --type ts -o -r '$3' | sort | uniq -c | sort -rn | head -30

# 专门找 formatDate 的重复
rg "function formatDate" --type ts

把发现的重复合并成单一源,update 调用点。这样以后 agent 跑的时候只有一个锚点可找,而不是三个。

Step 6:给重要 helper 起独特的名字

formatDate 太通用——既和模型默认起名撞车,又和满仓库的 date.ts 撞车。给领域 helper 起带信号的名字:formatInvoiceDateformatRelativeDateformatLocaleDate。grep 精度和 semantic ranking 都会上去,模型也几乎不会去重写一个名字明显不同的 helper。

怎么确认已经修好

  • 重跑原来的 prompt。在 tool call 链里你现在应该看到 edit 之前有 search / grep 步骤,而且回复会点名已有 helper,而不是另写一个。
  • diff 里新增的 helper 定义明显减少。
  • 让它 “List every file you read.”,已有 helper 的路径应该出现在列表里。
  • 让队友用同一 prompt 跑一遍,行为一致才说明规则在共享配置里(AGENTS.md.cursor/rules/)生效了,而不只是你本地 chat 里的临时提示。

如果还是没修好

  • 把 prompt 缩到只问”是否有现成的 date-format helper?搜一下并告诉我路径。“如果还是找不到,说明索引过期了——打开 Cursor Settings → Indexing & Docs 重新索引代码库,再试。
  • 确认文件没被排除:检查 .cursorignore / .cursorindexignore.gitignore,被 ignore 的路径不会被索引。
  • 如果某条规则没生效,确认它是 .cursor/rules/ 下的 .mdc 文件(不是 .md),且 alwaysApply 或 glob 确实匹配;Agent 模式里用旧版 .cursorrules 是最常见的”没生效”原因。
  • 回滚最近一次规则改动,逐条确认到底哪条在起作用。
  • forum.cursor.com 搜 “duplicate helper composer”;如果确实是 bug,带上 prompt 和 tool call 截图发到 Bug Reports。agent 日志在 Output 面板里抓(View → Output → 选 “Cursor”)。

预防建议

  • 每个包一个 utils/ 目录加一份 README 清单,给模型显式入口。
  • AGENTS.md.cursor/rules/*.mdc 里写”复用优先”规则并列出主要 helper 路径(不要放在旧版 .cursorrules 里)。
  • 重要 helper 命名要有领域信号(formatInvoiceDateformatDate 好)。
  • PR review checklist 加一条:“是不是在复用已有 helper?“在评审阶段挡住重复。
  • 季度做一次 utility 去重扫描:合并相似实现、更新 README。

FAQ

为什么 agent 有时复用 helper、有时又造重复? 检索是概率性的——哪些块能排进有限的上下文,取决于你的措辞、当前模型,以及 agent 这一轮有没有去 grep。与其指望检索碰巧捞对块,不如用一份你 @ 引用的清单加一条常驻规则,把复用变成确定性行为。

2026 年 .cursorrules 还能用吗? 根目录的 .cursorrules 单文件已是旧版、被弃用。它在某些模式下还能加载,但在 Agent 模式里行为不稳定。把约定迁到根目录 AGENTS.md.cursor/rules/*.mdc 项目规则,它们在 Chat、Composer、Agent 里加载都一致。

该用 AGENTS.md 还是 .cursor/rules/*.mdc 全仓通用、写一个文件就够的简单约定用 AGENTS.md;需要作用域时用 .cursor/rules/*.mdc——比如只在编辑某些 glob 时附加的规则,或按 description 让 agent 按需拉入的规则。两者可以共存,不必二选一。

helper 明明存在,agent 却说找不到,怎么办? 多半是索引过期或文件被 ignore 了。在 Cursor Settings → Indexing & Docs 重新索引,确认路径不在 .cursorignore.cursorindexignore.gitignore 里。然后直接 @ 那个文件,彻底绕过检索。

换模型有用吗? 有时有用。推理更重的模型(Opus 4.7)比快模型更常先搜再写。但换模型只是创可贴;真正长效的修法是清单加”复用优先”规则,让行为不依赖当前用的是哪个模型。

相关阅读

标签: #排查 #Cursor #排查 #重复逻辑