@trycore/spec-build-harness 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/GOVERNANCE.md +3 -3
- package/METODOLOGIA.md +44 -0
- package/README.md +60 -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/commands.md +15 -3
- package/docs/decisiones/2026-07-03-gsd-vs-openspec-fork-vs-rama.md +157 -0
- package/docs/hooks.md +22 -5
- package/hooks/build/build-gate-check.sh +1 -1
- package/hooks/build/context-monitor.sh +80 -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 +106 -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
|
@@ -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.1",
|
|
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/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
|
@@ -76,8 +76,61 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
|
|
|
76
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
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
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`. |
|
|
79
81
|
| `/opsx:*` (10) | Ciclo OpenSpec: `explore` · `new` · `continue` · `apply` · `verify` · `archive` · `bulk-archive` · `ff` · `onboard` · `sync`. Detalle → [`docs/commands.md`](docs/commands.md). |
|
|
80
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
|
+
|
|
81
134
|
## Arquitectura
|
|
82
135
|
|
|
83
136
|
```
|
|
@@ -88,11 +141,11 @@ trycore-spec-build-harness/
|
|
|
88
141
|
├── agents/build/ ← 12 agentes revisores (segunda opinión, contexto limpio)
|
|
89
142
|
├── commands/
|
|
90
143
|
│ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
|
|
91
|
-
│ └── build/ ←
|
|
92
|
-
├── skills/ ←
|
|
93
|
-
├── 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, …)
|
|
94
147
|
├── state/ ← máquina de estado: build-state.json + schema + README
|
|
95
|
-
├── config/ ← stack-allowlist.template.json (artefacto del consumidor)
|
|
148
|
+
├── config/ ← build-config.template.json (umbrales de contexto) + stack-allowlist.template.json (artefacto del consumidor)
|
|
96
149
|
├── src/ + dist/ ← CLI trycore-build (init/update/status/uninstall/doctor)
|
|
97
150
|
├── scripts/ ← installer + guardias (check-version-sync/agnostic/state-clean)
|
|
98
151
|
└── docs/examples/reference/ ← ejemplo de referencia (fuera del core, excluido de check-agnostic)
|
|
@@ -126,7 +179,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
126
179
|
- ✅ **v0.4.0** — carril `building-a-micro-change` (mantenimiento ligero sin slice) + DoR proporcional a la complejidad.
|
|
127
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**.
|
|
128
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**.
|
|
129
|
-
- ✅ **v0.7.0
|
|
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** — **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**.
|
|
184
|
+
- ✅ **v0.8.1 (actual)** — **hotfix del motor de contexto**: `context-monitor.sh` re-inyectaba `additionalContext` en cada evento `Stop`, lo que re-lanzaba el turno en bucle hasta el tope `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` (9→override). Ahora en `Stop` re-lanza como mucho una vez por sesión (solo la transición a crítico que graba el handoff) y `warning` nunca inyecta en `Stop`. Cubierto por `test-context-monitor.sh`.
|
|
130
185
|
|
|
131
186
|
## Licencia
|
|
132
187
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.8.1
|
|
@@ -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/commands.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Esta referencia cubre los **dos planos de operación** del arnés de construcción:
|
|
4
4
|
|
|
5
5
|
1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.7.0) — instala, actualiza, diagnostica y desinstala el arnés en el proyecto consumidor. Captura el **stack mecánico**.
|
|
6
|
-
2. Los **slash commands de Claude Code** (`/opsx:*` + los
|
|
6
|
+
2. Los **slash commands de Claude Code** (`/opsx:*` + los 7 `/build:*`: `onboard`, `reflect`, `slice`, `release`, `work`, `resume`, `front`) — 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/` → los
|
|
55
|
+
El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → los 7 `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`, `resume`, `front`).
|
|
56
56
|
|
|
57
57
|
### `/opsx:*` — pipeline OpenSpec
|
|
58
58
|
|
|
@@ -99,6 +99,18 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
|
|
|
99
99
|
|---|---|
|
|
100
100
|
| `/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`). |
|
|
101
101
|
|
|
102
|
+
### `/build:resume` — rehidratación sin pérdida de contexto
|
|
103
|
+
|
|
104
|
+
| Slash command | Propósito |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `/build:resume` | Reconstruye el contexto de construcción **desde disco**, no desde la conversación: corre `reconcile-build-state.py`, lee `active_slice` (`gates`, `wiring_checklist`, `progress_log`, `session_continuity`, `parallel_front`) y determina la siguiente acción por prioridad (`resume_hint` → item `failing` de wiring → `sub_slices` pendiente → fase del pipeline). Es el punto de entrada tras un handoff automático del hook `context-monitor.sh` o tras cualquier reinicio de contexto. |
|
|
107
|
+
|
|
108
|
+
### `/build:front` — front paralelo inter-épica
|
|
109
|
+
|
|
110
|
+
| Slash command | Propósito |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `/build:front` | Abre y coordina un **front paralelo** de épicas NO fundacionales y disjuntas en archivos, cada una en su propio worktree/rama/PR. Delega en la skill `managing-parallel-front`: verifica precondiciones (scaffold confirmado, sin épica foundational abierta), selecciona el conjunto disjunto (`scripts/lib/front-plan.py`) y mergea en orden con re-smoke. Úsalo solo con ≥2 épicas no fundacionales disjuntas listas; para una sola épica, usa `/build:slice`. |
|
|
113
|
+
|
|
102
114
|
---
|
|
103
115
|
|
|
104
116
|
## 3. Caveat de canales (CLI vs. plugin)
|
|
@@ -108,7 +120,7 @@ El arnés se distribuye por **dos canales** que coexisten, pero **namespacean di
|
|
|
108
120
|
| Aspecto | Canal **CLI** (canónico) | Canal **Plugin** nativo |
|
|
109
121
|
|---|---|---|
|
|
110
122
|
| 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` |
|
|
111
|
-
| Namespace de comandos | Por subcarpeta: `/opsx:*` y los
|
|
123
|
+
| Namespace de comandos | Por subcarpeta: `/opsx:*` y los 7 `/build:*` (`onboard`, `reflect`, `slice`, `release`, `work`, `resume`, `front`) | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
|
|
112
124
|
| Referencia a agentes | Por su nombre (p. ej. `build-orchestrator`) | Bajo el nombre del plugin |
|
|
113
125
|
| Cross-references internas | ✔ Escritas para este canal (skills invocan `/opsx:*`, agentes por nombre) | Pueden no resolver según están escritas |
|
|
114
126
|
| Recomendación | **Usar este canal para operar un proyecto** | Conveniencia a nivel usuario |
|