メインコンテンツへスキップ
50% off 全プラン対象、期間限定。月額 $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キャストせず、エラーの戻り値を無視せず、レビュー前にgosecが検出すべき認証情報をハードコードしないコードです。

AIモデルは古いStack Overflowの回答で学習し、そこで覚えたパターンをそのまま出力します。非推奨のAPI、欠落した型アノテーション、技術的には正しくても安全にレビューするには大きすぎる関数などです。ループの中にリンターが必要です。提案としてではなく、ゲートとして。

このガイドでは、3つのエコシステム(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ゲートを処理し、CursorのafterFileEditフックがインラインでリントを実行します
  • 4つの強制レイヤーにより、AIエージェントが--no-verifyでゲートをスキップするのははるかに難しくなります
  • CIが最後の防壁です。エージェントはGitHub Actionsに--no-verifyを渡せません

Python AIコード向けのRuff設定方法

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

RuffはAI支援コードベースに適したPythonリンターです。ファイルを保存するたびに何も妨げずに実行できるほど高速で、スタイルと実際のロジックエラーの両方をカバーし、フォーマッター機能(Blackの置き換え)を同じバイナリに同梱しています。以下のルールは、AIモデルがPythonで最も頻繁に持ち込む固有の失敗パターンを狙い撃ちします。

Ruffのインストール

pip install ruff

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

これだけです。プラグインのエコシステムも、ピア依存関係の調整もありません。

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ルールは、AIが3.10以前のPythonの回答から学んだ非推奨APIパターンの呼び出しを検出します。UP006とUP007だけでも、不要な型チェックパターンを何十件もフラグします。S(Bandit)ルールはセキュリティ上の失敗を捕捉します。認証情報のように見えるハードコードされた文字列(S105/S106)、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が安全に修正できないものは手動でレビューしてください。

TypeScriptとJavaScript AIコード向けのESLint v10設定方法

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

ESLint v10は従来の.eslintrc.*設定形式を廃止しました。すべてがeslint.config.mjsのflat configになりました。.eslintrc.jsonや.eslintrc.jsを使ったチュートリアルを見かけたら、それは構文の異なるESLint v8またはv9を対象にしています。以下のものを使ってください。

この設定の@typescript-eslintルールは、AI生成のTypeScriptで繰り返し現れる固有の失敗モードを狙います。anyという逃げ道、レビューが難しい巨大な関数、定数にすべきハードコードされた値などです。

TypeScriptサポート付きESLint v10のインストール

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年6月時点で最新です。@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モデルは引数5つの関数が普通であるコードベースから学習します。このルールはエージェントにオプションオブジェクトの使用を要求して押し戻し、それはより良い設計で読みやすくなります。

ESLintの実行

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

CIステップでは --max-warnings 0 を使ってください。これにより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
  },
}

Go AIコード向けのgolangci-lint設定方法

golangci-lintはGoの標準的なマルチリンターランナーです。gosec、errcheck、staticcheck、その他40以上のリンターを同梱し、単一のYAMLファイルから設定できます。AI生成のGoコードでは、重要なルールはエラー戻り値のチェックとセキュリティパターンの検出です。これらはAIモデルがGoで最も一貫して見逃す2つの失敗カテゴリです。

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のエラー処理パターンは設計上明示的です。失敗しうるすべての関数はエラー値を返します。AIモデルはこれを理解していますが軽視しがちで、重要でないコードパスではエラーチェックを省略します。errcheckはその省略をリント失敗にします。

gosecはセキュリティリンターです。AIコードでは、AIが2020年以前のGoチュートリアルから拾ったパターンを捕捉します。安全でない乱数生成(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エージェントに読まれます。このパターンは Claude Code向けLefthookリント強制に関するLiam Bigelowの解説に文書化されています。これだけでは決意の固いエージェントを止められませんが、回避策を推測させるのではなく、正しい次の指示(「リント違反を修正せよ」)を与えます。

代替案: 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}"
      }
    ]
  }
}

これはCursorのAIがファイルを変更するたびに発火します。エージェントはタスクを完了とみなす前に、インラインでリントのフィードバックを受け取るため、ほとんどの違反は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

4つの強制レイヤーにより、AIエージェントが--no-verifyでpre-commitゲートをスキップするのははるかに難しくなります。それぞれが異なる攻撃面を狙います。CLAUDE.md内のポリシー、Claude Codeのdenyルール、PreToolUseフック、そしてCIの防壁です。これらは4つの独立した保証としてではなく、層を成したセットアップとして扱ってください。

AIエージェントは、リントが失敗していて、その失敗が「自分の変更とは無関係」だとエージェントが判断したとき、pre-commitフックをスキップするためにgit commitに--no-verifyを渡すことがあります。その判断は常に間違っているわけではありませんが、エージェントに一方的に下させるべきではありません。リントゲートの要点は、人間がポリシーを設定したという点にあります。エージェントの仕事はそれを満たすことであって、迂回することではありません。

以下に各層と、それがカバーする攻撃面を示します。

レイヤー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: Claude Codeのdenyルール

.claude/settings.jsonに追加します。

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

これは明示的な呼び出しをブロックします。1つの限界として、denyルールは前方一致を使うため、commit直後の--no-verifyしか捕捉しません。十分に創造的なエージェントなら呼び出しを別の形に構成できます。これだけに頼らないでください。

レイヤー3: PreToolUseフック

block-no-verifyパッケージをインストールします。

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

次に、それをClaude Codeの設定でPreToolUseフックとして構成します。これはすべてのツール呼び出しの前に発火し、commitだけでなく6つのgitサブコマンドにわたって引数に--no-verifyがないか調べます。呼び出しが実行される前にブロックするため、非ゼロで終了します。

次のハンドブックは 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エージェントはCIに--no-verifyを渡せません。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に問い合わせできます。エージェントはインラインでリントのフィードバックを受け取り、一部の違反はフックに到達する前に修正できます。これはゲートを置き換えるものではなく、ゲートにかかるノイズを減らすものです。

よくある質問

1つの言語でしか作業しないなら、3つのリンターすべてを設定する必要はありますか?

いいえ。主要な言語のリンターと強制レイヤーを設定してください。スタックがTypeScriptのみなら、ESLintを設定してRuffとgolangci-lintは省略します。エージェント回避防止のセクションは、どの言語をリントするかに関係なく適用されます。

これらの設定は初回実行で既存のコードベースを壊しますか?

ほぼ確実に壊しますが、それは意図的です。まず ruff check --fix . または npx eslint . --fix を実行して、安全な違反を自動修正してください。自動修正後に残るものが手動レビューのリストです。no-explicit-anyのキャスト、50行を超える関数、欠落したエラー処理などです。それらを段階的に片付けていってください。対処を避けるためにignoreルールを追加しないでください。

現時点でBiomeはESLintの代替になりますか?

Biome v2.5.0(2026年6月リリース)は、フォーマットと基本的なリントルールで競争力があります。ESLintより高速で、bun x ultracite@latest init でインストールすれば設定のオーバーヘッドはゼロです。単一のツールを望み、@typescript-eslintのルールの深さを必要としないチームには、Biomeは妥当な選択肢です。このガイドにあるAI特有のルール(max-params、no-magic-numbers、AI向けに調整した閾値のmax-lines-per-function)については、@typescript-eslint付きの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日間返金保証。