From c5191d953a8897221b294c97cdbcebcd7d42723f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pol=20Marz=C3=A0?= Date: Tue, 18 Aug 2026 14:20:26 +0200 Subject: [PATCH 1/2] feat: requisitos verificables, ficha de feature y cobertura verificada en CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El protocolo era enteramente autodeclarado: todas las reglas eran "el agente debe" y el checklist del PR lo marcaba el mismo agente que había hecho el trabajo. Faltaba además la unidad de trabajo: una feature existía como conversación → código → changelog escrito a posteriori, sin ningún artefacto que fijara el acuerdo antes de escribir código. - Los requisitos del PRD llevan identificador estable (M-01, S-01) y criterio de aceptación comprobable "Dado…, cuando…, entonces…", con resultado observable y no un adjetivo - Nueva capa docs/features/: una ficha por unidad de trabajo, con tabla de cobertura y tres estados (Acordada / En construcción / Verificada) que permiten retomar el trabajo en otra sesión - Ningún requisito se queda sin tercera columna: o la ruta de su test, o "no verificable por interfaz: " y cómo se comprueba entonces - Los tests se escriben después de implementar, leyendo el código real; escritos antes apuntan a selectores imaginados y acaban vaciándose de aserciones hasta que pasan - El PR se cierra con la salida real de los comandos pegada, no con casillas: quien afirma haber verificado y quien tenía que verificar son el mismo - scripts/verificar-cobertura.mjs valida las tablas contra docs/prd.md y falla si un test declarado no existe en una ficha Verificada. Node sin dependencias, ejecutado en CI en cada pull request - Tabla de proporcionalidad: qué documentos de docs/ exige cada tamaño de proyecto, para que la ceremonia escale con lo que está en juego - Nueva sección "Límites de ejecución": todo se prueba en localhost, el agente no despliega, los secretos no van por línea de comandos, nada destructivo sin confirmación previa con el alcance exacto - Comandos nuevos: /feature (ficha antes de construir) y /doctor (parte del estado de documentación, entorno, variables, MCPs y tests) La verificación solo exige que los archivos de test existan en estado Verificada. Los tests van después de implementar, así que una ficha en construcción sin el archivo creado es lo correcto; un script que chillara ahí daría rojos legítimos y acabaría desactivado. Co-Authored-By: Claude Opus 5 --- .claude/commands/changelog.md | 4 +- .claude/commands/doctor.md | 72 ++++++ .claude/commands/feature.md | 45 ++++ .claude/commands/init-proyecto.md | 20 +- .claude/settings.json | 4 + .github/pull_request_template.md | 32 ++- .github/workflows/cobertura.yml | 26 ++ ...2-39_verificabilidad-y-ciclo-de-feature.md | 100 +++++++ ...13_verificacion-ejecutable-de-cobertura.md | 74 ++++++ CLAUDE.md | 185 +++++++++++-- README.md | 37 ++- changelog/README.md | 1 + docs/architecture.md | 7 +- docs/features/README.md | 120 +++++++++ docs/prd.md | 38 ++- docs/testing.md | 30 +++ scripts/verificar-cobertura.mjs | 244 ++++++++++++++++++ 17 files changed, 998 insertions(+), 41 deletions(-) create mode 100644 .claude/commands/doctor.md create mode 100644 .claude/commands/feature.md create mode 100644 .github/workflows/cobertura.yml create mode 100644 .template/changelog/2026-08-18_12-39_verificabilidad-y-ciclo-de-feature.md create mode 100644 .template/changelog/2026-08-18_13-13_verificacion-ejecutable-de-cobertura.md create mode 100644 docs/features/README.md create mode 100755 scripts/verificar-cobertura.mjs diff --git a/.claude/commands/changelog.md b/.claude/commands/changelog.md index b584e10..2fd81d8 100644 --- a/.claude/commands/changelog.md +++ b/.claude/commands/changelog.md @@ -3,6 +3,8 @@ Crea una nueva entrada en `changelog/` siguiendo el protocolo del proyecto. 1. Usa la fecha y hora actuales para nombrar el archivo: `YYYY-MM-DD_HH-MM_descripcion-breve.md` 2. Si el usuario no ha indicado qué cambio registrar, pregúntale. 3. Rellena las tres secciones obligatorias: qué se hizo, qué archivos se modificaron, por qué. -4. Si el cambio afecta algún documento de `docs/`, recuérdale al usuario que hay que actualizarlo en esta misma sesión. +4. Rellena el campo `Requisitos` con los IDs del PRD que este cambio deja terminados (`M-01`, `S-02`…). Si es un cambio interno —refactor, tooling, documentación— escribe "ninguno"; no lo dejes en blanco. +5. Si el cambio cierra una feature, comprueba que su ficha de `docs/features/` está en estado **Verificada** antes de escribir la entrada. +6. Si el cambio afecta algún documento de `docs/`, recuérdale al usuario que hay que actualizarlo en esta misma sesión. Si existe la carpeta `.template/` y el cambio es sobre la plantilla en sí (CLAUDE.md, docs vacíos, comandos, plantillas de GitHub), escribe la entrada en `.template/changelog/` en lugar de `changelog/`. La carpeta `changelog/` se reserva para el proyecto que use la plantilla. diff --git a/.claude/commands/doctor.md b/.claude/commands/doctor.md new file mode 100644 index 0000000..cd7c271 --- /dev/null +++ b/.claude/commands/doctor.md @@ -0,0 +1,72 @@ +Revisa el estado del proyecto y del entorno, y da un parte de qué está bien, qué falta y cómo +arreglarlo. **Solo diagnostica: no arregla nada por su cuenta.** Al terminar, propón las +correcciones y deja que el usuario decida cuáles aplicar. + +Ejecútalo cuando alguien entra al proyecto por primera vez, cuando algo falla sin motivo aparente +o antes de una sesión larga, para no descubrir a mitad que faltaba media configuración. + +## Qué comprobar + +### 1. Documentación + +- ¿Existen todos los archivos que `CLAUDE.md` marca como obligatorios para este proyecto? (La + obligatoriedad depende del tamaño: mira la tabla "Qué documentación necesita cada proyecto".) +- ¿Alguno está vacío — solo comentarios ``, sin contenido real? +- ¿Sigue existiendo `.template/`? Entonces la inicialización quedó a medias. + +### 2. Fichas de feature + +Ejecuta la verificación de cobertura y reporta su salida tal cual: + +```bash +node scripts/verificar-cobertura.mjs +``` + +Comprueba las tablas contra `docs/prd.md`: filas sin validación declarada, excepciones que no +explican nada, identificadores inexistentes y tests prometidos que no existen en fichas +**Verificada**. Sus FALLO son FALLO aquí; sus ATENCIÓN son ATENCIÓN. + +Añade a mano lo que el script no mira, porque requiere criterio: + +- Fichas en **En construcción**: son trabajo a medias. Di cuáles y desde cuándo. +- Tests declarados que existen pero están vacíos o sin aserciones reales. El script solo comprueba + que el archivo esté ahí; si te cruzas con uno hueco, es un FALLO aunque la verificación pase. + +### 3. Entorno + +- Versión de Node y de pnpm frente a lo que declare `CLAUDE.md`. Si no coinciden, dilo: la mayoría + de fallos raros de instalación son esto. +- ¿Están las dependencias instaladas (`node_modules/`)? ¿El lockfile está al día respecto a + `package.json`? +- Variables: compara los nombres de `.env.example` con los que hay definidos en el entorno o en + `.env.local`. Reporta **solo los nombres que faltan**. Nunca imprimas un valor, ni completo ni + parcial, ni siquiera para confirmar que es correcto. + +### 4. Servidores MCP + +Ejecuta `claude mcp list`. Contrasta el resultado con la tabla "MCPs del proyecto" de +`docs/architecture.md`: + +- Servidores documentados que no arrancan o no aparecen. +- Servidores configurados que no están documentados. + +### 5. Tests + +- ¿Existe el comando de test que declara `docs/testing.md`? ¿Arranca? +- Si es barato, ejecútalo y reporta el resultado real. Si tarda o necesita servicios levantados, no + lo lances: di que no se ha ejecutado y por qué. **No des por bueno lo que no has visto pasar.** + +## Cómo reportar + +Una tabla, un renglón por comprobación: + +| Comprobación | Estado | Detalle | +|--------------|--------|---------| +| Documentación | OK | 6 de 6 archivos con contenido | +| Fichas de feature | ATENCIÓN | `registro-usuarios` lleva 3 semanas En construcción | +| Node / pnpm | FALLO | pnpm 10.4 instalado, el proyecto pide v11 | + +Tres estados y nada más: **OK**, **ATENCIÓN** (funciona pero hay deuda) y **FALLO** (algo está roto +o falta). Para cada FALLO, di el comando exacto que lo arregla. + +Si todo está en orden, dilo en una línea y no adornes el informe. diff --git a/.claude/commands/feature.md b/.claude/commands/feature.md new file mode 100644 index 0000000..426db88 --- /dev/null +++ b/.claude/commands/feature.md @@ -0,0 +1,45 @@ +Crea la ficha de una feature nueva en `docs/features/`, siguiendo el formato de +`docs/features/README.md`. + +Esto se hace **antes** de escribir código, no después. La ficha es el acuerdo previo; si ya hay +código escrito, lo que toca es una entrada de changelog, no una ficha retroactiva. + +## 1. Comprueba que hace falta + +Una ficha se justifica si el trabajo cierra requisitos del PRD, toca varias capas o va a durar más +de una sesión. Para un arreglo puntual o un cambio de copy, dilo y no la crees: basta el changelog +al terminar. + +## 2. Reúne el contexto + +Lee `docs/prd.md` para localizar qué requisitos (`M-01`, `S-02`…) cierra esta feature. Si el +trabajo no se corresponde con ningún requisito del PRD, hay dos posibilidades y conviene +preguntarlas antes de seguir: + +- Es alcance nuevo → hay que añadirlo al PRD primero, con su ID y su criterio de aceptación. +- Está fuera de alcance → va a `mejoras/`, no a `docs/features/`. + +Lee también `docs/architecture.md` y `docs/data-model.md` si la feature toca estructura o datos. + +## 3. Pregunta lo que no puedas deducir + +- Nombre de la feature (el archivo será `kebab-case.md`) +- Qué debe poder hacer el usuario cuando esto exista +- Qué queda explícitamente fuera + +## 4. Escribe la ficha + +Usa la plantilla de `docs/features/README.md`. Estado inicial: **Acordada**. + +La tabla de cobertura se rellena entera, sin huecos. Por cada requisito, la tercera columna lleva +o la ruta del test que lo validará, o `no verificable por interfaz: ` seguido de +cómo se comprobará entonces. Si no sabes cuál de las dos poner, pregunta — no lo dejes en blanco +ni escribas un test que sabes que no vas a escribir. + +## 5. Confirma antes de construir + +Enseña la ficha al usuario y pregunta si el acuerdo es correcto. Con su visto bueno, cambia el +estado a **En construcción** y empieza. + +Mantén el estado al día durante el trabajo, no al final: es lo que permite retomar la feature en +otra sesión sin reconstruir el contexto. diff --git a/.claude/commands/init-proyecto.md b/.claude/commands/init-proyecto.md index e51b424..1ffc91e 100644 --- a/.claude/commands/init-proyecto.md +++ b/.claude/commands/init-proyecto.md @@ -5,7 +5,9 @@ Convierte esta plantilla en el repositorio del proyecto real. Es un proceso de u 1. Lee todos los archivos de `docs/`. 2. Si están vacíos o incompletos, **no inicialices todavía**: primero complétalos con el usuario siguiendo el orden de `CLAUDE.md` (prd.md → business.md → design-system.md → architecture.md → - data-model.md → roadmap.md → user-flows.md). + data-model.md → roadmap.md → user-flows.md). No hacen falta los ocho: mira antes la tabla "Qué + documentación necesita cada proyecto" de `CLAUDE.md` y pide solo los que apliquen al tamaño de + este proyecto. 3. Si no existe `.template/`, el repo ya está inicializado. Dilo y no toques nada, salvo que el usuario pida rehacer algo concreto. @@ -32,12 +34,18 @@ Ejecuta el checklist de "Inicialización del proyecto" de `CLAUDE.md`: 4. `.env.example` — deja solo las variables del stack real. 5. MCPs — pregunta qué servidores MCP quiere y con qué alcance, siguiendo el "Protocolo de MCPs" de `CLAUDE.md`. Si prefieres tratarlo aparte, lanza `/mcp-setup`. -6. `mejoras/backlog.md` — borra el ejemplo comentado. -7. `.template/` — bórrala (`rm -rf .template`). -8. `changelog/` — crea la primera entrada real del proyecto (tipo: Configuración) con `/changelog` +6. `docs/` — borra los documentos que este proyecto no necesite según la tabla de tamaños. Los que + no aplican se borran, no se dejan vacíos: un archivo con solo comentarios hace que el arranque + de cada sesión se pare a preguntar por él. `docs/features/` se queda vacía, solo con su + `README.md`. +7. `mejoras/backlog.md` — borra el ejemplo comentado. +8. `.template/` — bórrala (`rm -rf .template`). +9. `changelog/` — crea la primera entrada real del proyecto (tipo: Configuración) con `/changelog` y limpia de `changelog/README.md` la referencia a la plantilla. -9. Verifica que no queden restos: - `grep -ril "plantilla\|template" . --exclude-dir=.git --exclude-dir=node_modules` +10. Verifica que no queden restos: + `grep -ril "plantilla\|template" . --exclude-dir=.git --exclude-dir=node_modules` +11. Pasa `/doctor` como última comprobación: entorno, variables, MCPs y tests. Si algo sale en + FALLO, arréglalo antes de dar la inicialización por terminada. ## Al terminar diff --git a/.claude/settings.json b/.claude/settings.json index af8f92c..563ddde 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,6 +5,10 @@ "Bash(pnpm run *)", "Bash(pnpm dlx *)", "Bash(pnpm *)", + "Bash(node scripts/*)", + "Bash(node -v)", + "Bash(node --version)", + "Bash(claude mcp list)", "Bash(git status)", "Bash(git diff*)", "Bash(git log*)", diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3fea742..813bd2e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,6 +7,10 @@ ## Motivación +## Requisitos que cierra + + ## Tipo de cambio - [ ] Feature - [ ] Fix @@ -15,8 +19,34 @@ - [ ] Documentación - [ ] Configuración +## Evidencia + + + +``` +$ pnpm test +... +``` + +**Verificación de los requisitos:** + + + +| Requisito | Se validó con | Resultado | +|-----------|---------------|-----------| +| | | | + ## Checklist + + + - [ ] Los documentos afectados en `docs/` están actualizados +- [ ] La ficha de `docs/features/` está en estado **Verificada** (si este PR cierra una feature) - [ ] Hay una entrada en `changelog/` con este cambio -- [ ] He probado el cambio en local antes de pedir review +- [ ] La sección "Evidencia" contiene salida real de comandos, no una descripción de lo que pasaría - [ ] Se ha ejecutado `/security-review` si hay cambios sensibles diff --git a/.github/workflows/cobertura.yml b/.github/workflows/cobertura.yml new file mode 100644 index 0000000..2521486 --- /dev/null +++ b/.github/workflows/cobertura.yml @@ -0,0 +1,26 @@ +# Verifica que ninguna ficha de docs/features/ deje un requisito sin validación declarada, +# y que los tests prometidos existan cuando la ficha dice estar Verificada. +# +# Se ejecuta en CI a propósito: el agente que rellena las tablas es el mismo que las cumpliría, +# así que la comprobación tiene que vivir donde no pueda saltársela. + +name: Cobertura + +on: + pull_request: + push: + branches: [main] + +jobs: + verificar: + name: Tablas de cobertura + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + # Sin dependencias: el script usa solo módulos nativos de Node. + - run: node scripts/verificar-cobertura.mjs diff --git a/.template/changelog/2026-08-18_12-39_verificabilidad-y-ciclo-de-feature.md b/.template/changelog/2026-08-18_12-39_verificabilidad-y-ciclo-de-feature.md new file mode 100644 index 0000000..a6444bb --- /dev/null +++ b/.template/changelog/2026-08-18_12-39_verificabilidad-y-ciclo-de-feature.md @@ -0,0 +1,100 @@ +# Verificabilidad: criterios de aceptación, ficha de feature y evidencia en el PR + +**Fecha:** 2026-08-18 12:39 +**Tipo:** Documentación +**Requisitos:** Ninguno (cambio sobre el andamiaje de la plantilla) + +## Qué se hizo + +La plantilla cubría bien el principio de un proyecto (documentar antes de escribir) y el registro +posterior (changelog, PR, mejoras), pero no decía nada sobre cómo se sabe que algo está terminado. +"Hecho" quedaba en manos de quien lo declaraba. Este cambio cierra ese hueco en cuatro piezas. + +**1. Los requisitos del PRD ahora son comprobables.** Cada entrada MoSCoW de `docs/prd.md` lleva +un identificador estable (`M-01`, `S-01`, `C-01`) y un criterio de aceptación en formato +"Dado…, cuando…, entonces…", con la condición de que el "entonces" sea observable: un mensaje +visible, una redirección, un registro creado. No un adjetivo. El ID es el nombre por el que ese +requisito se cita después en la ficha, en el changelog, en el PR y en el nombre del test. + +**2. Nueva capa intermedia: `docs/features/`.** Entre la documentación del proyecto y el código no +había ninguna unidad de trabajo. Ahora cada feature tiene su ficha —qué se construye, qué +requisitos cierra, qué queda fuera— y sobre todo una **tabla de cobertura** con una regla: ningún +requisito se queda sin tercera columna. O lleva la ruta del test que lo valida, o lleva +`no verificable por interfaz: ` y cómo se comprueba entonces. La ficha tiene tres +estados (Acordada / En construcción / Verificada) que se actualizan durante el trabajo, no al +final, para poder retomar una feature en otra sesión sin reconstruir el contexto. + +**3. Los tests se escriben después de implementar.** Nueva sección en `docs/testing.md`. El +compromiso de validar se adquiere antes (la tabla de cobertura); el test se escribe leyendo el +código ya existente. Con las reglas que se derivan: verificar que el selector existe antes de +asertar, añadir `data-testid` si no hay selector estable, una aserción por cada "entonces", y +nunca arreglar un test que falla quitándole aserciones. + +**4. El PR se cierra con evidencia.** La plantilla de PR pide ahora los requisitos que cierra, una +sección de evidencia con la salida real de los comandos ejecutados y una tabla de verificación por +requisito. El checklist sigue estando, pero deja de ser la prueba: lo que prueba es la salida +pegada. + +Además, dos cosas que faltaban y no dependen del stack: + +- **Tabla de proporcionalidad** en `CLAUDE.md`: qué documentos de `docs/` son obligatorios según + el tamaño del proyecto (sitio pequeño / producto / producto con negocio). Los que no aplican se + borran en la inicialización, no se dejan vacíos. +- **Sección "Límites de ejecución"** en `CLAUDE.md`: todo se prueba en local, el agente no + despliega, los secretos no se imprimen ni se pasan por la línea de comandos, y nada destructivo + sin confirmación previa con el alcance exacto. + +Dos comandos nuevos: `/feature` (crea la ficha antes de construir) y `/doctor` (parte del estado +de documentación, fichas a medias, entorno, variables, MCPs y tests; solo diagnostica, no arregla). + +## Qué se modificó + +- `CLAUDE.md` — nueva sección "Qué documentación necesita cada proyecto" con la tabla de tamaños; + nueva sección "Límites de ejecución"; nueva sección "Ciclo de trabajo de una feature"; paso 5 de + arranque (revisar fichas En construcción) y referencia a `/doctor`; campo `Requisitos` en el + formato de changelog; dos ejemplos nuevos en la lista de documentación afectada; pasos 2 y 4 del + protocolo de PRs (requisitos y evidencia) con el apartado "Por qué la evidencia y no la casilla"; + dos reglas nuevas en "Qué NO hacer"; `docs/features/` en la estructura de carpetas; paso 7 del + checklist de inicialización (borrar documentos que no apliquen) y renumeración +- `docs/prd.md` — IDs estables y criterios de aceptación en el bloque MoSCoW +- `docs/features/README.md` — nuevo: formato de la ficha, regla de la tabla de cobertura y estados +- `docs/testing.md` — nueva sección "Cuándo se escriben los tests" +- `docs/architecture.md` — la estrategia de despliegue debe dejar escrito quién despliega +- `.claude/commands/feature.md` — nuevo comando `/feature` +- `.claude/commands/doctor.md` — nuevo comando `/doctor` +- `.claude/commands/changelog.md` — campo `Requisitos` y comprobación del estado de la ficha +- `.claude/commands/init-proyecto.md` — tabla de tamaños al completar docs; paso de borrado de + documentos que no apliquen; `/doctor` como comprobación final; renumeración +- `.github/pull_request_template.md` — sección "Requisitos que cierra", sección "Evidencia" con + tabla de verificación, checklist reformulado +- `changelog/README.md` — campo `Requisitos` en el formato, sincronizado con `CLAUDE.md` +- `.claude/settings.json` — permitidas tres comprobaciones de solo lectura que necesita `/doctor`: + `node -v`, `node --version` y `claude mcp list` +- `README.md` — `docs/features/` en el contenido; el protocolo pasa de 6 a 10 pasos; comandos + nuevos; filas de `docs/` y `docs/features/` en la tabla de adaptación; `/doctor` en el arranque + +## Por qué + +El protocolo anterior era enteramente autodeclarado. Todas las reglas eran "el agente debe", y el +checklist del PR lo marcaba el mismo agente que había hecho el trabajo: quien afirmaba haber +verificado y quien tenía que verificar eran el mismo. Una casilla marcada no distingue entre "lo +ejecuté y pasó" y "estoy razonablemente seguro de que pasaría"; la salida de un comando sí. + +El hueco de fondo era otro: la plantilla gobernaba el proyecto pero no la unidad de trabajo. Una +feature existía como conversación → código → entrada de changelog escrita a posteriori. No había +ningún artefacto que dijera "esto es lo que acordamos construir y así sabremos que funciona" +**antes** del código, así que el alcance se renegociaba solo, sin que nadie lo notara. + +La regla de la tercera columna es la que sostiene el resto. Lo que se queda sin validar casi nunca +se decide: se escurre. Nadie dice "este requisito no lo vamos a comprobar"; simplemente no aparece +en ningún sitio y nadie lo echa de menos hasta que falla. Obligar a escribir la excepción convierte +una omisión invisible en una frase que alguien puede leer y discutir. + +Lo de escribir los tests después de implementar viene del mismo sitio. Un test escrito durante la +planificación apunta a selectores y rutas imaginados; cuando no coinciden con la realidad, casi +nadie lo reescribe: se le van quitando aserciones hasta que pasa, y queda un test que no comprueba +nada pero da luz verde. Es peor que no tenerlo, porque además tranquiliza. + +La tabla de proporcionalidad resuelve el problema opuesto. Exigir ocho documentos rellenos para una +landing es la forma más rápida de que el protocolo se abandone en la segunda semana, y un protocolo +abandonado no protege nada. La ceremonia tiene que escalar con lo que está en juego. diff --git a/.template/changelog/2026-08-18_13-13_verificacion-ejecutable-de-cobertura.md b/.template/changelog/2026-08-18_13-13_verificacion-ejecutable-de-cobertura.md new file mode 100644 index 0000000..15f6c69 --- /dev/null +++ b/.template/changelog/2026-08-18_13-13_verificacion-ejecutable-de-cobertura.md @@ -0,0 +1,74 @@ +# La regla de la tercera columna pasa a verificarse sola + +**Fecha:** 2026-08-18 13:13 +**Tipo:** Feature +**Requisitos:** Ninguno (cambio sobre el andamiaje de la plantilla) + +## Qué se hizo + +La tabla de cobertura de `docs/features/` tenía una regla clara —ningún requisito sin su tercera +columna— pero era prosa: dependía de que quien rellenaba la tabla la cumpliera. Ahora hay un script +que la comprueba, y corre en CI. + +**`scripts/verificar-cobertura.mjs`** lee `docs/prd.md` y todas las fichas de `docs/features/`, y +falla si encuentra: + +- Una fila sin la columna "Se valida con" rellena. +- Una excepción `no verificable por interfaz:` sin razón, o con una razón de menos de 15 + caracteres ("no aplica" no cuela). +- Una tercera columna que no es ni ruta ni excepción ("pendiente", "TBD", un guion). +- Un identificador que no está declarado en `docs/prd.md`, o declarado dos veces allí. +- Un requisito listado en "Requisitos que cierra" que no tiene fila en la tabla. +- Una ficha sin `**Estado:**` válido o sin sección `## Cobertura`. +- **Un test declarado que no existe en disco**, cuando la ficha dice estar **Verificada**. + +Ese último es el que justifica el script: es el fallo típico al delegar —declarar +`tests/registro.spec.ts` en la tabla y no escribirlo nunca— y es invisible leyendo el diff, porque +no aparece un archivo que no existe. + +Node sin dependencias, solo módulos nativos. No hay nada que instalar y no toca `package.json`. + +**`.github/workflows/cobertura.yml`** lo ejecuta en cada pull request y en cada push a `main`. + +## Qué se modificó + +- `scripts/verificar-cobertura.mjs` — nuevo +- `.github/workflows/cobertura.yml` — nuevo +- `CLAUDE.md` — comando en el paso "Cerrar" del ciclo de feature; nuevo apartado "La verificación + de cobertura" con qué comprueba, qué no, y por qué corre en CI +- `docs/features/README.md` — la regla de la tercera columna ahora remite al script; aclarado que + una fila puede declarar varios tests separados por comas +- `.claude/commands/doctor.md` — la comprobación de fichas pasa a ejecutar el script y reportar su + salida; se mantiene a mano lo que exige criterio (tests existentes pero vacíos) +- `.claude/settings.json` — permitido `node scripts/*` +- `README.md` — `scripts/` en el contenido; paso 3 del protocolo; fila en la tabla de adaptación + +## Por qué + +Hasta ahora todo el protocolo era autodeclarado, y con un agente de por medio eso significa que +quien afirma haber cumplido y quien tenía que cumplir son el mismo. La regla de la tercera columna +era la más importante del ciclo de feature y también la más fácil de incumplir en silencio. + +Corre en CI a propósito, no en un hook local ni solo cuando el agente se acuerda: una comprobación +que el comprobado puede saltarse no es una comprobación. Si falla en CI se arregla la causa; no se +toca el workflow. + +**La decisión de diseño que sostiene todo esto** es que la existencia de los archivos solo se exige +en estado **Verificada**. Los tests se escriben después de implementar, así que una ficha en +construcción con el archivo aún sin crear es lo correcto, no un fallo. Un script que chillara ahí +daría rojos legítimos que habría que ignorar, y una verificación que se ignora por sistema es peor +que ninguna: enseña a ignorar los rojos. + +Lo que comprueba es estructural, no semántico. Un test vacío pasa la verificación. Pero un archivo +vacío se ve en el diff del PR y uno inexistente no, así que el suelo sube: ya no basta con no +escribir el test. + +## Verificado + +Probado sobre una copia del repositorio con fichas de prueba: + +- Plantilla sin rellenar y repositorio sin fichas → sale limpio, código 0. Era el caso crítico: si + un clon recién hecho diera rojo, el workflow nacería desactivado. +- Ficha **Verificada** con test existente y una excepción bien justificada → 0 fallos. +- Ficha **En construcción** con el test todavía sin escribir → 0 fallos (no debe exigirlo). +- Ficha con los nueve modos de fallo mezclados → los nueve detectados, código 1. diff --git a/CLAUDE.md b/CLAUDE.md index 7b200d3..4335603 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,17 +14,62 @@ Antes de hacer cualquier cosa, comprueba el estado del repositorio: - No escribas código - No rellenes nada todavía - Empieza con esta pregunta: "¿Qué quieres construir y para quién?" - - A partir de la respuesta, haz las preguntas necesarias para completar - los documentos de docs/ en este orden: prd.md → business.md → + - Con la respuesta en la mano, decide **qué documentación necesita este proyecto** según la + tabla de la sección siguiente, y dilo antes de empezar a preguntar. No pidas ocho documentos + para una landing. + - Completa los documentos que apliquen en este orden: prd.md → business.md → design-system.md → architecture.md → data-model.md → roadmap.md → user-flows.md - Confirma con el usuario antes de pasar al siguiente documento - - Cuando todos estén rellenos, ejecuta la **inicialización del proyecto** (sección + - Cuando estén rellenos, ejecuta la **inicialización del proyecto** (sección siguiente) y solo después pregunta: "¿Empezamos a construir?" 4. Si los documentos ya tienen contenido: lee todo lo que haya en `docs/` antes de actuar. Si además `.template/` sigue existiendo, la inicialización quedó a medias: avisa al usuario y ofrécete a completarla antes de seguir. +5. Mira `docs/features/`. Si hay alguna ficha en estado **En construcción**, ahí está el trabajo a + medias: léela antes de proponer nada nuevo. Es más rápido y más fiable que reconstruir el + contexto a partir del historial de git. + +Si algo no cuadra (falta configuración, los tests no arrancan, hay fichas colgadas), `/doctor` da +el parte completo del estado del proyecto y del entorno. + +--- + +## Qué documentación necesita cada proyecto + +`docs/` tiene ocho archivos, pero **no todos los proyectos necesitan los ocho**. Pedirlos siempre +es la forma más rápida de que el protocolo se abandone en la segunda semana: para una landing de +una página, rellenar un modelo de datos y un plan de negocio es burocracia, y la burocracia inútil +enseña a saltarse el proceso entero. + +Decide el tamaño al principio, dilo en voz alta y ajústate a la tabla: + +| Documento | Sitio pequeño | Producto | Producto con negocio detrás | +|-----------|:-------------:|:--------:|:---------------------------:| +| `prd.md` | Obligatorio | Obligatorio | Obligatorio | +| `architecture.md` | Obligatorio | Obligatorio | Obligatorio | +| `testing.md` | Si hay lógica | Obligatorio | Obligatorio | +| `design-system.md` | Recomendado | Obligatorio | Obligatorio | +| `data-model.md` | Si hay datos | Obligatorio | Obligatorio | +| `roadmap.md` | — | Obligatorio | Obligatorio | +| `user-flows.md` | — | Si hay flujos con estado | Obligatorio | +| `business.md` | — | Si se monetiza | Obligatorio | + +- **Sitio pequeño:** landing, portfolio, sitio de contenido. Poca lógica, sin cuentas de usuario. +- **Producto:** hay usuarios, estado y datos que persisten. +- **Producto con negocio detrás:** además hay que cobrar, medir o justificar decisiones a alguien. + +Reglas de la tabla: + +- `prd.md` y `architecture.md` no se saltan nunca. Sin saber qué se construye y sobre qué, no hay + proyecto que documentar. +- Un documento que no aplique **se borra**, no se deja vacío. Un archivo con solo comentarios es + indistinguible de uno que se olvidó rellenar, y el arranque de cada sesión se para a preguntar + por él. +- El tamaño puede subir a mitad de camino. Cuando un sitio pequeño empieza a tener cuentas de + usuario, toca crear los documentos que faltan — en ese momento, no al final. + --- ## Inicialización del proyecto (una sola vez) @@ -56,12 +101,15 @@ Puedes lanzar el proceso completo con `/init-proyecto`. 6. **`changelog/`** — debe quedar sin entradas heredadas. Crea la primera entrada real del proyecto (tipo: Configuración) describiendo la inicialización, y quita de `changelog/README.md` la referencia a la plantilla (o borra el archivo). -7. **`mejoras/backlog.md`** — borra el ejemplo comentado y déjalo listo para entradas reales. -8. **`.template/`** — bórrala entera (`rm -rf .template`). Es el historial de la plantilla, no +7. **`docs/`** — borra los archivos que este proyecto no necesite, según la tabla "Qué + documentación necesita cada proyecto". Un documento que no aplica se borra; no se deja vacío. + `docs/features/` se queda como está: empieza sin fichas, solo con su `README.md`. +8. **`mejoras/backlog.md`** — borra el ejemplo comentado y déjalo listo para entradas reales. +9. **`.template/`** — bórrala entera (`rm -rf .template`). Es el historial de la plantilla, no del proyecto. -9. **Verificación final** — busca referencias sobrantes: - `grep -ril "plantilla\|template" . --exclude-dir=.git --exclude-dir=node_modules`. - Revisa cada resultado y corrígelo si habla de la plantilla en lugar del proyecto. +10. **Verificación final** — busca referencias sobrantes: + `grep -ril "plantilla\|template" . --exclude-dir=.git --exclude-dir=node_modules`. + Revisa cada resultado y corrígelo si habla de la plantilla en lugar del proyecto. **Regla general:** después de la inicialización, ningún archivo del repo debe describirse a sí mismo como plantilla ni explicar cómo usar la plantilla. Toda la documentación habla del @@ -183,7 +231,12 @@ es el comportamiento esperado, no un fallo. Lee todo lo que haya en `docs/` antes de empezar a trabajar. Si algún archivo está vacío (solo tiene comentarios) o incompleto, pregunta al usuario para rellenarlo antes de actuar. -Si un archivo de `docs/` no existe todavía, pregunta antes de asumir. +Si un archivo de `docs/` no existe, puede ser deliberado: la tabla "Qué documentación necesita cada +proyecto" decide cuáles aplican, y los que no aplican se borran en lugar de dejarse vacíos. +Compruébalo ahí antes de darlo por olvidado, y si sigue sin estar claro, pregunta. + +`docs/features/` es aparte: no describe el proyecto, sino cada unidad de trabajo acordada. Léela +al empezar una sesión para saber qué hay en marcha (ver "Ciclo de trabajo de una feature"). --- @@ -218,6 +271,7 @@ Si un archivo de `docs/` no existe todavía, pregunta antes de asumir. └── types/ → tipos TypeScript compartidos docs/ → documentación del proyecto (ver sección anterior) + docs/features/ → fichas de las features acordadas, con su tabla de cobertura changelog/ → registro de cambios (ver protocolo más abajo) mejoras/ → ideas futuras no implementadas --> @@ -256,10 +310,92 @@ Si un archivo de `docs/` no existe todavía, pregunta antes de asumir. - No instalar servidores MCP por tu cuenta: pregunta antes, según el "Protocolo de MCPs". - No ejecutar un `claude mcp add` copiado de una fuente que no sea el proveedor oficial, ni sin haberle enseñado antes el comando al usuario. +- No dar por hecho lo que no has ejecutado. Si no has visto pasar el build o los tests, no digas + que pasan: di que no los has ejecutado. +- No desactivar, saltar ni vaciar de aserciones un test para que deje de fallar. - --- +## Límites de ejecución + +Estas cuatro reglas no dependen del proyecto ni del stack, y no admiten excepción por prisa. + +**1. Todo se prueba en local.** Los tests se ejecutan siempre contra `localhost`. Nunca contra +staging, nunca contra producción, nunca contra la máquina de nadie. Si la app no está levantada en +local, el veredicto es "no verificado" — no se busca un entorno remoto como alternativa. + +**2. Desplegar no es tuyo.** No publiques, no hagas deploy, no reinicies servicios, no toques +configuración de servidores ni ejecutes comandos en máquinas que no sean esta. Puedes preparar el +despliegue, explicarlo y dejarlo listo; el botón lo pulsa el usuario. Si alguna vez se te autoriza +explícitamente a lanzarlo, enseña antes qué vas a ejecutar y espera confirmación de esa vez +concreta: una autorización no se hereda a la siguiente. + +**3. Los secretos no se imprimen ni se pasan por la línea de comandos.** Ni completos, ni +recortados, ni "para confirmar que es el correcto". Viajan por variable de entorno o por cabecera. +Un token en un argumento acaba en el historial del shell y en los logs del proceso, y de ahí no se +borra. Cuando necesites referirte a uno, usa su nombre de variable. + +**4. Nada destructivo sin confirmación.** Borrar archivos o ramas, reescribir historial, tirar +migraciones, vaciar tablas: se pregunta antes, con el alcance exacto de lo que va a desaparecer. +Y antes de sobrescribir algo, míralo. + +--- + +## Ciclo de trabajo de una feature + +Una feature es lo que se acuerda, se construye y se da por terminado de una vez. El ciclo es +siempre el mismo: + +**1. Acordar.** Crea la ficha con `/feature`, siguiendo el formato de `docs/features/README.md`. +La ficha declara qué se construye, qué requisitos del PRD cierra, qué queda fuera y —lo importante— +cómo se va a validar cada requisito. Estado: **Acordada**. Enséñasela al usuario y espera su visto +bueno antes de escribir código. + +**2. Construir.** Estado: **En construcción**. Mantenlo actualizado en el momento, no al final: es +lo que permite retomar el trabajo en otra sesión sin reconstruir el contexto a mano. + +**3. Validar.** Con el código ya escrito, escribe los tests declarados en la tabla de cobertura +(ver "Cuándo se escriben los tests" en `docs/testing.md`) y ejecútalos. Los requisitos marcados +como no verificables por interfaz se comprueban por el medio que declare su ficha, y el resultado +se anota igual. + +**4. Cerrar.** Con todo validado: estado **Verificada**, entrada de changelog, documentos de +`docs/` afectados actualizados y PR con la evidencia pegada. Antes de abrir el PR, pasa la +verificación de cobertura: + +```bash +node scripts/verificar-cobertura.mjs +``` + +Para un arreglo puntual, un cambio de copy o un ajuste de estilos no hace falta ficha: basta la +entrada de changelog al terminar. La ficha existe para conservar el acuerdo previo, y en un cambio +pequeño no hay acuerdo previo que conservar. + +**La regla que sostiene todo esto:** ningún requisito de la tabla de cobertura se queda sin su +tercera columna. O lleva la ruta del test que lo valida, o lleva +`no verificable por interfaz: ` y cómo se comprueba entonces. Si no sabes cuál +poner, pregunta — no lo dejes en blanco. Lo que se queda sin validar casi nunca se decide: se +escurre, y nadie lo echa de menos hasta que falla. + +### La verificación de cobertura + +`scripts/verificar-cobertura.mjs` comprueba las tablas contra `docs/prd.md`: que ninguna fila se +quede sin validación declarada, que las excepciones expliquen algo, que los identificadores +existan y que **los tests prometidos existan de verdad** cuando la ficha dice estar Verificada. +Mientras la ficha está *Acordada* o *En construcción* no exige que los archivos existan: los tests +se escriben después de implementar, y hacerlo fallar antes solo enseñaría a ignorar los rojos. + +Corre también en CI con cada pull request, y eso no es redundancia: quien rellena la tabla es quien +tendría que cumplirla, así que la comprobación vive donde no se pueda saltar. Si falla en CI, se +arregla la causa — no se toca el workflow. + +Lo que verifica es estructural, no semántico: detecta el test que se prometió y no se escribió, no +el test que no comprueba nada. Un archivo vacío pasaría la verificación. La diferencia es que un +archivo vacío **sí se ve en el diff del PR**, y un archivo inexistente no. + +--- + ## Protocolo de cambios (obligatorio) Cada vez que hagas un cambio importante en el proyecto, debes: @@ -276,6 +412,7 @@ Usa `/changelog` para crear la entrada siguiendo el formato del proyecto. **Fecha:** YYYY-MM-DD HH:MM **Tipo:** Feature / Fix / Refactor / Migración / Documentación / Configuración +**Requisitos:** [IDs del PRD que cierra: M-01, S-02. "Ninguno" si es un cambio interno] ## Qué se hizo [Descripción de lo que se implementó o modificó] @@ -301,8 +438,11 @@ Ejemplos: - Nueva tabla en Supabase → actualizar `docs/data-model.md` - Nuevo componente o patrón visual → actualizar `docs/design-system.md` - Cambio en la arquitectura de carpetas → actualizar `docs/architecture.md` -- Nueva funcionalidad en scope → actualizar `docs/prd.md` y `docs/roadmap.md` +- Nueva funcionalidad en scope → actualizar `docs/prd.md` y `docs/roadmap.md`, con su ID y su + criterio de aceptación - Nuevo servidor MCP configurado → actualizar `docs/architecture.md` (sección "MCPs del proyecto") +- Feature terminada → poner su ficha de `docs/features/` en estado **Verificada** +- Cambio de alcance a mitad de una feature → actualizar su tabla de cobertura, no solo el código ### 3. Actualizar README.md si aplica @@ -331,11 +471,26 @@ Si por algún motivo abres el PR manualmente desde GitHub, tendrás que rellenar Cuando el agente crea un PR, debe rellenar la plantilla de `.github/pull_request_template.md` completa antes de enviarlo: 1. Rellena las secciones `¿Qué se hizo?` y `Motivación` con el contexto real del cambio (no dejarlo en blanco ni con el placeholder). -2. Marca con `[x]` la casilla correcta en `Tipo de cambio`. Usa las mismas categorías que el changelog: Feature, Fix, Refactor, Migración, Documentación o Configuración. -3. Repasa el checklist y marca con `[x]` **solo lo que hayas verificado de verdad**. Si no has hecho algo, déjalo sin marcar. -4. Si un punto del checklist no aplica (por ejemplo, no hay nada que probar en local para un cambio puramente de markdown), indícalo explícitamente en la descripción del PR en lugar de marcarlo a ciegas o dejarlo en silencio. - -El checklist no es burocracia: es el último filtro para que documentación, changelog, pruebas y revisión de seguridad no se queden a medias cuando hay prisa por mergear. +2. Indica en `Requisitos que cierra` los IDs del PRD que este cambio deja terminados, o "ninguno" si es un cambio interno. +3. Marca con `[x]` la casilla correcta en `Tipo de cambio`. Usa las mismas categorías que el changelog: Feature, Fix, Refactor, Migración, Documentación o Configuración. +4. Rellena la sección `Evidencia` **pegando la salida real de los comandos que has ejecutado**, recortada a lo relevante. Y completa la tabla de verificación con un renglón por requisito, copiando lo que ya declaraste en la ficha de `docs/features/`. +5. Repasa el checklist y marca con `[x]` **solo lo que hayas verificado de verdad**. Si no has hecho algo, déjalo sin marcar. +6. Si un punto del checklist no aplica (por ejemplo, no hay nada que probar en local para un cambio puramente de markdown), indícalo explícitamente en la descripción del PR en lugar de marcarlo a ciegas o dejarlo en silencio. + +### Por qué la evidencia y no la casilla + +Un checklist lo marca quien hizo el trabajo, y con un agente de por medio eso significa que quien +afirma haber verificado y quien tenía que verificar son el mismo. La casilla marcada no distingue +entre "lo ejecuté y pasó" y "estoy razonablemente seguro de que pasaría". La salida de un comando +sí: o está pegada o no está. + +Por eso la regla es literal — **pega la salida, no la parafrasees**. "Los tests pasan" no es +evidencia; las últimas líneas de `pnpm test` sí. Y si algo no se ha ejecutado, escríbelo: un +"no he ejecutado los e2e porque necesitan la base de datos sembrada" es información útil que +permite decidir. Un silencio, no. + +El checklist tampoco es burocracia: es el último filtro para que documentación, changelog, pruebas +y revisión de seguridad no se queden a medias cuando hay prisa por mergear. --- diff --git a/README.md b/README.md index cb6d726..21fa856 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,14 @@ Es agnóstica al stack. El protocolo funciona igual con Next.js, Astro, FastAPI ## ¿Qué hay dentro? -- **`CLAUDE.md`** — Contrato de entrada para el agente. Define qué leer, cómo registrar cambios, cómo configurar los MCPs del stack, qué no hacer y cuándo ejecutar revisiones de seguridad. -- **`docs/`** — Ocho archivos vivos que capturan las decisiones que típicamente se pierden entre conversaciones: producto, arquitectura, modelo de datos, design system, business, roadmap, flujos de usuario y testing. -- **`changelog/`** — Registro estructurado de cada cambio importante: qué, cuándo y por qué. **Llega vacío**: solo con el archivo que explica el formato. +- **`CLAUDE.md`** — Contrato de entrada para el agente. Define qué leer, cómo se trabaja una feature, cómo registrar cambios, cómo configurar los MCPs del stack, qué no hacer y dónde están los límites de lo que puede ejecutar por su cuenta. +- **`docs/`** — Ocho archivos vivos que capturan las decisiones que típicamente se pierden entre conversaciones: producto, arquitectura, modelo de datos, design system, business, roadmap, flujos de usuario y testing. **No todos aplican a todos los proyectos**: hay una tabla que decide cuáles según el tamaño. +- **`docs/features/`** — Una ficha por unidad de trabajo acordada: qué se construye, qué requisitos cierra y **cómo se va a comprobar cada uno**. Es el contrato que se firma antes de escribir código. **Llega vacía**. +- **`changelog/`** — Registro estructurado de cada cambio importante: qué, cuándo, por qué y qué requisitos cierra. **Llega vacío**: solo con el archivo que explica el formato. - **`mejoras/`** — Backlog de ideas que no entran en el sprint actual pero no se quieren perder. -- **`.claude/`** — Configuración de Claude Code con permisos sensatos y slash commands custom para no tener que recordar el protocolo de memoria. -- **`.github/`** — Plantillas de pull request e issues alineadas con el protocolo. +- **`.claude/`** — Configuración de Claude Code con permisos sensatos y slash commands custom (`/feature`, `/changelog`, `/mejora`, `/doctor`, `/mcp-setup`, `/init-proyecto`) para no tener que recordar el protocolo de memoria. +- **`scripts/`** — Una verificación ejecutable: comprueba que ninguna ficha deje un requisito sin validar y que los tests prometidos existan de verdad. Node sin dependencias. +- **`.github/`** — Plantillas de pull request e issues alineadas con el protocolo, y el workflow que ejecuta esa verificación en cada PR. El PR pide **evidencia pegada**, no casillas marcadas. - **`.template/`** — Historial de la plantilla en sí. Se borra al inicializar tu proyecto, así no arrastras cambios que no son tuyos. - Lo aburrido pero necesario: `.gitignore`, `.env.example`, `LICENSE`. @@ -36,12 +38,16 @@ Es agnóstica al stack. El protocolo funciona igual con Next.js, Astro, FastAPI ## ¿Cómo funciona el protocolo? -1. **Cualquier sesión empieza leyendo `docs/`.** Si están vacíos o incompletos, el agente pregunta antes de actuar. -2. **Cada cambio importante deja registro en `changelog/`** con qué se hizo, qué se modificó y por qué. -3. **Si el cambio afecta a algo documentado, se actualiza el doc en la misma sesión.** No hay documentación desincronizada. -4. **Con el stack ya decidido, el agente pregunta qué MCPs quieres** y con qué alcance: los globales que ya tengas, o servidores configurados a nivel de proyecto en `.mcp.json`. No instala nada por su cuenta ni antes de que haya stack. -5. **Antes de mergear a producción**, se ejecuta `/security-review` para detectar vulnerabilidades, credenciales filtradas y problemas comunes. -6. **Las ideas que no entran ahora se anotan en `mejoras/`** sin interrumpir el flujo actual. +1. **Cualquier sesión empieza leyendo `docs/`.** Si están vacíos o incompletos, el agente pregunta antes de actuar. Y no pide los ocho documentos: pide los que correspondan al tamaño del proyecto. +2. **Cada funcionalidad del PRD lleva ID y criterio de aceptación comprobable.** "Dado…, cuando…, entonces…", con un resultado que se pueda mirar. Ese criterio es el que después se convierte en test. +3. **Antes de construir una feature se escribe su ficha** en `docs/features/`, con una tabla que dice cómo se validará cada requisito. Ningún requisito se queda sin tercera columna: o lleva la ruta de su test, o lleva la razón concreta por la que no se puede testear así. **Y esto no es honor system**: un script lo verifica en cada PR, y falla si un test prometido no existe. +4. **Los tests se escriben después de implementar**, leyendo el código real. Escritos antes apuntan a selectores imaginados, y acaban vaciándose de aserciones hasta que pasan. +5. **Cada cambio importante deja registro en `changelog/`** con qué se hizo, qué se modificó, por qué y qué requisitos cierra. +6. **Si el cambio afecta a algo documentado, se actualiza el doc en la misma sesión.** No hay documentación desincronizada. +7. **Con el stack ya decidido, el agente pregunta qué MCPs quieres** y con qué alcance: los globales que ya tengas, o servidores configurados a nivel de proyecto en `.mcp.json`. No instala nada por su cuenta ni antes de que haya stack. +8. **El PR se cierra con evidencia, no con casillas.** La salida real de los comandos va pegada en el PR; lo que no se ha ejecutado se dice. +9. **Antes de mergear a producción**, se ejecuta `/security-review` para detectar vulnerabilidades, credenciales filtradas y problemas comunes. +10. **Las ideas que no entran ahora se anotan en `mejoras/`** sin interrumpir el flujo actual. --- @@ -50,8 +56,10 @@ Es agnóstica al stack. El protocolo funciona igual con Next.js, Astro, FastAPI 1. Usa este repo como plantilla en GitHub (botón **"Use this template"**) o clónalo directamente. 2. Abre el proyecto en Claude Code, Cursor o el agente que prefieras. 3. Cuando el agente lea `CLAUDE.md` por primera vez, te preguntará qué quieres construir y para quién. Responde y deja que vaya completando los docs contigo, uno a uno. -4. Con los docs rellenos, el agente **inicializa el proyecto**: reescribe este README para tu producto, rellena los datos de `CLAUDE.md`, ajusta la licencia y `.env.example`, borra `.template/` y deja el changelog con su primera entrada real. Lo hace solo; si quieres forzarlo, usa `/init-proyecto`. -5. A partir de ahí, arranca el desarrollo. Cada sesión nueva entra ya con todo el contexto cargado. +4. Con los docs rellenos, el agente **inicializa el proyecto**: reescribe este README para tu producto, rellena los datos de `CLAUDE.md`, ajusta la licencia y `.env.example`, borra los documentos que tu proyecto no necesite y `.template/`, y deja el changelog con su primera entrada real. Lo hace solo; si quieres forzarlo, usa `/init-proyecto`. +5. A partir de ahí, arranca el desarrollo. Cada feature empieza por su ficha (`/feature`) y termina con su evidencia. Cada sesión nueva entra ya con todo el contexto cargado. + +¿Algo no cuadra en cualquier momento? `/doctor` revisa documentación, fichas a medias, entorno, variables, MCPs y tests, y te dice qué falta y cómo arreglarlo. --- @@ -72,6 +80,9 @@ No tienes que hacerlo a mano: el agente lo hace en la inicialización, siguiendo | `CLAUDE.md` | Se rellenan nombre, stack, estructura y convenciones | | `LICENSE` | Se sustituyen `[YEAR]` y `[AUTHOR]` | | `.env.example` | Se queda solo con las variables de tu stack | +| `docs/` | Se borran los documentos que tu proyecto no necesita, según su tamaño | +| `scripts/` | Se queda tal cual: la verificación no depende del stack | +| `docs/features/` | Se queda vacía, lista para la primera ficha | | `changelog/` | Recibe la primera entrada real del proyecto | | `mejoras/backlog.md` | Se limpia el ejemplo | | `.template/` | Se borra | diff --git a/changelog/README.md b/changelog/README.md index d740f80..153cd95 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -21,6 +21,7 @@ Usa `/changelog`. El agente crea el archivo con la fecha y hora reales y rellena **Fecha:** YYYY-MM-DD HH:MM **Tipo:** Feature / Fix / Refactor / Migración / Documentación / Configuración +**Requisitos:** [IDs del PRD que cierra: M-01, S-02. "Ninguno" si es un cambio interno] ## Qué se hizo [Descripción de lo que se implementó o modificó] diff --git a/docs/architecture.md b/docs/architecture.md index 17a915f..4104e46 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -114,7 +114,12 @@ graph TD ## Estrategia de despliegue + Ramas, entornos (local / staging / producción), CI/CD si existe, variables de entorno por entorno. + + Deja escrito también **quién despliega y con qué comando**. El agente no publica por su cuenta + (ver "Límites de ejecución" en CLAUDE.md): puede dejarlo todo preparado y explicado, pero el + botón lo pulsa una persona. Tener el procedimiento documentado aquí es lo que hace que esa + separación no cueste tiempo. --> --- diff --git a/docs/features/README.md b/docs/features/README.md new file mode 100644 index 0000000..a784ff8 --- /dev/null +++ b/docs/features/README.md @@ -0,0 +1,120 @@ +# Fichas de feature + +Entre la documentación de `docs/` (que describe el proyecto entero) y el código hay un hueco: la +unidad de trabajo. Una feature es lo que se acuerda, se construye y se da por terminado de una vez. +Su ficha es el contrato: **qué se construye, cómo se sabrá que funciona y qué queda fuera** — +escrito antes de empezar, no reconstruido después a partir del diff. + +La ficha no sustituye a `docs/`. El PRD dice *qué* quiere el producto; la ficha dice *cómo* se +resuelve un trozo concreto y con qué se demuestra. Cuando la feature termina, lo que aprendimos +sube a `docs/` y la ficha se queda como registro de la decisión. + +Esta carpeta **empieza vacía a propósito**: solo con este archivo, que explica el formato. + +--- + +## Cuándo crear una ficha + +Cuando el trabajo cumpla alguna de estas condiciones: + +- Cierra uno o más requisitos del PRD (`M-01`, `S-02`…). +- Toca más de tres o cuatro archivos, o cruza capas (UI + datos, o app + integración externa). +- Va a ocupar más de una sesión de trabajo. + +Para un arreglo puntual, un cambio de copy o un ajuste de estilos, no hace falta ficha: basta la +entrada de changelog cuando esté hecho. La ficha existe para que no se pierda el *acuerdo previo*, +y en un cambio pequeño no hay acuerdo previo que perder. + +Crea la ficha con `/feature`. Un archivo por feature: `docs/features/nombre-en-kebab-case.md`. + +--- + +## Formato + +```markdown +# [Nombre de la feature] + +**Estado:** Acordada · En construcción · Verificada +**Requisitos que cierra:** M-01, M-03 +**Fecha de acuerdo:** YYYY-MM-DD + +## Qué se construye + +Dos o tres párrafos. Qué verá o podrá hacer el usuario cuando esto exista. +Sin detalle de implementación: eso va en la tabla de cobertura y en el código. + +## Decisiones tomadas + +Las que no se deducen del código y costaría volver a discutir. Una línea cada una, +con el motivo. Si alguna afecta a la arquitectura o al modelo de datos, además hay +que llevarla a `docs/` en la misma sesión. + +## Cobertura + +| Requisito | Se implementa en | Se valida con | +|-----------|------------------|---------------| +| M-01 | `src/app/(auth)/registro/` | `tests/registro.spec.ts` | +| M-03 | migración `002_indices` | no verificable por interfaz: índice de BD, se comprueba con `EXPLAIN` antes del PR | + +## Fuera de esta feature + +Lo que se ha hablado y se ha decidido NO hacer aquí, para que no vuelva a discutirse +a mitad de camino. Si algo de aquí merece existir algún día, va a `mejoras/`. +``` + +--- + +## La tabla de cobertura es la parte que importa + +Todo lo demás de la ficha es contexto. La tabla es el contrato, y tiene una sola regla: + +> **Ningún requisito se queda sin una tercera columna rellena.** + +Hay exactamente dos formas válidas de rellenarla: + +- **La ruta del test que lo valida.** No hace falta que el test exista todavía cuando se escribe la + ficha — se escribe después de implementar (ver `docs/testing.md`). Lo que se declara aquí es el + compromiso de que existirá. +- **`no verificable por interfaz: ` + cómo se comprueba entonces.** La excepción es + legítima: un índice de base de datos, una variable de entorno o un cambio de estilos no se testean + con un test de interfaz. Pero la razón se escribe, y se escribe concreta. "No aplica" no es una + razón. "No me dio tiempo" tampoco: eso es un test pendiente, no una excepción. + +El motivo de tanta insistencia: lo que se queda sin validar rara vez se decide, se *escurre*. Nadie +dice "este requisito no lo vamos a comprobar"; simplemente no aparece en ningún sitio y nadie lo +echa de menos hasta que falla en producción. Obligar a escribir la excepción convierte una omisión +invisible en una frase que alguien puede leer y discutir. + +**Y esta regla se comprueba sola.** `scripts/verificar-cobertura.mjs` valida las tablas contra +`docs/prd.md` y se ejecuta en CI con cada pull request: + +```bash +node scripts/verificar-cobertura.mjs +``` + +Detecta filas sin tercera columna, excepciones vacías de contenido ("no aplica" no cuela), +identificadores que no existen en el PRD, y —lo más útil— **tests declarados que nunca se +escribieron**, cuando la ficha ya dice estar Verificada. Mientras está *Acordada* o *En +construcción* no exige que los archivos existan: los tests van después de implementar. + +Una fila puede declarar varios tests separándolos por comas. + +--- + +## Estado de la ficha + +Tres valores, y se actualizan en el momento, no al final: + +| Estado | Significa | +|--------|-----------| +| **Acordada** | La ficha está escrita y validada con el usuario. No hay código todavía | +| **En construcción** | Se está implementando. La tabla de cobertura ya no cambia sin avisar | +| **Verificada** | Todos los requisitos de la tabla tienen su validación hecha y pasando | + +Sirve para retomar. Una sesión nueva que abra esta carpeta sabe en dos segundos qué hay a medias y +por dónde seguir, sin releer el repo entero ni fiarse de la memoria de la conversación anterior. + +--- + +Este archivo es la única excepción de la carpeta: no es una ficha, es la explicación del formato. +Consérvalo mientras la carpeta tenga sentido para el equipo. diff --git a/docs/prd.md b/docs/prd.md index 2aee987..304ebf3 100644 --- a/docs/prd.md +++ b/docs/prd.md @@ -37,20 +37,50 @@ - MUST: imprescindible para el MVP - SHOULD: importante pero no bloqueante - COULD: deseable si hay tiempo - - WON'T: explícitamente fuera de alcance en esta versión --> + - WON'T: explícitamente fuera de alcance en esta versión + + Cada funcionalidad lleva dos cosas obligatorias: + + 1. UN IDENTIFICADOR ESTABLE — `M-01`, `S-01`, `C-01`. Es el nombre por el que la + funcionalidad se cita en el resto del repo: en la ficha de `docs/features/`, en el + changelog, en el PR y en el nombre del test. Nunca se reutiliza ni se renumera: si una + funcionalidad se cae, su ID se queda vacante. + + 2. UN CRITERIO DE ACEPTACIÓN COMPROBABLE — redactado como + "Dado [contexto], cuando [acción], entonces [resultado observable]". + + El criterio no es literatura: es lo que después se convierte en aserción del test. Por eso + el "entonces" tiene que ser algo que se pueda mirar y decir sí o no (un mensaje visible, una + redirección, un registro creado), no un adjetivo. "Entonces la experiencia es fluida" no vale. + Añade el caso negativo siempre que el fallo sea previsible: es donde se esconden los bugs. + + Sin criterio de aceptación, "hecho" acaba siendo una opinión, y con agentes de por medio + acaba siendo la del agente. + + Ejemplo: + + ### MUST + - **[M-01] Registro con email** — Dado un visitante sin cuenta, cuando envía un email válido + y una contraseña de 8+ caracteres, entonces recibe email de confirmación y accede al + dashboard vacío. + *Negativo:* dado un email ya registrado, cuando lo envía, entonces ve un error inline y no + se crea ninguna cuenta. --> ### MUST -- +- ### SHOULD -- +- ### COULD -- +- ### WON'T (esta versión) - + + --- ## Flujos de usuario principales diff --git a/docs/testing.md b/docs/testing.md index d8f744f..89bebd7 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -14,6 +14,36 @@ --- +## Cuándo se escriben los tests + +**Después de implementar, en una pasada propia.** No durante la planificación, y no a la vez que +el código. + +El compromiso de que un requisito se va a validar se adquiere antes: es la tercera columna de la +tabla de cobertura de `docs/features/`. Pero el test en sí se escribe cuando el código ya existe, +leyéndolo. Es una diferencia de calendario pequeña con una consecuencia grande: un test escrito +antes que el código apunta a selectores, rutas y respuestas *imaginados*. Cuando luego no +coinciden con la realidad, casi nadie reescribe el test — se le van quitando aserciones hasta que +pasa, y acaba siendo un test que no comprueba nada pero que da luz verde. Escrito después, apunta +a lo que hay. + +Reglas que se derivan de eso: + +- **Antes de escribir una aserción, verifica que el selector existe en el código.** No lo + supongas por el nombre del componente. +- **Si un elemento no tiene selector estable, añádele uno.** Meter un `data-testid` en el código + es un cambio mínimo aceptable y preferible a colgar el test de una clase de estilos o de un + texto que cambiará con el próximo ajuste de copy. +- **Cada "entonces" del criterio de aceptación necesita al menos una aserción.** Si el criterio + define caso negativo, va su propio test. +- **Un test que falla no se arregla quitándole aserciones.** Si falla, o el código está mal o el + criterio estaba mal escrito. Ambas cosas se corrigen donde toca; degradar el test para forzar el + verde convierte la suite en decoración. +- **Los datos que crea un test los borra ese test.** Prefija lo que insertes para poder + identificarlo y limpia al terminar, aunque el test falle a mitad. + +--- + ## Stack de testing /g, ''); +const sinBackticks = (texto) => texto.replace(/`/g, '').trim(); + +// ─── docs/prd.md ────────────────────────────────────────────────────────────── + +function requisitosDelPrd() { + if (!existsSync(PRD)) { + aviso('docs/prd.md', 'No existe: no se pueden contrastar los identificadores de las fichas'); + return new Set(); + } + + const ids = new Set(); + const texto = sinComentarios(readFileSync(PRD, 'utf8')); + + // Solo cuentan las líneas de declaración: `- **[M-01] Título** — Dado…`. + // Una mención del ID en otra parte del documento no declara nada. + for (const linea of texto.split('\n')) { + const m = linea.match(/^\s*-\s*\*\*\[([MSC]-\d+)\]/); + if (!m) continue; + if (ids.has(m[1])) fallo('docs/prd.md', `El identificador ${m[1]} está declarado dos veces`); + ids.add(m[1]); + } + + return ids; +} + +// ─── docs/features/*.md ─────────────────────────────────────────────────────── + +function seccionCobertura(lineas) { + const inicio = lineas.findIndex((l) => /^##\s+Cobertura\s*$/.test(l.trim())); + if (inicio === -1) return null; + const resto = lineas.slice(inicio + 1); + const fin = resto.findIndex((l) => /^##\s/.test(l)); + return fin === -1 ? resto : resto.slice(0, fin); +} + +function filasDeTabla(lineas) { + return lineas + .map((l) => l.trim()) + .filter((l) => l.startsWith('|')) + .map((l) => l.replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim())) + .filter((celdas) => !celdas.every((c) => /^:?-{3,}:?$/.test(c))) + .filter((celdas) => !/^requisito$/i.test(celdas[0] ?? '')); +} + +function leerFicha(nombre) { + const rel = `docs/features/${nombre}`; + const texto = sinComentarios(readFileSync(join(FICHAS, nombre), 'utf8')); + const lineas = texto.split('\n'); + + const mEstado = texto.match(/^\*\*Estado:\*\*\s*(.+)$/m); + const estado = mEstado ? mEstado[1].trim() : null; + + const mReq = texto.match(/^\*\*Requisitos que cierra:\*\*\s*(.+)$/m); + const declarados = mReq + ? mReq[1].split(',').map((s) => sinBackticks(s)).filter((s) => /^[MSC]-\d+$/.test(s)) + : []; + + const seccion = seccionCobertura(lineas); + return { rel, estado, declarados, filas: seccion ? filasDeTabla(seccion) : null }; +} + +function validarFicha(ficha, idsPrd) { + const { rel, estado, declarados, filas } = ficha; + + if (!estado) { + fallo(rel, 'Falta la línea `**Estado:**` en la cabecera'); + } else if (!ESTADOS.includes(estado)) { + fallo(rel, `Estado "${estado}" no válido. Debe ser: ${ESTADOS.join(' · ')}`); + } + + if (filas === null) { + fallo(rel, 'No tiene sección `## Cobertura`'); + return; + } + if (filas.length === 0) { + fallo(rel, 'La tabla de cobertura está vacía'); + return; + } + + const verificada = estado === 'Verificada'; + const enTabla = new Set(); + + for (const celdas of filas) { + const id = sinBackticks(celdas[0] ?? ''); + const validacion = (celdas[2] ?? '').trim(); + + if (!id) { + fallo(rel, 'Hay una fila sin identificador de requisito'); + continue; + } + enTabla.add(id); + + if (idsPrd.size > 0 && !idsPrd.has(id)) { + fallo(rel, `${id}: no está declarado en docs/prd.md`); + } + + if (celdas.length < 3) { + fallo(rel, `${id}: la fila no tiene las tres columnas`); + continue; + } + + // ── La regla de la tercera columna ── + if (!validacion) { + fallo(rel, `${id}: la columna "Se valida con" está vacía. Escribe la ruta del test o ` + + `"${EXCEPCION} "`); + continue; + } + + if (validacion.toLowerCase().startsWith(EXCEPCION)) { + const razon = validacion.slice(EXCEPCION.length).trim(); + if (razon.length < RAZON_MINIMA) { + fallo(rel, `${id}: la excepción no explica nada ("${razon || 'sin texto'}"). ` + + `Escribe la razón concreta y cómo se comprueba entonces`); + } + continue; + } + + const rutas = validacion.split(',').map(sinBackticks).filter(Boolean); + const pareceRuta = rutas.every((r) => r.includes('/') || r.includes('.')); + + if (!pareceRuta) { + fallo(rel, `${id}: "${validacion}" no es ni una ruta de test ni una excepción justificada`); + continue; + } + + // La existencia solo se exige al cerrar: los tests se escriben después de implementar, + // así que una ficha en construcción con el archivo aún sin crear es lo normal. + if (!verificada) continue; + + for (const ruta of rutas) { + if (!existsSync(join(RAIZ, ruta))) { + fallo(rel, `${id}: la ficha está Verificada pero ${ruta} no existe`); + } + } + } + + for (const id of declarados) { + if (!enTabla.has(id)) { + fallo(rel, `${id} aparece en "Requisitos que cierra" pero no tiene fila en la tabla`); + } + } + + return enTabla; +} + +// ─── Ejecución ──────────────────────────────────────────────────────────────── + +function main() { + if (!existsSync(FICHAS)) { + console.log('No existe docs/features/: nada que verificar.'); + return 0; + } + + const nombres = readdirSync(FICHAS) + .filter((n) => n.endsWith('.md') && n !== 'README.md') + .sort(); + + if (nombres.length === 0) { + console.log('Sin fichas de feature todavía: nada que verificar.'); + return 0; + } + + const idsPrd = requisitosDelPrd(); + const cubiertos = new Set(); + + for (const nombre of nombres) { + const ficha = leerFicha(nombre); + const enTabla = validarFicha(ficha, idsPrd) ?? new Set(); + for (const id of enTabla) cubiertos.add(id); + } + + for (const id of idsPrd) { + if (!cubiertos.has(id)) { + aviso('docs/prd.md', `${id} no aparece en ninguna ficha todavía`); + } + } + + // ── Informe ── + console.log(`\nVerificación de cobertura — ${nombres.length} ficha(s)\n`); + + const porArchivo = new Map(); + for (const f of fallos) { + if (!porArchivo.has(f.donde)) porArchivo.set(f.donde, []); + porArchivo.get(f.donde).push(f.mensaje); + } + + for (const [donde, mensajes] of porArchivo) { + console.log(` ${donde}`); + for (const m of mensajes) console.log(` FALLO ${m}`); + console.log(''); + } + + for (const a of avisos) { + console.log(` ATENCIÓN ${a.mensaje} (${a.donde})`); + } + if (avisos.length) console.log(''); + + if (fallos.length === 0) { + console.log(avisos.length + ? `Sin fallos. ${avisos.length} aviso(s) que no bloquean.\n` + : 'Todo en orden.\n'); + return 0; + } + + console.log(`${fallos.length} fallo(s)${avisos.length ? `, ${avisos.length} aviso(s)` : ''}.\n`); + return 1; +} + +process.exit(main()); From 8ca242c48629cfacd6df81a3c25e2cb150ee7421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pol=20Marz=C3=A0?= Date: Tue, 18 Aug 2026 14:26:41 +0200 Subject: [PATCH 2/2] fix: acota el permiso de node, el token de CI y las rutas de la tabla de cobertura MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revisión de seguridad del PR #6. Cuatro correcciones, dos de ellas sobre cosas que introdujo el propio PR. - `Bash(node scripts/*)` autoaprobaba ejecutar cualquier archivo bajo scripts/. En esta plantilla solo hay uno, pero en un proyecto real esa carpeta acaba con scripts de despliegue o de migración, y el permiso contradecía la sección "Límites de ejecución" que este mismo PR añade. Se acota al script concreto - El workflow no declaraba `permissions`, así que en las ejecuciones sobre main heredaba el token por defecto del repositorio. El script solo lee archivos: se fija a `contents: read` - Las rutas de la tercera columna son entrada no confiable (una ficha puede llegar en un pull request desde un fork). Ahora se comprueba que apunten dentro del repositorio, en cualquier estado de la ficha, antes de tocarlas - La sección "Evidencia" del PR pide pegar salida de comandos, que puede arrastrar tokens o cadenas de conexión: se avisa de repasarla antes de enviar El disparador sigue siendo `pull_request` y no `pull_request_target`, de modo que los PR desde forks corren sin secretos y con token de solo lectura. Co-Authored-By: Claude Opus 5 --- .claude/settings.json | 2 +- .github/pull_request_template.md | 6 +++++- .github/workflows/cobertura.yml | 5 +++++ scripts/verificar-cobertura.mjs | 19 ++++++++++++++++--- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 563ddde..4b6ff15 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,7 +5,7 @@ "Bash(pnpm run *)", "Bash(pnpm dlx *)", "Bash(pnpm *)", - "Bash(node scripts/*)", + "Bash(node scripts/verificar-cobertura.mjs)", "Bash(node -v)", "Bash(node --version)", "Bash(claude mcp list)", diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 813bd2e..27495d6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,7 +23,11 @@ + Si algo no se ha ejecutado, dilo y explica por qué en lugar de omitirlo. + + Repasa lo que pegas antes de enviarlo: la salida de un comando puede arrastrar tokens, + cadenas de conexión o rutas locales. Un PR es público o, como mínimo, permanente. + Sustituye cualquier valor sensible por su nombre de variable. --> ``` $ pnpm test diff --git a/.github/workflows/cobertura.yml b/.github/workflows/cobertura.yml index 2521486..81958d0 100644 --- a/.github/workflows/cobertura.yml +++ b/.github/workflows/cobertura.yml @@ -11,6 +11,11 @@ on: push: branches: [main] +# El script solo lee archivos: no necesita escribir en el repositorio ni tocar nada +# de la API. Acotarlo aquí limita el daño si una de las acciones se viera comprometida. +permissions: + contents: read + jobs: verificar: name: Tablas de cobertura diff --git a/scripts/verificar-cobertura.mjs b/scripts/verificar-cobertura.mjs index 6953985..adb9235 100755 --- a/scripts/verificar-cobertura.mjs +++ b/scripts/verificar-cobertura.mjs @@ -14,7 +14,7 @@ */ import { readFileSync, existsSync, readdirSync } from 'node:fs'; -import { join, dirname, resolve } from 'node:path'; +import { join, dirname, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const RAIZ = resolve(dirname(fileURLToPath(import.meta.url)), '..'); @@ -158,12 +158,25 @@ function validarFicha(ficha, idsPrd) { continue; } + // Las fichas pueden llegar en un pull request, así que las rutas son entrada no + // confiable: se comprueba siempre que apunten dentro del repositorio, en cualquier + // estado, y no solo al cerrar la ficha. + const contenidas = []; + for (const ruta of rutas) { + const destino = resolve(RAIZ, ruta); + if (destino !== RAIZ && !destino.startsWith(RAIZ + sep)) { + fallo(rel, `${id}: la ruta ${ruta} apunta fuera del repositorio`); + continue; + } + contenidas.push({ ruta, destino }); + } + // La existencia solo se exige al cerrar: los tests se escriben después de implementar, // así que una ficha en construcción con el archivo aún sin crear es lo normal. if (!verificada) continue; - for (const ruta of rutas) { - if (!existsSync(join(RAIZ, ruta))) { + for (const { ruta, destino } of contenidas) { + if (!existsSync(destino)) { fallo(rel, `${id}: la ficha está Verificada pero ${ruta} no existe`); } }