Los agentes de IA pueden generar código que compila.
Aun así, ese no es el nivel que quieres. El nivel es código que no importa cosas que nunca usa, que no se escapa del sistema de tipos a base de any-cast, que no ignora los valores de error que se devuelven y que no deja credenciales escritas a fuego que gosec debería detectar antes de la revisión.
Los modelos de IA se entrenan con respuestas antiguas de Stack Overflow y generan los patrones que aprendieron ahí, incluidas APIs obsoletas, anotaciones de tipo ausentes y funciones que son técnicamente correctas pero demasiado grandes para revisarlas con seguridad. Necesitas un linter en el bucle. No como una sugerencia, sino como una barrera.
Esta guía cubre la configuración para tres ecosistemas (Python con Ruff, TypeScript/JavaScript con la configuración flat de ESLint v10 y Go con golangci-lint) con reglas ajustadas específicamente para los patrones de fallo que introduce la IA. Después explica cómo hacer que la barrera sea mucho más difícil de eludir, para que el agente no pueda simplemente saltarse los hooks locales con --no-verify sin que otra capa lo detecte.
La configuración está organizada por niveles: el linting a nivel de IDE detecta los problemas en línea mientras el agente escribe, los hooks pre-commit detectan cualquier cosa que llegue a un intento de commit y la CI detecta cualquier cosa que pase en local. Cada capa es independiente y puedes elegir las secciones de lenguaje que apliquen a tu stack. La capa de aplicación funciona igual sin importar qué lenguaje analices.
Resumen rápido
- Ruff (Python), ESLint v10 (TS/JS) y golangci-lint (Go) tienen cada uno reglas específicas que detectan los fallos más comunes de la IA
- Las configuraciones de abajo están comentadas; cada regla está ahí por una razón
- Lefthook gestiona la barrera pre-commit; el hook afterFileEdit de Cursor ejecuta el linter en línea
- Cuatro capas de aplicación hacen mucho más difícil que los agentes de IA se salten la barrera con --no-verify
- La CI es el último respaldo: los agentes no pueden pasar --no-verify a GitHub Actions
Cómo configurar Ruff para código Python de IA
Ruff es el linter de Python adecuado para bases de código asistidas por IA. Es lo bastante rápido como para ejecutarse en cada guardado de archivo sin bloquear nada, cubre tanto el estilo como errores reales de lógica, e incorpora el comportamiento de formateador (reemplazando a Black) en el mismo binario. Las reglas de abajo apuntan a los patrones de fallo específicos que los modelos de IA introducen con más frecuencia en Python.
Instalar Ruff
pip install ruff
# or via uv (faster for new projects):
uv add --dev ruff
Eso es todo. Sin ecosistema de plugins, sin negociación de dependencias entre pares.
La configuración de 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
Las reglas F se amortizan de inmediato. El código de IA genera sentencias de importación para paquetes que acaba sin usar, y F401 (importación sin usar) detecta cada una. Las reglas UP detectan llamadas a patrones de API obsoletos que la IA aprendió de respuestas de Python anteriores a la 3.10; solo UP006 y UP007 ya marcan docenas de patrones innecesarios de comprobación de tipos. Las reglas S (Bandit) detectan los fallos de seguridad: cadenas escritas a fuego que parecen credenciales (S105/S106), inyección de shell vía subprocess(shell=True) (S603/S607), elecciones débiles de criptografía (S324).
El ignore = [] es deliberado. Cada excepción que añades a esta lista es una clase de fallo de IA que has decidido permitir.
Ejecutar 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 aplica por defecto las correcciones seguras de Ruff, como eliminar importaciones sin usar o aplicar correcciones directas de formato y lint. Ruff también tiene correcciones inseguras, pero esas requieren una activación explícita y deben revisarse con más cuidado. Revisa manualmente cualquier cosa que Ruff no pueda corregir de forma segura.
Cómo configurar ESLint v10 para código TypeScript y JavaScript de IA
ESLint v10 abandonó el formato de configuración heredado .eslintrc.*. Ahora todo es configuración flat en eslint.config.mjs. Si encuentras un tutorial que usa .eslintrc.json o .eslintrc.js, está dirigido a ESLint v8 o v9, donde la sintaxis es distinta. Usa lo que aparece a continuación.
Las reglas de @typescript-eslint de esta configuración apuntan a los modos de fallo concretos que aparecen una y otra vez en el TypeScript generado por IA: la salida de emergencia con any, las funciones monolíticas difíciles de revisar y los valores escritos a fuego que deberían ser constantes.
Instalar ESLint v10 con soporte para 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 es la versión actual a fecha de junio de 2026. Los paquetes de @typescript-eslint deben coincidir con tu versión de TypeScript; consulta su README para ver la matriz de compatibilidad.
La configuración de 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 regla max-lines-per-function: 50 es lo más agresivo de esta configuración. La incumplirás constantemente en la primera ejecución sobre una base de código asistida por IA. Esa es la idea. Deberías estar incumpliéndola. Las funciones que superan las 50 líneas son lo primero que se vuelve imposible de razonar cuando revisas la salida de la IA a gran volumen.
La regla max-params: 2 fuerza la descomposición. Los modelos de IA aprenden de bases de código donde las funciones de cinco argumentos son normales; la regla los frena exigiendo que el agente use un objeto de opciones, lo cual es mejor diseño y más fácil de leer.
Ejecutar 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 en tu paso de CI. Eleva las advertencias de no-console de «técnicamente anotadas» a «realmente bloqueantes».
Opcional: reglas más estrictas para archivos generados por IA
Si tu equipo usa una convención de nombres de archivo para marcar el código generado por IA (*.ai.ts, *-generated.ts o similar), puedes aplicar reglas más estrictas a esos archivos en concreto:
// 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
},
}
Cómo configurar golangci-lint para código Go de IA
golangci-lint es el ejecutor de múltiples linters estándar para Go. Incluye gosec, errcheck, staticcheck y más de 40 linters adicionales configurables desde un solo archivo YAML. Para el código Go generado por IA, las reglas críticas son la comprobación de los valores de error que se devuelven y la detección de patrones de seguridad: las dos categorías de fallo que los modelos de IA pasan por alto con más constancia en Go.
Instalar 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 configuración de .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
El patrón de manejo de errores de Go es explícito por diseño: cada función que puede fallar devuelve un valor de error. Los modelos de IA entienden esto pero lo subestiman; omiten las comprobaciones de error en rutas de código no críticas. errcheck convierte esa omisión en un fallo de lint.
gosec es el linter de seguridad. Para el código de IA detecta los patrones que la IA recogió de tutoriales de Go anteriores a 2020: generación insegura de números aleatorios (G404), funciones hash inseguras (G401), problemas de permisos de archivos (G306). Estos son los errores que no detectas en la revisión de código porque parecen sintácticamente normales.
Ejecutar golangci-lint
golangci-lint run ./... # lint all packages
golangci-lint run --fix ./... # auto-fix where possible
Cómo conectar el linter a los hooks pre-commit
Un hook pre-commit ejecuta el linter antes de cada git commit y bloquea el commit si el linter falla. Esto significa que el agente de IA no puede hacer commit de código que incumpla tus reglas configuradas. Primero tiene que corregir las infracciones.
Lefthook es la opción recomendada. Es multiplataforma, rápida y tiene un patrón de configuración que funciona específicamente con la aplicación sobre agentes de IA (que se cubre en la siguiente sección).
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.
El mensaje fail_text lo leen los agentes de IA cuando un commit falla. El patrón está documentado en el artículo de Liam Bigelow sobre la aplicación del linter con Lefthook para Claude Code. Esto por sí solo no detendrá a un agente decidido, pero le da la instrucción correcta para el siguiente paso («corrige las infracciones del linter») en lugar de dejar que deduzca una solución alternativa.
Alternativa: pre-commit (configuraciones solo de Python)
Si trabajas con un stack solo de Python y prefieres el 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 en línea mediante el hook afterFileEdit
Si usas Cursor, puedes lanzar el linter en cuanto la IA modifica un archivo, antes incluso de que intente un commit. Crea .cursor/hooks.json en la raíz de tu proyecto:
{
"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}"
}
]
}
}
Esto se dispara cada vez que la IA de Cursor modifica un archivo. El agente recibe la información del linter en línea, antes de dar la tarea por terminada, así que la mayoría de las infracciones se corrigen antes de llegar a la barrera pre-commit.
Cómo hacer que la barrera sea más difícil de eludir por los agentes de IA
Cuatro capas de aplicación hacen mucho más difícil que un agente de IA se salte la barrera pre-commit con --no-verify. Cada una apunta a una superficie de ataque distinta: la política en CLAUDE.md, una regla de denegación de Claude Code, un hook PreToolUse y un respaldo en la CI. Trátalas como una configuración por capas, no como cuatro garantías independientes.
Los agentes de IA a veces pasan --no-verify a git commit para saltarse los hooks pre-commit cuando el linter falla y el agente ha decidido que los fallos son «ajenos a sus cambios». La decisión no siempre es errónea, pero no deberías dejar que el agente la tome de forma unilateral. Todo el sentido de la barrera del linter es que un humano fijó la política; el trabajo del agente es satisfacerla, no rodearla.
Aquí tienes cada capa y la superficie de ataque que cubre.
Capa 1: documentar la política en 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 lee CLAUDE.md al inicio de la sesión. Esto no es aplicación forzosa; el agente todavía puede intentar la elusión. Pero elimina la vía del «no lo sabía» y fija una política clara que el agente debe elegir activamente violar, algo menos probable que deducir una solución alternativa a partir del silencio.
Capa 2: regla de denegación de Claude Code
Añade a .claude/settings.json:
{
"permissions": {
"deny": [
"Bash(git commit --no-verify*)"
]
}
}
Esto bloquea la invocación explícita. Una limitación: la regla de denegación usa coincidencia por prefijo, así que solo detecta --no-verify inmediatamente después de commit. Un agente lo bastante creativo podría estructurar la llamada de otra forma. No te fíes solo de esto.
Capa 3: hook PreToolUse
Instala el paquete block-no-verify:
npm install --save-dev block-no-verify
Luego configúralo como un hook PreToolUse en los ajustes de Claude Code. Esto se dispara antes de cada llamada a una herramienta y examina los argumentos en busca de --no-verify en seis subcomandos de git, no solo commit. Sale con un código distinto de cero para bloquear la llamada antes de que se ejecute.
El manual en pydevtools.com es contundente al respecto: «la capa del hook es la única que aplica la regla de forma fiable». Úsalo junto con las demás capas; no trates ningún hook local como la garantía completa.
Capa 4: respaldo en la CI
La CI se ejecuta en el servidor, donde el agente no tiene ningún shell al que pasar 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
Los agentes de IA no pueden pasar --no-verify a la CI. GitHub Actions se ejecuta de forma independiente de lo que el agente hiciera en local. Si un commit ha pasado de alguna forma con el linter fallando, la CI lo detecta antes de que se fusione.
Este es el último respaldo.
Extra: servidor MCP de ESLint
Si usas Claude Code, hay una capa proactiva que reduce con qué frecuencia llegas siquiera a la barrera.
El servidor MCP de ESLint (@eslint/mcp) integra ESLint directamente en el bucle de herramientas del agente. El agente puede consultar a ESLint durante la tarea, antes de intentar un commit. Instálalo de forma global:
npm install -g @eslint/mcp@latest
Añade a .claude/settings.json:
{
"mcpServers": {
"eslint": {
"command": "npx",
"args": ["@eslint/mcp@latest"]
}
}
}
Con esto configurado, el agente puede consultar a ESLint durante la tarea. El agente recibe la información del linter en línea, y algunas infracciones pueden corregirse antes de llegar al hook. Esto no reemplaza la barrera, reduce el ruido en la barrera.
Preguntas frecuentes
¿Necesito configurar los tres linters si solo trabajo en un lenguaje?
No. Configura el linter para tu lenguaje principal y la capa de aplicación. Si tu stack es solo de TypeScript, configura ESLint y omite Ruff y golangci-lint. La sección de prevención de elusión del agente aplica sin importar qué lenguaje analices.
¿Estas configuraciones romperán mi base de código actual en la primera ejecución?
Casi con toda seguridad, y a propósito. Ejecuta primero ruff check --fix . o npx eslint . --fix para autocorregir las infracciones seguras. Lo que queda después de la autocorrección es la lista de revisión manual: casts de no-explicit-any, funciones que superan las 50 líneas, manejo de errores ausente. Ve resolviéndolas de forma progresiva. No añadas reglas a ignore para evitar lidiar con ellas.
¿Es Biome un reemplazo de ESLint a estas alturas?
Biome v2.5.0 (lanzado en junio de 2026) es competitivo en formateo y en reglas básicas de lint. Es más rápido que ESLint y no tiene sobrecarga de configuración si lo instalas con bun x ultracite@latest init. Para equipos que quieren una sola herramienta y no necesitan toda la profundidad de reglas de @typescript-eslint, Biome es una opción razonable. Para las reglas específicas de IA de esta guía (max-params, no-magic-numbers, max-lines-per-function con umbrales orientados a IA), ESLint con @typescript-eslint todavía tiene más cobertura. Puedes ejecutar ambos: Biome para el formateo y el lint básico, ESLint para las reglas específicas de IA.
¿Y si mi agente de IA sigue regenerando las mismas infracciones del linter?
El agente está rodeando la regla en lugar de corregir el problema de fondo. Para no-explicit-any, esto significa añadir una aserción de tipo en lugar de definir el tipo real. Para max-lines-per-function, significa extraer una función auxiliar que no hace nada útil pero deja el conteo de líneas por debajo del umbral. Ninguna de las dos soluciones pasa la revisión de código. La regla del linter detectó el síntoma; la causa raíz es el prompt del agente. Refina el prompt para especificar la restricción de tipo o la descomposición esperada; el agente seguirá una guía de estructura explícita de forma más fiable que reglas implícitas. Si trabajas con una configuración de agentes más elaborada, acotar el trabajo a un subagente dedicado con las restricciones incorporadas en sus instrucciones suele aguantar mejor que un solo prompt amplio.
¿El servidor MCP de ESLint reemplaza a la barrera pre-commit?
No. Reduce con qué frecuencia el agente genera código que incumple la barrera. La barrera sigue ejecutándose en cada commit. La comprobación en línea del servidor MCP y la aplicación del hook pre-commit son complementarias, así que no elimines ninguna.