Cursor Generates Duplicate Logic

AI wrote a new formatDate but src/utils already has one — existing helpers weren't in retrieval; make them findable.

You ask Composer for a date-formatting helper. It hands you function formatDate(d) {...}. Open src/utils/date.ts: a mature formatDate already lives there with timezone and locale handling. The model isn’t lazy — its embedding retrieval just didn’t surface the existing helper, so it “honestly” believed the repo had none and rewrote one.

Fix from both sides: get the model to look first, and make the existing helpers easier to find.

Common causes

1. Existing helper not in retrieval context

Repo has 10k files; Composer retrieval pulls top-K chunks. src/utils/date.ts didn’t match the prompt’s keywords well enough to make it in.

How to judge: ask “List every file you read” — if src/utils/date.ts isn’t there, it wasn’t seen.

2. Helper buried in a non-obvious path

packages/feature-a/internal/helpers/date.ts ranks low and the generic filename date.ts repeats everywhere.

How to judge: rg "function formatDate" yourself — count hits and locations.

3. Naming style mismatch

Your repo uses toLocaleDateString / renderDate / displayDate; the model defaults to formatDate. Semantic search partially helps but ranking stays low.

How to judge: list your repo’s actual naming vs the model’s proposal — style gap.

4. No centralized utility index

No src/utils/README.md, no .cursorrules listing helpers. The model has to discover from scratch every time.

How to judge: ls src/utils/ — no README.md / index.ts entry point.

5. Monorepo already has duplicates

The model isn’t only producing duplicates; the repo has multiple `formatDate’s already. The model sees one and adds a third.

How to judge: rg "function formatDate" --type ts — count occurrences in the repo.

6. Agent didn’t grep before writing

Agent mode has grep_search but the model doesn’t always call it. It writes first, searches never.

How to judge: Composer message tool-call chain has no grep_search / read_file step.

Before you start

  • Identify which entry point: Composer / Cmd+K / chat. Cmd+K doesn’t do repo-wide search and will always duplicate.
  • Commit before reproducing so later helper consolidation stays tracked.
  • Note Cursor version and active model — opus is more inclined to grep first than sonnet.

Info to collect

  • Full text of the new helper Composer produced.
  • Paths + implementations of existing similar helpers in your repo.
  • Screenshot of the message’s tool-call chain.
  • Full .cursorrules (any “reuse first” clause?).
  • Layout of src/utils/ or your equivalent.

Shortest fix path

“Fix this instance → systemic discoverability” order.

Step 1: Make the model grep before writing

Prompt template:

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 as a manifest

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 |

Then @src/utils/README.md in relevant Composer prompts so the model sees the manifest.

Step 3: Reuse rule in .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: Push back when you see duplication

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: Repo-wide dedupe sweep

# Find functions with similar names
rg "^(export )?(async )?function (\w+)" --type ts -o -r '$3' | sort | uniq -c | sort -rn | head -30

# Find specifically formatDate duplicates
rg "function formatDate" --type ts

Consolidate to a single source; update call sites. Future AI then has one anchor instead of three.

Step 6: Give important helpers unique names

formatDate is too generic. Use formatInvoiceDate / formatRelativeDate / formatLocaleDate — retrieval precision goes up and the model is less likely to rewrite a same-name variant.

How to verify the fix

  • Rerun the same prompt; the model should grep first, then speak — not write blind.
  • Diff has noticeably fewer new helpers.
  • A teammate running the same prompt sees the same behavior — confirms rules in team config.

If it still fails

  • Reduce prompt to just “is there an X helper?” — verify the model can find it.
  • Roll back the latest .cursorrules change to see which rule was working.
  • Search forum.cursor.com for “duplicate helper composer”; include prompt + tool-call screenshots.
  • Grab View → Output → Cursor agent logs and post to Bug Reports.

Prevention

  • One utils/ per package + README manifest — give the model an explicit entry point.
  • .cursorrules enforces “reuse first” + lists main helper paths.
  • Name important helpers with domain signal (formatInvoiceDate beats formatDate).
  • PR review checklist: “Is this reusing the existing helper?” — block duplication.
  • Quarterly utility dedupe sweep: merge near-duplicates, update README.

Tags: #Troubleshooting #Cursor #Debug #Duplicate logic