Decyzje architektoniczne, których agenci przestrzegają
Decyzja architektoniczna, której agent przestrzega, to jednostronicowy rekord decyzji architektonicznej (ADR) w repozytorium, którego ograniczenia są regułami MUSI albo NIE WOLNO, a każda wskazuje sprawdzenie, które ją egzekwuje. Agent czyta ADR przed edycją kodu, którego ten dotyczy, CI pada po złamaniu ograniczenia, a decyzję przyjmuje lub zastępuje tylko wskazany człowiek.
W sierpniu zespół ustalił, że każde wychodzące wywołanie HTTP przechodzi przez jeden wrapper z timeoutami i ponowieniami. Decyzja leży na stronie w wiki. We wrześniu jeden agent dodaje axios, żeby naprawić niestabilną integrację, drugi woła fetch prosto z warstwy domeny, a oba pull requesty przechodzą testy. Nikt nie złamał reguły, którą agent mógł zobaczyć, więc żadne review tego nie wyłapało. Ta strona jest dla tech leada, który odpowiada za takie decyzje, i dla programistów, których agenci mają ich przestrzegać.
Co zyskujesz dzięki ADR-om, których agenci przestrzegają
Dział zatytułowany „Co zyskujesz dzięki ADR-om, których agenci przestrzegają”- Szablon ADR z sekcją
Constraints, w której każda reguła wskazuje swoje sprawdzenie albo jest oznaczona do przeglądu przez człowieka. - Tabelę decyzyjną: które wybory zasługują na ADR, którym wystarczy reguła lintera, a którym nic z tych rzeczy.
- Przetestowane reguły dla TypeScriptu, Pythona i Javy, których komunikat błędu cytuje ADR, więc agent czyta powód, a nie tylko regułę.
adr-lint, 43-liniowy skrypt, który wywraca CI, gdy przyjęty ADR wskazuje nieistniejące sprawdzenie albo reguła powołuje się na zastąpiony ADR.- Konfigurację dla każdego narzędzia, która podsuwa właściwy ADR Claude Code, Codeksowi i Cursorowi, oraz regułę „zatrzymaj się i zaproponuj” dla decyzji, których nie obejmuje żaden ADR.
- Cztery prompty do skopiowania: szkic ADR, sprawdzenie planu względem ADR-ów, odtworzenie niezapisanych decyzji z istniejącego kodu i zastąpienie decyzji.
Dlaczego agenci rozmywają decyzje architektoniczne?
Dział zatytułowany „Dlaczego agenci rozmywają decyzje architektoniczne?”Decyzja wymyka się przez trzy luki i żadnej z nich nie zamknie lepszy prompt.
- Agent nigdy nie widzi decyzji. Strona w wiki, notatka ze spotkania czy pamięć seniora leżą poza kontekstem agenta. Agent optymalizuje pod bieżące zadanie, a najkrótsza droga często przecina granicę, której nikt nie zapisał.
- Decyzja to proza, którą agent może zinterpretować po swojemu. „Preferuj wspólny wrapper HTTP” ustępuje pod presją: agent uznaje, że ten jeden przypadek jest wyjątkiem.
- Nic nie pada, gdy decyzja zostaje złamana. Testy sprawdzają zachowanie, a pull request, który dodaje drugiego klienta HTTP, zachowuje się poprawnie.
DORA wiąże architekturę z tym, czy AI w ogóle pomaga. Google Cloud, ogłaszając raport DORA 2025 (23 września 2025), pisze: „Teams working in loosely coupled architectures with fast feedback loops see gains, while those constrained by tightly coupled systems and slow processes see little or no benefit”. Nasz wniosek: decyzja, która po cichu się rozmywa, spycha kod w stronę ściśle powiązanej architektury.
ADR zamyka pierwszą lukę, gdy leży w repozytorium i trafia do kontekstu agenta. Ograniczenia zapisane jako MUSI albo NIE WOLNO zamykają drugą. Sprawdzenie uruchamiane w CI zamyka trzecią, a strona o funkcjach dopasowania architektury zamienia te sprawdzenia w wymagane bramki.
Które decyzje potrzebują ADR, a którym wystarczy reguła?
Dział zatytułowany „Które decyzje potrzebują ADR, a którym wystarczy reguła?”ADR kosztuje decyzję człowieka i review. Przeznaczaj go na wybory, których cofnięcie jest kosztowne. Skill domain-modeling Matta Pococka (w mattpocock/skills, sprawdzone 26 września 2026) proponuje ADR tylko wtedy, gdy spełnione są trzy warunki: wybór jest „Hard to reverse”, „Surprising without context” i „The result of a real trade-off”. Stosuj ten sam test, a potem zdecyduj, jak egzekwować każde ograniczenie.
| Wybór | ADR? | Jak go egzekwować |
|---|---|---|
| Granice modułów i kierunek zależności („domena nie robi I/O”) | Tak | Reguła zależności: dependency-cruiser, import-linter albo ArchUnit |
| Jedna biblioteka na jeden problem (klient HTTP, ORM, daty, logowanie) | Tak | Zakaz importu: ESLint no-restricted-imports, Ruff TID251, ArchUnit |
| Nowa baza danych, kolejka albo usługa zewnętrzna | Tak | Przegląd człowieka: code owners na kodzie infrastruktury i manifestach zależności |
| Publiczne API albo kontrakt zdarzeń | Tak | Diff powierzchni API albo kontraktu w CI (zobacz stronę o funkcjach dopasowania) |
| Wzorce obsługi błędów, ponowień i idempotencji | Tak, jeśli był prawdziwy kompromis | Reguła lintera, gdzie się da, w pozostałych przypadkach ograniczenie Review: sprawdzane przez recenzenta |
| Nazewnictwo, formatowanie, układ plików | Nie | Konfiguracja lintera i formatera, bez ADR |
| „Wybieraj nudną technologię”, „trzymaj prosto” | Nie | Nic tego nie sprawdzi, więc agenta to nie ogranicza. Uzasadnienie wpisz w sekcję Context ADR-u, który z niego wynika |
Ostatni wiersz jest ważny. Zasada bez sprawdzenia nie zatrzyma agenta. Albo zamień ją w konkretne ograniczenie („NIE WOLNO dodawać zależności runtime bez przyjętego ADR”), albo zostaw w sekcji Context jako powód jakiegoś ograniczenia.
Jak wygląda ADR, którego agent przestrzega?
Dział zatytułowany „Jak wygląda ADR, którego agent przestrzega?”Zacznij od popularnego szablonu MADR (4.0.0, 17 września 2024). MADR ma już opcjonalny element „Confirmation”, który pyta, jak zgodność z ADR „can/will be confirmed”, i podsuwa „a design/code review or a test with a library such as ArchUnit”. Wersja dla agentów czyni tę sekcję obowiązkową i czytelną maszynowo, a do tego dodaje instrukcję na wypadek, gdy decyzja nie pasuje do zadania.
---status: accepteddate: 2026-09-14decision-makers: Anna Kowalska (tech lead), Tomasz Nowak (platform)scope: src/**---
# ADR-0012: All outbound HTTP goes through src/lib/http.ts
## Context
Three HTTP clients are in use (axios, got, the global fetch), each with its owntimeout and retry behavior. During the 2026-08-30 payment-provider outage, callsthrough got hung for 120 seconds because it had no timeout, and the checkoutworkers exhausted their pool.
## Decision
All outbound HTTP calls go through `src/lib/http.ts`, a wrapper over the platform`fetch` with a 10-second default timeout, retries with jitter for idempotentmethods, and trace headers. We add no other HTTP client library.
## Consequences
- Good: one place to change timeouts, retries and tracing; one behavior during an outage.- Bad: streaming uploads and websockets need the wrapper extended first.- Given up: axios interceptors, which two services used for auth headers.
## Constraints
- **adr-0012-c1** Code under `src/` MUST NOT import `axios`, `got`, `node-fetch` or `undici`. Check: `eslint.config.js#adr-0012-c1`- **adr-0012-c2** Code under `src/domain/` MUST NOT import `src/lib/http.ts`; the domain does no I/O. Check: `.dependency-cruiser.cjs#adr-0012-c2`- **adr-0012-c3** New call sites pass an explicit timeout when a call can take longer than 10 seconds. Review: the reviewer confirms it from the test that covers the slow path.
## When the agent must stop
If a task needs something the wrapper cannot do (streaming, websockets, a clientcertificate), stop. Write a proposed ADR that amends this one, and do not add aclient library.
## Revisit when
More than one service needs websockets, or the platform `fetch` gains a feature wewould otherwise wrap.ADR-y piszemy tu po angielsku, bo czytają je agenci i cały zespół, a nazwy sekcji wykorzystuje skrypt adr-lint. Cztery zasady sprawiają, że ten format działa z agentami:
- Jedna strona, jedna decyzja. Agent wczytuje ADR do kontekstu przy każdym zadaniu, którego ten dotyczy, więc każdy dodatkowy akapit kosztuje tokeny przy każdym uruchomieniu. Analizę opcji zostaw w pull requeście.
- Każde ograniczenie ma identyfikator i źródło werdyktu.
Check:wskazuje plik i identyfikator reguły w nim;Review:mówi, kto sprawdza ręcznie i na podstawie jakiego dowodu. Ograniczenie bez żadnego z nich to życzenie. - Komunikat reguły cytuje identyfikator. Gdy sprawdzenie pada, agent widzi
adr-0012-c1i ścieżkę do ADR, otwiera go i czyta, dlaczego. scopemówi, gdzie decyzja obowiązuje. Routing w każdym narzędziu korzysta z tego pola, żeby wczytywać tylko ADR-y dotyczące edytowanych plików.
Jak zapisać ograniczenie, które sprawdzi narzędzie?
Dział zatytułowany „Jak zapisać ograniczenie, które sprawdzi narzędzie?”Większość decyzji architektonicznych sprowadza się do dwóch kształtów reguły: „ten kod nie importuje tamtego kodu” i „nikt nie importuje tej biblioteki”. Każdy popularny stos ma narzędzie do obu. Poniższe reguły uruchomiliśmy 26 września 2026 (ESLint 10.11.0 z typescript-eslint 8.70.1, dependency-cruiser 18.4.0, Ruff 0.16.9) i każda padła na podłożonym naruszeniu, z identyfikatorem ADR w wyniku.
Zakaz biblioteki realizuje wbudowana reguła ESLint no-restricted-imports. Agent czyta pole message:
import { defineConfig } from 'eslint/config';import tseslint from 'typescript-eslint';
export default defineConfig({ files: ['src/**/*.ts'], languageOptions: { parser: tseslint.parser }, rules: { 'no-restricted-imports': ['error', { paths: ['axios', 'got', 'node-fetch', 'undici'].map((name) => ({ name, message: 'adr-0012-c1: outbound HTTP goes through src/lib/http.ts. See docs/adr/0012-outbound-http.md.', })), }], },});Granicę realizuje reguła dependency-cruiser nazwana identyfikatorem ograniczenia:
// .dependency-cruiser.cjs (add to the forbidden array){ name: 'adr-0012-c2', severity: 'error', comment: 'ADR-0012: the domain layer does no I/O. Pass the data in from src/app instead of importing src/lib/http.ts.', from: { path: '^src/domain/' }, to: { path: '^src/lib/http\\.ts$' },},Naruszenie daje w ESLint komunikat 'axios' import is restricted from being used. adr-0012-c1: outbound HTTP goes through src/lib/http.ts…, a w dependency-cruiser error adr-0012-c2: src/domain/order.ts → src/lib/http.ts. Uruchamiaj ESLint z --no-inline-config, żeby komentarz eslint-disable nie mógł wyłączyć ograniczenia.
Reguła Ruff TID251 (zakazane API) niesie komunikat, który czyta agent. W pyproject.toml:
[tool.ruff.lint]extend-select = ["TID251"]
[tool.ruff.lint.flake8-tidy-imports.banned-api]"requests".msg = "adr-0012-c1: outbound HTTP goes through shop.http. See docs/adr/0012-outbound-http.md""httpx".msg = "adr-0012-c1: outbound HTTP goes through shop.http. See docs/adr/0012-outbound-http.md"ruff check src zgłasza wtedy TID251 `requests` is banned: adr-0012-c1: … przy każdym imporcie zakazanego modułu. Ograniczenia granic trafiają do kontraktów import-linter typu forbidden albo layers. Nazwij kontrakt identyfikatorem ograniczenia (name = "adr-0012-c2 domain does no I/O"), żeby identyfikator pojawił się w błędzie. Pełna konfiguracja import-linter jest na stronie o funkcjach dopasowania.
Reguły ArchUnit przyjmują klauzulę because(), którą ArchUnit wypisuje razem z naruszeniem:
@ArchTeststatic final ArchRule adr0012c1 = noClasses() .should().dependOnClassesThat().resideInAnyPackage("okhttp3..", "org.apache.hc..") .because("adr-0012-c1: outbound HTTP goes through com.acme.shop.http.HttpGateway. See docs/adr/0012-outbound-http.md");
@ArchTeststatic final ArchRule adr0012c2 = noClasses().that().resideInAPackage("..domain..") .should().dependOnClassesThat().resideInAPackage("..http..") .because("adr-0012-c2: the domain does no I/O");Trzymaj identyfikator reguły w tekście because, a nie tylko w nazwie pola, żeby pojawił się w wyjściu Mavena, które czyta agent. Konfiguracja ArchUnit, łącznie z wersją surefire, która naprawdę uruchamia te testy, jest na stronie o funkcjach dopasowania.
Decyzja o wyborze bazy danych czy kolejki rzadko sprowadza się do reguły importu. Oznacz ją jako Review: i obejmij manifesty (package.json, pyproject.toml, pom.xml, kod infrastruktury) regułą code owners, żeby nowa zależność nie mogła wejść bez człowieka, który zna ADR-y. Jak sprawdzić, czy zależność dodana przez agenta jest prawdziwa i bezpieczna, opisuje strona o weryfikacji zależności.
Wdróż ADR-y dla agentów krok po kroku
Dział zatytułowany „Wdróż ADR-y dla agentów krok po kroku”-
Utwórz
docs/adr/i indeks.docs/adr/README.mdma jeden wiersz tabeli na każdy ADR: identyfikator, status, zakres i regułę w jednym zdaniu. Komórka z identyfikatorem linkuje do pliku, bo tego szukaadr-lint:| [ADR-0012](0012-outbound-http.md) | accepted | src/** | Outbound HTTP only through src/lib/http.ts |. Indeks agent wczytuje przy każdym zadaniu; pełny ADR tylko wtedy, gdy edytuje pliki z jego zakresu. -
Zapisz decyzje, które zespół już podjął. Uruchom poniższy prompt „odtwórz niezapisane decyzje”. Znajduje wzorce, których kod już się trzyma, i liczy wyjątki. Tech lead wybiera trzy do pięciu najważniejszych, a zespół przyjmuje je jako ADR-y. Agent pisze szkic; nie decyduje.
-
Wprowadzaj każdy ADR razem z jego sprawdzeniami w jednym pull requeście. ADR, reguła lintera albo zależności i, w istniejącym kodzie, baseline obecnych naruszeń wchodzą razem. Decyzja bez sprawdzenia nie jest przyjęta.
-
Dodaj
adr-lintdo CI. Skrypt z sekcji o udowadnianiu, że decyzje nadal obowiązują (niżej na tej stronie), wywraca build, gdy ADR i jego sprawdzenia się rozjadą. -
Podsuń ADR-y do kontekstu każdego agenta. Użyj konfiguracji dla poszczególnych narzędzi opisanej niżej. Część wspólna to jeden blok instrukcji w
AGENTS.mdalboCLAUDE.md. -
Obejmij ADR-y i pliki reguł regułą code owners. Dodaj
/docs/adr/, pliki konfiguracji reguł iscripts/adr-lint.mjsdoCODEOWNERSz tech leadem jako właścicielem i włącz w regułach gałęzi wymóg review od code ownera. Bez tego ustawieniaCODEOWNERStylko prosi o review; nie blokuje merge’a. -
Naucz agenta zatrzymywać się i proponować. Gdy zadanie wymaga wyboru, którego nie obejmuje żaden ADR (nowa zależność, nowy magazyn danych, przekroczenie granicy), agent zapisuje
docs/adr/NNNN-title.mdzestatus: proposedi się zatrzymuje. Człowiek przyjmuje, odrzuca albo poprawia propozycję. -
Zastępuj, nigdy nie edytuj. Gdy decyzja się zmienia, nowy ADR zastępuje stary, status starego zmienia się na
superseded by ADR-NNNN, a reguły powołujące się na stary identyfikator zostają w tym samym pull requeście przemianowane albo usunięte. CI jest czerwone w tym pull requeście, dopóki tech lead nie ustawi w nimstatus: acceptednowego ADR-u, i właśnie ten czerwony stan jest bramką akceptacji.
Jak podsunąć ADR-y agentowi?
Dział zatytułowany „Jak podsunąć ADR-y agentowi?”Blok instrukcji jest taki sam we wszystkich trzech narzędziach. Umieść go w AGENTS.md albo w CLAUDE.md, jeśli zespół używa tylko Claude Code:
## Architecture decisions- The accepted decisions are indexed in docs/adr/README.md. Before you edit a file, read every accepted ADR whose scope matches it.- A lint or dependency failure that cites `adr-NNNN-cN` is an architecture decision, not a style nit. Fix the code; never edit the rule, its baseline or the ADR.- If the task needs a new runtime dependency, a new data store or queue, a new public API, or a change an ADR forbids: stop. Write docs/adr/NNNN-short-title.md with `status: proposed`, the options you considered and the constraint you propose, then report back without implementing.- Never set an ADR's status to accepted. A human does that in review.Narzędzia różnią się tym, jak niezawodnie właściwy ADR trafia do kontekstu.
Zaimportuj indeks w CLAUDE.md, żeby wczytywał się przy starcie. Claude Code rozwija importy @ścieżka do kontekstu na początku sesji, a ścieżka w backtickach zostaje zwykłym tekstem:
@docs/adr/README.mdPotem daj każdemu często używanemu ADR-owi regułę ograniczoną do ścieżek. Plik w .claude/rules/ z polem paths wczytuje się dopiero wtedy, gdy Claude czyta pasujący plik, więc pełna treść ograniczeń trafia do kontekstu dokładnie wtedy, gdy jest potrzebna:
---paths: - "src/**/*.ts"---
# ADR-0012 applies hereOutbound HTTP only through src/lib/http.ts (adr-0012-c1). Nothing under src/domain/imports it (adr-0012-c2). Full decision: docs/adr/0012-outbound-http.mdZapisz go jako .claude/rules/adr-0012-outbound-http.md. Oba mechanizmy opisuje dokumentacja pamięci Claude Code, sprawdzona względem Claude Code 2.1.283 26 września 2026. Jeśli trzymasz jeden AGENTS.md dla wszystkich narzędzi, Claude Code czyta go bezpośrednio, gdy projekt nie ma CLAUDE.md, od wersji 2.1.277 (kanał latest na 26 września 2026); w starszych wersjach wstaw @AGENTS.md do CLAUDE.md.
Żeby sprawdzić plan względem ADR-ów, zanim zmieni się jakikolwiek plik, uruchom sesję przez claude --permission-mode plan i użyj poniższego promptu do sprawdzenia planu. Żeby Claude nie edytował plików reguł, użyj reguł deny i hooka Stop ze strony o funkcjach dopasowania.
Wstaw blok instrukcji do głównego AGENTS.md. Dokumentacja OpenAI mówi: „Codex reads AGENTS.md files before doing any work” (sprawdzone 28 sierpnia 2026). Od Codex CLI 0.150.0 niezaufane projekty nie dostarczają instrukcji z projektowego AGENTS.md, więc oznacz repozytorium jako zaufane, bo inaczej reguły ADR po cichu się nie wczytają. CI i tak egzekwuje sprawdzenia.
Do sprawdzenia planu wpisz /plan w sesji Codeksa w repozytorium. Do bezobsługowego sprawdzenia gotowej gałęzi uruchom Codeksa w trybie tylko do odczytu:
codex exec -c default_permissions=":read-only" "Read docs/adr/README.md and every accepted ADR whose scope matches a file changed in git diff origin/main...HEAD. For each constraint marked Review:, report PASS or FAIL with the file and line as evidence. Do not edit files.":read-only to wbudowany profil uprawnień (w Codex CLI 0.157.1 w wersji beta); starsza flaga --sandbox read-only robi to samo.
Code review Codeksa na GitHubie też czyta reguły review z AGENTS.md (sprawdzone 28 sierpnia 2026), więc dodaj tam: „Flag any change that violates a Review: constraint of an accepted ADR, citing the constraint ID”.
Dodaj blok instrukcji jako regułę projektu (Rule), która obowiązuje przy każdym zapytaniu do Agenta, i wskaż w niej docs/adr/README.md. Reguły „provide system-level instructions to Agent” (cursor.com/docs/rules, sprawdzone 28 sierpnia 2026). 26 września 2026 nie mogliśmy ponownie sprawdzić formatu plików reguł ani opcji zakresu, bo cursor.com był niedostępny z naszego środowiska. Aktualny format weź z dokumentacji reguł Cursora w dniu wdrożenia.
Przed dużą zmianą użyj Plan Mode, który „creates detailed implementation plans before writing any code”, z poniższym promptem do sprawdzenia planu. Cloud Agents działają na własnych maszynach, więc nic na twoim laptopie ich nie wiąże; gwarancję daje tylko wymagane sprawdzenie w CI. Bugbot może sprawdzać pull requesty pod kątem ograniczeń Review:, ale jest recenzentem, nie bramką.
Skill grill-with-docs Matta Pococka zamienia pisanie szkicu w rozmowę: przepytuje cię o projekt i na bieżąco zapisuje uzgodnione decyzje w docs/adr/. Według migawki skills.sh z 26 września 2026 miał 1 046 834 instalacje (liczba z drugiej ręki, z lustra LinklyAI/best-skills). Zainstaluj go w Claude Code przez claude plugin install mattpocock-skills (cały plugin z 25 skillami, około 1600 tokenów opisów skilli na sesję; sprawdzisz to przez claude plugin details mattpocock-skills) albo dla Codeksa przez npx skills add mattpocock/skills --skill grill-with-docs grilling domain-modeling -a codex (dla Cursora -a cursor). Instaluj wszystkie trzy nazwy: grill-with-docs to jednolinijkowy wskaźnik na dwa pozostałe. Jego ADR-y nie mają sekcji Constraints, więc dopisz ją przed przyjęciem. Więcej skilli tego rodzaju porównuje strona o najlepszych skillach do praktyki programistycznej.
Prompty do skopiowania: decyzje architektoniczne
Dział zatytułowany „Prompty do skopiowania: decyzje architektoniczne”Sprawdzenie planu uruchamiaj w trybie planowania. Pierwszy i trzeci prompt zapisują tylko pliki, które same wskazują, a nic, co wytworzą, nie jest przyjęte bez decyzji człowieka; jeśli nowe reguły z pierwszego promptu trafią do CI, będą czerwone, dopóki ADR nie zostanie przyjęty. Czwarty edytuje pliki reguł, więc jego pull request człowiek przegląda jako zmianę reguł, przez code owners.
Jak udowodnić, że decyzje nadal obowiązują?
Dział zatytułowany „Jak udowodnić, że decyzje nadal obowiązują?”Nikt nie powinien ponownie czytać kodu, żeby potwierdzić, że ADR jest wciąż prawdziwy. Robią to trzy rzeczy: same reguły, sprawdzenie spójności między ADR-ami a regułami i zapis tego, kto co przyjął.
Reguły działają jako wymagane sprawdzenia CI w każdym pull requeście, tak jak na stronie o funkcjach dopasowania, razem z kanarkiem, który dowodzi, że reguła wciąż potrafi paść.
Sprawdzenie spójności to scripts/adr-lint.mjs. Pada, gdy przyjęty ADR nie ma ograniczeń, gdy ograniczenie nie ma ani Check:, ani Review:, gdy sprawdzenie wskazuje nieistniejący identyfikator reguły, gdy reguła powołuje się na ADR, który nie jest przyjęty, albo gdy ADR-u brakuje w indeksie:
#!/usr/bin/env node// scripts/adr-lint.mjs: every accepted ADR constraint names a live check,// and every check names an accepted ADR.import { existsSync, readdirSync, readFileSync } from 'node:fs';
const DIR = 'docs/adr';const ENFORCERS = ['eslint.config.js', '.dependency-cruiser.cjs']; // add pyproject.toml, ArchitectureTest.java…const errors = [];const accepted = new Set();const index = readFileSync(`${DIR}/README.md`, 'utf8');
for (const file of readdirSync(DIR).filter((f) => /^\d{4}-.*\.md$/.test(f))) { const text = readFileSync(`${DIR}/${file}`, 'utf8'); const id = file.slice(0, 4); const status = text.match(/^status:\s*(.+)$/m)?.[1].trim() ?? 'missing'; if (!index.includes(`(${file})`)) errors.push(`${file}: not listed in ${DIR}/README.md`); if (status !== 'accepted') continue; accepted.add(id); const section = text.split(/^## Constraints$/m)[1]?.split(/^## /m)[0] ?? ''; const bullets = section.split('\n').filter((l) => l.startsWith('- ')); if (bullets.length === 0) errors.push(`${file}: accepted ADR has no constraints`); for (const line of bullets) { const check = line.match(/Check: `([^`#]+)#([^`]+)`/); if (check) { const [, path, ruleId] = check; if (!existsSync(path) || !new RegExp(`\\b${ruleId}\\b`).test(readFileSync(path, 'utf8'))) errors.push(`${file}: ${ruleId} is not defined in ${path}`); } else if (!line.includes('Review:')) { errors.push(`${file}: constraint has neither "Check:" nor "Review:": ${line.slice(0, 60)}`); } }}
for (const path of ENFORCERS.filter((p) => existsSync(p))) { for (const [, id] of readFileSync(path, 'utf8').matchAll(/adr-(\d{4})-c\d+/g)) if (!accepted.has(id)) errors.push(`${path}: rule cites ADR-${id}, which is not accepted`);}
if (errors.length) { console.error(errors.join('\n')); process.exit(1);}console.log(`adr-lint: ${accepted.size} accepted ADRs, every constraint traced.`);Uruchomiliśmy go 26 września 2026 w Node.js na przykładzie ADR-0012 z tej strony. Przeszedł na czystej konfiguracji i padł w każdym podłożonym przypadku: przy przemianowanej regule (adr-0012-c2 is not defined in .dependency-cruiser.cjs), przy zastąpionym ADR, którego reguły wciąż działały (rule cites ADR-0012, which is not accepted), i przy ograniczeniu bez źródła werdyktu. Dodaj go jako kolejny krok w jobie CI z funkcjami dopasowania: node scripts/adr-lint.mjs. Nie potrzebuje zależności ani klucza API.
Akceptacja dzieli się tak samo jak przy bramkach funkcji dopasowania:
| Kto | Odpowiada za | Zatwierdza |
|---|---|---|
| Tech lead | Zestaw ADR-ów, indeks i pliki reguł | Każdą zmianę statusu ADR na accepted albo superseded, przez code owners |
| Zespół | Samą decyzję | Pull request z ADR, w zwykłym review, zanim ADR zostanie przyjęty |
| Programista albo agent | Zielone sprawdzenia; szkice ADR-ów w statusie proposed | Nic w przyjętych ADR-ach ani regułach |
| Recenzent (człowiek albo agent do review) | Ograniczenia Review: | Każdy pull request, który dotyka zakresu ADR |
Trzy liczby mówią tech leadowi, czy system działa, a każdą da się odczytać z repozytorium: proponowane ADR-y na miesiąc (agenci zatrzymują się przy decyzjach, zamiast je podejmować), ograniczenia, które padły w tygodniu na pull requestach agentów (reguły żyją), i przyjęte ograniczenia oznaczone Review: (malejący udział oznacza, że coraz większa część architektury jest sprawdzana maszynowo). Te liczby stoją obok pakietu dowodów, który zespół czyta zamiast diffu.
Co się psuje, gdy agenci przestrzegają ADR-ów?
Dział zatytułowany „Co się psuje, gdy agenci przestrzegają ADR-ów?”Agent edytuje ADR, żeby pasował do jego kodu. Zmienia ograniczenie albo sam ustawia swojej propozycji status accepted. Jak z tego wyjść: code owners na docs/adr/, linia „never set accepted” w bloku instrukcji i review, które każdą zmianę w przyjętym ADR traktuje jak zmianę reguły. adr-lint wyłapuje regułę i ADR, które po takiej edycji przestały się zgadzać.
Agent spełnia literę, a łamie intencję. Opakowuje axios w plik src/lib/http2.ts albo przenosi I/O do folderu, którego reguła nie obejmuje. Jak z tego wyjść: pisz ograniczenia jako listę tego, co dozwolone („tylko src/lib/http.ts może importować undici”), a nie tego, co zakazane. W dependency-cruiser to reguła forbidden z from: { pathNot: '^src/lib/http\\.ts$' } i to: { path: 'node_modules/(undici|axios|got)' }, więc nowy http2.ts pada, gdy tylko zaimportuje klienta. Potem łataj lukę w regule tego samego dnia, w którym ją znajdziesz. Triage w review pull requesta agenta traktuje nowe pliki przy granicy jako sygnał ryzyka.
Katalog ADR-ów przerasta kontekst. Czterdzieści ADR-ów wczytywanych przy każdym zadaniu wypycha kod. Jak z tego wyjść: przy starcie wczytuj tylko indeks, każdy wiersz indeksu trzymaj w jednej linii, a pełne ADR-y wczytuj przez reguły ograniczone do ścieżek albo instrukcję „przeczytaj ADR, którego zakres pasuje”. Zastępuj śmiało: zastąpiony ADR znika z przyjętych wierszy indeksu.
Nieaktualny ADR sprawia, że agent „naprawia” poprawny kod. Kod poszedł dalej za zgodą zespołu, ale nikt nie zastąpił ADR-u, więc agent cofa zmianę, żeby była zgodna. Jak z tego wyjść: decyzja zmienia się tylko przez nowy ADR, w tym samym pull requeście co kod. Linia „Revisit when” daje tech leadowi powód do przeglądu każdego ADR-u, a prompt odtwarzający decyzje pokazuje, gdzie kod i ADR-y się rozjechały.
Każda sesja agenta zatrzymuje się, żeby zaproponować ADR. Lista wyzwalaczy jest za szeroka i zespół tonie w propozycjach. Jak z tego wyjść: zawęź ją do czterech wyzwalaczy z bloku instrukcji i napisz ADR-y dla wzorców, o które agent ciągle pyta. Jeśli to samo pytanie pada trzy razy, zespół ma niezapisaną decyzję.
Reguła istnieje, ale nikt nie pamięta dlaczego. Stara reguła lintera blokuje dobrą zmianę, a agent nie ma z czym dyskutować. Jak z tego wyjść: adr-lint odrzuca każdą regułę, która powołuje się na nieprzyjęty ADR, więc każda reguła adr- ma zapisany powód. W sporach agent zapisuje swoje argumenty w tym samym ARCH_DISPUTE.md, którego używa strona o funkcjach dopasowania, a decyzję podejmuje tech lead.
Co dalej z decyzjami architektonicznymi
Dział zatytułowany „Co dalej z decyzjami architektonicznymi”Na ścieżce tech leada następny krok to egzekwowanie tych ograniczeń jako bramek w CI.