@trycore/spec-build-harness 0.1.0 → 0.2.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/METODOLOGIA.md +12 -2
- package/VERSION +1 -1
- package/agents/build/dor-dod-gatekeeper.md +7 -0
- package/dist/commands/doctor.js +15 -0
- package/dist/commands/status.js +1 -0
- package/dist/lib/settings-merge.js +1 -1
- package/dist/lib/state-seed.js +1 -0
- package/docs/superpowers/specs/2026-06-02-scaffold-gate-design.md +87 -0
- package/hooks/build/scaffold-guard.sh +47 -0
- package/hooks/build-harness.json +4 -0
- package/package.json +1 -1
- package/skills/building-a-slice/SKILL.md +27 -5
- package/state/README.md +10 -1
- package/state/build-state.schema.json +15 -2
- package/state/build-state.template.json +6 -0
- package/templates/settings-hooks.template.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "trycore-spec-build-harness",
|
|
4
4
|
"displayName": "Trycore — Spec & Build Harness",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"description": "Arnés de construcción de dos loops (slice por épica + release gate) para Claude Code, con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Trycore",
|
package/METODOLOGIA.md
CHANGED
|
@@ -65,6 +65,15 @@ por slice. Y el inner loop **no** dispara reviewers pesados. Cada gate vive en e
|
|
|
65
65
|
|
|
66
66
|
## 2. La regla del "esqueleto que camina"
|
|
67
67
|
|
|
68
|
+
> **Paso 1 fundamental — el scaffold (precondición, NO generada por el arnés).** Antes del primer
|
|
69
|
+
> slice debe **existir** un *scaffold* runnable: el esqueleto del proyecto que arranca vacío (el
|
|
70
|
+
> script de build/dev corre sin error). El arnés lo **exige y bloquea** —gate de proyecto
|
|
71
|
+
> `scaffold.confirmed`, que solo pasa a `true` por **confirmación explícita** (no por auto-detección
|
|
72
|
+
> de `package.json`), respaldado por el hook determinista `scaffold-guard.sh`— pero **no lo genera**
|
|
73
|
+
> (es agnóstico al stack: el equipo lo crea según el PRD/allowlist). El *esqueleto que camina* se
|
|
74
|
+
> construye **encima** del scaffold: el scaffold es el shell vacío que arranca; el walking skeleton
|
|
75
|
+
> es el primer journey real más delgado. Sin scaffold confirmado no se abre ningún slice.
|
|
76
|
+
|
|
68
77
|
El arnés prohíbe construir capas horizontales aisladas que "se juntan al final" (el anti-patrón que
|
|
69
78
|
hace que todos los gates por-slice estén en verde y el producto aun así no funcione de punta a
|
|
70
79
|
punta). En su lugar:
|
|
@@ -142,8 +151,9 @@ vía el hook `stack-guard.sh` en tiempo real, no vía subagente.
|
|
|
142
151
|
|
|
143
152
|
- `ux` y `api` admiten `null` cuando el slice no tiene UI o endpoints. `null` ≠ abierto: **no
|
|
144
153
|
bloquea** el DoD.
|
|
145
|
-
- En `harness_phase: authoring` (sin `package.json`) se puede hacer
|
|
146
|
-
de código (`tdd`, `journey_smoke`, `api`, `data`) **no
|
|
154
|
+
- En `harness_phase: authoring` (sin `package.json`) se puede hacer la Fase 0 (crear/confirmar el
|
|
155
|
+
scaffold) + `dor` + `change`, pero los gates de código (`tdd`, `journey_smoke`, `api`, `data`) **no
|
|
156
|
+
se cierran** hasta tener el scaffold confirmado (gate de proyecto `scaffold.confirmed`, ver §2).
|
|
147
157
|
|
|
148
158
|
---
|
|
149
159
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
|
@@ -9,6 +9,13 @@ Eres el **gatekeeper DoR/DoD** del arnés de construcción. Eres read-only sobre
|
|
|
9
9
|
proponer la escritura del estado (no editas código de producto).
|
|
10
10
|
|
|
11
11
|
## Definition of Ready (gate `dor`) — antes de construir
|
|
12
|
+
|
|
13
|
+
**Precondición — Paso 1 fundamental (scaffold):** antes de validar nada más, exige
|
|
14
|
+
`scaffold.confirmed=true` en `build-state.json`. Si es `false`, **NO valides el DoR**: instruye
|
|
15
|
+
confirmar primero que existe un scaffold runnable del proyecto (ver `building-a-slice` Fase 0). El
|
|
16
|
+
arnés **no genera** el scaffold; lo exige. (El hook `scaffold-guard.sh` respalda este bloqueo en
|
|
17
|
+
las fases de código.)
|
|
18
|
+
|
|
12
19
|
La unidad es la **épica**. Identifica `EP-XXX` en `docs/03-backlog/epicas.md` y el conjunto de HU
|
|
13
20
|
que la componen (las que tienen `epica: EP-XXX` en `docs/04-historias/`). Pasa SOLO si **todas** se
|
|
14
21
|
cumplen; lista cada una con ✓/✗:
|
package/dist/commands/doctor.js
CHANGED
|
@@ -59,6 +59,21 @@ export async function doctor(opts) {
|
|
|
59
59
|
else {
|
|
60
60
|
console.log(' — sin hooks instalados (corre `trycore-build init`)');
|
|
61
61
|
}
|
|
62
|
+
// Gate de scaffold (Paso 1 fundamental)
|
|
63
|
+
console.log('');
|
|
64
|
+
if (fs.existsSync(t.stateFile)) {
|
|
65
|
+
try {
|
|
66
|
+
const st = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
|
|
67
|
+
const confirmed = st?.scaffold?.confirmed === true;
|
|
68
|
+
console.log(`Scaffold (Paso 1): ${confirmed ? '✓ confirmado' : '✗ pendiente — confírmalo en building-a-slice (Fase 0) antes de fases de código'}`);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.log('Scaffold (Paso 1): ⚠ build-state.json malformado');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log('Scaffold (Paso 1): — (sin build-state.json; corre `trycore-build init`)');
|
|
76
|
+
}
|
|
62
77
|
// Detección de doble canal (CLI settings.json + plugin) [H3]
|
|
63
78
|
const settingsHasHooks = fs.existsSync(t.settingsFile) &&
|
|
64
79
|
/hooks\/build\//.test(fs.readFileSync(t.settingsFile, 'utf8'));
|
package/dist/commands/status.js
CHANGED
|
@@ -38,6 +38,7 @@ export async function status(opts) {
|
|
|
38
38
|
console.log('');
|
|
39
39
|
console.log('Estado del arnés:');
|
|
40
40
|
console.log(` Fase: ${st.harness_phase ?? '?'}`);
|
|
41
|
+
console.log(` Scaffold: ${st.scaffold?.confirmed === true ? '✓ confirmado (Paso 1)' : '✗ pendiente (Paso 1)'}`);
|
|
41
42
|
const slice = st.active_slice;
|
|
42
43
|
if (slice) {
|
|
43
44
|
const openGates = Object.entries(slice.gates ?? {})
|
|
@@ -18,7 +18,7 @@ function cmd(script) {
|
|
|
18
18
|
const HOOK_SPECS = [
|
|
19
19
|
{ event: 'SessionStart', matcher: 'startup|clear|compact', scripts: ['load-build-state.sh'] },
|
|
20
20
|
{ event: 'PreToolUse', matcher: 'Bash', scripts: ['gitflow-guard.sh'] },
|
|
21
|
-
{ event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh'] },
|
|
21
|
+
{ event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh'] },
|
|
22
22
|
{ event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['lint-typecheck.sh', 'coherence-flag.sh'] },
|
|
23
23
|
{ event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh'] },
|
|
24
24
|
];
|
package/dist/lib/state-seed.js
CHANGED
|
@@ -10,6 +10,7 @@ import { ensureDir } from './install-engine.js';
|
|
|
10
10
|
const EMPTY_STATE = {
|
|
11
11
|
version: '1.0',
|
|
12
12
|
harness_phase: 'authoring',
|
|
13
|
+
scaffold: { confirmed: false, confirmed_by: null, confirmed_at: null, notes: '' },
|
|
13
14
|
active_slice: null,
|
|
14
15
|
history: [],
|
|
15
16
|
releases: [],
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Diseño: Scaffold como Paso 1 fundamental (scaffold gate)
|
|
2
|
+
|
|
3
|
+
Fecha: 2026-06-02 · Target: `@trycore/spec-build-harness` v0.2.0 · Estado: aprobado (diseño)
|
|
4
|
+
|
|
5
|
+
## Contexto y problema
|
|
6
|
+
|
|
7
|
+
Hoy el arnés es **permisivo** respecto al scaffold del proyecto (el esqueleto runnable: `package.json` + framework que arranca vacío). Lo trata como un evento implícito: `harness_phase` pasa de `authoring` a `active` cuando `load-build-state.sh` detecta `package.json`, y los gates de código "no se cierran" en `authoring`, pero nada **exige ni bloquea** explícitamente. La decisión del usuario:
|
|
8
|
+
|
|
9
|
+
> Es requerido que exista un scaffold. **No lo forcemos** (el arnés NO lo genera — sigue agnóstico al stack), **pero nuestro setup debe ser restrictivo para avanzar**. Es el **Paso 1 fundamental**. Y debe **preguntarse explícitamente** (no inferirse en silencio de `package.json`).
|
|
10
|
+
|
|
11
|
+
Objetivo: convertir "scaffold presente" en una **precondición explícita y restrictiva** — confirmada por el equipo, registrada en el estado, y respaldada por un bloqueo determinista — sin que el arnés genere el scaffold.
|
|
12
|
+
|
|
13
|
+
## Decisiones tomadas (brainstorming)
|
|
14
|
+
|
|
15
|
+
1. **Condición = confirmación explícita**, no inferencia silenciosa de `package.json`.
|
|
16
|
+
2. **Enforcement = skill/agente (Fase 0 de DoR) + gate en estado + hook determinista de respaldo.**
|
|
17
|
+
3. **Regla del hook = Enfoque A (por estado/fase):** bloquea solo cuando un slice está en fase de código sin el scaffold confirmado; permite crear el scaffold (fases `dor`/`change` o sin slice).
|
|
18
|
+
4. **No se genera el scaffold.** El arnés exige, bloquea e instruye; el equipo lo crea (guiado por `stack-allowlist.json`/PRD).
|
|
19
|
+
|
|
20
|
+
## Diseño
|
|
21
|
+
|
|
22
|
+
### 1. Estado (`state/build-state.json` + `build-state.schema.json`)
|
|
23
|
+
Nuevo gate **a nivel proyecto** (no por-slice), explícito, default cerrado:
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
"scaffold": {
|
|
27
|
+
"confirmed": false,
|
|
28
|
+
"confirmed_by": null, // agente/skill que confirmó
|
|
29
|
+
"confirmed_at": null, // ISO 8601 UTC
|
|
30
|
+
"notes": "" // ej. "Next.js app arranca vacía; npm run dev ok"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Top-level, junto a `harness_phase`/`active_slice`/`history`/`releases`.
|
|
35
|
+
- `harness_phase` (authoring/active) se mantiene como señal **informativa** auto-detectada; el **gate de avance** es `scaffold.confirmed`.
|
|
36
|
+
- Solo pasa a `true` por **confirmación explícita** (nunca auto). Una transición = una escritura, con `confirmed_by`/`confirmed_at` (protocolo de `state/README.md`).
|
|
37
|
+
- Schema: `scaffold` se añade a `properties` y a `required` del objeto raíz; `build-state.template.json` lo incluye con `confirmed:false`.
|
|
38
|
+
|
|
39
|
+
### 2. Skill `building-a-slice` — Fase 0 (precondición de DoR)
|
|
40
|
+
Antes de abrir cualquier slice:
|
|
41
|
+
- Si `scaffold.confirmed` ya es `true` → continúa al DoR normal.
|
|
42
|
+
- Si es `false` → **pregunta explícitamente** (AskUserQuestion): *"¿Existe un scaffold runnable del proyecto (arranca vacío: build/dev corre sin error)?"*
|
|
43
|
+
- **No** → **STOP**. Instruye crearlo según `stack-allowlist.json#source` (PRD) y el stack permitido; **no lo genera**. No abre el slice.
|
|
44
|
+
- **Sí** → registra `scaffold.confirmed=true` (con `confirmed_by`, `confirmed_at`, `notes`) y continúa.
|
|
45
|
+
- Documenta la relación con el walking skeleton (ver §5).
|
|
46
|
+
|
|
47
|
+
### 3. Agente `dor-dod-gatekeeper`
|
|
48
|
+
Añade **"scaffold confirmado"** como criterio **duro** de DoR (primer ítem). No deja pasar a fases de código sin `scaffold.confirmed=true`. Es el dueño de escribir el gate cuando valida el DoR.
|
|
49
|
+
|
|
50
|
+
### 4. Hook determinista `hooks/build/scaffold-guard.sh` (PreToolUse · Write|Edit) — Enfoque A
|
|
51
|
+
Backstop ineludible si el agente se salta la Fase 0:
|
|
52
|
+
- Resuelve `ROOT` por `git rev-parse --show-toplevel` (+ fallback `${CLAUDE_PROJECT_DIR}`); lee `$ROOT/.claude/state/build-state.json`.
|
|
53
|
+
- Guarda python3 fail-closed dirigida (igual patrón que `stack-guard.sh`).
|
|
54
|
+
- **Regla de bloqueo:** si `active_slice` existe y `active_slice.phase ∈ {red, green, refactor, smoke, api, data}` **y** `scaffold.confirmed != true` → **exit 2** con mensaje: *"⛔ scaffold-guard: confirma el scaffold (Paso 1) antes de escribir código de slice. Ver building-a-slice / DoR."* En cualquier otro caso (sin slice, o fase `dor`/`change`) → exit 0 (permite crear el scaffold y planificar).
|
|
55
|
+
- Auto-arme: si no existe `build-state.json` → exit 0.
|
|
56
|
+
|
|
57
|
+
### 5. Metodología (`METODOLOGIA.md`) — clarificación walking skeleton
|
|
58
|
+
- **Paso 1 — Scaffold (precondición, NO generada por el arnés):** esqueleto runnable mínimo del proyecto (build/dev arranca vacío). El equipo lo crea, guiado por el stack del PRD/allowlist; el arnés lo **exige, lo pregunta explícitamente y lo bloquea** hasta confirmarlo.
|
|
59
|
+
- **Primer slice — Walking skeleton:** sobre el scaffold confirmado, construye el journey end-to-end más delgado. El scaffold (shell vacío) precede al walking skeleton (primer journey real delgado).
|
|
60
|
+
|
|
61
|
+
### 6. Wiring de canales y CLI
|
|
62
|
+
- `src/lib/settings-merge.ts`: añade `scaffold-guard.sh` al `HOOK_SPECS` (PreToolUse · `Write|Edit|MultiEdit`), misma cadena única.
|
|
63
|
+
- `hooks/build-harness.json`: añade la misma entrada (canal plugin).
|
|
64
|
+
- `src/commands/doctor.ts` y `status.ts`: reportan `scaffold: ✓ confirmado / ✗ pendiente` leyendo el estado.
|
|
65
|
+
- `state/README.md`: documenta el gate `scaffold` y su protocolo de confirmación.
|
|
66
|
+
|
|
67
|
+
### 7. Versión
|
|
68
|
+
`0.1.0 → 0.2.0` (feature). Sincronizar `VERSION` / `package.json` / `.claude-plugin/plugin.json`. Incluye el fix pendiente de `state/README.md` ("scaffold Next.js" → "scaffold de código del proyecto"). Entrada en `CHANGELOG.md`. Nota de cadencia/uso en `GOVERNANCE.md` si aplica.
|
|
69
|
+
|
|
70
|
+
## Casos borde
|
|
71
|
+
- **Scaffold ya existe al instalar** (proyecto brownfield): la Fase 0 pregunta igual; el usuario confirma `Sí` una vez y queda registrado. No se re-pregunta mientras `scaffold.confirmed=true`.
|
|
72
|
+
- **El equipo crea el scaffold dentro de un slice ya abierto**: desaconsejado por diseño (scaffold es Paso 1, antes del primer slice); el hook bloquea fases de código hasta confirmar. La creación del scaffold debe ocurrir con `active_slice=null` o en `dor`/`change`.
|
|
73
|
+
- **Regresión de scaffold** (alguien borra package.json): `harness_phase` volvería a `authoring` (informativo); `scaffold.confirmed` sigue `true` salvo que se resetee manualmente. Aceptable para v0.2.0; documentar que el gate es una confirmación, no un chequeo continuo.
|
|
74
|
+
- **python3 ausente**: el hook bloquea dirigido (no puede leer el estado de forma fiable) con mensaje claro, consistente con los otros hooks.
|
|
75
|
+
|
|
76
|
+
## Verificación (end-to-end)
|
|
77
|
+
1. `tsc` build limpio; `check-version-sync` (0.2.0 en los 3) / `check-agnostic` / `check-state-clean` en verde.
|
|
78
|
+
2. `build-state.template.json` incluye `scaffold.confirmed=false`; el schema valida.
|
|
79
|
+
3. Instalación en repo temporal: `scaffold-guard.sh` instalado y ejecutable; `settings.json` y `build-harness.json` contienen su entrada con la cadena única (sin doble disparo).
|
|
80
|
+
4. Test del hook por estado: con un `build-state.json` que tenga `active_slice.phase="green"` y `scaffold.confirmed=false`, un `Write` simulado → exit 2; con `scaffold.confirmed=true` → exit 0; con `active_slice=null` → exit 0.
|
|
81
|
+
5. `doctor`/`status` reportan el estado del scaffold.
|
|
82
|
+
6. Lectura de `building-a-slice`/`dor-dod-gatekeeper`: el ask de Fase 0 y el criterio duro están presentes y son agnósticos (pasa `check-agnostic`).
|
|
83
|
+
|
|
84
|
+
## Fuera de alcance
|
|
85
|
+
- Generar el scaffold (decisión explícita: no lo forzamos).
|
|
86
|
+
- Chequeo continuo de salud del scaffold (es una confirmación puntual, no un monitor).
|
|
87
|
+
- Cambiar el modelo `harness_phase` (se mantiene como señal informativa).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scaffold-guard.sh — PreToolUse · Write/Edit/MultiEdit
|
|
3
|
+
# Backstop determinista del "Paso 1 fundamental": no se escribe código de slice sin
|
|
4
|
+
# scaffold confirmado. Enfoque A (por estado/fase): bloquea (exit 2) SOLO si hay un
|
|
5
|
+
# active_slice en fase de código (red/green/refactor/smoke/api/data) y scaffold.confirmed
|
|
6
|
+
# no es true. Permite crear el scaffold y planificar (sin slice, o fases dor/change).
|
|
7
|
+
# AUTO-ARME: si no existe build-state.json, no hay nada que vigilar -> exit 0.
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
|
|
10
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
|
|
11
|
+
STATE="$ROOT/.claude/state/build-state.json"
|
|
12
|
+
[ -f "$STATE" ] || exit 0
|
|
13
|
+
|
|
14
|
+
INPUT="$(cat)"
|
|
15
|
+
|
|
16
|
+
# Guarda python3 [H4]: si falta, no podemos leer el estado de forma fiable. Fail-closed
|
|
17
|
+
# DIRIGIDO: solo bloqueamos si la edición NO es del propio scaffold/planificación obvia.
|
|
18
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
19
|
+
echo "⛔ scaffold-guard: python3 no disponible; no puedo verificar el gate de scaffold. Instala python3 (trycore-build doctor)." >&2
|
|
20
|
+
exit 2
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
VERDICT="$(STATE="$STATE" python3 <<'PY' 2>/dev/null
|
|
24
|
+
import os, sys, json
|
|
25
|
+
try:
|
|
26
|
+
d = json.load(open(os.environ["STATE"]))
|
|
27
|
+
except Exception:
|
|
28
|
+
sys.exit(0) # estado ilegible -> no bloquear (auto-arme)
|
|
29
|
+
slice_ = d.get("active_slice")
|
|
30
|
+
if not slice_:
|
|
31
|
+
sys.exit(0) # sin slice: se permite crear el scaffold / planificar
|
|
32
|
+
code_phases = {"red", "green", "refactor", "smoke", "api", "data"}
|
|
33
|
+
if slice_.get("phase") not in code_phases:
|
|
34
|
+
sys.exit(0) # fases dor/change/pr/archived: permitido
|
|
35
|
+
if (d.get("scaffold") or {}).get("confirmed") is True:
|
|
36
|
+
sys.exit(0) # scaffold confirmado: permitido
|
|
37
|
+
print("BLOCK")
|
|
38
|
+
PY
|
|
39
|
+
)"
|
|
40
|
+
|
|
41
|
+
if [ "$VERDICT" = "BLOCK" ]; then
|
|
42
|
+
echo "⛔ scaffold-guard (Paso 1 fundamental): el slice está en fase de código pero el scaffold NO está confirmado." >&2
|
|
43
|
+
echo " Confirma primero que existe un scaffold runnable del proyecto (ver building-a-slice Fase 0 / DoR)." >&2
|
|
44
|
+
echo " El arnés NO genera el scaffold: créalo según tu stack (stack-allowlist.json) y confírmalo." >&2
|
|
45
|
+
exit 2
|
|
46
|
+
fi
|
|
47
|
+
exit 0
|
package/hooks/build-harness.json
CHANGED
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
{
|
|
28
28
|
"type": "command",
|
|
29
29
|
"command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/stack-guard.sh\""
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/scaffold-guard.sh\""
|
|
30
34
|
}
|
|
31
35
|
]
|
|
32
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trycore/spec-build-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Arnés agéntico de construcción de Trycore para Claude Code: pipeline de dos loops (slice por épica + release gate) con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,23 @@ inner loop: **≤ ~20 min por épica** y producto que **camina end-to-end en tod
|
|
|
33
33
|
> cada paso sea un stub. Cada épica posterior **engorda** un paso de ese esqueleto y mantiene el
|
|
34
34
|
> `journey_smoke` verde. Nunca se construyen capas horizontales aisladas que "se juntan al final".
|
|
35
35
|
|
|
36
|
+
## Fase 0 · Scaffold (Paso 1 fundamental — precondición restrictiva)
|
|
37
|
+
|
|
38
|
+
Antes de abrir **cualquier** slice, el scaffold runnable del proyecto debe **existir y estar
|
|
39
|
+
confirmado explícitamente**. El arnés **NO genera** el scaffold (es agnóstico al stack), pero
|
|
40
|
+
**bloquea el avance** hasta confirmarlo. Es la precondición del primer slice; el *esqueleto que
|
|
41
|
+
camina* se construye **encima** del scaffold ya existente.
|
|
42
|
+
|
|
43
|
+
1. Lee `build-state.json`. Si `scaffold.confirmed` ya es `true` → continúa a la Fase 1 (dor).
|
|
44
|
+
2. Si es `false` → **pregunta explícitamente** (AskUserQuestion): *"¿Existe un scaffold runnable del
|
|
45
|
+
proyecto (arranca vacío: el script de build/dev corre sin error)?"*
|
|
46
|
+
- **No** → **STOP**. Indica crearlo según el stack permitido (`.claude/config/stack-allowlist.json`
|
|
47
|
+
/ el PRD técnico). **No lo generes tú.** No abras el slice.
|
|
48
|
+
- **Sí** → registra en el estado `scaffold.confirmed=true` (con `confirmed_by`, `confirmed_at`,
|
|
49
|
+
`notes` — p.ej. "build/dev arranca vacío sin error") y continúa.
|
|
50
|
+
3. El gate lo valida también el `dor-dod-gatekeeper` (criterio duro de DoR) y lo respalda el hook
|
|
51
|
+
determinista `scaffold-guard.sh` (bloquea escribir código de slice sin scaffold confirmado).
|
|
52
|
+
|
|
36
53
|
## Pipeline — inner loop (carga la referencia indicada en cada paso)
|
|
37
54
|
|
|
38
55
|
| Fase | Acción | Delega en | Gate | Referencia |
|
|
@@ -66,16 +83,21 @@ El usuario siempre puede sobreescribir el default. Si acepta, invoca la skill
|
|
|
66
83
|
**`releasing-a-version`** sobre la release correspondiente.
|
|
67
84
|
|
|
68
85
|
## Cómo empezar
|
|
69
|
-
1.
|
|
86
|
+
1. **Fase 0 — scaffold**: verifica `scaffold.confirmed` (ver arriba). Si no está confirmado, resuélvelo
|
|
87
|
+
primero (pregunta explícita; STOP si no existe). Sin scaffold confirmado no se abre slice.
|
|
88
|
+
2. Pregunta/identifica la **épica** objetivo (`EP-XXX` en `docs/03-backlog/epicas.md`) y reúne las
|
|
70
89
|
**HU que cubre** (las que tienen `epica: EP-XXX` en `docs/04-historias/`) → poblarán `hus[]`.
|
|
71
|
-
|
|
90
|
+
3. Lee `build-state.json`. Si hay `active_slice`, retoma su primer gate abierto; si es `null`,
|
|
72
91
|
arranca en **dor**.
|
|
73
|
-
|
|
92
|
+
4. Invoca al `build-orchestrator` para conducir el pipeline, o ejecuta fase a fase tú mismo
|
|
74
93
|
respetando los gates.
|
|
75
94
|
|
|
76
95
|
## Reglas duras
|
|
77
|
-
-
|
|
78
|
-
|
|
96
|
+
- **Paso 1 fundamental**: sin `scaffold.confirmed=true` no se abre slice ni se escribe código de
|
|
97
|
+
slice (lo respalda `scaffold-guard.sh`). El arnés exige el scaffold pero **no lo genera**.
|
|
98
|
+
- En `harness_phase` = `authoring` (sin `package.json`) puedes hacer la Fase 0 (crear/confirmar el
|
|
99
|
+
scaffold) + dor + change, pero los gates de código (tdd, journey_smoke, api, data) NO se cierran
|
|
100
|
+
hasta tener el scaffold confirmado.
|
|
79
101
|
- El enlace change↔épica va en `## Trazabilidad` del `proposal.md`, **nunca** en frontmatter YAML
|
|
80
102
|
(rompe `openspec validate`). Ver `link-change-epic.md`.
|
|
81
103
|
- Integración solo por **PR** a `main` (el hook `gitflow-guard.sh` bloquea commits/push directos).
|
package/state/README.md
CHANGED
|
@@ -40,7 +40,16 @@ Luego se pregunta el **Release Gate** (outer loop, skill `releasing-a-version`)
|
|
|
40
40
|
computado desde las líneas de release del Story Map.
|
|
41
41
|
|
|
42
42
|
`harness_phase` arranca en `authoring` y lo cambia `load-build-state.sh` a `active` cuando
|
|
43
|
-
detecta `package.json` en la raíz (aparición del scaffold
|
|
43
|
+
detecta `package.json` en la raíz (aparición del scaffold de código del proyecto). Es una señal
|
|
44
|
+
**informativa**, no el gate de avance.
|
|
45
|
+
|
|
46
|
+
### Gate `scaffold` (Paso 1 fundamental)
|
|
47
|
+
|
|
48
|
+
`scaffold` es un gate **de proyecto** (no por-slice): `{ confirmed, confirmed_by, confirmed_at, notes }`.
|
|
49
|
+
Arranca en `confirmed: false` y **solo** pasa a `true` por **confirmación explícita** (vía
|
|
50
|
+
`building-a-slice` Fase 0 / `dor-dod-gatekeeper`), nunca por auto-detección. Es la precondición
|
|
51
|
+
restrictiva para abrir cualquier slice: el hook `scaffold-guard.sh` bloquea escribir código de
|
|
52
|
+
slice (fases `red…data`) mientras `confirmed` no sea `true`. El arnés **no genera** el scaffold.
|
|
44
53
|
|
|
45
54
|
## Quién escribe qué
|
|
46
55
|
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
"description": "Single source of truth para el handoff secuencial entre agentes de construcción. Lo escriben build-orchestrator y los gates; lo lee load-build-state.sh.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
|
-
"required": ["version", "harness_phase", "active_slice", "history", "releases"],
|
|
8
|
+
"required": ["version", "harness_phase", "scaffold", "active_slice", "history", "releases"],
|
|
9
9
|
"properties": {
|
|
10
10
|
"version": { "type": "string", "const": "1.0" },
|
|
11
11
|
"harness_phase": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"enum": ["authoring", "active"],
|
|
14
|
-
"description": "authoring = aún no hay package.json; active = scaffold de código presente."
|
|
14
|
+
"description": "Señal INFORMATIVA auto-detectada: authoring = aún no hay package.json; active = scaffold de código presente. El gate de avance es `scaffold.confirmed`, NO esta señal."
|
|
15
15
|
},
|
|
16
|
+
"scaffold": { "$ref": "#/$defs/scaffold" },
|
|
16
17
|
"active_slice": {
|
|
17
18
|
"description": "El slice (épica) en construcción. null si no hay ninguno activo.",
|
|
18
19
|
"oneOf": [
|
|
@@ -32,6 +33,18 @@
|
|
|
32
33
|
}
|
|
33
34
|
},
|
|
34
35
|
"$defs": {
|
|
36
|
+
"scaffold": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"description": "Gate de PROYECTO (Paso 1 fundamental): el scaffold runnable existe. Precondición EXPLÍCITA y restrictiva — solo pasa a confirmed=true por confirmación humana (vía building-a-slice Fase 0 / dor-dod-gatekeeper), nunca por auto-detección. El arnés NO genera el scaffold.",
|
|
40
|
+
"required": ["confirmed"],
|
|
41
|
+
"properties": {
|
|
42
|
+
"confirmed": { "type": "boolean", "description": "true solo tras confirmación explícita de que el esqueleto runnable existe." },
|
|
43
|
+
"confirmed_by": { "type": ["string", "null"], "description": "Agente/skill que registró la confirmación." },
|
|
44
|
+
"confirmed_at": { "type": ["string", "null"], "format": "date-time" },
|
|
45
|
+
"notes": { "type": "string", "description": "Evidencia libre (p.ej. 'build/dev arranca vacío sin error')." }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
35
48
|
"slice": {
|
|
36
49
|
"type": "object",
|
|
37
50
|
"additionalProperties": false,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"PreToolUse": [
|
|
15
15
|
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/gitflow-guard.sh\"" } ] },
|
|
16
|
-
{ "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/stack-guard.sh\"" } ] }
|
|
16
|
+
{ "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/stack-guard.sh\"" }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/scaffold-guard.sh\"" } ] }
|
|
17
17
|
],
|
|
18
18
|
"PostToolUse": [
|
|
19
19
|
{ "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/lint-typecheck.sh\"" }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/coherence-flag.sh\"" } ] }
|