Skip to content

Large codebase strategies in Cursor

A large codebase strategy in Cursor narrows what the agent sees to the slice a task touches: open the package instead of the monorepo root, keep tracked noise out with .cursorignore, load conventions through glob-scoped rules, and split cross-package work into planned steps. Each step ends with a scope check, a leftover-pattern count, and tests on dependent packages.

This page is for developers and tech leads working in a repository past roughly 100,000 lines, usually a TypeScript monorepo. You ask the agent for a notification-preferences endpoint and it writes a new service from scratch instead of using the one three directories down. It copies a pattern from packages/legacy-billing/, which nobody has touched in two years. Halfway through a rename it forgets which packages it already finished. None of this is a Cursor defect. The agent reads a slice of the repository, and in a large repository the default slice is the wrong one.

What you get from scoping Cursor to a large codebase

Section titled “What you get from scoping Cursor to a large codebase”
  • A way to open a large repository so the agent’s search starts in the right package.
  • A .cursorignore aimed at the files .gitignore does not already cover.
  • A root rule, glob-scoped package rules and module summaries that tell the agent what the code cannot.
  • Four copy-paste prompts: map an area, plan a cross-package change, execute one step, and audit stale rules.
  • A verify-change.sh gate that proves scope, completeness and consumer health without a line-by-line review.

Why does Cursor’s agent get worse as the repository grows?

Section titled “Why does Cursor’s agent get worse as the repository grows?”

The agent answers from what it reads, and it reads a fraction of the repository. In a large codebase three things go wrong, and each has its own fix.

SymptomCauseFix on this page
Copies a deprecated module, cites generated codeSearch finds the wrong files firstOpen the package and filter tracked noise
Writes a new helper instead of using yoursConventions live in people’s heads, not in the codeScoped rules and module summaries
Forgets earlier decisions, repeats workOne chat carries too much historyPlanned steps, one chat each

A larger context window does not remove any of these causes. More room lets the agent hold more of the wrong files. Which files to attach and when to let the agent search is covered in context management in Cursor; this page applies those habits to repositories that do not fit in any context window.

Open the package you work in, not the monorepo root

Section titled “Open the package you work in, not the monorepo root”

Where you open Cursor decides where the agent’s search starts and which rules load. The rule loader in @cursor/sdk 1.0.32 always reads .cursor/rules/ in the opened directory and in every directory above it. Opening packages/api/ therefore loads the API package’s own rules and the repository’s root rules, while the search starts among API files.

  1. For work inside one package, open that package’s directory as the folder.

  2. For work that spans two or three packages, create a multi-root workspace file in the repository root and open it instead of the root folder. Cursor, as a VS Code fork, opens VS Code multi-root .code-workspace files; confirm this in your build:

    // api-web.code-workspace
    {
    "folders": [
    { "path": "packages/api", "name": "api" },
    { "path": "packages/web", "name": "web" },
    { "path": "packages/shared", "name": "shared" }
    ]
    }
  3. Commit the workspace files your team reuses, one per common combination, and name them after the task (api-web.code-workspace, not workspace2.code-workspace).

  4. Open the whole root only for tasks that are cross-cutting by nature, such as a repository-wide rename, and pair them with the plan in Make a cross-package change in planned steps.

Across separate repositories, rather than packages in one, the same idea extends to contract rules and a contract-first workflow; see multi-repo workflows in Cursor.

Keep tracked noise out of the agent’s search with .cursorignore

Section titled “Keep tracked noise out of the agent’s search with .cursorignore”

Cursor’s agent reads both .gitignore and .cursorignore when it walks the workspace, and both use the same syntax (checked in @cursor/sdk 1.0.32). Anything already in .gitignore, such as node_modules/ or dist/, is excluded without a second entry. .cursorignore is for files that are committed but that the agent must never copy from:

# .cursorignore: committed files the agent must not read or imitate
# Generated code that is checked in
packages/api/src/generated/
packages/web/src/__generated__/
*.generated.ts
# Deprecated modules kept only until the migration finishes
packages/legacy-billing/
# Vendored third-party code and large fixtures
vendor/
packages/api/test/fixtures/large/
# Lockfiles and minified bundles
pnpm-lock.yaml
*.min.js

Two rules keep the file useful. Never exclude live source, tests or configuration, because the agent needs them to match your patterns. And treat the deprecated-module entry as temporary: delete it when the module is gone, or the agent cannot help with the migration that removes it. A path blocked this way fails with an error that names .cursorignore when the agent tries to read it, which also makes a wrong entry easy to spot.

To measure what an ignore change does to indexing and search speed, with before-and-after numbers, see tuning Cursor for large codebases. For the security use of the same file, see privacy and security for Cursor teams.

Write down what the code cannot show in scoped rules

Section titled “Write down what the code cannot show in scoped rules”

A new hire needs a map and a few house rules before touching a 300,000-line repository, and so does the agent. Project rules are .mdc files in .cursor/rules/. A plain .md file there is skipped by the rule loader (checked in @cursor/sdk 1.0.32), so it fails silently. Three layers cover a large repository.

1. One short always-on map at the root. Keep it to what every task needs: where things live, what is deprecated, and the gate commands.

---
description: Repository map and gates for the acme monorepo
alwaysApply: true
---
- pnpm monorepo. Live packages: packages/api (Fastify), packages/web (Next.js),
packages/shared (types only, no runtime code).
- packages/legacy-billing is deprecated. Never import from it or copy its patterns;
the replacement is packages/api/src/billing.
- Before reporting a task done, run for each package you touched:
pnpm --filter <package> run typecheck && pnpm --filter <package> test

2. Glob-scoped rules per package. A rule with globs loads when a matching file is in context, so API conventions never reach web work. Put it in the package it governs, for example packages/api/.cursor/rules/api-conventions.mdc, and write the glob from the package root, the folder you open. Confirm it matches with the probe routine in custom rules and templates:

---
description: HTTP handler conventions for packages/api
globs: src/**/*.ts
alwaysApply: false
---
- Handlers call services; services call repositories. No SQL in handlers.
- Validate input with the Zod schemas in packages/api/src/schemas/.
- Errors use AppError from packages/api/src/errors.ts, never a bare throw.
- Canonical example: packages/api/src/routes/users/index.ts

3. A summary file for each large module. For a module too big to attach, a one-page ARCHITECTURE.md gives the agent the map for a fraction of the tokens. Reference it from the module’s rule so the agent reads it before opening individual files:

packages/api/src/payments/ARCHITECTURE.md
# Payments
- service.ts: checkout and refunds (Stripe). repository.ts: Drizzle queries.
- webhook.ts: verifies the signature, then updates status via repository.ts.
- Invariants: amounts are integer cents; every Stripe call sends an idempotency key.

When you open the monorepo root instead of the package, the loader reaches rules nested in packages/*/.cursor/rules/ only when nested-rule discovery is on, which it is by default in @cursor/sdk 1.0.32, and a glob written from the package root may not match from the repository root. Prove which rules load with the probe routine in the rules guide rather than assuming. Load modes, templates and a CI check that fails when a glob matches nothing are covered in custom rules and templates. If you are writing your first rule, start with project rules.

Map an unfamiliar area before you change it

Section titled “Map an unfamiliar area before you change it”

Before the first edit in code you did not write, ask for a read-only map with file paths. The map becomes the context for the next chat, and its file list becomes the scope you check the change against.

Check the answer against the code with two or three spot checks: open the files it names at the lines it names. A map with a wrong file path is a sign the agent guessed, and the fix is a narrower question, not a longer one.

Make a cross-package change in planned steps

Section titled “Make a cross-package change in planned steps”

A change that crosses packages fails when one chat tries to do it all. Plan it once, write the plan to a file, and execute one package per chat with a gate after each step.

  1. Switch the agent to Plan Mode, which creates an implementation plan before writing any code, and ask for a plan file. Review the plan, not code: the caller list and the step order are where mistakes are cheap to fix.

  2. Execute one step per chat. A new chat per package keeps earlier attempts out of the context, and the plan file carries the decisions forward.

  3. Commit after each green step. A small commit is the unit you can revert, and the next chat starts from a known-good tree.

  4. For long migrations that span many packages, run independent steps in parallel in separate worktrees, or hand them to Cloud Agents; see Cloud Agents and automations.

Whether the agent runs gate commands without asking depends on your run mode. Configure run modes and the sandbox as described in privacy and security for Cursor teams rather than approving each command by hand.

How do you verify a large-codebase change without reading every diff?

Section titled “How do you verify a large-codebase change without reading every diff?”

Prove three things mechanically, then spend human attention on the plan and on whatever the checks flag.

QuestionCheckPasses when
Did the change stay in scope?git diff --name-only compared with the plan’s file listEvery changed file is in the plan, or has a stated reason
Is it complete?An rg count of the old patternThe count is 0
Does every consumer still work?Typecheck and tests on changed packages and their dependentsGreen on all of them
#!/usr/bin/env bash
# verify-change.sh: run in a terminal at the repository root after the last step
set -euo pipefail
base=origin/main
echo "== Files changed (compare with docs/plans/user-organization-id.md)"
git diff --name-only "$base"...HEAD
echo "== Files changed outside the allowed packages (must be empty)"
outside=$(git diff --name-only "$base"...HEAD \
| grep -v -E '^(packages/(shared|api|web)/|docs/plans/)' || true)
echo "$outside"
test -z "$outside"
echo "== Imports from the deprecated module (must be 0)"
count=$( { rg -n "from ['\"].*legacy-billing" packages/ --type ts --glob '!packages/legacy-billing/**' || true; } | wc -l)
echo "$count"
test "$count" -eq 0
echo "== Typecheck and test every changed package and its dependents"
pnpm --filter "...[$base]" run typecheck
pnpm --filter "...[$base]" test

The ...[origin/main] filter selects packages changed since origin/main plus every package that depends on them, which is where a shared-type change breaks. Nx (nx affected -t typecheck test) and Turborepo (turbo run typecheck test --filter=...[origin/main]) do the same for their repositories. Swap the rg pattern for whatever the change removes: an old function name, a positional call style, a deprecated import.

Run the same script in CI so the gate does not depend on anyone remembering it. Then let Bugbot review the pull request for correctness, and route it through CODEOWNERS so each package owner signs off on their slice. The owner reads the plan and their package’s gate output, not every line. The wider review setup is in code review with Cursor.

Keep long sessions accurate in a large repository

Section titled “Keep long sessions accurate in a large repository”
  • One task per chat. Start a new chat when a step is committed, when you move to another package, or when the agent repeats a mistake you already corrected. Carry state in the plan file or a short handoff note, as shown in context management in Cursor.
  • Attach one canonical example, not a folder. @packages/api/src/routes/users/index.ts costs a few thousand tokens; mentioning all of packages/api/src/ can fill the window before the agent starts. Token management in Cursor shows how to measure the difference.
  • Keep rules in step with the code. A rule that describes last year’s architecture misleads the agent in every chat it loads into. Audit them after large refactors.

What breaks in a large codebase in Cursor, and how to recover

Section titled “What breaks in a large codebase in Cursor, and how to recover”

The agent copies a deprecated or generated module

Section titled “The agent copies a deprecated or generated module”

Search found the old module first. Recovery: restore the checkpoint from before the edit (see checkpoints and branching), add the path to .cursorignore or name it as deprecated in the root rule, and rerun the step in a new chat.

The agent writes a new helper instead of using the existing one

Section titled “The agent writes a new helper instead of using the existing one”

The convention exists only in people’s heads. Recovery: add the canonical file to the package’s glob-scoped rule, then rerun with that file attached. If it happens twice for the same helper, add a lint rule that bans the duplicate; lint fails in CI, a rule only asks.

The agent edits a similarly named file in another package

Section titled “The agent edits a similarly named file in another package”

packages/web/src/user.ts and packages/api/src/user.ts look the same to a search. Recovery: open the package or its workspace instead of the root, write full paths in the prompt, and let the scope check in verify-change.sh catch the stray file.

Indexing never finishes, or search is slow

Section titled “Indexing never finishes, or search is slow”

A large generated or binary file sits in a tracked directory. Recovery: find it with git ls-files -z | xargs -0 du -k | sort -rn | head -20, add it to .cursorignore, and follow the measurement steps in tuning Cursor for large codebases.

The file is saved as .md instead of .mdc, or its glob matches nothing. Recovery: rename the file, test the glob against git ls-files, and add the CI check from custom rules and templates.

The agent loses the thread halfway through a change

Section titled “The agent loses the thread halfway through a change”

The chat carries too many attempts. Recovery: stop, make sure the plan file marks what is done, commit what passes its gate, and continue from the first unfinished step in a new chat.

How Claude Code and Codex handle large repositories

Section titled “How Claude Code and Codex handle large repositories”

The scoping principles are the same across tools; the configuration is not. Claude Code scopes with the start directory, nested CLAUDE.md files and deny rules, covered in large codebases and monorepos in Claude Code. Codex keeps long sessions on track with AGENTS.md files and handoff files, covered in context management across Codex surfaces. For the cross-tool strategy, see strategies for million-line codebases and monorepo workflows with AI assistants.

Where to go next with large codebases in Cursor

Section titled “Where to go next with large codebases in Cursor”