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設定方法
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は従来の.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エージェントによるゲート回避を難しくする方法
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フックの強制は補完的なので、どちらも削除しないでください。