@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.
- package/.claude-plugin/plugin.json +1 -1
- package/GOVERNANCE.md +27 -4
- package/INSTALL.md +27 -5
- package/METODOLOGIA.md +55 -5
- package/README.md +39 -6
- package/VERSION +1 -1
- package/agents/build/build-orchestrator.md +33 -7
- package/agents/build/dor-dod-gatekeeper.md +13 -5
- package/agents/build/wiring-adversarial-verifier.md +52 -5
- package/commands/build/architect.md +1 -1
- package/commands/build/claim.md +46 -0
- package/commands/build/escalate.md +36 -0
- package/commands/build/front.md +9 -3
- package/commands/build/onboard.md +59 -14
- package/commands/build/prototype.md +3 -2
- package/commands/build/reflect.md +60 -40
- package/commands/build/release.md +10 -7
- package/commands/build/resume.md +33 -13
- package/commands/build/slice.md +32 -27
- package/commands/build/status.md +35 -0
- package/commands/build/work.md +11 -8
- package/config/build-config.template.json +4 -0
- package/dist/cli.js +22 -0
- package/dist/commands/doctor.js +42 -0
- package/dist/commands/init.js +84 -1
- package/dist/commands/migrate.js +48 -0
- package/dist/commands/status.js +34 -0
- package/dist/lib/normalize.js +276 -0
- package/dist/lib/paths.js +6 -0
- package/dist/lib/runtime-client.js +196 -0
- package/dist/lib/settings-merge.js +3 -3
- package/dist/lib/state-bundle.js +46 -0
- package/docs/commands.md +25 -8
- package/docs/getting-started.md +1 -0
- package/docs/hooks.md +114 -27
- package/docs/runtime/guia-modo-dual-y-migracion.md +136 -0
- package/docs/runtime/plan-migracion-harness-v0.9.md +11 -0
- package/docs/runtime/protocolo-cliente-runtime.md +109 -34
- package/hooks/build/build-gate-check.sh +21 -0
- package/hooks/build/context-monitor.sh +82 -15
- package/hooks/build/context-sync.sh +192 -0
- package/hooks/build/design-source-guard.sh +30 -2
- package/hooks/build/dual-compare.sh +92 -0
- package/hooks/build/event-emitter.sh +75 -0
- package/hooks/build/gitflow-guard.sh +164 -14
- package/hooks/build/heartbeat.sh +259 -0
- package/hooks/build/lib/agent-context.sh +139 -0
- package/hooks/build/lib/config.sh +27 -0
- package/hooks/build/lib/projection.sh +71 -0
- package/hooks/build/lib/runtime-client.sh +465 -0
- package/hooks/build/lib/runtime-ops.sh +221 -0
- package/hooks/build/lib/state-io.sh +5 -18
- package/hooks/build/load-build-state.sh +64 -2
- package/hooks/build/reflect-nudge.sh +15 -0
- package/hooks/build/release-gate-nudge.sh +15 -0
- package/hooks/build/release-ops.sh +164 -0
- package/hooks/build/scaffold-guard.sh +29 -2
- package/hooks/build/session-start.sh +103 -0
- package/hooks/build/session-stop.sh +22 -0
- package/hooks/build/slice-ops.sh +877 -0
- package/hooks/build/stack-guard.sh +8 -0
- package/hooks/build/statusline-bridge.sh +24 -3
- package/hooks/build-harness.json +16 -0
- package/package.json +3 -3
- package/scripts/check-agnostic.sh +3 -1
- package/scripts/check-pack-clean.sh +31 -0
- package/scripts/check-runtime-purity.sh +43 -0
- package/scripts/lib/front-plan.py +4 -0
- package/scripts/lib/graph-bundle.py +133 -0
- package/scripts/runtime-purity-allow.txt +5 -0
- package/scripts/smoke-test.sh +1 -1
- package/scripts/tests/lib/http-stub.py +46 -0
- package/scripts/tests/test-baseline-verdict.sh +92 -0
- package/scripts/tests/test-config.sh +25 -0
- package/scripts/tests/test-hooks-runtime.sh +853 -0
- package/scripts/tests/test-install.sh +57 -0
- package/scripts/tests/test-runtime-client.sh +298 -0
- package/scripts/tests/test-schema.sh +29 -1
- package/scripts/tests/test-skill-ops.sh +847 -0
- package/skills/building-a-micro-change/SKILL.md +22 -4
- package/skills/building-a-slice/SKILL.md +55 -21
- package/skills/building-a-slice/assets/baseline-verdict.sh +172 -0
- package/skills/building-a-slice/references/dod.md +12 -3
- package/skills/building-a-slice/references/dor.md +3 -2
- package/skills/building-a-slice/references/evidence-budget.md +51 -0
- package/skills/building-a-slice/references/exploration-fanout.md +1 -1
- package/skills/building-a-slice/references/gitflow.md +1 -1
- package/skills/building-a-slice/references/regression-baseline.md +67 -0
- package/skills/building-a-slice/references/runtime-protocol.md +75 -0
- package/skills/building-a-slice/references/state-protocol.md +12 -1
- package/skills/building-a-slice/workflows/README.md +7 -3
- package/skills/building-a-slice/workflows/explore-fanout.workflow.js +3 -3
- package/skills/building-a-slice/workflows/wiring-verify.workflow.js +26 -4
- package/skills/managing-parallel-front/SKILL.md +32 -16
- package/skills/openspec-archive-change/SKILL.md +15 -0
- package/skills/prototyping-screens/SKILL.md +9 -5
- package/skills/releasing-a-version/SKILL.md +25 -16
- package/skills/releasing-a-version/references/release-dod.md +7 -5
- package/skills/releasing-a-version/workflows/README.md +2 -1
- package/skills/releasing-a-version/workflows/release-gate.workflow.js +6 -5
- package/skills/setup-architecture/SKILL.md +4 -2
- package/state/README.md +16 -1
- package/state/build-state.schema.json +2 -1
- package/templates/CLAUDE.md.template +16 -0
- package/templates/settings-hooks.template.json +8 -4
- 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`)
|
|
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
|
|
69
|
-
historial de git y el PR. No entra
|
|
70
|
-
reflexionar por él (no hay aprendizaje de épica que
|
|
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)
|
|
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
|
|
25
|
-
|
|
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
|
|
33
|
-
capas): nace `failing`, pasa a `passing` **solo tras prueba real ejecutada
|
|
34
|
-
`
|
|
35
|
-
`
|
|
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
|
|
75
|
-
`build-orchestrator` (o el agente dueño del gate)
|
|
76
|
-
una escritura; gates monótonos;
|
|
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.
|
|
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í** →
|
|
95
|
-
`
|
|
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.
|
|
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í** →
|
|
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
|
-
|
|
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.
|
|
163
|
-
|
|
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
|
|
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
|
|
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 ✓** →
|
|
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
|
-
|
|
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 (
|
|
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).
|