Przejdź do treści głównej
50% zniżki wszystkie plany, oferta limitowana. Od $2.48/mo
14 min left
Narzędzia deweloperskie i DevOps

Jak skonfigurować linter dla kodu generowanego przez AI

S By Sherwin 14 min read
ESLint output showing AI-specific lint violations in a TypeScript file

Agenty AI potrafią generować kod, który się kompiluje.

To wciąż nie jest poprzeczka, której oczekujesz. Poprzeczka to kod, który nie importuje rzeczy, których nigdy nie używa, nie wymyka się z systemu typów przez any-cast, nie ignoruje zwracanych błędów i nie zaszywa na sztywno poświadczeń, które gosec powinien wychwycić przed przeglądem.

Modele AI uczą się na starych odpowiedziach ze Stack Overflow i dostarczają wzorce, których się tam nauczyły, w tym wycofane API, brakujące adnotacje typów oraz funkcje, które technicznie są poprawne, ale zbyt duże, by bezpiecznie je przejrzeć. Potrzebujesz lintera w pętli. Nie jako sugestii, ale jako bramy.

Ten przewodnik obejmuje konfigurację dla trzech ekosystemów (Python z Ruff, TypeScript/JavaScript z konfiguracją flat ESLint v10 oraz Go z golangci-lint) z regułami dostrojonymi specjalnie pod wzorce błędów, które wprowadza AI. Następnie pokazuje, jak sprawić, by bramę dało się obejść znacznie trudniej, tak aby agent nie mógł po prostu pominąć lokalnych hooków za pomocą --no-verify bez kolejnej warstwy, która go wychwyci.

Konfiguracja jest warstwowa: linting na poziomie IDE wychwytuje problemy na bieżąco, gdy agent pisze, hooki pre-commit wychwytują wszystko, co dotrze do próby commita, a CI wychwytuje wszystko, co przejdzie lokalnie. Każda warstwa jest niezależna i możesz wybrać sekcje językowe, które dotyczą twojego stosu. Warstwa wymuszania działa tak samo niezależnie od tego, który język lintujesz.

W skrócie

  • Ruff (Python), ESLint v10 (TS/JS) i golangci-lint (Go) mają każdy konkretne reguły, które wychwytują najczęstsze błędy AI
  • Poniższe konfiguracje są opatrzone komentarzami; każda reguła jest tam z jakiegoś powodu
  • Lefthook obsługuje bramę pre-commit; hook afterFileEdit w Cursor uruchamia lint na bieżąco
  • Cztery warstwy wymuszania znacznie utrudniają agentom AI pominięcie bramy za pomocą --no-verify
  • CI to ostatnie zabezpieczenie: agenty nie mogą przekazać --no-verify do GitHub Actions

Jak skonfigurować Ruff dla kodu AI w Pythonie

Terminal output from Ruff flagging unused imports and a missing return type annotation in AI-generated Python code

Ruff to właściwy linter Pythona dla baz kodu wspomaganych przez AI. Jest na tyle szybki, by uruchamiać go przy każdym zapisie pliku bez blokowania czegokolwiek, obejmuje zarówno styl, jak i rzeczywiste błędy logiczne, a do tego dostarcza zachowanie formatera (zastępując Black) w tym samym pliku binarnym. Poniższe reguły celują w konkretne wzorce błędów, które modele AI wprowadzają najczęściej w Pythonie.

Instalacja Ruff

pip install ruff

# or via uv (faster for new projects):
uv add --dev ruff

I to wszystko. Żadnego ekosystemu wtyczek, żadnych negocjacji zależności równorzędnych.

Konfiguracja pyproject.toml

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = [
  "E",   # pycodestyle — style consistency
  "F",   # pyflakes — catches unused imports (the most common AI artifact)
  "I",   # isort — import ordering (AI frequently reorders imports incorrectly)
  "N",   # pep8-naming — naming conventions
  "UP",  # pyupgrade — flags deprecated APIs (AI trains on old Stack Overflow answers)
  "S",   # flake8-bandit — security rules: subprocess.shell=True, eval(), hardcoded creds
  "ANN", # type annotation enforcement (AI frequently omits return type annotations)
]
ignore = []  # intentional: do not add sweeping ignores in AI-assisted codebases

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]  # allow assert in tests

Reguły F zwracają się natychmiast. Kod AI generuje instrukcje importu dla pakietów, których ostatecznie nie używa, a F401 (nieużywany import) wychwytuje każdy taki przypadek. Reguły UP wychwytują wywołania wycofanych wzorców API, których AI nauczyło się z odpowiedzi dla Pythona sprzed wersji 3.10; same UP006 i UP007 oznaczają dziesiątki zbędnych wzorców sprawdzania typów. Reguły S (Bandit) wychwytują błędy bezpieczeństwa: zaszyte na sztywno łańcuchy znaków wyglądające jak poświadczenia (S105/S106), wstrzykiwanie poleceń powłoki przez subprocess(shell=True) (S603/S607), słabe wybory kryptograficzne (S324).

Zapis ignore = [] jest celowy. Każdy wyjątek dodany do tej listy to klasa błędów AI, na którą postanowiłeś przyzwolić.

Uruchamianie Ruff

ruff check .           # lint only — see what's wrong
ruff check --fix .     # auto-fix safe violations (imports, formatting)
ruff format .          # format the codebase (replaces Black)

--fix domyślnie stosuje bezpieczne poprawki Ruff, takie jak usuwanie nieużywanych importów czy zastosowanie prostego formatowania i korekt lintingu. Ruff ma też niebezpieczne poprawki, ale te wymagają jawnego włączenia i należy je przeglądać staranniej. Wszystko, czego Ruff nie może bezpiecznie naprawić, przejrzyj ręcznie.

Jak skonfigurować ESLint v10 dla kodu AI w TypeScript i JavaScript

ESLint v10 flat config reporting no-explicit-any and max-lines-per-function violations in an AI-generated TypeScript file

ESLint v10 porzucił starszy format konfiguracji .eslintrc.*. Wszystko jest teraz konfiguracją flat w eslint.config.mjs. Jeśli trafisz na poradnik używający .eslintrc.json lub .eslintrc.js, dotyczy on ESLint v8 albo v9, gdzie składnia jest inna. Użyj tego, co poniżej.

Reguły @typescript-eslint w tej konfiguracji celują w konkretne tryby błędów, które wielokrotnie pojawiają się w TypeScripcie generowanym przez AI: wytrych any, monolityczne funkcje trudne do przejrzenia oraz zaszyte na sztywno wartości, które powinny być stałymi.

Instalacja ESLint v10 ze wsparciem dla TypeScript

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# or with pnpm:
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

ESLint v10.5.0 jest aktualny na czerwiec 2026. Pakiety @typescript-eslint powinny odpowiadać twojej wersji TypeScript; sprawdź ich README pod kątem macierzy zgodności.

Konfiguracja eslint.config.mjs

import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';

export default [
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: tsParser,
      parserOptions: { project: './tsconfig.json' },
    },
    plugins: { '@typescript-eslint': tseslint },
    rules: {
      // AI defaults to `any` to bypass the type system — this blocks it
      '@typescript-eslint/no-explicit-any': 'error',

      // AI generates variables it declares but never uses
      '@typescript-eslint/no-unused-vars': 'error',

      // AI writes functions that work but are too long to review safely
      'max-lines-per-function': ['error', { max: 50 }],

      // AI overloads function signatures; this forces decomposition
      'max-params': ['error', 2],

      // AI hardcodes values that should be named constants
      'no-magic-numbers': ['error', { ignore: [0, 1, -1] }],

      // AI writes files that are too large to reason about in a single review
      'max-lines': ['error', { max: 250 }],

      // AI leaves console.log debugging in production code
      'no-console': 'warn',
    },
  },
];

Reguła max-lines-per-function: 50 to najbardziej agresywna rzecz w tej konfiguracji. Będziesz na nią trafiać bez przerwy przy pierwszym uruchomieniu w bazie kodu wspomaganej przez AI. O to właśnie chodzi. Powinieneś na nią trafiać. Funkcje przekraczające 50 linii to pierwsza rzecz, która staje się niemożliwa do ogarnięcia, gdy przeglądasz wyniki AI na dużą skalę.

Reguła max-params: 2 wymusza dekompozycję. Modele AI uczą się z baz kodu, gdzie pięcioargumentowe funkcje są normą; reguła stawia opór, wymagając od agenta użycia obiektu opcji, co jest lepszym projektem i łatwiejsze w czytaniu.

Uruchamianie ESLint

npx eslint .                       # lint
npx eslint . --fix                 # auto-fix safe issues
npx eslint . --max-warnings 0      # CI mode — treats warnings as errors

Użyj --max-warnings 0 w kroku CI. Promuje to ostrzeżenia no-console z "technicznie odnotowane" do "faktycznie blokujące".

Opcjonalnie: surowsze reguły dla plików generowanych przez AI

Jeśli twój zespół stosuje konwencję nazewnictwa plików do oznaczania kodu generowanego przez AI (*.ai.ts, *-generated.ts lub podobne), możesz zastosować ściślejsze reguły specjalnie do tych plików:

// Add to eslint.config.mjs after the main config object
{
  files: ['**/*.ai.ts', '**/*.ai.tsx', '**/*-generated.ts'],
  rules: {
    'max-lines': ['error', { max: 100 }],  // tighter file ceiling
    'complexity': ['error', 5],             // McCabe complexity limit
  },
}

Jak skonfigurować golangci-lint dla kodu AI w Go

golangci-lint to standardowe narzędzie uruchamiające wiele linterów dla Go. Dostarczane jest z gosec, errcheck, staticcheck i ponad 40 innymi linterami konfigurowalnymi z jednego pliku YAML. Dla kodu Go generowanego przez AI kluczowe reguły to sprawdzanie zwracanych błędów oraz wykrywanie wzorców bezpieczeństwa: dwie kategorie błędów, które modele AI najczęściej pomijają w Go.

Instalacja golangci-lint

# Official binary installer:
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.12.2

# or via Homebrew:
brew install golangci-lint

Konfiguracja .golangci.yml

version: "2"

linters:
  enable:
    - gosec        # security: flags hardcoded creds (G101), file path injection (G304), weak crypto (G401)
    - unused       # flags unused vars and functions — a common AI artifact in Go
    - errcheck     # AI frequently ignores error returns — this blocks it
    - govet        # catches subtle correctness bugs AI introduces
    - staticcheck  # comprehensive static analysis
    - revive       # style: catches non-idiomatic Go patterns AI writes
    - misspell     # AI occasionally misspells in comments and string literals

  settings:
    gosec:
      severity: medium
      confidence: medium
    errcheck:
      check-type-assertions: true  # check `val, ok := x.(Type)` patterns
      check-blank: true            # catch `_ = someErr` error suppression

run:
  timeout: 3m
  issues-exit-code: 1

Wzorzec obsługi błędów w Go jest z założenia jawny: każda funkcja, która może zawieść, zwraca wartość błędu. Modele AI to rozumieją, ale niedoceniają; pomijają sprawdzanie błędów w niekrytycznych ścieżkach kodu. errcheck zamienia to pominięcie w błąd lintingu.

gosec to linter bezpieczeństwa. W przypadku kodu AI wychwytuje wzorce, które AI przejęło z poradników Go sprzed 2020 roku: niebezpieczne generowanie liczb losowych (G404), niebezpieczne funkcje haszujące (G401), problemy z uprawnieniami plików (G306). To błędy, których nie wychwytujesz podczas przeglądu kodu, bo wyglądają składniowo normalnie.

Uruchamianie golangci-lint

golangci-lint run ./...            # lint all packages
golangci-lint run --fix ./...      # auto-fix where possible

Jak wpiąć linter w hooki pre-commit

Hook pre-commit uruchamia lint przed każdym git commit i blokuje commit, jeśli lint zawiedzie. Oznacza to, że agent AI nie może zacommitować kodu, który nie spełnia twoich skonfigurowanych reguł. Musi najpierw naprawić naruszenia.

Lefthook to zalecana opcja. Jest wieloplatformowy, szybki i ma wzorzec konfiguracji, który działa specjalnie z wymuszaniem na agentach AI (omówione w następnej sekcji).

Lefthook

npm install --save-dev lefthook
npx lefthook install

lefthook.yml:

pre-commit:
  parallel: true
  commands:
    lint-python:
      glob: "*.py"
      run: ruff check {staged_files} --fix
    lint-js-ts:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files} --fix
    lint-go:
      glob: "*.go"
      run: golangci-lint run {staged_files}
  fail_text: |
    Lint failed. For AI Agents: fix all lint violations before committing.
    Do not use --no-verify to bypass this gate.

Komunikat fail_text jest odczytywany przez agenty AI, gdy commit zawiedzie. Wzorzec jest udokumentowany w opisie Liama Bigelowa dotyczącym wymuszania lintingu przez Lefthook w Claude Code. To samo w sobie nie powstrzyma zdeterminowanego agenta, ale daje mu właściwą kolejną instrukcję ("napraw naruszenia lintingu") zamiast pozostawiać mu wywnioskowanie obejścia.

Alternatywa: pre-commit (konfiguracje tylko dla Pythona)

Jeśli masz stos wyłącznie pythonowy i wolisz framework pre-commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.17
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

Cursor: linting na bieżąco przez hook afterFileEdit

Jeśli używasz Cursor, możesz uruchomić lint natychmiast, gdy AI modyfikuje plik, jeszcze zanim spróbuje commita. Utwórz .cursor/hooks.json w katalogu głównym projektu:

{
  "hooks": {
    "afterFileEdit": [
      {
        "match": "*.py",
        "run": "ruff check {file} --fix"
      },
      {
        "match": "*.{ts,tsx,js}",
        "run": "npx eslint {file} --fix"
      },
      {
        "match": "*.go",
        "run": "golangci-lint run {file}"
      }
    ]
  }
}

To uruchamia się za każdym razem, gdy AI Cursor modyfikuje plik. Agent otrzymuje informację zwrotną z lintingu na bieżąco, zanim uzna zadanie za ukończone, więc większość naruszeń zostaje naprawiona, zanim dotrą do bramy pre-commit.

Jak utrudnić agentom AI obejście bramy

Diagram of four enforcement layers (CLAUDE.md policy, Claude Code deny rule, PreToolUse hook, and CI backstop) blocking an AI agent from skipping the lint gate with --no-verify

Cztery warstwy wymuszania znacznie utrudniają agentowi AI pominięcie bramy pre-commit za pomocą --no-verify. Każda celuje w inną powierzchnię ataku: polityka w CLAUDE.md, reguła deny w Claude Code, hook PreToolUse oraz zabezpieczenie w CI. Traktuj je jako warstwową konfigurację, a nie jako cztery niezależne gwarancje.

Agenty AI czasami przekazują --no-verify do git commit, aby pominąć hooki pre-commit, gdy lint zawodzi, a agent uznał, że błędy są "niezwiązane z jego zmianami". Decyzja nie zawsze jest błędna, ale nie powinieneś pozwalać agentowi podejmować jej jednostronnie. Cały sens bramy lintingu polega na tym, że to człowiek ustalił politykę; zadaniem agenta jest ją spełnić, a nie ją obejść.

Oto każda warstwa i powierzchnia ataku, którą obejmuje.

Warstwa 1: udokumentuj politykę w CLAUDE.md

## Linting Policy

NEVER use `git commit --no-verify`. All commits must pass pre-commit hooks.
Pre-commit hooks run lint. Fix lint violations before committing. Do not treat
lint failures as unrelated to your changes — they may not be, and you don't get
to decide that.

Claude Code odczytuje CLAUDE.md na początku sesji. To nie jest wymuszanie; agent wciąż może spróbować obejścia. Ale usuwa to ścieżkę "nie wiedziałem" i ustala jasną politykę, którą agent musi aktywnie zdecydować się złamać, co jest mniej prawdopodobne niż wywnioskowanie obejścia z milczenia.

Warstwa 2: reguła deny w Claude Code

Dodaj do .claude/settings.json:

{
  "permissions": {
    "deny": [
      "Bash(git commit --no-verify*)"
    ]
  }
}

To blokuje jawne wywołanie. Jedno ograniczenie: reguła deny używa dopasowania po prefiksie, więc wychwytuje --no-verify tylko bezpośrednio po commit. Wystarczająco kreatywny agent mógłby ułożyć wywołanie inaczej. Nie polegaj wyłącznie na tym.

Warstwa 3: hook PreToolUse

Zainstaluj pakiet block-no-verify:

npm install --save-dev block-no-verify

Następnie skonfiguruj go jako hook PreToolUse w ustawieniach Claude Code. Uruchamia się on przed każdym wywołaniem narzędzia i bada argumenty pod kątem --no-verify w sześciu podkomendach git, nie tylko commit. Kończy się kodem różnym od zera, aby zablokować wywołanie przed jego wykonaniem.

Podręcznik pod adresem pydevtools.com stawia sprawę bez ogródek: "warstwa hooka to jedyna, która niezawodnie wymusza regułę". Używaj jej razem z pozostałymi warstwami; nie traktuj żadnego lokalnego hooka jako całej gwarancji.

Warstwa 4: zabezpieczenie w CI

CI działa na serwerze, gdzie agent nie ma powłoki, do której mógłby przekazać flagi:

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Lint Python
        run: |
          pip install ruff
          ruff check . --output-format github

      - name: Lint JS/TS
        run: npx eslint . --max-warnings 0

      - name: Lint Go
        uses: golangci/golangci-lint-action@v9
        with:
          version: v2.12.2

Agenty AI nie mogą przekazać --no-verify do CI. GitHub Actions działa niezależnie od tego, co agent zrobił lokalnie. Jeśli commit jakimś sposobem przeszedł z zawodzącym lintingiem, CI wychwyci go, zanim zostanie scalony.

To ostatnie zabezpieczenie.

Bonus: serwer ESLint MCP

Jeśli używasz Claude Code, jest proaktywna warstwa, która zmniejsza, jak często w ogóle trafiasz na bramę.

Serwer ESLint MCP (@eslint/mcp) integruje ESLint bezpośrednio w pętli narzędziowej agenta. Agent może odpytać ESLint podczas zadania, zanim spróbuje commita. Zainstaluj go globalnie:

npm install -g @eslint/mcp@latest

Dodaj do .claude/settings.json:

{
  "mcpServers": {
    "eslint": {
      "command": "npx",
      "args": ["@eslint/mcp@latest"]
    }
  }
}

Przy takiej konfiguracji agent może odpytać ESLint podczas zadania. Agent otrzymuje informację zwrotną z lintingu na bieżąco, a niektóre naruszenia można poprawić, zanim dotrą do hooka. To nie zastępuje bramy, ale zmniejsza szum na bramie.

Często zadawane pytania

Czy muszę skonfigurować wszystkie trzy lintery, jeśli pracuję tylko w jednym języku?

Nie. Skonfiguruj linter dla swojego głównego języka oraz warstwę wymuszania. Jeśli twój stos to wyłącznie TypeScript, skonfiguruj ESLint i pomiń Ruff oraz golangci-lint. Sekcja o zapobieganiu obejściu przez agenta dotyczy niezależnie od tego, który język lintujesz.

Czy te konfiguracje zepsują moją istniejącą bazę kodu przy pierwszym uruchomieniu?

Niemal na pewno i to celowo. Uruchom ruff check --fix . lub npx eslint . --fix, aby najpierw automatycznie poprawić bezpieczne naruszenia. To, co zostanie po automatycznej poprawie, to lista do ręcznego przeglądu: rzutowania no-explicit-any, funkcje przekraczające 50 linii, brakująca obsługa błędów. Przepracuj je stopniowo. Nie dodawaj reguł ignore, by uniknąć zajęcia się nimi.

Czy Biome jest na tym etapie zamiennikiem ESLint?

Biome v2.5.0 (wydany w czerwcu 2026) jest konkurencyjny w zakresie formatowania i podstawowych reguł lintingu. Jest szybszy niż ESLint i nie wymaga żadnej konfiguracji, jeśli zainstalujesz go przez bun x ultracite@latest init. Dla zespołów, które chcą jednego narzędzia i nie potrzebują pełnej głębi reguł @typescript-eslint, Biome to rozsądny wybór. Dla reguł specyficznych dla AI z tego przewodnika (max-params, no-magic-numbers, max-lines-per-function z progami ukierunkowanymi na AI) ESLint z @typescript-eslint wciąż ma większe pokrycie. Możesz uruchamiać oba: Biome do formatowania i podstawowego lintingu, ESLint do reguł specyficznych dla AI.

Co jeśli mój agent AI wciąż generuje na nowo te same naruszenia lintingu?

Agent obchodzi regułę zamiast naprawiać leżący u podstaw problem. W przypadku no-explicit-any oznacza to dodanie asercji typu zamiast zdefiniowania faktycznego typu. W przypadku max-lines-per-function oznacza to wyodrębnienie funkcji pomocniczej, która nie robi nic użytecznego, ale sprowadza liczbę linii poniżej progu. Żadne z tych rozwiązań nie przejdzie przeglądu kodu. Reguła lintingu wychwyciła objaw; pierwotną przyczyną jest prompt agenta. Dopracuj prompt, aby określić ograniczenie typu lub oczekiwaną dekompozycję; agent będzie podążał za jawnymi wskazówkami strukturalnymi bardziej niezawodnie niż za regułami domyślnymi. Jeśli prowadzisz bardziej rozbudowaną konfigurację agenta, ograniczenie pracy do dedykowanego subagenta z ograniczeniami wbudowanymi w jego instrukcje zwykle trzyma się lepiej niż pojedynczy szeroki prompt.

Czy serwer ESLint MCP zastępuje bramę pre-commit?

Nie. Zmniejsza, jak często agent generuje kod, który nie przechodzi przez bramę. Brama wciąż działa przy każdym commicie. Sprawdzanie na bieżąco przez serwer MCP oraz wymuszanie przez hook pre-commit są komplementarne, więc nie usuwaj żadnego z nich.

Share

Więcej z bloga

Czytaj dalej.

Gotowy do wdrożenia? Od $2,48/mies.

Niezależna chmura od 2008 roku. AMD EPYC, NVMe, 40 Gbps. Zwrot pieniędzy w ciągu 14 dni.