@trycore/spec-build-harness 0.8.5 → 0.10.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 (106) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +27 -4
  3. package/INSTALL.md +27 -5
  4. package/METODOLOGIA.md +55 -5
  5. package/README.md +39 -6
  6. package/VERSION +1 -1
  7. package/agents/build/build-orchestrator.md +33 -7
  8. package/agents/build/dor-dod-gatekeeper.md +13 -5
  9. package/agents/build/wiring-adversarial-verifier.md +52 -5
  10. package/commands/build/architect.md +1 -1
  11. package/commands/build/claim.md +46 -0
  12. package/commands/build/escalate.md +36 -0
  13. package/commands/build/front.md +9 -3
  14. package/commands/build/onboard.md +59 -14
  15. package/commands/build/prototype.md +3 -2
  16. package/commands/build/reflect.md +60 -40
  17. package/commands/build/release.md +10 -7
  18. package/commands/build/resume.md +33 -13
  19. package/commands/build/slice.md +32 -27
  20. package/commands/build/status.md +35 -0
  21. package/commands/build/work.md +11 -8
  22. package/config/build-config.template.json +4 -0
  23. package/dist/cli.js +22 -0
  24. package/dist/commands/doctor.js +42 -0
  25. package/dist/commands/init.js +84 -1
  26. package/dist/commands/migrate.js +48 -0
  27. package/dist/commands/status.js +34 -0
  28. package/dist/lib/normalize.js +276 -0
  29. package/dist/lib/paths.js +6 -0
  30. package/dist/lib/runtime-client.js +196 -0
  31. package/dist/lib/settings-merge.js +3 -3
  32. package/dist/lib/state-bundle.js +46 -0
  33. package/docs/commands.md +25 -8
  34. package/docs/getting-started.md +1 -0
  35. package/docs/hooks.md +114 -27
  36. package/docs/runtime/guia-modo-dual-y-migracion.md +136 -0
  37. package/docs/runtime/plan-migracion-harness-v0.9.md +11 -0
  38. package/docs/runtime/protocolo-cliente-runtime.md +109 -34
  39. package/hooks/build/build-gate-check.sh +21 -0
  40. package/hooks/build/context-monitor.sh +82 -15
  41. package/hooks/build/context-sync.sh +192 -0
  42. package/hooks/build/design-source-guard.sh +30 -2
  43. package/hooks/build/dual-compare.sh +92 -0
  44. package/hooks/build/event-emitter.sh +75 -0
  45. package/hooks/build/gitflow-guard.sh +164 -14
  46. package/hooks/build/heartbeat.sh +259 -0
  47. package/hooks/build/lib/agent-context.sh +139 -0
  48. package/hooks/build/lib/config.sh +27 -0
  49. package/hooks/build/lib/projection.sh +71 -0
  50. package/hooks/build/lib/runtime-client.sh +465 -0
  51. package/hooks/build/lib/runtime-ops.sh +221 -0
  52. package/hooks/build/lib/state-io.sh +5 -18
  53. package/hooks/build/load-build-state.sh +64 -2
  54. package/hooks/build/reflect-nudge.sh +15 -0
  55. package/hooks/build/release-gate-nudge.sh +15 -0
  56. package/hooks/build/release-ops.sh +164 -0
  57. package/hooks/build/scaffold-guard.sh +29 -2
  58. package/hooks/build/session-start.sh +103 -0
  59. package/hooks/build/session-stop.sh +22 -0
  60. package/hooks/build/slice-ops.sh +877 -0
  61. package/hooks/build/stack-guard.sh +8 -0
  62. package/hooks/build/statusline-bridge.sh +24 -3
  63. package/hooks/build-harness.json +16 -0
  64. package/package.json +3 -3
  65. package/scripts/check-agnostic.sh +3 -1
  66. package/scripts/check-pack-clean.sh +31 -0
  67. package/scripts/check-runtime-purity.sh +43 -0
  68. package/scripts/lib/front-plan.py +4 -0
  69. package/scripts/lib/graph-bundle.py +133 -0
  70. package/scripts/runtime-purity-allow.txt +5 -0
  71. package/scripts/smoke-test.sh +1 -1
  72. package/scripts/tests/lib/http-stub.py +46 -0
  73. package/scripts/tests/test-baseline-verdict.sh +92 -0
  74. package/scripts/tests/test-config.sh +25 -0
  75. package/scripts/tests/test-hooks-runtime.sh +853 -0
  76. package/scripts/tests/test-install.sh +57 -0
  77. package/scripts/tests/test-runtime-client.sh +298 -0
  78. package/scripts/tests/test-schema.sh +29 -1
  79. package/scripts/tests/test-skill-ops.sh +847 -0
  80. package/skills/building-a-micro-change/SKILL.md +22 -4
  81. package/skills/building-a-slice/SKILL.md +55 -21
  82. package/skills/building-a-slice/assets/baseline-verdict.sh +172 -0
  83. package/skills/building-a-slice/references/dod.md +12 -3
  84. package/skills/building-a-slice/references/dor.md +3 -2
  85. package/skills/building-a-slice/references/evidence-budget.md +51 -0
  86. package/skills/building-a-slice/references/exploration-fanout.md +1 -1
  87. package/skills/building-a-slice/references/gitflow.md +1 -1
  88. package/skills/building-a-slice/references/regression-baseline.md +67 -0
  89. package/skills/building-a-slice/references/runtime-protocol.md +75 -0
  90. package/skills/building-a-slice/references/state-protocol.md +12 -1
  91. package/skills/building-a-slice/workflows/README.md +7 -3
  92. package/skills/building-a-slice/workflows/explore-fanout.workflow.js +3 -3
  93. package/skills/building-a-slice/workflows/wiring-verify.workflow.js +26 -4
  94. package/skills/managing-parallel-front/SKILL.md +32 -16
  95. package/skills/openspec-archive-change/SKILL.md +15 -0
  96. package/skills/prototyping-screens/SKILL.md +9 -5
  97. package/skills/releasing-a-version/SKILL.md +25 -16
  98. package/skills/releasing-a-version/references/release-dod.md +7 -5
  99. package/skills/releasing-a-version/workflows/README.md +2 -1
  100. package/skills/releasing-a-version/workflows/release-gate.workflow.js +6 -5
  101. package/skills/setup-architecture/SKILL.md +4 -2
  102. package/state/README.md +16 -1
  103. package/state/build-state.schema.json +2 -1
  104. package/templates/CLAUDE.md.template +16 -0
  105. package/templates/settings-hooks.template.json +8 -4
  106. package/internal/skills/auditar-arnes/SKILL.md +0 -29
@@ -46,7 +46,8 @@ producto.
46
46
  que cruzas uno, **detente y escala** a `building-a-slice`.
47
47
  3. **Test de regresión — solo si cambia comportamiento.** Si el micro-change repara un bug,
48
48
  añade/ajusta un test que falle antes y pase después (red→green del fix, delega en
49
- `superpowers:test-driven-development`). Para cambios **no conductuales** (typo en copy, docs,
49
+ `superpowers:test-driven-development`) y deja **la suite del módulo tocado en verde** (no hace
50
+ falta la suite completa del repo). Para cambios **no conductuales** (typo en copy, docs,
50
51
  config) **no** se exige test.
51
52
  4. **PR a `main`.** Abre el Pull Request (`gitflow-guard.sh` impide la integración por push directo).
52
53
  En la descripción del PR indica que es un micro-change y por qué califica (qué límite NO cruza).
@@ -63,11 +64,28 @@ producto.
63
64
  `scaffold-guard` no aplica: no hay slice activo y el carril exige proyecto en fase `active` (scaffold
64
65
  ya confirmado).
65
66
 
67
+ ## Ámbito de las reglas pesadas — este carril está EXENTO
68
+
69
+ Las reglas de **mutación obligatoria por test**, **evidencia anclada a sha** y **regresión con
70
+ worktree/baseline** aplican al **inner loop de slices** (fase tdd→dod de `building-a-slice`; ver
71
+ `../building-a-slice/references/evidence-budget.md`), **no** a este carril. Un micro-change exige
72
+ exactamente:
73
+
74
+ 1. **1 test de regresión si cambia comportamiento** (paso 3 del pipeline), y
75
+ 2. **la suite del módulo tocado en verde**.
76
+
77
+ Nada más. La mutación aquí es **opcional** — recomendada solo si ese test es la **única defensa de
78
+ un invariante**. Presupuesto de latencia: un micro-change típico (fix de pocas líneas + su test)
79
+ debe poder cerrar en **< 30 min** de terminal; si el ritual lo excede sistemáticamente, sobra
80
+ ritual, no carril. Esta exención **no relaja los límites duros del Paso 0**: nueva dependencia,
81
+ endpoint/API nueva o cambio de dominio/datos siguen escalando a `building-a-slice`.
82
+
66
83
  ## Estado y trazabilidad
67
84
 
68
- El micro-change **no escribe** `build-state.json` es mantenimiento fuera de banda, trazado por el
69
- historial de git y el PR. No entra a `history[]`, así que `reflect-nudge.sh` **no** sugiere
70
- reflexionar por él (no hay aprendizaje de épica que capturar en un typo).
85
+ El micro-change **no transiciona el estado del slice** — ni reclama, ni reporta gates, ni archiva:
86
+ es mantenimiento fuera de banda, trazado por el historial de git y el PR. No entra al histórico de
87
+ épicas, así que `reflect-nudge.sh` **no** sugiere reflexionar por él (no hay aprendizaje de épica que
88
+ capturar en un typo).
71
89
 
72
90
  ## Reglas duras
73
91
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: building-a-slice
3
- description: Use when building, continuing, or shipping a product epic (EP-XXX) end to end — drives the fast inner-loop pipeline (DoR → OpenSpec change linked to its epic → TDD → journey-smoke → api/data → reduced DoD → PR+archive → ask for Release Gate) using the build-state.json file to synchronize agents. Heavy reviews (security/design/UX/three-way coherence/architecture/integration) run once per release in the releasing-a-version skill, not per epic. The epic is the build unit; the HUs it covers are its internal scope. Delegates to opsx:* for changes and superpowers:test-driven-development for TDD; never reimplements them.
3
+ description: Use when building, continuing, or shipping a product epic (EP-XXX) end to end — drives the fast inner-loop pipeline (DoR → OpenSpec change linked to its epic → TDD → journey-smoke → api/data → reduced DoD → PR+archive → ask for Release Gate) synchronizing agents through the Agent Orchestrator Runtime (slice-ops.sh) — or the local state file in legacy mode. Heavy reviews (security/design/UX/three-way coherence/architecture/integration) run once per release in the releasing-a-version skill, not per epic. The epic is the build unit; the HUs it covers are its internal scope. Delegates to opsx:* for changes and superpowers:test-driven-development for TDD; never reimplements them.
4
4
  ---
5
5
 
6
6
  # Construir un slice (épica EP-XXX) — Build
@@ -21,18 +21,22 @@ el avance en el estado.
21
21
  > **aquí** y ábrelo como épica.
22
22
 
23
23
  ## Principio de operación
24
- - **Una sola fuente de verdad**: `.claude/state/build-state.json` (schema + protocolo en
25
- `.claude/state/README.md`). Lee antes de actuar; escribe una vez por transición.
24
+ - **Una sola fuente de verdad, según el modo**: pregunta primero
25
+ `bash .claude/hooks/build/slice-ops.sh mode`. En `dual`/`runtime` el estado vive en el
26
+ **Agent Orchestrator Runtime** y toda transición pasa por `slice-ops.sh`
27
+ (`references/runtime-protocol.md`); en modo `legacy` la fuente es el fichero local
28
+ (`references/state-protocol.md`). Lee antes de actuar; una transición = una llamada.
26
29
  - **Secuencial**: un slice activo a la vez. Un gate no se salta.
27
30
  - **Divulgación progresiva**: carga el `references/<tema>.md` solo cuando la fase lo necesita.
28
31
  - **Delega en subagentes** para revisión pesada y para **explorar** (devuelven síntesis condensada,
29
32
  protegen el presupuesto de atención de la sesión principal para **cablear**, no para descubrir).
30
33
  - **Refresh de contexto = estado por defecto**: cada iteración nace **headless / contexto virgen** y
31
- reconstruye el estado **desde disco** (git + `build-state.json` + logs), no desde la conversación
32
- viva. Mantén el **`wiring_checklist[]`** (un item por escenario AC y por punto de integración entre
33
- capas): nace `failing`, pasa a `passing` **solo tras prueba real ejecutada**. Deja una nota en
34
- `progress_log[]` por hito. **Mientras quede un item `failing`, el slice NO está terminado.** Ver
35
- `references/state-protocol.md`.
34
+ reconstruye el estado **desde disco** (git + `build-state.json` en modo legacy + logs), no desde la conversación
35
+ viva. Mantén el **checklist de cableado** (un item por escenario AC y por punto de integración
36
+ entre capas): nace `failing`, pasa a `passing` **solo tras prueba real ejecutada**
37
+ (`slice-ops.sh wiring update --evidence-file`). Deja una nota por hito
38
+ (`slice-ops.sh progress`). **Mientras quede un item `failing`, el slice NO está terminado.**
39
+ Protocolo: `references/runtime-protocol.md` (modos `dual`/`runtime`).
36
40
 
37
41
  ## Dos loops
38
42
 
@@ -71,9 +75,10 @@ Los archivos `*.workflow.js` bajo `workflows/` son **plantillas de referencia**
71
75
  no scripts a correr verbatim (si contradicen `METODOLOGIA.md`, gana la metodología). Reglas duras:
72
76
  - **Opt-in y solo para épicas grandes.** Los workflows del inner loop solo aplican a épicas troceadas por el
73
77
  gate de tamaño (`sub_slices[]` no vacío); **nunca** en el camino caliente ≤ ~20 min de una épica atómica.
74
- - **Read-only sobre el estado.** Ningún workflow escribe `build-state.json`: devuelven un diagnóstico y
75
- `build-orchestrator` (o el agente dueño del gate) aplica el mapeo respetando el protocolo (una transición =
76
- una escritura; gates monótonos; **validar contra el schema tras escribir**).
78
+ - **Read-only sobre el estado.** Ningún workflow transiciona el estado (ni por `slice-ops.sh` ni, en modo legacy,
79
+ escribiendo en `build-state.json`—legacy protocol—): devuelven un diagnóstico y `build-orchestrator` (o el agente dueño del gate)
80
+ aplica el mapeo respetando el protocolo (una transición = una escritura; gates monótonos; en legacy, validar contra
81
+ el schema tras escribir).
77
82
  - **Subagentes de exploración = solo-lectura** (Read/Grep/Glob); el cableado lo hace la sesión.
78
83
 
79
84
  Ver `workflows/README.md`. Hoy: `workflows/explore-fanout.workflow.js` (exploración fan-out) y
@@ -86,20 +91,22 @@ confirmado explícitamente**. El arnés **NO genera** el scaffold (es agnóstico
86
91
  **bloquea el avance** hasta confirmarlo. Es la precondición del primer slice; el *esqueleto que
87
92
  camina* se construye **encima** del scaffold ya existente.
88
93
 
89
- 1. Lee `build-state.json`. Si `scaffold.confirmed` ya es `true` continúa a la Fase 1 (dor).
94
+ 1. Consulta el hecho: `bash .claude/hooks/build/slice-ops.sh status` (campo `scaffold`). Si ya
95
+ está confirmado → continúa a la Fase 1 (dor).
90
96
  2. Si es `false` → **pregunta explícitamente** (AskUserQuestion): *"¿Existe un scaffold runnable del
91
97
  proyecto (arranca vacío: el script de build/dev corre sin error)?"*
92
98
  - **No** → **STOP**. Indica crearlo según el stack permitido (`.claude/config/stack-allowlist.json`
93
99
  / el PRD técnico). **No lo generes tú.** No abras el slice.
94
- - **Sí** → registra en el estado `scaffold.confirmed=true` (con `confirmed_by`, `confirmed_at`,
95
- `notes` p.ej. "build/dev arranca vacío sin error") y continúa.
100
+ - **Sí** → reporta el hecho confirmado por la persona:
101
+ `slice-ops.sh fact scaffold-confirmed --by «quién» --notes «build/dev arranca vacío sin error»`
102
+ (en modo `legacy` el comando devuelve rc 3 y lo escribes en el fichero) y continúa.
96
103
  3. El gate lo valida también el `dor-dod-gatekeeper` (criterio duro de DoR) y lo respalda el hook
97
104
  determinista `scaffold-guard.sh` (bloquea escribir código de slice sin scaffold confirmado).
98
105
 
99
106
  ## Fase 0-bis · Fuente de diseño (seguro para slices con UI)
100
107
 
101
108
  Espejo de la Fase 0, para proyectos **con UI**. Antes de abrir el primer slice con UI:
102
- 1. Lee `design_source` en `build-state.json`.
109
+ 1. Consulta la fuente de diseño con `slice-ops.sh status` (campo `design_source`).
103
110
  - `applies` indeterminado (ausente) → pregunta *"¿este proyecto tiene UI?"* y fija `applies`.
104
111
  - `applies === false` → N/A, salta esta fase.
105
112
  - `confirmed === true` → continúa.
@@ -108,7 +115,8 @@ Espejo de la Fase 0, para proyectos **con UI**. Antes de abrir el primer slice c
108
115
  - **No** → **STOP**. Ofrece dos salidas: **generarla con `/build:prototype`** (skill
109
116
  `prototyping-screens`; la confirmación sigue siendo humana) o declarar una fuente externa
110
117
  (ruta/URL del prototipo o export). No abras el slice con UI sin fuente confirmada.
111
- - **Sí** → registra `design_source.source`, `confirmed=true`, `confirmed_by`, `confirmed_at`, `notes`.
118
+ - **Sí** → repórtalo:
119
+ `slice-ops.sh fact design-source --applies true --source «ruta/URL» --confirmed true --by «quién»`.
112
120
  3. Lo respalda el hook determinista `design-source-guard.sh` (bloquea código de slice UI sin fuente
113
121
  confirmada) y lo valida el `dor-dod-gatekeeper` (criterio duro de DoR).
114
122
 
@@ -121,7 +129,7 @@ Espejo de la Fase 0, para proyectos **con UI**. Antes de abrir el primer slice c
121
129
  | 3 · tdd | red → green → refactor | `superpowers:test-driven-development` | `tdd` | — |
122
130
  | 4 · smoke | Recorrer el journey-hasta-aquí end-to-end con el **runner determinista fuera-de-chat** (`integration-check`: suite+build+reporte) en **sesión/contexto virgen**; **slices con UI:** fidelidad por **verificación visual REAL** (MCP chrome-devtools, screenshot app vs prototipo) | runner `integration-check`, skill `verify`/`run` + MCP chrome-devtools, `ux-fidelity-reviewer` | `journey_smoke`,`fidelity` | `integration-check.md`, `mcp-map.md` |
123
131
  | 5 · api/data | contratos + consistencia (si aplican al slice) | `api-contract-tester`, `data-consistency-checker` | `api`,`data` | `newman-tests.md`, `data-consistency.md` |
124
- | 6 · dod | **Primero** verificación adversarial INDEPENDIENTE del cableado (contexto virgen: refuta stubs/rutas sin cablear/AC sin test/items `failing`) → `wiring_verified`; **solo entonces** Definition of Done | `wiring-adversarial-verifier`, `dor-dod-gatekeeper` | `wiring_verified`,`dod` | `dod.md`, `integration-check.md` |
132
+ | 6 · dod | **Primero** verificación adversarial INDEPENDIENTE del cableado (contexto virgen: refuta stubs/rutas sin cablear/AC sin test/items `failing`) → `wiring_verified`; **solo entonces** Definition of Done. **Máximo 2 pasadas completas por slice** (condición de parada, ver abajo) | `wiring-adversarial-verifier`, `dor-dod-gatekeeper` | `wiring_verified`,`dod` | `dod.md`, `integration-check.md` |
125
133
  | 7 · pr | Abrir PR + archivar change en el mismo PR | `opsx:archive`, `opsx:sync` | — | `gitflow.md` |
126
134
  | 8 · release? | Preguntar si correr el Release Gate ahora | usuario (default computado) | — | abajo |
127
135
 
@@ -139,7 +147,26 @@ chrome-devtools** (screenshot app vs prototipo). Sin MCP, `fidelity` queda `fals
139
147
  pantalla del prototipo en alcance sin construir; ninguna pantalla de la app sin HU/EP) y el
140
148
  **journey-smoke de clic real como tenant no-admin**.
141
149
 
142
- MCP/LSP por gate: ver `references/mcp-map.md`. Protocolo de estado: `references/state-protocol.md`.
150
+ **Condición de parada del bucle adversarial (fase dod): máximo 2 pasadas completas por slice.** Sin
151
+ esta cota, tres reglas legítimas (sesgo «está incompleto», «nadie cierra sus propios gates» y el
152
+ patrón medido de que los arreglos siembran hallazgos nuevos) se combinan en un bucle sin salida. La
153
+ salida definida es esta y no depende de que alguien corte el bucle a mano:
154
+ - **Pasada 1** completa; **pasada 2** incremental (solo items cuyo código cambió desde su
155
+ `verified_at_sha` + el diff de los arreglos y sus rutas gemelas).
156
+ - Los hallazgos de la 2ª pasada que estén en **código nuevo del cierre** se arreglan y se cierran
157
+ con **mutación verificada** (re-ejecutar la evidencia del item afectado tras el fix) — **sin
158
+ tercera pasada**.
159
+ - La revisión independiente restante se **difiere EXPLÍCITAMENTE al Release Gate**
160
+ (`releasing-a-version`), declarándolo en el PR del slice: los gates `wiring_verified`/`dod` se
161
+ cierran **honestos** (todo lo verificado, verificado; lo diferido, declarado — no es recorte en
162
+ silencio, lo custodia el gate que ya existe para eso).
163
+ - **Excepción única**: un hallazgo ALTA en código **preexistente** durante la 2ª pasada habilita una
164
+ pasada extra **acotada a ese frente** (nunca una tercera pasada completa).
165
+
166
+ MCP/LSP por gate: ver `references/mcp-map.md`. Protocolo de estado: **`references/runtime-protocol.md`**
167
+ (modos `dual`/`runtime`, vía `slice-ops.sh`) o `references/state-protocol.md` (modo `legacy`).
168
+ Presupuesto de mutación y anclaje de la evidencia (determinista vs viva): `references/evidence-budget.md`.
169
+ ¿Suite del destino con rojos heredados? Medición de regresión con baseline cacheado por sha: `references/regression-baseline.md` (script `assets/baseline-verdict.sh`).
143
170
 
144
171
  ## Fase 8 · ¿Release Gate ahora? (default computado, humano decide)
145
172
 
@@ -159,8 +186,9 @@ El usuario siempre puede sobreescribir el default. Si acepta, invoca la skill
159
186
  primero (pregunta explícita; STOP si no existe). Sin scaffold confirmado no se abre slice.
160
187
  2. Pregunta/identifica la **épica** objetivo (`EP-XXX` en `docs/03-backlog/epicas.md`) y reúne las
161
188
  **HU que cubre** (las que tienen `epica: EP-XXX` en `docs/04-historias/`) → poblarán `hus[]`.
162
- 3. Lee `build-state.json`. Si hay `active_slice`, retoma su primer gate abierto; si es `null`,
163
- arranca en **dor**.
189
+ 3. Reclama o retoma: `slice-ops.sh claim --epic EP-XXX` (rc 7 = sin trabajo; rc 3 = modo legacy o
190
+ dual, donde el slice lo decide el fichero). Luego `slice-ops.sh next-step` te dice dónde seguir;
191
+ si no hay slice, arranca en **dor**.
164
192
  4. Invoca al `build-orchestrator` para conducir el pipeline, o ejecuta fase a fase tú mismo
165
193
  respetando los gates.
166
194
 
@@ -178,9 +206,15 @@ El usuario siempre puede sobreescribir el default. Si acepta, invoca la skill
178
206
  generador y verificador produce auto-confirmación. La generación y la verificación van separadas.
179
207
  Opcionalmente, esa verificación se **conduce** con la plantilla read-only
180
208
  `workflows/wiring-verify.workflow.js` (envuelve al `wiring-adversarial-verifier`); `build-orchestrator`
181
- sigue siendo quien **escribe** `gates.wiring_verified` a partir del veredicto que la plantilla devuelve.
209
+ sigue siendo quien **escribe** `gates.wiring_verified` a partir del veredicto que la plantilla devuelve
210
+ y quien **estampa** `verified_at_sha` en los items reproducidos. **Máximo 2 pasadas completas por
211
+ slice** (condición de parada de la fase dod, ver arriba); lo diferido se declara en el PR y lo
212
+ custodia el Release Gate.
182
213
  - **Producto completo, no MVP.** El alcance acordado se construye entero. **Recortar o diferir es
183
214
  bloqueante explícito** que requiere acuerdo del equipo — nunca una decisión del modelo. No derives
184
215
  en lo complejo. La verificación es **ejecutada, no por inspección** (ver `METODOLOGIA.md` y el
185
216
  bloque del arnés en `CLAUDE.md`).
217
+ - **Todo acto de dominio pasa por `slice-ops.sh`**: nunca armes peticiones al runtime a mano ni
218
+ edites el fichero de estado en modo `runtime`. Un `rc 6` es la compuerta del servidor funcionando:
219
+ muestra su razón y corrige la causa, no lo reintentes.
186
220
  - Si una regla aquí contradice la metodología Trycore (`METODOLOGIA.md`), **gana la metodología**.
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env bash
2
+ # baseline-verdict.sh — veredicto de baseline cacheado por sha (skill building-a-slice).
3
+ #
4
+ # Responde «¿rompí algo?» en proyectos con tests rojos «ambientales» heredados SIN pagar
5
+ # el ritual de worktree + suite ×2 en cada medición: cachea el conjunto de ficheros rojos
6
+ # del baseline (origin/<destino>) en un JSON versionable por sha, y la medición de
7
+ # regresión pasa a ser UNA corrida de la suite del working tree + comparación estilo comm.
8
+ #
9
+ # Uso:
10
+ # baseline-verdict.sh capture <ref> # captura el veredicto del baseline en un worktree temporal
11
+ # baseline-verdict.sh compare <ref> # corre la suite actual 1 vez y compara contra el cache
12
+ #
13
+ # <ref> DEBE ser un ref remoto (`origin/<rama>`) o un sha verificado como remoto
14
+ # (`git branch -r --contains <sha>` no vacío). Los refs locales se RECHAZAN: comparar
15
+ # contra un ref local obsoleto ya produjo una medición falsa (issue #30 del arnés).
16
+ #
17
+ # Parametrización (env), agnóstica del runner de tests:
18
+ # BASELINE_SUITE_CMD comando de la suite (default: `npm test`; con vitest, p.ej.
19
+ # `npx vitest run`). Puede salir ≠0 — los rojos son el dato, no un error.
20
+ # BASELINE_RED_FILTER pipeline shell que lee la salida de la suite por stdin y emite un
21
+ # fichero rojo por línea. Default (formato `FAIL <fichero> …`, como el
22
+ # reporter de vitest): grep '^\s*FAIL\s' | awk '{print $2}' | sort -u
23
+ # BASELINE_DIR dónde viven los JSON (default: `.baseline/` en la raíz del repo).
24
+ # BASELINE_LINK_DIRS dirs a symlinkear de la raíz al worktree temporal en `capture`
25
+ # (default: `node_modules`; separa con espacios; vacío = ninguno).
26
+ # BASELINE_NO_FETCH=1 no hacer `git fetch origin` antes de resolver el ref.
27
+ #
28
+ # Salidas: 0 sin regresiones · 1 regresiones nuevas · 2 uso/guard de ref · 3 cache
29
+ # inválido o inexistente (re-captura) · 4 error de entorno (git/worktree/suite ilocalizable).
30
+ set -uo pipefail
31
+
32
+ SUITE_CMD="${BASELINE_SUITE_CMD:-npm test}"
33
+ RED_FILTER="${BASELINE_RED_FILTER:-grep -E '^[[:space:]]*FAIL([[:space:]]|\$)' | awk '{print \$2}' | sort -u}"
34
+ LINK_DIRS="${BASELINE_LINK_DIRS-node_modules}"
35
+
36
+ usage() {
37
+ sed -n '2,29p' "$0" | sed 's/^# \{0,1\}//'
38
+ }
39
+
40
+ err() { echo "✗ $*" >&2; }
41
+ info() { echo "▶ $*"; }
42
+
43
+ ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { err "no estamos dentro de un repo git"; exit 4; }
44
+ cd "$ROOT" || exit 4
45
+ BASE_DIR="${BASELINE_DIR:-$ROOT/.baseline}"
46
+
47
+ CMD="${1:-}"; REF="${2:-}"
48
+ case "$CMD" in
49
+ capture|compare) : ;;
50
+ -h|--help|help|"") usage; exit 2 ;;
51
+ *) err "subcomando desconocido: $CMD"; usage; exit 2 ;;
52
+ esac
53
+ [ -n "$REF" ] || { err "falta <ref> (p.ej. origin/main)"; exit 2; }
54
+
55
+ # ── Guard de ref remoto ──────────────────────────────────────────────────────
56
+ # Acepta SOLO `origin/...` o un sha contenido en alguna rama remota. Nunca refs
57
+ # locales: `main` local puede estar detrás de `origin/main` y la medición sale falsa.
58
+ if [ "${BASELINE_NO_FETCH:-0}" != 1 ]; then
59
+ git fetch --quiet origin 2>/dev/null || info "aviso: git fetch origin falló; sigo con los refs remotos ya conocidos"
60
+ fi
61
+
62
+ SHA=""
63
+ if [[ "$REF" == origin/* ]]; then
64
+ SHA="$(git rev-parse --verify --quiet "refs/remotes/${REF}^{commit}")" \
65
+ || { err "el ref remoto '$REF' no existe (¿git fetch origin?)"; exit 2; }
66
+ elif [[ "$REF" =~ ^[0-9a-f]{7,40}$ ]]; then
67
+ SHA="$(git rev-parse --verify --quiet "${REF}^{commit}")" \
68
+ || { err "el sha '$REF' no existe en este repo"; exit 2; }
69
+ if [ -z "$(git branch -r --contains "$SHA" 2>/dev/null)" ]; then
70
+ err "el sha '$REF' no está contenido en ninguna rama remota (git branch -r --contains vacío)."
71
+ err "GUARD: la comparación es SIEMPRE contra origin/<destino>, nunca contra un ref local"
72
+ err "(este error ya ocurrió y costó una medición falsa). Usa origin/<rama> o un sha ya pusheado."
73
+ exit 2
74
+ fi
75
+ else
76
+ err "ref local rechazado: '$REF'."
77
+ err "GUARD: la comparación es SIEMPRE contra origin/<destino>, nunca contra un ref local"
78
+ err "(este error ya ocurrió y costó una medición falsa). Usa origin/<rama> o un sha remoto."
79
+ exit 2
80
+ fi
81
+
82
+ CACHE_FILE="$BASE_DIR/baseline-verdict.${SHA}.json"
83
+
84
+ # ── Suite → conjunto ordenado de ficheros rojos ──────────────────────────────
85
+ # Corre BASELINE_SUITE_CMD en $1 (dir), deja los rojos (uno por línea, sort -u) en $2.
86
+ run_suite_red_files() {
87
+ local dir="$1" out="$2" raw
88
+ raw="$(mktemp)"
89
+ info "corriendo suite en $dir: $SUITE_CMD"
90
+ ( cd "$dir" && bash -c "$SUITE_CMD" ) >"$raw" 2>&1
91
+ local rc=$?
92
+ [ $rc -ne 0 ] && info "la suite salió con rc=$rc (esperable con rojos heredados)"
93
+ bash -c "$RED_FILTER" <"$raw" | LC_ALL=C sort -u >"$out"
94
+ rm -f "$raw"
95
+ }
96
+
97
+ # ─────────────────────────────────────────────────────────────────────────────
98
+ if [ "$CMD" = capture ]; then
99
+ WT="$(mktemp -d)/baseline-wt"
100
+ cleanup() { git worktree remove --force "$WT" >/dev/null 2>&1; rm -rf "$(dirname "$WT")"; }
101
+ trap cleanup EXIT
102
+ git worktree add --quiet --detach "$WT" "$SHA" || { err "no pude crear el worktree temporal de $SHA"; exit 4; }
103
+ for d in $LINK_DIRS; do
104
+ [ -e "$ROOT/$d" ] && [ ! -e "$WT/$d" ] && ln -s "$ROOT/$d" "$WT/$d"
105
+ done
106
+
107
+ REDS="$(mktemp)"
108
+ run_suite_red_files "$WT" "$REDS"
109
+
110
+ mkdir -p "$BASE_DIR"
111
+ CAPTURED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" SHA="$SHA" REF="$REF" \
112
+ SUITE_CMD="$SUITE_CMD" RED_FILTER="$RED_FILTER" CACHE_FILE="$CACHE_FILE" \
113
+ python3 - "$REDS" <<'PY'
114
+ import json, os, sys
115
+ red = [l.rstrip("\n") for l in open(sys.argv[1]) if l.strip()]
116
+ doc = {
117
+ "sha": os.environ["SHA"],
118
+ "ref": os.environ["REF"],
119
+ "captured_at": os.environ["CAPTURED_AT"],
120
+ "suite_cmd": os.environ["SUITE_CMD"],
121
+ "red_filter": os.environ["RED_FILTER"],
122
+ "red_files": sorted(red),
123
+ }
124
+ with open(os.environ["CACHE_FILE"], "w") as f:
125
+ json.dump(doc, f, ensure_ascii=False, indent=2)
126
+ f.write("\n")
127
+ PY
128
+ [ $? -eq 0 ] || { err "no pude escribir el JSON del veredicto"; exit 4; }
129
+ N="$(grep -c . "$REDS" || true)"
130
+ rm -f "$REDS"
131
+ info "baseline capturado: $N fichero(s) rojo(s) en $REF @ $SHA"
132
+ info "veredicto: $CACHE_FILE (versiónalo en git si quieres compartirlo)"
133
+ exit 0
134
+ fi
135
+
136
+ # ─────────────────────────────────────────────────────────────────────────────
137
+ # compare
138
+ if [ ! -f "$CACHE_FILE" ]; then
139
+ err "no hay veredicto cacheado para $REF @ $SHA — el sha remoto cambió o nunca se capturó."
140
+ OLD="$(ls "$BASE_DIR"/baseline-verdict.*.json 2>/dev/null | head -3)"
141
+ [ -n "$OLD" ] && err "caches existentes (obsoletos para este sha): $(echo "$OLD" | tr '\n' ' ')"
142
+ err "re-captura con: bash ${0##*/} capture $REF"
143
+ exit 3
144
+ fi
145
+
146
+ BASE_REDS="$(mktemp)"; CUR_REDS="$(mktemp)"
147
+ trap 'rm -f "$BASE_REDS" "$CUR_REDS"' EXIT
148
+ python3 -c 'import json,sys; [print(f) for f in json.load(open(sys.argv[1]))["red_files"]]' \
149
+ "$CACHE_FILE" | LC_ALL=C sort -u >"$BASE_REDS" \
150
+ || { err "el cache $CACHE_FILE no es un veredicto válido; re-captura"; exit 3; }
151
+
152
+ run_suite_red_files "$ROOT" "$CUR_REDS"
153
+
154
+ NEW="$(LC_ALL=C comm -13 "$BASE_REDS" "$CUR_REDS")" # rojo ahora, no estaba: REGRESIÓN
155
+ FIXED="$(LC_ALL=C comm -23 "$BASE_REDS" "$CUR_REDS")" # estaba rojo, ya no: arreglado
156
+ SAME="$(LC_ALL=C comm -12 "$BASE_REDS" "$CUR_REDS")" # rojo en ambos: heredado sin cambio
157
+
158
+ count() { [ -n "$1" ] && printf '%s\n' "$1" | grep -c . || echo 0; }
159
+ echo ""
160
+ echo "── veredicto vs $REF @ $SHA (capturado $(python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["captured_at"])' "$CACHE_FILE")) ──"
161
+ echo " regresiones nuevas : $(count "$NEW")"
162
+ [ -n "$NEW" ] && printf '%s\n' "$NEW" | sed 's/^/ ✗ /'
163
+ echo " arreglados : $(count "$FIXED")"
164
+ [ -n "$FIXED" ] && printf '%s\n' "$FIXED" | sed 's/^/ ✓ /'
165
+ echo " heredados sin cambio: $(count "$SAME")"
166
+
167
+ if [ -n "$NEW" ]; then
168
+ err "hay regresiones nuevas respecto del baseline $REF"
169
+ exit 1
170
+ fi
171
+ info "sin regresiones nuevas respecto del baseline $REF"
172
+ exit 0
@@ -1,7 +1,7 @@
1
1
  # Definition of Done (DoD) — checklist de salida **por slice** (inner loop)
2
2
 
3
3
  Un slice (épica) **no se archiva ni se mergea** hasta cumplir TODO esto. Lo valida
4
- `dor-dod-gatekeeper` leyendo los gates de `build-state.json`. Es el DoD **reducido**: las revisiones
4
+ `dor-dod-gatekeeper` leyendo los gates del slice (`slice-ops.sh status`; en modo legacy, del fichero). Es el DoD **reducido**: las revisiones
5
5
  pesadas (seguridad, diseño, UX, coherencia triple, arquitectura, integración) **no** se piden aquí
6
6
  — se piden una vez por release en el **Release Gate** (`release-dod.md` de la skill
7
7
  `releasing-a-version`).
@@ -26,9 +26,18 @@ pesadas (seguridad, diseño, UX, coherencia triple, arquitectura, integración)
26
26
  duro de `dod`**: el DoD declarativo de este checklist es un **piso, no el arreglo** (la auto-confirmación
27
27
  surge de reusar el mismo agente como generador y verificador). Esa verificación puede conducirse,
28
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).
29
+ `build-orchestrator` es quien escribe el gate a partir de su veredicto y estampa `verified_at_sha`
30
+ en los items cuya evidencia fue reproducida — las pasadas 2+ son **incrementales**: solo re-ejecutan
31
+ items cuyo código cambió desde su sha).
32
+ **Condición de parada: máximo 2 pasadas completas por slice.** Hallazgos de la 2ª pasada en código
33
+ nuevo del cierre → se arreglan y se cierran con **mutación verificada**, sin tercera pasada; la
34
+ revisión independiente restante se **difiere explícitamente al Release Gate** y se declara en el PR
35
+ (gates honestos, no recorte en silencio). Excepción: un hallazgo ALTA en código **preexistente**
36
+ durante la 2ª pasada habilita una pasada extra **acotada** a ese frente.
30
37
  - [ ] **OpenSpec**: todas las tasks `[x]`; el archive del change va **en el mismo PR** (no PR aparte).
31
- - [ ] **Docs/trazabilidad**: back-ref del change añadida en la épica y en cada HU de `hus[]`.
38
+ - [ ] **Docs/trazabilidad**: back-ref del change añadida en la épica y en cada HU de `hus[]`. Si la
39
+ condición de parada del bucle adversarial aplicó, el **PR declara** qué revisión quedó diferida al
40
+ Release Gate.
32
41
  - [ ] **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`).
33
42
 
34
43
  **Todo ✓** → `gates.dod: true`; se hace `opsx:archive` + se abre/mergea el PR. **Algo ✗** → listar
@@ -11,7 +11,7 @@ la épica; lo de abajo aplica a la épica y a **cada HU** que cubre (`hus[]`). L
11
11
  - [ ] **Dependencias resueltas**: las épicas/HU de las que depende están archivadas (`history[]`) o explícitamente no bloquean. **Excepción dura — cimiento:** si la dependencia es **infraestructura fundacional** (autenticación, acceso a datos, arquitectura base, design-system/componentes base), la cláusula "explícitamente no bloquean" **NO aplica**: debe estar **construida y archivada** antes (ver criterio "Cimiento construido").
12
12
  - [ ] **Cimiento construido (épicas de negocio)**: si esta épica es `layer: business`, todo el cimiento que arrastra (auth, acceso a datos, arquitectura base, design-system/componentes base) ya existe como épica(s) `layer: foundational` **archivada(s)** en `history[]`. Si arrastra cimiento no construido → **STOP**: extráelo a una épica fundacional previa y constrúyela primero. Las épicas fundacionales se priorizan **antes** que las de negocio.
13
13
  - [ ] **Caparazón construido (solo greenfield — gate PROACTIVO)**: si `project_kind === "greenfield"`
14
- y `foundation.required === true` en `build-state.json`, ninguna épica `layer: business` entra a
14
+ y `foundation.required === true` en los hechos de proyecto del runtime (`slice-ops.sh status`; en modo legacy, `build-state.json`), ninguna épica `layer: business` entra a
15
15
  construcción mientras la épica caparazón (`foundation.epic`) no esté **archivada con su checklist
16
16
  evidenciada** (`foundation.completed_at` estampado). Las épicas `layer: foundational` (la
17
17
  caparazón `foundation.epic` u otras fundacionales) sí pueden abrir. A diferencia de "Cimiento
@@ -40,7 +40,8 @@ la épica; lo de abajo aplica a la épica y a **cada HU** que cubre (`hus[]`). L
40
40
  - [ ] **Clasificación `layer`**: `foundational` (auth/datos/design-system/arquitectura base) | `business`. Se escribe en `active_slice.layer`. Gatea el front paralelo (foundational nunca en paralelo).
41
41
  - [ ] **`files_scope`**: globs de los archivos que la épica tocará (p.ej. `src/reports/**`). Fuente de la disjunción inter-épica. Se escribe en `active_slice.files_scope`.
42
42
 
43
- **Si todo ✓** → `dor-dod-gatekeeper` abre `active_slice` en `build-state.json` con `epica`, `hus[]`,
43
+ **Si todo ✓** → el slice se abre por el claim del runtime (`slice-ops.sh claim --epic EP-XXX`), que
44
+ devuelve `slice_id`, épica, historias,
44
45
  `phase: dor`, `gates.dor: true` y el resto en `false` —incluido **`wiring_verified: false`**—
45
46
  (`fidelity`/`api` en `null` si la épica no toca UI/endpoints; `fidelity` arranca en `false` si toca UI).
46
47
  **Si algo ✗** → no se abre el slice; se reporta qué falta y se vuelve a discovery (Trycore).
@@ -0,0 +1,51 @@
1
+ # Presupuesto de mutación y clases de evidencia — inner loop (fase tdd→dod)
2
+
3
+ > **Si una regla aquí contradice la metodología Trycore (`METODOLOGIA.md`), gana la metodología.**
4
+
5
+ Estas reglas acotan **dónde rinde el rigor** del inner loop sin bajarlo donde importa. Aplican a la
6
+ construcción de slices (fase tdd→dod de `building-a-slice`). El carril `building-a-micro-change`
7
+ está **exento** (ver su SKILL.md): allí basta 1 test de regresión si cambia comportamiento + la
8
+ suite del módulo tocado en verde.
9
+
10
+ ## Presupuesto de mutación
11
+
12
+ La regla «un test no vale hasta que su mutación lo tumba» nació de un patrón real (tests-teatro:
13
+ fixtures irreales, comparaciones vacuas, skips que siempre saltan) y **no se elimina — se acota**:
14
+
15
+ - **Obligatoria** para los tests que sostienen un item de `wiring_checklist[]` (escenarios AC de HU,
16
+ puntos de integración entre capas): son los que **compran el veredicto del gate**. La evidencia de
17
+ un item del checklist sigue exigiendo la mutación que lo demuestra — manual o por reporte
18
+ automatizado.
19
+ - **Opcional** para tests auxiliares de helpers/unidades internas. Recomendada solo si ese test es
20
+ la **única defensa de un invariante**; en el resto, la suite en verde basta.
21
+
22
+ ### Vía automatizada (alternativa al ciclo manual)
23
+
24
+ El ciclo manual — editar producción → correr la suite → revertir, N veces — puede sustituirse por
25
+ **una corrida de testing por mutación acotada a los ficheros cambiados del slice** (la herramienta
26
+ la aporta el stack del consumidor; p. ej. Stryker en stacks JS/TS, con `--mutate` restringido al
27
+ diff). El **reporte** de esa corrida es la evidencia: cada mutante sobreviviente en un fichero que
28
+ sostiene un item del checklist es un hueco a cerrar. El arnés no impone ni configura la herramienta;
29
+ si el stack no tiene una, el ciclo manual sigue valiendo — pero solo dentro del presupuesto de
30
+ arriba.
31
+
32
+ ## Dos clases de evidencia y su anclaje
33
+
34
+ Todo artefacto de evidencia declara `{sha, hora, rama}`. Lo que cambia entre clases es **a qué sha
35
+ se ancla** y **cuándo caduca**:
36
+
37
+ | Clase | Qué es | Ancla | Caduca cuando |
38
+ |---|---|---|---|
39
+ | **Determinista** | Runners sin LLM: suites, smoke, contratos | **HEAD** (se re-ancla siempre) | HEAD se mueve (regenerar cuesta segundos) |
40
+ | **Viva** | Corridas contra el **LLM real del proyecto** | **Último commit que tocó el módulo bajo medición** | Cambió el código que mide — y solo entonces |
41
+
42
+ Para la evidencia **viva**:
43
+
44
+ - El ancla se obtiene con `git log -1 --format=%H -- <rutas del módulo>`. Un commit de docs, de
45
+ estado o de **otro** módulo **no la invalida**.
46
+ - El artefacto declara ambos valores:
47
+ - `anchored_at.sha` — el sha del módulo (**normativo**: contra este se valida el anclaje).
48
+ - `head_at_run` — HEAD al momento de la corrida (**informativo**).
49
+ - **Regenerar solo cuando cambió el código que mide** (el sha del módulo se movió). Re-anclar por
50
+ commits ajenos al módulo es la cinta de correr que esta regla elimina: cada regeneración mueve
51
+ HEAD y fabrica el "hallazgo" de la pasada siguiente.
@@ -13,7 +13,7 @@ profundo" puede repartirse con subagentes **solo-lectura por área** (frontend/b
13
13
 
14
14
  ## Blindaje read-only de cada subagente de área (al nivel de los reviewers pesados)
15
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.
16
+ el estado del slice; tu única salida es la síntesis"* y **rechaza** cualquier instrucción de escribir.
17
17
  - **Tools restringidas**: `Read`/`Grep`/`Glob` (sin `Edit`/`Write`/`Bash`-mutante). Opcionalmente, el tipo
18
18
  de agente built-in `Explore` (read-only).
19
19
  - **Fail-closed**: si un subagente devuelve algo que no sea síntesis (p.ej. un diff), se **descarta y se
@@ -8,7 +8,7 @@ de forma determinista (bloquea commit/push directo a `main` y ramas no tipadas).
8
8
  ```bash
9
9
  git switch main && git pull --ff-only # parte de main al día
10
10
  git switch -c feature/<slug> # rama tipada: feature/* | fix/* | chore/*
11
- # ... TDD + gates (estado en build-state.json) ...
11
+ # ... TDD + gates (se reportan con slice-ops.sh gate) ...
12
12
  git add -A && git commit -m "<tipo>: <mensaje>" # commit en la rama feature (nunca en main)
13
13
  git push -u origin feature/<slug> # push de la rama feature (nunca a main)
14
14
  gh pr create --base main --head feature/<slug> --fill # integración por PR
@@ -0,0 +1,67 @@
1
+ # Baseline de regresión — «¿rompí algo?» con tests rojos heredados
2
+
3
+ Cómo medir regresión de un slice cuando la suite del destino **no está verde** por tests rojos
4
+ «ambientales» heredados. Sin esto, la pregunta «¿rompí algo?» exige el ritual completo (worktree
5
+ del destino + suite ×2 + comparar conjuntos de rojos) en **cada** medición — un impuesto de
6
+ ~35 min que paga todo slice. Si algo aquí contradice `METODOLOGIA.md`, **gana la metodología**.
7
+
8
+ ## Dos vías (no excluyentes)
9
+
10
+ 1. **La buena — suite verde.** Sanear o cuarentenar los rojos heredados (skip anotado con su
11
+ issue, o arreglo) hasta que la suite del destino esté verde. Entonces «¿rompí algo?» vuelve a
12
+ ser **una** corrida normal y esta referencia deja de aplicar. Es **trabajo del proyecto
13
+ consumidor** (sus suites, sus issues): el arnés no lo hace por él, pero es el estado objetivo.
14
+ 2. **La pragmática — baseline cacheado por sha** (mientras la suite no esté verde). El veredicto
15
+ del baseline (el conjunto de ficheros rojos de `origin/<destino>`) se captura **una vez** y se
16
+ cachea en un JSON versionable (`baseline-verdict.<sha>.json`). La medición de regresión de un
17
+ slice pasa a ser: **1 corrida de la suite del working tree + comparación estilo `comm`** contra
18
+ el JSON. Ni worktree ni segunda suite por medición.
19
+
20
+ ## El script: `assets/baseline-verdict.sh`
21
+
22
+ Bash portable (macOS/Linux), agnóstico del runner de tests. En el consumidor queda instalado en
23
+ `.claude/skills/building-a-slice/assets/baseline-verdict.sh`.
24
+
25
+ ```bash
26
+ # 1) capturar el baseline (una vez por sha del destino; ~el coste del ritual, pagado UNA vez)
27
+ bash .claude/skills/building-a-slice/assets/baseline-verdict.sh capture origin/main
28
+
29
+ # 2) medir regresión del slice (cada vez que haga falta; 1 corrida de suite)
30
+ bash .claude/skills/building-a-slice/assets/baseline-verdict.sh compare origin/main
31
+ ```
32
+
33
+ - `capture <ref>` corre la suite del sha remoto en un **worktree temporal** (symlinkeando
34
+ `node_modules` por defecto, `BASELINE_LINK_DIRS`) y escribe
35
+ `.baseline/baseline-verdict.<sha>.json` con el conjunto ordenado de ficheros rojos +
36
+ metadatos (`captured_at`, `suite_cmd`, `red_filter`). Versiona el JSON en git si quieres que
37
+ todo el equipo/agente reuse la captura.
38
+ - `compare <ref>` corre la suite del working tree **una vez** y reporta tres conjuntos:
39
+ **regresiones nuevas** (rojo que no estaba en el baseline → sale con rc 1), **arreglados** y
40
+ **heredados sin cambio** (no cuentan como regresión). **Invalidación automática**: si el sha de
41
+ `origin/<destino>` ya no coincide con ningún cache, lo dice y pide re-capturar (rc 3).
42
+
43
+ ### Parametrización (agnóstica del runner)
44
+
45
+ | Variable | Default | Qué es |
46
+ |---|---|---|
47
+ | `BASELINE_SUITE_CMD` | `npm test` | Comando de la suite. Con vitest, p.ej. `npx vitest run`. Puede salir ≠0 (los rojos son el dato). |
48
+ | `BASELINE_RED_FILTER` | `grep -E '^[[:space:]]*FAIL([[:space:]]\|$)' \| awk '{print $2}' \| sort -u` | Pipeline shell que lee la salida de la suite y emite un fichero rojo por línea (el default entiende el formato `FAIL <fichero> …` de reporters tipo vitest; ajústalo a tu runner). |
49
+ | `BASELINE_DIR` | `.baseline/` en la raíz | Dónde viven los JSON de veredicto. |
50
+ | `BASELINE_LINK_DIRS` | `node_modules` | Dirs de la raíz a symlinkear en el worktree de `capture`. |
51
+ | `BASELINE_NO_FETCH=1` | — | No hacer `git fetch origin` antes de resolver el ref. |
52
+
53
+ ## Guard: siempre contra `origin/<destino>`, nunca un ref local
54
+
55
+ El script **rechaza refs locales** (rc 2): solo acepta `origin/<rama>` o un sha verificado como
56
+ remoto (`git branch -r --contains <sha>` no vacío). Comparar contra la rama local (que puede ir
57
+ detrás del remoto) **ya produjo una medición falsa** que costó un hallazgo ALTA corregir — el
58
+ guard no es negociable y no se puentea editando el JSON a mano.
59
+
60
+ ## Cuándo usar cada cosa
61
+
62
+ - Suite del destino **verde** → ni cache ni ritual: una corrida normal (esta referencia N/A).
63
+ - Suite del destino **con rojos heredados** → `compare` contra el cache en cada medición del
64
+ slice; `capture` solo cuando el destino avanza (el propio `compare` avisa con rc 3).
65
+ - El cache **no sustituye** los gates del pipeline (`tdd`, `journey_smoke`, `wiring_verified`):
66
+ solo abarata la pregunta «¿introduje regresiones respecto del destino?». Y cada slice es una
67
+ oportunidad de mover rojos heredados a la vía buena (arreglar o cuarentenar con issue).