project-docs v2: el mapa no sirve si nadie verifica que lo estén leyendo

5 min de lectura

De la teoría al campo de batalla

Hace unos días publiqué sobre project-docs, un framework de documentación que le da estructura y memoria a los agentes de IA. El post explicaba la arquitectura, los 6 roles, el task routing. Todo muy prolijo en papel.

Pero un framework no se prueba con diagramas. Se prueba construyendo algo real.

Así que lo pusimos a trabajar: Cube Trainer, un entrenador interactivo de algoritmos de Rubik con visualización 3D, construido desde cero con Astro + React + cubing.js. 4 fases, 24 tareas, 47 commits. Todo orquestado por agentes de Claude Code usando project-docs v1.

El resultado fue un proyecto que funciona. Pero el proceso expuso un problema fundamental: el agente completó 4 fases de desarrollo sin crear una sola session note, sin actualizar CURRENT_STATE.md, y dejando VISION.md como template vacío. El validador pasaba porque solo verificaba que los archivos existieran — no que tuvieran contenido real.

project-docs v2 es el resultado de esa lección.

Lo que funcionó desde el día uno

El trío always-read salvó sesiones

Los tres archivos que se leen siempre — VISION.md, TECH_STACK.md, CONVENTIONS.md — demostraron ser exactamente lo que necesitaba el agente para no desviarse. En 47 commits no hubo un solo caso de “componente de clase en un proyecto de hooks” o “importó una librería que no usamos”. El agente sabía que el stack era Astro + React islands, que TypeScript estaba en strict mode, que cubing.js solo se cargaba en páginas de detalle.

Tres archivos. Menos de 200 líneas combinadas. Eso bastó para mantener coherencia durante semanas.

Los ADRs evitaron re-litigar decisiones

Cube Trainer tiene 3 Architecture Decision Records:

  • ADR-001: Por qué Astro con React islands (no un SPA)
  • ADR-002: Por qué cubing.js (aunque es GPL-3.0 y pesa)
  • ADR-003: Por qué JSON estático en vez de CMS o base de datos

Sin estos documentos, cada vez que el agente tocaba arquitectura tenía que tomar la decisión de nuevo. Con los ADRs, la decisión ya estaba tomada, documentada con alternativas consideradas, y el agente simplemente la respetaba.

El task routing funcionó como filtro de complejidad

  • Fast path para ajustes de CSS, typos, configuración: el implementer solo, sin overhead de planning ni review
  • Standard path para features completas como “agregar las 57 cases de OLL”: planner → implementer → tester → reviewer
  • Full path para decisiones de arquitectura: todos los roles + checkpoint humano

Esto evitó dos problemas comunes: overkill en cambios triviales y rigor insuficiente en cambios de alto impacto.

La validación build-time como gate automático

El script de validación de algoritmos (npm run validate) se convirtió en el test más importante del proyecto. Verificaba que cada notación era parseable por cubing.js y que cada algoritmo efectivamente resolvía el caso.

El agente generaba el dato, el validador lo rechazaba, y el agente corregía sin intervención humana. El loop de feedback funcionaba porque el reviewer tenía como condición de rechazo explícita “algorithm data not validated”.

Donde v1 falló: tres gaps de enforcement

El framework tenía las reglas correctas. Lo que no tenía era forma de hacerlas cumplir.

Gap 1: Sin validación de contenido

El validador verificaba que VISION.md existiera y tuviera frontmatter YAML válido. Pero no verificaba que el contenido fuera real. Un archivo con <!-- TODO: Describe the long-term purpose --> pasaba todas las checks. En Cube Trainer, varios archivos “always-read” seguían teniendo placeholders después de 25 commits.

Gap 2: Sin enforcement en commit-time

Nada bloqueaba el commit si la documentación estaba incompleta. El agente podía — y de hecho lo hizo — commitear features sin actualizar un solo archivo de docs. El docs-maintainer tenía la “responsabilidad teórica” de hacerlo, pero en la práctica el flujo de trabajo no tenía un trigger explícito.

Gap 3: Sin detección de actividad

No había forma de detectar que un proyecto llevaba N commits sin session notes. La documentación podía estar completamente abandonada y el framework no se enteraba.

La lección: un sistema de documentación basado en honor-system no funciona con agentes. Si no se verifica, no existe.

Qué cambió en v2: cambio arquitectónico principal

Los docs son ciudadanos de primera clase

La decisión más impactante en UX. En v1, todo vivía en un subdirectorio project-docs/ (via git submodule o clone). Cada path necesitaba un prefijo:

# v1 — 17 archivos con ~50 ocurrencias de esta variable
{{PROJECT_DOCS_PATH}}/product/VISION.md
{{PROJECT_DOCS_PATH}}/context/TECH_STACK.md
{{PROJECT_DOCS_PATH}}/architecture/SYSTEM_OVERVIEW.md

En v2, los docs viven en el root del repo al mismo nivel que src/. La template variable {{PROJECT_DOCS_PATH}} desapareció de los 17 archivos donde existía:

# v2 — paths directos, cero prefijo
product/VISION.md
context/TECH_STACK.md
architecture/SYSTEM_OVERVIEW.md

Solo queda {{PROJECT_NAME}} en 2 archivos (CLAUDE.md y AGENTS.md), reemplazado por init.mjs.

Instalación: de 3 pasos a un curl

Terminal window
# v1 — git submodule + node + agente interactivo
git submodule add https://github.com/lea2696/project-docs.git project-docs/
node project-docs/scripts/bootstrap.mjs
# v2 — un one-liner que descarga 41 archivos directo al root
curl -sL https://raw.githubusercontent.com/lea2696/project-docs/main/install.sh | bash

El curl descarga y extrae dist/ (41 archivos) directo en el root del repo. Cero rastro del framework: no .git ajeno, no .gitmodules, no subdirectorio extra. Los docs quedan en product/, context/, architecture/, etc. al mismo nivel que src/.

Con soporte opcional de PRD para acelerar el bootstrap:

Terminal window
curl -sL .../install.sh | bash -s -- --prd path/to/prd.md

Copia el PRD a plans/initial-prd.md. El bootstrapper lo usa como fuente para llenar los docs automáticamente en vez de hacer preguntas.

Scripts reubicados

v1v2
scripts/bootstrap.mjsscripts/docs/init.mjs (más simple, soporta --prd)
scripts/validate-docs.mjsscripts/docs/validate-docs.mjs (misma lógica + --strict)

Enforcement de documentación: la feature principal de v2

Tres capas que resuelven los tres gaps de v1.

Capa 1 — Validador estricto (--strict)

El validador ahora tiene un modo estricto que verifica contenido real, no solo estructura:

Detección de placeholders: Los archivos requeridos (VISION.md, CURRENT_STATE.md, TECH_STACK.md, CONVENTIONS.md, KNOWN_ISSUES.md) no pueden tener contenido template. Si el validador encuentra <!-- TODO: o <!-- Example: en estos archivos, es error.

Validación de fechas: last_updated en el frontmatter debe ser una fecha ISO real, no el literal YYYY-MM-DD del template.

Actividad de sesiones: Si hay 6+ commits sin session notes en sessions/, es error. El framework detecta que la documentación se abandonó.

Grace period: Repos con menos de 3 commits omiten todas las checks estrictas. Sin esto, sería imposible hacer el primer commit después de instalar el framework — todos los docs están vacíos.

Capa 2 — Hook pre-commit (Claude Code)

{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"if": "Bash(git commit *)",
"command": "node scripts/docs/validate-docs.mjs --strict"
}]
}]
}
}

El hook intercepta cada git commit y corre el validador estricto antes de permitirlo.

Detalle técnico que cambió el diseño: Claude Code usa exit code 2 para bloquear acciones, no exit code 1. Exit 1 es un error no-bloqueante que el agente puede ignorar. Exit 2 es un bloqueo duro que impide la acción. Este fue un hallazgo durante la implementación — el validador originalmente retornaba exit 1 y el agente lo ignoraba silenciosamente.

Capa 3 — CI como safety net

docs-lint.yml corre --strict en cada push/PR. Si el hook pre-commit se bypasea (o si se commitea desde fuera de Claude Code), CI lo atrapa.

.github/workflows/docs-lint.yml
- run: node scripts/docs/validate-docs.mjs --strict

El resultado: es físicamente imposible mergear código con documentación incompleta o abandonada. El “honor system” se convirtió en enforcement real.

Frontmatter como metadatos machine-readable

Cada archivo de documentación tiene un bloque YAML que los agentes interpretan programáticamente:

---
status: draft | active | completed | abandoned
owner: human | planner | implementer | docs-maintainer
last_updated: 2026-04-06
read_policy: always | on-demand | never-default
---

En v1, la política de lectura existía solo como texto libre en el README. En v2, read_policy es un campo estructurado. El status permite saber si un documento está vigente. El owner dice quién es responsable de mantenerlo actualizado. Y last_updated ahora se valida — no puede ser un placeholder.

Templates con ejemplos concretos

Los TODOs genéricos se reemplazaron con ejemplos comentados:

<!-- EXAMPLE: "Interactive 3D Rubik's cube algorithm trainer
focused on CFOP method. Targets intermediate cubers
transitioning from beginner method." -->

La diferencia en calidad de output entre “Describe your project” y un ejemplo real de 3 líneas es enorme. Pero ahora el validador estricto también verifica que esos ejemplos se reemplacen con contenido real — no pueden quedarse como están.

ADR-003: Documentation Enforcement

Las decisiones del propio framework están documentadas como ADRs:

  • ADR-001: Arquitectura de dos capas (scaffold + root) — el problema de autodiscovery
  • ADR-002: Remoción de soporte OpenCode — riesgo de ban de cuenta
  • ADR-003: Documentation enforcement — por qué tres capas, por qué exit code 2, por qué grace period

El framework se documenta con el mismo formato que propone para los proyectos que lo usan.

Flujo de actualización

Terminal window
curl -sL .../install.sh | bash -s -- --update

Solo actualiza infraestructura (scripts, agents, rules, skills). No toca contenido de docs, CLAUDE.md, AGENTS.md, ni settings.json. Tu documentación real queda intacta.

Resultados concretos en Cube Trainer

Para poner números al impacto de lo que funcionó (y lo que no):

  • 47 commits con convenciones consistentes (feat:, data:, docs:)
  • 4 fases completadas sin rollbacks de arquitectura
  • 0 decisiones re-litigadas gracias a los ADRs
  • 78 algoritmos (PLL + OLL + F2L) validados automáticamente en cada build
  • 3 archivos always-read fueron suficientes para mantener coherencia cross-sesión
  • 25+ commits sin una session note — el gap que motivó v2
  • VISION.md como template vacío después de 4 fases — el validador nunca lo detectó

El agente escribió código correcto pero ignoró completamente la documentación. Los mecanismos de feedback del código (validación build-time, condiciones de rechazo del reviewer) funcionaron. Los mecanismos de feedback de la documentación no existían.

El cambio de actitud

v1: “Los agentes no necesitan más libertad, necesitan un mapa mejor.”

v2: “El mapa no sirve si nadie verifica que lo estén leyendo.”

La diferencia más grande entre v1 y v2 no es técnica. Es de actitud. v1 era un contrato de buena fe: los templates están ahí, las carpetas existen, los roles están definidos. Si algo no funciona, es culpa del agente que no leyó.

v2 asume que si no se verifica, no existe. Placeholders en archivos always-read → error de validación. 6 commits sin session notes → error. Fecha literal del template en frontmatter → error. Hook pre-commit que bloquea con exit code 2 → el agente no puede ignorarlo.

La documentación dejó de ser un “nice to have” y se convirtió en un artefacto con invariantes verificables, al mismo nivel que los tests o el linter.

Qué sigue

v2 resuelve los gaps de enforcement. Pero hay cosas pendientes:

  • Memoria activa: RECURRING_MISTAKES necesita un trigger automático, no depender de la disciplina del agente
  • Sesiones automáticas: Generar notas de sesión debería ser un hook post-commit, no una tarea manual
  • Métricas de uso: Saber qué documentos lee cada agente y cuáles ignora ayudaría a optimizar la política de lectura

El framework está en github.com/lea2696/project-docs. Se instala con un curl, y si ya lo usaste en v1, --update actualiza la infra sin tocar tus docs.

Repo: github.com/lea2696/project-docs