@trycore/spec-build-harness 0.7.0 → 0.8.0

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 (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +3 -3
  3. package/INSTALL.md +7 -7
  4. package/METODOLOGIA.md +44 -0
  5. package/README.md +63 -5
  6. package/VERSION +1 -1
  7. package/agents/build/build-orchestrator.md +6 -0
  8. package/commands/build/front.md +15 -0
  9. package/commands/build/resume.md +29 -0
  10. package/config/build-config.template.json +8 -0
  11. package/dist/commands/init.js +15 -5
  12. package/dist/commands/status.js +1 -0
  13. package/dist/commands/uninstall.js +2 -1
  14. package/dist/lib/paths.js +7 -0
  15. package/dist/lib/settings-merge.js +29 -2
  16. package/dist/lib/state-seed.js +14 -0
  17. package/docs/agents.md +20 -13
  18. package/docs/commands.md +34 -4
  19. package/docs/customization/mcp-extensions.md +5 -4
  20. package/docs/decisiones/2026-07-03-gsd-vs-openspec-fork-vs-rama.md +157 -0
  21. package/docs/flujo-harness-funcional.md +42 -0
  22. package/docs/flujo-harness.md +192 -0
  23. package/docs/getting-started.md +6 -5
  24. package/docs/hooks.md +31 -8
  25. package/hooks/build/build-gate-check.sh +1 -1
  26. package/hooks/build/context-monitor.sh +74 -0
  27. package/hooks/build/design-source-guard.sh +1 -1
  28. package/hooks/build/lib/state-io.sh +55 -0
  29. package/hooks/build/lint-typecheck.sh +1 -1
  30. package/hooks/build/load-build-state.sh +46 -2
  31. package/hooks/build/reconcile-build-state.py +70 -0
  32. package/hooks/build/reflect-nudge.sh +1 -1
  33. package/hooks/build/release-gate-nudge.sh +1 -1
  34. package/hooks/build/scaffold-guard.sh +1 -1
  35. package/hooks/build/stack-guard.sh +1 -1
  36. package/hooks/build/statusline-bridge.sh +32 -0
  37. package/hooks/build-harness.json +24 -0
  38. package/package.json +1 -1
  39. package/scripts/lib/front-plan.py +47 -0
  40. package/scripts/smoke-test.sh +12 -0
  41. package/scripts/tests/test-context-monitor.sh +70 -0
  42. package/scripts/tests/test-front-plan.sh +38 -0
  43. package/scripts/tests/test-install.sh +89 -0
  44. package/scripts/tests/test-reconciler.sh +54 -0
  45. package/scripts/tests/test-schema.sh +58 -0
  46. package/skills/building-a-slice/references/dor.md +2 -0
  47. package/skills/managing-parallel-front/SKILL.md +36 -0
  48. package/state/build-state.schema.json +50 -0
  49. package/templates/CLAUDE.md.template +6 -2
  50. package/templates/settings-hooks.template.json +1 -1
  51. package/docs/super-power-workflows.md +0 -281
@@ -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.7.0",
5
+ "version": "0.8.0",
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
@@ -9,9 +9,9 @@ evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
9
9
  | Contexto | sección Construcción de CLAUDE.md, `openspec/project.md` | raíz / `openspec/` |
10
10
  | Estado | `build-state.json` (+schema, README) | `.claude/state/` |
11
11
  | Agentes | 12 agentes de build | `.claude/agents/build/` |
12
- | Hooks | settings.json + 10 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) | `.claude/skills/` |
14
- | Comandos | `/opsx:*` · `/build:onboard` · `/build:reflect` · `/build:slice` · `/build:release` · `/build:work` | `.claude/commands/` |
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/` |
15
15
  | Config | allowlist de stack | `.claude/config/stack-allowlist.json` |
16
16
 
17
17
  ## Fases de activación (`harness_phase`)
package/INSTALL.md CHANGED
@@ -11,7 +11,7 @@ Es el **compañero** de [`@trycore/spec-product-flow`](https://www.npmjs.com/pac
11
11
  | CLI (bin) | `trycore-build` |
12
12
  | Plugin | `trycore-spec-build-harness` |
13
13
  | Marketplace | `trycore-build` |
14
- | Versión | `0.5.0` |
14
+ | Versión | `0.7.0` |
15
15
 
16
16
  > **¿Solo quieres empezar ya?** El [Quickstart](docs/getting-started.md) te lleva de 0 a tu primer slice en pocos comandos. Esta guía es la **referencia detallada** (flags, CI, plugin, troubleshooting).
17
17
 
@@ -30,7 +30,7 @@ npm install -g @trycore/spec-build-harness
30
30
  Esto expone el binario `trycore-build`. Comprueba la versión:
31
31
 
32
32
  ```bash
33
- trycore-build --version # → 0.5.0
33
+ trycore-build --version # → 0.7.0
34
34
  trycore-build --help
35
35
  ```
36
36
 
@@ -87,9 +87,9 @@ 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/` — 12 agentes.
90
- - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`, `/build:reflect`).
91
- - `.claude/skills/` — 12 skills (`building-a-slice`, `releasing-a-version`, `openspec-*`).
92
- - `.claude/hooks/build/` — 9 hooks bash.
90
+ - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (5 comandos: `/build:onboard`, `/build:reflect`, `/build:slice`, `/build:release`, `/build:work`).
91
+ - `.claude/skills/` — 13 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `openspec-*`).
92
+ - `.claude/hooks/build/` — 10 hooks bash.
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`).
95
95
  4. **Siembra `config/stack-allowlist.json`** (artefacto del consumidor; lo puebla `/build:onboard`).
@@ -205,7 +205,7 @@ Como conveniencia a nivel usuario, el arnés también se instala como **plugin n
205
205
  ### ⚠ Caveat de canales (importante)
206
206
 
207
207
  - El **canal npm CLI es el CANÓNICO**. `trycore-build init` instala los comandos en
208
- `.claude/commands/{opsx,build}/`, que namespacean **por subcarpeta** → `/opsx:*` y `/build:onboard`,
208
+ `.claude/commands/{opsx,build}/`, que namespacean **por subcarpeta** → `/opsx:*` y `/build:*`,
209
209
  y los agentes se referencian por su **nombre** (`security-reviewer`, `stack-guardian`, …).
210
210
  - El **canal plugin nativo** namespacea **todos** los componentes bajo el **nombre del plugin**
211
211
  (→ `/trycore-spec-build-harness:*`), por diseño de Claude Code.
@@ -235,7 +235,7 @@ Ambos paquetes coexisten en el mismo `.claude/` **sin colisión**, porque usan *
235
235
 
236
236
  | | Discovery — `spec-product-flow` | Construcción — `spec-build-harness` |
237
237
  |---|---|---|
238
- | Comandos | `/trycore:*` | `/opsx:*` + `/build:onboard` + `/build:reflect` |
238
+ | Comandos | `/trycore:*` | `/opsx:*` + `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`) |
239
239
  | Marca de versión | `.trycore-version` | `.build-harness-version` |
240
240
  | Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
241
241
 
package/METODOLOGIA.md CHANGED
@@ -282,6 +282,43 @@ paralelo** y devuelven síntesis (protegen el contexto). Resultado en `releases[
282
282
 
283
283
  ---
284
284
 
285
+ ## 5-bis. Front paralelo inter-épica (outer-loop, opcional)
286
+
287
+ El inner loop (§3) construye **una épica a la vez, secuencial**. Cuando hay **≥ 2 épicas
288
+ `layer: business` listas** (DoR pasado) y **disjuntas en archivos**, el arnés permite construirlas
289
+ **en paralelo** sin romper la regla de un slice activo por árbol: el comando `/build:front`
290
+ (skill `managing-parallel-front`) abre un **`git worktree` por épica** —cada worktree con su propio
291
+ `active_slice` singular y su propio `build-state.json`; el inner loop **no cambia** dentro de cada
292
+ worktree— y coordina la selección, la construcción y el merge desde el estado raíz
293
+ (`parallel_front`). Es un concepto de **outer-loop**: no es una fase nueva del pipeline por-épica, es
294
+ orquestación **entre** épicas.
295
+
296
+ **Compuertas del front:**
297
+
298
+ 1. **Foundational-first (G1).** Ninguna épica `layer: foundational` entra jamás al front: el
299
+ cimiento se construye secuencial, antes que el negocio (§1-bis.2, §3.1). Solo épicas
300
+ `layer: business` son candidatas a paralelizarse.
301
+ 2. **Disjunción por `files_scope` (G2).** `scripts/lib/front-plan.py` calcula, a partir de los globs
302
+ declarados en `files_scope` de cada épica candidata, qué subconjunto es mutuamente disjunto
303
+ (`selected`, va al front) y cuál se solapa (`serialized`, espera y se construye después,
304
+ secuencial). Sin `files_scope` declarado, una épica no es candidata al front.
305
+ 3. **Merge en orden + re-smoke (G3).** El merge de los worktrees sigue un `merge_order`
306
+ determinista; tras **cada** merge se re-corre el `journey_smoke` completo sobre el árbol
307
+ principal (no basta con el smoke local del worktree). Un solape no capturado por la disjunción
308
+ declarada que produzca conflicto de merge lo caza este re-smoke: la épica perdedora se
309
+ **serializa** (rebase + re-correr sus gates), nunca se fuerza el merge ni se aborta el front
310
+ entero.
311
+
312
+ **Regla de drenado (drain).** Si mientras el front está activo (`parallel_front.status: "open"`)
313
+ aparece o se vuelve elegible una épica `layer: foundational`, el front **deja de admitir épicas
314
+ nuevas** y pasa a `parallel_front.status: "draining"`: los worktrees en curso terminan y mergean
315
+ normalmente, pero no se abren worktrees adicionales. Solo cuando todos los miembros llegan a
316
+ `merge_status: "merged"` (`parallel_front` se limpia a `null`) puede abrirse la épica fundacional
317
+ pendiente. El drenado existe porque **fundacional siempre precede a negocio** (§1-bis.2): el front
318
+ nunca puede ser la razón por la que el cimiento se retrasa.
319
+
320
+ ---
321
+
285
322
  ## 6. Contrato de trazabilidad change ↔ épica
286
323
 
287
324
  Cada OpenSpec change corresponde a **exactamente una épica** (la unidad de construcción) y declara
@@ -349,6 +386,9 @@ agente lo lee antes de actuar. Estructura: `version`, `harness_phase`, `active_s
349
386
  | `gates.data` | `data-consistency-checker` | slice |
350
387
  | `gates.wiring_verified` | `wiring-adversarial-verifier` (independiente, contexto virgen) | slice (antes de `dod`) |
351
388
  | `releases[]` (security, smell, ux, coherence, stack_arch, integration, status) | `releasing-a-version` (delega en los reviewers) | release |
389
+ | `parallel_front` (status, members[], merge_order) | skill `managing-parallel-front` (vía `/build:front`) | front (outer-loop, opcional) |
390
+ | `active_slice.session_continuity` | `context-monitor.sh` (auto-handoff) / `/build:resume` | por sesión (critical/PreCompact) |
391
+ | `active_slice.branch_drift`, degradación de `wiring_checklist` sin evidencia | `reconcile-build-state.py` (SessionStart / `/build:resume`) | por sesión |
352
392
  | `harness_phase` | `load-build-state.sh` (SessionStart) | — |
353
393
 
354
394
  **Régimen de fases (`harness_phase`):** arranca en `authoring`; `load-build-state.sh` lo cambia a
@@ -457,3 +497,7 @@ El arnés **no escribe** en `docs/`; cuando una HU no cumple DoR, devuelve el tr
457
497
  10. El core es **agnóstico**: lo específico del dominio se inyecta vía `/build:onboard` y
458
498
  `stack-allowlist.json`; el ejemplo de referencia vive en `docs/examples/reference/`.
459
499
  11. Si una skill, agente o reference contradice este documento, **gana la metodología**.
500
+ 12. **Front paralelo es outer-loop y drena ante fundacional (§5-bis)**: solo épicas `layer: business`
501
+ disjuntas en `files_scope` se paralelizan; una épica `layer: foundational` nunca entra al front y
502
+ lo pone en `draining` hasta que se vacía; el merge exige re-smoke del journey completo tras cada
503
+ integración.
package/README.md CHANGED
@@ -73,8 +73,64 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
73
73
  |---|---|
74
74
  | `/build:onboard` | Onboarding capa 2: lee el PRD, pregunta por PII/IA/determinismo/secretos, resuelve `{{placeholders}}` y escribe la auto-memory. |
75
75
  | `/build:reflect` | Reflexión post-slice: tras archivar, propone convenciones aprendidas / errores recurrentes al bloque `trycore-build-learnings` de `CLAUDE.md` (tras tu aprobación) y marca el slice como reflexionado. |
76
+ | `/build:slice` | Entrada del **inner loop**: abre o continúa un slice (épica `EP-XXX`) y conduce el pipeline DoR → change → TDD → smoke → api/data → DoD → PR+archive. Adaptador delgado que delega en la skill `building-a-slice`. |
77
+ | `/build:release` | Entrada del **outer loop**: corre el Release Gate **una sola vez** sobre el diff acumulado (los 5 reviewers pesados en paralelo + integración secuencial). Delega en la skill `releasing-a-version`. |
78
+ | `/build:work` | Router *classify-and-act*: clasifica el trabajo entrante y enruta al carril correcto (`building-a-micro-change` · `building-a-slice` · `releasing-a-version`). Es ruteo, no política: no ejecuta el pipeline ni toca el estado. |
79
+ | `/build:resume` | Rehidrata el slice activo **desde disco** (no desde la conversación) tras un reinicio de contexto: reconcilia el estado, lee `session_continuity`/`wiring_checklist`/`parallel_front` y determina la siguiente acción por prioridad. |
80
+ | `/build:front` | Abre y coordina un **front paralelo** de épicas no fundacionales y disjuntas en archivos (`parallel_front`), cada una en su worktree/rama/PR. Delega en la skill `managing-parallel-front`. |
76
81
  | `/opsx:*` (10) | Ciclo OpenSpec: `explore` · `new` · `continue` · `apply` · `verify` · `archive` · `bulk-archive` · `ff` · `onboard` · `sync`. Detalle → [`docs/commands.md`](docs/commands.md). |
77
82
 
83
+ ## Gestión de contexto
84
+
85
+ Las sesiones largas (una épica multicapa, un slice que se alarga) pueden agotar la ventana de
86
+ contexto antes de terminar el cableado. El arnés lo mitiga con un **motor de contexto** que corre
87
+ solo, sin que el humano lo invoque:
88
+
89
+ - **Aviso por umbrales.** El hook `context-monitor.sh` lee el contexto restante en cada
90
+ `PostToolUse`/`PreCompact`/`Stop` y lo compara contra dos umbrales **configurables**
91
+ (`context.warning_pct: 35`, `context.critical_pct: 25` en `build-config.json`): por debajo del
92
+ umbral de aviso, inyecta un recordatorio de acercarse a un punto de corte natural; por debajo del
93
+ crítico (o en `PreCompact`), dispara un **auto-handoff** —una sola vez por sesión— que escribe en
94
+ `active_slice.session_continuity` un `resume_hint` derivado de los items `wiring_checklist` aún
95
+ `failing`, sin esperar a que el humano lo pida.
96
+ - **`/build:resume`.** Para retomar sin pérdida: reconcilia el estado contra disco y determina la
97
+ siguiente acción por prioridad (`resume_hint` → primer item `failing` → siguiente `sub_slice` →
98
+ fase del pipeline). Reconstruye el contexto **desde el estado**, nunca desde la conversación viva.
99
+ - **`config context.auto_checkpoint`** (opt-in, default `false`): si se activa, el handoff marca
100
+ `auto_continue: true` para que la siguiente sesión retome automáticamente el `resume_hint` sin
101
+ preguntar. Apagado por defecto porque el checkpoint automático es una decisión que el equipo debe
102
+ optar explícitamente a tomar.
103
+ - **Caveat de canal.** El aviso proactivo depende de `statusLine`, un ajuste de `settings.json` que
104
+ **solo existe en el canal CLI** (`trycore-build init`/`update`). En una instalación **plugin-only**
105
+ no hay puente de contexto que leer y el motor **degrada fail-open**: no rompe nada, simplemente no
106
+ avisa; `/build:resume` y el reconciliador de disco (ver abajo) siguen funcionando igual porque no
107
+ dependen del puente.
108
+
109
+ El **estado sigue anclado a disco** entre sesiones: `reconcile-build-state.py` corre en cada
110
+ `SessionStart` (y en `/build:resume`) y deriva la verdad desde git y la evidencia de tests —degrada a
111
+ `failing` cualquier item `wiring_checklist` marcado `passing` sin `evidence`, anota *branch drift* si
112
+ la rama real difiere de la del slice, y nunca revierte un gate booleano por sí mismo (ratchet)—.
113
+ Fail-open siempre: un estado ilegible o `git` ausente no bloquean la sesión.
114
+
115
+ ## Front paralelo
116
+
117
+ Cuando hay **≥ 2 épicas de negocio (`layer: business`)** listas (DoR pasado) y **disjuntas en
118
+ archivos**, el comando **`/build:front`** (skill `managing-parallel-front`) las construye en
119
+ paralelo, cada una en su propio `git worktree`/rama/PR — el inner loop no cambia: cada worktree
120
+ mantiene su `active_slice` singular y su propio `build-state.json`. Tres compuertas gobiernan el
121
+ front:
122
+
123
+ 1. **Foundational-first.** Ninguna épica `layer: foundational` entra jamás al front; si una aparece
124
+ mientras el front está activo, el front pasa a `status: "draining"` (termina lo que ya empezó, no
125
+ admite épicas nuevas) hasta vaciarse.
126
+ 2. **Disjunción por `files_scope`.** `scripts/lib/front-plan.py` calcula, por conjuntos de globs
127
+ declarados en cada épica, qué candidatas son mutuamente disjuntas (`selected`) y cuáles deben
128
+ esperar (`serialized`) por solaparse.
129
+ 3. **Merge en orden + re-smoke.** El merge de los worktrees sigue un `merge_order` determinista; tras
130
+ cada merge se re-corre el `journey_smoke` completo en el árbol principal. Un conflicto no
131
+ detectado por la disjunción declarada lo caza el re-smoke: la épica perdedora se serializa
132
+ (rebase + re-correr sus gates) en vez de abortar el front.
133
+
78
134
  ## Arquitectura
79
135
 
80
136
  ```
@@ -85,11 +141,11 @@ trycore-spec-build-harness/
85
141
  ├── agents/build/ ← 12 agentes revisores (segunda opinión, contexto limpio)
86
142
  ├── commands/
87
143
  │ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
88
- │ └── build/ ← /build:onboard, /build:reflect
89
- ├── skills/ ← 12 skills (building-a-slice, releasing-a-version, 10 openspec-*)
90
- ├── hooks/build/ ← 9 hooks bash (gate-check, reflect-nudge, scaffold-guard, gitflow-guard, stack-guard, …)
144
+ │ └── build/ ← 7 comandos /build:* (onboard, reflect, slice, release, work, resume, front)
145
+ ├── skills/ ← 14 skills (building-a-slice, building-a-micro-change, releasing-a-version, managing-parallel-front, 10 openspec-*) + 3 plantillas *.workflow.js (opt-in, read-only)
146
+ ├── hooks/build/ ← 13 hooks (gate-check, reflect-nudge, release-gate-nudge, scaffold-guard, gitflow-guard, stack-guard, statusline-bridge, context-monitor, reconcile-build-state, …)
91
147
  ├── state/ ← máquina de estado: build-state.json + schema + README
92
- ├── config/ ← stack-allowlist.template.json (artefacto del consumidor)
148
+ ├── config/ ← build-config.template.json (umbrales de contexto) + stack-allowlist.template.json (artefacto del consumidor)
93
149
  ├── src/ + dist/ ← CLI trycore-build (init/update/status/uninstall/doctor)
94
150
  ├── scripts/ ← installer + guardias (check-version-sync/agnostic/state-clean)
95
151
  └── docs/examples/reference/ ← ejemplo de referencia (fuera del core, excluido de check-agnostic)
@@ -122,7 +178,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
122
178
  - ✅ **v0.3.0** — **ciclo autocorrectivo** (hook `reflect-nudge.sh` + comando `/build:reflect`: propone convenciones aprendidas al bloque `trycore-build-learnings` de `CLAUDE.md` tras tu aprobación; campos `reflected`/`reflected_at`) y **LSP opt-in** (`docs/customization/lsp-extensions.md` + sugerencia en `doctor` para stacks tipados). Total: **8 hooks**; comandos `/opsx:*` + `/build:onboard` + `/build:reflect`.
123
179
  - ✅ **v0.4.0** — carril `building-a-micro-change` (mantenimiento ligero sin slice) + DoR proporcional a la complejidad.
124
180
  - ✅ **v0.5.0** — seguro de fuente de diseño (`design_source` + `design-source-guard.sh`) + agente `ux-fidelity-reviewer` (gate `fidelity`, inner loop). Total: **11 agentes**, **9 hooks**.
125
- - ✅ **v0.6.0 (actual)** — **calidad de cierre contra horizonte largo** (feedback exodocs): handoff fino en disco (`wiring_checklist[]` + `progress_log[]` + `sub_slices[]`), gate `wiring_verified` por nuevo agente **`wiring-adversarial-verifier`** (verificación adversarial independiente, contexto virgen), cimiento pre-construido + tag `layer` y gate de tamaño en el DoR, runner fuera-de-chat `integration-check`, y **fidelidad estricta por verificación visual real** (MCP requerido para UI). Convenciones anti-deriva (producto completo, no MVP) upstreadas al bloque del arnés. Total: **12 agentes**, **9 hooks**.
181
+ - ✅ **v0.6.0** — **calidad de cierre contra horizonte largo** (feedback exodocs): handoff fino en disco (`wiring_checklist[]` + `progress_log[]` + `sub_slices[]`), gate `wiring_verified` por nuevo agente **`wiring-adversarial-verifier`** (verificación adversarial independiente, contexto virgen), cimiento pre-construido + tag `layer` y gate de tamaño en el DoR, runner fuera-de-chat `integration-check`, y **fidelidad estricta por verificación visual real** (MCP requerido para UI). Convenciones anti-deriva (producto completo, no MVP) upstreadas al bloque del arnés. Total: **12 agentes**, **9 hooks**.
182
+ - ✅ **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:*`**.
183
+ - ✅ **v0.8.0 (actual)** — **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**.
126
184
 
127
185
  ## Licencia
128
186
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
@@ -14,6 +14,12 @@ Protocolo en `.claude/state/README.md`. **Sólo un slice activo a la vez** (mode
14
14
  La **unidad de construcción es la épica** (`active_slice.epica`); las HU que cubre el change van en
15
15
  `active_slice.hus[]`. Un slice = una épica = un change = una rama = un PR.
16
16
 
17
+ ## Coexistencia con el front paralelo (outer-loop)
18
+ Si `parallel_front` existe en el estado principal, el paralelismo lo gobierna la skill
19
+ `managing-parallel-front`; cada worktree corre su propio inner loop con `active_slice` singular.
20
+ **Regla de drenado:** si se necesita abrir una épica `layer=foundational`, primero pon
21
+ `parallel_front.status="draining"` (termina las en curso, no admite nuevas) y espera a cerrarlo.
22
+
17
23
  ## Trabajo por fases encadenadas (NO one-shot)
18
24
  Una épica multicapa es demasiado para una pasada. Trabaja por **fases encadenadas** —**mapear →
19
25
  generar → revisar → fix-loop → optimizar**— nunca todo de golpe. Reparte la **exploración** "ancho
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: "BUILD: Front"
3
+ description: Abre y coordina un front paralelo de épicas NO fundacionales y disjuntas en archivos, cada una en su worktree/rama/PR. Delega en la skill managing-parallel-front (selección disjunta vía scripts/lib/front-plan.py, worktrees, merge en orden con re-smoke).
4
+ category: Workflow
5
+ tags: [build-harness, outer-loop, front-paralelo, trycore]
6
+ ---
7
+
8
+ # /build:front — Front paralelo inter-épica
9
+
10
+ Delega en la skill **managing-parallel-front**. Resumen:
11
+ 1. Verifica precondiciones (scaffold confirmado; sin épica foundational abierta).
12
+ 2. Reúne candidatas no fundacionales listas (DoR pasado) con `layer` y `files_scope`.
13
+ 3. Selecciona el conjunto disjunto (`scripts/lib/front-plan.py`), abre worktrees, construye y mergea en orden con re-smoke.
14
+
15
+ Úsalo solo cuando haya ≥2 épicas no fundacionales disjuntas listas. Para una sola épica, usa `/build:slice`.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: "BUILD: Resume"
3
+ description: Rehidrata el slice activo desde disco tras un reinicio de contexto (reconcilia estado, muestra continuidad y la siguiente acción).
4
+ category: Workflow
5
+ tags: [build-harness, resume, rehydrate, trycore]
6
+ ---
7
+
8
+ # /build:resume — Retomar sin pérdida
9
+
10
+ Objetivo: reconstruir el contexto de construcción **desde disco**, no desde la conversación.
11
+
12
+ ## Pasos
13
+
14
+ 1. **Reconciliar**: `python3 .claude/hooks/build/reconcile-build-state.py .claude/state/build-state.json`
15
+ (degrada wiring sin evidencia; anota branch drift; fail-open).
16
+
17
+ 2. **Leer estado**: `.claude/state/build-state.json`. Extraer `active_slice`, sus `gates`, `wiring_checklist`, `progress_log`, `session_continuity`, y `parallel_front` si existe.
18
+
19
+ 3. **Determinar la siguiente acción por prioridad** (la primera que aplique):
20
+ 1. `session_continuity.resume_hint` presente → ejecutarla.
21
+ 2. Items de `wiring_checklist` en `failing` → cablear el primero (con prueba real; no marcar passing sin evidencia).
22
+ 3. `sub_slices` con `status!=done` → construir el siguiente.
23
+ 4. Según `active_slice.phase` → continuar el pipeline (delegar en la skill `building-a-slice`).
24
+
25
+ 4. **Si hay `parallel_front`**: delegar en la skill `managing-parallel-front` (Task 12+).
26
+
27
+ 5. Registrar un hito en `progress_log[]` (`by: /build:resume`).
28
+
29
+ **Regla dura:** mientras quede un item `failing`, el slice NO está terminado. Nunca marques `passing` sin evidencia de ejecución real.
@@ -0,0 +1,8 @@
1
+ {
2
+ "context": {
3
+ "warning_pct": 35,
4
+ "critical_pct": 25,
5
+ "auto_checkpoint": false,
6
+ "stale_seconds": 60
7
+ }
8
+ }
@@ -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 } from '../lib/state-seed.js';
12
+ import { seedState, seedConfig, seedBuildConfig } 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';
@@ -45,9 +45,16 @@ export async function init(opts) {
45
45
  linkChildren(ASSETS.commandsOpsx, t.commandsOpsx, linkMode, (n) => n.endsWith('.md'));
46
46
  linkChildren(ASSETS.commandsBuild, t.commandsBuild, linkMode, (n) => n.endsWith('.md'));
47
47
  const nSkills = linkChildren(ASSETS.skills, t.skillsDir, linkMode);
48
- linkChildren(ASSETS.hooksBuild, t.hooksBuild, linkMode, (n) => n.endsWith('.sh'));
49
- if (linkMode === 'copy')
50
- chmodExec(t.hooksBuild); // preserva +x [H1]
48
+ // hooks/build: acepta .sh Y .py en el nivel raíz (reconcile-build-state.py es un hook v0.8).
49
+ linkChildren(ASSETS.hooksBuild, t.hooksBuild, linkMode, (n) => n.endsWith('.sh') || n.endsWith('.py'));
50
+ // hooks/build/lib: subcarpeta NO recorrida por el linkChildren de arriba — hay que recursar a mano.
51
+ linkChildren(ASSETS.hooksBuildLib, t.hooksBuildLib, linkMode, (n) => n.endsWith('.sh'));
52
+ // scripts/lib: asset nuevo consumido por /build:front (front-plan.py).
53
+ linkChildren(ASSETS.scriptsLib, t.scriptsLib, linkMode, (n) => n.endsWith('.py'));
54
+ if (linkMode === 'copy') {
55
+ chmodExec(t.hooksBuild); // preserva +x [H1] (recorre hooks/build/ incl. lib/)
56
+ chmodExec(t.scriptsLib);
57
+ }
51
58
  // 2) Estado (schema/README versionados; build-state.json vacío solo si falta) [C1]
52
59
  const { stateSeeded } = seedState(targetDir);
53
60
  // 3) stack-allowlist.json del consumidor (artefacto del consumidor)
@@ -60,6 +67,7 @@ export async function init(opts) {
60
67
  }
61
68
  }
62
69
  const { configSeeded } = seedConfig(targetDir, allowlist);
70
+ const { buildConfigSeeded } = seedBuildConfig(targetDir);
63
71
  // 4) Hooks + permisos mínimos en settings.json (canal CLI) [H3,H11]
64
72
  const { addedHooks, addedPerms } = mergeHarnessSettings(t.settingsFile);
65
73
  // 5) CLAUDE.md: bloque marcado con {{placeholders}} SIN resolver (los resuelve /build:onboard)
@@ -75,8 +83,10 @@ export async function init(opts) {
75
83
  console.log(` Comandos: ${countChildren(t.commandsOpsx)} /opsx:* + ${countChildren(t.commandsBuild)} /build:*`);
76
84
  console.log(` Skills: ${nSkills} en .claude/skills/`);
77
85
  console.log(` Hooks: ${countChildren(t.hooksBuild)} en .claude/hooks/build/ (settings.json +${addedHooks} hook(s), +${addedPerms} permiso(s))`);
86
+ console.log(` Scripts: ${countChildren(t.scriptsLib)} en .claude/scripts/lib/`);
78
87
  console.log(` Estado: ${stateSeeded ? 'build-state.json sembrado (vacío)' : 'build-state.json preservado'}`);
79
88
  console.log(` Config: ${configSeeded ? 'stack-allowlist.json sembrado' : 'stack-allowlist.json preservado'}`);
89
+ console.log(` Config: ${buildConfigSeeded ? 'build-config.json sembrado' : 'build-config.json preservado'}`);
80
90
  console.log('');
81
91
  console.log('Siguiente paso — abre Claude Code y ejecuta:');
82
92
  console.log(' /build:onboard # parametriza el dominio (PII, capa IA, capa determinista) y escribe memoria');
@@ -133,7 +143,7 @@ function syncGitignoreBlock(targetDir, mode) {
133
143
  const lines = [GI_BEGIN + ' ───────────────────────', '.claude/state/build-state.json'];
134
144
  if (mode === 'symlink') {
135
145
  // Los symlinks absolutos a node_modules global se rompen en otra máquina si se commitean.
136
- lines.push('.claude/agents/build/', '.claude/commands/opsx/', '.claude/commands/build/', '.claude/hooks/build/');
146
+ lines.push('.claude/agents/build/', '.claude/commands/opsx/', '.claude/commands/build/', '.claude/hooks/build/', '.claude/scripts/lib/');
137
147
  const skillNames = fs.existsSync(ASSETS.skills)
138
148
  ? fs.readdirSync(ASSETS.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
139
149
  : [];
@@ -27,6 +27,7 @@ export async function status(opts) {
27
27
  console.log(` Comandos: ${countChildren(t.commandsOpsx)} /opsx:* · ${countChildren(t.commandsBuild)} /build:*`);
28
28
  console.log(` Skills: ${countChildren(t.skillsDir)} en .claude/skills/`);
29
29
  console.log(` Hooks: ${countChildren(t.hooksBuild)} en .claude/hooks/build/`);
30
+ console.log(` Scripts: ${countChildren(t.scriptsLib)} en .claude/scripts/lib/`);
30
31
  console.log('');
31
32
  console.log('Requisitos externos:');
32
33
  console.log(` openspec: ${hasBinary('openspec') ? '✓' : '✗ falta (npm i -g @fission-ai/openspec)'}`);
@@ -14,7 +14,8 @@ export async function uninstall(opts) {
14
14
  }
15
15
  console.log(`▶ uninstall trycore-build-harness de ${targetDir}`);
16
16
  // 1) Assets exclusivos del arnés
17
- for (const p of [t.agentsBuild, t.commandsOpsx, t.commandsBuild, t.hooksBuild]) {
17
+ // scriptsLib (NO t.scriptsDir completo .claude/scripts/ podría llevar contenido del consumidor).
18
+ for (const p of [t.agentsBuild, t.commandsOpsx, t.commandsBuild, t.hooksBuild, t.scriptsLib]) {
18
19
  if (fs.existsSync(p))
19
20
  fs.rmSync(p, { recursive: true, force: true });
20
21
  }
package/dist/lib/paths.js CHANGED
@@ -32,12 +32,15 @@ export const ASSETS = {
32
32
  commandsBuild: path.join(PACKAGE_ROOT, 'commands', 'build'),
33
33
  skills: path.join(PACKAGE_ROOT, 'skills'),
34
34
  hooksBuild: path.join(PACKAGE_ROOT, 'hooks', 'build'),
35
+ hooksBuildLib: path.join(PACKAGE_ROOT, 'hooks', 'build', 'lib'),
36
+ scriptsLib: path.join(PACKAGE_ROOT, 'scripts', 'lib'),
35
37
  // Declaración de hooks para el canal PLUGIN (no la instala el CLI; la consume el plugin nativo).
36
38
  pluginHooks: path.join(PACKAGE_ROOT, 'hooks', 'build-harness.json'),
37
39
  stateSchema: path.join(PACKAGE_ROOT, 'state', 'build-state.schema.json'),
38
40
  stateReadme: path.join(PACKAGE_ROOT, 'state', 'README.md'),
39
41
  stateTemplate: path.join(PACKAGE_ROOT, 'state', 'build-state.template.json'),
40
42
  configTemplate: path.join(PACKAGE_ROOT, 'config', 'stack-allowlist.template.json'),
43
+ buildConfigTemplate: path.join(PACKAGE_ROOT, 'config', 'build-config.template.json'),
41
44
  templateClaudeMd: path.join(PACKAGE_ROOT, 'templates', 'CLAUDE.md.template'),
42
45
  settingsHooksTemplate: path.join(PACKAGE_ROOT, 'templates', 'settings-hooks.template.json'),
43
46
  };
@@ -51,8 +54,12 @@ export function targetPaths(targetDir) {
51
54
  commandsBuild: path.join(claudeDir, 'commands', 'build'),
52
55
  skillsDir: path.join(claudeDir, 'skills'),
53
56
  hooksBuild: path.join(claudeDir, 'hooks', 'build'),
57
+ hooksBuildLib: path.join(claudeDir, 'hooks', 'build', 'lib'),
58
+ scriptsDir: path.join(claudeDir, 'scripts'),
59
+ scriptsLib: path.join(claudeDir, 'scripts', 'lib'),
54
60
  configDir: path.join(claudeDir, 'config'),
55
61
  configFile: path.join(claudeDir, 'config', 'stack-allowlist.json'),
62
+ buildConfigFile: path.join(claudeDir, 'config', 'build-config.json'),
56
63
  stateDir: path.join(claudeDir, 'state'),
57
64
  stateSchema: path.join(claudeDir, 'state', 'build-state.schema.json'),
58
65
  stateReadme: path.join(claudeDir, 'state', 'README.md'),
@@ -14,13 +14,23 @@ const CHAIN = '${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/';
14
14
  function cmd(script) {
15
15
  return `"${CHAIN}${script}"`;
16
16
  }
17
- /** Las 5 agrupaciones de hooks del arnés (= las de hooks/build-harness.json). */
17
+ // statusLine: SOLO canal CLI los plugins no pueden inyectar statusLine.
18
+ // Misma cadena autorresolutiva que los hooks, así funciona tanto si el
19
+ // arnés se instaló por CLI como si conviven ambos canales (CLAUDE_PLUGIN_ROOT
20
+ // resuelve si el plugin está presente; si no, cae a $CLAUDE_PROJECT_DIR/.claude).
21
+ // En instalaciones plugin-only NO hay statusLine → context-monitor.sh no
22
+ // tiene puente (claude-ctx-*.json) que leer → degrada fail-open (sin aviso),
23
+ // lo cual es aceptable.
24
+ const STATUSLINE_CMD = cmd('statusline-bridge.sh');
25
+ /** Las agrupaciones de hooks del arnés (= las de hooks/build-harness.json). */
18
26
  const HOOK_SPECS = [
19
27
  { event: 'SessionStart', matcher: 'startup|clear|compact', scripts: ['load-build-state.sh'] },
20
28
  { event: 'PreToolUse', matcher: 'Bash', scripts: ['gitflow-guard.sh'] },
21
29
  { event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh', 'design-source-guard.sh'] },
22
30
  { event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['lint-typecheck.sh', 'coherence-flag.sh'] },
23
- { event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh'] },
31
+ { event: 'PostToolUse', matcher: 'Bash|Edit|Write|MultiEdit|Task', scripts: ['context-monitor.sh'] },
32
+ { event: 'PreCompact', matcher: '.*', scripts: ['context-monitor.sh'] },
33
+ { event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh', 'release-gate-nudge.sh', 'context-monitor.sh'] },
24
34
  ];
25
35
  /** Permisos MÍNIMOS y enumerados [H11]. Nunca permisos amplios (mcp__*, additionalDirectories…). */
26
36
  const MIN_PERMISSIONS = [
@@ -49,6 +59,10 @@ function readSettings(file) {
49
59
  function writeSettings(file, s) {
50
60
  fs.writeFileSync(file, JSON.stringify(s, null, 2) + '\n', 'utf8');
51
61
  }
62
+ /** True si el statusLine actual (si existe) ya es el nuestro (por script). */
63
+ function isOwnStatusLine(statusLine) {
64
+ return typeof statusLine?.command === 'string' && statusLine.command.includes('statusline-bridge.sh');
65
+ }
52
66
  function existingCommands(settings, event) {
53
67
  const out = new Set();
54
68
  for (const group of settings.hooks?.[event] ?? []) {
@@ -83,6 +97,14 @@ export function mergeHarnessSettings(settingsFile) {
83
97
  addedPerms++;
84
98
  }
85
99
  }
100
+ // statusLine: solo canal CLI (el plugin no inyecta statusLine). Escribe el
101
+ // puente de contexto. Idempotente: si ya apunta a statusline-bridge.sh no
102
+ // lo reescribe (evita diffs innecesarios); si el consumidor no tiene
103
+ // statusLine o tiene otra cosa, este paquete reclama el slot (es único,
104
+ // no se puede "mergear" como los hooks).
105
+ if (!isOwnStatusLine(settings.statusLine)) {
106
+ settings.statusLine = { type: 'command', command: STATUSLINE_CMD };
107
+ }
86
108
  writeSettings(settingsFile, settings);
87
109
  return { addedHooks, addedPerms };
88
110
  }
@@ -119,6 +141,11 @@ export function removeHarnessSettings(settingsFile) {
119
141
  changed = true;
120
142
  }
121
143
  }
144
+ // statusLine: quitar SOLO si es el nuestro (simetría con la instalación).
145
+ if (isOwnStatusLine(settings.statusLine)) {
146
+ delete settings.statusLine;
147
+ changed = true;
148
+ }
122
149
  if (changed)
123
150
  writeSettings(settingsFile, settings);
124
151
  return changed;
@@ -59,3 +59,17 @@ export function seedConfig(targetDir, allowlist) {
59
59
  void path;
60
60
  return { configSeeded: true };
61
61
  }
62
+ /**
63
+ * Siembra el build-config.json del consumidor (umbrales del context engine).
64
+ * Solo si NO existe. Idempotente: nunca pisa la config ya ajustada por el equipo.
65
+ */
66
+ export function seedBuildConfig(targetDir) {
67
+ const t = targetPaths(targetDir);
68
+ ensureDir(t.configDir);
69
+ if (fs.existsSync(t.buildConfigFile))
70
+ return { buildConfigSeeded: false };
71
+ if (!fs.existsSync(ASSETS.buildConfigTemplate))
72
+ return { buildConfigSeeded: false };
73
+ fs.copyFileSync(ASSETS.buildConfigTemplate, t.buildConfigFile);
74
+ return { buildConfigSeeded: true };
75
+ }
package/docs/agents.md CHANGED
@@ -12,7 +12,14 @@ veredicto al `build-orchestrator`**, que es quien propone la escritura del estad
12
12
  > **inner loop** (skill `building-a-slice`, por épica `EP-XXX`). Los 5 revisores pesados de
13
13
  > release (`security-reviewer`, `simple-design-reviewer`, `ux-krug-reviewer`,
14
14
  > `coherence-three-way`, `stack-guardian`) corren **una vez por release** en el **outer loop**
15
- > (skill `releasing-a-version`).
15
+ > (skill `releasing-a-version`) y escriben sus veredictos en `releases[].gates`
16
+ > (`security`, `smell`, `ux`, `coherence`, `stack_arch` — este último renombrado desde el antiguo
17
+ > `stack` por-slice).
18
+ >
19
+ > **Conducción opcional vía plantillas de workflow.** Tres plantillas read-only (opt-in, no editan
20
+ > estado) sirven de andamiaje para orquestar estos agentes sin sustituir su juicio:
21
+ > `explore-fanout.workflow.js` y `wiring-verify.workflow.js` en `building-a-slice` (inner loop), y
22
+ > `release-gate.workflow.js` en `releasing-a-version` (outer loop).
16
23
 
17
24
  ## Tabla resumen
18
25
 
@@ -23,8 +30,8 @@ veredicto al `build-orchestrator`**, que es quien propone la escritura del estad
23
30
  | 3 | `security-reviewer` | sonnet | Revisores de release | Gate `security` — seguridad enfocada al dominio |
24
31
  | 4 | `simple-design-reviewer` | sonnet | Revisores de release | Gate `smell` — 4 reglas de Beck + code smells |
25
32
  | 5 | `ux-krug-reviewer` | sonnet | Revisores de release | Gate `ux` — usabilidad (Steve Krug) |
26
- | 6 | `coherence-three-way` | **opus** | Revisores de release | Gate `coherence` — coherencia triple AC↔change↔código |
27
- | 7 | `stack-guardian` | sonnet | Revisores de release | Gate `stack` — stack y arquitectura vs. allowlist |
33
+ | 6 | `coherence-three-way` | **opus** | Revisores de release | Gate `coherence` (en `releases[].gates`) — coherencia triple AC↔change↔código |
34
+ | 7 | `stack-guardian` | sonnet | Revisores de release | Gate `stack_arch` (en `releases[].gates`) — stack y arquitectura vs. allowlist |
28
35
  | 8 | `api-contract-tester` | sonnet | Contrato / datos | Gate `api` — pruebas de contrato (Newman/Postman) |
29
36
  | 9 | `data-consistency-checker` | sonnet | Contrato / datos | Gate `data` — invariantes y consistencia de datos |
30
37
  | 10 | `change-epic-coherence` | sonnet | Trazabilidad | Gate `coherence_link` — enlace change↔épica↔HU |
@@ -53,7 +60,7 @@ frontmatter completo y `estado: lista`; AC en Given/When/Then con happy/error/ed
53
60
  dependencias declaradas; alcance dentro de la allowlist) y abre el `active_slice` si pasa. A
54
61
  la salida valida la **Definition of Done reducida por slice** (gate `dod`): `tdd`,
55
62
  `journey_smoke`, `coherence_link`, `data`, `api`, documentación con back-refs y hooks verdes.
56
- **No valida aquí** `security`, `smell`, `ux`, `coherence` ni `stack`: esos son del Release Gate.
63
+ **No valida aquí** `security`, `smell`, `ux`, `coherence` ni `stack_arch`: esos son del Release Gate (viven en `releases[].gates`).
57
64
 
58
65
  ## Revisores de release
59
66
 
@@ -69,39 +76,39 @@ los **especializa al dominio declarado por el consumidor**: secretos de servicio
69
76
  solo server-side, datos sensibles/PII regulados no persistidos crudos, validación de archivos
70
77
  y entrada, salida de servicios externos/IA tratada como input no confiable, logs sin PII y
71
78
  decisiones auditables sin sobre-exposición. Veredicto por severidad
72
- (CRÍTICO/ALTO/MEDIO/BAJO); sin CRÍTICO/ALTO → `gates.security: true`.
79
+ (CRÍTICO/ALTO/MEDIO/BAJO); sin CRÍTICO/ALTO → `releases[].gates.security: true`.
73
80
 
74
81
  ### `simple-design-reviewer` · modelo `sonnet`
75
82
  **Revisor de diseño simple y code smells** (gate `smell`), sobre código ya en verde. Aplica
76
83
  las 4 reglas de Kent Beck (pasa los tests → revela la intención → sin duplicación → mínimos
77
84
  elementos) y caza un catálogo de smells (funciones largas, clases "Dios", números mágicos,
78
85
  duplicación de validación, props drilling, código muerto, `any`, acoplamiento a servicios
79
- externos). Hallazgos BLOQUEANTE/RECOMENDADO/NIT; sin bloqueantes → `gates.smell: true`.
86
+ externos). Hallazgos BLOQUEANTE/RECOMENDADO/NIT; sin bloqueantes → `releases[].gates.smell: true`.
80
87
 
81
88
  ### `ux-krug-reviewer` · modelo `sonnet` · lee el dominio
82
- **Revisor de usabilidad** según Steve Krug (gate `ux`); aplica solo a slices con UI (sin UI →
83
- `gates.ux: null`). Verifica "don't make me think", jerarquía visual, convenciones,
89
+ **Revisor de usabilidad** según Steve Krug (gate `releases[].gates.ux`); aplica solo a releases con UI (sin UI →
90
+ `releases[].gates.ux: null`). Verifica "don't make me think", jerarquía visual, convenciones,
84
91
  escaneabilidad, affordances, tolerancia al error (claridad de las decisiones de alto impacto y
85
92
  su justificación según el dominio) y accesibilidad básica. Revisión estática y, si la app
86
93
  corre, dinámica vía MCP `chrome-devtools` (`take_snapshot`, `lighthouse_audit`). Sin
87
- bloqueantes → `gates.ux: true`.
94
+ bloqueantes → `releases[].gates.ux: true`.
88
95
 
89
96
  ### `coherence-three-way` · modelo `opus` · lee el dominio
90
- **Auditor de coherencia triple** (gate `coherence`). Usa el modelo más capaz porque razona a
97
+ **Auditor de coherencia triple** (gate `releases[].gates.coherence`). Usa el modelo más capaz porque razona a
91
98
  la vez sobre tres documentos: los **AC (G/W/T)** de las HU de la épica ↔ el **OpenSpec change**
92
99
  (`specs/` + `tasks.md`) ↔ el **código y tests** implementados. Hace comprobaciones top-down
93
100
  (cada requisito tiene implementación) y bottom-up (nada huérfano: ni tests sin propósito ni
94
101
  código fuera del alcance de `hus[]`). Produce una matriz de trazabilidad; COHERENTE →
95
- `gates.coherence: true`. Complementa a `change-epic-coherence` verificando la implementación real.
102
+ `releases[].gates.coherence: true`. Complementa a `change-epic-coherence` verificando la implementación real.
96
103
 
97
104
  ### `stack-guardian` · modelo `sonnet` · lee el dominio
98
- **Guardián del stack** (gate `stack`, arquitectura). Defiende la sección de requisitos
105
+ **Guardián del stack** (gate `releases[].gates.stack_arch`, arquitectura; antes `gates.stack` por-slice). Defiende la sección de requisitos
99
106
  técnicos del PRD del consumidor (ruta en `stack-allowlist.json#source`), operacionalizada en
100
107
  `.claude/config/stack-allowlist.json`. Verifica que las **dependencias** matcheen la
101
108
  allowlist y que la **arquitectura** respete el stack declarado (frontend/runtime del
102
109
  consumidor; servicio externo/IA usado solo en su frontera server-side; capa de decisión del
103
110
  dominio determinista sin servicio no determinista cuando el PRD lo exige; persistencia sin PII
104
- regulada cruda) y señala anti-patrones. STACK-OK → `gates.stack: true`. El hook
111
+ regulada cruda) y señala anti-patrones. STACK-OK → `releases[].gates.stack_arch: true`. El hook
105
112
  `stack-guard.sh` bloquea deps fuera de lista en tiempo real; este agente razona sobre
106
113
  arquitectura y uso.
107
114