@trycore/spec-build-harness 0.2.0 → 0.4.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 +15 -4
- package/INSTALL.md +10 -5
- package/METODOLOGIA.md +18 -4
- package/README.md +8 -5
- package/VERSION +1 -1
- package/agents/build/dor-dod-gatekeeper.md +3 -1
- package/commands/build/reflect.md +163 -0
- package/dist/commands/doctor.js +35 -0
- package/dist/commands/init.js +32 -9
- package/dist/commands/status.js +4 -0
- package/dist/commands/uninstall.js +4 -1
- package/dist/lib/settings-merge.js +1 -1
- package/docs/commands.md +10 -4
- package/docs/customization/lsp-extensions.md +90 -0
- package/docs/customization/mcp-extensions.md +3 -0
- package/docs/getting-started.md +12 -3
- package/docs/hooks.md +24 -8
- package/hooks/build/lint-typecheck.sh +21 -1
- package/hooks/build/reflect-nudge.sh +29 -0
- package/hooks/build-harness.json +4 -0
- package/package.json +2 -1
- package/skills/building-a-micro-change/SKILL.md +78 -0
- package/skills/building-a-slice/SKILL.md +6 -0
- package/skills/building-a-slice/references/dor.md +1 -1
- package/skills/building-a-slice/references/gitflow.md +3 -1
- package/state/README.md +10 -0
- package/state/build-state.schema.json +3 -1
- package/templates/CLAUDE.md.template +6 -0
- package/docs/superpowers/specs/2026-06-02-scaffold-gate-design.md +0 -87
|
@@ -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.4.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/GOVERNANCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Gobernanza del arnés de construcción `.claude`
|
|
2
2
|
|
|
3
|
-
Versión: ver `.claude/.build-harness-version
|
|
3
|
+
Versión del paquete: ver `.claude/.build-harness-version`. El arnés es **soporte cognitivo vivo**:
|
|
4
4
|
evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
|
|
5
5
|
|
|
6
6
|
## Componentes y dueño
|
|
@@ -9,14 +9,15 @@ evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
|
|
|
9
9
|
| Contexto | sección Construcción de CLAUDE.md, `openspec/project.md` | raíz / `openspec/` |
|
|
10
10
|
| Estado | `build-state.json` (+schema, README) | `.claude/state/` |
|
|
11
11
|
| Agentes | 10 agentes de build | `.claude/agents/build/` |
|
|
12
|
-
| Hooks | settings.json +
|
|
13
|
-
| Skill | `building-a-slice` +
|
|
12
|
+
| Hooks | settings.json + 8 scripts | `.claude/settings.json`, `.claude/hooks/build/` |
|
|
13
|
+
| Skill | `building-a-slice` (+10 refs) · `building-a-micro-change` (carril ligero de mantenimiento) | `.claude/skills/` |
|
|
14
14
|
| Config | allowlist de stack | `.claude/config/stack-allowlist.json` |
|
|
15
15
|
|
|
16
16
|
## Fases de activación (`harness_phase`)
|
|
17
17
|
- **authoring** (actual): aún no hay `package.json`. Activos: `gitflow-guard`, `load-build-state`,
|
|
18
18
|
`coherence-flag`; agentes de coherencia/stack/DoR/DoD operan sobre texto. Los hooks de
|
|
19
|
-
lint/typecheck/stack(package.json)/tests están **armados** pero inertes (`[ -f package.json ] || exit 0`)
|
|
19
|
+
lint/typecheck/stack(package.json)/tests están **armados** pero inertes (`[ -f package.json ] || exit 0`);
|
|
20
|
+
`scaffold-guard` permite (sin slice en fases de código que bloquear) y `reflect-nudge` calla (sin historial que reflexionar).
|
|
20
21
|
- **active**: al aparecer `package.json`, `load-build-state.sh` cambia la fase y los hooks armados
|
|
21
22
|
empiezan a disparar solos. Sin intervención manual.
|
|
22
23
|
|
|
@@ -40,6 +41,16 @@ Editar `stack-allowlist.json` SOLO si la sección de requisitos técnicos del PR
|
|
|
40
41
|
### Bitácora de excepciones de stack
|
|
41
42
|
- _(vacío)_ — registrar fecha, dependencia, justificación y aprobador.
|
|
42
43
|
|
|
44
|
+
## Bitácora de cambios de metodología
|
|
45
|
+
Cambios a la política de construcción (unidad de trabajo, gates, DoR/DoD). Aprueba el DRI; van por PR.
|
|
46
|
+
|
|
47
|
+
- **2026-06-02 · v0.4.0** — Carril `building-a-micro-change` + DoR proporcional. El mantenimiento que
|
|
48
|
+
no es producto nuevo (typo, bump de dep permitida, copy/config/docs, fix de pocas líneas) deja de
|
|
49
|
+
modelarse como épica y usa un carril ligero (`fix/*`|`chore/*` → PR, sin `active_slice`), con
|
|
50
|
+
límites duros que lo escalan a épica (dep nueva, API nueva, lógica de dominio/datos). El DoR escala
|
|
51
|
+
el nº de escenarios G/W/T con `complejidad` en vez de exigir 3–5 fijos. La regla "épica = unidad"
|
|
52
|
+
se mantiene intacta para producto. Origen: auditoría del arnés vs. crítica de sobre-configuración.
|
|
53
|
+
|
|
43
54
|
## Extensiones futuras (no implementadas)
|
|
44
55
|
- **Construcción en paralelo** de slices con `superpowers:using-git-worktrees` + orquestación
|
|
45
56
|
multi-worktree (hoy el modelo es **secuencial** por decisión de proyecto). El `build-state.json`
|
package/INSTALL.md
CHANGED
|
@@ -11,7 +11,7 @@ Es el **compañero** de [`@trycore/spec-product-flow`](https://www.npmjs.com/pac
|
|
|
11
11
|
| CLI (bin) | `trycore-build` |
|
|
12
12
|
| Plugin | `trycore-spec-build-harness` |
|
|
13
13
|
| Marketplace | `trycore-build` |
|
|
14
|
-
| Versión | `0.
|
|
14
|
+
| Versión | `0.3.0` |
|
|
15
15
|
|
|
16
16
|
Hay **dos canales** de instalación: el **CLI npm** (canónico, recomendado para operar en un
|
|
17
17
|
proyecto) y el **plugin nativo** de Claude Code (conveniencia a nivel usuario). Lee el
|
|
@@ -28,7 +28,7 @@ npm install -g @trycore/spec-build-harness
|
|
|
28
28
|
Esto expone el binario `trycore-build`. Comprueba la versión:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
trycore-build --version # → 0.
|
|
31
|
+
trycore-build --version # → 0.3.0
|
|
32
32
|
trycore-build --help
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -85,9 +85,9 @@ Qué hace `init`:
|
|
|
85
85
|
1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
|
|
86
86
|
2. **Siembra los assets** en rutas nativas de Claude Code:
|
|
87
87
|
- `.claude/agents/build/` — 10 agentes.
|
|
88
|
-
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`).
|
|
88
|
+
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`, `/build:reflect`).
|
|
89
89
|
- `.claude/skills/` — 12 skills (`building-a-slice`, `releasing-a-version`, `openspec-*`).
|
|
90
|
-
- `.claude/hooks/build/` —
|
|
90
|
+
- `.claude/hooks/build/` — 8 hooks bash.
|
|
91
91
|
3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
|
|
92
92
|
`state/build-state.json` se siembra **vacío y nunca se sobrescribe** (va al `.gitignore`).
|
|
93
93
|
4. **Siembra `config/stack-allowlist.json`** (artefacto del consumidor; lo puebla `/build:onboard`).
|
|
@@ -184,6 +184,11 @@ Qué resuelve:
|
|
|
184
184
|
|
|
185
185
|
Si un punto no aplica, se registra explícitamente "no aplica" (no se deja como `{{...}}`).
|
|
186
186
|
|
|
187
|
+
> **Durante la construcción** (no en la instalación), el comando `/build:reflect` captura las
|
|
188
|
+
> convenciones aprendidas de cada slice cerrado en el bloque `trycore-build-learnings` de
|
|
189
|
+
> `CLAUDE.md` —con tu aprobación— y lo **sugiere** el hook `reflect-nudge.sh` al cerrar sesión.
|
|
190
|
+
> Detalle en `docs/commands.md`.
|
|
191
|
+
|
|
187
192
|
---
|
|
188
193
|
|
|
189
194
|
## 5. Alternativa: plugin nativo (con caveat de canales)
|
|
@@ -228,7 +233,7 @@ Ambos paquetes coexisten en el mismo `.claude/` **sin colisión**, porque usan *
|
|
|
228
233
|
|
|
229
234
|
| | Discovery — `spec-product-flow` | Construcción — `spec-build-harness` |
|
|
230
235
|
|---|---|---|
|
|
231
|
-
| Comandos | `/trycore:*` | `/opsx:*` + `/build:onboard` |
|
|
236
|
+
| Comandos | `/trycore:*` | `/opsx:*` + `/build:onboard` + `/build:reflect` |
|
|
232
237
|
| Marca de versión | `.trycore-version` | `.build-harness-version` |
|
|
233
238
|
| Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
|
|
234
239
|
|
package/METODOLOGIA.md
CHANGED
|
@@ -28,7 +28,7 @@ Ambos paquetes **coexisten en el mismo `.claude/` sin colisión**, con namespace
|
|
|
28
28
|
|
|
29
29
|
| Eje | Discovery (`spec-product-flow`) | Construcción (`spec-build-harness`) |
|
|
30
30
|
|---|---|---|
|
|
31
|
-
| Comandos | `trycore/` (`/trycore:*`) | `opsx/` + `build/` (`/opsx:*`, `/build:onboard`) |
|
|
31
|
+
| Comandos | `trycore/` (`/trycore:*`) | `opsx/` + `build/` (`/opsx:*`, `/build:onboard`, `/build:reflect`) |
|
|
32
32
|
| Sentinela de versión | `.trycore-version` | `.build-harness-version` |
|
|
33
33
|
| Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
|
|
34
34
|
|
|
@@ -90,6 +90,15 @@ Consecuencia operativa: **la unidad de construcción es la épica, no la HU suel
|
|
|
90
90
|
épica = un OpenSpec change = una rama = un PR. Las HU que cubre la épica son su *alcance interno* y
|
|
91
91
|
se listan en `active_slice.hus[]`. Construir por HU individual es sobre-ingeniería.
|
|
92
92
|
|
|
93
|
+
> **Carril micro-change (mantenimiento).** Esta regla rige la **construcción de producto**. El
|
|
94
|
+
> **mantenimiento** que no es capacidad nueva —un typo, un ajuste de copy/config/docs, un bump de
|
|
95
|
+
> dependencia ya permitida, o un fix de pocas líneas sin nueva capacidad— **no se modela como
|
|
96
|
+
> épica**: usa el carril ligero de la skill `building-a-micro-change` (`fix/*`|`chore/*` → cambio →
|
|
97
|
+
> PR, sin abrir `active_slice`). No es una excepción a la regla, sino mantenimiento fuera de su
|
|
98
|
+
> alcance. **Límites duros:** si el cambio añade una dependencia nueva, crea un endpoint/API nuevo, o
|
|
99
|
+
> toca lógica de dominio o el modelo/invariantes de datos, **deja de ser micro-change** y se escala a
|
|
100
|
+
> épica. Ante la duda, es una épica.
|
|
101
|
+
|
|
93
102
|
---
|
|
94
103
|
|
|
95
104
|
## 3. Pipeline del inner loop, fase por fase (con sus gates)
|
|
@@ -117,7 +126,10 @@ Una épica **no entra a construcción** hasta cumplir todo (lo valida `dor-dod-g
|
|
|
117
126
|
- La épica tiene ≥1 HU; todas las que entran se listan en `hus[]`.
|
|
118
127
|
- Frontmatter completo en cada `docs/04-historias/HU-XXX.md` (`id, titulo, epica, prioridad,
|
|
119
128
|
complejidad, estado`) con `estado: lista`.
|
|
120
|
-
- AC en Given/When/Then por HU
|
|
129
|
+
- AC en Given/When/Then por HU, **proporcional a `complejidad`** (cubre los modos de fallo que
|
|
130
|
+
*realmente existen*, no una cuota fija): `trivial`/baja → **1–2** (happy + error/edge crítico si
|
|
131
|
+
existe); `media` → **3** (happy + error + edge); `alta` → **3–5** (cobertura completa). Si existe
|
|
132
|
+
rama de error/edge, debe tener su escenario.
|
|
121
133
|
- Cada HU pasa los 6 criterios **INVEST**.
|
|
122
134
|
- Dependencias resueltas (las épicas/HU de las que depende están en `history[]` o no bloquean).
|
|
123
135
|
- Cabe en el stack del PRD (no requiere tecnología fuera de `stack-allowlist.json`).
|
|
@@ -351,8 +363,10 @@ El arnés **no escribe** en `docs/`; cuando una HU no cumple DoR, devuelve el tr
|
|
|
351
363
|
|
|
352
364
|
## 10. Reglas duras (resumen)
|
|
353
365
|
|
|
354
|
-
1. La **épica** es la unidad de construcción
|
|
355
|
-
Las HU son alcance interno (`hus[]`).
|
|
366
|
+
1. La **épica** es la unidad de construcción de **producto**: un slice = una épica = un change = una
|
|
367
|
+
rama = un PR. Las HU son alcance interno (`hus[]`). El **mantenimiento** que no es producto nuevo
|
|
368
|
+
(typo, bump, copy, infra/docs, fix de pocas líneas) usa el carril `building-a-micro-change`, fuera
|
|
369
|
+
de esta regla; cruzar un límite duro (dep nueva, API nueva, dominio/datos) lo escala a épica.
|
|
356
370
|
2. El **esqueleto que camina** nunca deja de caminar: nada de capas horizontales que se juntan al
|
|
357
371
|
final; `journey_smoke` verde en cada épica.
|
|
358
372
|
3. **Dos loops sin duplicación**: gates baratos por épica (inner), gates pesados una vez por release
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ trycore-build init
|
|
|
45
45
|
| `trycore-build update` | Refresca assets y schema tras `npm update -g`. No pisa estado ni allowlist. |
|
|
46
46
|
| `trycore-build status` | Muestra el estado de la instalación y la fase activa del arnés. |
|
|
47
47
|
| `trycore-build uninstall` | Quita el arnés. **Preserva** `.claude/state/` y `.claude/config/`. |
|
|
48
|
-
| `trycore-build doctor` | Verifica requisitos externos (`openspec`/`python3`/`git`), hooks ejecutables y canales. |
|
|
48
|
+
| `trycore-build doctor` | Verifica requisitos externos (`openspec`/`python3`/`git`), hooks ejecutables y canales; reporta slices sin reflexionar y sugiere **LSP** en stacks tipados. |
|
|
49
49
|
|
|
50
50
|
Flags de `init`: `--copy` (copiar en vez de symlinkear), `--yes` (no interactivo), `--skip-doctor`, `--force-init`, `--stack <deps>`, `--pkg-manager <pm>`, `--runtime <semver>`, `--prd-path <path>`.
|
|
51
51
|
|
|
@@ -59,7 +59,7 @@ El arnés modela la construcción como dos ciclos anidados, cada uno con su skil
|
|
|
59
59
|
DoR → OpenSpec change → TDD → journey-smoke → api/data → DoD → PR + archive
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Por cada épica del backlog: se valida el **Definition of Ready**, se abre un *change* en OpenSpec, se desarrolla con **TDD**, se corre el smoke del journey, se verifican contratos de API y consistencia de datos, se valida el **Definition of Done** y se cierra con PR + archivado del change.
|
|
62
|
+
Por cada épica del backlog: se valida el **Definition of Ready**, se abre un *change* en OpenSpec, se desarrolla con **TDD**, se corre el smoke del journey, se verifican contratos de API y consistencia de datos, se valida el **Definition of Done** y se cierra con PR + archivado del change. Tras archivar, `/build:reflect` puede capturar las convenciones aprendidas del slice (**ciclo autocorrectivo**, opcional; lo sugiere el hook `reflect-nudge.sh`).
|
|
63
63
|
|
|
64
64
|
### Outer loop — `releasing-a-version` (una vez por release)
|
|
65
65
|
|
|
@@ -70,6 +70,7 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
|
|
|
70
70
|
| Command | Propósito |
|
|
71
71
|
|---|---|
|
|
72
72
|
| `/build:onboard` | Onboarding capa 2: lee el PRD, pregunta por PII/IA/determinismo/secretos, resuelve `{{placeholders}}` y escribe la auto-memory. |
|
|
73
|
+
| `/build:reflect` | Reflexión post-slice: tras archivar, propone convenciones aprendidas / errores recurrentes al bloque `trycore-build-learnings` de `CLAUDE.md` (tras tu aprobación) y marca el slice como reflexionado. |
|
|
73
74
|
| `/opsx:explore` | Explora el dominio / specs antes de abrir un change. |
|
|
74
75
|
| `/opsx:new` | Crea un nuevo OpenSpec change. |
|
|
75
76
|
| `/opsx:continue` | Retoma un change en curso. |
|
|
@@ -91,9 +92,9 @@ trycore-spec-build-harness/
|
|
|
91
92
|
├── agents/build/ ← 10 agentes revisores (segunda opinión, contexto limpio)
|
|
92
93
|
├── commands/
|
|
93
94
|
│ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
|
|
94
|
-
│ └── build/ ← /build:onboard
|
|
95
|
+
│ └── build/ ← /build:onboard, /build:reflect
|
|
95
96
|
├── skills/ ← 12 skills (building-a-slice, releasing-a-version, 10 openspec-*)
|
|
96
|
-
├── hooks/build/ ←
|
|
97
|
+
├── hooks/build/ ← 8 hooks bash (gate-check, reflect-nudge, scaffold-guard, gitflow-guard, stack-guard, …)
|
|
97
98
|
├── state/ ← máquina de estado: build-state.json + schema + README
|
|
98
99
|
├── config/ ← stack-allowlist.template.json (artefacto del consumidor)
|
|
99
100
|
├── src/ + dist/ ← CLI trycore-build (init/update/status/uninstall/doctor)
|
|
@@ -123,7 +124,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
123
124
|
|
|
124
125
|
## Roadmap
|
|
125
126
|
|
|
126
|
-
- ✅ **v0.1.0
|
|
127
|
+
- ✅ **v0.1.0** — arnés de dos loops (`building-a-slice` + `releasing-a-version`), 10 agentes, comandos `/opsx:*` + `/build:onboard`, 12 skills, 6 hooks, máquina de estado `build-state.json`, allowlist de stack, CLI `trycore-build` (init/update/status/uninstall/doctor) y plugin nativo. Compañero de `@trycore/spec-product-flow`.
|
|
128
|
+
- ✅ **v0.2.0** — scaffold como "Paso 1 fundamental": gate de proyecto `scaffold.confirmed` (confirmación **explícita**, no auto), Fase 0 en `building-a-slice`, criterio duro de DoR y hook `scaffold-guard.sh`. El arnés **exige** el scaffold pero **no lo genera**.
|
|
129
|
+
- ✅ **v0.3.0 (actual)** — **ciclo autocorrectivo** (hook `reflect-nudge.sh` + comando `/build:reflect`: propone convenciones aprendidas al bloque `trycore-build-learnings` de `CLAUDE.md` tras tu aprobación; campos `reflected`/`reflected_at`) y **LSP opt-in** (`docs/customization/lsp-extensions.md` + sugerencia en `doctor` para stacks tipados). Total: **8 hooks**; comandos `/opsx:*` + `/build:onboard` + `/build:reflect`.
|
|
127
130
|
|
|
128
131
|
## Licencia
|
|
129
132
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.4.0
|
|
@@ -23,7 +23,9 @@ cumplen; lista cada una con ✓/✗:
|
|
|
23
23
|
2. Tiene **al menos una HU** asociada y todas se enumeran en `hus[]`.
|
|
24
24
|
3. **Cada HU** de la épica: frontmatter YAML completo (`id, titulo, epica, prioridad, complejidad,
|
|
25
25
|
estado`) y `estado: lista`.
|
|
26
|
-
4. **Cada HU**: AC en formato **Given/When/Then**,
|
|
26
|
+
4. **Cada HU**: AC en formato **Given/When/Then**, **proporcional a `complejidad`** (`trivial`/baja →
|
|
27
|
+
1–2; `media` → 3; `alta` → 3–5), cubriendo los modos de fallo que existen (happy + error/edge
|
|
28
|
+
reales). No exijas 3–5 a una HU trivial; sí exige el escenario de toda rama de error/edge que exista.
|
|
27
29
|
5. **Cada HU** pasa los 6 criterios **INVEST** (si dudas, invoca al agente `invest-validator`).
|
|
28
30
|
6. Dependencias declaradas (otras épicas/HU) están en `history[]` del estado o marcadas done.
|
|
29
31
|
7. El alcance de la épica cabe en el stack declarado del PRD (allowlist) (no exige tecnología fuera de la allowlist).
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "BUILD: Reflect"
|
|
3
|
+
description: Reflexión post-slice (ciclo autocorrectivo). Tras cerrar/archivar un slice, detecta convenciones aprendidas y errores recurrentes, PROPONE viñetas para el bloque de aprendizajes de CLAUDE.md (se aplican SOLO con tu aprobación) y estampa el slice como reflexionado. Lo dispara el hook reflect-nudge.sh, pero puedes invocarlo cuando quieras.
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [reflexion, conocimiento, ciclo-autocorrectivo, build-harness, trycore]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Captura el **conocimiento tribal** de un slice recién cerrado antes de que se pierda. Un hook bash no
|
|
9
|
+
puede razonar qué se aprendió; **tú (el modelo) sí**. Tu trabajo: detectar convención nueva o error
|
|
10
|
+
recurrente, **proponérselo al usuario**, y —solo con su aprobación— escribirlo al bloque
|
|
11
|
+
`trycore-build-learnings` de `CLAUDE.md`. Nunca escribes sin confirmación.
|
|
12
|
+
|
|
13
|
+
Esta es la mejora del **pilar "ciclo autocorrectivo"**: el hook `reflect-nudge.sh` (evento `Stop`)
|
|
14
|
+
solo **sugiere** ejecutar esto cuando hay slices archivados sin reflexionar; el razonamiento vive
|
|
15
|
+
aquí, en el modelo.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Preflight
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
test -f .claude/.build-harness-version || echo "NOT_INSTALLED"
|
|
23
|
+
command -v python3 >/dev/null 2>&1 || echo "NO_PYTHON3"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Si `NOT_INSTALLED`:**
|
|
27
|
+
|
|
28
|
+
> Este proyecto no tiene el arnés de construcción instalado. Ejecuta primero `trycore-build init`.
|
|
29
|
+
|
|
30
|
+
Stop aquí si no está instalado o si falta `python3`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Fase 1: Identificar slices sin reflexionar
|
|
35
|
+
|
|
36
|
+
Lee `.claude/state/build-state.json` y filtra las entradas de `history[]` con `reflected != true`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 - <<'PY'
|
|
40
|
+
import json
|
|
41
|
+
d = json.load(open(".claude/state/build-state.json"))
|
|
42
|
+
pend = [h for h in (d.get("history") or []) if h.get("reflected") is not True]
|
|
43
|
+
for h in pend:
|
|
44
|
+
print(h.get("epica"), "·", h.get("openspec_change"), "·", h.get("branch"), "·", ",".join(h.get("hus") or []))
|
|
45
|
+
print("TOTAL", len(pend))
|
|
46
|
+
PY
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Si `TOTAL 0`:** informa "No hay slices pendientes de reflexión ✅" y termina. No inventes trabajo.
|
|
50
|
+
|
|
51
|
+
Si hay varios, procésalos **de uno en uno** (el más reciente primero), o pregunta al usuario cuál.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Fase 2: Recolectar señal del slice
|
|
56
|
+
|
|
57
|
+
Para el slice elegido, reúne evidencia **real** (no especules):
|
|
58
|
+
|
|
59
|
+
1. **El change OpenSpec**: `openspec show <openspec_change>` y/o los archivos del change
|
|
60
|
+
(proposal/tasks). ¿Qué se decidió y qué tasks costaron más?
|
|
61
|
+
2. **El diff de la rama**: `git log --oneline <branch>` y `git diff main...<branch> --stat` (si la
|
|
62
|
+
rama o sus commits existen). ¿Qué patrón de código se repitió? ¿Qué se reescribió varias veces?
|
|
63
|
+
3. **La sesión actual**: qué hooks se dispararon más de una vez (p.ej. `stack-guard.sh`,
|
|
64
|
+
`gitflow-guard.sh`, `lint-typecheck.sh`), qué gates retrocedieron, qué correcciones se repitieron.
|
|
65
|
+
|
|
66
|
+
Con esa evidencia, detecta **0–3** aprendizajes candidatos, de estas categorías:
|
|
67
|
+
|
|
68
|
+
- **Convención nueva**: un patrón de código/estructura/nombrado que se repitió y conviene fijar para
|
|
69
|
+
el equipo (ej. "los handlers validan en el borde con el esquema X antes de tocar el dominio").
|
|
70
|
+
- **Error recurrente**: algo que un gate o hook atrapó >1 vez y vale la pena prevenir
|
|
71
|
+
(ej. "recordar correr migraciones antes del journey-smoke").
|
|
72
|
+
- **Fricción del propio arnés**: si el aprendizaje es sobre el arnés (un gate confuso, un paso que
|
|
73
|
+
sobra), NO lo escribas a CLAUDE.md — anótalo como candidato para `internal/skills/auditar-arnes`
|
|
74
|
+
y dilo al usuario.
|
|
75
|
+
|
|
76
|
+
Si no hay nada que valga la pena fijar, es válido **no proponer aprendizajes** (solo estampar, Fase 5).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Fase 3: Proponer (con aprobación explícita)
|
|
81
|
+
|
|
82
|
+
Por **cada** aprendizaje candidato, usa **AskUserQuestion** para que el usuario lo apruebe, edite o
|
|
83
|
+
descarte. Presenta cada uno como una **viñeta concreta y accionable**, lista para CLAUDE.md. Ejemplo
|
|
84
|
+
de opciones: "Agregar tal cual" (Recomendado) · "Editar" · "Descartar".
|
|
85
|
+
|
|
86
|
+
**Reglas:**
|
|
87
|
+
- Una viñeta = una convención. Concreta, verificable, en presente imperativo. Nada vago.
|
|
88
|
+
- No propongas conocimiento que ya está en el bloque (léelo antes). No dupliques.
|
|
89
|
+
- Si un aprendizaje es **personal/cross-proyecto** (no de equipo), ofrécelo para **auto-memory**
|
|
90
|
+
(`type: project`/`user`) en vez de CLAUDE.md — pregunta cuál destino.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Fase 4: Aplicar SOLO lo aprobado al bloque de aprendizajes
|
|
95
|
+
|
|
96
|
+
Para las viñetas aprobadas, **inserta** (no reemplaces) dentro del bloque marcado de `CLAUDE.md`,
|
|
97
|
+
**justo antes** de `<!-- END trycore-build-learnings -->`:
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
- (EP-XXX) <viñeta aprobada>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Prefija cada viñeta con la épica del slice `(EP-XXX)` para trazabilidad.
|
|
104
|
+
- **NO toques nada fuera del bloque** `trycore-build-learnings`. En particular, NO modifiques el
|
|
105
|
+
bloque `trycore-build-harness` ni sus `{{placeholders}}` de dominio (eso es de `/build:onboard`).
|
|
106
|
+
- Si el bloque `trycore-build-learnings` no existe (instalación vieja), pide correr
|
|
107
|
+
`trycore-build update` antes de continuar.
|
|
108
|
+
- Las viñetas son cambio de equipo: se revisan en PR como cualquier otra línea de `CLAUDE.md`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Fase 5: Estampar el slice como reflexionado
|
|
113
|
+
|
|
114
|
+
Marca el/los slice(s) procesado(s) en `history[]` para que el nudge calle. Usa la hora UTC real:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
118
|
+
python3 - "$NOW" "<openspec_change>" <<'PY'
|
|
119
|
+
import json, sys
|
|
120
|
+
now, change = sys.argv[1], sys.argv[2]
|
|
121
|
+
p = ".claude/state/build-state.json"
|
|
122
|
+
d = json.load(open(p))
|
|
123
|
+
for h in d.get("history") or []:
|
|
124
|
+
if h.get("openspec_change") == change:
|
|
125
|
+
h["reflected"] = True
|
|
126
|
+
h["reflected_at"] = now
|
|
127
|
+
json.dump(d, open(p, "w"), indent=2, ensure_ascii=False)
|
|
128
|
+
print("estampado:", change)
|
|
129
|
+
PY
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Estampa **aunque no haya habido aprendizajes** (reflexionar y no encontrar nada también cierra el
|
|
133
|
+
ciclo). Repite por cada slice procesado.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Fase 6: Confirmación
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
## ✓ Reflexión completada
|
|
141
|
+
|
|
142
|
+
Slice: <EP-XXX> (<openspec_change>)
|
|
143
|
+
Aprendizajes: <N agregados a CLAUDE.md> · <M a auto-memory> · <0 si solo se estampó>
|
|
144
|
+
Estampado: reflected=true
|
|
145
|
+
|
|
146
|
+
Pendientes de reflexión restantes: <K>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Si quedan slices pendientes (`K > 0`), ofrece continuar con el siguiente.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Guardrails
|
|
154
|
+
|
|
155
|
+
- **Aprobación explícita siempre.** Nunca escribes a CLAUDE.md sin que el usuario apruebe cada
|
|
156
|
+
viñeta. Espeja la regla del scaffold: el arnés propone, el humano confirma.
|
|
157
|
+
- **Evidencia, no invención.** Cada aprendizaje sale del change/diff/sesión reales. Si no hay
|
|
158
|
+
evidencia, no propongas.
|
|
159
|
+
- **No reescribas.** Inserta viñetas; jamás borres ni reemplaces aprendizajes previos del equipo.
|
|
160
|
+
- **No toques el paquete.** No modifiques agentes/skills/hooks del arnés (eso es mantenimiento, ver
|
|
161
|
+
`internal/skills/auditar-arnes`).
|
|
162
|
+
- **Estampa siempre** al terminar, para no re-molestar con el mismo slice.
|
|
163
|
+
- Si algo aquí contradice `METODOLOGIA.md`, **gana la metodología**.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -66,6 +66,11 @@ export async function doctor(opts) {
|
|
|
66
66
|
const st = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
|
|
67
67
|
const confirmed = st?.scaffold?.confirmed === true;
|
|
68
68
|
console.log(`Scaffold (Paso 1): ${confirmed ? '✓ confirmado' : '✗ pendiente — confírmalo en building-a-slice (Fase 0) antes de fases de código'}`);
|
|
69
|
+
const hist = Array.isArray(st?.history) ? st.history : [];
|
|
70
|
+
const pend = hist.filter((h) => h?.reflected !== true).length;
|
|
71
|
+
if (pend > 0) {
|
|
72
|
+
console.log(`Reflexión: ${pend} slice(s) archivado(s) sin reflexionar — ejecuta /build:reflect`);
|
|
73
|
+
}
|
|
69
74
|
}
|
|
70
75
|
catch {
|
|
71
76
|
console.log('Scaffold (Paso 1): ⚠ build-state.json malformado');
|
|
@@ -83,6 +88,14 @@ export async function doctor(opts) {
|
|
|
83
88
|
console.log(' Si además instalaste el plugin nativo, la cadena de comando es idéntica:');
|
|
84
89
|
console.log(' Claude Code deduplica y el hook dispara UNA vez. No requiere acción.');
|
|
85
90
|
}
|
|
91
|
+
// Sugerencia LSP para stacks tipados (informativa, NO es requisito) [pilar LSP]
|
|
92
|
+
const typed = detectTypedStack(targetDir);
|
|
93
|
+
if (typed) {
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(`ℹ Stack tipado detectado (${typed}). Considera activar LSP para precisión`);
|
|
96
|
+
console.log(' semántica (seguir símbolos/referencias como un compilador, mejor ROI que grep');
|
|
97
|
+
console.log(' en código tipado) — ver docs/customization/lsp-extensions.md');
|
|
98
|
+
}
|
|
86
99
|
console.log('═══════════════════════════════════════');
|
|
87
100
|
if (dep.missingHard.length > 0) {
|
|
88
101
|
console.error(`\n✗ Faltan requisitos duros: ${dep.missingHard.join(', ')}. Instálalos antes de operar el arnés.`);
|
|
@@ -90,3 +103,25 @@ export async function doctor(opts) {
|
|
|
90
103
|
}
|
|
91
104
|
console.log('\n✓ Requisitos satisfechos.');
|
|
92
105
|
}
|
|
106
|
+
/** Señales de stack tipado donde LSP rinde (informativo; nunca un requisito). */
|
|
107
|
+
function detectTypedStack(dir) {
|
|
108
|
+
const direct = [
|
|
109
|
+
['pom.xml', 'Java/Maven'],
|
|
110
|
+
['build.gradle', 'Java/Gradle'],
|
|
111
|
+
['build.gradle.kts', 'Kotlin/Gradle'],
|
|
112
|
+
['composer.json', 'PHP/Composer'],
|
|
113
|
+
['tsconfig.json', 'TypeScript'],
|
|
114
|
+
];
|
|
115
|
+
for (const [f, label] of direct) {
|
|
116
|
+
if (fs.existsSync(path.join(dir, f)))
|
|
117
|
+
return label;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (fs.readdirSync(dir).some((e) => e.endsWith('.csproj') || e.endsWith('.sln')))
|
|
121
|
+
return 'C#/.NET';
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* dir ilegible: sin sugerencia */
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -15,6 +15,8 @@ import { captureStack, renderAllowlist } from '../lib/stack-prompt.js';
|
|
|
15
15
|
import { checkDeps } from './doctor.js';
|
|
16
16
|
const BEGIN = '<!-- BEGIN trycore-build-harness ';
|
|
17
17
|
const END = '<!-- END trycore-build-harness -->';
|
|
18
|
+
const LEARN_BEGIN = '<!-- BEGIN trycore-build-learnings -->';
|
|
19
|
+
const LEARN_END = '<!-- END trycore-build-learnings -->';
|
|
18
20
|
const GI_BEGIN = '# ── trycore-build-harness (auto)';
|
|
19
21
|
const GI_END = '# ── /trycore-build-harness';
|
|
20
22
|
export async function init(opts) {
|
|
@@ -87,20 +89,41 @@ function syncClaudeMdBlock(targetDir, version) {
|
|
|
87
89
|
const template = fs
|
|
88
90
|
.readFileSync(ASSETS.templateClaudeMd, 'utf8')
|
|
89
91
|
.replace(/\{\{BUILD_HARNESS_VERSION\}\}/g, version);
|
|
90
|
-
|
|
91
|
-
const b = lines.findIndex((l) => l.includes(BEGIN));
|
|
92
|
-
const e = lines.findIndex((l) => l.includes(END));
|
|
93
|
-
if (b === -1 || e === -1)
|
|
94
|
-
throw new Error('CLAUDE.md.template sin markers BEGIN/END trycore-build-harness');
|
|
95
|
-
const blockContent = lines.slice(b, e + 1).join('\n');
|
|
92
|
+
// CLAUDE.md nuevo: el template completo ya incluye ambos bloques (arnés + aprendizajes).
|
|
96
93
|
if (!fs.existsSync(t.claudeMd)) {
|
|
97
94
|
fs.writeFileSync(t.claudeMd, template, 'utf8');
|
|
98
95
|
console.log(' ✓ CLAUDE.md creado desde template');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Bloque del arnés: siempre upsert (refresca versión/contenido en cada init/update).
|
|
99
|
+
const harnessBlock = extractBlock(template, BEGIN, END);
|
|
100
|
+
const action = upsertMarkedBlock({ beginMarker: BEGIN, endMarker: END, blockContent: harnessBlock, filePath: t.claudeMd });
|
|
101
|
+
console.log(` ✓ Bloque trycore-build-harness ${action === 'replaced' ? 'actualizado' : 'anexado'} en CLAUDE.md`);
|
|
102
|
+
// Bloque de aprendizajes: SOLO sembrar si falta. NUNCA reemplazar — un update no debe
|
|
103
|
+
// borrar las convenciones que /build:reflect acumuló.
|
|
104
|
+
const current = fs.readFileSync(t.claudeMd, 'utf8');
|
|
105
|
+
if (!current.includes(LEARN_BEGIN)) {
|
|
106
|
+
const learnBlock = extractBlock(template, LEARN_BEGIN, LEARN_END);
|
|
107
|
+
upsertMarkedBlock({ beginMarker: LEARN_BEGIN, endMarker: LEARN_END, blockContent: learnBlock, filePath: t.claudeMd });
|
|
108
|
+
console.log(' ✓ Bloque trycore-build-learnings sembrado en CLAUDE.md');
|
|
99
109
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
}
|
|
111
|
+
/** Extrae el bloque [begin..end] inclusive de un texto (para sembrar desde el template). */
|
|
112
|
+
function extractBlock(template, begin, end) {
|
|
113
|
+
const lines = template.split('\n');
|
|
114
|
+
const b = lines.findIndex((l) => l.includes(begin));
|
|
115
|
+
if (b === -1)
|
|
116
|
+
throw new Error(`CLAUDE.md.template sin marker de inicio: ${begin}`);
|
|
117
|
+
let e = -1;
|
|
118
|
+
for (let i = b; i < lines.length; i++) {
|
|
119
|
+
if (lines[i].includes(end)) {
|
|
120
|
+
e = i;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
103
123
|
}
|
|
124
|
+
if (e === -1)
|
|
125
|
+
throw new Error(`CLAUDE.md.template sin marker de fin: ${end}`);
|
|
126
|
+
return lines.slice(b, e + 1).join('\n');
|
|
104
127
|
}
|
|
105
128
|
function syncGitignoreBlock(targetDir, mode) {
|
|
106
129
|
const t = targetPaths(targetDir);
|
package/dist/commands/status.js
CHANGED
|
@@ -51,6 +51,10 @@ export async function status(opts) {
|
|
|
51
51
|
console.log(' Slice activo: ninguno');
|
|
52
52
|
}
|
|
53
53
|
console.log(` Historial: ${(st.history ?? []).length} slice(s) · Releases: ${(st.releases ?? []).length}`);
|
|
54
|
+
const sinReflexionar = (st.history ?? []).filter((h) => h?.reflected !== true).length;
|
|
55
|
+
if (sinReflexionar > 0) {
|
|
56
|
+
console.log(` Sin reflexionar: ${sinReflexionar} slice(s) — ejecuta /build:reflect`);
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
catch {
|
|
56
60
|
console.log(' ⚠ build-state.json malformado');
|
|
@@ -30,8 +30,9 @@ export async function uninstall(opts) {
|
|
|
30
30
|
// 2) Marca de versión
|
|
31
31
|
if (fs.existsSync(t.versionFile))
|
|
32
32
|
fs.rmSync(t.versionFile, { force: true });
|
|
33
|
-
// 3) Bloques marcados
|
|
33
|
+
// 3) Bloques marcados (arnés + aprendizajes + .gitignore)
|
|
34
34
|
removeMarkedBlock(t.claudeMd, BEGIN, END);
|
|
35
|
+
removeMarkedBlock(t.claudeMd, LEARN_BEGIN, LEARN_END);
|
|
35
36
|
removeMarkedBlock(t.gitignore, GI_BEGIN, GI_END);
|
|
36
37
|
// 4) Hooks + permisos del arnés en settings.json (por cadena exacta)
|
|
37
38
|
removeHarnessSettings(t.settingsFile);
|
|
@@ -40,6 +41,8 @@ export async function uninstall(opts) {
|
|
|
40
41
|
}
|
|
41
42
|
const BEGIN = '<!-- BEGIN trycore-build-harness ';
|
|
42
43
|
const END = '<!-- END trycore-build-harness -->';
|
|
44
|
+
const LEARN_BEGIN = '<!-- BEGIN trycore-build-learnings -->';
|
|
45
|
+
const LEARN_END = '<!-- END trycore-build-learnings -->';
|
|
43
46
|
const GI_BEGIN = '# ── trycore-build-harness (auto)';
|
|
44
47
|
const GI_END = '# ── /trycore-build-harness';
|
|
45
48
|
function isSymlink(p) {
|
|
@@ -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'] },
|
|
22
22
|
{ event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['lint-typecheck.sh', 'coherence-flag.sh'] },
|
|
23
|
-
{ event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh'] },
|
|
23
|
+
{ event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh'] },
|
|
24
24
|
];
|
|
25
25
|
/** Permisos MÍNIMOS y enumerados [H11]. Nunca permisos amplios (mcp__*, additionalDirectories…). */
|
|
26
26
|
const MIN_PERMISSIONS = [
|
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:onboard`) — operan el pipeline de dos loops
|
|
5
|
+
1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.3.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:*` + `/build:onboard` + `/build:reflect`) — 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:onboard`.
|
|
55
|
+
El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → `/build:onboard` y `/build:reflect`.
|
|
56
56
|
|
|
57
57
|
### `/opsx:*` — pipeline OpenSpec
|
|
58
58
|
|
|
@@ -75,6 +75,12 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
|
|
|
75
75
|
|---|---|
|
|
76
76
|
| `/build:onboard` | Parametriza el dominio del arnés: capa de servicios externos/IA, lógica determinista, PII, secretos y decisiones de alto impacto. Lee el PRD, pregunta vía AskUserQuestion, rellena el bloque marcado de `CLAUDE.md` y escribe la auto-memory. **Complementa** a `trycore-build init` (que ya sembró archivos y el stack mecánico). |
|
|
77
77
|
|
|
78
|
+
### `/build:reflect` — reflexión post-slice (ciclo autocorrectivo)
|
|
79
|
+
|
|
80
|
+
| Slash command | Propósito |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `/build:reflect` | Tras archivar un slice, detecta **convenciones aprendidas** / errores recurrentes (a partir del change, el diff y la sesión) y **propone** viñetas para el bloque `trycore-build-learnings` de `CLAUDE.md`; las aplica **solo tras tu aprobación** y estampa el slice como reflexionado (`reflected: true`). Lo **sugiere** el hook `reflect-nudge.sh` al cerrar sesión, pero puedes invocarlo cuando quieras. No toca el bloque de dominio ni sus `{{placeholders}}` (eso es de `/build:onboard`). |
|
|
83
|
+
|
|
78
84
|
---
|
|
79
85
|
|
|
80
86
|
## 3. Caveat de canales (CLI vs. plugin)
|
|
@@ -84,7 +90,7 @@ El arnés se distribuye por **dos canales** que coexisten, pero **namespacean di
|
|
|
84
90
|
| Aspecto | Canal **CLI** (canónico) | Canal **Plugin** nativo |
|
|
85
91
|
|---|---|---|
|
|
86
92
|
| 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` |
|
|
87
|
-
| Namespace de comandos | Por subcarpeta: `/opsx
|
|
93
|
+
| Namespace de comandos | Por subcarpeta: `/opsx:*`, `/build:onboard` y `/build:reflect` | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
|
|
88
94
|
| Referencia a agentes | Por su nombre (p. ej. `build-orchestrator`) | Bajo el nombre del plugin |
|
|
89
95
|
| Cross-references internas | ✔ Escritas para este canal (skills invocan `/opsx:*`, agentes por nombre) | Pueden no resolver según están escritas |
|
|
90
96
|
| Recomendación | **Usar este canal para operar un proyecto** | Conveniencia a nivel usuario |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Extensión LSP del arnés (opt-in, agnóstico)
|
|
2
|
+
|
|
3
|
+
> Guía de personalización para **consumidores** de `@trycore/spec-build-harness`. Explica cómo
|
|
4
|
+
> aprovechar **LSP** (Language Server Protocol) para que Claude Code navegue tu código con
|
|
5
|
+
> **precisión de símbolo** en vez de búsqueda de texto. **Todo lo de aquí es opcional.** El core es
|
|
6
|
+
> 100 % agnóstico: ningún gate, skill ni agente **depende** de LSP. Sin LSP, el arnés funciona igual
|
|
7
|
+
> navegando con `grep`/`glob` + lectura dirigida.
|
|
8
|
+
|
|
9
|
+
## TL;DR
|
|
10
|
+
|
|
11
|
+
- **LSP = precisión de compilador** para Claude Code: seguir la definición exacta de un símbolo,
|
|
12
|
+
distinguir homónimos, listar referencias cruzadas. En monorepos tipados, `grep` devuelve ruido y
|
|
13
|
+
falsos positivos; LSP no.
|
|
14
|
+
- **Mayor ROI en lenguajes tipados**: Java/Kotlin, C#, C/C++, PHP, TypeScript. `trycore-build doctor`
|
|
15
|
+
detecta señales de estos stacks y **sugiere** activarlo (es una nota informativa, **no** un
|
|
16
|
+
requisito).
|
|
17
|
+
- **Complementa, no reemplaza** a MCP: LSP es precisión **intra-repo** (símbolos del código); MCP es
|
|
18
|
+
conectividad a sistemas **externos** (Jira, BD, telemetría). Ver `mcp-extensions.md`.
|
|
19
|
+
- Regla de oro: **opt-in**. Lo habilitas **tú** en tu entorno; el arnés solo lo recomienda donde
|
|
20
|
+
rinde.
|
|
21
|
+
|
|
22
|
+
## Por qué LSP y no solo `grep`
|
|
23
|
+
|
|
24
|
+
El arnés define cada gate por su **resultado verificable**, no por la herramienta que lo produce
|
|
25
|
+
(misma filosofía que las extensiones MCP). Pero la fase de **exploración/navegación** del código —que
|
|
26
|
+
alimenta a los agentes de coherencia, diseño y arquitectura— mejora drásticamente con precisión
|
|
27
|
+
semántica:
|
|
28
|
+
|
|
29
|
+
| Tarea de navegación | Con `grep`/`glob` | Con LSP habilitado |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| "¿Dónde se define este método?" | Coincidencias de texto; homónimos mezclados. | La **definición exacta**, sin ruido. |
|
|
32
|
+
| "¿Quién usa este símbolo?" | Falsos positivos (comentarios, strings, nombres parecidos). | Referencias **reales** del compilador. |
|
|
33
|
+
| "Trazar símbolo → test" | Lectura manual encadenada. | Salto directo definición↔referencias. |
|
|
34
|
+
| "¿Hay duplicación / código muerto?" | Difícil de afirmar con texto. | Referencias vacías = candidato a muerto. |
|
|
35
|
+
|
|
36
|
+
Por eso, en stacks tipados grandes, LSP es la inversión de mayor ROI para la fase de exploración: el
|
|
37
|
+
subagente que **lee** (mapea el subsistema) devuelve una síntesis más fiel, protegiendo la ventana de
|
|
38
|
+
contexto para la fase de **edición**.
|
|
39
|
+
|
|
40
|
+
## Cuándo rinde (por stack)
|
|
41
|
+
|
|
42
|
+
| Stack | Señal que detecta `doctor` | Por qué rinde LSP |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Java / Kotlin | `pom.xml`, `build.gradle(.kts)` | Tipado fuerte + dependencias profundas (Spring): `grep` se ahoga. |
|
|
45
|
+
| C# / .NET | `*.csproj`, `*.sln` | Símbolos y namespaces densos; refactors guiados por tipo. |
|
|
46
|
+
| C / C++ | (no autodetectado; CMake/Make ambiguos) | Macros y headers hacen inútil la búsqueda de texto. |
|
|
47
|
+
| PHP | `composer.json` | Autoload + magia dinámica: seguir definiciones reales ayuda. |
|
|
48
|
+
| TypeScript | `tsconfig.json` | Tipos estructurales; el LSP de TS distingue lo que `grep` no. |
|
|
49
|
+
|
|
50
|
+
En lenguajes **dinámicos sin tipos** (p.ej. scripts sueltos), el ROI de LSP es menor; ahí `grep`/`glob`
|
|
51
|
+
suele bastar y el arnés no lo sugiere.
|
|
52
|
+
|
|
53
|
+
## Cómo activarlo (en TU entorno)
|
|
54
|
+
|
|
55
|
+
LSP es una capacidad **del cliente Claude Code**, no algo que el arnés instale o versione:
|
|
56
|
+
|
|
57
|
+
1. Instala el **language server** de tu stack en tu máquina/imagen de dev (cada lenguaje tiene el
|
|
58
|
+
suyo; sigue la doc del servidor que elijas).
|
|
59
|
+
2. Habilita la integración LSP en **tu** configuración de Claude Code (a nivel proyecto o usuario),
|
|
60
|
+
no en el arnés.
|
|
61
|
+
3. A partir de ahí, Claude Code puede navegar con precisión semántica durante la fase de exploración
|
|
62
|
+
de `building-a-slice` y para los agentes `coherence-three-way`, `simple-design-reviewer` y
|
|
63
|
+
`stack-guardian`.
|
|
64
|
+
|
|
65
|
+
> El arnés nunca levanta el servidor por ti ni versiona su configuración: la frontera (qué servidor,
|
|
66
|
+
> con qué permisos) es **tuya**.
|
|
67
|
+
|
|
68
|
+
## Relación con MCP
|
|
69
|
+
|
|
70
|
+
No son lo mismo y conviven:
|
|
71
|
+
|
|
72
|
+
- **LSP** → precisión de **símbolo** dentro del repo (definiciones, referencias, jerarquía de tipos).
|
|
73
|
+
- **MCP** → conectividad a **sistemas externos** fuera del repo (tickets, BD, navegador, carga).
|
|
74
|
+
|
|
75
|
+
Un mismo proyecto puede usar ambos: LSP para entender el código tipado, MCP (opt-in) para acelerar
|
|
76
|
+
gates que necesitan evidencia externa. Detalle de MCP por gate en `mcp-extensions.md` y el mapa
|
|
77
|
+
operativo en `skills/building-a-slice/references/mcp-map.md`.
|
|
78
|
+
|
|
79
|
+
## Reglas de uso
|
|
80
|
+
|
|
81
|
+
1. **Opt-in.** El arnés solo **sugiere** LSP (vía `doctor`); habilitarlo es decisión del consumidor.
|
|
82
|
+
2. **No es dependencia.** Ningún gate exige LSP. Si no está, la navegación cae a `grep`/`glob` y los
|
|
83
|
+
gates se cierran igual por su resultado verificable.
|
|
84
|
+
3. **Portabilidad.** No escribas en agentes/skills/hooks del arnés instrucciones que **requieran**
|
|
85
|
+
LSP: manténlo como acelerador documentado.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
**Fuente de verdad.** Si algo aquí contradice la metodología Trycore (`METODOLOGIA.md`), **gana la
|
|
90
|
+
metodología**. Para extensiones MCP (y la relación MCP↔LSP por gate), ver `mcp-extensions.md`.
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
> gate, skill ni agente **depende** de un MCP concreto. Si tu entorno no expone ningún MCP, el arnés
|
|
7
7
|
> funciona igual cubriendo cada fase con sus alternativas CLI/test.
|
|
8
8
|
|
|
9
|
+
> **LSP tiene guía dedicada.** Este documento cubre sobre todo **MCP**. Para **LSP** (precisión de
|
|
10
|
+
> símbolo en stacks tipados: por qué, cuándo y cómo), ver **`lsp-extensions.md`**.
|
|
11
|
+
|
|
9
12
|
## TL;DR
|
|
10
13
|
|
|
11
14
|
- Los gates **pueden apoyarse** en MCP/LSP como **aceleradores**, nunca como dependencia dura.
|
package/docs/getting-started.md
CHANGED
|
@@ -76,9 +76,9 @@ trycore-build init
|
|
|
76
76
|
- **10 agentes** en `.claude/agents/build/` (build-orchestrator, dor-dod-gatekeeper,
|
|
77
77
|
security-reviewer, simple-design-reviewer, ux-krug-reviewer, coherence-three-way, stack-guardian,
|
|
78
78
|
api-contract-tester, data-consistency-checker, change-epic-coherence).
|
|
79
|
-
- **Comandos** `/opsx:*` (10) en `.claude/commands/opsx/` + `/build:onboard` en `.claude/commands/build/`.
|
|
79
|
+
- **Comandos** `/opsx:*` (10) en `.claude/commands/opsx/` + `/build:onboard` y `/build:reflect` en `.claude/commands/build/`.
|
|
80
80
|
- **12 skills** en `.claude/skills/` (`building-a-slice`, `releasing-a-version`, 10 `openspec-*`).
|
|
81
|
-
- **
|
|
81
|
+
- **8 hooks** bash en `.claude/hooks/build/` + permisos mínimos en `settings.json`.
|
|
82
82
|
- **Estado**: `.claude/state/build-state.json` (sembrado **vacío** y **nunca** sobreescrito; va al
|
|
83
83
|
`.gitignore`), más el schema y el README versionados.
|
|
84
84
|
- **Config**: `.claude/config/stack-allowlist.json` (artefacto del consumidor; lo siembra el CLI y
|
|
@@ -136,10 +136,14 @@ trycore-build doctor
|
|
|
136
136
|
Comprueba:
|
|
137
137
|
|
|
138
138
|
- Los **3 requisitos duros** (`git`, `python3`, `openspec`) — **falla con exit 1** si falta alguno.
|
|
139
|
-
- Que los **
|
|
139
|
+
- Que los **8 hooks** tengan bit ejecutable (si no, sugiere `trycore-build init --copy` o `chmod +x`).
|
|
140
140
|
- **Doble canal**: si detecta hooks del arnés en `settings.json` (canal CLI) y además instalaste el
|
|
141
141
|
plugin, te recuerda que la cadena de comando es idéntica en ambos canales y Claude Code
|
|
142
142
|
**deduplica** → el hook dispara **una sola vez**. No requiere acción.
|
|
143
|
+
- **Reflexión y LSP (informativo, no falla):** reporta cuántos slices archivados están **sin
|
|
144
|
+
reflexionar** (sugiere `/build:reflect`) y, si detecta un stack tipado (`pom.xml`,
|
|
145
|
+
`build.gradle`, `*.csproj`, `tsconfig.json`…), sugiere activar **LSP**
|
|
146
|
+
(ver `docs/customization/lsp-extensions.md`).
|
|
143
147
|
|
|
144
148
|
Si ves `✓ Requisitos satisfechos.`, continúa.
|
|
145
149
|
|
|
@@ -211,6 +215,10 @@ Notas clave del inner loop:
|
|
|
211
215
|
(bloquea commits/push directos a `main` — integras solo por **PR**).
|
|
212
216
|
- **El enlace change↔épica** va en el bloque `## Trazabilidad` del `proposal.md`, **nunca** en
|
|
213
217
|
frontmatter YAML (rompe `openspec validate`).
|
|
218
|
+
- **Reflexión (ciclo autocorrectivo).** Tras archivar el slice, `/build:reflect` puede capturar las
|
|
219
|
+
convenciones aprendidas / errores recurrentes en el bloque `trycore-build-learnings` de
|
|
220
|
+
`CLAUDE.md` (con tu aprobación). Lo **sugiere** el hook `reflect-nudge.sh` al cerrar la sesión;
|
|
221
|
+
es opcional y no bloquea.
|
|
214
222
|
- Objetivo: **≤ ~20 min por épica**, y producto que **camina end-to-end en todo momento**.
|
|
215
223
|
|
|
216
224
|
---
|
|
@@ -252,6 +260,7 @@ trycore-build doctor # paso 3
|
|
|
252
260
|
# en Claude Code:
|
|
253
261
|
/build:onboard # paso 4
|
|
254
262
|
skill building-a-slice # EP-XXX: DoR → /opsx:new → TDD → smoke → DoD → PR # paso 5
|
|
263
|
+
/build:reflect # opcional: captura aprendizajes del slice recién archivado
|
|
255
264
|
# y cuando cierres una línea de release del Story Map:
|
|
256
265
|
skill releasing-a-version # paso 6
|
|
257
266
|
```
|
package/docs/hooks.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# Hooks del arnés de construcción
|
|
2
2
|
|
|
3
|
-
Este documento describe los **
|
|
3
|
+
Este documento describe los **8 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
|
|
5
|
+
Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarado y el scaffold (bloqueantes), inyectan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, y recuerdan validar trazabilidad, gates abiertos y reflexionar al cerrar un slice. 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 8 hooks
|
|
10
10
|
|
|
11
11
|
| Hook | Evento | Matcher | Qué hace | ¿Bloqueante? |
|
|
12
12
|
|---|---|---|---|---|
|
|
13
13
|
| `load-build-state.sh` | `SessionStart` | `startup\|clear\|compact` | Inyecta al contexto la rama, la fase del arnés, el slice activo y los gates abiertos; sincroniza `harness_phase`. | No |
|
|
14
14
|
| `gitflow-guard.sh` | `PreToolUse` | `Bash` | Enforce GitHub Flow estricto sobre `git commit` / `git push`. | **Sí (exit 2)** |
|
|
15
15
|
| `stack-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea dependencias en `package.json` fuera de la allowlist del stack del PRD. | **Sí (exit 2)** |
|
|
16
|
+
| `scaffold-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea escribir código de slice (fases `red…data`) si el scaffold no está confirmado (`scaffold.confirmed`). | **Sí (exit 2)** |
|
|
16
17
|
| `lint-typecheck.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Corre prettier/eslint/tsc sobre el archivo `.ts`/`.tsx` editado. | No |
|
|
17
18
|
| `coherence-flag.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Recuerda validar la trazabilidad de un `proposal.md` de OpenSpec recién tocado. | No |
|
|
18
19
|
| `build-gate-check.sh` | `Stop` | `.*` | Al cerrar el turno, avisa si el slice activo tiene gates abiertos. | No |
|
|
20
|
+
| `reflect-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere `/build:reflect` si hay slice(s) archivado(s) sin reflexionar (`reflected != true`). | No |
|
|
19
21
|
|
|
20
|
-
>
|
|
22
|
+
> Tres bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`) y cinco informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -58,7 +60,11 @@ Tras editar un archivo `.ts`/`.tsx`, delega el estilo a las herramientas para no
|
|
|
58
60
|
|
|
59
61
|
- `prettier --write` sobre el archivo.
|
|
60
62
|
- `eslint --fix` sobre el archivo (primeras 20 líneas de salida a `stderr`).
|
|
61
|
-
- `tsc --noEmit` y filtra los errores que mencionan el archivo editado.
|
|
63
|
+
- `tsc --noEmit --incremental` con un `tsBuildInfoFile` persistente, y filtra los errores que mencionan el archivo editado.
|
|
64
|
+
|
|
65
|
+
**Typecheck incremental (desde v0.3.x).** `tsc` siempre recorre todo el grafo de tipos del proyecto, no solo el archivo editado. Para que ese coste no escale con el tamaño del repo en cada edición, el hook usa `--incremental` con un `tsBuildInfoFile` en `node_modules/.cache/trycore-build/tsbuildinfo`: el **primer** typecheck de la sesión paga `O(repo)` y cada edición posterior paga `~O(delta)`. El cache vive bajo `node_modules/` (que el consumidor casi siempre tiene gitignored), así que no contamina el repo. El typecheck va envuelto en `timeout 60` **si** `timeout`/`gtimeout` (coreutils) está disponible; si no, corre sin límite (al ser no bloqueante, agotar el tiempo solo omite el reporte de esa edición).
|
|
66
|
+
|
|
67
|
+
> **Caveat `composite`:** en proyectos con `composite: true` en `tsconfig.json`, el typecheck canónico es `tsc -b`. Aquí `--noEmit` prevalece (igual que antes) y los posibles errores de configuración se suprimen (`|| true`); el reporte de tipos puede quedar vacío. No es una regresión respecto al comportamiento previo.
|
|
62
68
|
|
|
63
69
|
Nunca bloquea (siempre `exit 0`); reporta a `stderr` como información. Inerte si no hay `package.json` o si el archivo no es `.ts`/`.tsx`.
|
|
64
70
|
|
|
@@ -70,6 +76,14 @@ Cuando se edita un `openspec/changes/**/proposal.md`, imprime un recordatorio: c
|
|
|
70
76
|
|
|
71
77
|
Al cerrar el turno, si hay un slice activo con gates en `false`, avisa por `stderr` qué gates quedan abiertos y recuerda **no archivar ni abrir PR** hasta cerrarlos (ver skill `building-a-slice` / `dod.md`). Inerte si no hay `package.json`, ni estado, ni `python3`.
|
|
72
78
|
|
|
79
|
+
### 7. `scaffold-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante** (desde v0.2.0)
|
|
80
|
+
|
|
81
|
+
Refuerza el **scaffold como "Paso 1 fundamental"**. Bloquea con `exit 2` la escritura de **código de slice** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data`— mientras `scaffold.confirmed` no sea `true` en `build-state.json`. **Permite** crear el scaffold (sin slice activo, o en fases `dor`/`change`). El arnés **exige** el scaffold pero **no lo genera**; la confirmación es **explícita** (vía `building-a-slice` Fase 0 / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido (solo bloquea si la entrada parece código de slice).
|
|
82
|
+
|
|
83
|
+
### 8. `reflect-nudge.sh` — `Stop` · no bloqueante (desde v0.3.0)
|
|
84
|
+
|
|
85
|
+
Cierra el **ciclo autocorrectivo**. Al terminar el turno, si en `history[]` hay slice(s) archivado(s) con `reflected != true`, imprime un *nudge* sugiriendo ejecutar `/build:reflect` para capturar las convenciones aprendidas (y errores recurrentes) en el bloque `trycore-build-learnings` de `CLAUDE.md`. **Nunca bloquea** el cierre de sesión: si falta `python3` o el estado, sale `0` en silencio (**fail-open**). El razonamiento —qué se aprendió— vive en el comando `/build:reflect`, no en el hook; este solo recuerda. Tras reflexionar y estampar `reflected: true`, el nudge calla.
|
|
86
|
+
|
|
73
87
|
---
|
|
74
88
|
|
|
75
89
|
## La cadena de comando única (sin doble disparo entre canales)
|
|
@@ -93,8 +107,8 @@ Como la cadena es **carácter por carácter idéntica** en ambos canales, si el
|
|
|
93
107
|
|
|
94
108
|
Los hooks parsean el JSON del evento con `python3`. Qué pasa si **falta** `python3` depende de si el hook es bloqueante:
|
|
95
109
|
|
|
96
|
-
- **Bloqueantes** (`gitflow-guard`, `stack-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`,
|
|
97
|
-
- **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0
|
|
110
|
+
- **Bloqueantes** (`gitflow-guard`, `stack-guard`, `scaffold-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 confirmado); 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`).
|
|
111
|
+
- **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).
|
|
98
112
|
|
|
99
113
|
> `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.
|
|
100
114
|
|
|
@@ -111,7 +125,9 @@ Los hooks que tocan el código construido se **auto-arman**: permanecen **inerte
|
|
|
111
125
|
| `stack-guard.sh` | Inerte si no existe la allowlist; sin `package.json` que comparar, no hay violación que detectar. |
|
|
112
126
|
| `lint-typecheck.sh` | Inerte (sale `0` de inmediato). |
|
|
113
127
|
| `coherence-flag.sh` | Funciona siempre (depende de OpenSpec, no del código). |
|
|
128
|
+
| `scaffold-guard.sh` | Permite (sin slice en fases de código no hay nada que bloquear; también permite crear el scaffold). |
|
|
114
129
|
| `build-gate-check.sh` | Inerte (sale `0` de inmediato). |
|
|
130
|
+
| `reflect-nudge.sh` | Silencioso (en `authoring` no hay slices archivados que reflexionar). |
|
|
115
131
|
|
|
116
132
|
Así el arnés convive sin fricción con la fase de *discovery* y se "enciende" cuando empieza la construcción real.
|
|
117
133
|
|
|
@@ -125,7 +141,7 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
|
|
|
125
141
|
|
|
126
142
|
`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`):
|
|
127
143
|
|
|
128
|
-
- Agrega las 5 agrupaciones de hooks (las
|
|
144
|
+
- Agrega las 5 agrupaciones de hooks (las 8 invocaciones: `PreToolUse·Write` agrupa `stack-guard` + `scaffold-guard`; `PostToolUse·Write` agrupa `lint-typecheck` + `coherence-flag`; `Stop` agrupa `build-gate-check` + `reflect-nudge`) sin pisar lo que ya exista.
|
|
129
145
|
- Agrega **permisos mínimos y enumerados** (sin `mcp__*` ni rutas absolutas):
|
|
130
146
|
|
|
131
147
|
```
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
# AUTO-ARME: inerte mientras no exista package.json (fase authoring).
|
|
4
4
|
# No bloquea: reporta lint/format/typecheck del archivo editado para liberar
|
|
5
5
|
# capacidad de razonamiento del modelo (estilo delegado a herramientas).
|
|
6
|
+
# El typecheck es INCREMENTAL (--incremental + tsBuildInfoFile persistente): el primer
|
|
7
|
+
# run de la sesión paga O(repo) y cada edición posterior paga ~O(delta), para que el
|
|
8
|
+
# coste por edición no escale con el tamaño del repo.
|
|
6
9
|
set -uo pipefail
|
|
7
10
|
|
|
8
11
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
|
|
@@ -29,5 +32,22 @@ HAS() { [ -d node_modules ] && [ -x "node_modules/.bin/$1" ]; }
|
|
|
29
32
|
|
|
30
33
|
if HAS prettier; then node_modules/.bin/prettier --write "$FILE" >/dev/null 2>&1 || true; fi
|
|
31
34
|
if HAS eslint; then node_modules/.bin/eslint --fix "$FILE" 2>&1 | sed -n '1,20p' >&2 || true; fi
|
|
32
|
-
|
|
35
|
+
|
|
36
|
+
# Typecheck INCREMENTAL del proyecto. `tsc` siempre recorre todo el grafo de tipos, pero con
|
|
37
|
+
# --incremental + un tsBuildInfoFile persistente, tras el primer run cada edición paga solo el delta.
|
|
38
|
+
# El .tsbuildinfo vive bajo node_modules/ (que el consumidor casi siempre tiene gitignored) → no
|
|
39
|
+
# contamina el repo. Filtramos la salida al archivo editado (grep -F) y la capamos a 20 líneas.
|
|
40
|
+
# timeout OPCIONAL: si coreutils está disponible acota un tsconfig patológico; si no, corre sin
|
|
41
|
+
# límite (el hook nunca bloquea, así que agotar el timeout solo omite el reporte de esta edición).
|
|
42
|
+
# Caveat: proyectos con `composite: true` deben usar `tsc -b`; aquí --noEmit prevalece como hoy
|
|
43
|
+
# y los errores se suprimen (|| true), igual que antes de v0.3.x.
|
|
44
|
+
if HAS tsc; then
|
|
45
|
+
TSBI="node_modules/.cache/trycore-build/tsbuildinfo"
|
|
46
|
+
mkdir -p "$(dirname "$TSBI")" 2>/dev/null || true
|
|
47
|
+
TSC_TIMEOUT=""
|
|
48
|
+
if command -v timeout >/dev/null 2>&1; then TSC_TIMEOUT="timeout 60"
|
|
49
|
+
elif command -v gtimeout >/dev/null 2>&1; then TSC_TIMEOUT="gtimeout 60"; fi
|
|
50
|
+
$TSC_TIMEOUT node_modules/.bin/tsc --noEmit --incremental --tsBuildInfoFile "$TSBI" 2>&1 \
|
|
51
|
+
| grep -F "$FILE" | sed -n '1,20p' >&2 || true
|
|
52
|
+
fi
|
|
33
53
|
exit 0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# reflect-nudge.sh — Stop
|
|
3
|
+
# Ciclo autocorrectivo (reflexión post-sesión): NUNCA bloquea el cierre de sesión.
|
|
4
|
+
# Sugiere /build:reflect SOLO si hay slice(s) archivado(s) sin reflexionar (reflected != true).
|
|
5
|
+
# Determinista y barato: el razonamiento (qué se aprendió) lo hace el MODELO en /build:reflect.
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
|
|
9
|
+
STATE="$ROOT/.claude/state/build-state.json"
|
|
10
|
+
[ -f "$STATE" ] || exit 0
|
|
11
|
+
command -v python3 >/dev/null 2>&1 || exit 0 # fail-open: jamás impide cerrar sesión
|
|
12
|
+
|
|
13
|
+
# Mensaje a STDOUT (no stderr): un Stop hook con exit 0 no debe bloquear el cierre;
|
|
14
|
+
# el `2>/dev/null` suprime SOLO trazas de python, nunca el nudge.
|
|
15
|
+
python3 - "$STATE" <<'PY' 2>/dev/null || true
|
|
16
|
+
import json, sys
|
|
17
|
+
try:
|
|
18
|
+
d = json.load(open(sys.argv[1]))
|
|
19
|
+
except Exception:
|
|
20
|
+
sys.exit(0)
|
|
21
|
+
hist = d.get("history") or []
|
|
22
|
+
pend = [h for h in hist if isinstance(h, dict) and h.get("reflected") is not True]
|
|
23
|
+
if pend:
|
|
24
|
+
n = len(pend)
|
|
25
|
+
plural = "s" if n != 1 else ""
|
|
26
|
+
print(f"💡 Reflexión pendiente: {n} slice{plural} archivado{plural} sin capturar aprendizajes.")
|
|
27
|
+
print(" Ejecuta /build:reflect para proponer convenciones aprendidas a CLAUDE.md (se aplican tras tu aprobación).")
|
|
28
|
+
PY
|
|
29
|
+
exit 0
|
package/hooks/build-harness.json
CHANGED
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
{
|
|
58
58
|
"type": "command",
|
|
59
59
|
"command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/build-gate-check.sh\""
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "command",
|
|
63
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/reflect-nudge.sh\""
|
|
60
64
|
}
|
|
61
65
|
]
|
|
62
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trycore/spec-build-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"templates/",
|
|
19
19
|
"internal/",
|
|
20
20
|
"docs/",
|
|
21
|
+
"!docs/superpowers",
|
|
21
22
|
"scripts/",
|
|
22
23
|
"METODOLOGIA.md",
|
|
23
24
|
"GOVERNANCE.md",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: building-a-micro-change
|
|
3
|
+
description: Use for genuine maintenance that is NOT new product capability — a typo, a copy/string tweak, a dependency version bump within the stack allowlist, an infra/config/docs change, or a small bug fix of a few lines that adds no new capability. Lightweight lane — branch fix/*|chore/* → change → regression test only if behavior changes → PR — WITHOUT opening active_slice, an epic (EP-XXX), or an OpenSpec change. HARD LIMITS: escalate to building-a-slice (a full epic) if the change adds a new dependency, creates a new public API/endpoint, or changes domain logic or the data model/invariants. The epic stays the unit for product construction; this lane is out-of-band maintenance only.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Micro-change (mantenimiento) — carril ligero
|
|
7
|
+
|
|
8
|
+
Carril para **mantenimiento que no es construcción de producto nueva**. Espeja la filosofía del
|
|
9
|
+
arnés: la épica es la unidad de **construcción**, pero un typo o un bump de dependencia **no son
|
|
10
|
+
construcción** — forzarlos por las 8 fases de `building-a-slice` es ceremonia desproporcionada. Este
|
|
11
|
+
carril les da una vía corta **sin** diluir los guardarraíles deterministas.
|
|
12
|
+
|
|
13
|
+
> **Si una regla aquí contradice la metodología Trycore (`METODOLOGIA.md`), gana la metodología.**
|
|
14
|
+
|
|
15
|
+
## Paso 0 · Decision gate (obligatorio) — ¿es esto un micro-change?
|
|
16
|
+
|
|
17
|
+
Un cambio califica como micro-change **solo si cumple TODO**:
|
|
18
|
+
|
|
19
|
+
- **No añade capacidad de producto nueva.** Corrige, ajusta o mantiene algo que ya existe.
|
|
20
|
+
- **Alcance acotado:** pocas líneas / una sola preocupación. No toca múltiples módulos a la vez.
|
|
21
|
+
- **Proyecto en fase `active`** (el scaffold runnable ya existe y está confirmado). El carril **no**
|
|
22
|
+
es para arrancar proyectos (eso es la Fase 0 de `building-a-slice`).
|
|
23
|
+
|
|
24
|
+
Ejemplos típicos (neutros): corregir un typo o un texto visible; ajustar un valor de configuración;
|
|
25
|
+
actualizar la versión de una dependencia **ya presente en la allowlist**; cambios de docs; un fix de
|
|
26
|
+
una a pocas líneas que repara un comportamiento sin introducir nada nuevo.
|
|
27
|
+
|
|
28
|
+
### Límites DUROS — si el cambio cruza **cualquiera**, STOP: esto es una épica
|
|
29
|
+
|
|
30
|
+
Escala a `building-a-slice` (abre una épica `EP-XXX` con su DoR) si el cambio:
|
|
31
|
+
|
|
32
|
+
1. **Añade una dependencia nueva** (fuera de `stack-allowlist.json`). *Lo bloquea además
|
|
33
|
+
`stack-guard.sh` de forma determinista.*
|
|
34
|
+
2. **Crea un endpoint o una API pública nueva.**
|
|
35
|
+
3. **Cambia lógica de dominio** o el **modelo/invariantes de datos**.
|
|
36
|
+
4. **Desborda el alcance acotado** (introduce capacidad, toca muchos archivos, mezcla preocupaciones).
|
|
37
|
+
|
|
38
|
+
Ante la duda, **es una épica**. El carril micro-change nunca es un atajo para esquivar gates de
|
|
39
|
+
producto.
|
|
40
|
+
|
|
41
|
+
## Pipeline ligero
|
|
42
|
+
|
|
43
|
+
1. **Rama tipada.** Crea `fix/<slug>` (corrección) o `chore/<slug>` (infra/config/docs/bump).
|
|
44
|
+
`gitflow-guard.sh` ya exige rama tipada y prohíbe commit/push directo a `main`.
|
|
45
|
+
2. **Aplica el cambio acotado.** Mantente dentro de los límites duros. Si al implementar descubres
|
|
46
|
+
que cruzas uno, **detente y escala** a `building-a-slice`.
|
|
47
|
+
3. **Test de regresión — solo si cambia comportamiento.** Si el micro-change repara un bug,
|
|
48
|
+
añade/ajusta un test que falle antes y pase después (red→green del fix, delega en
|
|
49
|
+
`superpowers:test-driven-development`). Para cambios **no conductuales** (typo en copy, docs,
|
|
50
|
+
config) **no** se exige test.
|
|
51
|
+
4. **PR a `main`.** Abre el Pull Request (`gitflow-guard.sh` impide la integración por push directo).
|
|
52
|
+
En la descripción del PR indica que es un micro-change y por qué califica (qué límite NO cruza).
|
|
53
|
+
|
|
54
|
+
## Qué se mantiene y qué se salta
|
|
55
|
+
|
|
56
|
+
| Se mantiene (gratis, vía hooks deterministas) | Se salta (por diseño) |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `gitflow-guard` (rama tipada + PR) | DoR formal (escenarios G/W/T, INVEST) |
|
|
59
|
+
| `stack-guard` (no dependencia nueva) | OpenSpec change + bloque `## Trazabilidad` |
|
|
60
|
+
| `lint-typecheck` (estilo + typecheck incremental) | `journey_smoke`, `api`, `data`, DoD reducido |
|
|
61
|
+
| | Apertura de `active_slice` / decisión de Release Gate |
|
|
62
|
+
|
|
63
|
+
`scaffold-guard` no aplica: no hay slice activo y el carril exige proyecto en fase `active` (scaffold
|
|
64
|
+
ya confirmado).
|
|
65
|
+
|
|
66
|
+
## Estado y trazabilidad
|
|
67
|
+
|
|
68
|
+
El micro-change **no escribe** `build-state.json` — es mantenimiento fuera de banda, trazado por el
|
|
69
|
+
historial de git y el PR. No entra a `history[]`, así que `reflect-nudge.sh` **no** sugiere
|
|
70
|
+
reflexionar por él (no hay aprendizaje de épica que capturar en un typo).
|
|
71
|
+
|
|
72
|
+
## Reglas duras
|
|
73
|
+
|
|
74
|
+
- **El decision gate es obligatorio.** Si dudas si algo es micro-change o épica, **es épica**.
|
|
75
|
+
- **Límites duros = STOP, no excepción.** Cruzar un límite obliga a escalar a `building-a-slice`;
|
|
76
|
+
jamás se "fuerza" un micro-change para evitar el DoR.
|
|
77
|
+
- **Integración solo por PR** a `main` (lo respalda `gitflow-guard.sh`).
|
|
78
|
+
- Si una regla aquí contradice `METODOLOGIA.md`, **gana la metodología**.
|
|
@@ -14,6 +14,12 @@ el avance en el estado.
|
|
|
14
14
|
> rama = un PR. Las HU de la épica (que siguen viviendo en `docs/04-historias/`) son el **alcance
|
|
15
15
|
> interno** del change y se listan en `active_slice.hus[]`. Construir por HU suelta es sobre-ingeniería.
|
|
16
16
|
|
|
17
|
+
> **¿Mantenimiento, no producto nuevo?** Un typo, un bump de dependencia ya permitida, un ajuste de
|
|
18
|
+
> copy/config/docs o un fix de pocas líneas **sin nueva capacidad** NO abren una épica: usa la skill
|
|
19
|
+
> **`building-a-micro-change`** (carril ligero `fix/*`|`chore/*` → cambio → PR). Si ese micro-change
|
|
20
|
+
> cruza un **límite duro** (dependencia nueva, API/endpoint nuevo, lógica de dominio o datos), escala
|
|
21
|
+
> **aquí** y ábrelo como épica.
|
|
22
|
+
|
|
17
23
|
## Principio de operación
|
|
18
24
|
- **Una sola fuente de verdad**: `.claude/state/build-state.json` (schema + protocolo en
|
|
19
25
|
`.claude/state/README.md`). Lee antes de actuar; escribe una vez por transición.
|
|
@@ -6,7 +6,7 @@ la épica; lo de abajo aplica a la épica y a **cada HU** que cubre (`hus[]`). L
|
|
|
6
6
|
- [ ] **Épica válida**: `EP-XXX` existe en `docs/03-backlog/epicas.md` con trazabilidad a objetivos del PRD.
|
|
7
7
|
- [ ] **HU enumeradas**: la épica tiene ≥1 HU; todas las que entran se listan en `hus[]`.
|
|
8
8
|
- [ ] **Frontmatter completo** en cada `docs/04-historias/HU-XXX.md`: `id, titulo, epica, prioridad, complejidad, estado` y `estado: lista`.
|
|
9
|
-
- [ ] **AC en Given/When/Then** por HU:
|
|
9
|
+
- [ ] **AC en Given/When/Then** por HU, **proporcional a `complejidad`** (cubre los modos de fallo que *realmente existen*, no una cuota fija): `trivial`/baja → **1–2** (happy + el error/edge crítico si existe); `media` → **3** (happy + error + edge); `alta` → **3–5** (cobertura completa). Regla dura Trycore: si existe una rama de error/edge, **debe** tener su escenario (lo que se elimina es fabricar 3–5 para una HU trivial).
|
|
10
10
|
- [ ] **INVEST** por HU: pasa los 6 criterios (Independent, Negotiable, Valuable, Estimable, Small, Testable). Ante duda, invocar `invest-validator`.
|
|
11
11
|
- [ ] **Dependencias resueltas**: las épicas/HU de las que depende están archivadas (`history[]`) o explícitamente no bloquean.
|
|
12
12
|
- [ ] **Cabe en el stack** del PRD §7 (no requiere tecnología fuera de `stack-allowlist.json`).
|
|
@@ -16,7 +16,9 @@ gh pr create --base main --head feature/<slug> --fill # integración por PR
|
|
|
16
16
|
|
|
17
17
|
## Convenciones
|
|
18
18
|
- **Rama**: `feature/<slug-kebab>` (nuevo valor), `fix/<slug>` (corrección), `chore/<slug>` (infra/docs).
|
|
19
|
-
Una rama por **épica** (= un slice): `feature/ep-003-pricing-engine`.
|
|
19
|
+
Una rama por **épica** (= un slice): `feature/ep-003-pricing-engine`. Las ramas `fix/*` y `chore/*`
|
|
20
|
+
son también el carril de la skill `building-a-micro-change` (mantenimiento que no es producto nuevo:
|
|
21
|
+
va a PR sin abrir épica ni `active_slice`; ver sus límites duros).
|
|
20
22
|
- **Commits**: Conventional Commits (`feat:`, `fix:`, `test:`, `refactor:`, `chore:`, `docs:`).
|
|
21
23
|
- **PR**: título claro, descripción enlazando la épica, sus HU (`hus[]`) y el OpenSpec change; checks
|
|
22
24
|
(lint, types, tests, newman) en verde antes de merge; squash recomendado.
|
package/state/README.md
CHANGED
|
@@ -51,6 +51,15 @@ Arranca en `confirmed: false` y **solo** pasa a `true` por **confirmación expl
|
|
|
51
51
|
restrictiva para abrir cualquier slice: el hook `scaffold-guard.sh` bloquea escribir código de
|
|
52
52
|
slice (fases `red…data`) mientras `confirmed` no sea `true`. El arnés **no genera** el scaffold.
|
|
53
53
|
|
|
54
|
+
### Reflexión post-slice (ciclo autocorrectivo)
|
|
55
|
+
|
|
56
|
+
Tras archivar un slice, su entrada en `history[]` puede llevar `reflected` / `reflected_at`. El
|
|
57
|
+
hook `reflect-nudge.sh` (evento `Stop`, **no bloqueante**) sugiere ejecutar `/build:reflect` mientras
|
|
58
|
+
exista al menos una entrada con `reflected != true`. `/build:reflect` lo ejecuta el **modelo**:
|
|
59
|
+
detecta convención nueva o error recurrente, **propone** un parche al bloque `trycore-build-learnings`
|
|
60
|
+
de `CLAUDE.md` (se aplica **solo tras tu aprobación**) y estampa `reflected: true` → el nudge calla.
|
|
61
|
+
El razonamiento vive en el modelo; el hook solo es un recordatorio determinista.
|
|
62
|
+
|
|
54
63
|
## Quién escribe qué
|
|
55
64
|
|
|
56
65
|
| Campo / gate | Lo escribe | Cadencia |
|
|
@@ -62,4 +71,5 @@ slice (fases `red…data`) mientras `confirmed` no sea `true`. El arnés **no ge
|
|
|
62
71
|
| `gates.api` | `api-contract-tester` | slice |
|
|
63
72
|
| `gates.data` | `data-consistency-checker` | slice |
|
|
64
73
|
| `releases[]` (`security`, `smell`, `ux`, `coherence`, `stack_arch`, `integration`, `status`) | `releasing-a-version` (delega en `security-reviewer`, `simple-design-reviewer`, `ux-krug-reviewer`, `coherence-three-way`, `stack-guardian`) | release |
|
|
74
|
+
| `history[].reflected` · `history[].reflected_at` | `/build:reflect` | post-slice (tras archivar) |
|
|
65
75
|
| `harness_phase` | `load-build-state.sh` (SessionStart) | — |
|
|
@@ -86,7 +86,9 @@
|
|
|
86
86
|
},
|
|
87
87
|
"updated_at": { "type": "string", "format": "date-time" },
|
|
88
88
|
"updated_by": { "type": "string", "description": "Agente o hook que escribió el estado." },
|
|
89
|
-
"notes": { "type": "string", "description": "Nota libre opcional (p.ej. changes/branches adicionales de una épica construida en varios pasos)." }
|
|
89
|
+
"notes": { "type": "string", "description": "Nota libre opcional (p.ej. changes/branches adicionales de una épica construida en varios pasos)." },
|
|
90
|
+
"reflected": { "type": "boolean", "description": "True si /build:reflect ya capturó los aprendizajes de este slice archivado (ciclo autocorrectivo). Mientras sea false/ausente, reflect-nudge.sh sugiere reflexionar al cerrar sesión." },
|
|
91
|
+
"reflected_at": { "type": "string", "format": "date-time", "description": "Cuándo se reflexionó (ISO-8601 UTC). Lo escribe /build:reflect." }
|
|
90
92
|
}
|
|
91
93
|
},
|
|
92
94
|
"release": {
|
|
@@ -54,4 +54,10 @@ Estos puntos de extensión los leen los agentes `security-reviewer`, `stack-guar
|
|
|
54
54
|
|
|
55
55
|
<!-- END trycore-build-harness -->
|
|
56
56
|
|
|
57
|
+
<!-- BEGIN trycore-build-learnings -->
|
|
58
|
+
## Convenciones aprendidas (mantenido por /build:reflect)
|
|
59
|
+
|
|
60
|
+
<!-- /build:reflect propone aquí viñetas concretas (convención nueva o error recurrente) tras cerrar un slice; se agregan SOLO con tu aprobación. Revísalas en PR como cualquier cambio de equipo. -->
|
|
61
|
+
<!-- END trycore-build-learnings -->
|
|
62
|
+
|
|
57
63
|
<!-- A partir de aquí, el equipo del proyecto puede agregar instrucciones específicas del cliente. -->
|
|
@@ -1,87 +0,0 @@
|
|
|
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).
|