Перейти до основного вмісту
Знижка 50% усі плани, обмежений час. Від $2.48/mo
14 min left
Інструменти розробника та DevOps

Як налаштувати лінтер для коду, згенерованого AI

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

AI-агенти можуть генерувати код, що компілюється.

Але це все одно не та планка, яка вам потрібна. Планка — це код, який не імпортує те, чого ніколи не використовує, не обходить систему типів через any-cast, не ігнорує повернуті помилки і не зашиває облікові дані, які gosec має виявити ще до рев'ю.

AI-моделі навчаються на старих відповідях зі Stack Overflow і видають патерни, яких там навчилися, зокрема застарілі API, відсутні анотації типів і функції, що технічно правильні, але надто великі, щоб їх можна було безпечно перевірити. Вам потрібен лінтер у циклі. Не як порада, а як бар'єр.

Цей посібник охоплює конфігурацію для трьох екосистем (Python із Ruff, TypeScript/JavaScript з ESLint v10 у форматі flat config і Go з golangci-lint) із правилами, налаштованими саме під ті патерни помилок, які привносить AI. Далі він пояснює, як зробити бар'єр набагато складнішим для обходу, щоб агент не міг просто пропустити локальні хуки через --no-verify без того, щоб його зловив ще один шар.

Налаштування багаторівневе: лінтинг на рівні IDE виявляє проблеми прямо під час того, як агент пише код, pre-commit-хуки ловлять усе, що дійшло до спроби коміту, а CI ловить усе, що пройшло локально. Кожен шар незалежний, і ви можете обрати ті розділи мов, які стосуються вашого стеку. Шар примусу працює однаково незалежно від того, яку мову ви лінтуєте.

Коротко

  • Ruff (Python), ESLint v10 (TS/JS) і golangci-lint (Go) — кожен має конкретні правила, що ловлять найпоширеніші помилки AI
  • Конфігурації нижче анотовані; кожне правило тут не випадково
  • Lefthook відповідає за pre-commit-бар'єр; хук afterFileEdit у Cursor запускає лінт прямо в процесі
  • Чотири шари примусу значно ускладнюють для AI-агентів обхід бар'єра через --no-verify
  • CI — остання лінія оборони: агенти не можуть передати --no-verify у GitHub Actions

Як налаштувати Ruff для коду Python, згенерованого AI

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

Ruff — правильний лінтер Python для кодових баз, написаних із допомогою AI. Він достатньо швидкий, щоб запускатися при кожному збереженні файлу, нічого не блокуючи, охоплює і стиль, і реальні логічні помилки та постачає поведінку форматувальника (замінюючи Black) у тому самому бінарнику. Правила нижче націлені на конкретні патерни помилок, які AI-моделі привносять у Python найчастіше.

Встановлення Ruff

pip install ruff

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

Ось і все. Жодної екосистеми плагінів, жодних узгоджень peer-залежностей.

Конфігурація 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

Правила F окуповуються одразу. AI-код генерує оператори import для пакетів, які зрештою не використовує, і F401 (невикористаний імпорт) ловить кожен з них. Правила UP виявляють виклики застарілих патернів API, яких AI навчився з відповідей для Python до версії 3.10; лише UP006 і UP007 позначають десятки зайвих патернів перевірки типів. Правила S (Bandit) ловлять помилки безпеки: зашиті рядки, схожі на облікові дані (S105/S106), shell-ін'єкцію через subprocess(shell=True) (S603/S607), слабкий вибір криптографії (S324).

ignore = [] зроблено навмисно. Кожен виняток, який ви додаєте до цього списку, — це клас помилок AI, який ви вирішили дозволити.

Запуск 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 застосовує безпечні виправлення Ruff за замовчуванням, наприклад видалення невикористаних імпортів або застосування простого форматування й виправлень лінту. У Ruff є й небезпечні виправлення, але вони вимагають явного увімкнення і потребують більш ретельної перевірки. Усе, що Ruff не може виправити безпечно, перегляньте вручну.

Як налаштувати ESLint v10 для коду TypeScript і JavaScript, згенерованого AI

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

ESLint v10 відмовився від застарілого формату конфігурації .eslintrc.*. Тепер усе — це flat config у eslint.config.mjs. Якщо ви знайшли туторіал, де використовується .eslintrc.json або .eslintrc.js, він орієнтований на ESLint v8 чи v9, де синтаксис інший. Використовуйте те, що наведено нижче.

Правила @typescript-eslint у цій конфігурації націлені на конкретні режими помилок, які неодноразово виникають у згенерованому AI коді TypeScript: лазівку any, монолітні функції, які важко перевіряти, і зашиті значення, що мають бути константами.

Встановлення ESLint v10 з підтримкою 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 актуальний станом на червень 2026 року. Пакети @typescript-eslint мають відповідати вашій версії TypeScript; перевірте їхній README щодо матриці сумісності.

Конфігурація 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',
    },
  },
];

Правило max-lines-per-function: 50 — найагресивніша річ у цій конфігурації. Ви натикатиметеся на нього постійно під час першого запуску в кодовій базі, написаній із допомогою AI. У цьому й суть. Ви маєте на нього натикатися. Функції, що перевищують 50 рядків, — це перше, що стає неможливим осягнути, коли ви переглядаєте вихід AI у великих обсягах.

Правило max-params: 2 змушує декомпонувати код. AI-моделі навчаються на кодових базах, де функції з п'ятьма аргументами — норма; правило протидіє цьому, вимагаючи від агента використовувати об'єкт опцій, що є кращим дизайном і легше читається.

Запуск ESLint

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

Використовуйте --max-warnings 0 у своєму кроці CI. Це підвищує попередження no-console з «технічно зафіксовано» до «реально блокує».

Опційно: суворіші правила для файлів, згенерованих AI

Якщо ваша команда використовує угоду про іменування файлів, щоб позначати код, згенерований AI (*.ai.ts, *-generated.ts чи подібне), ви можете застосувати суворіші правила саме до цих файлів:

// 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
  },
}

Як налаштувати golangci-lint для коду Go, згенерованого AI

golangci-lint — стандартний запускач кількох лінтерів для Go. Він постачається з gosec, errcheck, staticcheck і ще 40+ лінтерами, які налаштовуються з одного YAML-файлу. Для згенерованого AI коду Go критичними є правила перевірки повернення помилок і виявлення патернів безпеки: дві категорії помилок, які AI-моделі пропускають у Go найбільш послідовно.

Встановлення 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

Конфігурація .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

Патерн обробки помилок у Go явний за задумом: кожна функція, яка може зазнати невдачі, повертає значення error. AI-моделі це розуміють, але недооцінюють; вони пропускатимуть перевірки помилок у некритичних шляхах виконання. errcheck перетворює таке пропускання на помилку лінту.

gosec — лінтер безпеки. Для коду AI він ловить патерни, які AI підхопив із туторіалів по Go до 2020 року: небезпечну генерацію випадкових чисел (G404), небезпечні геш-функції (G401), проблеми з правами доступу до файлів (G306). Це помилки, які ви не помічаєте під час рев'ю коду, бо вони виглядають синтаксично нормально.

Запуск golangci-lint

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

Як вбудувати лінтер у pre-commit-хуки

Pre-commit-хук запускає лінт перед кожним git commit і блокує коміт, якщо лінт не пройдено. Це означає, що AI-агент не може закомітити код, який не відповідає вашим налаштованим правилам. Спершу він має виправити порушення.

Lefthook — рекомендований варіант. Він кросплатформний, швидкий і має патерн конфігурації, що працює саме для примусу до AI-агентів (розглядається в наступному розділі).

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.

Повідомлення fail_text читається AI-агентами, коли коміт зазнає невдачі. Цей патерн задокументовано в статті Ліама Бігелоу про примус лінту через Lefthook для Claude Code. Лише цього не вистачить, щоб зупинити рішучого агента, але це дає йому правильну наступну інструкцію («виправити порушення лінту») замість того, щоб лишати йому здогадуватися про обхідний шлях.

Альтернатива: pre-commit (налаштування лише для Python)

Якщо у вас стек лише з Python і ви віддаєте перевагу фреймворку 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: інлайн-лінтинг через хук afterFileEdit

Якщо ви користуєтеся Cursor, ви можете запускати лінт одразу, коли AI змінює файл, ще до того, як він спробує зробити коміт. Створіть .cursor/hooks.json у корені проєкту:

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

Це спрацьовує щоразу, коли AI у Cursor змінює файл. Агент отримує зворотний зв'язок від лінту прямо в процесі, перш ніж вважатиме завдання виконаним, тож більшість порушень виправляється до того, як вони досягнуть pre-commit-бар'єра.

Як ускладнити обхід бар'єра для AI-агентів

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

Чотири шари примусу значно ускладнюють для AI-агента обхід pre-commit-бар'єра через --no-verify. Кожен націлений на іншу поверхню атаки: політика в CLAUDE.md, правило deny у Claude Code, хук PreToolUse і остання лінія оборони в CI. Сприймайте їх як багаторівневе налаштування, а не як чотири незалежні гарантії.

AI-агенти іноді передають --no-verify у git commit, щоб пропустити pre-commit-хуки, коли лінт не проходить, а агент вирішив, що помилки «не стосуються його змін». Рішення не завжди хибне, але не варто дозволяти агенту ухвалювати його одноосібно. Уся суть лінт-бар'єра в тому, що політику встановила людина; завдання агента — задовольнити її, а не обійти.

Ось кожен шар і поверхня атаки, яку він покриває.

Шар 1: задокументуйте політику в 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 читає CLAUDE.md на початку сесії. Це не примус; агент усе ще може спробувати обхід. Але це усуває шлях «я не знав» і встановлює чітку політику, яку агент має активно вирішити порушити, що він зробить менш імовірно, ніж здогадається про обхідний шлях із мовчання.

Шар 2: правило deny у Claude Code

Додайте до .claude/settings.json:

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

Це блокує явний виклик. Одне обмеження: правило deny використовує зіставлення за префіксом, тож воно ловить --no-verify лише одразу після commit. Достатньо винахідливий агент міг би побудувати виклик інакше. Не покладайтеся лише на це.

Шар 3: хук PreToolUse

Встановіть пакет block-no-verify:

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

Потім налаштуйте його як хук PreToolUse у налаштуваннях Claude Code. Він спрацьовує перед кожним викликом інструмента і перевіряє аргументи на наявність --no-verify у шести git-підкомандах, а не лише в commit. Він завершується з ненульовим кодом, щоб заблокувати виклик до його виконання.

Довідник за адресою pydevtools.com відверто про це каже: «шар хука — єдиний, що надійно забезпечує дотримання правила». Використовуйте це разом з іншими шарами; не сприймайте жоден локальний хук як цілковиту гарантію.

Шар 4: остання лінія оборони в CI

CI працює на сервері, де агент не має оболонки, у яку можна передати прапорці:

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

AI-агенти не можуть передати --no-verify у CI. GitHub Actions працює незалежно від того, що агент робив локально. Якщо коміт якимось чином пройшов із невдалим лінтом, CI зловить його до злиття.

Це остання лінія оборони.

Бонус: сервер ESLint MCP

Якщо ви користуєтеся Claude Code, є проактивний шар, який зменшує те, наскільки часто ви взагалі натикаєтеся на бар'єр.

Сервер ESLint MCP (@eslint/mcp) інтегрує ESLint безпосередньо в цикл інструментів агента. Агент може звертатися до ESLint під час виконання завдання, перш ніж спробувати коміт. Встановіть його глобально:

npm install -g @eslint/mcp@latest

Додайте до .claude/settings.json:

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

З таким налаштуванням агент може звертатися до ESLint під час виконання завдання. Агент отримує зворотний зв'язок від лінту прямо в процесі, і деякі порушення можна виправити до того, як вони досягнуть хука. Це не замінює бар'єр, а зменшує шум на ньому.

Часті запитання

Чи потрібно мені налаштовувати всі три лінтери, якщо я працюю лише однією мовою?

Ні. Налаштуйте лінтер для своєї основної мови і шар примусу. Якщо ваш стек лише на TypeScript, налаштуйте ESLint і пропустіть Ruff та golangci-lint. Розділ про запобігання обходу агентом застосовний незалежно від того, яку мову ви лінтуєте.

Чи зламають ці конфігурації мою наявну кодову базу під час першого запуску?

Майже напевно, і навмисно. Спершу запустіть ruff check --fix . або npx eslint . --fix, щоб автоматично виправити безпечні порушення. Те, що залишиться після автовиправлення, — це список для ручного рев'ю: касти no-explicit-any, функції, що перевищують 50 рядків, відсутня обробка помилок. Опрацьовуйте їх поступово. Не додавайте правила ignore, щоб не мати з ними справи.

Чи є Biome заміною ESLint на цей момент?

Biome v2.5.0 (випущено в червні 2026 року) конкурентний у форматуванні й базових правилах лінту. Він швидший за ESLint і має нульові накладні витрати на конфігурацію, якщо встановити його через bun x ultracite@latest init. Для команд, які хочуть єдиний інструмент і не потребують усієї глибини правил @typescript-eslint, Biome — розумний вибір. Для AI-специфічних правил у цьому посібнику (max-params, no-magic-numbers, max-lines-per-function з порогами, націленими на AI) ESLint з @typescript-eslint усе ще має ширше покриття. Можна запускати обидва: Biome для форматування й базового лінту, ESLint для AI-специфічних правил.

Що робити, якщо мій AI-агент постійно відтворює ті самі порушення лінту?

Агент обходить правило, а не виправляє первопричину. Для no-explicit-any це означає додавання твердження типу замість визначення фактичного типу. Для max-lines-per-function це означає винесення допоміжної функції, яка не робить нічого корисного, але опускає кількість рядків нижче порога. Жодне з цих рішень не пройде рев'ю коду. Правило лінту зловило симптом; первопричина — у промпті агента. Уточніть промпт, щоб задати обмеження типу або очікувану декомпозицію; агент надійніше дотримується явних вказівок щодо структури, ніж неявних правил. Якщо ви запускаєте складніше налаштування агента, обмеження роботи виділеним субагентом із вшитими в його інструкції обмеженнями зазвичай тримається краще, ніж один широкий промпт.

Чи замінює сервер ESLint MCP pre-commit-бар'єр?

Ні. Він зменшує те, наскільки часто агент генерує код, що не проходить бар'єр. Бар'єр усе одно запускається при кожному коміті. Інлайн-перевірка сервера MCP і примус через pre-commit-хук доповнюють одне одного, тож не видаляйте жодного з них.

Share

Більше з блогу

Продовжуйте читати.

Готові розгортати? Від $2,48/міс.

Незалежна хмара з 2008 року. AMD EPYC, NVMe, 40 Gbps. Повернення коштів за 14 днів.