@trycore/spec-build-harness 0.7.0 → 0.8.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 +3 -3
- package/INSTALL.md +7 -7
- package/METODOLOGIA.md +44 -0
- package/README.md +63 -5
- package/VERSION +1 -1
- package/agents/build/build-orchestrator.md +6 -0
- package/commands/build/front.md +15 -0
- package/commands/build/resume.md +29 -0
- package/config/build-config.template.json +8 -0
- package/dist/commands/init.js +15 -5
- package/dist/commands/status.js +1 -0
- package/dist/commands/uninstall.js +2 -1
- package/dist/lib/paths.js +7 -0
- package/dist/lib/settings-merge.js +29 -2
- package/dist/lib/state-seed.js +14 -0
- package/docs/agents.md +20 -13
- package/docs/commands.md +34 -4
- package/docs/customization/mcp-extensions.md +5 -4
- package/docs/decisiones/2026-07-03-gsd-vs-openspec-fork-vs-rama.md +157 -0
- package/docs/flujo-harness-funcional.md +42 -0
- package/docs/flujo-harness.md +192 -0
- package/docs/getting-started.md +6 -5
- package/docs/hooks.md +31 -8
- package/hooks/build/build-gate-check.sh +1 -1
- package/hooks/build/context-monitor.sh +74 -0
- package/hooks/build/design-source-guard.sh +1 -1
- package/hooks/build/lib/state-io.sh +55 -0
- package/hooks/build/lint-typecheck.sh +1 -1
- package/hooks/build/load-build-state.sh +46 -2
- package/hooks/build/reconcile-build-state.py +70 -0
- package/hooks/build/reflect-nudge.sh +1 -1
- package/hooks/build/release-gate-nudge.sh +1 -1
- package/hooks/build/scaffold-guard.sh +1 -1
- package/hooks/build/stack-guard.sh +1 -1
- package/hooks/build/statusline-bridge.sh +32 -0
- package/hooks/build-harness.json +24 -0
- package/package.json +1 -1
- package/scripts/lib/front-plan.py +47 -0
- package/scripts/smoke-test.sh +12 -0
- package/scripts/tests/test-context-monitor.sh +70 -0
- package/scripts/tests/test-front-plan.sh +38 -0
- package/scripts/tests/test-install.sh +89 -0
- package/scripts/tests/test-reconciler.sh +54 -0
- package/scripts/tests/test-schema.sh +58 -0
- package/skills/building-a-slice/references/dor.md +2 -0
- package/skills/managing-parallel-front/SKILL.md +36 -0
- package/state/build-state.schema.json +50 -0
- package/templates/CLAUDE.md.template +6 -2
- package/templates/settings-hooks.template.json +1 -1
- package/docs/super-power-workflows.md +0 -281
|
@@ -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.8.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
|
@@ -9,9 +9,9 @@ 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 | 12 agentes de build | `.claude/agents/build/` |
|
|
12
|
-
| Hooks | settings.json +
|
|
13
|
-
| Skill | `building-a-slice` (+11 refs · `workflows/`) · `releasing-a-version` (`workflows/`) · `building-a-micro-change` (carril ligero de mantenimiento) | `.claude/skills/` |
|
|
14
|
-
| Comandos | `/opsx:*` · `/build:onboard` · `/build:reflect` · `/build:slice` · `/build:release` · `/build:work` | `.claude/commands/` |
|
|
12
|
+
| Hooks | settings.json + 13 scripts | `.claude/settings.json`, `.claude/hooks/build/` |
|
|
13
|
+
| Skill | `building-a-slice` (+11 refs · `workflows/`) · `releasing-a-version` (`workflows/`) · `building-a-micro-change` (carril ligero de mantenimiento) · `managing-parallel-front` (front paralelo inter-épica) | `.claude/skills/` |
|
|
14
|
+
| Comandos | `/opsx:*` · `/build:onboard` · `/build:reflect` · `/build:slice` · `/build:release` · `/build:work` · `/build:resume` · `/build:front` | `.claude/commands/` |
|
|
15
15
|
| Config | allowlist de stack | `.claude/config/stack-allowlist.json` |
|
|
16
16
|
|
|
17
17
|
## Fases de activación (`harness_phase`)
|
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.7.0` |
|
|
15
15
|
|
|
16
16
|
> **¿Solo quieres empezar ya?** El [Quickstart](docs/getting-started.md) te lleva de 0 a tu primer slice en pocos comandos. Esta guía es la **referencia detallada** (flags, CI, plugin, troubleshooting).
|
|
17
17
|
|
|
@@ -30,7 +30,7 @@ npm install -g @trycore/spec-build-harness
|
|
|
30
30
|
Esto expone el binario `trycore-build`. Comprueba la versión:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
trycore-build --version # → 0.
|
|
33
|
+
trycore-build --version # → 0.7.0
|
|
34
34
|
trycore-build --help
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -87,9 +87,9 @@ Qué hace `init`:
|
|
|
87
87
|
1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
|
|
88
88
|
2. **Siembra los assets** en rutas nativas de Claude Code:
|
|
89
89
|
- `.claude/agents/build/` — 12 agentes.
|
|
90
|
-
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`, `/build:reflect`).
|
|
91
|
-
- `.claude/skills/` —
|
|
92
|
-
- `.claude/hooks/build/` —
|
|
90
|
+
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (5 comandos: `/build:onboard`, `/build:reflect`, `/build:slice`, `/build:release`, `/build:work`).
|
|
91
|
+
- `.claude/skills/` — 13 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `openspec-*`).
|
|
92
|
+
- `.claude/hooks/build/` — 10 hooks bash.
|
|
93
93
|
3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
|
|
94
94
|
`state/build-state.json` se siembra **vacío y nunca se sobrescribe** (va al `.gitignore`).
|
|
95
95
|
4. **Siembra `config/stack-allowlist.json`** (artefacto del consumidor; lo puebla `/build:onboard`).
|
|
@@ -205,7 +205,7 @@ Como conveniencia a nivel usuario, el arnés también se instala como **plugin n
|
|
|
205
205
|
### ⚠ Caveat de canales (importante)
|
|
206
206
|
|
|
207
207
|
- El **canal npm CLI es el CANÓNICO**. `trycore-build init` instala los comandos en
|
|
208
|
-
`.claude/commands/{opsx,build}/`, que namespacean **por subcarpeta** → `/opsx:*` y `/build
|
|
208
|
+
`.claude/commands/{opsx,build}/`, que namespacean **por subcarpeta** → `/opsx:*` y `/build:*`,
|
|
209
209
|
y los agentes se referencian por su **nombre** (`security-reviewer`, `stack-guardian`, …).
|
|
210
210
|
- El **canal plugin nativo** namespacea **todos** los componentes bajo el **nombre del plugin**
|
|
211
211
|
(→ `/trycore-spec-build-harness:*`), por diseño de Claude Code.
|
|
@@ -235,7 +235,7 @@ Ambos paquetes coexisten en el mismo `.claude/` **sin colisión**, porque usan *
|
|
|
235
235
|
|
|
236
236
|
| | Discovery — `spec-product-flow` | Construcción — `spec-build-harness` |
|
|
237
237
|
|---|---|---|
|
|
238
|
-
| Comandos | `/trycore:*` | `/opsx:*` + `/build
|
|
238
|
+
| Comandos | `/trycore:*` | `/opsx:*` + `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`) |
|
|
239
239
|
| Marca de versión | `.trycore-version` | `.build-harness-version` |
|
|
240
240
|
| Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
|
|
241
241
|
|
package/METODOLOGIA.md
CHANGED
|
@@ -282,6 +282,43 @@ paralelo** y devuelven síntesis (protegen el contexto). Resultado en `releases[
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
+
## 5-bis. Front paralelo inter-épica (outer-loop, opcional)
|
|
286
|
+
|
|
287
|
+
El inner loop (§3) construye **una épica a la vez, secuencial**. Cuando hay **≥ 2 épicas
|
|
288
|
+
`layer: business` listas** (DoR pasado) y **disjuntas en archivos**, el arnés permite construirlas
|
|
289
|
+
**en paralelo** sin romper la regla de un slice activo por árbol: el comando `/build:front`
|
|
290
|
+
(skill `managing-parallel-front`) abre un **`git worktree` por épica** —cada worktree con su propio
|
|
291
|
+
`active_slice` singular y su propio `build-state.json`; el inner loop **no cambia** dentro de cada
|
|
292
|
+
worktree— y coordina la selección, la construcción y el merge desde el estado raíz
|
|
293
|
+
(`parallel_front`). Es un concepto de **outer-loop**: no es una fase nueva del pipeline por-épica, es
|
|
294
|
+
orquestación **entre** épicas.
|
|
295
|
+
|
|
296
|
+
**Compuertas del front:**
|
|
297
|
+
|
|
298
|
+
1. **Foundational-first (G1).** Ninguna épica `layer: foundational` entra jamás al front: el
|
|
299
|
+
cimiento se construye secuencial, antes que el negocio (§1-bis.2, §3.1). Solo épicas
|
|
300
|
+
`layer: business` son candidatas a paralelizarse.
|
|
301
|
+
2. **Disjunción por `files_scope` (G2).** `scripts/lib/front-plan.py` calcula, a partir de los globs
|
|
302
|
+
declarados en `files_scope` de cada épica candidata, qué subconjunto es mutuamente disjunto
|
|
303
|
+
(`selected`, va al front) y cuál se solapa (`serialized`, espera y se construye después,
|
|
304
|
+
secuencial). Sin `files_scope` declarado, una épica no es candidata al front.
|
|
305
|
+
3. **Merge en orden + re-smoke (G3).** El merge de los worktrees sigue un `merge_order`
|
|
306
|
+
determinista; tras **cada** merge se re-corre el `journey_smoke` completo sobre el árbol
|
|
307
|
+
principal (no basta con el smoke local del worktree). Un solape no capturado por la disjunción
|
|
308
|
+
declarada que produzca conflicto de merge lo caza este re-smoke: la épica perdedora se
|
|
309
|
+
**serializa** (rebase + re-correr sus gates), nunca se fuerza el merge ni se aborta el front
|
|
310
|
+
entero.
|
|
311
|
+
|
|
312
|
+
**Regla de drenado (drain).** Si mientras el front está activo (`parallel_front.status: "open"`)
|
|
313
|
+
aparece o se vuelve elegible una épica `layer: foundational`, el front **deja de admitir épicas
|
|
314
|
+
nuevas** y pasa a `parallel_front.status: "draining"`: los worktrees en curso terminan y mergean
|
|
315
|
+
normalmente, pero no se abren worktrees adicionales. Solo cuando todos los miembros llegan a
|
|
316
|
+
`merge_status: "merged"` (`parallel_front` se limpia a `null`) puede abrirse la épica fundacional
|
|
317
|
+
pendiente. El drenado existe porque **fundacional siempre precede a negocio** (§1-bis.2): el front
|
|
318
|
+
nunca puede ser la razón por la que el cimiento se retrasa.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
285
322
|
## 6. Contrato de trazabilidad change ↔ épica
|
|
286
323
|
|
|
287
324
|
Cada OpenSpec change corresponde a **exactamente una épica** (la unidad de construcción) y declara
|
|
@@ -349,6 +386,9 @@ agente lo lee antes de actuar. Estructura: `version`, `harness_phase`, `active_s
|
|
|
349
386
|
| `gates.data` | `data-consistency-checker` | slice |
|
|
350
387
|
| `gates.wiring_verified` | `wiring-adversarial-verifier` (independiente, contexto virgen) | slice (antes de `dod`) |
|
|
351
388
|
| `releases[]` (security, smell, ux, coherence, stack_arch, integration, status) | `releasing-a-version` (delega en los reviewers) | release |
|
|
389
|
+
| `parallel_front` (status, members[], merge_order) | skill `managing-parallel-front` (vía `/build:front`) | front (outer-loop, opcional) |
|
|
390
|
+
| `active_slice.session_continuity` | `context-monitor.sh` (auto-handoff) / `/build:resume` | por sesión (critical/PreCompact) |
|
|
391
|
+
| `active_slice.branch_drift`, degradación de `wiring_checklist` sin evidencia | `reconcile-build-state.py` (SessionStart / `/build:resume`) | por sesión |
|
|
352
392
|
| `harness_phase` | `load-build-state.sh` (SessionStart) | — |
|
|
353
393
|
|
|
354
394
|
**Régimen de fases (`harness_phase`):** arranca en `authoring`; `load-build-state.sh` lo cambia a
|
|
@@ -457,3 +497,7 @@ El arnés **no escribe** en `docs/`; cuando una HU no cumple DoR, devuelve el tr
|
|
|
457
497
|
10. El core es **agnóstico**: lo específico del dominio se inyecta vía `/build:onboard` y
|
|
458
498
|
`stack-allowlist.json`; el ejemplo de referencia vive en `docs/examples/reference/`.
|
|
459
499
|
11. Si una skill, agente o reference contradice este documento, **gana la metodología**.
|
|
500
|
+
12. **Front paralelo es outer-loop y drena ante fundacional (§5-bis)**: solo épicas `layer: business`
|
|
501
|
+
disjuntas en `files_scope` se paralelizan; una épica `layer: foundational` nunca entra al front y
|
|
502
|
+
lo pone en `draining` hasta que se vacía; el merge exige re-smoke del journey completo tras cada
|
|
503
|
+
integración.
|
package/README.md
CHANGED
|
@@ -73,8 +73,64 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
|
|
|
73
73
|
|---|---|
|
|
74
74
|
| `/build:onboard` | Onboarding capa 2: lee el PRD, pregunta por PII/IA/determinismo/secretos, resuelve `{{placeholders}}` y escribe la auto-memory. |
|
|
75
75
|
| `/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. |
|
|
76
|
+
| `/build:slice` | Entrada del **inner loop**: abre o continúa un slice (épica `EP-XXX`) y conduce el pipeline DoR → change → TDD → smoke → api/data → DoD → PR+archive. Adaptador delgado que delega en la skill `building-a-slice`. |
|
|
77
|
+
| `/build:release` | Entrada del **outer loop**: corre el Release Gate **una sola vez** sobre el diff acumulado (los 5 reviewers pesados en paralelo + integración secuencial). Delega en la skill `releasing-a-version`. |
|
|
78
|
+
| `/build:work` | Router *classify-and-act*: clasifica el trabajo entrante y enruta al carril correcto (`building-a-micro-change` · `building-a-slice` · `releasing-a-version`). Es ruteo, no política: no ejecuta el pipeline ni toca el estado. |
|
|
79
|
+
| `/build:resume` | Rehidrata el slice activo **desde disco** (no desde la conversación) tras un reinicio de contexto: reconcilia el estado, lee `session_continuity`/`wiring_checklist`/`parallel_front` y determina la siguiente acción por prioridad. |
|
|
80
|
+
| `/build:front` | Abre y coordina un **front paralelo** de épicas no fundacionales y disjuntas en archivos (`parallel_front`), cada una en su worktree/rama/PR. Delega en la skill `managing-parallel-front`. |
|
|
76
81
|
| `/opsx:*` (10) | Ciclo OpenSpec: `explore` · `new` · `continue` · `apply` · `verify` · `archive` · `bulk-archive` · `ff` · `onboard` · `sync`. Detalle → [`docs/commands.md`](docs/commands.md). |
|
|
77
82
|
|
|
83
|
+
## Gestión de contexto
|
|
84
|
+
|
|
85
|
+
Las sesiones largas (una épica multicapa, un slice que se alarga) pueden agotar la ventana de
|
|
86
|
+
contexto antes de terminar el cableado. El arnés lo mitiga con un **motor de contexto** que corre
|
|
87
|
+
solo, sin que el humano lo invoque:
|
|
88
|
+
|
|
89
|
+
- **Aviso por umbrales.** El hook `context-monitor.sh` lee el contexto restante en cada
|
|
90
|
+
`PostToolUse`/`PreCompact`/`Stop` y lo compara contra dos umbrales **configurables**
|
|
91
|
+
(`context.warning_pct: 35`, `context.critical_pct: 25` en `build-config.json`): por debajo del
|
|
92
|
+
umbral de aviso, inyecta un recordatorio de acercarse a un punto de corte natural; por debajo del
|
|
93
|
+
crítico (o en `PreCompact`), dispara un **auto-handoff** —una sola vez por sesión— que escribe en
|
|
94
|
+
`active_slice.session_continuity` un `resume_hint` derivado de los items `wiring_checklist` aún
|
|
95
|
+
`failing`, sin esperar a que el humano lo pida.
|
|
96
|
+
- **`/build:resume`.** Para retomar sin pérdida: reconcilia el estado contra disco y determina la
|
|
97
|
+
siguiente acción por prioridad (`resume_hint` → primer item `failing` → siguiente `sub_slice` →
|
|
98
|
+
fase del pipeline). Reconstruye el contexto **desde el estado**, nunca desde la conversación viva.
|
|
99
|
+
- **`config context.auto_checkpoint`** (opt-in, default `false`): si se activa, el handoff marca
|
|
100
|
+
`auto_continue: true` para que la siguiente sesión retome automáticamente el `resume_hint` sin
|
|
101
|
+
preguntar. Apagado por defecto porque el checkpoint automático es una decisión que el equipo debe
|
|
102
|
+
optar explícitamente a tomar.
|
|
103
|
+
- **Caveat de canal.** El aviso proactivo depende de `statusLine`, un ajuste de `settings.json` que
|
|
104
|
+
**solo existe en el canal CLI** (`trycore-build init`/`update`). En una instalación **plugin-only**
|
|
105
|
+
no hay puente de contexto que leer y el motor **degrada fail-open**: no rompe nada, simplemente no
|
|
106
|
+
avisa; `/build:resume` y el reconciliador de disco (ver abajo) siguen funcionando igual porque no
|
|
107
|
+
dependen del puente.
|
|
108
|
+
|
|
109
|
+
El **estado sigue anclado a disco** entre sesiones: `reconcile-build-state.py` corre en cada
|
|
110
|
+
`SessionStart` (y en `/build:resume`) y deriva la verdad desde git y la evidencia de tests —degrada a
|
|
111
|
+
`failing` cualquier item `wiring_checklist` marcado `passing` sin `evidence`, anota *branch drift* si
|
|
112
|
+
la rama real difiere de la del slice, y nunca revierte un gate booleano por sí mismo (ratchet)—.
|
|
113
|
+
Fail-open siempre: un estado ilegible o `git` ausente no bloquean la sesión.
|
|
114
|
+
|
|
115
|
+
## Front paralelo
|
|
116
|
+
|
|
117
|
+
Cuando hay **≥ 2 épicas de negocio (`layer: business`)** listas (DoR pasado) y **disjuntas en
|
|
118
|
+
archivos**, el comando **`/build:front`** (skill `managing-parallel-front`) las construye en
|
|
119
|
+
paralelo, cada una en su propio `git worktree`/rama/PR — el inner loop no cambia: cada worktree
|
|
120
|
+
mantiene su `active_slice` singular y su propio `build-state.json`. Tres compuertas gobiernan el
|
|
121
|
+
front:
|
|
122
|
+
|
|
123
|
+
1. **Foundational-first.** Ninguna épica `layer: foundational` entra jamás al front; si una aparece
|
|
124
|
+
mientras el front está activo, el front pasa a `status: "draining"` (termina lo que ya empezó, no
|
|
125
|
+
admite épicas nuevas) hasta vaciarse.
|
|
126
|
+
2. **Disjunción por `files_scope`.** `scripts/lib/front-plan.py` calcula, por conjuntos de globs
|
|
127
|
+
declarados en cada épica, qué candidatas son mutuamente disjuntas (`selected`) y cuáles deben
|
|
128
|
+
esperar (`serialized`) por solaparse.
|
|
129
|
+
3. **Merge en orden + re-smoke.** El merge de los worktrees sigue un `merge_order` determinista; tras
|
|
130
|
+
cada merge se re-corre el `journey_smoke` completo en el árbol principal. Un conflicto no
|
|
131
|
+
detectado por la disjunción declarada lo caza el re-smoke: la épica perdedora se serializa
|
|
132
|
+
(rebase + re-correr sus gates) en vez de abortar el front.
|
|
133
|
+
|
|
78
134
|
## Arquitectura
|
|
79
135
|
|
|
80
136
|
```
|
|
@@ -85,11 +141,11 @@ trycore-spec-build-harness/
|
|
|
85
141
|
├── agents/build/ ← 12 agentes revisores (segunda opinión, contexto limpio)
|
|
86
142
|
├── commands/
|
|
87
143
|
│ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
|
|
88
|
-
│ └── build/ ← /build
|
|
89
|
-
├── skills/ ←
|
|
90
|
-
├── hooks/build/ ←
|
|
144
|
+
│ └── build/ ← 7 comandos /build:* (onboard, reflect, slice, release, work, resume, front)
|
|
145
|
+
├── skills/ ← 14 skills (building-a-slice, building-a-micro-change, releasing-a-version, managing-parallel-front, 10 openspec-*) + 3 plantillas *.workflow.js (opt-in, read-only)
|
|
146
|
+
├── hooks/build/ ← 13 hooks (gate-check, reflect-nudge, release-gate-nudge, scaffold-guard, gitflow-guard, stack-guard, statusline-bridge, context-monitor, reconcile-build-state, …)
|
|
91
147
|
├── state/ ← máquina de estado: build-state.json + schema + README
|
|
92
|
-
├── config/ ← stack-allowlist.template.json (artefacto del consumidor)
|
|
148
|
+
├── config/ ← build-config.template.json (umbrales de contexto) + stack-allowlist.template.json (artefacto del consumidor)
|
|
93
149
|
├── src/ + dist/ ← CLI trycore-build (init/update/status/uninstall/doctor)
|
|
94
150
|
├── scripts/ ← installer + guardias (check-version-sync/agnostic/state-clean)
|
|
95
151
|
└── docs/examples/reference/ ← ejemplo de referencia (fuera del core, excluido de check-agnostic)
|
|
@@ -122,7 +178,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
122
178
|
- ✅ **v0.3.0** — **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`.
|
|
123
179
|
- ✅ **v0.4.0** — carril `building-a-micro-change` (mantenimiento ligero sin slice) + DoR proporcional a la complejidad.
|
|
124
180
|
- ✅ **v0.5.0** — seguro de fuente de diseño (`design_source` + `design-source-guard.sh`) + agente `ux-fidelity-reviewer` (gate `fidelity`, inner loop). Total: **11 agentes**, **9 hooks**.
|
|
125
|
-
- ✅ **v0.6.0
|
|
181
|
+
- ✅ **v0.6.0** — **calidad de cierre contra horizonte largo** (feedback exodocs): handoff fino en disco (`wiring_checklist[]` + `progress_log[]` + `sub_slices[]`), gate `wiring_verified` por nuevo agente **`wiring-adversarial-verifier`** (verificación adversarial independiente, contexto virgen), cimiento pre-construido + tag `layer` y gate de tamaño en el DoR, runner fuera-de-chat `integration-check`, y **fidelidad estricta por verificación visual real** (MCP requerido para UI). Convenciones anti-deriva (producto completo, no MVP) upstreadas al bloque del arnés. Total: **12 agentes**, **9 hooks**.
|
|
182
|
+
- ✅ **v0.7.0** — **orquestación con workflows dinámicos + hardening** (de una evaluación adversarial del propio arnés): **3 plantillas `*.workflow.js`** opt-in y read-only (`explore-fanout`, `wiring-verify`, `release-gate`) que entran **solo donde aportan valor** y nunca en el camino caliente del inner loop; **3 comandos nuevos** `/build:slice` (entrada del inner loop), `/build:release` (outer loop) y `/build:work` (router *classify-and-act*); hook **`release-gate-nudge.sh`** (Stop, determinista: solo sugiere el Release Gate). Rename de los gates de los 5 reviewers pesados → `releases[].gates.{security,smell,ux,coherence,stack_arch}` (`stack`→`stack_arch`; separación `coherence` (release) / `coherence_link` (inner)). Hardening: degradación segura en 8 agentes, escritura atómica del estado, cierre del bypass de specs no-semver, `wiring` exige evidencia ejecutada y `check-agnostic` barre `*.js`. Total: **12 agentes**, **10 hooks**, **5 comandos `/build:*`**.
|
|
183
|
+
- ✅ **v0.8.0 (actual)** — **motor de contexto + estado anclado a disco + front paralelo inter-épica**: hooks **`statusline-bridge.sh`** (canal CLI) + **`context-monitor.sh`** (umbrales `context.warning_pct`/`context.critical_pct` configurables, 35%/25% por defecto) con **auto-handoff** a `session_continuity` en critical/`PreCompact` y comando **`/build:resume`** para rehidratar desde disco; config **`context.auto_checkpoint`** (opt-in). Reconciliador **`reconcile-build-state.py`** (`SessionStart`): deriva de git + evidencia de tests, degrada `wiring_checklist` sin evidencia, anota *branch drift*, ratchet de gates, fail-open. **Front paralelo** (`parallel_front` en el estado): comando **`/build:front`** + skill **`managing-parallel-front`** + `scripts/lib/front-plan.py` (disjunción por `files_scope`, foundational-first). Schema nuevo: `slice.layer`, `slice.files_scope`, `slice.branch_drift`, `slice.session_continuity`. **Caveat:** `statusLine` es solo canal CLI; en plugin-only el motor de contexto degrada fail-open. Total: **12 agentes**, **13 hooks**, **7 comandos `/build:*`**, **14 skills**.
|
|
126
184
|
|
|
127
185
|
## Licencia
|
|
128
186
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.8.0
|
|
@@ -14,6 +14,12 @@ Protocolo en `.claude/state/README.md`. **Sólo un slice activo a la vez** (mode
|
|
|
14
14
|
La **unidad de construcción es la épica** (`active_slice.epica`); las HU que cubre el change van en
|
|
15
15
|
`active_slice.hus[]`. Un slice = una épica = un change = una rama = un PR.
|
|
16
16
|
|
|
17
|
+
## Coexistencia con el front paralelo (outer-loop)
|
|
18
|
+
Si `parallel_front` existe en el estado principal, el paralelismo lo gobierna la skill
|
|
19
|
+
`managing-parallel-front`; cada worktree corre su propio inner loop con `active_slice` singular.
|
|
20
|
+
**Regla de drenado:** si se necesita abrir una épica `layer=foundational`, primero pon
|
|
21
|
+
`parallel_front.status="draining"` (termina las en curso, no admite nuevas) y espera a cerrarlo.
|
|
22
|
+
|
|
17
23
|
## Trabajo por fases encadenadas (NO one-shot)
|
|
18
24
|
Una épica multicapa es demasiado para una pasada. Trabaja por **fases encadenadas** —**mapear →
|
|
19
25
|
generar → revisar → fix-loop → optimizar**— nunca todo de golpe. Reparte la **exploración** "ancho
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "BUILD: Front"
|
|
3
|
+
description: Abre y coordina un front paralelo de épicas NO fundacionales y disjuntas en archivos, cada una en su worktree/rama/PR. Delega en la skill managing-parallel-front (selección disjunta vía scripts/lib/front-plan.py, worktrees, merge en orden con re-smoke).
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [build-harness, outer-loop, front-paralelo, trycore]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /build:front — Front paralelo inter-épica
|
|
9
|
+
|
|
10
|
+
Delega en la skill **managing-parallel-front**. Resumen:
|
|
11
|
+
1. Verifica precondiciones (scaffold confirmado; sin épica foundational abierta).
|
|
12
|
+
2. Reúne candidatas no fundacionales listas (DoR pasado) con `layer` y `files_scope`.
|
|
13
|
+
3. Selecciona el conjunto disjunto (`scripts/lib/front-plan.py`), abre worktrees, construye y mergea en orden con re-smoke.
|
|
14
|
+
|
|
15
|
+
Úsalo solo cuando haya ≥2 épicas no fundacionales disjuntas listas. Para una sola épica, usa `/build:slice`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "BUILD: Resume"
|
|
3
|
+
description: Rehidrata el slice activo desde disco tras un reinicio de contexto (reconcilia estado, muestra continuidad y la siguiente acción).
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [build-harness, resume, rehydrate, trycore]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /build:resume — Retomar sin pérdida
|
|
9
|
+
|
|
10
|
+
Objetivo: reconstruir el contexto de construcción **desde disco**, no desde la conversación.
|
|
11
|
+
|
|
12
|
+
## Pasos
|
|
13
|
+
|
|
14
|
+
1. **Reconciliar**: `python3 .claude/hooks/build/reconcile-build-state.py .claude/state/build-state.json`
|
|
15
|
+
(degrada wiring sin evidencia; anota branch drift; fail-open).
|
|
16
|
+
|
|
17
|
+
2. **Leer estado**: `.claude/state/build-state.json`. Extraer `active_slice`, sus `gates`, `wiring_checklist`, `progress_log`, `session_continuity`, y `parallel_front` si existe.
|
|
18
|
+
|
|
19
|
+
3. **Determinar la siguiente acción por prioridad** (la primera que aplique):
|
|
20
|
+
1. `session_continuity.resume_hint` presente → ejecutarla.
|
|
21
|
+
2. Items de `wiring_checklist` en `failing` → cablear el primero (con prueba real; no marcar passing sin evidencia).
|
|
22
|
+
3. `sub_slices` con `status!=done` → construir el siguiente.
|
|
23
|
+
4. Según `active_slice.phase` → continuar el pipeline (delegar en la skill `building-a-slice`).
|
|
24
|
+
|
|
25
|
+
4. **Si hay `parallel_front`**: delegar en la skill `managing-parallel-front` (Task 12+).
|
|
26
|
+
|
|
27
|
+
5. Registrar un hito en `progress_log[]` (`by: /build:resume`).
|
|
28
|
+
|
|
29
|
+
**Regla dura:** mientras quede un item `failing`, el slice NO está terminado. Nunca marques `passing` sin evidencia de ejecución real.
|
package/dist/commands/init.js
CHANGED
|
@@ -9,7 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import { ASSETS, readPackageVersion, targetPaths } from '../lib/paths.js';
|
|
10
10
|
import { linkChildren, countChildren, chmodExec } from '../lib/install-engine.js';
|
|
11
11
|
import { upsertMarkedBlock } from '../lib/markers.js';
|
|
12
|
-
import { seedState, seedConfig } from '../lib/state-seed.js';
|
|
12
|
+
import { seedState, seedConfig, seedBuildConfig } from '../lib/state-seed.js';
|
|
13
13
|
import { mergeHarnessSettings } from '../lib/settings-merge.js';
|
|
14
14
|
import { captureStack, renderAllowlist } from '../lib/stack-prompt.js';
|
|
15
15
|
import { checkDeps } from './doctor.js';
|
|
@@ -45,9 +45,16 @@ export async function init(opts) {
|
|
|
45
45
|
linkChildren(ASSETS.commandsOpsx, t.commandsOpsx, linkMode, (n) => n.endsWith('.md'));
|
|
46
46
|
linkChildren(ASSETS.commandsBuild, t.commandsBuild, linkMode, (n) => n.endsWith('.md'));
|
|
47
47
|
const nSkills = linkChildren(ASSETS.skills, t.skillsDir, linkMode);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// hooks/build: acepta .sh Y .py en el nivel raíz (reconcile-build-state.py es un hook v0.8).
|
|
49
|
+
linkChildren(ASSETS.hooksBuild, t.hooksBuild, linkMode, (n) => n.endsWith('.sh') || n.endsWith('.py'));
|
|
50
|
+
// hooks/build/lib: subcarpeta NO recorrida por el linkChildren de arriba — hay que recursar a mano.
|
|
51
|
+
linkChildren(ASSETS.hooksBuildLib, t.hooksBuildLib, linkMode, (n) => n.endsWith('.sh'));
|
|
52
|
+
// scripts/lib: asset nuevo consumido por /build:front (front-plan.py).
|
|
53
|
+
linkChildren(ASSETS.scriptsLib, t.scriptsLib, linkMode, (n) => n.endsWith('.py'));
|
|
54
|
+
if (linkMode === 'copy') {
|
|
55
|
+
chmodExec(t.hooksBuild); // preserva +x [H1] (recorre hooks/build/ incl. lib/)
|
|
56
|
+
chmodExec(t.scriptsLib);
|
|
57
|
+
}
|
|
51
58
|
// 2) Estado (schema/README versionados; build-state.json vacío solo si falta) [C1]
|
|
52
59
|
const { stateSeeded } = seedState(targetDir);
|
|
53
60
|
// 3) stack-allowlist.json del consumidor (artefacto del consumidor)
|
|
@@ -60,6 +67,7 @@ export async function init(opts) {
|
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
const { configSeeded } = seedConfig(targetDir, allowlist);
|
|
70
|
+
const { buildConfigSeeded } = seedBuildConfig(targetDir);
|
|
63
71
|
// 4) Hooks + permisos mínimos en settings.json (canal CLI) [H3,H11]
|
|
64
72
|
const { addedHooks, addedPerms } = mergeHarnessSettings(t.settingsFile);
|
|
65
73
|
// 5) CLAUDE.md: bloque marcado con {{placeholders}} SIN resolver (los resuelve /build:onboard)
|
|
@@ -75,8 +83,10 @@ export async function init(opts) {
|
|
|
75
83
|
console.log(` Comandos: ${countChildren(t.commandsOpsx)} /opsx:* + ${countChildren(t.commandsBuild)} /build:*`);
|
|
76
84
|
console.log(` Skills: ${nSkills} en .claude/skills/`);
|
|
77
85
|
console.log(` Hooks: ${countChildren(t.hooksBuild)} en .claude/hooks/build/ (settings.json +${addedHooks} hook(s), +${addedPerms} permiso(s))`);
|
|
86
|
+
console.log(` Scripts: ${countChildren(t.scriptsLib)} en .claude/scripts/lib/`);
|
|
78
87
|
console.log(` Estado: ${stateSeeded ? 'build-state.json sembrado (vacío)' : 'build-state.json preservado'}`);
|
|
79
88
|
console.log(` Config: ${configSeeded ? 'stack-allowlist.json sembrado' : 'stack-allowlist.json preservado'}`);
|
|
89
|
+
console.log(` Config: ${buildConfigSeeded ? 'build-config.json sembrado' : 'build-config.json preservado'}`);
|
|
80
90
|
console.log('');
|
|
81
91
|
console.log('Siguiente paso — abre Claude Code y ejecuta:');
|
|
82
92
|
console.log(' /build:onboard # parametriza el dominio (PII, capa IA, capa determinista) y escribe memoria');
|
|
@@ -133,7 +143,7 @@ function syncGitignoreBlock(targetDir, mode) {
|
|
|
133
143
|
const lines = [GI_BEGIN + ' ───────────────────────', '.claude/state/build-state.json'];
|
|
134
144
|
if (mode === 'symlink') {
|
|
135
145
|
// Los symlinks absolutos a node_modules global se rompen en otra máquina si se commitean.
|
|
136
|
-
lines.push('.claude/agents/build/', '.claude/commands/opsx/', '.claude/commands/build/', '.claude/hooks/build/');
|
|
146
|
+
lines.push('.claude/agents/build/', '.claude/commands/opsx/', '.claude/commands/build/', '.claude/hooks/build/', '.claude/scripts/lib/');
|
|
137
147
|
const skillNames = fs.existsSync(ASSETS.skills)
|
|
138
148
|
? fs.readdirSync(ASSETS.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
139
149
|
: [];
|
package/dist/commands/status.js
CHANGED
|
@@ -27,6 +27,7 @@ export async function status(opts) {
|
|
|
27
27
|
console.log(` Comandos: ${countChildren(t.commandsOpsx)} /opsx:* · ${countChildren(t.commandsBuild)} /build:*`);
|
|
28
28
|
console.log(` Skills: ${countChildren(t.skillsDir)} en .claude/skills/`);
|
|
29
29
|
console.log(` Hooks: ${countChildren(t.hooksBuild)} en .claude/hooks/build/`);
|
|
30
|
+
console.log(` Scripts: ${countChildren(t.scriptsLib)} en .claude/scripts/lib/`);
|
|
30
31
|
console.log('');
|
|
31
32
|
console.log('Requisitos externos:');
|
|
32
33
|
console.log(` openspec: ${hasBinary('openspec') ? '✓' : '✗ falta (npm i -g @fission-ai/openspec)'}`);
|
|
@@ -14,7 +14,8 @@ export async function uninstall(opts) {
|
|
|
14
14
|
}
|
|
15
15
|
console.log(`▶ uninstall trycore-build-harness de ${targetDir}`);
|
|
16
16
|
// 1) Assets exclusivos del arnés
|
|
17
|
-
|
|
17
|
+
// scriptsLib (NO t.scriptsDir completo — .claude/scripts/ podría llevar contenido del consumidor).
|
|
18
|
+
for (const p of [t.agentsBuild, t.commandsOpsx, t.commandsBuild, t.hooksBuild, t.scriptsLib]) {
|
|
18
19
|
if (fs.existsSync(p))
|
|
19
20
|
fs.rmSync(p, { recursive: true, force: true });
|
|
20
21
|
}
|
package/dist/lib/paths.js
CHANGED
|
@@ -32,12 +32,15 @@ export const ASSETS = {
|
|
|
32
32
|
commandsBuild: path.join(PACKAGE_ROOT, 'commands', 'build'),
|
|
33
33
|
skills: path.join(PACKAGE_ROOT, 'skills'),
|
|
34
34
|
hooksBuild: path.join(PACKAGE_ROOT, 'hooks', 'build'),
|
|
35
|
+
hooksBuildLib: path.join(PACKAGE_ROOT, 'hooks', 'build', 'lib'),
|
|
36
|
+
scriptsLib: path.join(PACKAGE_ROOT, 'scripts', 'lib'),
|
|
35
37
|
// Declaración de hooks para el canal PLUGIN (no la instala el CLI; la consume el plugin nativo).
|
|
36
38
|
pluginHooks: path.join(PACKAGE_ROOT, 'hooks', 'build-harness.json'),
|
|
37
39
|
stateSchema: path.join(PACKAGE_ROOT, 'state', 'build-state.schema.json'),
|
|
38
40
|
stateReadme: path.join(PACKAGE_ROOT, 'state', 'README.md'),
|
|
39
41
|
stateTemplate: path.join(PACKAGE_ROOT, 'state', 'build-state.template.json'),
|
|
40
42
|
configTemplate: path.join(PACKAGE_ROOT, 'config', 'stack-allowlist.template.json'),
|
|
43
|
+
buildConfigTemplate: path.join(PACKAGE_ROOT, 'config', 'build-config.template.json'),
|
|
41
44
|
templateClaudeMd: path.join(PACKAGE_ROOT, 'templates', 'CLAUDE.md.template'),
|
|
42
45
|
settingsHooksTemplate: path.join(PACKAGE_ROOT, 'templates', 'settings-hooks.template.json'),
|
|
43
46
|
};
|
|
@@ -51,8 +54,12 @@ export function targetPaths(targetDir) {
|
|
|
51
54
|
commandsBuild: path.join(claudeDir, 'commands', 'build'),
|
|
52
55
|
skillsDir: path.join(claudeDir, 'skills'),
|
|
53
56
|
hooksBuild: path.join(claudeDir, 'hooks', 'build'),
|
|
57
|
+
hooksBuildLib: path.join(claudeDir, 'hooks', 'build', 'lib'),
|
|
58
|
+
scriptsDir: path.join(claudeDir, 'scripts'),
|
|
59
|
+
scriptsLib: path.join(claudeDir, 'scripts', 'lib'),
|
|
54
60
|
configDir: path.join(claudeDir, 'config'),
|
|
55
61
|
configFile: path.join(claudeDir, 'config', 'stack-allowlist.json'),
|
|
62
|
+
buildConfigFile: path.join(claudeDir, 'config', 'build-config.json'),
|
|
56
63
|
stateDir: path.join(claudeDir, 'state'),
|
|
57
64
|
stateSchema: path.join(claudeDir, 'state', 'build-state.schema.json'),
|
|
58
65
|
stateReadme: path.join(claudeDir, 'state', 'README.md'),
|
|
@@ -14,13 +14,23 @@ const CHAIN = '${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/';
|
|
|
14
14
|
function cmd(script) {
|
|
15
15
|
return `"${CHAIN}${script}"`;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
// statusLine: SOLO canal CLI — los plugins no pueden inyectar statusLine.
|
|
18
|
+
// Misma cadena autorresolutiva que los hooks, así funciona tanto si el
|
|
19
|
+
// arnés se instaló por CLI como si conviven ambos canales (CLAUDE_PLUGIN_ROOT
|
|
20
|
+
// resuelve si el plugin está presente; si no, cae a $CLAUDE_PROJECT_DIR/.claude).
|
|
21
|
+
// En instalaciones plugin-only NO hay statusLine → context-monitor.sh no
|
|
22
|
+
// tiene puente (claude-ctx-*.json) que leer → degrada fail-open (sin aviso),
|
|
23
|
+
// lo cual es aceptable.
|
|
24
|
+
const STATUSLINE_CMD = cmd('statusline-bridge.sh');
|
|
25
|
+
/** Las agrupaciones de hooks del arnés (= las de hooks/build-harness.json). */
|
|
18
26
|
const HOOK_SPECS = [
|
|
19
27
|
{ event: 'SessionStart', matcher: 'startup|clear|compact', scripts: ['load-build-state.sh'] },
|
|
20
28
|
{ event: 'PreToolUse', matcher: 'Bash', scripts: ['gitflow-guard.sh'] },
|
|
21
29
|
{ event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh', 'design-source-guard.sh'] },
|
|
22
30
|
{ event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['lint-typecheck.sh', 'coherence-flag.sh'] },
|
|
23
|
-
{ event: '
|
|
31
|
+
{ event: 'PostToolUse', matcher: 'Bash|Edit|Write|MultiEdit|Task', scripts: ['context-monitor.sh'] },
|
|
32
|
+
{ event: 'PreCompact', matcher: '.*', scripts: ['context-monitor.sh'] },
|
|
33
|
+
{ event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh', 'release-gate-nudge.sh', 'context-monitor.sh'] },
|
|
24
34
|
];
|
|
25
35
|
/** Permisos MÍNIMOS y enumerados [H11]. Nunca permisos amplios (mcp__*, additionalDirectories…). */
|
|
26
36
|
const MIN_PERMISSIONS = [
|
|
@@ -49,6 +59,10 @@ function readSettings(file) {
|
|
|
49
59
|
function writeSettings(file, s) {
|
|
50
60
|
fs.writeFileSync(file, JSON.stringify(s, null, 2) + '\n', 'utf8');
|
|
51
61
|
}
|
|
62
|
+
/** True si el statusLine actual (si existe) ya es el nuestro (por script). */
|
|
63
|
+
function isOwnStatusLine(statusLine) {
|
|
64
|
+
return typeof statusLine?.command === 'string' && statusLine.command.includes('statusline-bridge.sh');
|
|
65
|
+
}
|
|
52
66
|
function existingCommands(settings, event) {
|
|
53
67
|
const out = new Set();
|
|
54
68
|
for (const group of settings.hooks?.[event] ?? []) {
|
|
@@ -83,6 +97,14 @@ export function mergeHarnessSettings(settingsFile) {
|
|
|
83
97
|
addedPerms++;
|
|
84
98
|
}
|
|
85
99
|
}
|
|
100
|
+
// statusLine: solo canal CLI (el plugin no inyecta statusLine). Escribe el
|
|
101
|
+
// puente de contexto. Idempotente: si ya apunta a statusline-bridge.sh no
|
|
102
|
+
// lo reescribe (evita diffs innecesarios); si el consumidor no tiene
|
|
103
|
+
// statusLine o tiene otra cosa, este paquete reclama el slot (es único,
|
|
104
|
+
// no se puede "mergear" como los hooks).
|
|
105
|
+
if (!isOwnStatusLine(settings.statusLine)) {
|
|
106
|
+
settings.statusLine = { type: 'command', command: STATUSLINE_CMD };
|
|
107
|
+
}
|
|
86
108
|
writeSettings(settingsFile, settings);
|
|
87
109
|
return { addedHooks, addedPerms };
|
|
88
110
|
}
|
|
@@ -119,6 +141,11 @@ export function removeHarnessSettings(settingsFile) {
|
|
|
119
141
|
changed = true;
|
|
120
142
|
}
|
|
121
143
|
}
|
|
144
|
+
// statusLine: quitar SOLO si es el nuestro (simetría con la instalación).
|
|
145
|
+
if (isOwnStatusLine(settings.statusLine)) {
|
|
146
|
+
delete settings.statusLine;
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
122
149
|
if (changed)
|
|
123
150
|
writeSettings(settingsFile, settings);
|
|
124
151
|
return changed;
|
package/dist/lib/state-seed.js
CHANGED
|
@@ -59,3 +59,17 @@ export function seedConfig(targetDir, allowlist) {
|
|
|
59
59
|
void path;
|
|
60
60
|
return { configSeeded: true };
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Siembra el build-config.json del consumidor (umbrales del context engine).
|
|
64
|
+
* Solo si NO existe. Idempotente: nunca pisa la config ya ajustada por el equipo.
|
|
65
|
+
*/
|
|
66
|
+
export function seedBuildConfig(targetDir) {
|
|
67
|
+
const t = targetPaths(targetDir);
|
|
68
|
+
ensureDir(t.configDir);
|
|
69
|
+
if (fs.existsSync(t.buildConfigFile))
|
|
70
|
+
return { buildConfigSeeded: false };
|
|
71
|
+
if (!fs.existsSync(ASSETS.buildConfigTemplate))
|
|
72
|
+
return { buildConfigSeeded: false };
|
|
73
|
+
fs.copyFileSync(ASSETS.buildConfigTemplate, t.buildConfigFile);
|
|
74
|
+
return { buildConfigSeeded: true };
|
|
75
|
+
}
|
package/docs/agents.md
CHANGED
|
@@ -12,7 +12,14 @@ veredicto al `build-orchestrator`**, que es quien propone la escritura del estad
|
|
|
12
12
|
> **inner loop** (skill `building-a-slice`, por épica `EP-XXX`). Los 5 revisores pesados de
|
|
13
13
|
> release (`security-reviewer`, `simple-design-reviewer`, `ux-krug-reviewer`,
|
|
14
14
|
> `coherence-three-way`, `stack-guardian`) corren **una vez por release** en el **outer loop**
|
|
15
|
-
> (skill `releasing-a-version`).
|
|
15
|
+
> (skill `releasing-a-version`) y escriben sus veredictos en `releases[].gates`
|
|
16
|
+
> (`security`, `smell`, `ux`, `coherence`, `stack_arch` — este último renombrado desde el antiguo
|
|
17
|
+
> `stack` por-slice).
|
|
18
|
+
>
|
|
19
|
+
> **Conducción opcional vía plantillas de workflow.** Tres plantillas read-only (opt-in, no editan
|
|
20
|
+
> estado) sirven de andamiaje para orquestar estos agentes sin sustituir su juicio:
|
|
21
|
+
> `explore-fanout.workflow.js` y `wiring-verify.workflow.js` en `building-a-slice` (inner loop), y
|
|
22
|
+
> `release-gate.workflow.js` en `releasing-a-version` (outer loop).
|
|
16
23
|
|
|
17
24
|
## Tabla resumen
|
|
18
25
|
|
|
@@ -23,8 +30,8 @@ veredicto al `build-orchestrator`**, que es quien propone la escritura del estad
|
|
|
23
30
|
| 3 | `security-reviewer` | sonnet | Revisores de release | Gate `security` — seguridad enfocada al dominio |
|
|
24
31
|
| 4 | `simple-design-reviewer` | sonnet | Revisores de release | Gate `smell` — 4 reglas de Beck + code smells |
|
|
25
32
|
| 5 | `ux-krug-reviewer` | sonnet | Revisores de release | Gate `ux` — usabilidad (Steve Krug) |
|
|
26
|
-
| 6 | `coherence-three-way` | **opus** | Revisores de release | Gate `coherence` — coherencia triple AC↔change↔código |
|
|
27
|
-
| 7 | `stack-guardian` | sonnet | Revisores de release | Gate `
|
|
33
|
+
| 6 | `coherence-three-way` | **opus** | Revisores de release | Gate `coherence` (en `releases[].gates`) — coherencia triple AC↔change↔código |
|
|
34
|
+
| 7 | `stack-guardian` | sonnet | Revisores de release | Gate `stack_arch` (en `releases[].gates`) — stack y arquitectura vs. allowlist |
|
|
28
35
|
| 8 | `api-contract-tester` | sonnet | Contrato / datos | Gate `api` — pruebas de contrato (Newman/Postman) |
|
|
29
36
|
| 9 | `data-consistency-checker` | sonnet | Contrato / datos | Gate `data` — invariantes y consistencia de datos |
|
|
30
37
|
| 10 | `change-epic-coherence` | sonnet | Trazabilidad | Gate `coherence_link` — enlace change↔épica↔HU |
|
|
@@ -53,7 +60,7 @@ frontmatter completo y `estado: lista`; AC en Given/When/Then con happy/error/ed
|
|
|
53
60
|
dependencias declaradas; alcance dentro de la allowlist) y abre el `active_slice` si pasa. A
|
|
54
61
|
la salida valida la **Definition of Done reducida por slice** (gate `dod`): `tdd`,
|
|
55
62
|
`journey_smoke`, `coherence_link`, `data`, `api`, documentación con back-refs y hooks verdes.
|
|
56
|
-
**No valida aquí** `security`, `smell`, `ux`, `coherence` ni `
|
|
63
|
+
**No valida aquí** `security`, `smell`, `ux`, `coherence` ni `stack_arch`: esos son del Release Gate (viven en `releases[].gates`).
|
|
57
64
|
|
|
58
65
|
## Revisores de release
|
|
59
66
|
|
|
@@ -69,39 +76,39 @@ los **especializa al dominio declarado por el consumidor**: secretos de servicio
|
|
|
69
76
|
solo server-side, datos sensibles/PII regulados no persistidos crudos, validación de archivos
|
|
70
77
|
y entrada, salida de servicios externos/IA tratada como input no confiable, logs sin PII y
|
|
71
78
|
decisiones auditables sin sobre-exposición. Veredicto por severidad
|
|
72
|
-
(CRÍTICO/ALTO/MEDIO/BAJO); sin CRÍTICO/ALTO → `gates.security: true`.
|
|
79
|
+
(CRÍTICO/ALTO/MEDIO/BAJO); sin CRÍTICO/ALTO → `releases[].gates.security: true`.
|
|
73
80
|
|
|
74
81
|
### `simple-design-reviewer` · modelo `sonnet`
|
|
75
82
|
**Revisor de diseño simple y code smells** (gate `smell`), sobre código ya en verde. Aplica
|
|
76
83
|
las 4 reglas de Kent Beck (pasa los tests → revela la intención → sin duplicación → mínimos
|
|
77
84
|
elementos) y caza un catálogo de smells (funciones largas, clases "Dios", números mágicos,
|
|
78
85
|
duplicación de validación, props drilling, código muerto, `any`, acoplamiento a servicios
|
|
79
|
-
externos). Hallazgos BLOQUEANTE/RECOMENDADO/NIT; sin bloqueantes → `gates.smell: true`.
|
|
86
|
+
externos). Hallazgos BLOQUEANTE/RECOMENDADO/NIT; sin bloqueantes → `releases[].gates.smell: true`.
|
|
80
87
|
|
|
81
88
|
### `ux-krug-reviewer` · modelo `sonnet` · lee el dominio
|
|
82
|
-
**Revisor de usabilidad** según Steve Krug (gate `ux`); aplica solo a
|
|
83
|
-
`gates.ux: null`). Verifica "don't make me think", jerarquía visual, convenciones,
|
|
89
|
+
**Revisor de usabilidad** según Steve Krug (gate `releases[].gates.ux`); aplica solo a releases con UI (sin UI →
|
|
90
|
+
`releases[].gates.ux: null`). Verifica "don't make me think", jerarquía visual, convenciones,
|
|
84
91
|
escaneabilidad, affordances, tolerancia al error (claridad de las decisiones de alto impacto y
|
|
85
92
|
su justificación según el dominio) y accesibilidad básica. Revisión estática y, si la app
|
|
86
93
|
corre, dinámica vía MCP `chrome-devtools` (`take_snapshot`, `lighthouse_audit`). Sin
|
|
87
|
-
bloqueantes → `gates.ux: true`.
|
|
94
|
+
bloqueantes → `releases[].gates.ux: true`.
|
|
88
95
|
|
|
89
96
|
### `coherence-three-way` · modelo `opus` · lee el dominio
|
|
90
|
-
**Auditor de coherencia triple** (gate `coherence`). Usa el modelo más capaz porque razona a
|
|
97
|
+
**Auditor de coherencia triple** (gate `releases[].gates.coherence`). Usa el modelo más capaz porque razona a
|
|
91
98
|
la vez sobre tres documentos: los **AC (G/W/T)** de las HU de la épica ↔ el **OpenSpec change**
|
|
92
99
|
(`specs/` + `tasks.md`) ↔ el **código y tests** implementados. Hace comprobaciones top-down
|
|
93
100
|
(cada requisito tiene implementación) y bottom-up (nada huérfano: ni tests sin propósito ni
|
|
94
101
|
código fuera del alcance de `hus[]`). Produce una matriz de trazabilidad; COHERENTE →
|
|
95
|
-
`gates.coherence: true`. Complementa a `change-epic-coherence` verificando la implementación real.
|
|
102
|
+
`releases[].gates.coherence: true`. Complementa a `change-epic-coherence` verificando la implementación real.
|
|
96
103
|
|
|
97
104
|
### `stack-guardian` · modelo `sonnet` · lee el dominio
|
|
98
|
-
**Guardián del stack** (gate `
|
|
105
|
+
**Guardián del stack** (gate `releases[].gates.stack_arch`, arquitectura; antes `gates.stack` por-slice). Defiende la sección de requisitos
|
|
99
106
|
técnicos del PRD del consumidor (ruta en `stack-allowlist.json#source`), operacionalizada en
|
|
100
107
|
`.claude/config/stack-allowlist.json`. Verifica que las **dependencias** matcheen la
|
|
101
108
|
allowlist y que la **arquitectura** respete el stack declarado (frontend/runtime del
|
|
102
109
|
consumidor; servicio externo/IA usado solo en su frontera server-side; capa de decisión del
|
|
103
110
|
dominio determinista sin servicio no determinista cuando el PRD lo exige; persistencia sin PII
|
|
104
|
-
regulada cruda) y señala anti-patrones. STACK-OK → `gates.
|
|
111
|
+
regulada cruda) y señala anti-patrones. STACK-OK → `releases[].gates.stack_arch: true`. El hook
|
|
105
112
|
`stack-guard.sh` bloquea deps fuera de lista en tiempo real; este agente razona sobre
|
|
106
113
|
arquitectura y uso.
|
|
107
114
|
|