Skip to content

Large Codebase Strategies

Large codebases past roughly 100,000 lines break the default Cursor workflow: indexing slows, the agent cannot hold the project in context, and it invents patterns instead of matching existing ones. The fixes are aggressive .cursorignore exclusions, rules that encode what code cannot show, prompts scoped to a handful of files, and short conversations.

You joined a team working on a 300,000-line TypeScript monolith. Cursor’s indexing takes twenty minutes. You ask it to explain the authentication flow and get a vague answer that misses half the middleware chain. You try Agent mode for a feature and it writes a new file from scratch instead of integrating with the service layer three directories down. Tab keeps suggesting patterns from a module that was deprecated two years ago.

The tool that made you faster on small projects is now slowing you down on the project that matters. Nothing here is a Cursor bug — it is what happens when a finite context window meets a codebase that does not fit in it, and every strategy below is about deciding what goes into that window.

  • An indexing strategy that keeps Cursor fast on codebases over 500k lines
  • Context budget management that stops the agent drowning in irrelevant files
  • Rules that encode the domain knowledge the AI cannot infer from code alone
  • Multi-root workspace configurations for monorepos and multi-repo setups
  • Scoped search and navigation that finds the right code in seconds
  • Architectural analysis prompts for the parts of the codebase nobody explained to you

Getting indexing right before anything else

Section titled “Getting indexing right before anything else”

The first time you open a large project, Cursor indexes everything. If node_modules holds 100,000 files, that is 100,000 files consuming memory and CPU before you write a line of code.

Create or update .cursorignore in the project root; it works like .gitignore:

# Build artifacts
dist/
build/
.next/
out/
coverage/
# Dependencies
node_modules/
vendor/
.pnp/
# Generated code
*.generated.ts
*.generated.js
__generated__/
prisma/generated/
graphql/generated/
# Large binary files
*.wasm
*.map
*.min.js
*.min.css
*.bundle.js
# Lock files (huge, rarely useful for AI)
package-lock.json
pnpm-lock.yaml
yarn.lock

This cuts indexing time, keeps the context window clean, and stops the agent citing generated or vendored code as if it were yours.

In a monorepo with twenty packages, opening the root forces Cursor to index all of them. Open the ones you are actually working in:

Terminal window
# Instead of:
cursor ~/projects/big-monorepo
# Open specific packages as a multi-root workspace:
cursor ~/projects/big-monorepo/packages/web \
~/projects/big-monorepo/packages/api \
~/projects/big-monorepo/packages/shared

For combinations you use repeatedly, save a workspace file:

// web-api.code-workspace
{
"folders": [
{ "path": "packages/web", "name": "Web App" },
{ "path": "packages/api", "name": "API Server" },
{ "path": "packages/shared", "name": "Shared Types" },
{ "path": "packages/config", "name": "Config" }
]
}

Check the status in Settings > Indexing and Docs. Cursor does not publish official index-time or index-size figures, so treat this as a rough rule of thumb from field experience, not as documented limits:

Codebase SizeRough Index TimeRough Index Size
Under 10k files1-3 minutesUnder 500 MB
10k-50k files3-10 minutes500 MB - 2 GB
50k-200k files10-30 minutes2-5 GB
Over 200k filesUse exclusions aggressivelyKeep it lean with exclusions

If indexing takes dramatically longer than that, the exclusions need work. The usual culprits are node_modules that never got excluded, large generated files, and binary assets sitting in the source tree.

Encoding what the AI cannot infer from code

Section titled “Encoding what the AI cannot infer from code”

Think about what you would tell a new hire on their first day. That same context is what .cursor/rules/ is for — the knowledge that is true about the project but not visible in any single file.

A rule that describes how work gets done here

Section titled “A rule that describes how work gets done here”
---
description: Add a new API endpoint
alwaysApply: false
---
When creating new API endpoints:
1. Define the route in src/routes/[domain]/index.ts
2. Create the controller in src/controllers/[domain]/
3. Add validation schemas in src/schemas/[domain].ts using Zod
4. Register the route in src/app.ts under the appropriate middleware group
5. All endpoints must use the withAuth middleware unless explicitly public
6. Error responses use our standard format: { error: string, code: string, details?: unknown }
See @src/routes/users/index.ts for a complete example.

The @file reference at the end is what turns a description into an example the agent can copy.

Rules matching a file pattern are included automatically when the agent works on matching files, so backend conventions load only for backend work:

---
globs: "src/api/**/*.ts"
---
Backend API conventions:
- All handlers receive (req: Request, res: Response, next: NextFunction)
- Use the logger from @src/lib/logger, never console.log
- Database queries go through the repository layer, never raw SQL in handlers
- All mutations must be wrapped in transactions

This is the mechanism that keeps React patterns out of your Express code without anyone repeating the rule in every prompt.

Every agent conversation has a finite context window, and in a large codebase you hit it fast. Treat context as money:

  • Budget: around 200k tokens per conversation (Cursor’s documented default context window; Max Mode and some models extend it)
  • Cost per file: a typical 200-line TypeScript file costs roughly 1,000-2,000 tokens
  • Cost per directory: referencing @src/ on a 500-file project spends the entire budget in one move

The discipline is to reference the minimum number of files the task needs, then expand only when the agent says it is missing something.

For modules too large to reference directly, write a summary the agent can read instead:

src/payments/ARCHITECTURE.md
# Payments Module Architecture
## Key Files
- `service.ts` - Core payment processing (Stripe integration)
- `webhook.ts` - Webhook handlers for payment events
- `types.ts` - TypeScript interfaces for payment entities
- `repository.ts` - Database operations (PostgreSQL via Drizzle)
## Data Flow
1. Client calls POST /api/payments/checkout
2. service.ts creates Stripe session
3. Stripe sends webhook to webhook.ts
4. webhook.ts updates payment status via repository.ts
5. WebSocket notifies client of status change
## Key Constraints
- All amounts in cents (integer, never float)
- Idempotency keys required for all Stripe calls
- Webhook verification must happen before processing

Referencing @src/payments/ARCHITECTURE.md costs around 2k tokens where @src/payments/ might cost 50k, and you pull in the specific files only once you know which ones matter.

For the project you work in every day, the same trick is worth doing once at the top level:

In a large codebase the agent needs help finding the right files, and naming four of them costs far less than letting it search:

Add a new user notification preferences endpoint.
Follow the patterns in:
- @src/routes/users/index.ts (route definition)
- @src/controllers/users/profile.ts (controller pattern)
- @src/schemas/user.ts (validation schema)
- @src/services/user-service.ts (service layer)
The new endpoint should be PATCH /api/users/notification-preferences

The single most important habit for large codebases: never ask Agent to do too much at once. Instead of “add role-based access control to the application,” break it down:

  1. “Add a role column to the users table and create the migration” (1-2 files)
  2. “Create a withRole middleware that checks user roles, following the pattern in @src/middleware/auth.ts” (1-2 files)
  3. “Apply the withRole('admin') middleware to the admin routes in @src/routes/admin/index.ts” (1 file)
  4. “Add tests for the role middleware in @src/middleware/tests/role.test.ts” (1 file)

Each step is small enough for the agent to hold every relevant file, and each one builds on committed, verified work.

The scope can also be stated as a boundary rather than a file list, which is the version to reach for when the agent has been wandering:

Before changing anything in an unfamiliar area, ask:

Trace the request lifecycle for POST /api/orders/create.
Start from the route definition, through all middleware, into the controller,
through the service layer, and into the database queries. List every file
in the call chain and explain what each one does.
Include error handling paths -- what happens when validation fails,
when the database is unavailable, and when the payment provider rejects.

That exploration takes thirty seconds in Ask mode and saves fifteen minutes of manual file navigation.

Neither search alone is enough at scale. Semantic search finds conceptually related code but happily returns results from a deprecated module; text search finds exact matches but misses the function that does the same thing under a different name. Combine them and exclude the graveyard explicitly:

Find all code that handles subscription renewal. Use semantic search
to identify the relevant modules, then grep for specific function calls
to 'renewSubscription', 'processRenewal', and 'handleRenewalWebhook'.
Show me results only from the active codebase -- ignore anything in
src/legacy/ or src/deprecated/.

When the question spans modules rather than sitting inside one, reach for project-wide semantic search:

@codebase runs project-wide semantic search; if it is not available as a literal mention in your build, the same search is reachable through the @ menu’s codebase and folder search. It is slower than a scoped search and worth it only for genuinely cross-cutting questions.

When onboarding, ask these in order and let each answer inform the next:

  1. “What are the top-level directories and what is each one responsible for?”
  2. “Show me the main entry points — where do HTTP requests arrive, and where do scheduled jobs start?”
  3. “What are the core domain models and how do they relate to each other?”
  4. “What external services does this application depend on? (databases, APIs, message queues)”
  5. “Where are the most complex parts of the codebase? Which files have the most dependencies?”
ToolBest ForScopeSpeed
TabQuick manual edits with AI suggestionsSingle cursor positionInstant
Inline Edit (Cmd/Ctrl+K)Focused changes to selected codeSingle fileFast
Agent (Cmd/Ctrl+I)Multi-file features and refactoringMultiple filesThorough

In large codebases Tab and Inline Edit handle most of your changes. They are faster, more predictable, and never require the AI to understand the whole project. Save Agent for changes that genuinely span files.

Different packages usually have different conventions, and rules live next to the package they govern:

packages/web/.cursor/rules/web.md # React conventions
packages/api/.cursor/rules/api.md # Express conventions
packages/shared/.cursor/rules/shared.md # Pure TypeScript rules
packages/mobile/.cursor/rules/mobile.md # React Native conventions

Editing a file under packages/web/ picks up the React rules; editing packages/api/ picks up the Express ones.

When a change in one package forces updates in another, spell out the chain rather than describing the goal:

Refactors with a monorepo-wide blast radius

Section titled “Refactors with a monorepo-wide blast radius”

Renaming a shared type touches everything downstream. Give the agent the verification command as part of the task:

Rename the "UserRole" type to "AccountRole" in @packages/shared/src/types.ts.
Then find and update every import and usage across all packages. Run
pnpm run typecheck at the monorepo root after the rename to verify
there are no broken references.

With Auto-Run enabled (Settings -> Cursor Settings -> Agents), the agent does the rename, runs the typecheck, sees the remaining broken references, and fixes them iteratively — as long as pnpm run typecheck is on the Command Allowlist or sandbox mode is on.

  • Close files you are not editing. Cursor treats open files as high-priority context, so thirty stale tabs degrade Tab prediction quality, slow agent responses, and hold memory. Use Cmd+P to jump to a file instead of keeping it open forever.
  • Use Max Mode selectively. Extended context costs more. It earns its price for a single file over 3,000 lines, a dependency chain spanning ten files, or planning an architectural change that needs the whole module structure in view. For implementing features, fixing bugs, and writing tests, standard mode is enough.
  • Start new chats often. Long conversations accumulate stale context and the agent starts referencing code that no longer exists. Start fresh when you move to a different part of the codebase, when you have committed a set of changes, or after five or six exchanges.

Indexing stalls at a percentage and never finishes. Usually one large file is blocking the indexer — a GraphQL codegen output, a Prisma client, a compiled asset that slipped past your exclusions. Add it to .cursorignore and restart the index.

The agent creates files from scratch instead of integrating with existing patterns. The rules are not detailed enough. Add a rule that describes the file structure and points at a canonical example with an @file mention.

The agent answers about the wrong part of the codebase. In a monorepo it confuses similarly named files across packages. Name the package explicitly: “@packages/api/src/users.ts — not the one in packages/web.”

The agent loses the thread partway through a change. The change is too big for one prompt. Break it into smaller committed steps.

The agent follows the wrong conventions. Create glob-scoped rules that auto-attach for the directory in question, so a backend rule loading on src/api/**/*.ts does the work no prompt has to repeat.

Tab suggestions get slow. Close tabs, verify the exclusions are actually applying, and check that no generated directory crept back in. Tab’s latency is proportional to the context it has to process.

Response quality drops mid-conversation. Context saturation. Start a new conversation — in large codebases, sessions should be shorter and more focused than on small projects.