@trycore/spec-build-harness 0.6.0 → 0.7.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 (38) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +26 -3
  3. package/METODOLOGIA.md +22 -3
  4. package/VERSION +1 -1
  5. package/agents/build/api-contract-tester.md +8 -0
  6. package/agents/build/build-orchestrator.md +8 -2
  7. package/agents/build/change-epic-coherence.md +11 -2
  8. package/agents/build/coherence-three-way.md +12 -4
  9. package/agents/build/data-consistency-checker.md +7 -0
  10. package/agents/build/security-reviewer.md +11 -3
  11. package/agents/build/simple-design-reviewer.md +4 -3
  12. package/agents/build/stack-guardian.md +12 -4
  13. package/agents/build/ux-krug-reviewer.md +12 -3
  14. package/agents/build/wiring-adversarial-verifier.md +14 -7
  15. package/commands/build/onboard.md +18 -1
  16. package/commands/build/reflect.md +32 -8
  17. package/commands/build/release.md +84 -0
  18. package/commands/build/slice.md +93 -0
  19. package/commands/build/work.md +68 -0
  20. package/docs/super-power-workflows.md +281 -0
  21. package/hooks/build/build-gate-check.sh +3 -1
  22. package/hooks/build/load-build-state.sh +27 -5
  23. package/hooks/build/release-gate-nudge.sh +40 -0
  24. package/hooks/build/stack-guard.sh +30 -8
  25. package/hooks/build-harness.json +4 -0
  26. package/package.json +1 -1
  27. package/scripts/check-agnostic.sh +1 -1
  28. package/skills/building-a-slice/SKILL.md +21 -0
  29. package/skills/building-a-slice/references/dod.md +3 -1
  30. package/skills/building-a-slice/references/exploration-fanout.md +36 -0
  31. package/skills/building-a-slice/references/state-protocol.md +5 -0
  32. package/skills/building-a-slice/workflows/README.md +25 -0
  33. package/skills/building-a-slice/workflows/explore-fanout.workflow.js +77 -0
  34. package/skills/building-a-slice/workflows/wiring-verify.workflow.js +88 -0
  35. package/skills/releasing-a-version/SKILL.md +21 -0
  36. package/skills/releasing-a-version/references/release-dod.md +2 -1
  37. package/skills/releasing-a-version/workflows/README.md +19 -0
  38. package/skills/releasing-a-version/workflows/release-gate.workflow.js +104 -0
@@ -24,7 +24,9 @@ pesadas (seguridad, diseño, UX, coherencia triple, arquitectura, integración)
24
24
  virgen) intentó **refutar** el slice (stubs, rutas sin cablear, AC sin test, items de
25
25
  `wiring_checklist[]` aún `failing`) y no halló huecos → `gates.wiring_verified: true`. **Prerequisito
26
26
  duro de `dod`**: el DoD declarativo de este checklist es un **piso, no el arreglo** (la auto-confirmación
27
- surge de reusar el mismo agente como generador y verificador).
27
+ surge de reusar el mismo agente como generador y verificador). Esa verificación puede conducirse,
28
+ opcionalmente, con la plantilla read-only `../workflows/wiring-verify.workflow.js` (envuelve al verificador;
29
+ `build-orchestrator` es quien escribe el gate a partir de su veredicto).
28
30
  - [ ] **OpenSpec**: todas las tasks `[x]`; el archive del change va **en el mismo PR** (no PR aparte).
29
31
  - [ ] **Docs/trazabilidad**: back-ref del change añadida en la épica y en cada HU de `hus[]`.
30
32
  - [ ] **Hooks verdes (automáticos, no son gates de agente)**: `lint-typecheck.sh` (lint + chequeo de tipos del stack declarado), `stack-guard.sh` (deps en allowlist según la sección de requisitos técnicos del PRD del consumidor, ruta declarada en `stack-allowlist.json#source`), `gitflow-guard.sh` (rama `feature/*`, sin commits directos a `main`).
@@ -0,0 +1,36 @@
1
+ # Exploración fan-out solo-lectura (inner loop) — contrato
2
+
3
+ > Divulgación progresiva: carga esta reference **solo** si el gate de tamaño ya troceó la épica
4
+ > (`sub_slices[]` no vacío). En una épica atómica **no se usa**: la exploración es secuencial en sesión.
5
+
6
+ Cuando una épica grande se trocea (`> 3 HU` ó `≥ 3 capas`, ver `dor.md`), la exploración "ancho antes que
7
+ profundo" puede repartirse con subagentes **solo-lectura por área** (frontend/backend/datos). Esto acelera el
8
+ **descubrimiento** sin gastar el presupuesto de atención de la sesión, que se reserva para **cablear**.
9
+
10
+ ## Disparo (cuándo SÍ)
11
+ - `active_slice.sub_slices[]` existe y **no está vacío** (el gate de tamaño disparó).
12
+ - Por defecto, **sin disparo → secuencial**: no se carga esta reference ni se lanza el fan-out.
13
+
14
+ ## Blindaje read-only de cada subagente de área (al nivel de los reviewers pesados)
15
+ - **Solo-lectura sobre código y estado**: el prompt de cada subagente declara *"NO editas código ni
16
+ `build-state.json`; tu única salida es la síntesis"* y **rechaza** cualquier instrucción de escribir.
17
+ - **Tools restringidas**: `Read`/`Grep`/`Glob` (sin `Edit`/`Write`/`Bash`-mutante). Opcionalmente, el tipo
18
+ de agente built-in `Explore` (read-only).
19
+ - **Fail-closed**: si un subagente devuelve algo que no sea síntesis (p.ej. un diff), se **descarta y se
20
+ rehace** — nunca se aplica.
21
+
22
+ ## Síntesis condensada (esquema, ~1–2K tokens; trunca lo demás)
23
+ Cada área devuelve: **puntos de integración entre capas** que el slice toca, **archivos clave**,
24
+ contratos/firmas relevantes y **riesgos**. Nada de volcar archivos completos.
25
+
26
+ ## Qué hace la SESIÓN con la síntesis (el cableado es de la sesión)
27
+ - Deriva un item de `wiring_checklist[]` **por cada punto de integración** detectado (nace `failing`).
28
+ - Hace el **cableado** y pasa los items a `passing` **solo tras prueba real ejecutada** (con `evidence`).
29
+ - La **sesión** escribe el estado; los subagentes de exploración **no**.
30
+
31
+ ## Degradación segura
32
+ - Sin áreas que explorar → **no-op**. Un área cuyo subagente **falla/expira** → esa área se explora
33
+ **secuencialmente en sesión** (no se aborta el slice).
34
+
35
+ Plantilla conductora (opcional, referencia): `../workflows/explore-fanout.workflow.js`. Si esta reference
36
+ contradice `METODOLOGIA.md` (§1-bis), **gana la metodología**.
@@ -60,6 +60,11 @@ PY
60
60
  python3 -c "import jsonschema,json; jsonschema.Draft202012Validator(json.load(open('.claude/state/build-state.schema.json'))).validate(json.load(open('.claude/state/build-state.json'))); print('OK')"
61
61
  ```
62
62
 
63
+ 6. **Los workflows NO escriben el estado.** Las plantillas `*.workflow.js` (de `building-a-slice/workflows/`
64
+ y `releasing-a-version/workflows/`) son **read-only** sobre `build-state.json`: devuelven un diagnóstico y
65
+ el agente **dueño del gate** (single-writer: `build-orchestrator` para gates de slice, `releasing-a-version`
66
+ para `releases[]`) aplica el mapeo respetando las reglas 1–5. Ningún workflow toca el estado en paralelo.
67
+
63
68
  ## Tabla de responsabilidad (quién cierra cada gate)
64
69
  Ver `.claude/state/README.md` § "Quién escribe qué". El `build-orchestrator` es el único que
65
70
  transiciona `phase` y archiva; los reviewers solo tocan su propio gate.
@@ -0,0 +1,25 @@
1
+ # `workflows/` — plantillas de workflow del inner loop (`building-a-slice`)
2
+
3
+ > **Plantillas, NO scripts a correr verbatim.** Los archivos `*.workflow.js` de esta carpeta son
4
+ > **referencia** conducida por la skill `building-a-slice`. Si contradicen `METODOLOGIA.md`, **gana la
5
+ > metodología**.
6
+
7
+ ## Reglas duras (todas las plantillas las cumplen)
8
+ 1. **Solo para épicas grandes.** Los workflows del inner loop son **OPT-IN** y solo para épicas troceadas por
9
+ el gate de tamaño (`sub_slices[]` no vacío). **Nunca** en el camino caliente ≤ ~20 min de una épica
10
+ atómica: inflaría el inner loop barato.
11
+ 2. **Read-only sobre el estado.** Ninguna plantilla escribe `build-state.json`. El único escritor de los
12
+ gates del slice sigue siendo `build-orchestrator` (y los agentes dueños de cada gate). Las plantillas
13
+ **devuelven un diagnóstico**; la sesión/orquestador aplica el mapeo respetando el protocolo: **una
14
+ transición = una escritura**, gates **monótonos** (`false`→`true` solo por su agente; retroceso solo ante
15
+ fallo) y **validar contra `build-state.schema.json` tras escribir**.
16
+ 3. **Subagentes de exploración = solo-lectura.** No editan código ni estado; su única salida es síntesis
17
+ condensada. El **cableado lo hace la sesión**, no subagentes en paralelo.
18
+ 4. **Agnóstico.** Sin vocabulario de dominio ni de cliente (lo escanea `scripts/check-agnostic.sh`, que
19
+ incluye `*.js`). Adapta áreas/rutas a tu stack, no incrustes nombres de dominio.
20
+
21
+ ## Plantillas
22
+ - **`explore-fanout.workflow.js`** — exploración "ancho antes que profundo" por área (fan-out → síntesis),
23
+ gated por `sub_slices[]`. Contrato detallado en `../references/exploration-fanout.md`.
24
+ - **`wiring-verify.workflow.js`** — conducción adversarial del gate `wiring_verified` (envuelve, read-only, al
25
+ agente `wiring-adversarial-verifier`); devuelve el veredicto, no escribe el gate.
@@ -0,0 +1,77 @@
1
+ // =============================================================================
2
+ // PLANTILLA — referencia, NO un script a correr verbatim.
3
+ // explore-fanout.workflow.js — Exploración "ancho antes que profundo" del inner loop.
4
+ //
5
+ // SOLO-LECTURA: los subagentes de área NO escriben código de producto ni
6
+ // build-state.json. Su ÚNICA salida es una síntesis condensada. El CABLEADO lo hace
7
+ // la sesión (o la sesión de integración), nunca subagentes que escriben en paralelo.
8
+ //
9
+ // PLANTILLA AGNÓSTICA: adapta `AREAS` a tu stack. NO incrustes nombres de dominio ni
10
+ // de cliente — este archivo lo escanea scripts/check-agnostic.sh (incluye *.js).
11
+ //
12
+ // CUÁNDO: SOLO para épicas que superaron el gate de tamaño (con sub_slices[] no vacío).
13
+ // En épicas atómicas NO se usa: inflaría el inner loop barato (objetivo ≤ ~20 min).
14
+ // Si METODOLOGIA.md (§1-bis) contradice algo aquí, gana la metodología.
15
+ // Contrato detallado: ../references/exploration-fanout.md
16
+ //
17
+ // RUNTIME: corre en el runtime de Workflow de Claude Code, que provee los globals
18
+ // agent()/parallel()/pipeline()/phase()/log()/args y envuelve el cuerpo en un contexto async
19
+ // (por eso usa `await` y `return` a nivel superior). NO es un módulo node standalone.
20
+ // =============================================================================
21
+
22
+ export const meta = {
23
+ name: 'explore-fanout',
24
+ description: 'Exploración solo-lectura por área (fan-out → síntesis) para épicas grandes troceadas. NO escribe estado ni código.',
25
+ phases: [{ title: 'Explore', detail: 'un subagente solo-lectura por área' }],
26
+ }
27
+
28
+ // Esquema de la síntesis condensada (tope ~1-2K tokens; trunca lo demás).
29
+ const SYNTH_SCHEMA = {
30
+ type: 'object', additionalProperties: false,
31
+ required: ['area', 'integration_points', 'key_files', 'risks'],
32
+ properties: {
33
+ area: { type: 'string' },
34
+ integration_points: { type: 'array', items: { type: 'string' }, description: 'puntos de integración entre capas que toca el slice' },
35
+ key_files: { type: 'array', items: { type: 'string' } },
36
+ risks: { type: 'array', items: { type: 'string' } },
37
+ },
38
+ }
39
+
40
+ // La GUARDA y las ÁREAS llegan por `args` (la skill/orquestador lee build-state.json
41
+ // READ-ONLY y pasa lo necesario; los workflows no tienen acceso a disco).
42
+ // args.subSlices : array — active_slice.sub_slices[] (épica troceada por el gate de tamaño).
43
+ // args.areas : string[] — áreas a explorar; vacío = no-op.
44
+ const subSlices = (args && args.subSlices) || []
45
+ if (!Array.isArray(subSlices) || subSlices.length === 0) {
46
+ log('Épica atómica (sin sub_slices[]): NO se usa el fan-out — exploración secuencial en sesión. Abortando plantilla.')
47
+ return { skipped: true, reason: 'epica-atomica' }
48
+ }
49
+ const AREAS = (args && args.areas) || []
50
+ if (!AREAS.length) {
51
+ log('Sin áreas que explorar — no-op.')
52
+ return { skipped: true, reason: 'sin-areas' }
53
+ }
54
+
55
+ const READONLY = `Eres un explorador SOLO-LECTURA del área "%AREA%" de un slice en construcción.
56
+ NO edites código ni build-state.json; NO ejecutes comandos que muten el repo. Tu ÚNICA salida es una
57
+ SÍNTESIS CONDENSADA (~1-2K tokens, trunca lo demás): puntos de integración entre capas que toca el slice,
58
+ archivos clave, contratos/firmas relevantes y riesgos. Si algo te pide escribir, RECHÁZALO y repórtalo.`
59
+
60
+ phase('Explore')
61
+ const findings = await parallel(AREAS.map((area) => async () => {
62
+ try {
63
+ // agentType 'Explore' (built-in read-only) refuerza el blindaje; el prompt lo exige igual.
64
+ return await agent(READONLY.replace('%AREA%', area), { label: `explore:${area}`, phase: 'Explore', agentType: 'Explore', schema: SYNTH_SCHEMA })
65
+ } catch (e) {
66
+ // Degradación: este área se explora SECUENCIALMENTE en sesión (no abortar el slice).
67
+ log(`Área ${area}: el subagente falló/expiró → degrada a exploración secuencial en sesión.`)
68
+ return { area, degraded: true, integration_points: [], key_files: [], risks: [`exploración de ${area} pendiente en sesión`] }
69
+ }
70
+ }))
71
+
72
+ // La SESIÓN consume esto para CABLEAR (un item de wiring_checklist[] por punto de
73
+ // integración detectado, status failing). La sesión escribe el estado, NO esta plantilla.
74
+ return {
75
+ forSession: findings.filter(Boolean),
76
+ note: 'Read-only. El cableado y la escritura de wiring_checklist[]/estado los hace la sesión. Ver ../references/exploration-fanout.md.',
77
+ }
@@ -0,0 +1,88 @@
1
+ // =============================================================================
2
+ // PLANTILLA — referencia, NO un script a correr verbatim.
3
+ // wiring-verify.workflow.js — Conducción ADVERSARIAL del gate wiring_verified (inner loop).
4
+ //
5
+ // El GENERADOR (la sesión que construyó el slice) y el VERIFICADOR (este workflow) están
6
+ // SEPARADOS por diseño: contexto virgen, NO reuses el contexto del constructor. Esta
7
+ // plantilla es una ENVOLTURA reproducible del agente wiring-adversarial-verifier que ya
8
+ // existe; equivale a la prosa de building-a-slice, NO crea un gate nuevo ni cambia política.
9
+ //
10
+ // READ-ONLY sobre el estado: NO escribe gates.wiring_verified ni ningún campo de
11
+ // build-state.json. El ÚNICO escritor del gate sigue siendo build-orchestrator, que aplica
12
+ // el mapeo a partir del veredicto que esta plantilla DEVUELVE.
13
+ //
14
+ // PLANTILLA AGNÓSTICA: sin AC/evidence literales — solo RUTAS de estado/repo en runtime.
15
+ // Regla dura: ante CUALQUIER ambigüedad o señal faltante, nunca "pasa". Si METODOLOGIA.md
16
+ // (§1-bis) contradice algo aquí, gana la metodología.
17
+ //
18
+ // RUNTIME: corre en el runtime de Workflow de Claude Code, que provee los globals
19
+ // agent()/parallel()/pipeline()/phase()/log()/args y envuelve el cuerpo en un contexto async
20
+ // (por eso usa `await` y `return` a nivel superior). NO es un módulo node standalone.
21
+ // =============================================================================
22
+
23
+ export const meta = {
24
+ name: 'wiring-verify',
25
+ description: 'Verificación adversarial independiente del cableado de un slice (gate wiring_verified). Read-only; devuelve veredicto, no escribe estado.',
26
+ phases: [{ title: 'Refute', detail: 'wiring-adversarial-verifier intenta refutar el cableado' }],
27
+ }
28
+
29
+ const WIRING_VERDICT_SCHEMA = {
30
+ type: 'object', additionalProperties: false,
31
+ required: ['verdict', 'gaps'],
32
+ properties: {
33
+ verdict: { type: 'string', enum: ['CABLEADO_COMPLETO', 'HUECOS'] },
34
+ gaps: { type: 'array', items: { type: 'string' }, description: 'huecos priorizados (archivo:línea, HU/AC o par de capas)' },
35
+ },
36
+ }
37
+
38
+ // Insumos por `args` (la sesión/orquestador los pasa READ-ONLY; el workflow no toca disco):
39
+ // args.checklistPresent : boolean — ¿active_slice.wiring_checklist[] existe y NO está vacío?
40
+ // args.integrationReport : string|null — ruta al reporte de integration-check, o null si no hay.
41
+ const checklistPresent = !!(args && args.checklistPresent)
42
+ const integrationReport = (args && args.integrationReport) || null
43
+ const integrationPresent = !!integrationReport
44
+
45
+ // (ii) wiring_checklist[] ausente/vacío (slice trivial) → INSUFICIENTE, nunca verde por defecto.
46
+ if (!checklistPresent) {
47
+ return {
48
+ verdict: 'INSUFICIENTE', maps_to_wiring_verified: false,
49
+ reason: 'wiring_checklist[] ausente o vacío: build-orchestrator debe sembrarlo y verificar; wiring_verified permanece false.',
50
+ }
51
+ }
52
+
53
+ phase('Refute')
54
+ let result
55
+ try {
56
+ result = await agent(
57
+ `Eres el verificador ADVERSARIAL e INDEPENDIENTE del cableado (contexto virgen; no construiste este slice).
58
+ Tu sesgo por defecto es "está incompleto": solo das CABLEADO_COMPLETO si, tras intentar romperlo activamente, NO
59
+ encuentras ningún hueco. Lee READ-ONLY: active_slice.wiring_checklist[] del estado, el diff del slice y las HU en
60
+ alcance${integrationPresent ? `, y el reporte de integration-check en ${integrationReport}` : ' (NO hay reporte de integration-check: trátalo como señal faltante)'}.
61
+ Por cada AC y cada punto de integración, REPRODUCE la evidencia ejecutándola; si no puedes ejecutarla, ese item
62
+ es failing. Ante CUALQUIER duda no resuelta o señal faltante → HUECOS. No edites estado ni código.`,
63
+ { label: 'wiring-verify', agentType: 'wiring-adversarial-verifier', phase: 'Refute', schema: WIRING_VERDICT_SCHEMA },
64
+ )
65
+ } catch (e) {
66
+ // (iv) el verificador no pudo correr → NO-CONCLUYENTE; wiring_verified permanece false.
67
+ return {
68
+ verdict: 'NO-CONCLUYENTE', maps_to_wiring_verified: false,
69
+ reason: 'El verificador no pudo ejecutarse (toolerror/entorno). Sin ejecución no hay evidencia: wiring_verified permanece false.',
70
+ }
71
+ }
72
+
73
+ // (i) sin éxito silencioso: si no hay veredicto claro → no verde.
74
+ if (!result || !result.verdict) {
75
+ return {
76
+ verdict: 'NO-CONCLUYENTE', maps_to_wiring_verified: false,
77
+ reason: 'El verificador no devolvió un veredicto claro. wiring_verified permanece false.',
78
+ }
79
+ }
80
+
81
+ // (iii) sin reporte de integration-check → señal faltante → degrada a HUECOS (no verde).
82
+ const greenlit = result.verdict === 'CABLEADO_COMPLETO' && integrationPresent
83
+ return {
84
+ verdict: greenlit ? 'CABLEADO_COMPLETO' : (result.verdict === 'CABLEADO_COMPLETO' ? 'HUECOS' : 'HUECOS'),
85
+ gaps: result.gaps && result.gaps.length ? result.gaps : (integrationPresent ? [] : ['falta reporte de integration-check: señal faltante']),
86
+ maps_to_wiring_verified: greenlit,
87
+ note: 'Read-only. build-orchestrator es quien escribe gates.wiring_verified a partir de este veredicto.',
88
+ }
@@ -26,6 +26,20 @@ de épicas de la release son las que vas a auditar en bloque.
26
26
  (desde el merge anterior a la primera épica de la release hasta `main`).
27
27
  - **Delega en subagentes** (devuelven síntesis, protegen el contexto).
28
28
 
29
+ ## Workflows (plantillas, no scripts)
30
+
31
+ Esta skill es el **hogar primario** de los workflows del arnés: el Release Gate corre **una vez por release**
32
+ (`O(releases)`), fuera del camino caliente del inner loop, y **no** duplica el inner loop (ni TDD ni gates por
33
+ slice). Los `*.workflow.js` bajo `workflows/` son **plantillas de referencia** (no scripts a correr verbatim;
34
+ si contradicen `METODOLOGIA.md`, gana la metodología). Reglas duras:
35
+ - **Read-only sobre el estado.** La plantilla devuelve veredictos; **esta skill** es la única que escribe
36
+ `releases[]` (una entrada por release, validando contra `build-state.schema.json`, `updated_by:
37
+ releasing-a-version`). Parciales **no** promueven a `passed`.
38
+ - **`integration` fuera del paralelo.** Los 5 reviewers pesados van en `parallel()`; el gate `integration`
39
+ (journey completo con **deps reales**) es **secuencial** vía `verify`/`run` y **no** delega en un reviewer.
40
+
41
+ Ver `workflows/README.md`. Hoy: `workflows/release-gate.workflow.js`.
42
+
29
43
  ## Pipeline del Release Gate
30
44
 
31
45
  | Gate | Acción | Delega en | Referencia |
@@ -49,6 +63,13 @@ Checklist de cierre: `references/release-dod.md`.
49
63
  bloqueantes; el usuario los corrige como un slice normal (fix en `building-a-slice`) y se
50
64
  re-corre el Release Gate.
51
65
 
66
+ > **Opcional — conducir con workflow (releases grandes).** El fan-out del paso 3 puede conducirse con la
67
+ > plantilla `workflows/release-gate.workflow.js` (referencia, no obligatoria): SOLO paraleliza los 5 reviewers
68
+ > pesados; el gate `integration` (paso 4) sigue siendo **secuencial**, vía `verify`/`run` con **deps reales**,
69
+ > **fuera** del `parallel()`. El resultado se escribe igual en `releases[]` respetando **una escritura por
70
+ > entrada** y **validando contra el schema**; esta skill sigue siendo la única escritora. Parciales NO
71
+ > promueven a `passed`.
72
+
52
73
  ## Reglas duras
53
74
  - **No dupliques el inner loop.** Aquí no se hace TDD ni se cierran gates por slice.
54
75
  - **Integración con deps reales es obligatoria** para `status: passed` — es el gate que faltaba y
@@ -9,7 +9,8 @@ corre **una vez** sobre el diff acumulado de todas sus épicas. Resultado en `bu
9
9
  - [ ] **`ux`** — `ux-krug-reviewer` ok sobre la UI ensamblada (o `null` si la release no tiene UI). Lighthouse/accesibilidad si la app corre.
10
10
  - [ ] **`coherence`** — `coherence-three-way` confirma trazabilidad AC↔change↔código de **todas** las HU de **todas** las épicas de la release, sin huérfanos.
11
11
  - [ ] **`stack_arch`** — `stack-guardian` confirma la arquitectura del PRD del consumidor: la capa de servicios externos/IA en la frontera declarada server-side (no decide), la capa de decisión determinista del dominio sin IA, sin claves de servicios externos en cliente.
12
- - [ ] **`integration`** — el **journey completo** de la release se recorre end-to-end con **dependencias reales** del proyecto (servicios externos/IA y capa de decisión reales, según el PRD del consumidor), no stubs. Verificado con la skill `verify`/`run` (+ MCP `chrome-devtools`).
12
+ - [ ] **`integration`** — el **journey completo** de la release se recorre end-to-end con **dependencias reales** del proyecto (servicios externos/IA y capa de decisión reales, según el PRD del consumidor), no stubs. Verificado con la skill `verify`/`run` (+ MCP `chrome-devtools`). Se corre **fuera** del fan-out
13
+ paralelo de reviewers (es **secuencial**, con deps reales); ver `../workflows/release-gate.workflow.js`.
13
14
 
14
15
  **Todo ✓ (o `null` cuando N/A)** → `releases[].status: "passed"`, escribe `gates` y `updated_by:
15
16
  releasing-a-version`. **Algo ✗** → `status: "failed"`, lista hallazgos bloqueantes; se corrigen como
@@ -0,0 +1,19 @@
1
+ # `workflows/` — plantillas de workflow del outer loop (`releasing-a-version`)
2
+
3
+ > **Plantillas, NO scripts a correr verbatim.** Conducidas por la skill `releasing-a-version`. Si contradicen
4
+ > `METODOLOGIA.md`, **gana la metodología**.
5
+
6
+ ## Reglas duras
7
+ 1. **Hogar primario de los workflows.** El Release Gate corre **una vez por release** (`O(releases)`), fuera
8
+ del camino caliente del inner loop. **No duplica** el inner loop (ni TDD ni gates por slice).
9
+ 2. **Read-only sobre el estado.** La plantilla devuelve veredictos; la skill `releasing-a-version` es la
10
+ **única** que escribe `releases[]` (una entrada por release, validando contra `build-state.schema.json`,
11
+ `updated_by: releasing-a-version`). Parciales **no** promueven a `passed`.
12
+ 3. **`integration` fuera del paralelo.** Los 5 reviewers pesados van en `parallel()`; el gate `integration`
13
+ (journey completo con **deps reales**) es **secuencial**, lo corre la skill `verify`/`run`, y **no** delega
14
+ en un reviewer. Es el gate no negociable.
15
+ 4. **Agnóstico.** Sin vocabulario de dominio/cliente (`scripts/check-agnostic.sh` incluye `*.js`).
16
+
17
+ ## Plantillas
18
+ - **`release-gate.workflow.js`** — `parallel(5 reviewers)` → `integration` secuencial → síntesis a
19
+ `releases[].gates.{security, smell, ux, coherence, stack_arch, integration}`.
@@ -0,0 +1,104 @@
1
+ // =============================================================================
2
+ // PLANTILLA — referencia, no obligatoria. Adapta los nombres de agente y el rango de
3
+ // diff a tu proyecto. El arnés es AGNÓSTICO: este archivo NO debe contener vocabulario
4
+ // de dominio ni de cliente — lo verifica scripts/check-agnostic.sh (incluye *.js).
5
+ //
6
+ // release-gate.workflow.js — Conducción del Release Gate (outer loop), §5 de METODOLOGIA.md.
7
+ // Hogar PRIMARIO de los workflows: corre UNA vez por release (O(releases)), fuera del camino
8
+ // caliente del inner loop. NO duplica el inner loop (ni TDD ni gates por slice).
9
+ //
10
+ // READ-ONLY sobre el estado: esta plantilla devuelve un diagnóstico; la skill
11
+ // releasing-a-version es la ÚNICA que escribe releases[] (una entrada por release, validando
12
+ // contra build-state.schema.json antes de persistir, updated_by:releasing-a-version).
13
+ // Parciales NO promueven a passed. Si METODOLOGIA.md (§5) contradice algo aquí, gana la metodología.
14
+ //
15
+ // RUNTIME: corre en el runtime de Workflow de Claude Code, que provee los globals
16
+ // agent()/parallel()/pipeline()/phase()/log()/args y envuelve el cuerpo en un contexto async
17
+ // (por eso usa `await` y `return` a nivel superior). NO es un módulo node standalone.
18
+ // =============================================================================
19
+
20
+ export const meta = {
21
+ name: 'release-gate',
22
+ description: 'Reviewers pesados en paralelo + integración secuencial + síntesis para el Release Gate (outer loop). Read-only; devuelve veredictos, no escribe estado.',
23
+ phases: [
24
+ { title: 'Reviewers', detail: '5 reviewers pesados en paralelo sobre el diff acumulado' },
25
+ { title: 'Integration', detail: 'journey completo con deps reales (SECUENCIAL, fuera del parallel)' },
26
+ ],
27
+ }
28
+
29
+ const RELEASE_REVIEW_SCHEMA = {
30
+ type: 'object', additionalProperties: false,
31
+ required: ['pass', 'findings'],
32
+ properties: {
33
+ pass: { type: 'boolean', description: 'true solo si no hay hallazgos bloqueantes y se pudo verificar' },
34
+ findings: { type: 'array', items: { type: 'string' } },
35
+ },
36
+ }
37
+
38
+ // diffRange y si la release tiene UI llegan por `args` (la skill los computa READ-ONLY).
39
+ const diffRange = (args && args.diffRange) || '<merge-anterior>..main'
40
+ const hasUI = !!(args && args.hasUI)
41
+
42
+ // --- PASO A · Reviewers pesados EN PARALELO (exactamente estos 5) ------------
43
+ // Cada uno delega en su subagente sobre el MISMO diff acumulado y devuelve síntesis.
44
+ // Barrera deliberada: la síntesis de release necesita los 5 veredictos juntos.
45
+ phase('Reviewers')
46
+ const REVIEWERS = [
47
+ { gate: 'security', agentType: 'security-reviewer' },
48
+ { gate: 'smell', agentType: 'simple-design-reviewer' },
49
+ { gate: 'ux', agentType: 'ux-krug-reviewer' }, // null SOLO si la release no tiene UI
50
+ { gate: 'coherence', agentType: 'coherence-three-way' },
51
+ { gate: 'stack_arch', agentType: 'stack-guardian' },
52
+ ]
53
+ const reviews = await parallel(REVIEWERS.map((r) => async () => {
54
+ // N/A legítimo: ux sin UI → null (NO es fallo). El resto SIEMPRE corre.
55
+ if (r.gate === 'ux' && !hasUI) return { gate: r.gate, value: null, na: true }
56
+ try {
57
+ const v = await agent(
58
+ `Eres el reviewer pesado del Release Gate para el gate "${r.gate}". Revisa READ-ONLY el diff acumulado de la
59
+ release (${diffRange}) y devuelve tu veredicto + hallazgos bloqueantes. Si NO puedes verificar (herramienta
60
+ ausente, app no levantable), devuelve pass:false con el motivo — nunca PASS por defecto, nunca null por fallo.`,
61
+ { label: `release:${r.gate}`, agentType: r.agentType, phase: 'Reviewers', schema: RELEASE_REVIEW_SCHEMA },
62
+ )
63
+ if (!v) return { gate: r.gate, value: false, error: 'sin veredicto' } // ausente/indeterminado = FALLO
64
+ return { gate: r.gate, value: v.pass === true, findings: v.findings || [] }
65
+ } catch (e) {
66
+ return { gate: r.gate, value: false, error: 'el reviewer falló' } // fallo = gate false, NUNCA null
67
+ }
68
+ }))
69
+
70
+ // --- PASO B · integration SECUENCIAL, FUERA del parallel() ------------------
71
+ // integration NO va dentro del parallel() y NO delega en un reviewer: lo corre la skill
72
+ // verify/run con DEPS REALES (+ MCP chrome-devtools). Es el gate no negociable.
73
+ phase('Integration')
74
+ let integration
75
+ try {
76
+ integration = await agent(
77
+ `Recorre el JOURNEY COMPLETO de la release end-to-end con DEPENDENCIAS REALES (no stubs), usando la skill
78
+ verify/run (+ MCP chrome-devtools si hay UI). Diff: ${diffRange}. Devuelve si el journey camina entero. Si no
79
+ puedes levantarlo o falta una dep real → pass:false (nunca PASS por defecto).`,
80
+ { label: 'release:integration', phase: 'Integration', schema: RELEASE_REVIEW_SCHEMA },
81
+ )
82
+ } catch (e) {
83
+ integration = { pass: false, findings: ['integration no pudo correr (deps reales/app no levantable)'] }
84
+ }
85
+
86
+ // --- PASO C · Síntesis → diagnóstico para releasing-a-version ----------------
87
+ // Mapea los 6 gates. Parciales NO promueven a passed: todos true (o ux=null por N/A) +
88
+ // integration=true → passed; cualquier otra cosa → failed.
89
+ const gates = {}
90
+ for (const rv of reviews.filter(Boolean)) gates[rv.gate] = rv.na ? null : rv.value
91
+ gates.integration = !!(integration && integration.pass === true)
92
+
93
+ const reviewersGreen = reviews.filter(Boolean).every((r) => r.na || r.value === true)
94
+ const status = (reviewersGreen && gates.integration === true) ? 'passed' : 'failed'
95
+
96
+ return {
97
+ status, // releasing-a-version lo persiste en releases[] (única escritora).
98
+ gates, // {security, smell, ux, coherence, stack_arch, integration}
99
+ blocking: [
100
+ ...reviews.filter((r) => r && r.value === false).map((r) => ({ gate: r.gate, error: r.error || null, findings: r.findings || [] })),
101
+ ...(gates.integration === true ? [] : [{ gate: 'integration', findings: (integration && integration.findings) || [] }]),
102
+ ],
103
+ note: 'Read-only. releasing-a-version valida contra build-state.schema.json y escribe UNA entrada en releases[] (updated_by:releasing-a-version). Parciales NO promueven a passed.',
104
+ }