La factura del VibeCoding ya está sobre la mesa

Antes de cualquier configuración, tres datos que justifican el tiempo que vas a invertir en este setup:

  • 40-62% del código generado por IA contiene vulnerabilidades de seguridad (research Q1 2026 sobre 470 PRs de GitHub).

  • 91.5% de las apps "vibe-coded" tienen al menos una vulnerabilidad relacionada con alucinaciones del modelo.

  • 8x más bloques de código duplicado en 2024 que en años previos (estudio GitClear sobre 211M líneas).

  • Por cada 25% de aumento en adopción de IA, hay un 7.2% de deterioro en delivery stability (DORA 2024 de Google).

Y tres casos reales que pasaron de "anécdota" a "patrón medible":

  • Replit AI borró la base viva de una compañía durante un code freeze. Datos perdidos: 1.200 ejecutivos y 1.190 compañías. El agente admitió que "ejecutó comandos no autorizados y entró en pánico al ver queries vacías".

  • Lovable (CVE-2025-48757) expuso código fuente, credenciales y chats de 170+ apps en producción durante 48 días porque la IA implementó Row Level Security al revés.

  • Amazon registró 4 incidentes Sev-1 en 90 días vinculados internamente a "cambios asistidos por IA generativa". Un outage de 6 horas costó 6.3 millones de pedidos.

El problema técnico de fondo: los LLMs son probabilísticos, no lógicos. Un archivo agents.md o .cursorrules es una sugerencia que el modelo puede ignorar. Un linter, un type-checker o una fitness function arquitectónica es una verdad absoluta: pasa o falla.

Esta guía cubre 8 capas de defensa determinística, ordenadas por ROI por minuto invertido. Si solo tienes una hora hoy, implementa las primeras tres.

Capa 1 — TypeScript strict + el flag que casi nadie usa

Tiempo de setup: 5 minutos. Costo: 0. Resuelve: bugs de acceso a undefined que la IA introduce sistemáticamente al asumir que un acceso por índice siempre devuelve algo.

Setup

Edita el tsconfig que contenga compilerOptions. En proyectos modernos con
references (Vite, Next.js, monorepos) suele ser tsconfig.app.json o
similar, no el tsconfig.json raíz que solo lista referencias:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitOverride": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true
  }
}

El flag clave es noUncheckedIndexedAccess. La mayoría de equipos que tienen TypeScript "estricto" no lo usa, y es exactamente el que más bugs de IA atrapa.

Ejemplo before/after

// Antes (la IA escribe esto y compila sin problema)
const userIds = ['a', 'b', 'c'];
const first = userIds[0];
first.toUpperCase();  // Runtime error si el array está vacío

// Después (con noUncheckedIndexedAccess)
const first = userIds[0];  // tipo: string | undefined
first.toUpperCase();  // Error de compilación, te obliga a checkear

Verificación

npx tsc -b

Usa tsc -b (build mode) si tu proyecto tiene references en el tsconfig raíz. Con tsc --noEmit clásico, los referenced configs se ignoran y verás "0 errores" engañosos. Espera ver decenas o cientos de errores la primera vez. Esos son los bugs latentes que la IA dejó.

Capa 2 — Knip: matar el código muerto que contamina el contexto del agente

Tiempo de setup: 10 minutos. Costo: 0 (open source). Resuelve: archivos huérfanos, exports nunca usados y dependencias muertas que ensucian el context window de la IA. Mientras más código irrelevante encuentre el agente al buscar patrones, peores resultados produce.

Setup

npm install --save-dev knip

Configuración mínima en knip.json:

{
  "$schema": "https://unpkg.com/knip@5/schema.json",
  "entry":["src/main.tsx"],
  "project": ["src/**/*.{ts,tsx}"]
}

El entry depende de tu stack: src/main.tsx para Vite/React, src/index.ts
para libs Node, pages/** o app/** para Next.js. No mezcles los dos.

Para Next.js, Vite, Remix o frameworks similares, Knip auto-detecta entry points. Si solo quieres correrlo sin config, salta el knip.json y ejecuta npx knip directo — la config explícita arriba es para cuando quieras restringir el scope o agregar ignore rules después.

Comando para correr

npx knip

Output esperado:

Output esperado:

  Unused files (12) src/utils/legacy-helpers.ts src/components/OldButton.tsx ...

  Unused exports (34) src/lib/api.ts:12 - exportedUnusedFn ...

  Unused dependencies (3) moment lodash.debounce react-icons

  Configuration hints (n) — patterns redundantes en knip.json que puedes limpiar.

Integración en CI

En tu .github/workflows/ci.yml:

- name: Check for unused code
  run: npx knip --include files,dependencies

Por qué Knip y no ts-prune

ts-prune está en maintenance mode desde hace meses. El propio mantenedor recomienda migrar a Knip. Knip detecta lo que ts-prune nunca detectó: dependencias unused, devDependencies, archivos nunca importados por código no-test, missing dependencies, duplicate exports, class members y enum members.

Capa 3 — ESLint custom + AgentLint: las reglas que la IA no puede ignorar

Tiempo de setup: 15 minutos. Costo: 0. Resuelve: patrones específicos que la IA repite sin importar cuántas veces lo pidas en el prompt (uso de as, magic values, funciones gigantes, debug statements olvidados, API keys en source).

Setup base — las 5 reglas que más impacto tienen

ESLint 9+ usa flat config. En tu eslint.config.js, agrega un bloque con las rules dentro del array exportado:

export default [{
    files: ['**/*.{ts,tsx}'],
    rules: {
      'max-lines-per-function': ['error', { max: 50, skipBlankLines: true }],
      'max-lines': ['error', { max: 250 }],
      'max-params': ['error', 2],
      'no-magic-numbers': ['warn', { ignore: [0, 1, -1, 2, 100, 1000] }],
      'no-console': 'error',
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/consistent-type-assertions': ['error', {
        assertionStyle: 'never' // prohíbe el uso de "as"
      }],
    },
}];

Si todavía estás en ESLint 8 con .eslintrc.js, las mismas rules van bajo module.exports = { rules: { ... } }. Setea no-magic-numbers como warn y no error: en error, cualquier UI con sizing/colors hex genera cientos de violaciones que el equipo termina desactivando.

Setup avanzado — AgentLint

AgentLint NO escanea tu source code. Escanea los archivos de configuración de agentes (CLAUDE.md, .cursorrules, .claude/, agents.md) buscando supply-chain risk en la propia config del agente — el ataque vector que estuvo detrás del Bitwarden hijack y de los falsos plugins de Cursor.

npm install --save-dev agentlint
npx agentlint scan .

Lo que AgentLint detecta:

  • Comandos curl | bash o wget | bash en hooks de tu agente.

  • Referencias a secrets ($STRIPE_SECRET_KEY, $AWS_ACCESS_KEY) en config
    files que el agente lee como contexto.

  • Capacidades de shell exec dinámico sin permission manifest explícito.

  • Configs sin declaración de permisos que dejan al agente en modo "todo
    permitido" por default.

  • Para los patterns en source (console.log, .skip/.only, imports unused, API keys hardcodeadas), las defensas son las que ya tienes en este stack:
    ESLint con no-console + no-debugger, eslint-plugin-vitest/jest para .skip/.only, y Gitleaks (Capa 4) para credenciales. La protección contra force-push a main es branch protection en GitHub/GitLab, no un linter.

    Bonus para frontend — Deslint

Si tu equipo hace UI, agrega Deslint para CSS generado por IA. Cubre colores fuera de paleta, spacing arbitrario, drift del design system, y accesibilidad WCAG 2.2/2.1 AA. Soporta React, Vue, Svelte, Angular y HTML plano.

npm install --save-dev deslint
npx deslint scan .

Ojo con el package name: el paquete vive bajo el scope @deslint/cli, no como deslint plano (eso da 404 en npm).

El meta-patrón

Si necesitas una regla custom para tu codebase específico, pídele al agente que la escriba. Una regla ESLint custom toma ~10 minutos de prompt. Una vez en CI, es determinista por siempre. El error message bien escrito enseña al modelo qué patrón usar correctamente — la iteración hace que el agente aprenda tu arquitectura sin que tengas que repetirlo en cada prompt.

Capa 4 — Pre-commit hooks + Secret Scanning: la última línea contra el próximo "Bitwarden hijack"

Tiempo de setup: 20 minutos. Costo: 0. Resuelve: credenciales que la IA "ayudó" a hardcodear, secrets en commits accidentales, y supply chain attacks que cazan específicamente credenciales de IDEs con IA.

Contexto importante: el caso Bitwarden CLI hijack del mes pasado fue diseñado específicamente para buscar credenciales de Claude, Cursor y Codex CLI en máquinas de developers. Tu IDE con IA es el target prioritario hoy. Si no tienes secret scanning en pre-commit, tienes un airbag faltando.

Setup mínimo

npm install --save-dev husky lint-staged
npx husky init

Husky v9+ ya no necesita el shebang ni el . "$(dirname...)/husky.sh" que
veías en v8. El archivo .husky/pre-commit ahora es solo los comandos:

npx lint-staged
gitleaks git --staged --verbose

Ojo: el subcomando gitleaks protect quedó deprecated desde v8.19.0.
El reemplazo oficial es gitleaks git --staged --verbose (mismo
comportamiento, scan del staged area).

En tu package.json:

{ "lint-staged": { "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"] } }

Si no tienes prettier instalado, agregalo o saltalo del array — sino
lint-staged falla con ENOENT antes de llegar al gitleaks:

npm install --save-dev prettier

Instalación de Gitleaks

macOS:

brew install gitleaks

Linux:

curl -sSfL https://raw.githubusercontent.com/gitleaks/gitleaks/master/install.sh | sh -s -- -b /usr/local/bin

Setup avanzado — Infisical CLI

Si quieres detección más exhaustiva (140+ tipos de secret leaks distintos), reemplaza Gitleaks por Infisical:

npm install -g @infisical/cli
infisical scan install --pre-commit-hook

Verificación

Intenta hacer commit a un archivo con una API key falsa:

echo 'const KEY = "sk-1234567890abcdef1234567890abcdef";' > test.js
git add test.js && git commit -m "test"

Gitleaks debería identificarlo como generic-api-key (entropía ~4.2) y
bloquear el commit antes de llegar al servidor. Output esperado:

Finding:     const KEY = "sk-..."
Secret:      sk-1234567890abcdef1234567890abcdef
RuleID:      generic-api-key
Fingerprint: test.js:generic-api-key:1
husky - pre-commit script failed (code 1)

Capa 5 — dependency-cruiser: fitness function arquitectónica como CI gate

Tiempo de setup: 30 minutos. Costo: 0. Resuelve: la IA cruzando boundaries arquitectónicas (UI importando de DB, features importando de otras features, domain importando de infrastructure). Es el problema que ningún linter tradicional detecta porque no es un patrón sintáctico, es uno topológico.

Por qué importa más con IA

Con un developer manual escribiendo código, una regla arquitectónica no enforced produce ~1 violación por semana. Con múltiples agentes generando código en paralelo, la misma regla produce violaciones en cada sesión. El costo del no-enforcement crece con la cantidad de IA en tu pipeline.

Setup

Setup

npm install --save-dev dependency-cruiser
npx depcruise --init yes

El flag yes salta el wizard interactivo (sin él, te hace 6-8 preguntas sobre stack, source dir, etc).

Crea un .dependency-cruiser.cjs con reglas baseline. Edita para agregar las tuyas:

module.exports = {
  forbidden: [
    {
      name: 'no-ui-to-db',
      severity: 'error',
      comment: 'UI nunca debe importar directamente de la capa DB',
      from: { path: '^src/ui' },
      to: { path: '^src/database' }
    },
    {
      name: 'no-feature-cross-import',
      severity: 'error',
      comment: 'Features no pueden importarse entre sí',
      from: { path: '^src/features/([^/]+)' },
      to: { path: '^src/features/(?!$1)([^/]+)' }
    },
    {
      name: 'no-circular',
      severity: 'error',
      from: {},
      to: { circular: true }
    }
  ],
  options: {
    tsConfig: { fileName: 'tsconfig.json' }
  }
};

Comando para correr

npx depcruise src

En dependency-cruiser v17 el flag --validate quedó implicito: el
comando default ya valida contra las reglas del config. Si tienes
documentacion vieja con depcruise --validate src, sigue funcionando
pero el flag es redundante.

Integración en CI

- name: Validate architecture
  run: npx depcruise --validate src

Cualquier PR que cruce un boundary queda bloqueado antes de merge.

Output esperado cuando hay violaciones:

error no-ui-to-db: src/ui/Page.ts → src/database/db.ts
error no-feature-cross-import: src/features/auth/index.ts → src/features/billing/index.ts
x 2 dependency violations (2 errors, 0 warnings). 23 modules, 27 dependencies cruised.

Equivalentes en otros stacks

  • Java: ArchUnit

  • Kotlin: Konsist

  • Python: pytest + custom AST checks

  • Go: archtest

Capa 6 — Storybook + MCP: el design system como fuente de verdad ejecutable

Tiempo de setup: 1 hora (más si vas a actualizar Storybook). Costo: 0. Resuelve: la IA inventando componentes nuevos cuando ya existe uno equivalente en tu design system, o usando los componentes existentes con props inconsistentes.

Pre-requisito

Storybook 10.3+ con renderer React. Si tu Storybook es anterior:

npx storybook upgrade

Instalación del MCP addon

npm install --save-dev @storybook/addon-mcp

En .storybook/main.ts agrega @storybook/addon-mcp al array addons existente (NO reemplaces el archivo entero; tu Storybook ya viene con otros addons cargados):

 const config: StorybookConfig = {
    stories: [/* ... lo que ya tenias ... */],
    addons: [
      /* ... tus addons actuales ... */
      "@storybook/addon-mcp"
    ],
    framework: "@storybook/react-vite"
  };
  export default config;

Conectar tu agente

Corre Storybook (puerto 6006 por default; el script npm run storybook
generado por el init hace lo mismo):

npx storybook dev -p 6006

Conecta tu agente al endpoint MCP:

  • Cursor: agrega en Settings → MCP el server http://localhost:6006/mcp

  • Claude Code: claude mcp add --transport http storybook http://localhost:6006/mcp

Verificación

Pregúntale a tu agente:

"Usa list-all-documentation para listar todos los componentes en Storybook,
y get-documentation para los detalles del Button."

El addon expone tools MCP especificos: list-all-documentation, get-documentation, get-documentation-for-story, preview-stories, run-story-tests, get-storybook-story-instructions. El agente debe usar esos tools (no inventar la lista). Confirma que el endpoint http://localhost:6006/mcp responde a un POST JSON-RPC con tools/list antes de gastar tiempo debuggeando integraciones.

Workflow ideal

En lugar de promptear "haz un botón estilo primario", el prompt correcto pasa a ser:

"Usa el componente Button del manifest de Storybook con variant primary y size md"

El agente consulta el manifest, encuentra el componente y lo instancia con las props exactas. Cero drift.

Capa 7 — Sentry MCP + Spotlight: que el agente debugee con datos reales

Tiempo de setup: 30 minutos (si ya usas Sentry). Costo: plan free de Sentry alcanza para empezar. Resuelve: el ciclo ineficiente de "copio el error de la consola → lo pego en el chat → la IA propone un fix → falla → repito". Con Sentry MCP el agente accede directo al stack trace, tags, y datos de eventos.

Sentry MCP (cloud, sin instalación local)

El servidor lo hostea Sentry en https://mcp.sentry.dev/mcp. Conexión OAuth, sin install.

Cursor: agrega en Settings → MCP:

{
    "mcpServers": {
      "sentry": {
        "url": "https://mcp.sentry.dev/mcp"
      }
    }
  }

Cursor detecta HTTP por la presencia del campo url. El servidor responde 401 sin OAuth (eso es esperado: la primera conexion abre el browser para autorizar tu org de Sentry).

Claude Code:

claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

Te abre OAuth en el browser para autorizar acceso a tu org de Sentry.

Queries en lenguaje natural que ya funcionan

  • "¿Cuáles son los top 5 unresolved errors en producción esta semana?"

  • "Muéstrame los errores que arrancaron después del último deploy"

  • "Dame el stack trace del issue que afecta más usuarios pagos"

  • "Para este URL de issue, propón un fix basado en el código actual del repo"

Spotlight para datos locales

Spotlight es el complemento para datos de runtime en tu entorno local — traces, logs, query times de DB, render times de componentes. Desde la v4 cambio el modelo: ya no se importa como modulo browser. Ahora es un sidecar que corres por CLI y al que tu app le manda eventos via Sentry SDK.

  1. Instala el sidecar y el SDK de Sentry:

npm install --save-dev @spotlightjs/spotlight
npm install @sentry/react   # o @sentry/node, segun tu app
  1. En tu app (modo dev) inicializa Sentry apuntando al sidecar:

import * as Sentry from '@sentry/react';

  if (import.meta.env.DEV) {
    Sentry.init({
      dsn: 'https://spotlight@local/0',
      integrations: [Sentry.spotlightBrowserIntegration()],
      tracesSampleRate: 1.0,
    });
  }
  1. Corre el sidecar (puerto 8969 por default):

npx @spotlightjs/spotlight

Conecta el MCP de Spotlight al agente. Spotlight expone tanto stdio como HTTP en el mismo puerto del sidecar:

claude mcp add --transport http spotlight http://localhost:8969/mcp

Tools que expone: search_errors, search_logs, search_traces, get_traces.

Para verificar manualmente:

curl -X POST http://localhost:8969/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

AI-to-AI delegation

Sentry MCP permite que tu agente general (Claude, Cursor) invoque a Seer, la IA especializada de debugging de Sentry. Dos modelos colaborando: el general entiende el contexto del proyecto, el especializado entiende el patrón del bug. Esto reduce el ciclo de debugging de 15-20 min a 2-3 min.

Capa 8 — Fallow: el detector avanzado de duplicación semántica

Tiempo de setup: 30 minutos. Costo: capa estática gratis (open source), runtime layer es paga y opcional. Resuelve: código copy-pasteado y disfrazado con nombres de variables cambiados — el patrón que el estudio GitClear identificó como el principal driver de la 8x de duplicación en 2024.

Setup

Via npm (mas simple, recomendado para proyectos JS/TS):

npm install --save-dev fallow   # or: pnpm add -D fallow / yarn add -D fallow / bun add -d fallow

O via Cargo si prefieres binario nativo de Rust:

cargo install fallow-cli

(El package en npm es fallow, no fallow-cli — ese nombre solo
existe en crates.io. No hay tap oficial de Homebrew documentado.)

Comando inicial

npx fallow init           # crea .fallowrc.json con framework detection
npx fallow health         # complejidad ciclomatica + cognitiva por archivo
npx fallow health --targets   # ranked refactoring targets
npx fallow dupes          # duplicacion (default mode: mild)
npx fallow dead-code      # unused exports + circular deps

Output de health --targets: lista de targets de refactor con score y confianza. Cualquier developer (o agente) puede atacarla de mayor a menor confianza.

Configuración de límites arquitectónicos

Configuracion de limites arquitectonicos

El config se llama .fallowrc.json (o .fallowrc.jsonc / fallow.toml).
La forma de los boundaries es por zonas + reglas, no flat:

{
    "boundaries": {
      "zones": [
        { "name": "ui", "patterns": ["src/ui/**"] },
        { "name": "database", "patterns": ["src/database/**"] }
      ],
      "rules": [
        { "from": "ui", "allow": [] }
      ]
    }
  }

Tip: si tu arquitectura es estandar, usa un preset ("preset": "layered", "hexagonal", "feature-sliced", "bulletproof") y Fallow expande las zonas/reglas por ti.

El modo semantic es el feature killer

Fallow tiene 4 modos de deteccion de duplicacion en el comando dupes, no en el config (detection.mode no existe):strict: tokens exactos

npx fallow dupes --mode strict     # tokens exactos
npx fallow dupes --mode mild       # AST-based (default)
npx fallow dupes --mode weak       # diferentes string literals
npx fallow dupes --mode semantic   # variables y literales renombrados

El modo semantic captura el copy-paste con disfraz que ningun grep ni
eslint tradicional detecta. Si la IA genero la misma funcion 5 veces
con nombres distintos, Fallow las identifica como un unico patron
duplicado.

Si quieres fijar el default en config, va bajo duplicates.mode:

{
"duplicates": { "mode": "semantic" }
}

Knip vs Fallow

Knip y Fallow son complementarios, no excluyentes. Knip es más rápido y cubre el 80% del trabajo de detección de código muerto. Fallow agrega el modo semantic y los boundaries arquitectónicos. Si tu equipo es chico, empieza con Knip. Si tienes un monorepo o codebase grande, vale el doble setup.

Aquí va la sección sola, lista para que la integres donde corresponda. Está pensada para ir como Capa 9 (antes de la tabla de stack completo).

Capa 9 — Hooks de Claude Code y Codex: que el agente corra los checks por sí mismo

Tiempo de setup: 20 minutos. Costo: 0. Resuelve: el último gap del flujo. De nada sirve tener 8 capas de defensa determinística si dependes de tu memoria para correrlas. Los hooks hacen que el propio agente dispare type-check, linter, knip y dependency-cruiser automáticamente cada vez que termina de editar un archivo, y reciba el output como contexto para corregirse antes de pasar al siguiente paso.

Este es el cierre del loop. Sin esto, las 8 capas anteriores son guardrails que se activan solo cuando llegas al CI. Con esto, los guardrails están encendidos durante toda la sesión del agente.

Setup para Claude Code

Crea el archivo .claude/settings.json en la raíz de tu proyecto:

 {
    "hooks": {
      "PostToolUse": [
        {
          "matcher": "Edit|Write",
          "hooks": [
            {
              "type": "command",
              "command": "bash .claude/hooks/typecheck.sh",
              "timeout": 30,
              "statusMessage": "Running TypeScript type check..."
            },
            {
              "type": "command",
              "command": "bash .claude/hooks/knip.sh",
              "timeout": 30,
              "statusMessage": "Checking unused code..."
            },
            {
              "type": "command",
              "command": "bash .claude/hooks/depcruise.sh",
              "timeout": 30,
              "statusMessage": "Validating architecture..."
            }
          ]
        }
      ]
    }
  }

Crea los scripts en .claude/hooks/:

typecheck.sh:

#!/bin/bash
npx tsc -b
exit_code=$?
if [ $exit_code -ne 0 ]; then
  echo '{"decision": "block", "reason": "TypeScript errors found"}' >&2
  exit 2
fi
exit 0

knip.sh:

#!/bin/bash
npx knip --include files
exit_code=$?
if [ $exit_code -ne 0 ]; then
  echo '{"decision": "block", "reason": "Unused code detected"}' >&2
  exit 2
fi
exit 0

depcruise.sh:

#!/bin/bash
npx depcruise src
exit_code=$?
if [ $exit_code -ne 0 ]; then
  echo '{"decision": "block", "reason": "Architecture violations found"}' >&2
  exit 2
fi
exit 0

Hazlos ejecutables:

chmod +x .claude/hooks/*.sh

Como se comporta el hook con exit codes (PostToolUse):

  • Exit 0 → exito. Si imprimiste JSON valido en stdout, Claude lo parsea (campos como decision, additionalContext).

  • Exit 2 con stderr → el tool YA corrio (PostToolUse no puede cancelarlo retroactivamente). Lo que pasa es que el stderr se inyecta en el contexto de Claude como feedback, y si tu JSON dice {"decision": "block"}, Claude no puede hacer su siguiente turno hasta que reaccione al error.

  • Cualquier otro exit code → no bloquea, queda en el debug log.

Nota: el shape {"continue": false, "stopReason": "..."} que vas a ver
en muchos ejemplos online es para los eventos Stop y SessionStart,
no para PostToolUse. Para PostToolUse usa {"decision": "block", "reason": "..."}.

Setup para Codex

Codex usa la misma filosofía de eventos (PreToolUse, PostToolUse, Stop) pero los configuras en hooks.json o inline en config.toml.

Crea .codex/hooks.json en la raíz del proyecto:

 {
    "hooks": {
      "PostToolUse": [
        {
          "matcher": "apply_patch|Bash",
          "command": "bash .codex/hooks/checks.sh",
          "timeout": 60
        }
      ]
    }
  }

O, si prefieres mantener todo en un solo archivo, en ~/.codex/config.toml:

[[hooks.PostToolUse]]
matcher = "apply_patch|Bash"
command = "bash .codex/hooks/checks.sh"
timeout = 60

Crea .codex/hooks/checks.sh:

#!/bin/bash

echo "Running deterministic checks..."

npx tsc -b || { echo '{"decision": "block", "reason": "TypeScript errors"}' >&2; exit 2; }
npx knip --include files || { echo '{"decision": "block", "reason": "Unused code"}' >&2; exit 2; }
npx depcruise src || { echo '{"decision": "block", "reason": "Architecture violation"}' >&2; exit 2; }
npx eslint . --max-warnings 0 || { echo '{"decision": "block", "reason": "Lint errors"}' >&2; exit 2; }

echo "All checks passed."
exit 0

Trust importante en Codex: los hooks de proyecto (<repo>/.codex/) solo se cargan si el directorio .codex/ esta marcado como trusted. Codex te lo va a pedir la primera vez. Los hooks de usuario (~/.codex/) se cargan siempre.

Orden de descubrimiento (precedencia, primero gana):

  1. ~/.codex/hooks.json

  2. ~/.codex/config.toml ([hooks])

  3. <repo>/.codex/hooks.json (requiere trust)

  4. <repo>/.codex/config.toml (requiere trust)

Recomendación de qué meter en cada hook

No metas todo en PostToolUse después de cada Edit. Eso vuelve cada turn de Claude/Codex 30-60 segundos más lento y mata la experiencia de iteración. Estrategia recomendada:

  • PostToolUse (Edit|Write|apply_patch): solo lo rápido — tsc --noEmit y eslint. Idealmente menos de 5 segundos.

  • Stop (cuando el agente cierra la sesión o pausa): lo más pesado — knip, depcruise, fallow health.

Esto te da feedback inmediato sobre errores de tipo y lint mientras el agente trabaja, sin penalizar cada keystroke con el análisis estructural completo.

Verificación

Inicia una sesión con tu agente y pide algo simple como "agrega un campo email al tipo User". Si el cambio rompe el type-check de algún consumer, deberías ver al agente recibir el error y proponer un fix automáticamente, sin que tengas que pegarle el output manualmente.

Stack completo en una tabla

Capa

Herramienta

Setup

Costo

Qué bloquea

Type safety

TS strict + noUncheckedIndexedAccess

5 min

$0

Bugs de undefined que la IA introduce

Dead code

Knip

10 min

$0

Pollution del context window

Pattern enforcement

ESLint custom + AgentLint + Deslint

15 min

$0

Magic values, as, debug statements, drift de design

Pre-commit

Husky + Gitleaks/Infisical

20 min

$0

Secrets, commits a main, force-push

Architecture

dependency-cruiser

30 min

$0

Imports cross-layer, ciclos

Design system

Storybook 10.3+ con MCP addon

1 hora

$0

Componentes inventados, props inconsistentes

Runtime feedback

Sentry MCP + Spotlight

30 min

$0 free tier

Debugging por suposición

Static analysis avanzado

Fallow

30 min

$0 capa estática

Duplicación semántica

Hooks del agente

Claude Code / Codex hooks

20 min

$0

Que el agente avance sin correr los checks anteriores

Total: ~4 horas de trabajo de setup, $0 de costo recurrente, defensa determinística end-to-end con el agente verificándose a sí mismo en cada paso.

Checklist para el lunes

Si solo tienes una hora hoy, haz los primeros 3. Si tienes media jornada, los primeros 6.

□ Editar tsconfig.json: agregar noUncheckedIndexedAccess + flags strict adicionales
□ npm install --save-dev knip + crear knip.json mínimo
□ npm install --save-dev agentlint + agregar 5 reglas baseline en .eslintrc
□ npm install --save-dev husky + configurar pre-commit con Gitleaks
□ npm install --save-dev dependency-cruiser + escribir 3 reglas de boundaries críticos
□ Verificar que tu Storybook esté en 10.3+ y agregar @storybook/addon-mcp
□ Conectar Sentry MCP a Cursor o Claude Code (sin install, vía OAuth)
□ Setup Spotlight para tu entorno local de dev
□ Probar Fallow en modo semantic en una feature pequeña antes de extender
□ Documentar el stack en tu CONTRIBUTING.md o README para nuevos miembros del equipo
□ Configurar hooks en .claude/settings.json o .codex/hooks.json para que tsc y eslint corran en automático tras cada Edit

El cambio de rol no es opcional

El developer senior de 2026 no es quien mejor escribe código. Es quien diseña el sistema de reglas determinísticas que controlan, validan y limpian lo que la IA produce.

Pasamos de Vibe Coder a Arquitecto de Reglas. Los equipos que no hacen este cambio van a aparecer en la próxima ronda de horror stories junto a Replit, Lovable, Moltbook y Bitwarden. La diferencia entre la columna izquierda y la derecha de esa lista no va a ser el modelo que usaron. Va a ser el stack determinístico que rodeaba al modelo cuando algo salió mal.

Estudios y reportes:

Casos reales:

Herramientas:

bitneuronal se escribe desde LATAM, para equipos que construyen con IA de verdad. Si te lo reenviaron y quieres recibirlo cada semana, suscríbete. Si ya estás suscrito, compártelo con alguien que esté mergeando AI slop a producción ahora mismo.

Hasta el próximo domingo.

Keep Reading