Przejdź do głównej zawartości

Bramy jakości kodu wspomagane przez AI

Bramy jakości kodu wspomagane przez AI to warstwowa weryfikacja: agent naprawiający błędy lintingu i typów w trakcie pisania, bezgłowy przegląd skanujący każdy diff pull requesta, zanim spojrzy na niego człowiek, oraz ciągłe monitorowanie, które wywala build przy regresji pokrycia lub złożoności. Każda warstwa łapie mechaniczne defekty przepuszczone przez poprzednią, w Cursorze, Claude Code i Codeksie.

Pewien null prześlizgnął się przez przegląd, trafił na produkcję w piątek i wybudził cię o drugiej w nocy. Diff wyglądał w porządku — zatwierdziło go trzech recenzentów — ale nikt nie zauważył niezabezpieczonego response.data.user.id na ścieżce, która uruchamia się wyłącznie przy logowaniu przez SSO. Piątek wcześniej pewien PR wprowadził wyciek pamięci w obsłudze sesji: przeszedł wszystkie testy, recenzent zatwierdził go po rzucie oka, a linter nie miał nic do powiedzenia. Do poniedziałku serwis zjadał 4GB RAM i crashował co sześć godzin.

Przegląd przez człowieka to niewłaściwe narzędzie do obu tych spraw. Styl, dziury w typach, zapytania N+1, brakująca walidacja danych wejściowych, nieobsłużone odrzucenia i powolne wycieki zasobów — to właśnie wyłapuje za każdym razem agent wpięty w bramę jakości, zanim jakikolwiek człowiek otworzy PR. Ten artykuł buduje taką bramę w Cursorze, Claude Code i Codeksie, aby recenzenci poświęcali uwagę architekturze i intencji zamiast udawać linter.

  • Wspólny plik standardów (.cursor/rules, CLAUDE.md lub AGENTS.md), względem którego każdy agent sprawdza własny wynik
  • Działający hook Claude Code uruchamiający Prettier, ESLint i tsc --noEmit na każdym pliku edytowanym przez agenta — z poprawnym schematem kluczowanym zdarzeniem i ścieżką pliku ze stdin
  • Bezgłowy krok przeglądu PR przez AI w GitHub Actions, który możesz wrzucić do dowolnego repo, dla wszystkich trzech narzędzi
  • Drabinę promptów do przeglądu: rutynowy audyt PR świadomy stacku, głęboki przegląd sześciu wymiarów z klasyfikacją ważności oraz rozbicie na soczewki, gdy jeden przebieg daje papkę
  • Ciągłe monitorowanie, którego agent nie podrobi, plus prompty do triażu jego wyników i do postawienia testów mutacyjnych
  • Metryki mówiące, czy brama faktycznie koreluje ze stabilnością produkcyjną
  • Zestaw rozwiązań na wypadek, gdy brama robi się zbyt głośna, blokuje CI na niepowiązanych plikach lub uderza w limity diffów

Chcesz wyłapywać defekty tak wcześnie i tak tanio, jak to możliwe, a żadna pojedyncza warstwa nie wystarcza sama w sobie. To oznacza trzy, z których każda wyłapuje to, co przepuściła poprzednia:

  1. W trakcie programowania — agent naprawia błędy lintingu i typów w trakcie pisania, w edytorze lub w pętli hooka. Najtańsza możliwa informacja zwrotna.
  2. Przed mergem — bezgłowy agent przegląda diff w CI i publikuje uwagi na PR, zanim spojrzy na niego człowiek.
  3. Po mergu, ciągła — narzędzie takie jak SonarQube śledzi trendy pokrycia, złożoności i duplikacji, więc regresje jakości pojawiają się jako wykres, a nie zaskoczenie o drugiej w nocy.

Reszta tego artykułu buduje każdą z warstw. Warstwy 1 i 2 są tam, gdzie te trzy narzędzia się różnią, więc korzystają z <Tabs>.

Warstwa 1: standardy, względem których agent sprawdza własną pracę

Dział zatytułowany „Warstwa 1: standardy, względem których agent sprawdza własną pracę”

Wszystkie trzy narzędzia czytają plik reguł na poziomie projektu i stosują go do wszystkiego, co generują. Format i lokalizacja pliku się różnią; treść jest niemal identyczna. Trzymaj go w kontroli wersji, aby cały zespół — i każdy agent — pracowali według tego samego standardu. Pisz go jako sprawdzalne warunki, a nie aspiracje: limit linii i sufit złożoności da się zweryfikować, „pisz czysty kod” nie.

.cursor/rules/code-standards.mdc
---
description: Enterprise Code Quality Standards
alwaysApply: true
---
## Style
- 2-space indentation, max line length 100
- Every exported function has a JSDoc block
- No `any` without a `// eslint-disable-next-line` and a reason
- No magic numbers - use named constants
## Size and complexity
- No function longer than 50 lines; no file longer than 300
- Cyclomatic complexity under 10 per function
## Architecture
- Data access goes through the repository layer, never inline SQL in handlers
- Services receive dependencies via constructor injection
- All outbound HTTP calls go through the shared `httpClient` wrapper
- Error handling follows our Result<T, E> pattern (no bare try/catch)
## Performance
- Paginate any endpoint that returns a list
- No queries inside loops — batch with `IN (...)` or a join
- Memoize pure functions that run on every render
## Security
- Parameterized queries only
- Validate request bodies with the Zod schema in `schemas/`
- Never log tokens, passwords, or full request bodies

Automatyzacja warstwy 1: hook Claude Code, który naprawdę się ładuje

Dział zatytułowany „Automatyzacja warstwy 1: hook Claude Code, który naprawdę się ładuje”

Najczęstszym błędem jest tutaj konfiguracja hooka, która po cichu nigdy się nie uruchamia. Claude Code zagnieżdża tablice hooków pod nazwą zdarzenia (PostToolUse, PreToolUse) wewnątrz obiektu hooks najwyższego poziomu — goła tablica hooks na najwyższym poziomie się nie załaduje. Matcher to wyrażenie regularne uruchamiane na tool_name, więc musi mieć postać Write|Edit z tą wielkością liter. Hooki nie otrzymują też edytowanej ścieżki w zmiennej środowiskowej; czytają JSON ze stdin i wyciągają .tool_input.file_path.

Umieść logikę w skrypcie, aby konfiguracja pozostała czytelna:

.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "node scripts/quality-check.js" }
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-and-lint.sh"
}
]
}
]
}
}
.claude/hooks/format-and-lint.sh
#!/usr/bin/env bash
set -euo pipefail
# The edited path arrives as JSON on stdin, not as an env var.
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE_PATH" ] && exit 0
npx prettier --write "$FILE_PATH"
npx eslint --fix "$FILE_PATH"
# Type-check only TS files; tsc does the type checking, not a linter.
case "$FILE_PATH" in
*.ts|*.tsx) npx tsc --noEmit ;;
esac

PreToolUse to brama, która może odmówić zapisu, zanim ten nastąpi — użyj jej do kontroli polityki, które mają zatrzymać edycję (chroniona ścieżka, zakazany import). PostToolUse to przebieg naprawczy. $CLAUDE_PROJECT_DIR to jedna z nielicznych prawdziwych zmiennych hooków (obok $CLAUDE_ENV_FILE dla SessionStart i $CLAUDE_CODE_REMOTE); otocz ją cudzysłowami, aby ścieżki ze spacjami przetrwały.

W Cursorze odpowiednikiem jest pętla auto-naprawy: gdy błędy ESLint trafiają do panelu Problems, agent je naprawia i uruchamia ponownie, aż będzie czysto. Codex stosuje ten sam krok eslint --fix w swoim sandboksie, gdy poprosisz go o „make lint pass” w ramach zadania.

JavaScript / TypeScript

  • ESLint z twoją wspólną konfiguracją
  • Prettier do formatowania
  • typescript-eslint dla reguł lintingu świadomych TypeScriptu
  • tsc --noEmit do sprawdzania typów

Python

  • Ruff do szybkiego lintingu (oraz formatowania, zastępując Blacka)
  • mypy do sprawdzania typów
  • bandit do lintingu bezpieczeństwa

Java

  • Checkstyle dla standardów
  • SpotBugs do wykrywania błędów
  • PMD do analizy kodu

Go

  • agregator golangci-lint
  • gofmt do formatowania
  • go vet oraz staticcheck

Najużyteczniejszym nawykiem w edytorze jest zabijanie any w chwili, gdy kontroler typów na nie wskaże — any to dziura, przez którą przegląda cała brama. Pętla auto-naprawy Cursora robi to przy włączeniu, ale prompt działa we wszystkich trzech narzędziach.

Czysty wynik wygląda tak — rzutowanie staje się nazwanym, sprawdzalnym kontraktem:

interface UserResponse {
id: string;
status: 'active' | 'inactive';
metadata: Record<string, unknown>;
}
const data = response.data as UserResponse;

Warstwa 2: przegląd diffa przez AI, zanim spojrzy człowiek

Dział zatytułowany „Warstwa 2: przegląd diffa przez AI, zanim spojrzy człowiek”

To tutaj brama zarabia na swoje utrzymanie. Konfiguracja jest naprawdę trójnarzędziowa: każde narzędzie uruchamia bezgłowego agenta na diffie PR i publikuje uwagi.

Wbudowanym przeglądem PR w Cursorze jest BugBot. Włącz go z integracji GitHub w panelu, a następnie wrzuć .cursor/BUGBOT.md do katalogu głównego repo, aby sterować tym, co oznacza (zobacz wytyczne przeglądu poniżej). Po połączeniu BugBot automatycznie komentuje inline na PR.

Zarówno .cursor/BUGBOT.md w Cursorze, jak i prompt podany Claude Code lub Codeksowi zyskują na jawnej liście kontrolnej. Skup ją na tym, co ludzie regularnie przeoczają:

# .cursor/BUGBOT.md (or paste into the review prompt)
## Security (block on any of these)
- Hardcoded credentials, tokens, or API keys
- Unparameterized SQL or string-concatenated queries
- Unvalidated request bodies reaching the database
- Missing auth check on a protected route
- User input rendered without escaping (XSS)
## Correctness
- Unhandled promise rejections / missing `await`
- Null/undefined dereferences on optional fields
- N+1 query patterns (a query inside a `.map`/loop)
## Quality
- New code without tests
- Functions over 50 lines or complexity over 10
- Logging that includes sensitive data

Wrzuć to do dowolnego repo. Uruchamia się na pull requestach i publikuje uwagi agenta. Zwróć uwagę na actions/checkout@v6@v3 jest wycofany i wymusza uruchamianie akcji JavaScript na nieobsługiwanym środowisku Node.

BugBot działa jako hostowana integracja GitHub, więc nie ma żadnego YAML-a CI do utrzymywania — po włączeniu przegląda PR-y automatycznie. Skorzystaj z zakładki Claude Code lub Codex, jeśli zamiast tego chcesz, aby krok przeglądu mieszkał w twoim własnym pliku workflow.

Ten nieinteraktywny przegląd nie może wyświetlić nowego monitu o zatwierdzenie. Używaj approval_policy=never tylko z zaufaną tożsamością CI o minimalnych uprawnieniach; read-only pozostaje egzekwowaną granicą sandboxa, a każda akcja wymagająca szerszego dostępu kończy się błędem.

Dla codziennych pull requestów trzymaj przegląd wąsko i świadomie wobec stacku. Wartością jest krótka lista prawdziwych defektów, a nie rzetelny esej.

Przegląd głęboki: jeden raport, z klasyfikacją ważności

Dział zatytułowany „Przegląd głęboki: jeden raport, z klasyfikacją ważności”

Zmiana na ścieżce płatności albo we współdzielonej abstrakcji zasługuje na więcej niż przebieg rutynowy. Ten prompt dodaje wymiary, które rutynowy celowo pomija — architekturę, utrzymywalność, testowalność — i każe agentowi sklasyfikować każdą uwagę, żebyś mógł ją przetriażować.

Gdy jeden przebieg daje płytką, zlepioną odpowiedź — częsta awaria przy dużym lub nieznanym diffie — uruchom wymiary jako osobne konwersacje. Każda soczewka dostaje pełną uwagę modelu, a te, które wrócą puste, po prostu wyrzucasz.

Uruchom każdą soczewkę jako osobną konwersację w trybie Agent dla większej głębi:

Lens 1 - Correctness: Review /src/services/payment.ts changes.
Assume every input is adversarial. Find every way this code could
produce incorrect results, crash, or behave unexpectedly.
Lens 2 - Performance: Same file. Assume 10,000 requests per second.
Find bottlenecks, memory leaks, and unnecessary allocations.
Lens 3 - Security: Same file. You are a penetration tester.
Find every way to exploit this code.

Warstwa 3: ciągłe monitorowanie, którego agent nie podrobi

Dział zatytułowany „Warstwa 3: ciągłe monitorowanie, którego agent nie podrobi”

Prawdziwe monitorowanie oznacza prawdziwe narzędzia, a nie model przypominający sobie liczby. SonarQube (lub SonarCloud) to standard: liczy pokrycie, złożoność cyklomatyczną i duplikację przy każdym buildzie oraz śledzi trend. Wepnij go w ten sam workflow:

# add to .github/workflows/ai-review.yml
- name: SonarQube scan
uses: SonarSource/sonarqube-scan-action@v6
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.qualitygate.wait=true

Flaga qualitygate.wait=true blokuje PR, jeśli brama jakości Sonara dla projektu nie przejdzie (na przykład pokrycie nowego kodu poniżej 80% albo nowy problem o krytyczności blocker). To twój punkt egzekwowania — konkretny, zmierzony i taki, którego agent nie zagada.

Aby uzyskać odpowiedź na pytanie „co AI o tym sądzi?”, podaj agentowi wyniki Sonara, zamiast prosić go o wymyślanie metryk:

Comiesięczny przegląd kondycji obejmuje to, czego brama działająca per PR strukturalnie nie zobaczy: dryf. Uruchamiaj go w sesji, w której agent faktycznie może mierzyć — raport pokrycia na dysku, git log, skan martwego kodu, skan duplikacji — i napisz mu to wprost. Zapytany na sucho model chętnie wyprodukuje wiarygodnie wyglądający dashboard liczb, których nigdy nie policzył, a to gorsze niż brak dashboardu.

Procent pokrycia nie mierzy jakości; mierzy, które linie się wykonały. Oto liczby, które ruszają się, gdy brama naprawdę działa:

MetrykaCo ujawniaCel
Wynik mutacyjnyTesty rzeczywiście łapią błędy, nie tylko wykonują kod> 75%
Średni czas wykryciaJak szybko błędy są znajdowane po wprowadzeniu< 1 sprint
Wskaźnik uciekających defektówBłędy, które docierają do produkcji< 2% zmian
Czas reakcji na przeglądJak długo PR czeka na przegląd< 4 godziny
Wskaźnik przeróbekPR wymagające > 2 rund przeglądu< 15%
Niezawodność builduWskaźnik sukcesu pipeline CI> 95%

Wynik mutacyjny warto postawić jako pierwszy, bo to jedyna metryka na tej liście, której nie da się ograć pisaniem kolejnych testów niczego nie sprawdzających.

Częstą regresją na produkcji jest zapytanie lub endpoint, które działają świetnie podczas przeglądu i padają pod obciążeniem. Wpisz testy obciążeniowe w bramę za pomocą k6 — progi są prawdziwe i sprawiają, że test sam przechodzi lub nie przechodzi.

Wygenerowany test koduje progi jako warunki bramy, więc regresja zmienia krok CI na czerwony:

checkout.load.test.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 200 },
{ duration: '5m', target: 200 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.post(
`${__ENV.BASE_URL}/api/checkout`,
JSON.stringify({ cartId: 'c_1', paymentMethodId: 'pm_1', idempotencyKey: `${__VU}-${__ITER}` }),
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'checkout' } },
);
check(res, { 'status 200': (r) => r.status === 200 });
}

Brama, którą uruchamia jedno repozytorium, to prywatny nawyk. Rozprowadzenie jej oznacza, że same standardy stają się wersjonowanym artefaktem:

  1. Stwórz współdzielony pakiet konfiguracji

    Jeden pakiet — w twoim monorepo albo opublikowany w npm — zawierający konfiguracje ESLint, TypeScript i Prettier oraz pliki reguł AI.

  2. Dystrybuuj przez zarządzanie pakietami

    Każdy projekt rozszerza współdzieloną konfigurację. Lokalne nadpisania muszą być udokumentowane i zatwierdzone, a nie po cichu dodane.

  3. Egzekwuj w CI

    Pipeline sprawdza, czy współdzielone konfiguracje nie zostały nadpisane bez zatwierdzenia.

  4. Wskaż regułom AI współdzielony standard

    .cursor/rules, CLAUDE.md i AGENTS.md odwołują się do współdzielonego dokumentu standardów, zamiast każdy dryfować z własną kopią.

  5. Przeglądaj kondycję co miesiąc

    Uruchamiaj przegląd kondycji bazy kodu cyklicznie i porównuj zespoły ze współdzielonymi benchmarkami, tak by regresja była linią trendu, a nie incydentem.

Gdy powyższe prompty okażą się przydatne, zapisz je jako wielokrotnego użytku polecenia slash. Plik .claude/commands/security-audit.md staje się poleceniem /security-audit w interaktywnej sesji Claude Code (podkatalogi dodają przestrzenie nazw — .claude/commands/review/pr.md to /review:pr). Wywołaj je w REPL:

> /security-audit

przy czym plik polecenia zawiera twój prompt skupiony na OWASP. Cursor udostępnia tę samą ideę przez zapisane prompty; Codex przez workflow w AGENTS.md i niestandardowe prompty.