Ejecución del script check
Este skill es la fuente de verdad única para ejecutar el script check de package.json en los flujos de trabajo de lt-dev. Cada revisor, rebasador y orquestador que necesite garantizar la capacidad de ejecución del proyecto debe seguir este procedimiento al pie de la letra — duplicar las reglas entre agentes provoca desviaciones.
Objetivo: Una ejecución de
checkverdaderamente verde (código de salida 0) es un requisito previo no negociable para que cualquier revisión o rebase se considere completo. El único residual aceptable es una vulnerabilidad de dependencia ascendente donde se haya agotado toda la escalera de corrección.
Cuándo usar este skill
| Invocador | Fase | Disparador | Modo |
|---|---|---|---|
/lt-dev:review | Fase 1.5 | Antes de generar cualquier revisor especializado | Advisory |
lt-dev:code-reviewer | Fase 1.5 | Antes de la revisión de una sola pasada (omitir si el orquestador ya lo ejecutó) | Advisory |
lt-dev:test-reviewer | (briefing de entrada) | Respeta la semántica de omisión definida aquí | Advisory |
/lt-dev:take-ticket | PASO 8 | Después de que todos los pilares de prueba estén verdes | Bloqueante |
/lt-dev:git:ship | PASO 1, PASO 4b | Prevuelo, y después de la reverificación posterior al rebase | Bloqueante |
/lt-dev:ticket-cycle | vía take-ticket + git:ship | Ciclo completo | Bloqueante |
lt-dev:branch-rebaser | Fase 6.5 | Después de lint/formateo, antes de pruebas | Bloqueante |
Modos de invocación
El procedimiento es idéntico en ambos modos. Solo difiere lo que ocurre con un error no resuelto, y esa diferencia es el sentido de la distinción:
- Advisory — la tarea del invocador es informar. Un
checkrojo se reporta como un hallazgo Crítico y el flujo continúa, porque el usuario aún desea el resto de la revisión. - Bloqueante — la tarea del invocador es entregar. Un
checkrojo detiene el flujo. No se envía nada, no se crea ninguna MR/PR, no se fusiona nada.
Cada ruta que lleva a una MR/PR o a una fusión se ejecuta en modo Bloqueante. Un check rojo que llega a la rama base le cuesta a todo el equipo: la CI se vuelve roja para todos, el próximo take-ticket se bifurca desde código roto, y la persona que finalmente lo arregla no tiene idea de qué cambio lo causó. Por eso la escalera en el Paso 3 no tiene límite de iteraciones y por eso la lista de residuales del Paso 5 es tan estrecha.
Procedimiento
Paso 1 — Descubrir scripts check
Usa el script auxiliar dedicado (ubicado en el plugin lt-dev):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/discover-check-scripts.sh"
La salida es TSV, un proyecto por línea:
<ruta package.json>\t<script check>\t<incluye_pruebas:no|unit+api|sí>\t<gestor de paquetes>
includes_tests valores — los invocadores deciden qué hacer con cada uno, el descubrimiento en sí no prescribe ningún comportamiento:
| Valor | Significado | Cobertura de Playwright |
|---|---|---|
no | No se detectó ningún ejecutor de pruebas en la cadena de check. | No cubierta. |
unit+api | La cadena de check delega en el wrapper check.mjs de lt, que ejecuta los pasos de prueba Unit (app) + API (api) y omite deliberadamente Playwright (Playwright se ejecuta vía lt dev test / CI — ver scripts/check.mjs). | No cubierta — Playwright aún debe ejecutarse por separado si es necesario. |
sí | Coincidencia directa de patrón con test, vitest, jest o playwright en la cadena de check (de forma transitiva, un nivel de resolución compuesta). El alcance es lo que defina el script. | Desconocida — los invocadores deben verificar localmente si se requiere cobertura de Playwright. |
El auxiliar:
- Usa
git ls-files "package.json" "**/package.json"para que respete.gitignorey omitanode_modules - Analiza JSON mediante
jq→node -e→ cadena de respaldogrep+sed(funciona incluso sinjqinstalado) - Resuelve scripts compuestos de un solo nivel (
"check": "pnpm run ci"→ buscaci) - Detecta el gestor de paquetes por proyecto mediante el archivo de bloqueo (
pnpm-lock.yaml→pnpm, etc.; valor predeterminadopnpm)
Si el auxiliar no devuelve nada → ningún proyecto define check → omitir el resto de este skill.
Paso 1a — Sin correcciones sugeridas de oxlint
oxlint --fix-suggestions aplica correcciones que el propio oxlint marca como posiblemente modificadoras del comportamiento: su sugerencia no-console elimina la llamada. --fix-dangerously va más allá. Las plantillas actuales no pasan ninguna, pero los proyectos de versiones anteriores aún lo hacen, en el wrapper de check, lint:fix, lint-staged o el scripts/check.mjs de un proyecto. lt fullstack update las elimina en la raíz del espacio de trabajo y en la app (archivos rastreados y limpios solamente); el proyecto API mantiene sus propios archivos, por lo que un acierto allí se elimina manualmente o tomando el archivo actual de nest-server-starter. Observado en nest-server (2026-09, claude-code PR #10): un hook de commit convirtió un console.log en un cuerpo de función vacío y aún reportó "pasó". Antes del Paso 2, busca un uso de cualquiera de las banderas; una mención en un comentario no cuenta:
git grep -n -E "(^|[[:space:]\"']?)--fix-(suggestions|dangerously)" -- '*package.json' '*.lintstagedrc*' '*lint-staged.config.*' '*scripts/check.mjs' \
| grep -v -E '^[^:]+:[0-9]+:[[:space:]]*(//|/?\*|#)'
El segundo filtro elimina líneas de comentario: el wrapper actual de nest-server-starter explica en un comentario por qué pasa solo --fix simple.
Ante un acierto, la comprobación se ejecuta sin corrección automática:
- Ejecuta el wrapper de lt como su puerta de solo lectura:
<pm> run check --no-fix(npm:npm run check -- --no-fix). Cada wrapper que aún pasa una de las banderas ya conoce--no-fix. Cuando la cadena de un proyecto llama alint:fix, o su propioscripts/check.mjses uno de los aciertos, ejecuta los pasos de ese proyecto conlinten lugar delint:fix. - Corrige los hallazgos de lint manualmente o con
oxlint --fixsimple; formatea con el formateador del proyecto como de costumbre. - Nombra cada acierto (
archivo:línea) en el informe del Paso 8 con su remedio:lt fullstack updatepara la raíz y la app, eliminar la bandera manualmente (o el archivo actual de nest-server-starter) para el proyecto API.
Paso 2 — Ejecutar check por proyecto
Para cada proyecto descubierto, cd a su directorio y ejecuta el script check con el gestor de paquetes detectado:
cd "$(dirname <ruta package.json>)"
<gestor de paquetes> run check
Captura stdout, stderr y código de salida. Lleva un contador de iteraciones que comienza en 1.
Paso 3 — Bucle de corrección automática (iterar hasta que esté verdaderamente verde)
Si el check de un proyecto sale con código distinto de cero:
- Analiza todos los errores de la ejecución actual (typecheck, lint, build, imports faltantes, discrepancias de tipo, símbolos no utilizados, hallazgos de auditoría, etc.)
- Corrige cada error en su causa raíz mediante
Read+Edit. Los errores preexistentes también se corrigen — no importa si un error provino de este diff o ya estaba allí; no hace ninguna diferencia para que el proyecto se ejecute, y uncheckrojo bloquea a la siguiente persona igual de fuerte en cualquier caso. Los límites de alcance no se aplican a la capacidad de ejecución. La única pregunta que decide algo aquí es "¿se puede arreglar esto?", y mientras la respuesta sea sí, se arregla. - Vuelve a ejecutar
checkdesde cero. Nunca confíes en el estado parcial. - Continúa iterando hasta que se cumpla una de estas condiciones terminales:
- (a) VERDE —
checksale con 0 → hecho para este proyecto - (b) ESTANCADO — una iteración completa no produjo una reducción neta en el número de errores
- (a) VERDE —
- Sin límite fijo de iteraciones. Mientras cada iteración reduzca estrictamente el recuento de errores, continúa. El objetivo es el verde verdadero.
Una excepción estrecha, y se trata de colisión, no de alcance. El punto 2 se mantiene: un error preexistente se corrige, porque un check rojo bloquea a la siguiente persona independientemente de quién lo haya escrito. Lo que el punto 2 no cubre es un error dentro de trabajo no confirmado en el que otra sesión activa está en medio de un cambio — remodelar eso no es una cuestión de alcance, son dos sesiones editando un archivo, y el par regresa a una refactorización reescrita habiendo perdido más tiempo del que ahorró la corrección. Cuando el árbol está sucio y bash "${CLAUDE_PLUGIN_ROOT}/scripts/change-provenance.sh" atribuye la ruta fallida a un par activo: corrígelo si es trivial y autocontenido (un import faltante, una violación de formato, un console.log suelto), y de lo contrario repórtalo como un bloqueador nombrando al autor, más un SOLVED que lleve el diagnóstico para que pueda corregir su propio trabajo en un solo paso. Nadie activo, o nada atribuible: el punto 2 se aplica sin cambios.
ESTANCADO es una señal para cambiar de enfoque, no un permiso para detenerse. El recuento deja de caer cuando se reintenta la misma corrección, por lo que la siguiente iteración debe atacar el error de manera diferente: leer el archivo fallido en su totalidad en lugar de parchear alrededor de la línea reportada, verificar si dos errores comparten una causa, observar cómo un módulo hermano funcional resuelve lo mismo, ejecutar el paso fallido solo para ver la salida que la ejecución agregada tragó, verificar si es uno de los peligros conocidos en los Pasos 6.5 a 6.8. Solo cuando se haya probado un enfoque genuinamente diferente y el recuento aún se mantenga, se aplica la clasificación residual en el Paso 5 — y en el modo Bloqueante, todo excepto una vulnerabilidad de dependencia con escalera agotada aún detiene el flujo de trabajo.
Paso 4 — Hallazgos de auditoría: escalera de corrección obligatoria
Cuando pnpm audit / npm audit / yarn audit (invocado por check) reporta una vulnerabilidad, agota esta escalera antes de clasificar el hallazgo como Aceptado. Vuelve a ejecutar check después de cada paso.
| # | Paso | Comando (pnpm / npm / yarn) |
|---|---|---|
| 1 | Actualizar a la última versión compatible | pnpm update <pkg> / npm update <pkg> / yarn upgrade <pkg> |
| 2 | Corrección automática | pnpm audit --fix / npm audit fix / yarn audit --fix |
| 3 | Forzar actualización semver-major | pnpm audit --fix --force / npm audit fix --force |
| 4 | Subir la dependencia directa a la siguiente major si el advisory lista la corrección allí | pnpm add <pkg>@<major> / npm install <pkg>@<major> / yarn add <pkg>@<major> |
| 5 | Forzar una versión de dependencia transitiva | Bloque pnpm.overrides / resolutions (yarn) / overrides (npm) en package.json, luego reinstalar |
| 6 | Actualizar el CONSUMIDOR, no el paquete vulnerable | la dependencia directa que lo trae, y el framework lt si el framework lo fija (@lenne.tech/nest-server / @lenne.tech/nuxt-extensions) — un /lt-dev:maintenance:maintain completo es la forma exhaustiva |
| 7 | Parchear el consumidor — solo bajo las condiciones siguientes | pnpm patch <pkg> → patchedDependencies |
| 8 | Reemplazar el paquete | Cambiar a una alternativa mantenida si está abandonado |
El Paso 6 es el escalón que se salta, y es el que más a menudo funciona. Los pasos 1 a 5 empujan el paquete vulnerable hacia adelante. Cuando eso falla, la razón generalmente no es el paquete vulnerable en absoluto — es el consumidor quien lo retiene, fijando un rango vulnerable o usando una API que la major parcheada eliminó. Así que la pregunta a hacer es "¿quién mantiene esta versión aquí?", y la respuesta es una dependencia directa, o un framework cuyo pin una anulación no puede superar. Una anulación es un pin, y un pin es estrictamente peor que una subida de versión real: diverge el árbol de lo que prueba el upstream, y se pudre silenciosamente. Toma la subida donde exista.
El Paso 7 (parcheado) es estrecho a propósito, y la estrechez es el punto. Cuando la major parcheada solo falla al cargar — un export por defecto eliminado, un punto de entrada renombrado — un parche de una línea parece cerrar el advisory correctamente en lugar de suprimirlo. A veces lo hace. Tres condiciones deben cumplirse primero, y la tercera es la que generalmente falla:
- El parche apunta a una ruta estable en el paquete vulnerable, y está fijado a una versión exacta. Lo que rompe un parche no es
dist/como tal — los paquetes publicados envíandist/y nada más — es una ruta que se mueve entre versiones.dist/chunks/<hash>.mjses renombrado por la próxima compilación y el parche entonces falla para todos en el equipo;dist/index.mjses el propio punto de entrada del paquete y permanece. Un parche que nombra un símbolo minificado sigue siendo aceptable con el pin de versión, porque el pin convierte el riesgo en un fallo ruidoso depnpm installen lugar de un mal parche silencioso. Parchea la dependencia vulnerable, no un consumidor que casualmente la importa — ver el caso medido a continuación, donde la primera evaluación miró el chunk del consumidor y rechazó el escalón por esa base. - Sopesa el cierre de producción contra el costo de mantenimiento. Un parche es una obligación permanente en cada subida futura, por lo que para una ruta de herramientas de desarrollo que no puede llegar a producción, bien puede gastar más de lo que compra — en un solo proyecto. Una PLANTILLA invierte esa aritmética: cada proyecto generado hereda un parche, mientras que un residual tendría que ser discutido de nuevo en cada uno de ellos. Esto es un juicio, no un veto; las condiciones 1 y 3 son las difíciles.
- Cargar no es lo mismo que funcionar. Una major que eliminó un export generalmente también cambió el comportamiento, por lo que un import parcheado hace que el módulo importe mientras deja al consumidor ejecutándose contra una API en la que nunca fue probado. Si no puedes decir qué más cambió en esa major y por qué no importa aquí, el parche es una conjetura disfrazada de corrección.
Parchea el paquete VULNERABLE, no el consumidor. Esta es la distinción que decide si el escalón funciona, y es fácil de equivocarse, porque el error apunta al consumidor. Medido, lt-crm 2026-10-06: @nuxt/[email protected] hace import Git from 'simple-git', y subir simple-git a la única línea corregida (4.x, que eliminó ese export por defecto) hizo que nuxt prepare abortara con does not provide an export named 'default'. La primera evaluación miró el dist/chunks/module-main.mjs propio de devtools, encontró el import allí, y rechazó parchear por la condición 1 — un chunk compilado — que fue el veredicto correcto sobre el archivo equivocado. El parche pertenece un nivel más abajo, en el dist/index.mjs de simple-git, donde una sola línea añadida reexporta como default la misma fábrica que el módulo ya exporta como simpleGit. Nada sobre el código endurecido cambia; solo la superficie del módulo vuelve.
Con eso, las tres condiciones se cumplen: es el paquete vulnerable en lugar de un artefacto del consumidor, está fijado a una versión exacta (patchedDependencies: { '[email protected]': … } más el destino de anulación), y la API que devtools realmente llama — branch, revparse, log — es la misma en 3.x y 4.x. Si lo que devtools llama hubiera cambiado, el parche aún se consideraría, pero la inspección directa del código fuente de devtools lo habría revelado; no adivines qué API interna usa.
Paso 5 — Clasificar errores residuales
Regla fundamental: Un check rojo nunca se acepta en modo Bloqueante excepto para el único caso listado a continuación. Si no puedes hacer que se ponga verde, no puedes entregar.
| Estado | Ejemplo | Modo Advisory | Modo Bloqueante |
|---|---|---|---|
| Error de lint | Variable no utilizada | Reportar como Crítico | BLOQUEAR — detener el flujo de trabajo |
| Error de tipo | Asignación number a string | Reportar como Crítico | BLOQUEAR |
| Error de compilación | Módulo no encontrado | Reportar como Crítico | BLOQUEAR |
| Error de prueba | Prueba fallida (dentro de check) | Reportar como Crítico | BLOQUEAR |
| Vulnerabilidad de auditoría | high severity en lodash | Reportar como Crítico | BLOQUEAR (a menos que la escalera esté agotada) |
| Vulnerabilidad de dependencia ascendente, escalera agotada | CVE en librería interna de 3ros sin parche | Reportar como Crítico | Aceptado — único residual permitido en modo Bloqueante |
Paso 6 — Informar el resultado
Proporciona al invocador un resumen claro:
- Lista de proyectos verificados y su estado final (VERDE / ESTANCADO / SALTADO)
- Para cada proyecto VERDE: confirmación de que
checksalió con 0 en la iteración final - Para cada proyecto ESTANCADO:
- Recuento de errores en la última iteración y en la penúltima (para demostrar la meseta)
- Los 3-5 errores principales con rutas de archivo, mensajes y contexto de código (suficiente para que un humano empiece a depurar)
- En modo Advisory: reportar como Hallazgo Crítico de Revisión
- En modo Bloqueante: reportar como Bloqueador + razón por la que no se pudo resolver (cualquier residual que no sea de escalera agotada = bloqueador)
- Para hallazgos de auditoría: mostrar qué escalones se probaron y dónde se detuvo la escalera
- En modo Bloqueante: declaración explícita: "Listo para enviar" o "BLOQUEADO — consulte los residuales arriba"
Paso 7 — Limpiar (solo en modo Bloqueante)
Antes de devolver el control:
git diff --stat
Confirma que todos los cambios son intencionales y que no se modificó ningún archivo fuera del alcance de la tarea (a menos que sea necesario para la ejecutabilidad). Si se corrigieron errores preexistentes fuera del alcance, enuméralos en el informe como "Correcciones de ejecutabilidad fuera del alcance" para visibilidad del equipo.
Paso 8 — Plantilla de informe
## Resultado de `check`
**Modo:** {Advisory|Bloqueante}
| Proyecto | Estado | Iteraciones | Notas |
|----------|--------|-------------|-------|
| `apps/web` | VERDE | 3 | Se corrigieron 2 lint, 1 error de tipo |
| `packages/api` | ESTANCADO | 7 | 3 errores restantes (ver abajo) |
| `packages/shared` | SALTADO | — | Sin script `check` |
### Detalles de ESTANCADO
**packages/api** — meseta en 3 errores (últimas 2 iteraciones):
1. `src/users/user.service.ts:42` — `Type 'string | undefined' is not assignable to type 'string'` (error preexistente)
2. `src/auth/auth.controller.ts:15` — `Module 'crypto' has no exported member 'randomUUID'` (requiere `@types/node`)
3. `pnpm audit` — `protobufjs` CVE-2024-..., escalera agotada en el paso 6 (framework lt lo fija)
### Hallazgos de auditoría
| Paquete | Gravedad | Escalera | Resultado |
|---------|----------|----------|-----------|
| `protobufjs` | Alta | Pasos 1-6 probados | Aceptado — fijado por `@lenne.tech/nest-server`, se necesita actualización del framework |
### Correcciones fuera del alcance
- `src/config/index.ts`: se añadió el import faltante de `ConfigService` (rotura preexistente)
**Veredicto:** {Listo para enviar|BLOQUEADO — ver arriba}
Peligros conocidos y estrategias de depuración
Paso 6.5 — Ejecuciones fallidas de preguntas de ESM/CJS
Cuando un script check ejecuta múltiples herramientas secuencialmente, una herramienta posterior puede fallar porque una anterior dejó el proceso en un estado inconsistente (por ejemplo, jest carga ESM y luego tsc falla debido a cambios en tsconfig.json). Si ves fallos extraños de compilación o prueba después de una corrección exitosa de lint, intenta limpiar:
git stash # o guarda el trabajo actual
<pkg mgr> run check # línea base limpia
git stash pop
<pkg mgr> run check # ahora con los cambios
Paso 6.6 — Límites de memoria/node --heap
Las compilaciones grandes pueden fallar con JavaScript heap out of memory. Si los errores no son de lint ni de tipo, comprueba:
NODE_OPTIONS="--max-old-space-size=4096" <pkg mgr> run check
Paso 6.7 — Errores de tipo que desaparecen al re-ejecutar
tsc --noEmit a veces reporta errores de tipo transitorios cuando se usa con incremental: true. Si el Paso 3 está atascado en un error que ya parece corregido:
rm -rf node_modules/.cache/typescript
<pkg mgr> run check
Paso 6.8 — Contaminación de caché de lint
Si una corrección de lint se aplicó pero la re-ejecución aún reporta el antiguo error de lint:
rm -rf node_modules/.cache/eslint # o .oxlint_cache
<pkg mgr> run check
Paso 6.9 — Errores de tipo de Vue/SCSS en devtools
Los proyectos devtools de nuxt: a veces vue-tsc se queja de tipos que solo existen en tiempo de compilación de Vite. Si el error está en un archivo .vue y menciona una macro o import virtual, ejecuta npm run dev una vez para generar los tipos, luego vuelve a ejecutar check.