@trycore/spec-build-harness 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +17 -2
  3. package/INSTALL.md +2 -2
  4. package/METODOLOGIA.md +35 -5
  5. package/README.md +5 -3
  6. package/VERSION +1 -1
  7. package/agents/build/dor-dod-gatekeeper.md +22 -1
  8. package/agents/build/ux-fidelity-reviewer.md +4 -1
  9. package/commands/build/onboard.md +49 -2
  10. package/commands/build/prototype.md +22 -0
  11. package/commands/build/slice.md +5 -0
  12. package/commands/build/work.md +9 -0
  13. package/dist/commands/doctor.js +8 -0
  14. package/dist/commands/init.js +3 -1
  15. package/dist/commands/status.js +19 -0
  16. package/dist/lib/state-seed.js +95 -1
  17. package/docs/commands.md +9 -3
  18. package/docs/getting-started.md +1 -1
  19. package/docs/hooks.md +1 -1
  20. package/hooks/build/design-source-guard.sh +1 -1
  21. package/package.json +1 -1
  22. package/scripts/tests/test-install.sh +88 -0
  23. package/scripts/tests/test-schema.sh +21 -0
  24. package/skills/building-a-slice/SKILL.md +3 -2
  25. package/skills/building-a-slice/references/dor.md +14 -1
  26. package/skills/building-a-slice/references/foundation-contract.md +44 -0
  27. package/skills/prototyping-screens/SKILL.md +100 -0
  28. package/skills/prototyping-screens/assets/DESIGN.md.template +55 -0
  29. package/skills/prototyping-screens/assets/manifest.schema.json +70 -0
  30. package/skills/prototyping-screens/assets/screen.template.html +34 -0
  31. package/skills/prototyping-screens/references/aesthetic-directions.md +42 -0
  32. package/skills/prototyping-screens/references/extraction.md +57 -0
  33. package/skills/prototyping-screens/references/self-check.md +40 -0
  34. package/state/README.md +26 -2
  35. package/state/build-state.schema.json +41 -2
  36. package/state/build-state.template.json +8 -0
  37. package/templates/CLAUDE.md.template +2 -2
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "trycore-spec-build-harness",
4
4
  "displayName": "Trycore — Spec & Build Harness",
5
- "version": "0.8.3",
5
+ "version": "0.8.5",
6
6
  "description": "Arnés de construcción de dos loops (slice por épica + release gate) para Claude Code, con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
7
7
  "author": {
8
8
  "name": "Trycore",
package/GOVERNANCE.md CHANGED
@@ -10,8 +10,8 @@ evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
10
10
  | Estado | `build-state.json` (+schema, README) | `.claude/state/` |
11
11
  | Agentes | 14 agentes de build | `.claude/agents/build/` |
12
12
  | Hooks | settings.json + 13 scripts | `.claude/settings.json`, `.claude/hooks/build/` |
13
- | Skill | `building-a-slice` (+11 refs · `workflows/`) · `releasing-a-version` (`workflows/`) · `building-a-micro-change` (carril ligero de mantenimiento) · `managing-parallel-front` (front paralelo inter-épica) | `.claude/skills/` |
14
- | Comandos | `/opsx:*` · `/build:onboard` · `/build:reflect` · `/build:slice` · `/build:release` · `/build:work` · `/build:resume` · `/build:front` | `.claude/commands/` |
13
+ | Skill | `building-a-slice` (+11 refs · `workflows/`) · `releasing-a-version` (`workflows/`) · `building-a-micro-change` (carril ligero de mantenimiento) · `managing-parallel-front` (front paralelo inter-épica) · `prototyping-screens` (prototipo HTML de referencia) | `.claude/skills/` |
14
+ | Comandos | `/opsx:*` · `/build:onboard` · `/build:reflect` · `/build:prototype` · `/build:slice` · `/build:release` · `/build:work` · `/build:resume` · `/build:front` | `.claude/commands/` |
15
15
  | Config | allowlist de stack | `.claude/config/stack-allowlist.json` |
16
16
 
17
17
  ## Fases de activación (`harness_phase`)
@@ -46,6 +46,21 @@ Editar `stack-allowlist.json` SOLO si la sección de requisitos técnicos del PR
46
46
  ## Bitácora de cambios de metodología
47
47
  Cambios a la política de construcción (unidad de trabajo, gates, DoR/DoD). Aprueba el DRI; van por PR.
48
48
 
49
+ - **2026-08-05 · v0.8.5** — **El arnés puede generar el prototipo (fuente de diseño)** (origen:
50
+ investigación de comunidad sobre fidelidad visual + dolor recurrente de pantallas que difieren del
51
+ prototipo; spec `docs/superpowers/specs/2026-08-05-prototype-generator-design.md`). **Aprobación del
52
+ DRI (Agent Manager) requerida en el PR.** Cambia una regla de política: "el arnés NO genera el
53
+ prototipo" → "**puede generarlo** vía `/build:prototype` (skill `prototyping-screens`); la
54
+ confirmación (`design_source.confirmed`) y la aprobación de cada pantalla (`borrador`→`aprobada`
55
+ en `manifest.json`) siguen siendo **exclusivamente humanas**". No cambia unidad de trabajo, gates,
56
+ DoR ni DoD: el gate `design_source` conserva su semántica (espejo de `scaffold`) y `fidelity`
57
+ sigue igual. Toca §9.2 (4º carve-out de escritura: subárbol `docs/05-prototipo/`, análogo a
58
+ `docs/adr/`) y §10 regla 9 (nota de producción de la fuente). Modo feature con **precondición
59
+ dura** (app corriendo + MCP de inspección de UI; sin degradación estática), coherente con la
60
+ postura estricta de `fidelity`. Artefactos nuevos: **1 comando** (`/build:prototype`, total 9
61
+ `/build:*`) y **1 skill** (`prototyping-screens`, total 16); sin hooks ni agentes nuevos; schema
62
+ sin cambio estructural (solo texto de `description`). Release **0.8.5** (version-sync ×3).
63
+
49
64
  - **2026-06-24 · v0.7.0** — **Workflows dinámicos en el flujo build-a-slice** (origen:
50
65
  evaluación adversarial del arnés con workflows dinámicos; 18 mejoras aprobadas contra una rúbrica de
51
66
  hardness). **Aprobado por el DRI (Agent Manager); fusionado en PR #7.** No cambia la unidad de trabajo, los gates,
package/INSTALL.md CHANGED
@@ -87,8 +87,8 @@ Qué hace `init`:
87
87
  1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
88
88
  2. **Siembra los assets** en rutas nativas de Claude Code:
89
89
  - `.claude/agents/build/` — 14 agentes.
90
- - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (8 comandos: `/build:onboard`, `/build:reflect`, `/build:architect`, `/build:slice`, `/build:release`, `/build:work`, `/build:resume`, `/build:front`).
91
- - `.claude/skills/` — 15 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `openspec-*`).
90
+ - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (9 comandos: `/build:onboard`, `/build:reflect`, `/build:architect`, `/build:prototype`, `/build:slice`, `/build:release`, `/build:work`, `/build:resume`, `/build:front`).
91
+ - `.claude/skills/` — 16 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens`, `openspec-*`).
92
92
  - `.claude/hooks/build/` — 13 hooks (bash + python).
93
93
  3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
94
94
  `state/build-state.json` se siembra **vacío y nunca se sobrescribe** (va al `.gitignore`).
package/METODOLOGIA.md CHANGED
@@ -124,6 +124,18 @@ sola pasada**: a medida que crece el contexto, la atención se degrada ("context
124
124
  > construye **encima** del scaffold: el scaffold es el shell vacío que arranca; el walking skeleton
125
125
  > es el primer journey real más delgado. Sin scaffold confirmado no se abre ningún slice.
126
126
 
127
+ > **Paso 1-bis — la épica caparazón (solo greenfield).** En un proyecto **nuevo**
128
+ > (`project_kind: "greenfield"`), entre el scaffold y las épicas de negocio existe un tercer
129
+ > concepto: el **caparazón** — navegación/menús, layout/panel central, homepage, login/authN y
130
+ > redirecciones/guards (contrato podable en
131
+ > `skills/building-a-slice/references/foundation-contract.md`). Se modela como **épica
132
+ > `layer: foundational` obligatoria y primera**: el DoR bloquea **proactivamente** toda épica
133
+ > `business` hasta archivarla con su checklist **evidenciada** (gate de proyecto `foundation`).
134
+ > El arnés **propone** su borrador y solo lo escribe con aprobación humana (§9.2); si se
135
+ > rechaza, se crea en discovery. En **brownfield** el mecanismo es N/A y el arnés no pregunta.
136
+ > Scaffold = shell vacío que arranca; caparazón = shell con contenido fundacional; walking
137
+ > skeleton = primer journey de negocio más delgado, construido **sobre** ambos.
138
+
127
139
  El arnés prohíbe construir capas horizontales aisladas que "se juntan al final" (el anti-patrón que
128
140
  hace que todos los gates por-slice estén en verde y el producto aun así no funcione de punta a
129
141
  punta). En su lugar:
@@ -188,6 +200,11 @@ Una épica **no entra a construcción** hasta cumplir todo (lo valida `dor-dod-g
188
200
  acceso a datos, arquitectura base, design-system/componentes base) ya existe como épica(s)
189
201
  `layer: foundational` **archivada(s)**. Si arrastra cimiento no construido → STOP: se extrae a una
190
202
  épica fundacional previa. La cláusula "no bloquean" de dependencias **no aplica al cimiento**.
203
+ - **Caparazón construido (solo greenfield — PROACTIVO)**: con `project_kind: "greenfield"` y
204
+ `foundation.required: true`, ninguna épica `business` abre hasta que la épica caparazón esté
205
+ archivada con su checklist evidenciada (`foundation.completed_at`); las fundacionales sí abren.
206
+ No depende de detectar arrastre (eso es "Cimiento construido"): en greenfield bloquea siempre.
207
+ Brownfield → N/A.
191
208
  - **Tamaño acotado (gate de descomposición)**: si la épica supera el umbral —por defecto **> 3 HU** ó
192
209
  **≥ 3 capas tocadas**, configurable— se descompone en `sub_slices[]` construidos de a uno
193
210
  (`journey_smoke` verde entre cada uno). Umbral proporcional, no cuota rígida.
@@ -476,10 +493,16 @@ La construcción **consume** los artefactos de discovery y los trata como entrad
476
493
 
477
494
  El arnés **no escribe** en los artefactos de discovery (`docs/01-prd/` … `docs/04-historias/`); cuando
478
495
  una HU no cumple DoR, devuelve el trabajo a discovery (`/trycore:*`). Los contactos de escritura del
479
- arnés en `docs/` son dos y están acotados: la **back-reference** del change en la épica y las HU al
480
- archivar (§6.4), y el subárbol **`docs/adr/`** —propiedad de construcción— que produce la capa de
481
- arquitectura (§9.3). `docs/adr/` **no** es un artefacto de discovery: es la salida de construcción que
482
- traza *hacia* discovery (HU/PRD) sin modificarla.
496
+ arnés en `docs/` son **cuatro** y están acotados: la **back-reference** del change en la épica y las HU
497
+ al archivar (§6.4), el subárbol **`docs/adr/`** —propiedad de construcción— que produce la capa de
498
+ arquitectura (§9.3), la **épica caparazón** en `docs/03-backlog/epicas.md` únicamente esa épica,
499
+ únicamente con **aprobación humana explícita** del borrador propuesto en `/build:onboard` Fase 2c
500
+ (frontmatter `origin: harness-draft`); si el humano rechaza, la épica se crea en discovery como
501
+ siempre —, y el subárbol **`docs/05-prototipo/`** —propiedad de construcción— donde
502
+ `/build:prototype` (skill `prototyping-screens`) produce el prototipo HTML de referencia (la fuente
503
+ de diseño): las pantallas nacen `borrador` y solo pasan a `aprobada` con aprobación humana, análogo
504
+ a `docs/adr/`. Ni `docs/adr/` ni `docs/05-prototipo/` son artefactos de discovery: son salida de
505
+ construcción que traza *hacia* discovery (HU/PRD) sin modificarla.
483
506
 
484
507
  ### 9.3 Capa de arquitectura (ADD) — entre discovery y construcción
485
508
 
@@ -536,7 +559,9 @@ salida es file-based y la propuesta se materializa en git, mañana irá por API
536
559
  no MVP** (recortar/diferir es bloqueante explícito, nunca decisión del modelo; verificación
537
560
  ejecutada, no por inspección).
538
561
  9. **Fidelidad estricta**: para slices con UI, `fidelity` solo cierra con **verificación visual real**
539
- (MCP chrome-devtools); INCONCLUSO no pasa.
562
+ (MCP chrome-devtools); INCONCLUSO no pasa. La fuente de diseño puede **producirse** con
563
+ `/build:prototype` (en modo feature exige extracción viva de la app corriendo);
564
+ `design_source.confirmed` sigue siendo confirmación humana, nunca del arnés.
540
565
  10. El core es **agnóstico**: lo específico del dominio se inyecta vía `/build:onboard` y
541
566
  `stack-allowlist.json`; el ejemplo de referencia vive en `docs/examples/reference/`.
542
567
  11. Si una skill, agente o reference contradice este documento, **gana la metodología**.
@@ -544,3 +569,8 @@ salida es file-based y la propuesta se materializa en git, mañana irá por API
544
569
  disjuntas en `files_scope` se paralelizan; una épica `layer: foundational` nunca entra al front y
545
570
  lo pone en `draining` hasta que se vacía; el merge exige re-smoke del journey completo tras cada
546
571
  integración.
572
+ 13. **Caparazón primero en greenfield (§2 Paso 1-bis)**: en un proyecto nuevo, la épica caparazón
573
+ (`foundation.epic`) se construye y archiva **antes** que cualquier épica `business`; su DoD
574
+ exige evidencia de ejecución por cada ítem `applies: true` del contrato. En brownfield el
575
+ mecanismo es N/A y no se pregunta. El arnés propone el borrador de la épica; solo lo escribe
576
+ con aprobación humana explícita (§9.2).
package/README.md CHANGED
@@ -142,8 +142,8 @@ trycore-spec-build-harness/
142
142
  ├── agents/build/ ← 14 agentes revisores (segunda opinión, contexto limpio)
143
143
  ├── commands/
144
144
  │ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
145
- │ └── build/ ← 8 comandos /build:* (onboard, reflect, architect, slice, release, work, resume, front)
146
- ├── skills/ ← 15 skills (building-a-slice, building-a-micro-change, releasing-a-version, managing-parallel-front, setup-architecture, 10 openspec-*) + 3 plantillas *.workflow.js (opt-in, read-only)
145
+ │ └── build/ ← 9 comandos /build:* (onboard, reflect, architect, prototype, slice, release, work, resume, front)
146
+ ├── skills/ ← 16 skills (building-a-slice, building-a-micro-change, releasing-a-version, managing-parallel-front, setup-architecture, prototyping-screens, 10 openspec-*) + 3 plantillas *.workflow.js (opt-in, read-only)
147
147
  ├── hooks/build/ ← 13 hooks (gate-check, reflect-nudge, release-gate-nudge, scaffold-guard, gitflow-guard, stack-guard, statusline-bridge, context-monitor, reconcile-build-state, …)
148
148
  ├── state/ ← máquina de estado: build-state.json + schema + README
149
149
  ├── config/ ← build-config.template.json (umbrales de contexto) + stack-allowlist.template.json (artefacto del consumidor)
@@ -183,7 +183,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
183
183
  - ✅ **v0.7.0** — **orquestación con workflows dinámicos + hardening** (de una evaluación adversarial del propio arnés): **3 plantillas `*.workflow.js`** opt-in y read-only (`explore-fanout`, `wiring-verify`, `release-gate`) que entran **solo donde aportan valor** y nunca en el camino caliente del inner loop; **3 comandos nuevos** `/build:slice` (entrada del inner loop), `/build:release` (outer loop) y `/build:work` (router *classify-and-act*); hook **`release-gate-nudge.sh`** (Stop, determinista: solo sugiere el Release Gate). Rename de los gates de los 5 reviewers pesados → `releases[].gates.{security,smell,ux,coherence,stack_arch}` (`stack`→`stack_arch`; separación `coherence` (release) / `coherence_link` (inner)). Hardening: degradación segura en 8 agentes, escritura atómica del estado, cierre del bypass de specs no-semver, `wiring` exige evidencia ejecutada y `check-agnostic` barre `*.js`. Total: **12 agentes**, **10 hooks**, **5 comandos `/build:*`**.
184
184
  - ✅ **v0.8.0** — **motor de contexto + estado anclado a disco + front paralelo inter-épica**: hooks **`statusline-bridge.sh`** (canal CLI) + **`context-monitor.sh`** (umbrales `context.warning_pct`/`context.critical_pct` configurables, 35%/25% por defecto) con **auto-handoff** a `session_continuity` en critical/`PreCompact` y comando **`/build:resume`** para rehidratar desde disco; config **`context.auto_checkpoint`** (opt-in). Reconciliador **`reconcile-build-state.py`** (`SessionStart`): deriva de git + evidencia de tests, degrada `wiring_checklist` sin evidencia, anota *branch drift*, ratchet de gates, fail-open. **Front paralelo** (`parallel_front` en el estado): comando **`/build:front`** + skill **`managing-parallel-front`** + `scripts/lib/front-plan.py` (disjunción por `files_scope`, foundational-first). Schema nuevo: `slice.layer`, `slice.files_scope`, `slice.branch_drift`, `slice.session_continuity`. **Caveat:** `statusLine` es solo canal CLI; en plugin-only el motor de contexto degrada fail-open. Total: **12 agentes**, **13 hooks**, **7 comandos `/build:*`**, **14 skills**.
185
185
  - ✅ **v0.8.1** — **hotfix del motor de contexto**: `context-monitor.sh` re-inyectaba `additionalContext` en cada evento `Stop`, lo que re-lanzaba el turno en bucle hasta el tope `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` (9→override). Ahora en `Stop` re-lanza como mucho una vez por sesión (solo la transición a crítico que graba el handoff) y `warning` nunca inyecta en `Stop`. Cubierto por `test-context-monitor.sh`.
186
- - ✅ **v0.8.3 (actual)** — **gates de validación paralelizados (sharding lossless)**: el carril `coherence` del Release Gate se shardea **por HU** ( 3 HUs, `args.hus[]`) de un solo agente opus O(HUs) a un shard por HU en paralelo con consolidación **fail-closed** y cobertura completa; nueva plantilla **`dor-fanout.workflow.js`** para los chequeos per-HU del DoR (frontmatter/G-W-T/INVEST en paralelo; el nivel épica sigue en `dor-dod-gatekeeper`, único emisor del veredicto). `security`/`smell`/`ux`/`stack_arch` quedan monolíticos a propósito (riesgo cross-cutting); `integration` sigue secuencial (regla dura §5).
186
+ - ✅ **v0.8.5 (actual)** — **generador de prototipos HTML de referencia**: comando **`/build:prototype`** + skill **`prototyping-screens`** que producen la fuente de diseño (`DESIGN_SOURCE`) en `docs/05-prototipo/` (`DESIGN.md` + `tokens.css` + `manifest.json` pantalla↔épica/HU + un HTML autocontenido por pantalla). Dos modos: **greenfield** (inventario desde PRD/mapa/historias → dirección estética con 2-3 variantes a elección humana → generación por lotes) y **feature** (pantallas de una épica nueva con **extracción viva obligatoria** del UI implementado: CSS computado + screenshots en 3 viewports vía MCP; sin degradación estática). Auto-verificación visual (render real, máx. 3 iteraciones, sin pixel-diff) y **aprobación humana** de cada pantalla (`borrador`→`aprobada`); `ux-fidelity-reviewer` resuelve pantallas vía `manifest.json` (solo `aprobada`). La regla "el arnés no genera el prototipo" evoluciona a "puede generarlo; `design_source.confirmed` sigue siendo humano". carve-out de escritura en `docs/` (§9.2). Total: **14 agentes**, **13 hooks**, **9 comandos `/build:*`**, **16 skills**.
187
+ - ✅ **v0.8.4** — **épica caparazón obligatoria en greenfield**: nuevo campo `project_kind` con detección automática conservadora en `trycore-build init` (brownfield seguro = manifiesto + código + **historial git con commits de código**; ambigüedad → una sola pregunta en `/build:onboard`; **en brownfield el mecanismo es N/A total, sin preguntar**) y gate de proyecto **`foundation`**: contrato del caparazón (navegación/menús · layout/panel central · homepage · login/authN · redirecciones/guards) como checklist podable con **evidencia de ejecución por ítem** (patrón `wiring_checklist`, contrato en `skills/building-a-slice/references/foundation-contract.md`). `/build:onboard` **Fase 2c** poda el contrato y redacta el **borrador híbrido** de la épica (solo se escribe en `epicas.md` con aprobación humana explícita — carve-out §9.2). **DoR 7-bis proactivo**: en greenfield ninguna épica `business` abre slice hasta que la caparazón esté **archivada con checklist evidenciada** (`foundation.completed_at`); las fundacionales y el carril micro-change nunca se bloquean. Línea `Caparazón` en `status`/`doctor`. Retrocompatible: campos opcionales del schema.
188
+ - ✅ **v0.8.3** — **gates de validación paralelizados (sharding lossless)**: el carril `coherence` del Release Gate se shardea **por HU** (≥ 3 HUs, `args.hus[]`) — de un solo agente opus O(HUs) a un shard por HU en paralelo con consolidación **fail-closed** y cobertura completa; nueva plantilla **`dor-fanout.workflow.js`** para los chequeos per-HU del DoR (frontmatter/G-W-T/INVEST en paralelo; el nivel épica sigue en `dor-dod-gatekeeper`, único emisor del veredicto). `security`/`smell`/`ux`/`stack_arch` quedan monolíticos a propósito (riesgo cross-cutting); `integration` sigue secuencial (regla dura §5).
187
189
  - ✅ **v0.8.2** — **capa de arquitectura (ADD)** entre discovery y construcción: comando **`/build:architect`** + skill **`setup-architecture`** que aplica el método **Attribute-Driven Design** (Len Bass) leyendo `docs/` (solo lectura) y produciendo `docs/adr/` (drivers/ASRs → tácticas → estilos → vistas → ATAM-lite → stack), con **mínimo HITL** (autónomo, una revisión final; propone, no publica). Dos agentes nuevos (`asr-extractor`, `architecture-evaluator`), plantillas ADD embebidas en la skill (`skills/setup-architecture/assets/`) y **`asset-types.json`** (forward-compat runtime v0.9: `arch.drivers`/`arch.adr`/`arch.backlog`). Cierre del lazo: los ADRs se vuelven criterios — el **DoR** exige cobertura para el cimiento fundacional (opt-in, retrocompatible) y el gate **`stack_arch`** audita conformidad contra `docs/adr/`. Total: **14 agentes**, **13 hooks**, **8 comandos `/build:*`**, **15 skills**.
188
190
 
189
191
  ## Licencia
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.3
1
+ 0.8.5
@@ -41,12 +41,24 @@ cumplen; lista cada una con ✓/✗:
41
41
  como épica(s) `layer: foundational` **archivada(s)** en `history[]`. Si arrastra cimiento no construido,
42
42
  **NO abras el slice**: instruye extraerlo a una épica fundacional previa y construirla primero. Aquí la
43
43
  cláusula "explícitamente no bloquean" del criterio 6 **NO aplica**: el cimiento bloquea siempre.
44
+ 7-bis. **Caparazón construido (solo greenfield — PROACTIVO)**: lee `project_kind` y `foundation`
45
+ en `build-state.json`. Si `project_kind === "greenfield"` y `foundation.required === true` y
46
+ la épica evaluada es `layer: business`: exige `foundation.completed_at` estampado (la épica
47
+ caparazón, `foundation.epic`, archivada y con su checklist evidenciada). Si no está estampado,
48
+ **NO abras el slice**: instruye construir primero la épica caparazón (`foundation.epic`; si es
49
+ `null`, correr `/build:onboard` Fase 2c o crearla en discovery). Las épicas `layer: foundational`
50
+ no se bloquean por este criterio. Brownfield o `foundation.required !== true` → **N/A** (no
51
+ bloquea, no preguntes). A diferencia del criterio 7, aquí NO evalúas arrastre: en greenfield
52
+ el bloqueo es incondicional hasta que el cimiento exista archivado.
44
53
  8. **Tamaño acotado**: si la épica supera el umbral del gate de descomposición —heurística por defecto
45
54
  **> 3 HU** ó **≥ 3 capas tocadas** (configurable por proyecto)— **no la abras como slice único**:
46
55
  instruye descomponerla en `sub_slices[]` construidos de a uno (`journey_smoke` verde entre cada uno).
47
56
  9. **Fuente de diseño (solo slices con UI)**: si la épica toca UI, `design_source.confirmed===true`
48
57
  (fuente visual de verdad declarada para el proyecto) y la épica apunta a la(s) pantalla(s)
49
- equivalente(s) del `DESIGN_SOURCE`. Si no toca UI, este criterio es N/A.
58
+ equivalente(s) del `DESIGN_SOURCE`. Si la fuente es un prototipo generado (convención
59
+ `docs/05-prototipo/`), la(s) pantalla(s) equivalente(s) deben existir en su `manifest.json` con
60
+ `estado: "aprobada"` — un `borrador` **no** satisface este criterio (genera/aprueba primero con
61
+ `/build:prototype`). Si no toca UI, este criterio es N/A.
50
62
  10. **Cobertura arquitectónica (ADR)** — *opt-in, retrocompatible*: si el proyecto adoptó la capa de
51
63
  arquitectura (existe `docs/adr/_backlog-arquitectonico.md`, generado por `/build:architect`), los
52
64
  drivers **arquitecturalmente significativos** que la épica ejerce deben tener un ADR con estado
@@ -80,6 +92,15 @@ Pasa SOLO si **todos** estos gates del **inner loop** están en `true` (o `null`
80
92
  6-bis. **Sub-slices completos**: si `active_slice.sub_slices[]` no está vacío, **todos** deben estar en
81
93
  `status: done` (cada uno con su `journey_smoke` verde). Una épica descompuesta no cierra `dod` con
82
94
  sub-slices pendientes (sería cierre prematuro de alcance).
95
+ 6-ter. **Checklist del caparazón evidenciada (solo la épica caparazón)**: si la épica que cierra
96
+ es `foundation.epic`, **todos** los ítems de `foundation.checklist[]` con `applies: true`
97
+ deben tener `evidence` no vacía de **ejecución real** (test corrido, comando, screenshot MCP
98
+ — nunca inspección). Si falta evidencia, `dod` NO cierra: enumera los ítems pendientes. Al
99
+ cerrar `dod` y archivarse la épica, propone estampar `foundation.completed_at` (ISO-8601 UTC)
100
+ — con eso las épicas `layer: business` quedan desbloqueadas del criterio 7-bis. Para
101
+ cualquier otra épica este punto es N/A. La `evidence` de cada ítem la escribe el
102
+ `build-orchestrator` durante la construcción (misma disciplina que `wiring_checklist[]`: prueba
103
+ real ejecutada); tú solo la verificas y, al cerrar, estampas `foundation.completed_at`.
83
104
  7. **`wiring_verified`** — `true`. Prerequisito **duro** de `dod`: lo cierra el `wiring-adversarial-verifier`
84
105
  (subagente **independiente**, contexto virgen) tras intentar refutar el slice (stubs, rutas sin cablear,
85
106
  AC sin test, items de `wiring_checklist[]` aún `failing`) y no hallar huecos. **Tu DoD declarativo es un
@@ -20,7 +20,10 @@ igual que `ux-krug-reviewer`.
20
20
  ## Entradas (pídelas si faltan)
21
21
  - La(s) pantalla(s) del slice (rutas de la app, p.ej. `<URL-local-del-dev-server>/<ruta>`).
22
22
  - La fuente de diseño (`DESIGN_SOURCE`): archivo/URL del prototipo o export, y cómo localizar la
23
- pantalla equivalente.
23
+ pantalla equivalente. Si apunta a un directorio de prototipo generado (convención
24
+ `docs/05-prototipo/`), resuelve la pantalla equivalente vía su `manifest.json` (mapa
25
+ pantalla↔épica/HU↔archivo) y considera **solo** las entradas `estado: "aprobada"` como fuente
26
+ de verdad — un `borrador` no acredita fidelidad.
24
27
  - Tokens de diseño del proyecto (los que declare el stack del PRD del consumidor: variables CSS, tema,
25
28
  design tokens), si existen.
26
29
 
@@ -46,6 +46,7 @@ Voy a leer tu PRD/openspec para proponer valores y confirmar contigo:
46
46
  6. Decisiones de alto impacto que exigen explicabilidad en UX
47
47
  7. Fuente de diseño / referencia visual (prototipo/export) y pantallas — o "N/A" si no hay UI
48
48
  8. Capa de cada épica del backlog: **fundacional** (cimiento) vs **negocio**
49
+ 9. (Solo proyecto nuevo) Contrato del caparazón: checklist de la épica fundacional de app shell
49
50
  ```
50
51
 
51
52
  ---
@@ -65,13 +66,25 @@ Voy a leer tu PRD/openspec para proponer valores y confirmar contigo:
65
66
  - **Decisiones de alto impacto** (`HIGH_STAKES_DECISIONS`): decisiones que exigen explicabilidad/justificación en la UI.
66
67
  - **Fuente de diseño** (`DESIGN_SOURCE`): ¿el producto tiene UI? Si sí, ruta/URL de la fuente
67
68
  visual de verdad (prototipo, export de diseño o mockups) y cómo localizar cada pantalla; si no,
68
- "N/A". (La escritura del estado `design_source` en `build-state.json` se hace en la Fase 3c.)
69
+ "N/A". Si hay UI pero **no existe** fuente aún, indica que `/build:prototype` puede generarla
70
+ tras el onboarding (prototipo HTML de referencia en `docs/05-prototipo/`; la confirmación
71
+ sigue siendo humana). (La escritura del estado `design_source` en `build-state.json` se hace
72
+ en la Fase 3c.)
69
73
  3. Si un punto no aplica al proyecto, registra explícitamente "no aplica" (no lo dejes como `{{...}}`).
70
74
 
71
75
  ---
72
76
 
73
77
  ## Fase 2b: Clasificar la capa de las épicas (cimiento vs negocio)
74
78
 
79
+ **Primero, resuelve `project_kind`** (lee `.claude/state/build-state.json`):
80
+ - `"brownfield"` → el mecanismo del caparazón es **N/A**: no preguntes ni exijas nada de la
81
+ Fase 2c; clasifica capas como siempre y sigue.
82
+ - `null` / ausente (detección ambigua del CLI) → pregunta **una sola vez** vía AskUserQuestion:
83
+ *"¿Este proyecto es una app nueva (greenfield: el caparazón — menús, layout, homepage, login,
84
+ redirecciones — aún no existe) o ya construida (brownfield)?"*. Escribe `project_kind` y
85
+ `project_kind_source: "human"` en el estado (valida contra el schema tras escribir).
86
+ - `"greenfield"` → tras clasificar capas (abajo), continúa a la **Fase 2c**.
87
+
75
88
  El factor que más reduce el consumo de contexto por slice es que el **cimiento** ya esté construido y
76
89
  abstraído antes de que el loop tome historias de negocio. Para habilitar el gate de DoR "Cimiento
77
90
  construido":
@@ -87,6 +100,38 @@ No inventes la clasificación: derívala del PRD/Story Map y confírmala con el
87
100
 
88
101
  ---
89
102
 
103
+ ## Fase 2c: (Solo greenfield) Contrato del caparazón y su épica
104
+
105
+ Contrato completo en `.claude/skills/building-a-slice/references/foundation-contract.md`.
106
+ Si `project_kind !== "greenfield"`, salta esta fase (N/A total).
107
+
108
+ 1. **Podar la checklist** vía AskUserQuestion (multiSelect) partiendo de los 5 ítems canónicos
109
+ (`navegacion-menus`, `layout-panel-central`, `homepage`, `login-authn`,
110
+ `redirecciones-guards`) según el tipo de app (una API sin UI poda los de UI y conserva
111
+ `login-authn`). Ítem podado = `applies: false` (se conserva como decisión, no se borra).
112
+ El usuario puede añadir ítems propios (id kebab-case). Si el humano poda **todos** los ítems
113
+ (ningún `applies: true`), no hay caparazón exigible: escribe `foundation.required: false` y el
114
+ mecanismo queda N/A (el DoR no bloqueará por caparazón).
115
+ 2. **Identificar la épica caparazón** en `docs/03-backlog/epicas.md`: una épica
116
+ `layer: foundational` cuyo alcance cubra los ítems `applies: true`.
117
+ - **Existe** → propónla al usuario y fija `foundation.epic`.
118
+ - **No existe** → **borrador híbrido**: redacta la épica caparazón (título, objetivo, una HU
119
+ por ítem `applies: true` con AC en Given/When/Then) y preséntala vía AskUserQuestion.
120
+ - **Aprueba** → escríbela en `docs/03-backlog/epicas.md` con frontmatter
121
+ `layer: foundational` y `origin: harness-draft`, y fija `foundation.epic`.
122
+ **Este es el ÚNICO caso en que el arnés escribe una épica** (carve-out de METODOLOGIA
123
+ §9.2: solo la épica caparazón, solo con aprobación explícita).
124
+ - **Rechaza** → **STOP** de la fase: deja `foundation.epic: null`, instruye crearla en
125
+ discovery (`/trycore:*`) con la checklist como alcance. El gate del DoR bloqueará las
126
+ épicas de negocio igual hasta que exista y se archive.
127
+ 3. **Persistir en el estado**: escribe `foundation.required: true`, `foundation.checklist[]`
128
+ (todos los ítems con su `applies`, `evidence: ""`), `foundation.epic` (o `null`). Escribe
129
+ **solo** los campos del schema (`foundation` es `additionalProperties: false`) y **valida
130
+ contra `build-state.schema.json` tras escribir** (aborta si no valida). Una transición = una
131
+ escritura.
132
+
133
+ ---
134
+
90
135
  ## Fase 3: Resolver el bloque de CLAUDE.md
91
136
 
92
137
  Lee `CLAUDE.md`. Encuentra el bloque entre `<!-- BEGIN trycore-build-harness` y `<!-- END trycore-build-harness -->`.
@@ -125,7 +170,8 @@ Si el usuario lo desea y existe `package.json` en el proyecto:
125
170
  Espejo de la confirmación de scaffold, para `design_source` en `build-state.json`:
126
171
  - Si el producto **tiene UI**: setea `design_source.applies=true`, `source` (el puntero confirmado) y
127
172
  `confirmed=true` **solo si** el usuario confirma que la fuente de diseño existe (con `confirmed_by`,
128
- `confirmed_at`). El arnés **no genera** el prototipo.
173
+ `confirmed_at`). El prototipo puede generarse con `/build:prototype` (skill `prototyping-screens`);
174
+ `confirmed` sigue siendo exclusivamente humano.
129
175
  - Si **no hay UI**: setea `design_source.applies=false` (el mecanismo de fidelidad queda N/A).
130
176
 
131
177
  Escribe **solo** los campos del schema (`applies`, `source`, `confirmed`, `confirmed_by`, `confirmed_at`,
@@ -162,6 +208,7 @@ PII/datos: <...>
162
208
  Secretos: <...>
163
209
  Decisiones clave: <...>
164
210
  Fuente diseño: <...>
211
+ Caparazón: <N/A (brownfield) | EP-XXX con N ítems aplicables | pendiente en discovery>
165
212
 
166
213
  CLAUDE.md actualizado. Auto-memory escrita. Los agentes de calidad ya leen tu dominio.
167
214
 
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: "BUILD: Prototype"
3
+ description: Genera o amplía el prototipo HTML de referencia (la fuente de diseño / DESIGN_SOURCE) en docs/05-prototipo/. Dos modos — greenfield (prototipo inicial desde los docs de discovery + dirección estética elegida por el humano) y feature (pantallas de una épica nueva extraídas en vivo del UI ya implementado). Delega en la skill prototyping-screens. La confirmación de design_source sigue siendo humana.
4
+ category: Workflow
5
+ tags: [build-harness, outer-loop, prototipo, design-source, trycore]
6
+ ---
7
+
8
+ # /build:prototype — Prototipo HTML de referencia
9
+
10
+ Delega en la skill **prototyping-screens**. Uso:
11
+
12
+ - `/build:prototype` — modo **greenfield** (o detección automática): prototipo inicial completo.
13
+ Inventario de pantallas desde PRD/mapa/historias → confirmación humana → dirección estética
14
+ (manual de marca o 2-3 variantes a elección humana) → generación por lotes con auto-verificación.
15
+ - `/build:prototype <épica>` — modo **feature**: pantallas nuevas de esa épica, coherentes con el
16
+ UX/UI ya implementado. **Precondición dura**: app corriendo + MCP de inspección de UI (p.ej.
17
+ chrome-devtools); sin ellos hace STOP (la extracción viva de CSS computado no se degrada).
18
+
19
+ Es **outer-loop**: córrelo antes de abrir slices (como `/build:architect`). Produce
20
+ `docs/05-prototipo/` (DESIGN.md, tokens.css, manifest.json, pantallas/) y, en greenfield con
21
+ aprobación humana, registra `design_source` en `build-state.json`. **La skill genera; el humano
22
+ aprueba**: `design_source.confirmed` y el `estado: "aprobada"` del manifest nunca se auto-marcan.
@@ -59,6 +59,11 @@ Antes de escribir código de slice, verifica los gates de proyecto:
59
59
  - `scaffold.confirmed` debe ser `true`. Si es `false` → **STOP**: delega en la **Fase 0** de `building-a-slice`
60
60
  (pregunta explícita; el arnés **no genera** el scaffold). No abras el slice.
61
61
  - Si el proyecto tiene UI, `design_source.confirmed` debe ser `true` (Fase 0-bis). Si no → **STOP** igual.
62
+ - **Caparazón (solo greenfield)**: si `project_kind === "greenfield"` y `foundation.required === true`
63
+ y la épica objetivo es `layer: business` mientras `foundation.completed_at` no esté estampado (la
64
+ caparazón `foundation.epic` archivada con evidencia) → **STOP**: solo la épica caparazón
65
+ (`foundation.epic`) u otra fundacional puede abrir. Lo valida en detalle el `dor-dod-gatekeeper`
66
+ (criterio 7-bis). Brownfield → N/A, no preguntes.
62
67
 
63
68
  El hook `scaffold-guard.sh` respalda esto en tiempo real.
64
69
 
@@ -28,6 +28,15 @@ Si `NOT_INSTALLED` → ejecuta `trycore-build init` y vuelve.
28
28
 
29
29
  Aplica las reglas en orden:
30
30
 
31
+ 0. **¿Greenfield sin caparazón construido?** — si `build-state.json` tiene
32
+ `project_kind: "greenfield"`, `foundation.required: true` y `foundation.completed_at` sin
33
+ estampar → **enruta directo a construir la épica caparazón**
34
+ (`foundation.epic`) por el carril **`building-a-slice`**. Si `foundation.epic` es `null`,
35
+ enruta a `/build:onboard` (Fase 2c) o a discovery para definirla. Ninguna épica de negocio
36
+ pasa por delante. Brownfield o `foundation.required` ausente/false → esta regla es N/A.
37
+ Esta regla gatea **aperturas de slice** (épicas): el carril de **mantenimiento**
38
+ (`building-a-micro-change`, regla 1) no se bloquea — un typo/copy/config fix sigue su carril
39
+ normal aun sin caparazón construida.
31
40
  1. **¿Mantenimiento sin capacidad nueva?** — typo, ajuste de copy/config/docs, bump de dependencia **ya
32
41
  permitida**, o fix de **pocas líneas** sin nueva capacidad **Y** sin ninguno de los límites duros del
33
42
  paso 3 → carril **`building-a-micro-change`** (`fix/*`|`chore/*` → PR, **sin** abrir `active_slice`).
@@ -66,6 +66,14 @@ export async function doctor(opts) {
66
66
  const st = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
67
67
  const confirmed = st?.scaffold?.confirmed === true;
68
68
  console.log(`Scaffold (Paso 1): ${confirmed ? '✓ confirmado' : '✗ pendiente — confírmalo en building-a-slice (Fase 0) antes de fases de código'}`);
69
+ const pk = st?.project_kind ?? null;
70
+ const fnd = st?.foundation ?? null;
71
+ if (pk === 'greenfield' && fnd?.required === true && !fnd?.completed_at) {
72
+ console.log(`Caparazón (1-bis): ✗ pendiente — ${fnd?.epic ? `construye ${fnd.epic} antes de épicas de negocio` : 'define la épica caparazón en /build:onboard (Fase 2c)'}`);
73
+ }
74
+ else if (pk === 'greenfield' && fnd?.completed_at) {
75
+ console.log(`Caparazón (1-bis): ✓ construido (${fnd.epic})`);
76
+ }
69
77
  const hist = Array.isArray(st?.history) ? st.history : [];
70
78
  const pend = hist.filter((h) => h?.reflected !== true).length;
71
79
  if (pend > 0) {
@@ -9,7 +9,7 @@ import path from 'node:path';
9
9
  import { ASSETS, readPackageVersion, targetPaths } from '../lib/paths.js';
10
10
  import { linkChildren, countChildren, chmodExec } from '../lib/install-engine.js';
11
11
  import { upsertMarkedBlock } from '../lib/markers.js';
12
- import { seedState, seedConfig, seedBuildConfig } from '../lib/state-seed.js';
12
+ import { seedState, seedConfig, seedBuildConfig, stampProjectKind } from '../lib/state-seed.js';
13
13
  import { mergeHarnessSettings } from '../lib/settings-merge.js';
14
14
  import { captureStack, renderAllowlist } from '../lib/stack-prompt.js';
15
15
  import { checkDeps } from './doctor.js';
@@ -57,6 +57,7 @@ export async function init(opts) {
57
57
  }
58
58
  // 2) Estado (schema/README versionados; build-state.json vacío solo si falta) [C1]
59
59
  const { stateSeeded } = seedState(targetDir);
60
+ const projectKind = stampProjectKind(targetDir);
60
61
  // 3) stack-allowlist.json del consumidor (artefacto del consumidor)
61
62
  let allowlist = null;
62
63
  if (!fs.existsSync(t.configFile) && mode === 'init') {
@@ -87,6 +88,7 @@ export async function init(opts) {
87
88
  console.log(` Estado: ${stateSeeded ? 'build-state.json sembrado (vacío)' : 'build-state.json preservado'}`);
88
89
  console.log(` Config: ${configSeeded ? 'stack-allowlist.json sembrado' : 'stack-allowlist.json preservado'}`);
89
90
  console.log(` Config: ${buildConfigSeeded ? 'build-config.json sembrado' : 'build-config.json preservado'}`);
91
+ console.log(` Proyecto: ${projectKind === 'brownfield' ? 'brownfield (épica caparazón N/A)' : projectKind === 'greenfield' ? 'greenfield (épica caparazón requerida — se define en /build:onboard)' : 'indeterminado (se confirma en /build:onboard)'}`);
90
92
  console.log('');
91
93
  console.log('Siguiente paso — abre Claude Code y ejecuta:');
92
94
  console.log(' /build:onboard # parametriza el dominio (PII, capa IA, capa determinista) y escribe memoria');
@@ -40,6 +40,25 @@ export async function status(opts) {
40
40
  console.log('Estado del arnés:');
41
41
  console.log(` Fase: ${st.harness_phase ?? '?'}`);
42
42
  console.log(` Scaffold: ${st.scaffold?.confirmed === true ? '✓ confirmado (Paso 1)' : '✗ pendiente (Paso 1)'}`);
43
+ const pk = st.project_kind ?? null;
44
+ const fnd = st.foundation ?? null;
45
+ let capLine;
46
+ if (pk === 'brownfield') {
47
+ capLine = 'N/A (brownfield)';
48
+ }
49
+ else if (pk === 'greenfield' && fnd?.required !== true) {
50
+ capLine = '✗ contrato sin definir — /build:onboard (Fase 2c)';
51
+ }
52
+ else if (fnd?.required === true && fnd?.completed_at) {
53
+ capLine = `✓ construido${fnd.epic ? ` (${fnd.epic})` : ''}`;
54
+ }
55
+ else if (fnd?.required === true) {
56
+ capLine = `✗ pendiente${fnd?.epic ? ` (${fnd.epic})` : ' — define la épica en /build:onboard'}`;
57
+ }
58
+ else {
59
+ capLine = 'N/A';
60
+ }
61
+ console.log(` Caparazón: ${capLine}`);
43
62
  const slice = st.active_slice;
44
63
  if (slice) {
45
64
  const openGates = Object.entries(slice.gates ?? {})
@@ -4,6 +4,7 @@
4
4
  // y NUNCA se sobreescribe (es propiedad del equipo).
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
7
8
  import { ASSETS, targetPaths } from './paths.js';
8
9
  import { ensureDir } from './install-engine.js';
9
10
  /** Estado inicial vacío (fallback si falta el template del paquete). */
@@ -12,6 +13,9 @@ const EMPTY_STATE = {
12
13
  harness_phase: 'authoring',
13
14
  scaffold: { confirmed: false, confirmed_by: null, confirmed_at: null, notes: '' },
14
15
  design_source: { applies: false, confirmed: false, confirmed_by: null, confirmed_at: null, source: '', notes: '' },
16
+ project_kind: null,
17
+ project_kind_source: null,
18
+ foundation: { required: false, epic: null, completed_at: null, checklist: [] },
15
19
  active_slice: null,
16
20
  history: [],
17
21
  releases: [],
@@ -56,7 +60,6 @@ export function seedConfig(targetDir, allowlist) {
56
60
  else {
57
61
  return { configSeeded: false };
58
62
  }
59
- void path;
60
63
  return { configSeeded: true };
61
64
  }
62
65
  /**
@@ -73,3 +76,94 @@ export function seedBuildConfig(targetDir) {
73
76
  fs.copyFileSync(ASSETS.buildConfigTemplate, t.buildConfigFile);
74
77
  return { buildConfigSeeded: true };
75
78
  }
79
+ const MANIFESTS = [
80
+ 'package.json', 'pom.xml', 'build.gradle', 'build.gradle.kts', 'go.mod',
81
+ 'Cargo.toml', 'composer.json', 'pyproject.toml', 'requirements.txt', 'Gemfile',
82
+ ];
83
+ const CODE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.java', '.kt', '.go', '.rs', '.php', '.py', '.cs', '.rb', '.swift', '.vue', '.svelte'];
84
+ const CODE_DIRS = ['src', 'app', 'lib', 'apps', 'packages'];
85
+ function dirHasCode(dir, depth = 0) {
86
+ if (depth > 3 || !fs.existsSync(dir))
87
+ return false;
88
+ let entries;
89
+ try {
90
+ entries = fs.readdirSync(dir, { withFileTypes: true });
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ for (const e of entries) {
96
+ if (e.isFile() && CODE_EXTS.some((x) => e.name.endsWith(x)))
97
+ return true;
98
+ if (e.isDirectory() && e.name !== 'node_modules' && !e.name.startsWith('.')) {
99
+ if (dirHasCode(path.join(dir, e.name), depth + 1))
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+ /** ¿El repo git del consumidor tiene commits que tocan código? Señal del spec para
106
+ * brownfield SEGURO. Acotado: mira los últimos 30 commits. Sin repo git → false. */
107
+ function hasGitCodeHistory(dir) {
108
+ const inRepo = spawnSync('git', ['-C', dir, 'rev-parse', '--git-dir'], { stdio: 'ignore' }).status === 0;
109
+ if (!inRepo)
110
+ return false;
111
+ const r = spawnSync('git', ['-C', dir, 'log', '-n', '30', '--name-only', '--pretty=format:'], { encoding: 'utf8' });
112
+ if (r.status !== 0 || !r.stdout)
113
+ return false;
114
+ return r.stdout.split('\n').some((f) => CODE_EXTS.some((x) => f.trim().endsWith(x)));
115
+ }
116
+ /**
117
+ * Detección greenfield/brownfield a partir de tres señales: manifiesto de paquete,
118
+ * presencia de código (raíz o directorios convencionales) e historial git que toca
119
+ * código. Brownfield CIERTO requiere las tres señales; greenfield CIERTO requiere la
120
+ * ausencia total de manifiesto y código. Señales mixtas (p.ej. scaffold recién creado
121
+ * sin historial git) devuelven null — ambiguo, lo resuelve `/build:onboard`.
122
+ */
123
+ export function detectProjectKind(targetDir) {
124
+ let rootEntries = [];
125
+ try {
126
+ rootEntries = fs.readdirSync(targetDir);
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ const hasManifest = MANIFESTS.some((m) => fs.existsSync(path.join(targetDir, m))) ||
132
+ rootEntries.some((n) => n.endsWith('.csproj') || n.endsWith('.sln'));
133
+ const rootCode = rootEntries.some((n) => CODE_EXTS.some((x) => n.endsWith(x)));
134
+ const hasCode = rootCode || CODE_DIRS.some((d) => dirHasCode(path.join(targetDir, d)));
135
+ if (hasManifest && hasCode && hasGitCodeHistory(targetDir))
136
+ return 'brownfield';
137
+ if (!hasManifest && !hasCode)
138
+ return 'greenfield';
139
+ return null; // señales mixtas (p.ej. scaffold recién creado sin historial) → ambiguo, lo resuelve onboard
140
+ }
141
+ /**
142
+ * Estampa project_kind en build-state.json SOLO si está ausente o null (migración
143
+ * aditiva; nunca pisa una decisión previa auto o humana) y solo con detección no-null.
144
+ */
145
+ export function stampProjectKind(targetDir) {
146
+ const t = targetPaths(targetDir);
147
+ if (!fs.existsSync(t.stateFile))
148
+ return null;
149
+ const detected = detectProjectKind(targetDir);
150
+ let parsed;
151
+ try {
152
+ parsed = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
153
+ }
154
+ catch {
155
+ return detected;
156
+ }
157
+ // JSON.parse puede devolver con éxito algo que no es un objeto plano (null, string,
158
+ // número, boolean, array) si el archivo está corrupto — degradar igual que un parse fallido.
159
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
160
+ return detected;
161
+ }
162
+ const st = parsed;
163
+ if (detected !== null && (st.project_kind === undefined || st.project_kind === null)) {
164
+ st.project_kind = detected;
165
+ st.project_kind_source = 'auto';
166
+ fs.writeFileSync(t.stateFile, JSON.stringify(st, null, 2) + '\n', 'utf8');
167
+ }
168
+ return st.project_kind ?? detected;
169
+ }
package/docs/commands.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Esta referencia cubre los **dos planos de operación** del arnés de construcción:
4
4
 
5
5
  1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.7.0) — instala, actualiza, diagnostica y desinstala el arnés en el proyecto consumidor. Captura el **stack mecánico**.
6
- 2. Los **slash commands de Claude Code** (`/opsx:*` + los 8 `/build:*`: `onboard`, `reflect`, `architect`, `slice`, `release`, `work`, `resume`, `front`) — operan el pipeline de dos loops, resuelven la parametrización **semántica** del dominio y capturan el conocimiento aprendido por slice.
6
+ 2. Los **slash commands de Claude Code** (`/opsx:*` + los 9 `/build:*`: `onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front`) — operan el pipeline de dos loops, resuelven la parametrización **semántica** del dominio y capturan el conocimiento aprendido por slice.
7
7
 
8
8
  > **División de responsabilidades del onboarding (dos capas).** Un binario Node **no puede** escribir la auto-memory de Claude. Por eso `trycore-build init` siembra archivos y captura el stack mecánico (lenguaje/deps, package manager, runtime, ruta del PRD), y el slash command `/build:onboard` —ejecutado por Claude— lee el PRD, pregunta por PII / capa de servicios externos-IA / capa determinista / secretos / decisiones de alto impacto, resuelve los `{{placeholders}}` del bloque marcado de `CLAUDE.md` y escribe la auto-memory.
9
9
 
@@ -52,7 +52,7 @@ Solo `init` y `update` aceptan flags. `status`, `uninstall` y `doctor` toman ún
52
52
 
53
53
  ## 2. Slash commands de Claude Code
54
54
 
55
- El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → los 8 `/build:*` (`onboard`, `reflect`, `architect`, `slice`, `release`, `work`, `resume`, `front`).
55
+ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → los 9 `/build:*` (`onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front`).
56
56
 
57
57
  ### `/opsx:*` — pipeline OpenSpec
58
58
 
@@ -117,6 +117,12 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
117
117
  |---|---|
118
118
  | `/build:front` | Abre y coordina un **front paralelo** de épicas NO fundacionales y disjuntas en archivos, cada una en su propio worktree/rama/PR. Delega en la skill `managing-parallel-front`: verifica precondiciones (scaffold confirmado, sin épica foundational abierta), selecciona el conjunto disjunto (`scripts/lib/front-plan.py`) y mergea en orden con re-smoke. Úsalo solo con ≥2 épicas no fundacionales disjuntas listas; para una sola épica, usa `/build:slice`. |
119
119
 
120
+ ### `/build:prototype` — prototipo HTML de referencia (fuente de diseño)
121
+
122
+ | Slash command | Propósito |
123
+ |---|---|
124
+ | `/build:prototype` | Genera o amplía el **prototipo HTML de referencia** (el `DESIGN_SOURCE`) en `docs/05-prototipo/` (`DESIGN.md` + `tokens.css` + `manifest.json` + un HTML autocontenido por pantalla). Adaptador delgado: **delega** en la skill `prototyping-screens`. Dos modos — **greenfield** (`/build:prototype`): inventario de pantallas desde PRD/mapa/historias → confirmación humana → dirección estética (manual de marca o 2-3 variantes a elección humana) → generación por lotes con auto-verificación visual; **feature** (`/build:prototype <épica>`): pantallas nuevas coherentes con el UX/UI **ya implementado**, con **precondición dura** (app corriendo + MCP de inspección de UI: extrae CSS computado real, screenshots en 3 viewports y estructura; sin degradación estática). Es **outer-loop** (antes de abrir slices). **La skill genera; el humano aprueba**: las pantallas nacen `borrador`, solo las `aprobada` son fuente de verdad (las lee `ux-fidelity-reviewer` vía `manifest.json`) y `design_source.confirmed` sigue siendo humano. |
125
+
120
126
  ---
121
127
 
122
128
  ## 3. Caveat de canales (CLI vs. plugin)
@@ -126,7 +132,7 @@ El arnés se distribuye por **dos canales** que coexisten, pero **namespacean di
126
132
  | Aspecto | Canal **CLI** (canónico) | Canal **Plugin** nativo |
127
133
  |---|---|---|
128
134
  | Instalación | `npm i -g @trycore/spec-build-harness` → `trycore-build init` | `/plugin marketplace add <repo-github>` → `/plugin install trycore-spec-build-harness@trycore-build` |
129
- | Namespace de comandos | Por subcarpeta: `/opsx:*` y los 8 `/build:*` (`onboard`, `reflect`, `architect`, `slice`, `release`, `work`, `resume`, `front`) | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
135
+ | Namespace de comandos | Por subcarpeta: `/opsx:*` y los 9 `/build:*` (`onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front`) | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
130
136
  | Referencia a agentes | Por su nombre (p. ej. `build-orchestrator`) | Bajo el nombre del plugin |
131
137
  | Cross-references internas | ✔ Escritas para este canal (skills invocan `/opsx:*`, agentes por nombre) | Pueden no resolver según están escritas |
132
138
  | Recomendación | **Usar este canal para operar un proyecto** | Conveniencia a nivel usuario |
@@ -48,7 +48,7 @@ npm i -g @fission-ai/openspec @trycore/spec-build-harness
48
48
 
49
49
  ## 2 · `trycore-build init` (terminal)
50
50
 
51
- Desde la raíz de tu proyecto. Es **idempotente** (re-correrlo es seguro) y siembra: **14 agentes**, **15 skills** (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture` + 10 `openspec-*`), **10 comandos `/opsx:*`** + **8 comandos `/build:*`** (`onboard`, `reflect`, `architect`, `slice`, `release`, `work`, `resume`, `front`), **13 hooks**, el estado vacío `.claude/state/build-state.json` (gitignored, nunca se sobreescribe) y el bloque `<!-- BEGIN trycore-build-harness -->` en tu `CLAUDE.md` con `{{placeholders}}` sin resolver.
51
+ Desde la raíz de tu proyecto. Es **idempotente** (re-correrlo es seguro) y siembra: **14 agentes**, **16 skills** (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens` + 10 `openspec-*`), **10 comandos `/opsx:*`** + **9 comandos `/build:*`** (`onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front`), **13 hooks**, el estado vacío `.claude/state/build-state.json` (gitignored, nunca se sobreescribe) y el bloque `<!-- BEGIN trycore-build-harness -->` en tu `CLAUDE.md` con `{{placeholders}}` sin resolver.
52
52
 
53
53
  ```bash
54
54
  trycore-build init