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