Przejdź do głównej zawartości

Strategie dla dużych baz kodu

Bazy kodu powyżej mniej więcej 100 000 linii rozbijają domyślny przepływ pracy w Cursorze: indeksowanie zwalnia, agent nie mieści projektu w kontekście i zmyśla wzorce zamiast trzymać się istniejących. Lekarstwem są ostre wykluczenia w .cursorignore, reguły zapisujące to, czego kod nie pokazuje, prompty zawężone do kilku plików i krótkie rozmowy.

Dołączasz do zespołu przy monolicie w TypeScripcie na 300 000 linii. Indeksowanie w Cursorze trwa dwadzieścia minut. Prosisz o wyjaśnienie przepływu uwierzytelniania i dostajesz mglistą odpowiedź, która pomija połowę łańcucha middleware. Próbujesz trybu Agent przy nowej funkcji, a on pisze plik od zera, zamiast wpiąć się w warstwę serwisową trzy katalogi niżej. Tab podsuwa wzorce z modułu wycofanego dwa lata temu.

Narzędzie, które przyspieszało cię przy małych projektach, spowalnia cię przy tym, na którym zależy najbardziej. Nic z tego nie jest błędem Cursora — tak wygląda spotkanie skończonego okna kontekstu z bazą kodu, która się w nim nie mieści. Każda strategia poniżej sprowadza się do decyzji, co do tego okna wpuścić.

  • Strategię indeksowania, która trzyma Cursora szybkim przy bazach powyżej 500 tys. linii
  • Zarządzanie budżetem kontekstu, żeby agent nie tonął w nieistotnych plikach
  • Reguły zapisujące wiedzę dziedzinową, której AI nie wywnioskuje z samego kodu
  • Konfiguracje workspace’ów wielokatalogowych dla monorepo i układów wielorepozytoryjnych
  • Zawężone wyszukiwanie i nawigację, które znajdują właściwy kod w sekundy
  • Prompty do analizy architektury tych części bazy kodu, których nikt ci nie objaśnił

Przy pierwszym otwarciu dużego projektu Cursor indeksuje wszystko. Jeśli w node_modules leży 100 000 plików, to 100 000 plików zjada pamięć i procesor, zanim napiszesz linijkę kodu.

Utwórz albo uzupełnij .cursorignore w katalogu głównym projektu; działa jak .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

To skraca indeksowanie, utrzymuje okno kontekstu w czystości i powstrzymuje agenta przed cytowaniem kodu generowanego czy zewnętrznego tak, jakby był twój.

W monorepo z dwudziestoma pakietami otwarcie korzenia zmusza Cursora do zindeksowania wszystkich. Otwórz te, w których faktycznie pracujesz:

Okno terminala
# 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

Dla kombinacji, które powtarzasz, zapisz plik workspace’u:

// 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" }
]
}

Status sprawdzisz w Settings > Indexing and Docs. Cursor nie publikuje oficjalnych danych o czasie ani rozmiarze indeksu, więc traktuj poniższe jako zgrubną regułę z praktyki, a nie udokumentowane limity:

Rozmiar bazy koduOrientacyjny czas indeksowaniaOrientacyjny rozmiar indeksu
Poniżej 10 tys. plików1-3 minutyPoniżej 500 MB
10-50 tys. plików3-10 minut500 MB - 2 GB
50-200 tys. plików10-30 minut2-5 GB
Powyżej 200 tys. plikówWykluczaj agresywnieTrzymaj chudo przez wykluczenia

Jeśli indeksowanie trwa dramatycznie dłużej, wykluczenia wymagają pracy. Zwykli winowajcy to niewykluczone node_modules, duże pliki generowane i binarne zasoby leżące w drzewie źródeł.

Pomyśl, co powiedziałbyś nowej osobie pierwszego dnia. Dokładnie po to jest .cursor/rules/ — na wiedzę, która jest prawdziwa o projekcie, ale nie widać jej w żadnym pojedynczym pliku.

---
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.

To odwołanie @file na końcu zamienia opis w przykład, który agent może skopiować.

Reguły pasujące do wzorca pliku dołączają się automatycznie, gdy agent pracuje na pasujących plikach, więc konwencje backendu wczytują się tylko przy backendzie:

---
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

To ten mechanizm trzyma wzorce Reacta z dala od kodu Expressa, bez powtarzania reguły w każdym prompcie.

Każda rozmowa z agentem ma skończone okno kontekstu, a w dużej bazie kodu dobijasz do niego szybko. Traktuj kontekst jak pieniądze:

  • Budżet: około 200 tys. tokenów na rozmowę (udokumentowane domyślne okno kontekstu Cursora; Max Mode i część modeli je poszerza)
  • Koszt pliku: typowy plik TypeScriptu na 200 linii kosztuje mniej więcej 1000-2000 tokenów
  • Koszt katalogu: odwołanie do @src/ w projekcie na 500 plików wydaje cały budżet jednym ruchem

Dyscyplina polega na wskazaniu minimalnej liczby plików, których zadanie wymaga, i poszerzaniu dopiero wtedy, gdy agent zgłosi, że czegoś mu brakuje.

Przy modułach zbyt dużych, żeby wskazać je wprost, napisz streszczenie, które agent przeczyta w zamian:

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

Odwołanie do @src/payments/ARCHITECTURE.md kosztuje jakieś 2 tys. tokenów tam, gdzie @src/payments/ potrafi kosztować 50 tys., a konkretne pliki dociągasz dopiero wtedy, gdy wiesz, które mają znaczenie.

W projekcie, w którym siedzisz codziennie, ten sam trik warto wykonać raz na najwyższym poziomie:

W dużej bazie kodu agent potrzebuje pomocy w znalezieniu właściwych plików, a wskazanie czterech kosztuje znacznie mniej niż puszczenie go w poszukiwania:

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

Najważniejszy nawyk przy dużych bazach kodu: nigdy nie zlecaj Agentowi zbyt wiele naraz. Zamiast “dodaj kontrolę dostępu opartą na rolach” rozbij to na części:

  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)

Każdy krok jest na tyle mały, że agent mieści wszystkie istotne pliki, i każdy stoi na zacommitowanej, zweryfikowanej pracy poprzedniego.

Zakres można też podać jako granicę, a nie listę plików — po tę wersję sięgaj, gdy agent zaczyna się rozłazić:

Zanim zmienisz cokolwiek w nieznanym obszarze, zapytaj:

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.

Taka eksploracja zajmuje trzydzieści sekund w trybie Ask i oszczędza piętnaście minut ręcznego skakania po plikach.

W skali żadne z wyszukiwań samo nie wystarcza. Semantyczne znajduje kod powiązany pojęciowo, ale beztrosko zwraca wyniki z wycofanego modułu; tekstowe znajduje dokładne dopasowania, ale gubi funkcję robiącą to samo pod inną nazwą. Połącz je i wyklucz cmentarzysko wprost:

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/.

Gdy pytanie rozciąga się na wiele modułów, zamiast siedzieć w jednym, sięgnij po wyszukiwanie semantyczne w całym projekcie:

@codebase uruchamia wyszukiwanie semantyczne w całym projekcie; jeśli w twojej wersji nie ma go jako dosłownej wzmianki, to samo wyszukiwanie znajdziesz w menu @ pod przeszukiwaniem bazy kodu i katalogów. Jest wolniejsze od zawężonego i opłaca się tylko przy pytaniach naprawdę przekrojowych.

Przy wdrażaniu się zadaj te pytania po kolei i pozwól, by każda odpowiedź karmiła następne:

  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?”
NarzędzieNajlepsze doZakresSzybkość
TabSzybkich ręcznych edycji z podpowiedziami AIPozycja kursoraNatychmiast
Inline Edit (Cmd/Ctrl+K)Skupionych zmian w zaznaczonym kodzieJeden plikSzybko
Agent (Cmd/Ctrl+I)Funkcji i refaktorów obejmujących wiele plikówWiele plikówDokładnie

W dużych bazach kodu Tab i Inline Edit obsługują większość zmian. Są szybsze, bardziej przewidywalne i nigdy nie wymagają, by AI rozumiało cały projekt. Agenta zostaw na zmiany, które naprawdę rozciągają się na wiele plików.

Różne pakiety zwykle mają różne konwencje, a reguły mieszkają obok pakietu, którym rządzą:

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

Edycja pliku w packages/web/ podciąga reguły Reacta, edycja w packages/api/ — reguły Expressa.

Kiedy zmiana w jednym pakiecie wymusza poprawki w drugim, rozpisz łańcuch zamiast opisywać cel:

Zmiana nazwy wspólnego typu dotyka wszystkiego w dół strumienia. Podaj agentowi komendę weryfikującą jako część zadania:

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.

Przy włączonym Auto-Run (Settings -> Cursor Settings -> Agents) agent zmienia nazwę, uruchamia sprawdzenie typów, widzi pozostałe zerwane odwołania i poprawia je iteracyjnie — o ile pnpm run typecheck jest na Command Allowlist albo działa tryb piaskownicy.

  • Zamykaj pliki, których nie edytujesz. Cursor traktuje otwarte pliki jako kontekst wysokiego priorytetu, więc trzydzieści zapomnianych kart psuje jakość podpowiedzi Taba, spowalnia agenta i zjada pamięć. Zamiast trzymać plik otwarty w nieskończoność, skacz do niego przez Cmd+P.
  • Max Mode włączaj wybiórczo. Poszerzony kontekst kosztuje więcej. Zarabia na siebie przy pojedynczym pliku powyżej 3000 linii, łańcuchu zależności przez dziesięć plików albo planowaniu zmiany architektonicznej wymagającej całej struktury modułu na widoku. Do wdrażania funkcji, poprawiania błędów i pisania testów wystarcza tryb standardowy.
  • Często zaczynaj nowe rozmowy. Długie wątki zbierają nieświeży kontekst i agent zaczyna odwoływać się do kodu, którego już nie ma. Zaczynaj od nowa, gdy przenosisz się w inną część bazy kodu, gdy zacommitowałeś pakiet zmian albo po pięciu-sześciu wymianach.

Indeksowanie staje na jakimś procencie i nie rusza. Zwykle blokuje je jeden duży plik — wynik codegenu GraphQL, klient Prismy, skompilowany zasób, który prześlizgnął się przez wykluczenia. Dopisz go do .cursorignore i przeładuj indeks.

Agent tworzy pliki od zera, zamiast wpiąć się w istniejące wzorce. Reguły są zbyt ogólne. Dodaj regułę opisującą strukturę plików i wskazującą wzorcowy przykład przez wzmiankę @file.

Agent odpowiada o niewłaściwej części bazy kodu. W monorepo myli podobnie nazwane pliki z różnych pakietów. Nazwij pakiet wprost: “@packages/api/src/users.ts — not the one in packages/web.”

Agent gubi wątek w połowie zmiany. Zmiana jest za duża na jeden prompt. Rozbij ją na mniejsze, commitowane kroki.

Agent trzyma się niewłaściwych konwencji. Utwórz reguły z globem, które podpinają się same dla danego katalogu — reguła backendowa wczytywana na src/api/**/*.ts robi robotę, której żaden prompt nie musi powtarzać.

Podpowiedzi Taba zwalniają. Pozamykaj karty, sprawdź, czy wykluczenia faktycznie działają, i upewnij się, że nie wrócił żaden katalog z plikami generowanymi. Opóźnienie Taba jest proporcjonalne do kontekstu, który musi przetworzyć.

Jakość odpowiedzi spada w połowie rozmowy. Kontekst się nasycił. Zacznij nową rozmowę — w dużych bazach kodu sesje powinny być krótsze i bardziej skupione niż w małych projektach.