@trycore/spec-build-harness 0.5.0 → 0.6.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.
@@ -11,6 +11,19 @@ python3 -c 'import json;print(json.dumps(json.load(open(".claude/state/build-sta
11
11
  - `active_slice == null` → no hay slice; solo `dor` puede abrir uno.
12
12
  - Si no es null → identifica el **primer gate en `false`**: ahí se reanuda.
13
13
 
14
+ ## Refresh de contexto = estado por defecto (handoff en disco)
15
+ Cada iteración nace **headless / contexto virgen** y reconstruye el estado **desde disco** (git history +
16
+ este `build-state.json` + logs de build), **no** desde la conversación viva: alargar una sesión degrada
17
+ el razonamiento (ruido acumulado → deriva). Para retomar sin "creer que ya está":
18
+ - **`wiring_checklist[]`** — un item por escenario AC de cada HU y por **punto de integración entre capas**.
19
+ Nace `failing`; pasa a `passing` **SOLO tras prueba real ejecutada** (con `evidence`), nunca por inspección.
20
+ **Mientras quede un item `failing`, el slice NO está cableado** — no cierres `wiring_verified` ni `dod`.
21
+ - **`progress_log[]`** — bitácora append-only (`{at, by, note}`) de qué se hizo / qué falta. Deja una nota
22
+ por hito para que la siguiente sesión retome el cableado.
23
+ - **`sub_slices[]`** — si la épica superó el gate de tamaño (>3 HU ó ≥3 capas), se trocea aquí; cada
24
+ sub-slice se construye de a uno con su `journey_smoke` verde antes de pasar al siguiente.
25
+ `load-build-state.sh` inyecta al arrancar los items `failing` y la última bitácora (JIT, solo lo pendiente).
26
+
14
27
  ## Escritura (una transición = una escritura)
15
28
  Patrón seguro (lee-modifica-escribe) con timestamp UTC:
16
29
  ```bash
@@ -27,16 +40,20 @@ PY
27
40
  ```
28
41
 
29
42
  ## Inner loop vs releases[]
30
- - **Inner loop (por slice)**: gates `dor`, `tdd`, `journey_smoke`, `coherence_link`, `data`, `api`,
31
- `dod` en `active_slice.gates`. Los escribe el flujo de `building-a-slice` / `build-orchestrator`.
43
+ - **Inner loop (por slice)**: gates `dor`, `tdd`, `journey_smoke`, `coherence_link`, `data`, `fidelity`,
44
+ `api`, `wiring_verified`, `dod` en `active_slice.gates`. Los escribe el flujo de `building-a-slice` /
45
+ `build-orchestrator`. **`wiring_verified` es prerequisito de `dod`** (lo cierra el verificador
46
+ adversarial independiente, no el mismo agente que construyó).
32
47
  - **Outer loop (por release)**: los gates pesados (`security`, `smell`, `ux`, `coherence`,
33
48
  `stack_arch`, `integration`) **no** viven en el slice — viven en una entrada de `releases[]` que
34
49
  escribe la skill `releasing-a-version`. Una release = una línea de release del Story Map.
35
50
 
36
51
  ## Reglas
37
- 1. **No saltes gates**: no cierres `dod` con gates abiertos.
52
+ 1. **No saltes gates**: no cierres `dod` con gates abiertos; en particular `dod` exige `wiring_verified: true`.
38
53
  2. **Retroceso permitido**: si una revisión posterior falla, pon su gate en `false` y retrocede `phase`.
39
- 3. **`ux`/`api` = `null`** cuando no aplican (no cuentan como abiertos para DoD).
54
+ 3. **`fidelity`/`api` = `null`** cuando no aplican (no cuentan como abiertos para DoD). `fidelity` NO puede
55
+ quedar `null` si el slice toca UI (`design_source.applies===true`): ahí debe llegar a `true` por
56
+ verificación visual real (MCP). `wiring_verified` aplica siempre (no admite `null`).
40
57
  4. **Archivar**: mueve `active_slice` a `history[]` con `phase:"archived"`, deja `active_slice:null`.
41
58
  5. **Valida tras escribir**:
42
59
  ```bash
package/state/README.md CHANGED
@@ -24,7 +24,9 @@ Cada gate del pipeline transiciona el estado; el siguiente agente lo lee antes d
24
24
  correspondiente, `updated_at` (ISO 8601 UTC) y `updated_by` (su nombre). No tocar otros campos.
25
25
  3. **Gates monótonos hacia adelante.** Un gate solo pasa de `false`→`true` cuando su agente lo
26
26
  aprueba. Si una revisión posterior falla, se vuelve a `false` y `phase` retrocede.
27
- 4. **`ux`/`api` admiten `null`** cuando el slice no tiene UI o endpoints (N/A, no bloquea DoD).
27
+ 4. **`fidelity`/`api` admiten `null`** cuando el slice no tiene UI o endpoints (N/A, no bloquea DoD).
28
+ `fidelity` **no** puede quedar `null` si el slice toca UI. `wiring_verified` aplica siempre (sin `null`)
29
+ y es **prerequisito de `dod`**.
28
30
  5. **Archivar**: al completar `opsx:archive`, mover el `active_slice` a `history[]` con
29
31
  `phase: "archived"` y dejar `active_slice: null`.
30
32
  6. **Dos loops**: el slice (inner loop) cierra gates baratos por épica; los gates pesados viven en
@@ -57,10 +59,25 @@ Espejo de `scaffold` para la UI: `applies` (¿el proyecto tiene UI?), `confirmed
57
59
  existe una fuente de diseño declarada), `source` (puntero al prototipo/export). El arnés NO genera el
58
60
  prototipo. Lo respalda `design-source-guard.sh`.
59
61
 
60
- ### gate `fidelity` (por-slice, inner loop)
62
+ ### gate `fidelity` (por-slice, inner loop) — ESTRICTO para UI
61
63
 
62
- `true` = FIEL (o desviaciones justificadas); `false` = desviaciones sin justificar; `null` = slice sin
63
- UI. Lo computa `ux-fidelity-reviewer` en la fase `smoke` y lo escribe el `build-orchestrator`.
64
+ `true` = FIEL (o desviaciones justificadas, **vía verificación visual real**); `false` = desviaciones
65
+ sin justificar **o** verificación visual no realizada; `null` = slice sin UI. Lo computa
66
+ `ux-fidelity-reviewer` en la fase `smoke` y lo escribe el `build-orchestrator`. Para slices con UI
67
+ (`design_source.applies===true`) solo cierra con **MCP chrome-devtools** (screenshot app vs prototipo):
68
+ INCONCLUSO ya **no** pasa (queda `false` → el `dod` no cierra hasta correrlo donde haya MCP).
69
+
70
+ ### Handoff fino del cableado (`wiring_checklist[]`, `progress_log[]`, `sub_slices[]`) + gate `wiring_verified`
71
+
72
+ Campos por-slice que hacen el **refresh de contexto el estado por defecto** (una sesión fresca retoma
73
+ desde disco, no desde la conversación):
74
+ - **`wiring_checklist[]`** — un item por escenario AC y por punto de integración entre capas; nace
75
+ `failing`, pasa a `passing` **solo tras prueba real ejecutada** (con `evidence`).
76
+ - **`progress_log[]`** — bitácora append-only (`{at, by, note}`) que sobrevive al reset.
77
+ - **`sub_slices[]`** — descomposición de una épica que superó el gate de tamaño (>3 HU ó ≥3 capas).
78
+ - **gate `wiring_verified`** — lo cierra el `wiring-adversarial-verifier` (subagente **independiente**,
79
+ contexto virgen, que intenta **refutar** el slice). `dod` no puede ser `true` sin él. Lo inicializa
80
+ el `dor-dod-gatekeeper` en `false` durante el DoR.
64
81
 
65
82
  ### Reflexión post-slice (ciclo autocorrectivo)
66
83
 
@@ -78,9 +95,10 @@ El razonamiento vive en el modelo; el hook solo es un recordatorio determinista.
78
95
  | `active_slice` (alta) · `gates.dor` · `gates.dod` | `dor-dod-gatekeeper` | slice |
79
96
  | `gates.coherence_link` | `change-epic-coherence` | slice |
80
97
  | `gates.tdd` | flujo `superpowers:test-driven-development` (vía `build-orchestrator`) | slice |
81
- | `gates.journey_smoke` · `phase` (transiciones) · `history[]` (archivado) | `build-orchestrator` | slice |
98
+ | `gates.journey_smoke` · `gates.fidelity` · `phase` (transiciones) · `history[]` (archivado) · `wiring_checklist[]` · `progress_log[]` · `sub_slices[]` | `build-orchestrator` (fidelity desde `ux-fidelity-reviewer`) | slice |
82
99
  | `gates.api` | `api-contract-tester` | slice |
83
100
  | `gates.data` | `data-consistency-checker` | slice |
101
+ | `gates.wiring_verified` | `wiring-adversarial-verifier` (independiente, contexto virgen) | slice (antes de `dod`) |
84
102
  | `releases[]` (`security`, `smell`, `ux`, `coherence`, `stack_arch`, `integration`, `status`) | `releasing-a-version` (delega en `security-reviewer`, `simple-design-reviewer`, `ux-krug-reviewer`, `coherence-three-way`, `stack-guardian`) | release |
85
103
  | `history[].reflected` · `history[].reflected_at` | `/build:reflect` | post-slice (tras archivar) |
86
104
  | `harness_phase` | `load-build-state.sh` (SessionStart) | — |
@@ -82,15 +82,16 @@
82
82
  "gates": {
83
83
  "type": "object",
84
84
  "additionalProperties": false,
85
- "description": "Inner loop (por slice): dor, tdd, journey_smoke, data, api(null), coherence_link, dod. Los gates pesados (stack, security, smell, ux, coherence) se cerraron por slice en la era anterior y hoy viven en releases[]; se mantienen como propiedades opcionales para validar historial.",
85
+ "description": "Inner loop (por slice): dor, tdd, journey_smoke, coherence_link, data, fidelity, api(null), wiring_verified, dod. Los gates pesados (stack, security, smell, ux, coherence) se cerraron por slice en la era anterior y hoy viven en releases[]; se mantienen como propiedades opcionales para validar historial.",
86
86
  "required": ["dor", "tdd", "dod"],
87
87
  "properties": {
88
88
  "dor": { "type": "boolean" },
89
89
  "tdd": { "type": "boolean" },
90
- "journey_smoke": { "type": "boolean", "description": "El backbone-hasta-aquí camina end-to-end (verificado con la skill verify/run)." },
90
+ "journey_smoke": { "type": "boolean", "description": "El backbone-hasta-aquí camina end-to-end (verificado con la skill verify/run + runner fuera-de-chat integration-check)." },
91
91
  "coherence_link": { "type": "boolean", "description": "Enlace change↔épica válido (change-epic-coherence, barato, por slice)." },
92
92
  "data": { "type": "boolean" },
93
- "fidelity": { "type": ["boolean", "null"], "description": "Gate VIVO de inner loop (slices con UI): fidelidad visual a la fuente de diseño declarada, computado en fase smoke por ux-fidelity-reviewer. true = FIEL (o DESVIACIONES justificadas); false = DESVIACIONES sin justificar; null = N/A (slice sin UI). NO es legado (los legados viven en releases[])." },
93
+ "fidelity": { "type": ["boolean", "null"], "description": "Gate VIVO de inner loop, computado en fase smoke por ux-fidelity-reviewer. Aplica cuando el slice toca UI (en un proyecto con design_source.applies===true); si el slice NO toca UI, es null aunque el proyecto tenga UI. ESTRICTO para UI: solo cierra con VERIFICACIÓN VISUAL REAL vía MCP de devtools de navegador (screenshot app vs prototipo). true = FIEL (o DESVIACIONES justificadas, vía MCP); false = DESVIACIONES sin justificar O verificación visual no realizada (INCONCLUSO ya NO pasa para UI); null = N/A (slice sin UI). NO es legado (los legados viven en releases[])." },
94
+ "wiring_verified": { "type": "boolean", "description": "Gate de cierre del inner loop (A4): un subagente adversarial INDEPENDIENTE de contexto virgen (wiring-adversarial-verifier) intentó refutar el slice (stubs, rutas sin cablear, AC sin test, items de wiring_checklist[] aún failing) y NO encontró huecos. dod NO puede ser true hasta que wiring_verified sea true. Lo inicializa el gatekeeper en false en el DoR." },
94
95
  "api": { "type": ["boolean", "null"], "description": "null = N/A (slice sin endpoints)." },
95
96
  "ux": { "type": ["boolean", "null"], "description": "null = N/A (slice sin UI). Legado: hoy se audita en releases[]." },
96
97
  "stack": { "type": "boolean", "description": "Legado por-slice; deps las cubre el hook stack-guard.sh, la arquitectura se audita en releases[]." },
@@ -104,7 +105,53 @@
104
105
  "updated_by": { "type": "string", "description": "Agente o hook que escribió el estado." },
105
106
  "notes": { "type": "string", "description": "Nota libre opcional (p.ej. changes/branches adicionales de una épica construida en varios pasos)." },
106
107
  "reflected": { "type": "boolean", "description": "True si /build:reflect ya capturó los aprendizajes de este slice archivado (ciclo autocorrectivo). Mientras sea false/ausente, reflect-nudge.sh sugiere reflexionar al cerrar sesión." },
107
- "reflected_at": { "type": "string", "format": "date-time", "description": "Cuándo se reflexionó (ISO-8601 UTC). Lo escribe /build:reflect." }
108
+ "reflected_at": { "type": "string", "format": "date-time", "description": "Cuándo se reflexionó (ISO-8601 UTC). Lo escribe /build:reflect." },
109
+ "wiring_checklist": {
110
+ "type": "array",
111
+ "description": "Handoff FINO del cableado (A1): un item por escenario AC de cada HU y por punto de integración entre capas (SPA↔gateway↔core↔worker↔persistencia). Nace failing; pasa a passing SOLO tras prueba real ejecutada (con evidence), nunca por inspección. Sobrevive al reset de contexto: una sesión fresca lee los failing de disco y NO declara 'hecho' si quedan. El wiring-adversarial-verifier audita estos items antes de cerrar wiring_verified. Ausente/vacío en slices triviales.",
112
+ "items": {
113
+ "type": "object",
114
+ "additionalProperties": false,
115
+ "required": ["id", "kind", "ref", "status"],
116
+ "properties": {
117
+ "id": { "type": "string", "description": "Identificador estable del item (p.ej. HU-003-AC2 o INT-spa-gateway)." },
118
+ "kind": { "type": "string", "enum": ["hu_ac", "integration_point"], "description": "hu_ac = escenario Given/When/Then de una HU; integration_point = cableado entre dos capas." },
119
+ "ref": { "type": "string", "description": "A qué apunta: el AC (HU-XXX#escenario) o el par de capas (capaA→capaB)." },
120
+ "status": { "type": "string", "enum": ["failing", "passing"], "description": "failing al nacer; passing SOLO tras prueba real ejecutada." },
121
+ "evidence": { "type": "string", "description": "Evidencia de ejecución que justificó passing (test, comando, salida). Vacío mientras failing." }
122
+ }
123
+ }
124
+ },
125
+ "progress_log": {
126
+ "type": "array",
127
+ "description": "Bitácora de progreso que sobrevive al reset de contexto (A1): permite a una sesión fresca/headless retomar el cableado sin 'creer que ya está'. Append-only; complementa a git history y los logs de build.",
128
+ "items": {
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "required": ["at", "by", "note"],
132
+ "properties": {
133
+ "at": { "type": "string", "format": "date-time" },
134
+ "by": { "type": "string", "description": "Agente/sesión que dejó la nota." },
135
+ "note": { "type": "string", "description": "Qué se hizo / qué falta (hito conciso)." }
136
+ }
137
+ }
138
+ },
139
+ "sub_slices": {
140
+ "type": "array",
141
+ "description": "Descomposición de una épica grande (A3): si supera el umbral del gate de tamaño (>3 HU ó ≥3 capas tocadas), se trocea en sub-slices verificables construidos DE A UNO, con journey_smoke verde entre cada uno. Ausente/vacío = épica atómica (no superó el umbral). Acota además el tamaño de wiring_checklist[] por paso.",
142
+ "items": {
143
+ "type": "object",
144
+ "additionalProperties": false,
145
+ "required": ["id", "title", "status"],
146
+ "properties": {
147
+ "id": { "type": "string", "description": "Identificador del sub-slice (p.ej. EP-003-a)." },
148
+ "title": { "type": "string" },
149
+ "hus": { "type": "array", "items": { "type": "string", "pattern": "^HU-[0-9]{3}$" }, "description": "Subconjunto de hus[] cubierto por este sub-slice." },
150
+ "status": { "type": "string", "enum": ["pending", "done"], "description": "done SOLO con su journey_smoke verde." },
151
+ "journey_smoke": { "type": "boolean", "description": "El backbone-hasta-este-sub-slice camina end-to-end." }
152
+ }
153
+ }
154
+ }
108
155
  }
109
156
  },
110
157
  "release": {
@@ -7,7 +7,7 @@ Este proyecto usa el arnés `@trycore/spec-build-harness` para **construir** sob
7
7
  (compañero de `@trycore/spec-product-flow`: Discovery → Construcción). Modelo de **dos loops**:
8
8
 
9
9
  ```
10
- Inner loop (por épica EP-XXX): DoR → OpenSpec change → TDD → journey-smoke → api/data → DoD → PR + archive
10
+ Inner loop (por épica EP-XXX): DoR → OpenSpec change → TDD → journey-smoke → api/data → verificación adversarial → DoD → PR + archive
11
11
  Outer loop (por release): Release Gate (seguridad · diseño · UX · coherencia triple · arquitectura · integración)
12
12
  ```
13
13
 
@@ -30,7 +30,11 @@ Outer loop (por release): Release Gate (seguridad · diseño · UX · cohe
30
30
  2. El enlace change↔épica va en el bloque `## Trazabilidad` del `proposal.md`, **nunca** en frontmatter YAML (rompe `openspec validate`).
31
31
  3. Las dependencias se vigilan contra `.claude/config/stack-allowlist.json` (hook `stack-guard.sh`), derivado de la sección de requisitos técnicos del PRD.
32
32
  4. Las revisiones pesadas (seguridad/diseño/UX/coherencia triple/arquitectura/integración) corren **una vez por release** (outer loop), no por épica.
33
- 5. Si una regla del arnés contradice la metodología Trycore (`METODOLOGIA.md`), **gana la metodología**.
33
+ 5. **Producto completo, no MVP.** El alcance acordado se construye **entero**. **Recortar o diferir es bloqueante explícito** que requiere acuerdo del equipo — **nunca** una decisión del modelo. No se "deja para después" ni se deriva en lo complejo. La verificación es **ejecutada, no por inspección** (correr la suite, cargar la página, leer la consola).
34
+ 6. **Cierre verificado, no declarado.** `dod` exige el gate `wiring_verified`: un subagente **adversarial independiente** (`wiring-adversarial-verifier`, contexto virgen) intenta refutar el slice (stubs, rutas sin cablear, AC sin test) antes de cerrar. El estado del cableado vive en disco (`wiring_checklist[]` + `progress_log[]`) para que una sesión fresca retome sin "creer que ya está".
35
+ 7. **Fidelidad por verificación visual real.** Para slices con UI, el gate `fidelity` solo cierra observando la salida real vía MCP de devtools de navegador (screenshot app vs prototipo); sin verificación visual queda `false` (no "INCONCLUSO pasa").
36
+ 8. **Cimiento antes que negocio y unidades pequeñas.** Las épicas de cimiento (auth, datos, arquitectura base, design-system) se construyen antes que las de negocio; una épica grande (>3 HU ó ≥3 capas) se descompone en sub-slices construidos de a uno.
37
+ 9. Si una regla del arnés contradice la metodología Trycore (`METODOLOGIA.md`), **gana la metodología**.
34
38
 
35
39
  ### Bloque de dominio (lo resuelve `/build:onboard`)
36
40
 
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # integration-check.sh — runner determinista FUERA DE CHAT para cerrar `journey_smoke` (inner loop).
3
+ #
4
+ # Qué es: un gate determinista que ejecuta build + suite completa + un journey-smoke end-to-end y
5
+ # emite un REPORTE de integración a disco, con salida 0 (verde) / ≠0 (rojo). Está pensado para correr
6
+ # en una **sesión/contexto virgen** distinta de la que escribió el código (el agente que cableó no es
7
+ # juez de su propio cableado), y para el **fix-loop** (lo corres, lees el reporte, arreglas, repites).
8
+ #
9
+ # Por qué un script y no un agente: el cableado end-to-end no se acredita "por inspección" sino
10
+ # EJECUTANDO. Mantenerlo fuera del chat lo hace reproducible y barato en contexto.
11
+ #
12
+ # AGNÓSTICO: este es un TEMPLATE. El arnés no asume tu stack. Ajusta los comandos marcados con
13
+ # «# ADAPTA» a tu proyecto (lenguaje, package manager, comando de e2e). Cópialo a tu repo
14
+ # (p.ej. tools/loop/integration-check.sh) y hazlo ejecutable.
15
+ #
16
+ # NO crea ningún gate nuevo en build-state.json: alimenta el gate EXISTENTE `journey_smoke` del slice.
17
+ # El gate `integration` con dependencias reales sigue siendo del Release Gate (outer loop), no de aquí.
18
+ set -uo pipefail
19
+
20
+ ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
21
+ REPORT_DIR="$ROOT/.claude/state"
22
+ REPORT="$REPORT_DIR/integration-report.txt"
23
+ mkdir -p "$REPORT_DIR"
24
+
25
+ # Detecta el package manager si hay package.json (Node). ADAPTA para otros stacks.
26
+ PM="npm"
27
+ if [ -f "$ROOT/pnpm-lock.yaml" ]; then PM="pnpm";
28
+ elif [ -f "$ROOT/yarn.lock" ]; then PM="yarn"; fi
29
+
30
+ fail=0
31
+ log() { printf '%s\n' "$*" | tee -a "$REPORT"; }
32
+
33
+ : > "$REPORT"
34
+ log "== integration-check =="
35
+ log "root: $ROOT"
36
+ log "pm: $PM"
37
+ log "ts: (sin timestamp determinista; lo estampa quien invoca)"
38
+ log ""
39
+
40
+ run() { # run <etiqueta> <comando...>
41
+ local label="$1"; shift
42
+ log "▶ $label: $*"
43
+ if "$@" >>"$REPORT" 2>&1; then
44
+ log " ✓ $label OK"
45
+ else
46
+ log " ✗ $label FALLÓ"
47
+ fail=1
48
+ fi
49
+ log ""
50
+ }
51
+
52
+ # --- Fases del runner (ADAPTA los comandos a tu stack) ---
53
+ # 1) Build: el proyecto compila/construye sin error.
54
+ run "build" $PM run build # ADAPTA (p.ej. cargo build, ./gradlew build, make)
55
+ # 2) Suite completa: todos los tests, no un subconjunto.
56
+ run "tests" $PM test # ADAPTA (p.ej. pytest, go test ./..., cargo test)
57
+ # 3) Journey-smoke end-to-end: recorre el backbone-hasta-aquí. ADAPTA al e2e de tu proyecto.
58
+ # Si tu e2e necesita la app levantada, levántala aquí (y bájala al salir).
59
+ # run "e2e" $PM run test:e2e # ADAPTA / descomenta
60
+
61
+ log "== resultado: $([ "$fail" -eq 0 ] && echo VERDE || echo ROJO) =="
62
+ log "Reporte: $REPORT"
63
+
64
+ # Salida determinista: 0 verde, 1 rojo. El gate journey_smoke solo se marca true con salida 0.
65
+ exit "$fail"