Desarrollo Basado en Restricciones
Resumen
Otras habilidades de este paquete describen cómo se ve lo bueno. code-review-and-quality te da cinco ejes. test-driven-development te da un ciclo. security-and-hardening te da una lista de amenazas. Todo eso vive en prosa que el agente lee y puede o no seguir, y nada de eso sobrevive al final de la sesión.
Esta habilidad produce algo diferente: un registro escrito del nivel de calidad de este proyecto, con números, que perdura más allá de la conversación y puede verificarse mecánicamente.
El motivo importa. Cuando escribiste el código, leerlo te decía si era bueno. Un agente escribe más en una tarde de lo que leerás esa semana, por lo que el juicio sale de tu cabeza y entra en verificaciones que se ejecutan en bucle. Esas verificaciones deben existir, necesitan números que realmente hayas elegido y deben ejecutarse cerca del trabajo para que el agente corrija su propio resultado.
El desarrollo guiado por especificaciones dice qué construir. El desarrollo guiado por pruebas demuestra que funciona. El desarrollo basado en restricciones define lo que significa "suficientemente bueno para enviar", antes de que alguien discuta sobre ello en una solicitud de incorporación de cambios.
Cuándo usar
Aplica esta habilidad cuando:
- Estés iniciando un proyecto o una funcionalidad importante y no haya un estándar de calidad escrito
- El usuario pida "configurar restricciones", "agregar puertas de calidad", "definir nuestros estándares" o "evitar que el agente envíe basura"
- Un agente esté produciendo volumen que nadie lee línea por línea
- CI tenga verificaciones pero nadie pueda decir cuáles bloquean una fusión y cuáles son decoración
- Los números de cobertura, rendimiento o accesibilidad se discutan por PR en lugar de decidirse una vez
- Estés a punto de ejecutar
/build autoo cualquier bucle autónomo, y lo único que se interponga entre él y main sea un conjunto de pruebas que el agente también escribió
Cuándo NO usar:
- El proyecto ya tiene un
CONSTRAINTS.mdy el usuario no lo está cambiando — léelo y síguelo en su lugar - Scripts desechables, prototipos rápidos
- El usuario quiere una revisión de código ahora mismo (
code-review-and-quality) o una canalización CI construida (ci-cd-and-automation) - Código previo a la adecuación producto-mercado con una vida útil esperada de dos semanas — el piso inferior sigue valiendo la pena, el resto no
Cargar restricciones
La entrevista necesita un usuario activo. No la ejecutes en contextos no interactivos (CI, /loop, ejecuciones autónomas). Si faltan restricciones y te encuentras en uno de esos, aplica el Piso que aparece a continuación, anota que lo hiciste y señala el resto para un humano.
El proceso
Paso 1: Detecta antes de preguntar
Nunca preguntes lo que puedas leer. Antes de la primera pregunta, recopila:
| Qué | Dónde buscar |
|---|---|
| Lenguaje y stack | package.json, pyproject.toml, go.mod, Cargo.toml |
| Ejecutor de pruebas | dependencias de desarrollo, script test, archivos de prueba existentes |
| Linters existentes | eslint.config.*, biome.json, .ruff.toml |
| Cobertura actual | salida de coverage/, o ejecuta el conjunto una vez |
| CI | .github/workflows/, .gitlab-ci.yml |
| Plataforma del agente | .claude/, .codex/, AGENTS.md |
Informa lo que encontraste en dos líneas, luego pregunta solo lo que falte.
Paso 2: Cuatro preguntas, cada una con un valor predeterminado
Sigue la disciplina de una pregunta a la vez de interview-me, con un cambio: cada pregunta aquí tiene un valor predeterminado, por lo que "no sé" es una respuesta completa que aún produce una configuración funcional.
P1: Además del piso, ¿cuáles de estos deseas que se apliquen?
(a) Cobertura de pruebas en código nuevo
(b) Escaneo de seguridad
(c) Presupuestos de rendimiento
(d) Accesibilidad
(e) Límites de arquitectura
ADIVINANZA: (a) y (b) — ya tienes un ejecutor de pruebas y estás manejando entrada del usuario.
VALOR PREDETERMINADO si no estás seguro: (a) y (b).
Di lo que cuesta cada elección: (c) y (d) necesitan una URL en ejecución, (e) necesita un archivo de reglas escrito.
P2: Cuando una verificación falla mientras el agente está en medio de una tarea, ¿debe bloquear o advertir?
ADIVINANZA: Bloquear. Estás ejecutando agentes sin supervisión y una advertencia que nadie lee es una advertencia.
VALOR PREDETERMINADO si no estás seguro: Bloquear en el piso, advertir en todo lo demás durante las primeras dos semanas.
P3: ¿Tienes números objetivo en mente, o debo medir dónde estás hoy y mantener esa línea?
ADIVINANZA: Medir. La mayoría de los equipos no tienen un número, y uno inventado se ignora.
VALOR PREDETERMINADO si no estás seguro: Medir y mantener. Consulta "Trinquetes" más abajo.
P4: ¿Cuál es la verificación más lenta que tolerarás antes de que el agente devuelva el trabajo?
ADIVINANZA: Unos 90 segundos. Más tiempo y dejarás de ejecutarla.
VALOR PREDETERMINADO si no estás seguro: 90 segundos al final de la tarea, ilimitado en CI.
Detente en cuatro. Una admisión de doce preguntas produce una configuración que nadie entiende y un usuario que se arrepiente de haber empezado.
Paso 3: Escribir CONSTRAINTS.md
Un archivo en la raíz del repositorio. Cualquier agente en cualquier plataforma puede leerlo, y un cambio en él aparece en la revisión donde debe estar.
# Restricciones
Última revisión: 2026-08-08 por @mukhon-core
## Piso (siempre aplicado, sin configuración necesaria)
- Sin nuevos comentarios de supresión: `@ts-ignore`, `eslint-disable`, `# noqa`, `# type: ignore`
- Sin stubs no implementados: `throw new Error("Not implemented")`, `catch {}` vacío
- Sin pruebas omitidas o eliminadas sin una razón en el mensaje de confirmación
- Sin secretos en el código fuente
- Este archivo no se debilita para que un cambio pase
## Aplicado con números
| Dimensión | Regla | Verificado por | Se ejecuta en |
|-----------|-------|----------------|---------------|
| Tipos | Cero errores de tipo | `tsc --noEmit` | cada edición |
| Lint | Cero errores de nuestra configuración | `biome check` | cada edición |
| Secretos | Sin secretos en el código fuente | `gitleaks detect --redact` | cada edición |
| Cobertura | Líneas cambiadas ≥ 80% cubiertas | `vitest run --coverage` + git diff | fin de tarea, CI |
| Seguridad: código | Sin hallazgos altos | `semgrep scan --config p/default` | CI |
| Seguridad: dependencias | Nada en alto o superior | `osv-scanner scan source -r .` | CI |
| Accesibilidad | Cero críticos o graves | `axe $PREVIEW_URL --tags wcag2a,wcag2aa,wcag21aa` | despliegue de vista previa |
| Rendimiento | LCP ≤ 2500ms, CLS ≤ 0.1 | `lighthouse $PREVIEW_URL --output=json` | despliegue de vista previa |
Cada fila nombra el comando que produce el veredicto. Una dimensión con un número y ningún comando en esta columna es una aspiración, no una restricción.
## Medido, aún no aplicado
| Métrica | Hoy | Dirección |
|---------|-----|-----------|
| Cobertura del proyecto | 62.4% | no debe caer |
| Tamaño del paquete (main) | 184 kB | no debe crecer |
## Excepciones
| ID | Regla | Ruta | Razón | Propietario | Vence |
|----|-------|------|-------|-------------|-------|
| W1 | `no-explicit-any` | `src/legacy/**` | Reescritura rastreada en ENG-441 | @mukhon-core | 2026-11-01 |
Luego agrega una línea a AGENTS.md y CLAUDE.md: Lee CONSTRAINTS.md antes de escribir código. No lo debilites para que un cambio pase.
Paso 4: Instala lo que necesita cada dimensión
Elegir una dimensión significa instalar algo. No dejes al usuario con un número y ningún mecanismo, y no inventes tu propio verificador cuando exista uno de facto — estas herramientas se enumeran porque sus formatos de reglas y umbrales son a los que apunta todo lo demás en el ecosistema, por lo que la configuración existente del equipo sigue funcionando.
| Dimensión | Herramienta | Instalación | Ejecución | Bloquear en |
|---|---|---|---|---|
| Tipos (TS) | tsc | ya está | tsc --noEmit | cualquier error |
| Tipos (Python) | mypy | pip install mypy | mypy . | cualquier error |
| Lint | tu config existente | ya está | eslint . / biome check / ruff check | cualquier error |
| Cobertura (JS) | tu ejecutor de pruebas | ya está | vitest run --coverage (o jest --coverage) | cobertura de líneas cambiadas |
| Cobertura (Python) | pytest-cov | pip install pytest-cov | pytest --cov --cov-report=lcov | mismo |
| Seguridad: código | Semgrep | pipx install semgrep | semgrep scan --config p/default --config p/owasp-top-ten | cualquier hallazgo alto |
| Seguridad: secretos | gitleaks | brew install gitleaks | gitleaks detect --redact --no-banner | cualquier hallazgo |
| Seguridad: dependencias | osv-scanner | brew install osv-scanner | osv-scanner scan source -r . | alto o superior |
| Rendimiento: página | Lighthouse | npm i -D lighthouse | lighthouse $URL --output=json --quiet | LCP, CLS, puntuación de rendimiento |
| Rendimiento: paquete | size-limit | npm i -D size-limit | size-limit --json | presupuesto de bytes por entrada |
| Accesibilidad | axe-core | npm i -D @axe-core/cli | axe $URL --tags wcag2a,wcag2aa,wcag21aa | cero críticos o graves |
| Arquitectura | dependency-cruiser | npm i -D dependency-cruiser | depcruise --validate src | cualquier violación |
| Calidad de aserciones | Stryker | npm i -D @stryker-mutator/core | stryker run --mutate <archivos cambiados> | puntuación de mutación |
Cinco cosas que te morderán si las omites:
--redacten gitleaks no es opcional. Sin él, el secreto coincidente termina en la transcripción del agente, que es cómo una clave filtrada termina en un registro, un resumen o un mensaje de confirmación. Informa la regla y la ubicación, nunca el valor.- Lighthouse y axe necesitan una URL. Solo funcionan contra una aplicación en ejecución, por lo que pertenecen a la etapa de ejecución contra un despliegue de vista previa o un servidor local que inicies primero. Si el proyecto no tiene URL a la que apuntar —una CLI, una biblioteca, una aplicación de escritorio— dilo y descarta la dimensión en lugar de inventar una verificación que no puede ejecutarse.
- Limita las costosas al diff.
stryker run --mutateen todo el repositorio lleva horas y se desactiva; en los archivos que tocó un cambio lleva menos de un minuto. Lo mismo para Semgrep, que toma una lista de rutas. - La cobertura no necesita una segunda ejecución de pruebas. Lee el lcov que tu suite ya escribe e intersecciónalo con
git diff. Ejecutar la suite dos veces para obtener un número es la forma más rápida de hacer que la gente odie esto. - Las reglas del registro de Semgrep son gratuitas de ejecutar; verifica la licencia antes de redistribuirlas.
opengrepes un fork de reemplazo con el mismo formato de reglas y salida JSON si eso le importa a tu equipo legal.
Agrega cada una al script propio del proyecto para que sea reproducible sin un agente:
{
"scripts": {
"check:fast": "tsc --noEmit && eslint . && gitleaks detect --redact --no-banner",
"check:task": "npm run check:fast && vitest run --coverage",
"check:full": "npm run check:task && semgrep scan --config p/default && osv-scanner scan source -r ."
}
}
Ese mapeo importa más que las herramientas. check:fast es lo que se ejecuta después de una edición, check:task cuando el agente cree que ha terminado, check:full en CI.
Los comandos ahora viven en dos lugares: la columna Verificado por en CONSTRAINTS.md y estos scripts. CONSTRAINTS.md es la fuente canónica: lleva la razón junto a cada comando y aparece en la revisión. Los scripts son envoltorios de conveniencia que deben reflejarlo, no una segunda fuente de verdad; si se desvían, gana el archivo.
Paso 5: Conéctalo al ciclo de vida
El error más grande es ejecutar todo en todas partes. Una verificación que detiene al agente se desactiva, y una puerta que la gente desactivó es peor que ninguna puerta, porque el estándar aún parece existir.
| Fase | Comando | Qué se ejecuta | Presupuesto |
|---|---|---|---|
| BUILD | /build | Tipos, lint, secretos, el piso | menos de 5s, solo archivo cambiado |
| VERIFY | /test | Pruebas relacionadas, cobertura en líneas cambiadas | menos de 90s |
| REVIEW | /review | Todo, más las protecciones a continuación | minutos |
| SHIP | /ship | Comprobaciones de dirección, sin regresiones | CI |
Dos reglas que mantienen esto tolerable:
- Limita al diff. Verifica las líneas que tocó este cambio, no todo el repositorio. La cobertura de líneas cambiadas es un número que el agente puede mover; la cobertura del proyecto es una que heredó.
- El costo decide la ubicación. Cualquier cosa que supere unos segundos sale del bucle de edición. Las pruebas de mutación en todo el repositorio llevan horas; en los archivos que tocó un cambio lleva menos de un minuto, que es la diferencia entre una verificación que la gente ejecuta y una que no.
Paso 6: Protege el estándar mismo
Alguien señalará que si el agente escribe el código y las verificaciones, las verificaciones no prueban nada. Medio cierto, y vale la pena diseñar alrededor de ello.
Los agentes no crean lagunas inteligentes. Se encuentran con una verificación roja y toman el camino más barato hacia el verde. Vigila estos cinco movimientos en el diff, en el momento de la revisión:
- El umbral se movió. Un presupuesto reducido, una gravedad bajada, una verificación eliminada de la etapa rápida. Compara
CONSTRAINTS.mdcon su estado en el punto de ramificación. - Una prueba se volvió más fácil. Se agregó
.skip, se eliminó un archivo de prueba, se extrajeron aserciones de pruebas que permanecieron. - Un verificador fue silenciado. Nuevo
@ts-ignoreoeslint-disable. Cuatro supresiones merecen atención especial porque desactivan una verificación en la que confías:istanbul ignoreelimina código de la cobertura en lugar de probarlo,Stryker disableoculta un mutante superviviente,nosemgrepygitleaks:allowlo hacen para hallazgos de seguridad. - El trabajo está sin terminar. Un stub que lanza, un
catchvacío que convierte un fallo en silencio, unTODOdonde debería estar la implementación. - Apareció una excepción. Una nueva fila en la tabla de Excepciones que nadie discutió.
Nada de esto necesita herramientas más allá de git diff. Apretar el estándar debe ser silencioso; aflojarlo debe ser ruidoso.
A diferencia de las dimensiones numeradas, el piso no tiene una herramienta de facto propia, por lo que un agente al que se le pide aplicarlo tiende a escribir un verificador desde cero, y dos agentes escriben dos diferentes. Una implementación de referencia de estas cinco comprobaciones se envía con esta habilidad en references/floor-guard.md (limitado al diff, salida 0/1/2, patrones adaptables por ecosistema). Adáptalo en lugar de reinventarlo, por la misma razón por la que cada dimensión nombra una herramienta de facto: para que el mecanismo sea el mismo en todas las ejecuciones y stacks.
No todas las comprobaciones son igualmente circulares. Clasifícalas por una pregunta: ¿puede el agente hacer que esto pase escribiendo código que no funcione?
- Externas — axe-core codifica WCAG,
osv-scannerlee una base de datos de vulnerabilidades, Lighthouse mide un navegador real. El agente no puede discutir con estas. - Del proyecto — tus reglas de lint, tus límites de capa. Un humano es dueño del archivo.
- Del suite — tus propias pruebas. Las más útiles, y las únicas genuinamente circulares.
Paso 7: Trinquetes
La cobertura y el rendimiento suben o bajan. Un trinquete evita que bajen sin una decisión consciente.
# En CONSTRAINTS.md
## Medido, aún no aplicado
| Métrica | Hoy | Dirección |
|---------|-----|-----------|
| Cobertura del proyecto | 62.4% | no debe caer |
El mecanismo:
- Después de cada fusión, un script mide la métrica y actualiza
HoyenCONSTRAINTS.md. - Si la métrica sube, el número sube. El agente nunca necesita recordar el máximo histórico.
- Si la métrica baja, la verificación falla a menos que un humano haya editado manualmente
CONSTRAINTS.mden esa misma confirmación para reconocer la caída.
Eso es todo. Sin tablas separadas, sin historial, sin un servicio externo. El archivo que contiene las restricciones también contiene la línea de base.
Paso 8: Lo que sucede cuando alguien elimina CONSTRAINTS.md
Nada. Eso es lo que sucede. Lo reconstruyes la próxima vez que se ejecute la habilidad, a partir del piso, que siempre es el mismo, más las métricas que el CI mide de todos modos.
Un esfuerzo mejor invertido que hacer que el archivo sea a prueba de manipulaciones es hacer que sea indoloro recrearlo. Los trinquetes significan que el número puede ser aproximado; el piso nunca cambia.
Integraciones
CI (GitHub Actions)
name: Constraint check
on: [pull_request]
jobs:
constraints:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check CONSTRAINTS.md exists
uses: andstor/file-existence-action@v2
with:
files: "CONSTRAINTS.md"
- name: Diff against main
run: |
git fetch origin ${{ github.base_ref }}
git diff origin/${{ github.base_ref }} -- CONSTRAINTS.md | \
grep -E '^[+-]' | grep -v '^[+-]{3}' | grep -v 'Last reviewed' || true
Agente (Claude Code / Cursor / Codex)
# En AGENTS.md, CLAUDE.md, .cursorrules, o el mecanismo de instrucciones de tu plataforma
Lee CONSTRAINTS.md antes de escribir código. No lo debilites para que un cambio pase.
Si una verificación falla, corrige el código, no la restricción.
# En las instrucciones del agente
Después de cada cambio:
1. Ejecuta `npm run check:fast` o el script equivalente para el ecosistema.
2. Si falla, corrige la causa, no el umbral.
3. Si el cambio introduce una nueva dependencia, ejecuta `npm run check:full` para verificar el escaneo de seguridad.
Stack de Node
// scripts/check-coverage.mjs
import { execSync } from "child_process";
import { readFileSync, writeFileSync } from "fs";
const coverage = JSON.parse(execSync("vitest run --coverage --reporter=json"));
const changed = execSync("git diff --name-only origin/main --diff-filter=ACMRT").toString().trim().split("\n");
const threshold = 80; // de CONSTRAINTS.md
const changedCoverage = coverage.map((f) => ({
file: f.file,
coverage: f.statementCoverage,
changed: changed.some((c) => f.file.startsWith(c)),
}));
const failing = changedCoverage.filter((f) => f.changed && f.coverage < threshold);
if (failing.length > 0) {
console.error("Cobertura por debajo del umbral:", failing);
process.exit(1);
}
Solución de problemas
"La verificación es demasiado lenta para el bucle de edición"
Mueve el paso costoso de check:fast a check:task o check:full. Siempre puedes agregar otra etapa, pero no puedes recuperar el enfoque de un agente que esperó 30 segundos por un escaneo de seguridad que no toca sus cambios.
"La verificación falla en CI pero no localmente"
Los agentes no tienen un entorno que sea como el tuyo. Haz que las versiones de las herramientas sean explícitas: npx, pipx, imágenes Docker.
"El equipo no está de acuerdo con un umbral"
Los umbrales en trinquete son más fáciles de aceptar que los arbitrarios. Mide, muestra el número y haz que se aplique. Un mes después de la medición, el equipo tiene datos para discutir en lugar de instintos.
"El agente sigue fallando las verificaciones"
Las verificaciones fallidas son datos. Mira el patrón de fallos:
- Siempre la misma verificación: el stack o las herramientas son diferentes. Haz que las versiones sean explícitas.
- Verificaciones diferentes cada vez: el agente está teniendo dificultades con el código, no con la verificación. Reduce la complejidad de la tarea.
- Nunca fallan: las verificaciones pueden ser demasiado fáciles. Aumenta los umbrales.
Referencias
- [La lista de verificación de calidad del usuario de @mukhon-core (publicación en el blog)]
- [Descripción general de WCAG 2.2]
- [Documentación de Lighthouse]
- [Reglas de Semgrep]
- [Documentación de axe-core]