Gli agenti AI possono generare codice che compila.
Ma questo non è ancora il livello che vuoi raggiungere. Il livello è codice che non importa cose che non usa mai, che non si tira fuori dal sistema di tipi con cast ad any, che non ignora i valori di errore restituiti e che non hardcoda credenziali che gosec dovrebbe intercettare prima della revisione.
I modelli AI si addestrano su vecchie risposte di Stack Overflow e propongono i pattern che hanno imparato lì, comprese API deprecate, annotazioni di tipo mancanti e funzioni tecnicamente corrette ma troppo grandi per essere revisionate in sicurezza. Ti serve un linter nel ciclo. Non come suggerimento, ma come gate.
Questa guida copre la configurazione per tre ecosistemi (Python con Ruff, TypeScript/JavaScript con la flat config di ESLint v10 e Go con golangci-lint) con regole ottimizzate specificamente per i pattern di errore che l'AI introduce. Poi spiega come rendere il gate molto più difficile da aggirare, in modo che l'agente non possa semplicemente saltare gli hook locali con --no-verify senza che un altro livello lo intercetti.
La configurazione è stratificata: il linting a livello di IDE intercetta i problemi in linea mentre l'agente scrive, gli hook pre-commit intercettano tutto ciò che arriva a un tentativo di commit e la CI intercetta tutto ciò che passa in locale. Ogni livello è indipendente e puoi scegliere le sezioni dei linguaggi che si applicano al tuo stack. Il livello di enforcement funziona allo stesso modo indipendentemente dal linguaggio su cui esegui il linting.
Riassunto rapido
- Ruff (Python), ESLint v10 (TS/JS) e golangci-lint (Go) hanno ciascuno regole specifiche che intercettano gli errori più comuni dell'AI
- Le configurazioni seguenti sono annotate; ogni regola è presente per un motivo
- Lefthook gestisce il gate pre-commit; l'hook afterFileEdit di Cursor esegue il lint in linea
- Quattro livelli di enforcement rendono molto più difficile per gli agenti AI saltare il gate con --no-verify
- La CI è l'ultima rete di sicurezza: gli agenti non possono passare --no-verify a GitHub Actions
Come configurare Ruff per il codice Python generato dall'AI
Ruff è il linter Python giusto per le codebase assistite dall'AI. È abbastanza veloce da essere eseguito a ogni salvataggio di file senza bloccare nulla, copre sia lo stile sia i veri errori logici e include il comportamento di formattazione (sostituendo Black) nello stesso binario. Le regole seguenti puntano agli specifici pattern di errore che i modelli AI introducono più spesso in Python.
Installare Ruff
pip install ruff
# or via uv (faster for new projects):
uv add --dev ruff
Tutto qui. Nessun ecosistema di plugin, nessuna negoziazione di dipendenze peer.
La configurazione 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
Le regole F si ripagano da sole immediatamente. Il codice AI genera istruzioni di import per pacchetti che poi non usa, e F401 (import inutilizzato) le intercetta tutte. Le regole UP intercettano le chiamate a pattern di API deprecate che l'AI ha imparato da risposte Python precedenti alla 3.10; UP006 e UP007 da sole segnalano decine di pattern di type-checking superflui. Le regole S (Bandit) intercettano i problemi di sicurezza: stringhe hardcodate che sembrano credenziali (S105/S106), shell injection tramite subprocess(shell=True) (S603/S607), scelte di crittografia deboli (S324).
Il ignore = [] è deliberato. Ogni eccezione che aggiungi a questo elenco è una categoria di errore dell'AI che hai deciso di consentire.
Eseguire 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 applica per default le correzioni sicure di Ruff, come rimuovere gli import inutilizzati o applicare correzioni di formattazione e lint immediate. Ruff dispone anche di correzioni non sicure, ma queste richiedono un'attivazione esplicita e dovrebbero essere revisionate con più attenzione. Rivedi manualmente tutto ciò che Ruff non può correggere in sicurezza.
Come configurare ESLint v10 per il codice TypeScript e JavaScript generato dall'AI
ESLint v10 ha eliminato il formato di configurazione legacy .eslintrc.*. Ora tutto è flat config in eslint.config.mjs. Se trovi un tutorial che usa .eslintrc.json o .eslintrc.js, si riferisce a ESLint v8 o v9, dove la sintassi è diversa. Usa quanto riportato qui sotto.
Le regole @typescript-eslint in questa configurazione puntano agli specifici tipi di errore che ricorrono spesso nel TypeScript generato dall'AI: la scappatoia any, le funzioni monolitiche difficili da revisionare e i valori hardcodati che dovrebbero essere costanti.
Installare ESLint v10 con supporto 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 è la versione attuale a giugno 2026. I pacchetti @typescript-eslint dovrebbero corrispondere alla tua versione di TypeScript; consulta il loro README per la matrice di compatibilità.
La configurazione 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',
},
},
];
La regola max-lines-per-function: 50 è la cosa più aggressiva in questa configurazione. La incontrerai di continuo alla prima esecuzione in una codebase assistita dall'AI. È proprio questo il punto. Dovresti incontrarla. Le funzioni che superano le 50 righe sono la prima cosa che diventa impossibile da comprendere quando revisioni l'output dell'AI in grandi volumi.
La regola max-params: 2 forza la decomposizione. I modelli AI imparano da codebase in cui le funzioni con cinque argomenti sono normali; la regola fa pressione contraria richiedendo all'agente di usare un oggetto di opzioni, il che è un design migliore e più facile da leggere.
Eseguire ESLint
npx eslint . # lint
npx eslint . --fix # auto-fix safe issues
npx eslint . --max-warnings 0 # CI mode — treats warnings as errors
Usa --max-warnings 0 nel tuo step di CI. Promuove gli avvisi no-console da "tecnicamente segnalati" ad "effettivamente bloccanti".
Opzionale: regole più severe per i file generati dall'AI
Se il tuo team usa una convenzione di denominazione dei file per contrassegnare il codice generato dall'AI (*.ai.ts, *-generated.ts o simili), puoi applicare regole più rigorose specificamente a quei file:
// 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
},
}
Come configurare golangci-lint per il codice Go generato dall'AI
golangci-lint è lo strumento standard per eseguire più linter in Go. Include gosec, errcheck, staticcheck e oltre 40 altri linter configurabili da un unico file YAML. Per il codice Go generato dall'AI, le regole critiche sono il controllo dei valori di errore restituiti e il rilevamento dei pattern di sicurezza: le due categorie di errore che i modelli AI trascurano più costantemente in Go.
Installare 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
La configurazione .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
Il pattern di gestione degli errori di Go è esplicito per scelta progettuale: ogni funzione che può fallire restituisce un valore di errore. I modelli AI lo comprendono ma lo sottovalutano; ometteranno i controlli di errore nei percorsi di codice non critici. errcheck rende quell'omissione un errore di lint.
gosec è il linter di sicurezza. Per il codice AI intercetta i pattern che l'AI ha assimilato dai tutorial Go precedenti al 2020: generazione di numeri casuali insicura (G404), funzioni di hash insicure (G401), problemi di permessi sui file (G306). Sono gli errori che non cogli nella revisione del codice perché sembrano sintatticamente normali.
Eseguire golangci-lint
golangci-lint run ./... # lint all packages
golangci-lint run --fix ./... # auto-fix where possible
Come integrare il linter negli hook pre-commit
Un hook pre-commit esegue il lint prima di ogni git commit e blocca il commit se il lint fallisce. Questo significa che l'agente AI non può fare il commit di codice che non rispetta le regole configurate. Deve prima correggere le violazioni.
Lefthook è l'opzione consigliata. È multipiattaforma, veloce e ha un pattern di configurazione che funziona specificamente con l'enforcement degli agenti AI (trattato nella sezione successiva).
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.
Il messaggio fail_text viene letto dagli agenti AI quando un commit fallisce. Il pattern è documentato in l'articolo di Liam Bigelow sull'enforcement del lint con Lefthook per Claude Code. Questo da solo non fermerà un agente determinato, ma gli fornisce l'istruzione successiva corretta ("correggi le violazioni di lint") invece di lasciargli dedurre un modo per aggirare il problema.
Alternativa: pre-commit (configurazioni solo Python)
Se lavori con uno stack solo Python e preferisci il 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 in linea tramite l'hook afterFileEdit
Se usi Cursor, puoi attivare il lint immediatamente quando l'AI modifica un file, prima ancora che tenti un commit. Crea .cursor/hooks.json nella radice del progetto:
{
"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}"
}
]
}
}
Questo si attiva ogni volta che l'AI di Cursor modifica un file. L'agente riceve il feedback del lint in linea, prima di considerare il compito completo, così la maggior parte delle violazioni viene corretta prima di raggiungere il gate pre-commit.
Come rendere il gate più difficile da aggirare per gli agenti AI
Quattro livelli di enforcement rendono molto più difficile per un agente AI saltare il gate pre-commit con --no-verify. Ciascuno punta a una superficie di attacco diversa: le policy in CLAUDE.md, una regola di deny di Claude Code, un hook PreToolUse e una rete di sicurezza in CI. Trattali come una configurazione stratificata, non come quattro garanzie indipendenti.
Gli agenti AI a volte passano --no-verify a git commit per saltare gli hook pre-commit quando il lint sta fallendo e l'agente ha deciso che i fallimenti sono "non correlati alle sue modifiche". La decisione non è sempre sbagliata, ma non dovresti lasciare che l'agente la prenda unilateralmente. L'intero scopo del gate di lint è che una persona ha stabilito la policy; il compito dell'agente è soddisfarla, non aggirarla.
Ecco ciascun livello e la superficie di attacco che copre.
Livello 1: documentare la policy in 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 legge CLAUDE.md all'avvio della sessione. Questo non è enforcement; l'agente può comunque tentare di aggirarlo. Ma elimina la via dell'"non lo sapevo" e fissa una policy chiara che l'agente deve scegliere attivamente di violare, cosa che ha meno probabilità di fare rispetto a dedurre un modo di aggirarla dal silenzio.
Livello 2: regola di deny di Claude Code
Aggiungi a .claude/settings.json:
{
"permissions": {
"deny": [
"Bash(git commit --no-verify*)"
]
}
}
Questo blocca l'invocazione esplicita. Una limitazione: la regola di deny usa la corrispondenza per prefisso, quindi intercetta --no-verify solo immediatamente dopo commit. Un agente sufficientemente creativo potrebbe strutturare la chiamata in modo diverso. Non affidarti solo a questo.
Livello 3: hook PreToolUse
Installa il pacchetto block-no-verify:
npm install --save-dev block-no-verify
Poi configuralo come hook PreToolUse nelle impostazioni di Claude Code. Si attiva prima di ogni chiamata di strumento ed esamina gli argomenti alla ricerca di --no-verify in sei sottocomandi git, non solo commit. Esce con codice diverso da zero per bloccare la chiamata prima che venga eseguita.
Il manuale di riferimento è pydevtools.com lapidario al riguardo: "il livello dell'hook è l'unico che applica la regola in modo affidabile". Usa questo insieme agli altri livelli; non trattare nessun hook locale come la garanzia completa.
Livello 4: rete di sicurezza in CI
La CI viene eseguita sul server, dove l'agente non ha una shell in cui inserire flag:
# .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
Gli agenti AI non possono passare --no-verify alla CI. GitHub Actions viene eseguito indipendentemente da qualsiasi cosa l'agente abbia fatto in locale. Se un commit fosse comunque passato con un lint fallito, la CI lo intercetta prima che venga unito.
Questa è l'ultima rete di sicurezza.
Bonus: server MCP di ESLint
Se usi Claude Code, c'è un livello proattivo che riduce la frequenza con cui ti imbatti del tutto nel gate.
Il server MCP di ESLint (@eslint/mcp) integra ESLint direttamente nel ciclo di strumenti dell'agente. L'agente può interrogare ESLint durante il compito, prima di tentare un commit. Installalo a livello globale:
npm install -g @eslint/mcp@latest
Aggiungi a .claude/settings.json:
{
"mcpServers": {
"eslint": {
"command": "npx",
"args": ["@eslint/mcp@latest"]
}
}
}
Con questa configurazione, l'agente può interrogare ESLint durante il compito. L'agente riceve il feedback del lint in linea e alcune violazioni possono essere corrette prima di raggiungere l'hook. Questo non sostituisce il gate, riduce il rumore sul gate.
Domande frequenti
Devo configurare tutti e tre i linter se lavoro con un solo linguaggio?
No. Configura il linter per il tuo linguaggio principale e il livello di enforcement. Se il tuo stack è solo TypeScript, configura ESLint e salta Ruff e golangci-lint. La sezione sulla prevenzione dell'aggiramento da parte dell'agente si applica indipendentemente dal linguaggio su cui esegui il linting.
Queste configurazioni romperanno la mia codebase esistente alla prima esecuzione?
Quasi certamente, e di proposito. Esegui prima ruff check --fix . o npx eslint . --fix per autocorreggere le violazioni sicure. Ciò che resta dopo l'autocorrezione è l'elenco da revisionare manualmente: cast no-explicit-any, funzioni che superano le 50 righe, gestione degli errori mancante. Affrontali progressivamente. Non aggiungere regole ignore per evitare di occupartene.
A questo punto Biome è un sostituto di ESLint?
Biome v2.5.0 (rilasciato a giugno 2026) è competitivo sulla formattazione e sulle regole di lint di base. È più veloce di ESLint e ha zero overhead di configurazione se lo installi tramite bun x ultracite@latest init. Per i team che vogliono un unico strumento e non hanno bisogno della piena profondità delle regole @typescript-eslint, Biome è una scelta ragionevole. Per le regole specifiche per l'AI di questa guida (max-params, no-magic-numbers, max-lines-per-function con soglie pensate per l'AI), ESLint con @typescript-eslint ha ancora una copertura maggiore. Puoi usare entrambi: Biome per la formattazione e il lint di base, ESLint per le regole specifiche per l'AI.
Cosa succede se il mio agente AI continua a rigenerare le stesse violazioni di lint?
L'agente sta aggirando la regola invece di correggere il problema sottostante. Per no-explicit-any, questo significa aggiungere un'asserzione di tipo invece di definire il tipo reale. Per max-lines-per-function, significa estrarre una funzione di supporto che non fa nulla di utile ma porta il conteggio delle righe sotto la soglia. Nessuna delle due soluzioni passa la revisione del codice. La regola di lint ha colto il sintomo; la causa profonda è il prompt dell'agente. Affina il prompt per specificare il vincolo di tipo o la decomposizione attesa; l'agente seguirà indicazioni strutturali esplicite in modo più affidabile rispetto alle regole implicite. Se stai usando una configurazione di agenti più articolata, affidare il lavoro a un subagente dedicato con i vincoli integrati nelle sue istruzioni tende a reggere meglio di un singolo prompt generico.
Il server MCP di ESLint sostituisce il gate pre-commit?
No. Riduce la frequenza con cui l'agente genera codice che non supera il gate. Il gate viene comunque eseguito a ogni commit. Il controllo in linea del server MCP e l'enforcement dell'hook pre-commit sono complementari, quindi non rimuovere nessuno dei due.