你让 Composer 写一段格式化日期的逻辑,它给了你一个 function formatDate(d) {...}。打开 src/utils/date.ts,里面早就有一份成熟的 formatDate 实现,连 timezone / locale 都处理了。问题不是模型偷懒,而是它的嵌入检索没把已有 helper 拉进上下文——它”诚实”地认为这个仓库没有,于是从头写。
修这类问题要从两侧入手:让模型主动查 + 让已有 helper 更容易被查到。
常见原因
1. 已有 helper 不在 retrieval 上下文里
仓库 1 万文件,Composer 检索只取 top-K 块。src/utils/date.ts 没匹配上当前 prompt 的关键词,根本没进 prompt。
如何判断:Composer 答完后追问 “List every file you read”;如果 src/utils/date.ts 不在,就是没看到。
2. helper 放在不明显的位置
packages/feature-a/internal/helpers/date.ts 这种深层路径相似度排名低,而且文件名 date.ts 太常见。
如何判断:你自己用 rg "function formatDate" grep 一下,看找到几处、分别在哪。
3. 命名风格和模型默认起名不一致
仓库里叫 toLocaleDateString、renderDate、displayDate,模型偏向起 formatDate。检索做 semantic search 多少能命中,但 ranking 仍偏低。
如何判断:把仓库现有命名和模型给的新命名列出来对比,是不是风格不同。
4. 没有中心化 utility 索引
没有 src/utils/README.md、没有 .cursorrules 列 helper 清单。模型每次都得从 0 探索。
如何判断:ls src/utils/ 看有没有 README.md / index.ts 这种 entry point。
5. monorepo 多包重复造的轮子已经存在
不仅模型造重复,仓库本身就有多份。packages/web 一个 formatDate,packages/admin 又一个,模型只看到一个、又造了第三个。
如何判断:rg "function formatDate" --type ts,看一个仓库里出现几次。
6. Agent 没主动 grep 就开始写
agent 模式有 grep_search 工具,但模型不一定调用。直接写代码而不先搜。
如何判断:Composer 这条消息的 tool call 链里没有 grep_search / read_file 步骤。
动手前先确认
- 确认问题是在 Composer / Cmd+K / chat 哪个入口;Cmd+K 不走全仓搜,必然重造。
- 复现前先 commit,避免后续合并 helper 时丢追踪。
- 记下 Cursor 版本和当前模型;不同模型主动 grep 的倾向不同(opus 比 sonnet 更愿意先搜)。
需要收集的信息
- Composer 给的新 helper 函数全文。
- 仓库里已有相似 helper 的路径 + 实现。
- Composer 这条消息的 tool call 链截图。
.cursorrules全文(看有没有”复用优先”条款)。src/utils/或对应 utility 目录结构。
最短修复路径
按”立刻修这次 → 系统改善发现性”排序。
Step 1:写之前让模型主动搜
新 prompt 模板:
Before writing new code:
1. Use grep_search to find existing functions related to "date formatting" / "format date" / "render date".
2. List what you found with paths.
3. If a suitable one exists, use it and explain why.
4. Only if nothing suitable exists, write new — and propose a location.
Step 2:写 utility README + index.ts 当目录
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 在 Composer 里 @src/utils/README.md 一下,模型会直接看到清单。
Step 3:.cursorrules 加复用规则
# .cursorrules
- Before writing a new utility function, search src/utils/ and packages/shared/ for existing implementations.
- Common helpers live in:
- src/utils/date.ts (date / time formatting)
- src/utils/money.ts (currency)
- src/utils/validation.ts (input validation)
- If you must create a new helper, place it in src/utils/ and update src/utils/README.md.
- DO NOT inline helpers inside component files; extract to src/utils/.
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"`.
Update any other duplicated helpers similarly.
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 调用点。这是给以后的 AI 提供单一锚点。
Step 6:给重要 helper 起独特的名字
formatDate 太通用。改成 formatInvoiceDate / formatRelativeDate / formatLocaleDate,retrieval 命中精度上去,模型也不会随便重写一个同名的。
怎么确认已经修好
- 同一个 prompt 重跑,看模型先 grep 再说话,而不是直接写代码。
- diff 里新增 helper 数量明显减少。
- 让队友用同一 prompt 跑,确认 rules 在团队配置里生效。
如果还是没修好
- 把 prompt 缩到只问”是否有 X helper”,看模型搜不搜得到。
- 回滚
.cursorrules最近改动,逐条确认哪条规则在起作用。 - 在 forum.cursor.com 搜 “duplicate helper composer”;附 prompt + tool calls 截图。
- 抓 View → Output → Cursor 的 agent 日志贴 Bug Reports。
预防建议
- 每个包一个
utils/目录 + README index,给模型显式入口。 .cursorrules写”复用优先”原则 + 主要 helper 路径清单。- 重要 helper 命名要有领域信号(
formatInvoiceDate比formatDate好检索)。 - PR review 时把”是否复用已有 helper”列入 checklist,发现重复要求合并。
- 季度做一次 utility 去重扫描,把多份相似实现合并,更新 README。