Przejdź do głównej zawartości

Projektowanie architektury systemu z agentem Cursora

Projektowanie architektury z agentem Cursora to pętla z czterech części: Plan Mode bada kod i porównuje opcje, zanim powstanie jakakolwiek linia, człowiek zapisuje decyzję jako Architecture Decision Record (ADR), reguła projektu przekazuje tę decyzję agentowi, a testy fitness w CI dowodzą, że każda późniejsza zmiana nadal jej przestrzega.

Twój produkt SaaS właśnie podpisał umowę z pierwszym klientem korporacyjnym. Umowa wymaga izolacji danych tenantów, SSO, ról i logu audytowego, a twój system to jedna aplikacja Next.js na jednej bazie PostgreSQL, z tabelą users i bez pojęcia tenanta. Wybór modelu multi-tenancy, którego dokonasz w tym miesiącu, będzie kształtował każde zapytanie, migrację i incydent przez lata, a po decyzji większość kodu napisze agent. Ta strona jest dla developera albo tech leada, który odpowiada za tę decyzję i nie chce czytać 50 migracji linia po linii, żeby wiedzieć, że została wdrożona.

Strona dotyczy linii Cursor 3.x, sprawdzonej 26 września 2026. Plan Mode, Rules i Hooks zweryfikowano na cursor.com 28 sierpnia 2026; 26 września 2026 cursor.com był niedostępny, więc nazwy menu potwierdź w swojej wersji. dependency-cruiser 18.4.0 sprawdzono przez npm view, a Postgres MCP Pro 0.3.0 na PyPI, 26 września 2026.

  • Plik z ograniczeniami, który agent czyta najpierw, więc jego propozycje pasują do twojego zespołu i budżetu, a nie do wyobrażonej firmy.
  • Cztery prompty do skopiowania: rozpoznanie, porównanie opcji, ADR i plan fazowy, w którym każda faza trafia na produkcję i sama dowodzi swojej poprawności.
  • ADR z sekcją „How we verify this holds”, zatwierdzony przez konkretną osobę.
  • Regułę projektu, która opisuje decyzję, oraz maszynowe testy, które ją egzekwują: regułę zależności, dwa zapytania SQL do schematu i test izolacji tenantów.
  • Kroki naprawcze dla sześciu typowych awarii tej pętli.

Metodę niezależną od narzędzia, czyli krótkie ADR-y plus ograniczenia sprawdzalne maszynowo, opisuje strona decyzje architektoniczne, których agenci przestrzegają. Projekt na poziomie schematu, czyli klucze, indeksy i normalizacja, należy do strony projektowanie bazy danych w Cursorze. Ta strona to procedura w Cursorze dla jednej decyzji systemowej: od pytania do egzekwowania.

Jak przeprowadzić decyzję architektoniczną przez Cursora?

Dział zatytułowany „Jak przeprowadzić decyzję architektoniczną przez Cursora?”

Kolejność jest ważniejsza niż którykolwiek pojedynczy prompt. Zdecyduj, zanim agent zacznie budować, i zbuduj testy, zanim agent napisze większość kodu.

  1. Spisz ograniczenia w docs/architecture/constraints.md: wielkość zespołu, budżet, obowiązki compliance, SLO i wszystko, co już zdecydowano. Agent nie wywnioskuje, że masz trzech inżynierów i nie masz dyżurów.
  2. Zbadaj system w Plan Mode. Plan Mode „creates detailed implementation plans before writing any code” (cursor.com/docs/agent/plan-mode, zweryfikowano 28 sierpnia 2026), więc agent bada kod i zadaje pytania, zanim napisze jakikolwiek kod.
  3. Porównaj opcje w wymiarach, które wskazują twoje ograniczenia, z dowodem z kodu dla każdego twierdzenia.
  4. Zdecyduj i zapisz. Opcję wybiera człowiek. Agent przygotowuje szkic ADR, a ty go poprawiasz, aż mówi dokładnie to, co masz na myśli.
  5. Zakoduj i egzekwuj. Napisz regułę projektu i testy fitness, a testy uruchamiaj w CI jeszcze przed rozpoczęciem implementacji.
  6. Rozplanuj implementację w fazach, z których każda wdraża się samodzielnie i kończy się nazwanym dowodem.

Każdy krok zaczynaj w nowym czacie. Świeży kontekst nie jest zakotwiczony w rozumowaniu z poprzedniego kroku, a stan przenoszą pliki na dysku.

Propozycja jest tak dobra jak ograniczenia, pod które ją zoptymalizowano. Bez nich agent zwykle poleca to, co pasuje do dużego zespołu platformowego: bazę na tenanta, service mesh, szynę zdarzeń. Napisz krótki plik i dołączaj go do każdego promptu architektonicznego:

docs/architecture/constraints.md
# Architecture constraints (owner: Anna Kowalska, reviewed 2026-09-26)
- Team: 3 backend engineers, no dedicated ops; on-call is best effort.
- Stack: Next.js App Router, PostgreSQL 17 on a managed service, Drizzle ORM.
- Scale today: ~500 users in 20 organisations; target 1,000 tenants in 18 months.
- Contract duties: tenant data isolation, SAML 2.0 and OIDC SSO, audit log of all writes, 99.9% monthly uptime.
- Budget: one additional managed database at most; no Kubernetes.
- Already decided: stay on PostgreSQL; no rewrite of the Next.js app.

Przełącz agenta w Plan Mode i wklej prompt rozpoznania. Celem tego kroku są pytania, a nie odpowiedź: agent ma znaleźć to, czego mu nie powiedziałeś.

Pliki migracji opisują schemat, który miał powstać. Jeśli agent ma zobaczyć także ten, który naprawdę istnieje, razem z ograniczeniami i politykami dodanymi ręcznie, podłącz do kopii stagingowej serwer MCP bazy danych w trybie tylko do odczytu. Postgres MCP Pro (PyPI postgres-mcp, uruchamiany przez uvx postgres-mcp --access-mode=restricted) ogranicza agenta do transakcji tylko do odczytu. Daj mu rolę, która może czytać katalog systemowy i nic więcej, a connection string trzymaj w globalnej konfiguracji MCP Cursora, nie w repozytorium. Konfigurację dla wszystkich trzech narzędzi opisuje strona serwery MCP dla baz danych.

Odpowiedz na pytania w czacie, a potem zapisz inwentaryzację i swoje odpowiedzi w docs/architecture/multi-tenancy-research.md. Sprawdź samodzielnie trzy z cytowanych ścieżek plików. Jeśli choć jedna jest błędna, inwentaryzacja jest niewiarygodna i przed pójściem dalej uruchom prompt ponownie z węższym zakresem @.

„Row-level security jest prostsze” to opinia. „Row-level security zmienia 38 plików, oto ich lista” to twierdzenie, które da się sprawdzić. Proś o ten drugi rodzaj.

Czytaj tabelę w świetle swoich ograniczeń, a nie tonu agenta. Dobry nawyk: wypełnij małą tabelę decyzyjną samodzielnie, zanim przeczytasz rekomendację agenta, jeśli o nią prosiłeś:

Kryterium (z constraints.md)WagaA: RLSB: schemat na tenantaC: baza na tenanta
Izolacja spełnia zapis umowyWymagane
Do utrzymania przez 3 osoby bez opsówWymagane
Nakład migracji3
Odtwarzanie pojedynczego tenanta2
Koszt przy 1000 tenantów2

Tego samego promptu użyj przy następnej decyzji (SSO: budować na bibliotece czy kupić dostawcę; co dzieje się z istniejącymi użytkownikami z hasłem, gdy ich organizacja włączy SSO). Jedna decyzja na czat utrzymuje każde porównanie w ryzach.

Krok 4: Zapisz decyzję jako ADR podpisany przez człowieka

Dział zatytułowany „Krok 4: Zapisz decyzję jako ADR podpisany przez człowieka”

Agent pisze szkic; decyduje konkretna osoba. ADR jest krótki, a jego najważniejsza sekcja to ta, którą większość szablonów pomija: skąd zespół będzie wiedział, że decyzja nadal obowiązuje.

W tym kroku wróć do trybu Agent — powstaje jeden plik.

Poprawiaj szkic, aż każda linia będzie prawdziwa, a potem zmień status na „Accepted” w pull requeście, który zatwierdza właściciel decyzji. Od tej chwili ADR zmienia się tylko przez kolejny pull request; strona ochrona wyroczni pokazuje, jak nie pozwolić agentowi edytować takich plików.

Są dwie warstwy i tylko druga daje gwarancję. Reguła projektu to instrukcja, którą agent czyta; test to coś, z czym agent nie może dyskutować.

Utwórz regułę architektoniczną, która obowiązuje w każdej sesji. Położenie pliku, zakresy i sposób sprawdzenia, czy reguła jest aktywna, opisuje strona reguły projektu w Cursorze. Treść trzymaj krótko i odsyłaj do ADR-ów zamiast je powtarzać:

# Architecture (source of truth: docs/adr/)
- Multi-tenancy (ADR-0003): every tenant-owned table has tenant_id NOT NULL with RLS
enabled and forced. New tables need a policy in the same migration. Global tables
are listed in the ADR; do not add one without a new ADR.
- Data access: route handlers in src/app/api/ call functions in src/repositories/;
they never import src/db/ directly.
- Every write goes through a repository function that also writes an audit_log row.
- Before you finish, run `npm run fitness` and `npm test`, and report their output.
- If a task seems to need a change to these rules, stop and say so. Do not work around them.

Napisz testy teraz, póki kod jest mały, a decyzja świeża, i pozwól im padać na obecnym kodzie tam, gdzie jeszcze nie spełnia decyzji. Ten ADR pokrywają trzy testy.

Warstwy, z dependency-cruiser. Jedna reguła z pełnego zestawu na stronie funkcje fitness:

// .dependency-cruiser.cjs (one rule of the full set)
module.exports = {
forbidden: [
{
name: 'handlers-use-repositories',
severity: 'error',
comment: 'ADR-0003: route handlers call src/repositories/, never src/db/ directly.',
from: { path: '^src/app/api/' },
to: { path: '^src/db/' },
},
],
};

Do analizy przypnij typescript@~6.0 jako zależność deweloperską. dependency-cruiser 18.4.0 obsługuje tylko TypeScript poniżej 7.0.0; z zainstalowanym TypeScript 7 analizuje zero modułów i mimo to kończy się kodem 0. Strona o funkcjach fitness zawiera zadanie kanarkowe, które oblewa build, gdy test przestaje cokolwiek widzieć.

Niezmienniki schematu jako SQL, który musi zwrócić zero wierszy. Zapisz oba zapytania jako checks/schema-invariants.sql i uruchamiaj je w CI na bazie zmigrowanej od zera:

-- Tenant-owned tables without RLS enabled and forced
SELECT c.relname
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r'
AND c.relname NOT IN ('plans', 'feature_flags', 'schema_migrations') -- ADR-0003 exceptions
AND NOT (c.relrowsecurity AND c.relforcerowsecurity);
-- Tenant-owned tables without a tenant_id column
SELECT t.table_name
FROM information_schema.tables t
WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE'
AND t.table_name NOT IN ('plans', 'feature_flags', 'schema_migrations')
AND NOT EXISTS (
SELECT 1 FROM information_schema.columns c
WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name
AND c.column_name = 'tenant_id');

Zachowanie: test izolacji tenantów. Zapytania SQL dowodzą, że polityki istnieją; ten test dowodzi, że działają. Każda tabela należąca do tenantów dostaje politykę, która porównuje tenant_id z ustawieniem połączenia. Dzięki drugiemu argumentowi true połączenie, które nie ustawiło tenanta, nie widzi żadnych wierszy, zamiast dostać błąd:

-- In the migration that creates each tenant-owned table (orders shown)
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);

Test ładuje dane dwóch tenantów, łączy się jako app_user z kontekstem tenanta A i pyta każdą tabelę należącą do tenantów o wiersze tenanta B. Sprawdza też rolę i to, że tenant A widzi własne dane, więc źle skonfigurowane połączenie ani puste dane testowe nie dadzą zielonego wyniku:

tests/tenant-isolation.test.ts
import { Client } from 'pg';
import { afterAll, beforeAll, expect, it } from 'vitest';
const TENANT_OWNED = ['orders', 'invoices', 'audit_log']; // every table outside the ADR exceptions
const A = process.env.TENANT_A_ID!; // both tenants seeded before the run
const B = process.env.TENANT_B_ID!;
const db = new Client({ connectionString: process.env.APP_DATABASE_URL }); // logs in as app_user
beforeAll(async () => {
await db.connect();
await db.query("SELECT set_config('app.tenant_id', $1, false)", [A]);
});
afterAll(() => db.end());
it('runs as the application role, not the owner', async () => {
const { rows } = await db.query('SELECT current_user');
expect(rows[0].current_user).toBe('app_user');
});
it('sees its own rows, so the check is not vacuous', async () => {
const { rows } = await db.query('SELECT count(*)::int AS n FROM orders');
expect(rows[0].n).toBeGreaterThan(0);
});
it.each(TENANT_OWNED)('%s returns no rows of tenant B', async (table) => {
const { rows } = await db.query(`SELECT count(*)::int AS n FROM ${table} WHERE tenant_id = $1`, [B]);
expect(rows[0].n).toBe(0);
});

Jedno polecenie dla agenta i CI. Reguła projektu i opisany niżej hook wywołują npm run fitness, które uruchamia regułę warstw i zapytania o schemat. psql kończy się kodem 0 także wtedy, gdy zapytanie zwraca wiersze, więc skrypt zapisuje wiersze do pliku i pada, jeśli plik nie jest pusty:

{
"scripts": {
"fitness": "depcruise src --config .dependency-cruiser.cjs && psql \"$DATABASE_URL\" -tAq -v ON_ERROR_STOP=1 -f checks/schema-invariants.sql -o .fitness-violations.txt && test ! -s .fitness-violations.txt"
}
}

Test izolacji działa w ramach npm test, którego reguła też wymaga.

Hooki Cursora działają „before or after defined stages of the agent loop and can observe, block, or modify behavior” (cursor.com/docs/hooks, zweryfikowano 28 sierpnia 2026), więc możesz też uruchamiać npm run fitness, gdy agent kończy pracę. Traktuj to jako szybką informację zwrotną dla agenta. Bramką jest CI, bo działa niezależnie od tego, czy hook jest zainstalowany.

Krok 6: Zamień ADR na fazy, z których każda sama się dowodzi

Dział zatytułowany „Krok 6: Zamień ADR na fazy, z których każda sama się dowodzi”

Duży plan zawodzi w środku, gdzie nic nie działa i nic nie jest zweryfikowane. Proś o fazy, które wdrażają się samodzielnie i kończą dowodem, który sprawdzisz bez czytania diffa.

W tym kroku wróć do trybu Agent — powstaje jeden plik.

Zatwierdź plan, a potem uruchamiaj każdą fazę jako osobne zadanie agenta w nowym czacie, który odwołuje się do planu i ADR. Jak wycofać się lokalnie z fazy, która poszła źle, opisuje strona checkpointy i rozgałęzianie.

Jak zweryfikować architekturę bez czytania każdego diffa?

Dział zatytułowany „Jak zweryfikować architekturę bez czytania każdego diffa?”

Decyzję i testy czytasz uważnie raz. Potem każdy pull request oceniasz na podstawie dowodów:

Co musisz wiedziećDowódKto zatwierdza
Czy decyzja jest właściwa?ADR z opcjami, konsekwencjami i sekcją weryfikacjiWłaściciel decyzji, raz
Czy kod nadal trzyma się warstw?dependency-cruiser w CI, zero nowych naruszeńCI
Czy każda tabela ma izolację?Oba zapytania SQL zwracają zero wierszyCI
Czy izolacja naprawdę działa?Test izolacji tenantów uruchomiony jako rola aplikacjiCI
Czy faza spełniła kryteria akceptacji?Dowody wymienione dla tej fazy w planieRecenzent, względem planu
Czy coś prześlizgnęło się obok testów?Bugbot lub inny agent recenzujący jako dodatkowy recenzent, nie bramkaRecenzent czyta jego uwagi

Ludzka recenzja skupia się wtedy na migracjach, nowych politykach i zmianach listy wyjątków, bo tam jedna błędna linia oznacza wyciek danych. Konfigurację Bugbota i kierowanie recenzji opisuje strona code review w Cursorze.

Co psuje się przy projektowaniu architektury z agentem Cursora?

Dział zatytułowany „Co psuje się przy projektowaniu architektury z agentem Cursora?”

Propozycja pasuje do firmy dziesięć razy większej. Objaw: baza na tenanta, szyna komunikatów i nowy serwis do logu audytowego dla zespołu trzech osób. Naprawa: sprawdź, czy constraints.md był w prompcie, dodaj kolumnę „the first thing that breaks for a team of three” do porównania i poproś agenta o usunięcie każdego komponentu, którego nie wymagają twoje ograniczenia.

Porównanie jest pewne siebie i błędne co do twojego kodu. Objaw: cytowany plik nie istnieje albo liczba zmian mocno odbiega od rzeczywistości. Naprawa: sprawdź trzy cytaty; jeśli któryś się nie zgadza, odrzuć tabelę, zawęź zakres @ do istotnych katalogów i uruchom prompt porównania ponownie w nowym czacie.

Agent ignoruje regułę architektoniczną. Objaw: nowy handler trasy importuje bezpośrednio src/db/. Reguły najpewniej nie było w kontekście: sprawdź listę aktywnych reguł, jak opisuje strona reguły projektu w Cursorze. Test dependency-cruiser i tak obleje build; wklej jego wynik do czatu i poproś o poprawkę, która zachowuje regułę.

Agent zmienia test zamiast kodu. Objaw: nowa pozycja na liście wyjątków SQL, severity reguły obniżone do warn albo nowy wpis w bazowej liście znanych naruszeń. Naprawa: przywróć te pliki, zacznij zadanie od nowa i dodaj konfigurację testów fitness oraz ADR do ścieżek wymagających zgody właściciela, jak pokazuje strona ochrona wyroczni.

ADR się dezaktualizuje. Objaw: reguła mówi o RLS, a późniejsza migracja przeniosła jedną domenę do osobnej bazy. Nieaktualna reguła jest gorsza niż brak reguły, bo pcha agenta w stronę starego projektu. Naprawa: zastąp ADR nowym w tym samym pull requeście co zmianę i zaktualizuj razem regułę oraz testy.

Faza trzecia z pięciu jest w połowie i nic nie działa. Plan napisano warstwami (najpierw wszystkie migracje, potem wszystkie zapytania) zamiast wdrażalnymi wycinkami. Naprawa: zatrzymaj się, uruchom ponownie prompt planu z powyższymi zasadami faz i potnij pozostałą pracę tak, żeby każda faza kończyła się na zielono w CI i na produkcji.