Os agentes de IA conseguem gerar código que compila.
Mesmo assim, não é esse o nível que se pretende. O nível é código que não importa coisas que nunca usa, não recorre a any-cast para escapar ao sistema de tipos, não ignora retornos de erro e não fixa credenciais no código que o gosec deveria apanhar antes da revisão.
Os modelos de IA treinam com respostas antigas do Stack Overflow e produzem os padrões que aprenderam aí, incluindo APIs descontinuadas, anotações de tipo em falta e funções tecnicamente corretas mas demasiado grandes para rever em segurança. É preciso um linter no ciclo. Não como sugestão, mas como barreira.
Este guia cobre a configuração para três ecossistemas (Python com Ruff, TypeScript/JavaScript com a configuração flat do ESLint v10 e Go com golangci-lint) com regras ajustadas especificamente para os padrões de falha que a IA introduz. Depois, explica como tornar a barreira muito mais difícil de contornar, para que o agente não possa simplesmente ignorar os hooks locais com --no-verify sem que outra camada o apanhe.
A configuração é faseada: o linting ao nível do IDE apanha problemas em tempo real enquanto o agente escreve, os hooks de pre-commit apanham tudo o que chega a uma tentativa de commit e o CI apanha tudo o que passa localmente. Cada camada é independente, e pode escolher as secções de linguagem que se aplicam à sua stack. A camada de imposição funciona da mesma forma, independentemente da linguagem que está a verificar.
Resumo rápido
- Ruff (Python), ESLint v10 (TS/JS) e golangci-lint (Go) têm, cada um, regras específicas que apanham as falhas mais comuns da IA
- As configurações abaixo estão anotadas; cada regra está lá por um motivo
- O Lefthook trata da barreira de pre-commit; o hook afterFileEdit do Cursor executa o lint em tempo real
- Quatro camadas de imposição tornam muito mais difícil para os agentes de IA ignorarem a barreira com --no-verify
- O CI é a última linha de defesa: os agentes não podem passar --no-verify para o GitHub Actions
Como Configurar o Ruff para Código Python de IA
O Ruff é o linter Python certo para bases de código assistidas por IA. É rápido o suficiente para correr a cada gravação de ficheiro sem bloquear nada, cobre tanto o estilo como erros de lógica reais e inclui comportamento de formatador (substituindo o Black) no mesmo binário. As regras abaixo visam os padrões de falha específicos que os modelos de IA introduzem com mais frequência em Python.
Instalar o Ruff
pip install ruff
# or via uv (faster for new projects):
uv add --dev ruff
É só isto. Sem ecossistema de plugins, sem negociação de dependências peer.
A Configuração do 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
As regras F compensam-se de imediato. O código de IA gera instruções de import para pacotes que acaba por não usar, e a F401 (import não usado) apanha cada uma delas. As regras UP apanham chamadas a padrões de API descontinuados que a IA aprendeu com respostas de Python anteriores ao 3.10; só a UP006 e a UP007 sinalizam dezenas de padrões de verificação de tipos desnecessários. As regras S (Bandit) apanham as falhas de segurança: strings fixas no código que parecem credenciais (S105/S106), injeção de shell via subprocess(shell=True) (S603/S607), escolhas de criptografia fraca (S324).
O ignore = [] é deliberado. Cada exceção que adiciona a esta lista é uma classe de falha de IA que decidiu permitir.
Executar o 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)
O --fix aplica por predefinição as correções seguras do Ruff, como remover imports não usados ou aplicar formatação e correções de lint diretas. O Ruff também tem correções inseguras, mas essas exigem adesão explícita e devem ser revistas com mais cuidado. Reveja manualmente tudo o que o Ruff não conseguir corrigir em segurança.
Como Configurar o ESLint v10 para Código TypeScript e JavaScript de IA
O ESLint v10 abandonou o formato de configuração legado .eslintrc.*. Tudo é agora a configuração flat em eslint.config.mjs. Se encontrar um tutorial a usar .eslintrc.json ou .eslintrc.js, está a visar o ESLint v8 ou v9, onde a sintaxe é diferente. Use o que está abaixo.
As regras @typescript-eslint nesta configuração visam os modos de falha específicos que surgem repetidamente em TypeScript gerado por IA: a saída de emergência any, funções monolíticas difíceis de rever e valores fixos no código que deviam ser constantes.
Instalar o ESLint v10 com Suporte de 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
O ESLint v10.5.0 é o atual em junho de 2026. Os pacotes @typescript-eslint devem corresponder à sua versão de TypeScript; consulte o README deles para a matriz de compatibilidade.
A Configuração do 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',
},
},
];
A regra max-lines-per-function: 50 é a coisa mais agressiva nesta configuração. Vai esbarrar nela constantemente na primeira execução numa base de código assistida por IA. É essa a intenção. Deve mesmo esbarrar nela. As funções que excedem 50 linhas são a primeira coisa que se torna impossível de raciocinar quando se revê output de IA em grande volume.
A regra max-params: 2 força a decomposição. Os modelos de IA aprendem com bases de código onde funções de cinco argumentos são normais; a regra contraria isso ao exigir que o agente use um objeto de opções, o que é melhor design e mais fácil de ler.
Executar o ESLint
npx eslint . # lint
npx eslint . --fix # auto-fix safe issues
npx eslint . --max-warnings 0 # CI mode — treats warnings as errors
Use --max-warnings 0 no seu passo de CI. Promove os avisos no-console de "tecnicamente registados" para "efetivamente bloqueantes".
Opcional: Regras Mais Rígidas para Ficheiros Gerados por IA
Se a sua equipa usar uma convenção de nomenclatura de ficheiros para marcar código gerado por IA (*.ai.ts, *-generated.ts ou semelhante), pode aplicar regras mais apertadas especificamente a esses ficheiros:
// 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
},
}
Como Configurar o golangci-lint para Código Go de IA
O golangci-lint é o executor padrão de múltiplos linters para Go. Vem com gosec, errcheck, staticcheck e mais de 40 outros linters configuráveis a partir de um único ficheiro YAML. Para código Go gerado por IA, as regras críticas são a verificação de retorno de erros e a deteção de padrões de segurança: as duas categorias de falha que os modelos de IA falham mais consistentemente em Go.
Instalar o 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
A Configuração do .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
O padrão de tratamento de erros do Go é explícito por design: cada função que pode falhar devolve um valor de erro. Os modelos de IA percebem isto mas subvalorizam-no; omitem verificações de erro em caminhos de código não críticos. O errcheck transforma essa omissão numa falha de lint.
O gosec é o linter de segurança. Para código de IA, apanha os padrões que a IA absorveu de tutoriais de Go anteriores a 2020: geração insegura de números aleatórios (G404), funções de hash inseguras (G401), problemas de permissões de ficheiros (G306). Estes são os erros que não se apanham na revisão de código porque parecem sintaticamente normais.
Executar o golangci-lint
golangci-lint run ./... # lint all packages
golangci-lint run --fix ./... # auto-fix where possible
Como Ligar o Linter aos Hooks de Pre-commit
Um hook de pre-commit executa o lint antes de cada git commit e bloqueia o commit se o lint falhar. Isto significa que o agente de IA não consegue fazer commit de código que falhe as regras configuradas. Tem de corrigir primeiro as violações.
O Lefthook é a opção recomendada. É multiplataforma, rápido e tem um padrão de configuração que funciona especificamente com a imposição a agentes de IA (abordada na próxima secção).
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.
A mensagem fail_text é lida pelos agentes de IA quando um commit falha. O padrão está documentado em no artigo de Liam Bigelow sobre imposição de lint com Lefthook para Claude Code. Isto, por si só, não detém um agente determinado, mas dá-lhe a instrução seguinte correta ("corrigir violações de lint") em vez de o deixar inferir uma solução de contorno.
Alternativa: pre-commit (configurações só de Python)
Se estiver numa stack só de Python e preferir a 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 em Tempo Real via o Hook afterFileEdit
Se usar o Cursor, pode acionar o lint de imediato quando a IA modifica um ficheiro, antes mesmo de tentar um commit. Crie o ficheiro .cursor/hooks.json na raiz do seu projeto:
{
"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}"
}
]
}
}
Isto dispara sempre que a IA do Cursor modifica um ficheiro. O agente recebe feedback do lint em tempo real, antes de considerar a tarefa concluída, pelo que a maioria das violações é corrigida antes de chegar à barreira de pre-commit.
Como Tornar a Barreira Mais Difícil de Contornar pelos Agentes de IA
Quatro camadas de imposição tornam muito mais difícil para um agente de IA ignorar a barreira de pre-commit com --no-verify. Cada uma visa uma superfície de ataque diferente: a política no CLAUDE.md, uma regra de negação do Claude Code, um hook PreToolUse e uma última linha de defesa no CI. Trate-as como uma configuração em camadas, não como quatro garantias independentes.
Os agentes de IA por vezes passam --no-verify ao git commit para ignorar os hooks de pre-commit quando o lint está a falhar e o agente decidiu que as falhas são "alheias às suas alterações". A decisão nem sempre está errada, mas não deve deixar o agente tomá-la unilateralmente. Todo o propósito da barreira de lint é que um humano definiu a política; o trabalho do agente é satisfazê-la, não contorná-la.
Eis cada camada e a superfície de ataque que cobre.
Camada 1: Documentar a Política no 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.
O Claude Code lê o CLAUDE.md no início da sessão. Isto não é imposição; o agente ainda pode tentar o contorno. Mas elimina o caminho do "eu não sabia" e estabelece uma política clara que o agente tem de optar ativamente por violar, o que é menos provável de fazer do que inferir uma solução de contorno a partir do silêncio.
Camada 2: Regra de Negação do Claude Code
Adicione ao .claude/settings.json:
{
"permissions": {
"deny": [
"Bash(git commit --no-verify*)"
]
}
}
Isto bloqueia a invocação explícita. Uma limitação: a regra de negação usa correspondência por prefixo, pelo que só apanha --no-verify imediatamente a seguir a commit. Um agente suficientemente criativo poderia estruturar a chamada de forma diferente. Não confie só nisto.
Camada 3: Hook PreToolUse
Instale o pacote block-no-verify:
npm install --save-dev block-no-verify
Depois, configure-o como um hook PreToolUse nas definições do Claude Code. Este dispara antes de cada chamada de ferramenta e examina os argumentos à procura de --no-verify em seis subcomandos git, não apenas commit. Termina com código diferente de zero para bloquear a chamada antes de ela executar.
O manual em pydevtools.com é direto quanto a isso: "a camada de hook é a única que impõe a regra de forma fiável." Use isto com as outras camadas; não trate nenhum hook local como a garantia completa.
Camada 4: Última Linha de Defesa no CI
O CI corre no servidor, onde o agente não tem shell para passar flags:
# .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
Os agentes de IA não podem passar --no-verify para o CI. O GitHub Actions corre independentemente do que o agente fez localmente. Se um commit, de alguma forma, passou com lint a falhar, o CI apanha-o antes de fazer merge.
Esta é a última linha de defesa.
Bónus: Servidor MCP do ESLint
Se usar o Claude Code, há uma camada proativa que reduz a frequência com que esbarra na barreira.
O servidor MCP do ESLint (@eslint/mcp) integra o ESLint diretamente no ciclo de ferramentas do agente. O agente pode consultar o ESLint durante a tarefa, antes de tentar um commit. Instale-o globalmente:
npm install -g @eslint/mcp@latest
Adicione ao .claude/settings.json:
{
"mcpServers": {
"eslint": {
"command": "npx",
"args": ["@eslint/mcp@latest"]
}
}
}
Com isto configurado, o agente pode consultar o ESLint durante a tarefa. O agente recebe feedback do lint em tempo real, e algumas violações podem ser corrigidas antes de chegarem ao hook. Isto não substitui a barreira, reduz o ruído nela.
Perguntas frequentes
Preciso de configurar os três linters se só trabalho numa linguagem?
Não. Configure o linter para a sua linguagem principal e a camada de imposição. Se a sua stack for só de TypeScript, configure o ESLint e ignore o Ruff e o golangci-lint. A secção de prevenção de contorno por agentes aplica-se independentemente da linguagem que está a verificar.
Estas configurações vão quebrar a minha base de código existente na primeira execução?
Quase de certeza, e de propósito. Execute primeiro ruff check --fix . ou npx eslint . --fix para corrigir automaticamente as violações seguras. O que restar após a correção automática é a lista de revisão manual: casts no-explicit-any, funções que excedem 50 linhas, tratamento de erros em falta. Trabalhe esses pontos progressivamente. Não adicione regras de ignore para evitar lidar com eles.
O Biome é, a esta altura, um substituto do ESLint?
O Biome v2.5.0 (lançado em junho de 2026) é competitivo em formatação e regras básicas de lint. É mais rápido do que o ESLint e tem zero sobrecarga de configuração se o instalar via bun x ultracite@latest init. Para equipas que querem uma única ferramenta e não precisam de toda a profundidade de regras do @typescript-eslint, o Biome é uma escolha razoável. Para as regras específicas de IA neste guia (max-params, no-magic-numbers, max-lines-per-function com limiares orientados para IA), o ESLint com @typescript-eslint ainda tem mais cobertura. Pode correr ambos: Biome para formatação e lint básico, ESLint para as regras específicas de IA.
E se o meu agente de IA continuar a regenerar as mesmas violações de lint?
O agente está a contornar a regra em vez de corrigir o problema subjacente. Para no-explicit-any, isto significa adicionar uma asserção de tipo em vez de definir o tipo real. Para max-lines-per-function, significa extrair uma função auxiliar que não faz nada de útil mas baixa a contagem de linhas para abaixo do limiar. Nenhuma das soluções passa na revisão de código. A regra de lint apanhou o sintoma; a causa raiz é o prompt do agente. Refine o prompt para especificar a restrição de tipo ou a decomposição esperada; o agente seguirá orientações de estrutura explícitas de forma mais fiável do que regras implícitas. Se estiver a correr uma configuração de agente mais elaborada, delimitar o trabalho a um subagente dedicado, com as restrições integradas nas suas instruções, tende a aguentar-se melhor do que um único prompt abrangente.
O servidor MCP do ESLint substitui a barreira de pre-commit?
Não. Reduz a frequência com que o agente gera código que falha a barreira. A barreira continua a correr em cada commit. A verificação em tempo real do servidor MCP e a imposição do hook de pre-commit são complementares, por isso não remova nenhuma.