@trycore/spec-build-harness 0.6.0 → 0.7.1

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 (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +26 -3
  3. package/INSTALL.md +7 -7
  4. package/METODOLOGIA.md +22 -3
  5. package/README.md +8 -4
  6. package/VERSION +1 -1
  7. package/agents/build/api-contract-tester.md +8 -0
  8. package/agents/build/build-orchestrator.md +8 -2
  9. package/agents/build/change-epic-coherence.md +11 -2
  10. package/agents/build/coherence-three-way.md +12 -4
  11. package/agents/build/data-consistency-checker.md +7 -0
  12. package/agents/build/security-reviewer.md +11 -3
  13. package/agents/build/simple-design-reviewer.md +4 -3
  14. package/agents/build/stack-guardian.md +12 -4
  15. package/agents/build/ux-krug-reviewer.md +12 -3
  16. package/agents/build/wiring-adversarial-verifier.md +14 -7
  17. package/commands/build/onboard.md +18 -1
  18. package/commands/build/reflect.md +32 -8
  19. package/commands/build/release.md +84 -0
  20. package/commands/build/slice.md +93 -0
  21. package/commands/build/work.md +68 -0
  22. package/dist/lib/settings-merge.js +1 -1
  23. package/docs/agents.md +20 -13
  24. package/docs/commands.md +22 -4
  25. package/docs/customization/mcp-extensions.md +5 -4
  26. package/docs/getting-started.md +6 -5
  27. package/docs/hooks.md +13 -7
  28. package/hooks/build/build-gate-check.sh +3 -1
  29. package/hooks/build/load-build-state.sh +27 -5
  30. package/hooks/build/release-gate-nudge.sh +40 -0
  31. package/hooks/build/stack-guard.sh +30 -8
  32. package/hooks/build-harness.json +4 -0
  33. package/package.json +1 -1
  34. package/scripts/check-agnostic.sh +1 -1
  35. package/skills/building-a-slice/SKILL.md +21 -0
  36. package/skills/building-a-slice/references/dod.md +3 -1
  37. package/skills/building-a-slice/references/exploration-fanout.md +36 -0
  38. package/skills/building-a-slice/references/state-protocol.md +5 -0
  39. package/skills/building-a-slice/workflows/README.md +25 -0
  40. package/skills/building-a-slice/workflows/explore-fanout.workflow.js +77 -0
  41. package/skills/building-a-slice/workflows/wiring-verify.workflow.js +88 -0
  42. package/skills/releasing-a-version/SKILL.md +21 -0
  43. package/skills/releasing-a-version/references/release-dod.md +2 -1
  44. package/skills/releasing-a-version/workflows/README.md +19 -0
  45. package/skills/releasing-a-version/workflows/release-gate.workflow.js +104 -0
  46. package/templates/CLAUDE.md.template +6 -2
  47. package/templates/settings-hooks.template.json +1 -1
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: "BUILD: Release"
3
+ description: Punto de entrada del outer loop. Corre el Release Gate UNA vez sobre el diff acumulado de una release — los 5 reviewers pesados (security, smell, ux, coherence, stack_arch) en paralelo + integración secuencial con deps reales — delegando en la skill releasing-a-version. No duplica el inner loop (ni TDD ni gates por slice).
4
+ category: Workflow
5
+ tags: [build-harness, outer-loop, release-gate, trycore]
6
+ ---
7
+
8
+ Lanza el **outer loop**: las revisiones profundas que corren **una sola vez por release** sobre el diff
9
+ acumulado, no por épica. Adaptador delgado: **delega** en la skill `releasing-a-version` (no la reimplementa).
10
+ Si algo aquí contradice `METODOLOGIA.md`, **gana la metodología**.
11
+
12
+ **Entrada:** `release_id` (p.ej. `R1-mvp`) o, si viene vacío, infiérelo del default computado tras archivar
13
+ (ver §4 de la metodología y el nudge de `release-gate-nudge.sh`).
14
+
15
+ ---
16
+
17
+ ## 1. Preflight
18
+
19
+ ```bash
20
+ test -f .claude/.build-harness-version || echo "NOT_INSTALLED"
21
+ command -v python3 >/dev/null 2>&1 || echo "NO_PYTHON3"
22
+ ```
23
+
24
+ Stop si no está instalado o falta `python3`.
25
+
26
+ ---
27
+
28
+ ## 2. Identificar la release y sus épicas
29
+
30
+ Lee `build-state.json` y cruza con `docs/02-user-story-map/` para resolver qué épicas componen la release.
31
+ Crea/actualiza la entrada en `releases[]` con `status: pending` (lo escribe `releasing-a-version`, única
32
+ escritora de `releases[]`).
33
+
34
+ ---
35
+
36
+ ## 3. Computar el diff acumulado
37
+
38
+ El alcance es el rango de commits de **todas** las épicas de la release: desde el merge anterior a la primera
39
+ épica de la release hasta `main`.
40
+
41
+ ---
42
+
43
+ ## 4. Fan-out de reviewers pesados (en paralelo)
44
+
45
+ Dispara **en paralelo** sobre ese diff los 5 reviewers (cada uno devuelve síntesis):
46
+
47
+ | Gate | Subagente |
48
+ |---|---|
49
+ | `security` | `security-reviewer` |
50
+ | `smell` | `simple-design-reviewer` |
51
+ | `ux` (o `null` si sin UI) | `ux-krug-reviewer` |
52
+ | `coherence` | `coherence-three-way` |
53
+ | `stack_arch` | `stack-guardian` |
54
+
55
+ Opcionalmente conduce este fan-out con la plantilla `skills/releasing-a-version/workflows/release-gate.workflow.js`
56
+ (referencia). La plantilla **solo** paraleliza los 5 reviewers.
57
+
58
+ ---
59
+
60
+ ## 5. Integración SECUENCIAL (fuera del paralelo)
61
+
62
+ Corre el gate `integration` con la skill `verify`/`run` (+ MCP chrome-devtools): el **journey completo** de la
63
+ release end-to-end con **dependencias reales**, no stubs. **No** va dentro del `parallel()` ni delega en un
64
+ reviewer. Es el gate **no negociable**: sin él, no hay release.
65
+
66
+ ---
67
+
68
+ ## 6. Síntesis y escritura
69
+
70
+ - Todos los gates ✓ (o `null` cuando N/A) **y** `integration` ✓ → `releases[].status: passed`; escribe
71
+ `gates` y `updated_by: releasing-a-version` (valida contra `build-state.schema.json` antes de persistir;
72
+ una escritura por entrada). **Parciales no promueven a `passed`.**
73
+ - Algo ✗ → `status: failed` con los hallazgos bloqueantes; el humano los corrige **como un slice normal**
74
+ (`/build:slice` / `building-a-slice`) y se **re-corre** el Release Gate.
75
+
76
+ ---
77
+
78
+ ## Guardrails
79
+
80
+ - **Es outer loop.** Correr reviewers pesados **por slice** rompería el modelo de dos loops — aquí corren una
81
+ vez por release (`O(releases)`).
82
+ - **No dupliques el inner loop**: no se hace TDD ni se cierran gates por slice.
83
+ - **`integration` con deps reales es obligatorio**; no se acepta con todo stubbeado.
84
+ - Delega en `releasing-a-version`; **no** la reimplementa. Si algo contradice `METODOLOGIA.md`, gana la metodología.
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: "BUILD: Slice"
3
+ description: Punto de 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 delegando en la skill building-a-slice (o el agente build-orchestrator para épicas multicapa). Respeta el orden estricto de gates y el scaffold como precondición.
4
+ category: Workflow
5
+ tags: [build-harness, inner-loop, slice, trycore]
6
+ ---
7
+
8
+ Lanza el **inner loop** de construcción sobre una épica. Este comando es un **adaptador delgado**: no
9
+ reimplementa el pipeline — **delega** en la skill `building-a-slice` (motor del inner loop) y en `opsx:*`
10
+ (motor de changes). Si algo aquí contradice `METODOLOGIA.md`, **gana la metodología**.
11
+
12
+ **Entrada:** `EP-XXX` o una descripción de la épica. Si viene vacío, usa **AskUserQuestion** para elegir la
13
+ épica desde `docs/03-backlog/epicas.md`.
14
+
15
+ ---
16
+
17
+ ## 1. Preflight
18
+
19
+ ```bash
20
+ test -f .claude/.build-harness-version || echo "NOT_INSTALLED"
21
+ command -v python3 >/dev/null 2>&1 || echo "NO_PYTHON3"
22
+ ```
23
+
24
+ **Si `NOT_INSTALLED`:** este proyecto no tiene el arnés instalado → ejecuta `trycore-build init` y vuelve.
25
+ Stop si no está instalado o falta `python3`.
26
+
27
+ ---
28
+
29
+ ## 2. Leer el estado y decidir punto de entrada
30
+
31
+ ```bash
32
+ python3 - <<'PY'
33
+ import json, os, sys
34
+ p = ".claude/state/build-state.json"
35
+ if not os.path.exists(p): print("NO_STATE"); sys.exit(0)
36
+ try: d = json.load(open(p))
37
+ except Exception as e: print("CORRUPT_STATE", e); sys.exit(0)
38
+ s = d.get("active_slice")
39
+ if not s:
40
+ print("START dor")
41
+ else:
42
+ g = s.get("gates", {})
43
+ abierto = next((k for k, v in g.items() if v is False), None)
44
+ print(f"RESUME {s.get('epica')} fase={s.get('phase')} primer_gate_abierto={abierto}")
45
+ PY
46
+ ```
47
+
48
+ - `NO_STATE`/`CORRUPT_STATE` → reporta y detente (no escribas).
49
+ - `START dor` → no hay slice activo: arranca en **dor** con la épica objetivo.
50
+ - `RESUME …` → ya hay un slice activo: **reanuda en su primer gate abierto** (no abras otro: el modelo es
51
+ secuencial, un solo slice activo).
52
+
53
+ ---
54
+
55
+ ## 3. Precondición de scaffold (y fuente de diseño si hay UI)
56
+
57
+ Antes de escribir código de slice, verifica los gates de proyecto:
58
+
59
+ - `scaffold.confirmed` debe ser `true`. Si es `false` → **STOP**: delega en la **Fase 0** de `building-a-slice`
60
+ (pregunta explícita; el arnés **no genera** el scaffold). No abras el slice.
61
+ - Si el proyecto tiene UI, `design_source.confirmed` debe ser `true` (Fase 0-bis). Si no → **STOP** igual.
62
+
63
+ El hook `scaffold-guard.sh` respalda esto en tiempo real.
64
+
65
+ ---
66
+
67
+ ## 4. Conducir el pipeline (delegar)
68
+
69
+ Invoca la skill **`building-a-slice`** para conducir el inner loop. Para una épica **multicapa / grande**
70
+ (superó el gate de tamaño → `sub_slices[]`), invoca el agente **`build-orchestrator`** (trabaja por fases
71
+ encadenadas y, opcionalmente, conduce la exploración solo-lectura con `workflows/explore-fanout.workflow.js`).
72
+
73
+ - **No** ejecutes `opsx:apply` directamente ni saltes gates: el orden es estricto
74
+ (`dor → change → tdd → smoke → api/data → dod → pr`).
75
+ - **No** dispares reviewers pesados aquí (`security`, `smell`, `ux`, `coherence`, `stack_arch`): pertenecen
76
+ al **Release Gate** (`/build:release`). Hacerlo por slice rompería el modelo de dos loops.
77
+
78
+ ---
79
+
80
+ ## 5. Resumen
81
+
82
+ Al terminar el paso, resume: fase actual, gates cerrados/abiertos y el siguiente gate. Si la épica quedó
83
+ archivada, recuerda el default del Release Gate (ver `/build:release`).
84
+
85
+ ---
86
+
87
+ ## Guardrails
88
+
89
+ - **Un solo slice activo** (secuencial). No abras un segundo mientras haya `active_slice`.
90
+ - **Orden estricto de gates**; un gate no se salta. `dod` exige `wiring_verified: true`.
91
+ - **Sin scaffold confirmado, no hay slice** (el arnés lo exige pero no lo genera).
92
+ - **Agnóstico**: este comando no asume dominio; lo específico entra por `/build:onboard` y `stack-allowlist.json`.
93
+ - Si algo contradice `METODOLOGIA.md`, **gana la metodología**.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: "BUILD: Work"
3
+ description: Router de entrada (classify-and-act) del arnés. Clasifica el trabajo entrante y enruta a la skill correcta — building-a-micro-change (mantenimiento), building-a-slice (épica/producto nuevo) o releasing-a-version (Release Gate) — codificando los límites duros del micro-change y el default del Release Gate. Es RUTEO, no política: no ejecuta el pipeline, no toca el estado ni crea ramas.
4
+ category: Workflow
5
+ tags: [build-harness, router, classify-and-act, trycore]
6
+ ---
7
+
8
+ **Router puro.** Decide *qué carril* aplica y **delega** en la skill correspondiente. NO ejecuta el pipeline,
9
+ NO escribe `build-state.json`, NO crea ramas. Codifica como **ruteo** (no política nueva) el *decision gate*
10
+ del micro-change y el default del Release Gate de `METODOLOGIA.md` (§2, §4). Si algo contradice la metodología,
11
+ **gana la metodología**.
12
+
13
+ **Entrada:** una descripción del trabajo a hacer.
14
+
15
+ ---
16
+
17
+ ## 1. Preflight
18
+
19
+ ```bash
20
+ test -f .claude/.build-harness-version || echo "NOT_INSTALLED"
21
+ ```
22
+
23
+ Si `NOT_INSTALLED` → ejecuta `trycore-build init` y vuelve.
24
+
25
+ ---
26
+
27
+ ## 2. Clasificar (decision gate)
28
+
29
+ Aplica las reglas en orden:
30
+
31
+ 1. **¿Mantenimiento sin capacidad nueva?** — typo, ajuste de copy/config/docs, bump de dependencia **ya
32
+ permitida**, o fix de **pocas líneas** sin nueva capacidad **Y** sin ninguno de los límites duros del
33
+ paso 3 → carril **`building-a-micro-change`** (`fix/*`|`chore/*` → PR, **sin** abrir `active_slice`).
34
+ 2. **¿Producto nuevo / una épica?** — capacidad nueva, o cualquier límite duro cruzado → carril
35
+ **`building-a-slice`** (una épica `EP-XXX` = un slice = un change = una rama = un PR). Si no hay épica aún,
36
+ el trabajo vuelve a discovery para crearla.
37
+ 3. **¿Toca correr el Release Gate?** — la épica recién archivada **cierra una línea de release** del Story Map,
38
+ o hay **≥ 2 épicas archivadas** desde el último entry de `releases[]` → sugiere el carril
39
+ **`releasing-a-version`** (outer loop). (El destino es la skill existente; este router no depende de
40
+ `/build:release`.)
41
+
42
+ ---
43
+
44
+ ## 3. Límites duros del micro-change (escalan a épica)
45
+
46
+ Si el cambio **añade una dependencia nueva**, **crea un endpoint/API nuevo**, o **toca lógica de dominio o el
47
+ modelo/invariantes de datos** → **deja de ser micro-change** y se enruta a **`building-a-slice`** (épica).
48
+ **Ante la duda, SIEMPRE épica.**
49
+
50
+ ---
51
+
52
+ ## 4. Actuar (delegar) — con degradación headless
53
+
54
+ - **Con TTY**: confirma la clasificación con **una sola** `AskUserQuestion` (ofrece el carril propuesto como
55
+ primera opción "(Recomendado)") y luego invoca la skill elegida.
56
+ - **Sin TTY / headless / entrada ausente**: **no bloquees**. Clasifica determinísticamente y emite por stdout
57
+ `{clasificación, skill recomendada, criterio que disparó la rama}`. Si es **ambiguo**, aplica el **default
58
+ duro**: escalar a épica → `building-a-slice`, declarándolo explícitamente.
59
+
60
+ ---
61
+
62
+ ## Guardrails
63
+
64
+ - **Solo ruteo.** No corres el pipeline, no tocas el estado, no creas ramas: eso es de las skills destino.
65
+ - **Ante la duda, épica.** Nunca degrades un cambio con límite duro a micro-change.
66
+ - **No dupliques gates** ni saltes el orden: las skills destino los gobiernan.
67
+ - **Agnóstico**: vocabulario genérico del arnés; lo específico del dominio entra por `/build:onboard`.
68
+ - Si algo contradice `METODOLOGIA.md`, **gana la metodología**.
@@ -20,7 +20,7 @@ const HOOK_SPECS = [
20
20
  { event: 'PreToolUse', matcher: 'Bash', scripts: ['gitflow-guard.sh'] },
21
21
  { event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh', 'design-source-guard.sh'] },
22
22
  { 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'] },
23
+ { event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh', 'release-gate-nudge.sh'] },
24
24
  ];
25
25
  /** Permisos MÍNIMOS y enumerados [H11]. Nunca permisos amplios (mcp__*, additionalDirectories…). */
26
26
  const MIN_PERMISSIONS = [
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
 
package/docs/commands.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Esta referencia cubre los **dos planos de operación** del arnés de construcción:
4
4
 
5
- 1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.3.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:*` + `/build:onboard` + `/build:reflect`) — operan el pipeline de dos loops, resuelven la parametrización **semántica** del dominio y capturan el conocimiento aprendido por slice.
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 5 `/build:*`: `onboard`, `reflect`, `slice`, `release`, `work`) — 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/` → `/build:onboard` y `/build:reflect`.
55
+ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → los 5 `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`).
56
56
 
57
57
  ### `/opsx:*` — pipeline OpenSpec
58
58
 
@@ -69,6 +69,24 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
69
69
  | `/opsx:onboard` | Onboarding guiado: recorre un ciclo completo del workflow OpenSpec con narración (tutorial de aprendizaje). |
70
70
  | `/opsx:sync` | Sincroniza los delta specs de un cambio hacia los specs principales. |
71
71
 
72
+ ### `/build:slice` — entrada del inner loop
73
+
74
+ | Slash command | Propósito |
75
+ |---|---|
76
+ | `/build:slice` | Punto de entrada del **inner loop** sobre una épica (`EP-XXX`). Adaptador delgado: **delega** en la skill `building-a-slice` (o en el agente `build-orchestrator` para épicas multicapa) y conduce el pipeline `DoR → change → TDD → smoke → api/data → DoD → PR + archive` respetando el **orden estricto de gates**. Exige `scaffold.confirmed: true` (y `design_source.confirmed: true` si hay UI) como precondición; el arnés **lo exige pero no lo genera**. **Un solo slice activo** (secuencial). No dispara los reviewers pesados (eso es del Release Gate). |
77
+
78
+ ### `/build:release` — entrada del outer loop (Release Gate)
79
+
80
+ | Slash command | Propósito |
81
+ |---|---|
82
+ | `/build:release` | Punto de entrada del **outer loop**: corre el **Release Gate UNA vez** sobre el diff acumulado de una release (no por épica). Dispara **en paralelo** los 5 reviewers pesados (`security` → `security-reviewer`, `smell` → `simple-design-reviewer`, `ux` → `ux-krug-reviewer`, `coherence` → `coherence-three-way`, `stack_arch` → `stack-guardian`) y luego el gate `integration` **secuencial** con dependencias reales (no stubs). Escribe `releases[].gates.{security,smell,ux,coherence,stack_arch}` y `status` vía la skill `releasing-a-version` (única escritora de `releases[]`). Lo **sugiere** el hook `release-gate-nudge.sh` al cerrar sesión. No duplica el inner loop (ni TDD ni gates por slice). |
83
+
84
+ ### `/build:work` — router classify-and-act
85
+
86
+ | Slash command | Propósito |
87
+ |---|---|
88
+ | `/build:work` | **Router puro** (classify-and-act): clasifica el trabajo entrante y **delega** en la skill correcta — `building-a-micro-change` (mantenimiento), `building-a-slice` (épica / capacidad nueva) o `releasing-a-version` (Release Gate) — codificando el *decision gate* del micro-change y el default del Release Gate. Es **ruteo, no política**: no ejecuta el pipeline, no escribe `build-state.json` ni crea ramas. Aplica los **límites duros** del micro-change (dependencia nueva, endpoint/API nuevo, o tocar lógica de dominio / invariantes de datos → escala a épica) y, **ante la duda, SIEMPRE épica**. |
89
+
72
90
  ### `/build:onboard` — parametrización del dominio
73
91
 
74
92
  | Slash command | Propósito |
@@ -90,7 +108,7 @@ El arnés se distribuye por **dos canales** que coexisten, pero **namespacean di
90
108
  | Aspecto | Canal **CLI** (canónico) | Canal **Plugin** nativo |
91
109
  |---|---|---|
92
110
  | 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` |
93
- | Namespace de comandos | Por subcarpeta: `/opsx:*`, `/build:onboard` y `/build:reflect` | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
111
+ | Namespace de comandos | Por subcarpeta: `/opsx:*` y los 5 `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`) | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
94
112
  | Referencia a agentes | Por su nombre (p. ej. `build-orchestrator`) | Bajo el nombre del plugin |
95
113
  | Cross-references internas | ✔ Escritas para este canal (skills invocan `/opsx:*`, agentes por nombre) | Pueden no resolver según están escritas |
96
114
  | Recomendación | **Usar este canal para operar un proyecto** | Conveniencia a nivel usuario |
@@ -78,14 +78,15 @@ cada uno por el que aplique a **tu** stack declarado en el PRD; ninguno es oblig
78
78
  | `ux` (release) | `ux-krug-reviewer` | Navegador headless con auditoría tipo Lighthouse | Accesibilidad / Best-Practices y snapshots de la UI ensamblada de la release. | Revisión heurística Krug sobre el código + capturas manuales. |
79
79
  | `api` (inner) | `api-contract-tester` | **Newman corre por CLI, no es MCP** | Contratos de endpoints sobre una colección de pruebas. | Es la vía por defecto: se ejecuta vía Bash, sin MCP. |
80
80
  | `data` (inner) | `data-consistency-checker` | MCP de base de datos (solo si tu slice usa esa BD) | Consultas de lectura / describe de tablas para validar invariantes y consistencia. | Validación por tests contra un almacén embebido o cliente del stack. |
81
- | `coherence` / diseño | `coherence-three-way`, `simple-design-reviewer`, `stack-guardian` | **LSP del lenguaje** (p. ej. LSP de TypeScript en stacks TS tipados) | Seguir definiciones/referencias con precisión de compilador: trazar símbolo→test, detectar duplicación y uso real. Mejor ROI que `grep` en código tipado. | Navegación con `grep`/`glob` + lectura dirigida. |
81
+ | `coherence` / `smell` / `stack_arch` (release) | `coherence-three-way`, `simple-design-reviewer`, `stack-guardian` | **LSP del lenguaje** (p. ej. LSP de TypeScript en stacks TS tipados) | Seguir definiciones/referencias con precisión de compilador: trazar símbolo→test, detectar duplicación y uso real. Mejor ROI que `grep` en código tipado. | Navegación con `grep`/`glob` + lectura dirigida. |
82
82
  | perf (opcional, **fuera del DoD**) | — | MCP de pruebas de carga (p. ej. un MCP de k6) | Carga/latencia si una HU de la épica lo exige explícitamente. | Omitir; no es un gate del arnés. |
83
83
 
84
84
  Notas de coherencia con el arnés:
85
85
 
86
- - En el **inner loop** los gates pesados (`security`, `smell`, `ux`, coherencia triple completa,
87
- arquitectura, integración con deps reales) **no** se cierran por épica: corren **una vez por
88
- release** en `releasing-a-version`. Habilita los MCP de esas fases pensando en el outer loop.
86
+ - En el **inner loop** los gates pesados (`security`, `smell`, `ux`, `coherence` —coherencia triple
87
+ completa, distinta del `coherence_link` barato del inner—, `stack_arch` —arquitectura— e
88
+ `integration` con deps reales) **no** se cierran por épica: corren **una vez por release** en
89
+ `releasing-a-version`. Habilita los MCP de esas fases pensando en el outer loop.
89
90
  - El gate `api` usa **Newman por CLI**: es un ejemplo de que la herramienta de un gate **no tiene por
90
91
  qué ser un MCP**. Lo importante es la evidencia (los contratos responden), no el canal.
91
92
  - **Excepción única — `fidelity` en slices con UI.** Es el **único** gate donde un MCP es **requerido**,
@@ -16,9 +16,10 @@ trycore-build doctor # verifica requisitos y hooks
16
16
  ```text
17
17
  # 3) En Claude Code (lo corre Claude, no la terminal)
18
18
  /build:onboard # parametriza el dominio: lee tu PRD, resuelve los {{placeholders}}
19
- skill building-a-slice # construye una épica: DoR → change → TDD → smoke → DoD → PR
19
+ /build:slice # construye una épica: DoR → change → TDD → smoke → DoD → PR
20
20
  /build:reflect # (opcional) captura aprendizajes del slice recién archivado
21
- skill releasing-a-version # al cerrar una línea de release del Story Map
21
+ /build:release # al cerrar una línea de release del Story Map
22
+ # /build:work # (opcional) router: clasifica la tarea y la enruta (micro-change/slice/release)
22
23
  ```
23
24
 
24
25
  Eso es el ciclo completo. Lo de abajo explica cada paso.
@@ -47,7 +48,7 @@ npm i -g @fission-ai/openspec @trycore/spec-build-harness
47
48
 
48
49
  ## 2 · `trycore-build init` (terminal)
49
50
 
50
- Desde la raíz de tu proyecto. Es **idempotente** (re-correrlo es seguro) y siembra: **12 agentes**, **12 skills**, **10 comandos `/opsx:*`** + `/build:onboard` + `/build:reflect`, **9 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: **12 agentes**, **13 skills** (`building-a-slice`, `building-a-micro-change`, `releasing-a-version` + 10 `openspec-*`), **10 comandos `/opsx:*`** + **5 comandos `/build:*`** (`onboard`, `slice`, `release`, `reflect`, `work`), **10 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
52
 
52
53
  ```bash
53
54
  trycore-build init
@@ -81,7 +82,7 @@ Si un punto no aplica, se registra como "no aplica" (no se deja como `{{...}}`).
81
82
 
82
83
  ## 4 · Construir un slice (Claude Code)
83
84
 
84
- **Un slice = una épica `EP-XXX` = un OpenSpec change = una rama = un PR.** Las HU de la épica son su alcance interno. Invoca `skill building-a-slice` (o pídelo en lenguaje natural: *"construye EP-001"*). Pipeline del **inner loop**:
85
+ **Un slice = una épica `EP-XXX` = un OpenSpec change = una rama = un PR.** Las HU de la épica son su alcance interno. Invoca `/build:slice` —la entrada del **inner loop**— (o pídelo en lenguaje natural: *"construye EP-001"*; o deja que `/build:work` clasifique y enrute la tarea). Pipeline del **inner loop**:
85
86
 
86
87
  ```
87
88
  DoR → change (+ trazabilidad) → TDD → journey-smoke (+ fidelidad si hay UI) → api/data → DoD reducido → PR + archive
@@ -99,7 +100,7 @@ Lo que importa:
99
100
 
100
101
  ## 5 · Release Gate (Claude Code, por release)
101
102
 
102
- Al archivar una épica, la skill te **pregunta** si correr el Release Gate (default computado desde las líneas de release del Story Map). `skill releasing-a-version` corre las **revisiones pesadas una sola vez** sobre el diff acumulado:
103
+ Al archivar una épica, la skill te **pregunta** si correr el Release Gate (default computado desde las líneas de release del Story Map; lo respalda el hook `release-gate-nudge`, que solo **sugiere** y nunca ejecuta trabajo pesado). `/build:release` (outer loop) corre las **revisiones pesadas una sola vez** sobre el diff acumulado:
103
104
 
104
105
  | Gate | Delega en |
105
106
  |---|---|
package/docs/hooks.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Hooks del arnés de construcción
2
2
 
3
- Este documento describe los **9 hooks** que `@trycore/spec-build-harness` instala en el proyecto del consumidor. Todos viven en `hooks/build/` (un script bash por hook) y se cablean a los eventos de Claude Code mediante una **cadena de comando única**, idéntica en ambos canales de instalación.
3
+ Este documento describe los **10 hooks** que `@trycore/spec-build-harness` instala en el proyecto del consumidor. Todos viven en `hooks/build/` (un script bash por hook) y se cablean a los eventos de Claude Code mediante una **cadena de comando única**, idéntica en ambos canales de instalación.
4
4
 
5
- Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarado, el scaffold y la fuente de diseño (bloqueantes), inyectan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, y recuerdan validar trazabilidad, gates abiertos y reflexionar al cerrar un slice. No reemplazan a los agentes ni a las skills; los **complementan** liberando capacidad de razonamiento del modelo y poniendo barandillas mecánicas donde un olvido cuesta caro.
5
+ Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarado, el scaffold y la fuente de diseño (bloqueantes), inyectan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, y recuerdan validar trazabilidad, gates abiertos, reflexionar al cerrar un slice y correr el Release Gate cuando se acumulan épicas sin auditar. No reemplazan a los agentes ni a las skills; los **complementan** liberando capacidad de razonamiento del modelo y poniendo barandillas mecánicas donde un olvido cuesta caro.
6
6
 
7
7
  ---
8
8
 
9
- ## Resumen de los 9 hooks
9
+ ## Resumen de los 10 hooks
10
10
 
11
11
  | Hook | Evento | Matcher | Qué hace | ¿Bloqueante? |
12
12
  |---|---|---|---|---|
@@ -19,8 +19,9 @@ Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarad
19
19
  | `coherence-flag.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Recuerda validar la trazabilidad de un `proposal.md` de OpenSpec recién tocado. | No |
20
20
  | `build-gate-check.sh` | `Stop` | `.*` | Al cerrar el turno, avisa si el slice activo tiene gates abiertos. | No |
21
21
  | `reflect-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere `/build:reflect` si hay slice(s) archivado(s) sin reflexionar (`reflected != true`). | No |
22
+ | `release-gate-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere correr el Release Gate (`/build:release`) cuando hay ≥2 épicas archivadas sin auditar desde el último release. Determinista; solo sugiere. | No |
22
23
 
23
- > Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y cinco informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
24
+ > Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y seis informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
24
25
 
25
26
  ---
26
27
 
@@ -89,6 +90,10 @@ Cierra el **ciclo autocorrectivo**. Al terminar el turno, si en `history[]` hay
89
90
 
90
91
  Refuerza el **seguro de fuente de diseño** (espejo de `scaffold-guard`, para slices con UI). Bloquea con `exit 2` la escritura de **código de un slice con UI** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data` **y** `active_slice.gates.fidelity === false` (marcado UI-pendiente por la DoR)— mientras el proyecto tenga UI (`design_source.applies === true`) y `design_source.confirmed` no sea `true`. **Permite** todo lo demás: slices sin UI (`gates.fidelity === null` o ausente), proyectos sin UI (`applies !== true`), fases de planificación (`dor`/`change`), o fuente ya confirmada. El arnés **exige** la fuente de diseño pero **no genera** el prototipo; la confirmación es **explícita** (vía `building-a-slice` Fase 0-bis / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido. En la práctica, como `design_source` es gate de proyecto, solo muerde la **primera** construcción de UI sin fuente declarada.
91
92
 
93
+ ### 10. `release-gate-nudge.sh` — `Stop` · no bloqueante (desde v0.7.0)
94
+
95
+ Cierra el lazo del **outer loop**. Al terminar el turno, cuenta las épicas **archivadas** (`history[].epica`) que **ningún** release ha cubierto todavía (`releases[].epicas`); si quedan **≥2 épicas sin auditar**, imprime un *nudge* sugiriendo ejecutar `/build:release` (o la skill `releasing-a-version`) para correr los gates pesados **una sola vez** sobre el diff acumulado. Es puramente determinista: **aritmética de conjuntos** (archivadas − cubiertas) independiente de *timestamps*, sin consultar fechas ni el criterio de "cierre de línea de release" (eso lo computa la skill `building-a-slice` en su fase de cierre). **Nunca bloquea** el cierre, **nunca** ejecuta trabajo pesado, **nunca** llama al modelo ni escribe el estado; si falta `python3` o el estado, sale `0` en silencio (**fail-open**).
96
+
92
97
  ---
93
98
 
94
99
  ## La cadena de comando única (sin doble disparo entre canales)
@@ -113,7 +118,7 @@ Como la cadena es **carácter por carácter idéntica** en ambos canales, si el
113
118
  Los hooks parsean el JSON del evento con `python3`. Qué pasa si **falta** `python3` depende de si el hook es bloqueante:
114
119
 
115
120
  - **Bloqueantes** (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) → **fail-closed dirigido**: si no pueden analizar el comando/edición, solo bloquean (`exit 2`) cuando la entrada *cruda* parece relevante (un `git commit`/`push`, una edición que menciona `package.json`, o código de slice sin scaffold/fuente de diseño confirmados); en cualquier otro caso salen `0`. Esto evita falsos negativos peligrosos sin frenar el trabajo no relacionado. El mensaje pide instalar `python3` (verificable con `trycore-build doctor`).
116
- - **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`, `reflect-nudge`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0` (fail-open).
121
+ - **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`, `reflect-nudge`, `release-gate-nudge`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0` (fail-open).
117
122
 
118
123
  > `python3` es un **requisito duro**: `trycore-build init` y `trycore-build doctor` **fallan** si no está presente, justo porque toda la cadena de hooks depende de él para leer el JSON del evento.
119
124
 
@@ -134,6 +139,7 @@ Los hooks que tocan el código construido se **auto-arman**: permanecen **inerte
134
139
  | `design-source-guard.sh` | Permite (sin slice UI en fases de código, o sin `design_source.applies=true`, no hay nada que bloquear). |
135
140
  | `build-gate-check.sh` | Inerte (sale `0` de inmediato). |
136
141
  | `reflect-nudge.sh` | Silencioso (en `authoring` no hay slices archivados que reflexionar). |
142
+ | `release-gate-nudge.sh` | Silencioso (en `authoring` no hay épicas archivadas que auditar). |
137
143
 
138
144
  Así el arnés convive sin fricción con la fase de *discovery* y se "enciende" cuando empieza la construcción real.
139
145
 
@@ -147,7 +153,7 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
147
153
 
148
154
  `trycore-build init` hace un **merge idempotente y aditivo** en el `settings.json` del consumidor (lógica en `src/lib/settings-merge.ts`; espejo documental en `templates/settings-hooks.template.json`):
149
155
 
150
- - Agrega las 5 agrupaciones de hooks (las 9 invocaciones: `PreToolUse·Write` agrupa `stack-guard` + `scaffold-guard` + `design-source-guard`; `PostToolUse·Write` agrupa `lint-typecheck` + `coherence-flag`; `Stop` agrupa `build-gate-check` + `reflect-nudge`) sin pisar lo que ya exista.
156
+ - Agrega las 5 agrupaciones de hooks (10 invocaciones: `PreToolUse·Write` agrupa `stack-guard` + `scaffold-guard` + `design-source-guard`; `PostToolUse·Write` agrupa `lint-typecheck` + `coherence-flag`; `Stop` agrupa `build-gate-check` + `reflect-nudge` + `release-gate-nudge`) sin pisar lo que ya exista. Ambos canales (CLI y plugin) cablean las **mismas 10 invocaciones**.
151
157
  - Agrega **permisos mínimos y enumerados** (sin `mcp__*` ni rutas absolutas):
152
158
 
153
159
  ```
@@ -160,6 +166,6 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
160
166
 
161
167
  ### Canal plugin — `hooks/build-harness.json`
162
168
 
163
- El plugin declara los hooks en `hooks/build-harness.json` (referenciado desde `.claude-plugin/plugin.json` con `"hooks": "./hooks/build-harness.json"`). El contenido es **equivalente** al bloque que mergea el CLI y usa la **misma cadena de comando**.
169
+ El plugin declara los hooks en `hooks/build-harness.json` (referenciado desde `.claude-plugin/plugin.json` con `"hooks": "./hooks/build-harness.json"`) y usa la **misma cadena de comando**. Es el bloque más completo: su grupo `Stop` cablea las **3** invocaciones (`build-gate-check` + `reflect-nudge` + `release-gate-nudge`), por lo que el canal plugin suma las **10** invocaciones. La única diferencia con el merge del CLI es ese `release-gate-nudge.sh`; las otras 4 agrupaciones son idénticas. Si un consumidor instala ambos canales, las cadenas comunes se deduplican (ver arriba) y `release-gate-nudge.sh` lo aporta el plugin.
164
170
 
165
171
  > **Caveat de canales:** el canal CLI es el **canónico** para operar en un proyecto. El plugin namespacea los componentes bajo el nombre del plugin (`/trycore-spec-build-harness:*`) por diseño de Claude Code; las cross-references internas (skills que invocan `/opsx:*`, agentes por nombre) están escritas para el canal CLI. Los **hooks**, en cambio, son idénticos en ambos canales y se deduplican si coexisten.
@@ -18,7 +18,9 @@ if not s: sys.exit(0)
18
18
  g=s.get("gates",{})
19
19
  abiertos=[k for k,v in g.items() if v is False]
20
20
  if abiertos:
21
- print(f"build-gate-check: slice {s.get('hu')} en fase '{s.get('phase')}' con gates abiertos: {', '.join(abiertos)}.", file=sys.stderr)
21
+ epica=s.get('epica') or '?'
22
+ hus=', '.join(s.get('hus') or []) or '—'
23
+ print(f"build-gate-check: slice {epica} [{hus}] en fase '{s.get('phase')}' con gates abiertos: {', '.join(abiertos)}.", file=sys.stderr)
22
24
  print(" No archives ni abras PR hasta cerrarlos (ver building-a-slice / dod.md).", file=sys.stderr)
23
25
  PY
24
26
  exit 0
@@ -12,15 +12,37 @@ BRANCH="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'descon
12
12
  if [ -f "$ROOT/package.json" ]; then PHASE="active"; else PHASE="authoring"; fi
13
13
 
14
14
  # Sincroniza harness_phase en el estado (si python3 disponible y el archivo existe).
15
+ # Escritura ATÓMICA + validada: este hook corre en CADA SessionStart (alta frecuencia,
16
+ # headless incluido); una escritura no atómica que se interrumpa truncaría la ÚNICA
17
+ # fuente de verdad. Solo escribe si harness_phase cambia; si algo falla, deja el original
18
+ # intacto (fail-open) y nunca rompe el SessionStart.
15
19
  if [ -f "$STATE" ] && command -v python3 >/dev/null 2>&1; then
16
20
  python3 - "$STATE" "$PHASE" <<'PY' 2>/dev/null || true
17
- import json,sys
21
+ import json,sys,os,tempfile
18
22
  path,phase=sys.argv[1],sys.argv[2]
19
23
  try:
20
- d=json.load(open(path))
21
- if d.get("harness_phase")!=phase:
22
- d["harness_phase"]=phase
23
- json.dump(d,open(path,"w"),indent=2,ensure_ascii=False)
24
+ with open(path) as fh:
25
+ d=json.load(fh)
26
+ # No escribir si no hay cambio.
27
+ if d.get("harness_phase")==phase:
28
+ sys.exit(0)
29
+ # Validación mínima de forma antes de tocar disco (no relajamos el schema completo,
30
+ # solo evitamos persistir algo que claramente no es un build-state).
31
+ if not isinstance(d,dict) or not all(k in d for k in ("version","harness_phase","active_slice","history","releases")):
32
+ sys.exit(0)
33
+ d["harness_phase"]=phase
34
+ dirn=os.path.dirname(path) or "."
35
+ fd,tmp=tempfile.mkstemp(dir=dirn,prefix=".build-state.",suffix=".tmp")
36
+ try:
37
+ with os.fdopen(fd,"w") as out:
38
+ json.dump(d,out,indent=2,ensure_ascii=False)
39
+ out.flush()
40
+ os.fsync(out.fileno())
41
+ os.replace(tmp,path) # sustitución atómica
42
+ except Exception:
43
+ try: os.unlink(tmp)
44
+ except OSError: pass
45
+ raise
24
46
  except Exception:
25
47
  pass
26
48
  PY