@trycore/spec-build-harness 0.13.0 → 0.14.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/INSTALL.md +56 -3
- package/METODOLOGIA.md +6 -1
- package/README.md +46 -2
- package/VERSION +1 -1
- package/agents/build/dor-dod-gatekeeper.md +10 -3
- package/commands/build/epic.md +109 -0
- package/commands/build/onboard.md +34 -9
- package/commands/build/slice.md +4 -2
- package/commands/build/work.md +6 -2
- package/config/build-config.template.json +2 -1
- package/dist/commands/init.js +13 -0
- package/dist/commands/status.js +13 -1
- package/dist/lib/paths.js +1 -0
- package/dist/lib/runtime-client.js +20 -0
- package/docs/commands.md +3 -2
- package/docs/getting-started.md +11 -1
- package/docs/hooks.md +11 -1
- package/docs/runtime/guia-modo-dual-y-migracion.md +89 -9
- package/docs/runtime/protocolo-cliente-runtime.md +64 -0
- package/hooks/build/design-source-guard.sh +12 -1
- package/hooks/build/heartbeat.sh +64 -4
- package/hooks/build/lib/agent-context.sh +16 -7
- package/hooks/build/lib/config.sh +25 -1
- package/hooks/build/lib/runtime-client.sh +540 -9
- package/hooks/build/lib/runtime-ops.sh +108 -0
- package/hooks/build/scaffold-guard.sh +14 -1
- package/hooks/build/slice-ops.sh +498 -16
- package/package.json +1 -1
- package/scripts/tests/test-config.sh +41 -0
- package/scripts/tests/test-hooks-runtime.sh +198 -3
- package/scripts/tests/test-install.sh +23 -0
- package/scripts/tests/test-runtime-client.sh +500 -0
- package/scripts/tests/test-skill-ops.sh +456 -0
- package/skills/building-a-slice/references/dor.md +21 -9
- package/skills/building-a-slice/references/foundation-contract.md +4 -0
- package/state/README.md +12 -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.14.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/INSTALL.md
CHANGED
|
@@ -19,6 +19,26 @@ Hay **dos canales** de instalación: el **CLI npm** (canónico, recomendado para
|
|
|
19
19
|
proyecto) y el **plugin nativo** de Claude Code (conveniencia a nivel usuario). Lee el
|
|
20
20
|
[caveat de canales](#5-alternativa-plugin-nativo-con-caveat-de-canales) antes de elegir.
|
|
21
21
|
|
|
22
|
+
## ¿Qué camino me toca?
|
|
23
|
+
|
|
24
|
+
Cuatro situaciones, cuatro caminos. Todos empiezan por §1 (CLI global) y §2 (requisitos).
|
|
25
|
+
|
|
26
|
+
| Tu situación | Cómo saberlo | Qué haces |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| **Proyecto nuevo**, sin el arnés | no existe `.claude/.build-harness-version` | §3 `init` → §4 `/build:onboard`. Listo. |
|
|
29
|
+
| **Proyecto existente** con el arnés, quieres ponerlo al día | existe la marca, con una versión vieja | §7 `update`. **No** re-corras `/build:onboard`: tu dominio ya está parametrizado. |
|
|
30
|
+
| **Proyecto existente en `legacy`**, quieres conectarlo al IA Hub | existe la marca y `runtime.mode` es `legacy` | §7 `update` **primero**, luego §9 → [guía runtime §4](docs/runtime/guia-modo-dual-y-migracion.md) |
|
|
31
|
+
| **Proyecto nuevo** que ya nace contra el IA Hub | — | §3 `init` **con las flags de runtime** (§9) → §4 `/build:onboard` |
|
|
32
|
+
|
|
33
|
+
Dos cosas que ahorran disgustos:
|
|
34
|
+
|
|
35
|
+
- **`init` y `update` son el mismo comando.** `init` detecta si ya hay instalación (por
|
|
36
|
+
`.claude/.build-harness-version`) y se comporta como `update`. Re-correrlo es seguro: **nunca**
|
|
37
|
+
pisa `build-state.json` ni `stack-allowlist.json`, que son del consumidor.
|
|
38
|
+
- **Conectar un proyecto al hub exige el arnés al día primero.** Los hooks del cliente runtime no
|
|
39
|
+
existen antes de la 0.9.0: si escribes el modo sobre una instalación vieja, queda escrito y nada
|
|
40
|
+
lo ejecuta.
|
|
41
|
+
|
|
22
42
|
---
|
|
23
43
|
|
|
24
44
|
## 1. Instalar el CLI global (npm)
|
|
@@ -87,7 +107,7 @@ Qué hace `init`:
|
|
|
87
107
|
1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
|
|
88
108
|
2. **Siembra los assets** en rutas nativas de Claude Code:
|
|
89
109
|
- `.claude/agents/build/` — 14 agentes.
|
|
90
|
-
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (
|
|
110
|
+
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (13 comandos: `/build:onboard`, `/build:reflect`, `/build:architect`, `/build:prototype`, `/build:slice`, `/build:release`, `/build:work`, `/build:resume`, `/build:front`, `/build:claim`, `/build:status`, `/build:escalate`, `/build:epic`). Los cuatro últimos son la **superficie de agente del modo runtime** (§9): en `legacy`, `claim`, `escalate` y `epic` devuelven `rc 3` remitiendo al flujo del fichero, y `status` informa de que la fuente de verdad es `build-state.json`.
|
|
91
111
|
- `.claude/skills/` — 16 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens`, `openspec-*`).
|
|
92
112
|
- `.claude/hooks/build/` — 19 hooks (bash + python; 6 son del cliente runtime opt-in — §9).
|
|
93
113
|
3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
|
|
@@ -166,9 +186,9 @@ Tras `init`, abre Claude Code en el proyecto y ejecuta:
|
|
|
166
186
|
```
|
|
167
187
|
|
|
168
188
|
`/build:onboard` lee el PRD técnico (y `openspec/project.md` si existe) y, vía `AskUserQuestion`
|
|
169
|
-
(proponiendo un valor "(Recomendado)" por punto), confirma y resuelve **
|
|
189
|
+
(proponiendo un valor "(Recomendado)" por punto), confirma y resuelve **7 puntos de extensión**
|
|
170
190
|
que leen los agentes de calidad (`security-reviewer`, `stack-guardian`, `data-consistency-checker`,
|
|
171
|
-
`ux-krug-reviewer`, `simple-design-reviewer`):
|
|
191
|
+
`ux-krug-reviewer`, `simple-design-reviewer`, `ux-fidelity-reviewer`):
|
|
172
192
|
|
|
173
193
|
| Placeholder | Qué pregunta |
|
|
174
194
|
|---|---|
|
|
@@ -178,6 +198,7 @@ que leen los agentes de calidad (`security-reviewer`, `stack-guardian`, `data-co
|
|
|
178
198
|
| `{{SENSITIVE_DATA_CATEGORIES}}` | Categorías de datos sensibles / PII reguladas |
|
|
179
199
|
| `{{SERVER_SIDE_SECRETS}}` | Secretos server-side que jamás van al cliente |
|
|
180
200
|
| `{{HIGH_STAKES_DECISIONS}}` | Decisiones de alto impacto que exigen explicabilidad en la UI |
|
|
201
|
+
| `{{DESIGN_SOURCE}}` | Fuente de diseño / referencia visual (prototipo, export), o `N/A` si el proyecto no tiene UI |
|
|
181
202
|
|
|
182
203
|
Qué resuelve:
|
|
183
204
|
|
|
@@ -308,12 +329,44 @@ modo `legacy` (default, sin cambios). Requiere un runtime corriendo y un **token
|
|
|
308
329
|
emitido por un ADMIN en la consola del hub.
|
|
309
330
|
|
|
310
331
|
```bash
|
|
332
|
+
# Proyecto nuevo contra el hub, o proyecto existente YA actualizado (ver la tabla del principio)
|
|
311
333
|
trycore-build init --runtime-url "https://tu-runtime.example.com" --runtime-token "<token>"
|
|
334
|
+
|
|
312
335
|
trycore-build doctor # sección "Runtime": token, conectividad, lock, cola offline
|
|
313
336
|
trycore-build status # sección "Runtime": conexión, proyección, contexto sincronizado
|
|
314
337
|
trycore-build migrate --project-ref "<nombre-en-el-hub>" # bundle de estado histórico para un ADMIN
|
|
315
338
|
```
|
|
316
339
|
|
|
340
|
+
Sin `--runtime-mode`, el default con URL+token es **`dual`** (nunca `runtime`): el fichero local
|
|
341
|
+
sigue mandando y cada transición se espeja al servidor para poder compararlas. El corte directo a
|
|
342
|
+
`runtime` no está soportado por diseño.
|
|
343
|
+
|
|
344
|
+
### Qué gana el proyecto al conectarse
|
|
345
|
+
|
|
346
|
+
| | Qué hace | Necesita el hub |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| **Contexto vivo** | El daemon refresca `/agent/context` cada `runtime.context_refresh_s` segundos (default 45, `0` desactiva) — una terminal abierta se entera de lo que pasa sin reiniciar | **No**, funciona hoy |
|
|
349
|
+
| **Épicas propuestas** | `/build:epic` propone la épica, el hub asigna el `EP-XXX` al aprobarla y `/build:epic --check` la proyecta a `epicas.md` | Sí (`trycore-ia-hub#113`/`#114`) |
|
|
350
|
+
| **Versión de grafo** | `claim` y la propuesta mandan la versión conocida; un `409` provoca refresco y reintento en vez de fallo | Sí (`trycore-ia-hub#115`) |
|
|
351
|
+
| **Reparto de trabajo** | `/build:claim` pide la siguiente tarea por lease; `/build:status` informa; `/build:escalate` registra un bloqueo | Sí |
|
|
352
|
+
|
|
353
|
+
Contra un hub que no implementa una de esas mitades, el cliente **degrada limpiamente**: un `404`
|
|
354
|
+
se aparta diciendo «esta instancia está sin soporte», sin reintentos en bucle, y un hub que no
|
|
355
|
+
versiona el grafo ve el mismo cuerpo de `claim` de siempre.
|
|
356
|
+
|
|
357
|
+
### Configuración
|
|
358
|
+
|
|
359
|
+
`.claude/config/build-config.json`, bloque `runtime`:
|
|
360
|
+
|
|
361
|
+
| Clave | Default | Para qué |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| `mode` | `legacy` | `legacy` \| `dual` \| `runtime`. Un valor inválido cae a `legacy`, nunca a la red. |
|
|
364
|
+
| `stale_seconds` | `900` | A partir de cuántos segundos la proyección se considera stale |
|
|
365
|
+
| `context_refresh_s` | `45` | Cadencia del refresco de contexto del daemon; `0` lo desactiva |
|
|
366
|
+
|
|
367
|
+
Overrides por entorno, útiles para una sesión puntual sin editar el fichero:
|
|
368
|
+
`TRYCORE_RUNTIME_MODE`, `TRYCORE_CONTEXT_REFRESH_S`.
|
|
369
|
+
|
|
317
370
|
Guía completa (los tres modos, cómo activar/verificar/volver a legacy, cuándo se corta) →
|
|
318
371
|
[`docs/runtime/guia-modo-dual-y-migracion.md`](docs/runtime/guia-modo-dual-y-migracion.md).
|
|
319
372
|
|
package/METODOLOGIA.md
CHANGED
|
@@ -542,7 +542,12 @@ al archivar (§6.4), el subárbol **`docs/adr/`** —propiedad de construcción
|
|
|
542
542
|
arquitectura (§9.3), la **épica caparazón** en `docs/03-backlog/epicas.md` — únicamente esa épica,
|
|
543
543
|
únicamente con **aprobación humana explícita** del borrador propuesto en `/build:onboard` Fase 2c
|
|
544
544
|
(frontmatter `origin: harness-draft`); si el humano rechaza, la épica se crea en discovery como
|
|
545
|
-
siempre
|
|
545
|
+
siempre. En modo `runtime` ese carve-out cambia de forma: el arnés **no elige la identidad** —
|
|
546
|
+
propone la épica al hub (`slice-ops.sh propose-epic`), un humano la aprueba y el fichero se
|
|
547
|
+
escribe después como **proyección** del grafo, con el `EP-XXX` que asignó el hub
|
|
548
|
+
(`origin: harness-writeback`, vía `/build:epic`). Es la misma frontera de escritura, con la
|
|
549
|
+
numeración fuera de las manos del agente: elegir el código mirando el fichero es lo que hace que
|
|
550
|
+
dos terminales colisionen —, y el subárbol **`docs/05-prototipo/`** —propiedad de construcción— donde
|
|
546
551
|
`/build:prototype` (skill `prototyping-screens`) produce el prototipo HTML de referencia (la fuente
|
|
547
552
|
de diseño): las pantallas nacen `borrador` y solo pasan a `aprobada` con aprobación humana, análogo
|
|
548
553
|
a `docs/adr/`. Ni `docs/adr/` ni `docs/05-prototipo/` son artefactos de discovery: son salida de
|
package/README.md
CHANGED
|
@@ -79,6 +79,7 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
|
|
|
79
79
|
| `/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. |
|
|
80
80
|
| `/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. |
|
|
81
81
|
| `/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`. |
|
|
82
|
+
| `/build:epic` | Propone una **épica incremental** al hub (modo `runtime`): el agente redacta y propone, el hub asigna el `EP-XXX` al aprobar y un humano aprueba; después `epicas.md` se escribe como **proyección** del grafo. En `legacy`/`dual` no aplica. |
|
|
82
83
|
| `/opsx:*` (10) | Ciclo OpenSpec: `explore` · `new` · `continue` · `apply` · `verify` · `archive` · `bulk-archive` · `ff` · `onboard` · `sync`. Detalle → [`docs/commands.md`](docs/commands.md). |
|
|
83
84
|
|
|
84
85
|
## Gestión de contexto
|
|
@@ -142,7 +143,7 @@ trycore-spec-build-harness/
|
|
|
142
143
|
├── agents/build/ ← 14 agentes revisores (segunda opinión, contexto limpio)
|
|
143
144
|
├── commands/
|
|
144
145
|
│ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
|
|
145
|
-
│ └── build/ ←
|
|
146
|
+
│ └── build/ ← 13 comandos /build:* (onboard, reflect, architect, prototype, slice, release, work, resume, front, claim, status, escalate, epic)
|
|
146
147
|
├── skills/ ← 16 skills (building-a-slice, building-a-micro-change, releasing-a-version, managing-parallel-front, setup-architecture, prototyping-screens, 10 openspec-*) + 3 plantillas *.workflow.js (opt-in, read-only)
|
|
147
148
|
├── hooks/build/ ← 19 hooks (gate-check, reflect-nudge, release-gate-nudge, scaffold-guard, gitflow-guard, stack-guard, statusline-bridge, context-monitor, reconcile-build-state, …) + 6 opt-in del cliente runtime (session-start, event-emitter, context-sync, heartbeat, dual-compare, session-stop — ver docs/hooks.md)
|
|
148
149
|
├── state/ ← máquina de estado legacy: build-state.json + schema + README
|
|
@@ -182,7 +183,50 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
182
183
|
|
|
183
184
|
## Roadmap
|
|
184
185
|
|
|
185
|
-
- ✅ **v0.
|
|
186
|
+
- ✅ **v0.14.0 (actual) — contexto vivo, épicas propuestas y versión de grafo** (#61, #62, #63) —
|
|
187
|
+
tres capacidades del cliente del runtime que componen entre sí. **(1)** El daemon de heartbeat
|
|
188
|
+
refresca `GET /agent/context` con **cadencia propia** (`runtime.context_refresh_s`, default 45 s,
|
|
189
|
+
`0` la desactiva): antes la caché se hidrataba al arrancar la sesión y en cada `claim` —y el
|
|
190
|
+
`claim` ocurre una vez por slice—, así que una terminal abierta podía trabajar horas contra una
|
|
191
|
+
foto vieja; un refresco fallido **conserva** la caché anterior y la marca stale, porque quedarse
|
|
192
|
+
sin proyección dejaría al agente huérfano. **(2)** En modo `runtime` la épica se **propone** al
|
|
193
|
+
hub en vez de escribirse en el backlog: comando nuevo **`/build:epic`**, subcomandos
|
|
194
|
+
`propose-epic`/`epic-status`/`epic-writeback` y un **carril directo** en la cola offline que
|
|
195
|
+
sobrevive a quedarse sin red; el hub asigna el `EP-XXX` al aprobar y `docs/03-backlog/epicas.md`
|
|
196
|
+
pasa a ser **proyección del grafo**, nunca una fuente paralela —el agente pierde la potestad de
|
|
197
|
+
inventar identidades, que es lo que hacía colisionar las historias de dos épicas creadas el mismo
|
|
198
|
+
día—. **(3)** `claim` y la propuesta mandan la **versión de grafo** conocida, estampada *al
|
|
199
|
+
despachar* y no al encolar; un `409` por grafo rancio provoca refresco y un reintento en vez de
|
|
200
|
+
un fallo. `legacy` sigue siendo el default y no cambia. Las mitades de servidor de (2) y (3)
|
|
201
|
+
(`trycore-ia-hub#113`/`#114`/`#115`) están pendientes: contra un hub sin ellas el cliente degrada
|
|
202
|
+
a «instancia sin soporte». Total: **14 agentes**, **19 hooks**, **13 comandos `/build:*`**,
|
|
203
|
+
**16 skills**.
|
|
204
|
+
- ✅ **v0.13.0 — el grafo llega al hub con aristas** — las dependencias entre épicas viven como
|
|
205
|
+
prosa en `epicas.md` (`**Depende de**: EP-001`) y su lectura estaba delegada al modelo, con un
|
|
206
|
+
ejemplo en `/build:onboard` que mostraba `"depends_on": []`: las 41 épicas del piloto entraron
|
|
207
|
+
sin una sola arista y nadie lo detectó, porque un grafo vacío no falla, solo empobrece (#56).
|
|
208
|
+
`graph-bundle.py` gana un **extractor determinista** (`--from-docs` / `--print-deps`) que relee
|
|
209
|
+
el campo, con avisos de cobertura y traza de la fuente en `depends_on_source`. Además,
|
|
210
|
+
`unmapped[].original` viaja siempre como dict (#55): el hub valida el bundle entero con pydantic,
|
|
211
|
+
así que un escalar lo rechazaba **completo** antes de persistir nada.
|
|
212
|
+
- ✅ **v0.12.0 — la fase del slice llega al hub** (#52) — el hub modela la fase con eventos
|
|
213
|
+
`phase_advanced` de orden estricto y exige `phase == pr` para archivar, pero el cliente no emitía
|
|
214
|
+
ninguna: los slices quedaban «en construcción» con su PR ya mergeado. Subcomando
|
|
215
|
+
`slice-ops.sh phase <to>`, que emite la cadena que falte (idempotente, sin saltos), y **catch-up
|
|
216
|
+
automático** al archivar. Además, `outbox/rejected/` deja de ser invisible: la razón del rechazo
|
|
217
|
+
se persiste en el propio fichero y la muestran `slice-ops.sh status` y `trycore-build status`/`doctor`.
|
|
218
|
+
- ✅ **v0.11.1 — auto-cierre de releases en verde** — el cierre manual de una release con 6/6 gates
|
|
219
|
+
PASS era control-teatro: el hub la cierra solo (`closed_by: AUTO_6_OF_6_PASS`) y los casos
|
|
220
|
+
degradados siguen siendo decisión humana. Arreglado también que `release-ops.sh verdict` devolvía
|
|
221
|
+
**422 en todo reporte** (faltaba el campo `verdict` en mayúsculas junto a `status`).
|
|
222
|
+
- ✅ **v0.11.0 — drift cliente↔hub del primer piloto real** (#36–#44) — la primera migración contra
|
|
223
|
+
el hub en producción reveló un desajuste sistemático entre lo que el cliente emite y lo que el
|
|
224
|
+
hub valida: `claim` devolvía 422 en todo intento, la cola offline **nunca drenaba**, `fact`
|
|
225
|
+
emitía tipos fuera del catálogo, `claim --epic` se ignoraba en silencio y repartía otra épica, y
|
|
226
|
+
el import rechazaba 36 de 41 entradas del bundle histórico. Cada fix verificado contra el código
|
|
227
|
+
real del hub, con regresión anti-drift nueva que valida todo tipo encolable contra una réplica
|
|
228
|
+
literal de su catálogo.
|
|
229
|
+
- ✅ **v0.10.0 — convergencia del inner loop** — ataque a los seis multiplicadores de
|
|
186
230
|
latencia de la verificación adversarial (iniciativa #31, issues #25–#30) **sin bajar el rigor**:
|
|
187
231
|
**re-verificación incremental** con `verified_at_sha` por item de `wiring_checklist[]` (las
|
|
188
232
|
pasadas 2+ re-ejecutan O(items tocados), no O(items totales)); **condición de parada** del bucle
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.1
|
|
@@ -46,9 +46,16 @@ cumplen; lista cada una con ✓/✗:
|
|
|
46
46
|
`slice-ops.sh status`. Si el proyecto es `greenfield` con caparazón **requerida** y la épica
|
|
47
47
|
evaluada es `layer: business`: exige la caparazón **completada** (su épica archivada y con la
|
|
48
48
|
checklist evidenciada). Si no lo está,
|
|
49
|
-
**NO abras el slice**: instruye construir primero la épica caparazón
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
**NO abras el slice**: instruye construir primero la épica caparazón. Cuál es esa épica
|
|
50
|
+
depende del modo: en `legacy`/`dual` es `foundation.epic` de `build-state.json` y, si es
|
|
51
|
+
`null`, se define en `/build:onboard` Fase 2c o en discovery. En `runtime` **`foundation.epic`
|
|
52
|
+
no existe** —la proyección solo trae `foundation_done`, y la Fase 2c propone la épica al hub
|
|
53
|
+
sin código—, así que no lo pidas: comprueba con `slice-ops.sh epic-status` si hay una
|
|
54
|
+
propuesta ya `PROPOSED` y, si la hay, instruye consultar `/build:epic --check` y esperar la
|
|
55
|
+
aprobación, **no** repetir la Fase 2c; solo si no hay ninguna propuesta, Fase 2c o discovery.
|
|
56
|
+
Las épicas `layer: foundational`
|
|
57
|
+
no se bloquean por este criterio. Brownfield o caparazón no requerida (`foundation.required
|
|
58
|
+
!== true` en `legacy`/`dual`; `foundation_done: true` en `runtime`) → **N/A** (no
|
|
52
59
|
bloquea, no preguntes). A diferencia del criterio 7, aquí NO evalúas arrastre: en greenfield
|
|
53
60
|
el bloqueo es incondicional hasta que el cimiento exista archivado.
|
|
54
61
|
8. **Tamaño acotado**: si la épica supera el umbral del gate de descomposición —heurística por defecto
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "BUILD: Epic"
|
|
3
|
+
description: Propone una épica nueva al hub (modo runtime) y, tras la aprobación humana, la escribe en docs/03-backlog/epicas.md con el código que asignó el hub. Con --check no propone nada: solo consulta el estado de las propuestas y proyecta las ya aprobadas. El agente NO inventa el EP-XXX. Adaptador delgado sobre slice-ops.sh propose-epic/epic-status/epic-writeback.
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [build-harness, runtime, backlog, epicas, trycore]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /build:epic — proponer una épica (la identidad la decide el hub)
|
|
9
|
+
|
|
10
|
+
Carril de las épicas **incrementales de construcción**: las que nacen en una terminal con el
|
|
11
|
+
proyecto ya registrado en el hub. La descomposición **inicial** del backlog sigue siendo de
|
|
12
|
+
discovery (`/trycore:epicas`, `@trycore/spec-product-flow`) y entra al hub por el import masivo
|
|
13
|
+
de admin — este comando no la sustituye.
|
|
14
|
+
|
|
15
|
+
**Regla dura**: el agente **no elige** el `EP-XXX`. Lo asigna el hub al aprobar. Elegirlo mirando
|
|
16
|
+
`epicas.md` es lo que hace que dos terminales creen la misma numeración y sus historias colisionen.
|
|
17
|
+
|
|
18
|
+
**Entrada (opcional):** `--check`. Sin argumento, el comando **propone** una épica nueva.
|
|
19
|
+
|
|
20
|
+
## Despacho por argumento (léelo antes que nada)
|
|
21
|
+
|
|
22
|
+
El comando tiene **dos carriles** y no se recorren de arriba abajo:
|
|
23
|
+
|
|
24
|
+
- **Con `--check`** → ve directo al **§0** y luego **salta al §3**. `--check` es una consulta de
|
|
25
|
+
**solo lectura** sobre propuestas que ya existen: **no redactes ningún borrador y no propongas
|
|
26
|
+
nada** (§1 y §2 no se ejecutan, ni siquiera "por si acaso"). Es el carril que invocan el DoR
|
|
27
|
+
(`references/dor.md`) y el `dor-dod-gatekeeper` para averiguar si la épica caparazón ya está
|
|
28
|
+
propuesta: si ahí se propusiera una épica, la comprobación crearía **una segunda propuesta de
|
|
29
|
+
la misma épica** — exactamente la colisión que este comando existe para impedir.
|
|
30
|
+
- **Sin argumento** → §0 → §1 → §2. El §3 se recorre después, cuando el humano haya aprobado en
|
|
31
|
+
la consola del hub (normalmente en otra invocación, con `--check`).
|
|
32
|
+
|
|
33
|
+
## 0. Precondición
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bash .claude/hooks/build/slice-ops.sh mode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `legacy` → **STOP**: no hay hub al que proponer. La épica se crea en discovery
|
|
40
|
+
(`/trycore:*`); si es la épica caparazón, por el carve-out de `/build:onboard` Fase 2c.
|
|
41
|
+
- `dual` → **STOP** igual, pero por otra razón: aquí **sí hay hub**, y el fichero local es el
|
|
42
|
+
primario. El carril de propuesta solo opera en `runtime`; la épica se crea como en `legacy` y
|
|
43
|
+
llega al hub por el import de admin. No lo diagnostiques como un problema de conexión.
|
|
44
|
+
- `runtime` → sigue.
|
|
45
|
+
|
|
46
|
+
## 1. Redactar el borrador (con el usuario)
|
|
47
|
+
|
|
48
|
+
Reúne del repo y de `docs/` lo necesario y **propón** al usuario, vía **AskUserQuestion**:
|
|
49
|
+
título, objetivo, capa (`foundational` | `business` | `technical`), alcance de archivos y
|
|
50
|
+
dependencias con épicas existentes. Las historias van con AC en Given/When/Then.
|
|
51
|
+
|
|
52
|
+
No inventes la clasificación de capa ni las dependencias: derívalas del PRD / Story Map y
|
|
53
|
+
confírmalas. Sin aprobación explícita del usuario, **no propongas nada**.
|
|
54
|
+
|
|
55
|
+
Escribe el borrador en un fichero temporal (nunca por argv):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"title": "…",
|
|
60
|
+
"objective": "…",
|
|
61
|
+
"layer": "business",
|
|
62
|
+
"files_scope": ["src/…/**"],
|
|
63
|
+
"depends_on": ["EP-012"],
|
|
64
|
+
"stories": [{"title": "…", "acceptance_criteria": "Dado … Cuando … Entonces …"}]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 2. Proponer
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bash .claude/hooks/build/slice-ops.sh propose-epic --file /tmp/epica-borrador.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| rc | Significado | Qué haces |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `0` | Enviada; el hub devolvió el identificador de la propuesta | Dile al usuario que queda **pendiente de aprobación humana** en la consola del hub |
|
|
77
|
+
| `5` | Encolada, aún sin entregar (sin conexión, o el daemon estaba drenando la cola en ese instante) | Igual, avisando de que se despachará sola y de que `epic-status` la sigue |
|
|
78
|
+
| `6` | El hub la rechazó (o falta `project_id`) | Muestra la razón; **no** reintentes en bucle |
|
|
79
|
+
| `2` | Borrador inválido | Corrige el campo que indica el mensaje y repite |
|
|
80
|
+
| `3` | `legacy`/`dual` | Vuelve al paso 0 |
|
|
81
|
+
|
|
82
|
+
**No escribas `docs/03-backlog/epicas.md` aquí.** El fichero es una proyección del grafo: se
|
|
83
|
+
escribe cuando existe el código, y el código no existe hasta que un humano aprueba.
|
|
84
|
+
|
|
85
|
+
## 3. Después de la aprobación — `/build:epic --check`
|
|
86
|
+
|
|
87
|
+
**Aquí aterriza `--check`** (viniendo del despacho de arriba). Los dos comandos son mecánicos:
|
|
88
|
+
uno consulta, el otro proyecta lo ya aprobado. Ninguno propone nada.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bash .claude/hooks/build/slice-ops.sh epic-status
|
|
92
|
+
bash .claude/hooks/build/slice-ops.sh epic-writeback
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`epic-status` resuelve el estado de cada propuesta (`QUEUED` · `PROPOSED` · `APPROVED` ·
|
|
96
|
+
`REJECTED` · `FAILED`). `epic-writeback` escribe en `epicas.md` **solo** las `APPROVED` que
|
|
97
|
+
falten, con el código del hub, y es idempotente (`rc 7` = nada pendiente).
|
|
98
|
+
|
|
99
|
+
Tras escribir, revisa el bloque con el usuario y **no toques el código a mano**: si algo está
|
|
100
|
+
mal, se corrige en el hub y se vuelve a proyectar.
|
|
101
|
+
|
|
102
|
+
## Guardrails
|
|
103
|
+
|
|
104
|
+
- El agente **propone**; la identidad la decide el hub y la aprobación es humana (gobierno,
|
|
105
|
+
METODOLOGIA §10 regla 8). Ni el modelo ni el arnés aprueban una épica.
|
|
106
|
+
- Los códigos de HU los pone el hub si los devuelve; el arnés **nunca** los inventa.
|
|
107
|
+
- El único fichero de `docs/` que este comando toca es `docs/03-backlog/epicas.md`, y solo con
|
|
108
|
+
un código ya asignado (carve-out de METODOLOGIA §9.2).
|
|
109
|
+
- En `legacy`/`dual` el comando no hace nada: el backlog vive en el fichero.
|
|
@@ -117,20 +117,45 @@ Si `project_kind !== "greenfield"`, salta esta fase (N/A total).
|
|
|
117
117
|
- **Existe** → propónla al usuario y fija `foundation.epic`.
|
|
118
118
|
- **No existe** → **borrador híbrido**: redacta la épica caparazón (título, objetivo, una HU
|
|
119
119
|
por ítem `applies: true` con AC en Given/When/Then) y preséntala vía AskUserQuestion.
|
|
120
|
-
- **Aprueba
|
|
121
|
-
`
|
|
122
|
-
**Este es el ÚNICO caso en que el
|
|
123
|
-
|
|
120
|
+
- **Aprueba**, y `slice-ops.sh mode` dice `legacy`/`dual` → escríbela en
|
|
121
|
+
`docs/03-backlog/epicas.md` con frontmatter `layer: foundational` y
|
|
122
|
+
`origin: harness-draft`, y fija `foundation.epic`. **Este es el ÚNICO caso en que el
|
|
123
|
+
arnés escribe una épica por decisión propia** (carve-out de METODOLOGIA §9.2: solo la
|
|
124
|
+
épica caparazón, solo con aprobación explícita).
|
|
125
|
+
- **Aprueba**, y el modo es `runtime` → **no escribas el fichero**: propón la épica al hub
|
|
126
|
+
(la identidad la asigna él, issue #62) y deja `foundation.epic: null` hasta que vuelva
|
|
127
|
+
con código:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bash .claude/hooks/build/slice-ops.sh propose-epic --file /tmp/epica-caparazon.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Explica al usuario que la épica queda **pendiente de aprobación humana** en la consola
|
|
134
|
+
del hub y que, una vez aprobada, `/build:epic --check` la escribe en `epicas.md` con su
|
|
135
|
+
`EP-XXX`. El hecho de fundación se reporta igual en el paso 3 (`--epic` no viaja al hub).
|
|
124
136
|
- **Rechaza** → **STOP** de la fase: deja `foundation.epic: null`, instruye crearla en
|
|
125
137
|
discovery (`/trycore:*`) con la checklist como alcance. El gate del DoR bloqueará las
|
|
126
138
|
épicas de negocio igual hasta que exista y se archive.
|
|
127
139
|
3. **Reportar el hecho**: escribe la checklist podada en un fichero temporal
|
|
128
|
-
(`[{"id","applies","evidence":""}, …]`) y repórtala en una sola transición
|
|
140
|
+
(`[{"id","applies","evidence":""}, …]`) y repórtala en una sola transición. El valor de
|
|
141
|
+
`--epic` depende de si ya tienes un `EP-XXX` real:
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
- **Existe / `legacy`/`dual` con épica ya escrita** → pasa el código real:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
bash .claude/hooks/build/slice-ops.sh fact foundation \
|
|
147
|
+
--required true --epic EP-XXX --checklist-file /tmp/foundation-checklist.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- **`runtime` con la propuesta aún sin aprobar** (`foundation.epic: null`) → **omite `--epic`
|
|
151
|
+
por completo**. No inventes un `EP-XXX` de relleno: el flag ni se valida ni viaja al hub
|
|
152
|
+
(`_fact_foundation` en `slice-ops.sh` lo acepta solo por compat local; el hub solo recibe
|
|
153
|
+
`--required`), así que no hay coste mecánico en omitirlo — y sí lo hay en escribir uno falso.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
bash .claude/hooks/build/slice-ops.sh fact foundation \
|
|
157
|
+
--required true --checklist-file /tmp/foundation-checklist.json
|
|
158
|
+
```
|
|
134
159
|
|
|
135
160
|
`--required false` cuando el humano podó todos los ítems (no hay caparazón exigible).
|
|
136
161
|
En modo legacy (rc 3) se escribe en el fichero con su protocolo.
|
package/commands/build/slice.md
CHANGED
|
@@ -62,8 +62,10 @@ fichero en `legacy`):
|
|
|
62
62
|
- **Caparazón (solo greenfield)**: si `project_kind` es `greenfield` y `foundation_done` es `false`, y
|
|
63
63
|
la épica objetivo es `layer: business` → **STOP**: solo la épica caparazón u otra fundacional puede
|
|
64
64
|
abrir. `status` no identifica **cuál** es la épica caparazón (eso vive hoy en el fichero legacy,
|
|
65
|
-
leído directo por `dor-dod-gatekeeper`); pregunta o consúltalo ahí.
|
|
66
|
-
|
|
65
|
+
leído directo por `dor-dod-gatekeeper`); pregunta o consúltalo ahí. En modo `runtime` puede no
|
|
66
|
+
existir todavía porque la propuesta está **pendiente de aprobación del hub**
|
|
67
|
+
(`/build:epic --check` lo dice): entonces el STOP es **esperar**, y no volver a proponerla.
|
|
68
|
+
Lo valida en detalle el `dor-dod-gatekeeper` (criterio 7-bis). Brownfield → N/A, no preguntes.
|
|
67
69
|
|
|
68
70
|
El hook `scaffold-guard.sh` respalda esto en tiempo real.
|
|
69
71
|
|
package/commands/build/work.md
CHANGED
|
@@ -33,8 +33,12 @@ Aplica las reglas en orden:
|
|
|
33
33
|
`greenfield` y `foundation_done` es `false` → **enruta directo a construir la épica caparazón**
|
|
34
34
|
por el carril **`building-a-slice`**. `status` no trae **cuál** es esa épica (`foundation.epic`
|
|
35
35
|
vive hoy en el fichero legacy, leído directo por `dor-dod-gatekeeper`); si no la encuentras o
|
|
36
|
-
está sin definir, enruta a `/build:onboard` (Fase 2c) o a discovery para definirla
|
|
37
|
-
|
|
36
|
+
está sin definir, enruta a `/build:onboard` (Fase 2c) o a discovery para definirla — **salvo en
|
|
37
|
+
modo `runtime`**, donde `foundation.epic` sin definir puede ser una propuesta **pendiente de
|
|
38
|
+
aprobación del hub**, no un olvido: consulta antes `slice-ops.sh epic-status` (o
|
|
39
|
+
`/build:epic --check`) y, si ya hay una propuesta `PROPOSED`, el destino es **esperar la
|
|
40
|
+
aprobación**, no re-onboardear (re-correr la Fase 2c propondría una segunda vez la misma épica
|
|
41
|
+
caparazón, issue #62). Ninguna épica de negocio pasa por delante. Brownfield, o caparazón no requerida (`foundation_done` ya `true`) →
|
|
38
42
|
esta regla es N/A.
|
|
39
43
|
Esta regla gatea **aperturas de slice** (épicas): el carril de **mantenimiento**
|
|
40
44
|
(`building-a-micro-change`, regla 1) no se bloquea — un typo/copy/config fix sigue su carril
|
package/dist/commands/init.js
CHANGED
|
@@ -225,6 +225,19 @@ function syncGitignoreBlock(targetDir, mode) {
|
|
|
225
225
|
'.claude/state/runtime-projection.json',
|
|
226
226
|
'.claude/state/.runtime-http-status',
|
|
227
227
|
'.claude/state/outbox/',
|
|
228
|
+
// [Ronda de arreglo 1, Minor 7 / #63] El ledger local de propuestas de épica y el
|
|
229
|
+
// marcador de desfase de grafo son estado derivado del hub, igual que los de arriba —
|
|
230
|
+
// sin esto, un consumidor los commitea por descuido.
|
|
231
|
+
'.claude/state/epic-proposals.json',
|
|
232
|
+
'.claude/state/graph-status.json',
|
|
233
|
+
// [Ronda final · C5] Estado del daemon de heartbeat (EP-OR-08-B): un pid y unos ppids
|
|
234
|
+
// solo significan algo en LA máquina que los escribió. Commiteados, otro clon recibe un
|
|
235
|
+
// pidfile ajeno que el relanzamiento da por bueno hasta el `kill -0`, y un
|
|
236
|
+
// `heartbeat-status.json` con el lease de otro. Hueco preexistente, cerrado aquí porque
|
|
237
|
+
// esta rama ya edita este bloque.
|
|
238
|
+
'.claude/state/heartbeat.pid',
|
|
239
|
+
'.claude/state/heartbeat-sessions.json',
|
|
240
|
+
'.claude/state/heartbeat-status.json',
|
|
228
241
|
];
|
|
229
242
|
if (mode === 'symlink') {
|
|
230
243
|
// Los symlinks absolutos a node_modules global se rompen en otra máquina si se commitean.
|
package/dist/commands/status.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { readPackageVersion, targetPaths, PACKAGE_ROOT } from '../lib/paths.js';
|
|
5
5
|
import { countChildren } from '../lib/install-engine.js';
|
|
6
6
|
import { hasBinary } from './doctor.js';
|
|
7
|
-
import { runtimeMode, getAgentContext, readProjection, readLock, rejectedStats } from '../lib/runtime-client.js';
|
|
7
|
+
import { runtimeMode, getAgentContext, readProjection, readLock, rejectedStats, readGraphStatus } from '../lib/runtime-client.js';
|
|
8
8
|
export async function status(opts) {
|
|
9
9
|
const targetDir = path.resolve(opts.targetDir);
|
|
10
10
|
const t = targetPaths(targetDir);
|
|
@@ -45,6 +45,18 @@ export async function status(opts) {
|
|
|
45
45
|
if (proj.fetched_at) {
|
|
46
46
|
const ageS = Math.max(0, Math.floor((Date.now() - Date.parse(String(proj.fetched_at))) / 1000));
|
|
47
47
|
console.log(` Proyección: ✓ v${proj.context?.version ?? '?'} (hace ${ageS}s${ageS > 900 ? ' — ⚠ stale' : ''})`);
|
|
48
|
+
// [#63] La versión de grafo solo se muestra si el hub la expone: una instancia que no
|
|
49
|
+
// versiona no debe aparentar que sí.
|
|
50
|
+
const graphVersion = proj.context?.graph_version;
|
|
51
|
+
if (typeof graphVersion === 'number') {
|
|
52
|
+
// [Ronda de arreglo 1, Minor 5] El CHANGELOG dice que este comando avisa del desfase,
|
|
53
|
+
// no solo `slice-ops.sh status` — mismo criterio que el bash (Important 3): «servidor
|
|
54
|
+
// MAYOR que local», nunca `!==`, para no acusar desfase con un marcador de grafo viejo
|
|
55
|
+
// que apunta a una versión que la proyección ya superó.
|
|
56
|
+
const gs = readGraphStatus(targetDir);
|
|
57
|
+
const behind = typeof gs.server === 'number' && gs.server > graphVersion;
|
|
58
|
+
console.log(` Grafo: v${String(graphVersion)}${behind ? ` ⚠ por detrás del hub (v${gs.server}): refresca antes de reclamar o proponer` : ''}`);
|
|
59
|
+
}
|
|
48
60
|
const slice = proj.active_slice;
|
|
49
61
|
if (slice) {
|
|
50
62
|
console.log(` Slice activo: ${slice.epic_code ?? '?'} (fase ${slice.phase ?? '?'})`);
|
package/dist/lib/paths.js
CHANGED
|
@@ -69,6 +69,7 @@ export function targetPaths(targetDir) {
|
|
|
69
69
|
runtimeCredentialsFile: path.join(claudeDir, 'state', 'runtime.credentials'),
|
|
70
70
|
runtimeLockFile: path.join(claudeDir, 'state', 'context.lock'),
|
|
71
71
|
runtimeProjectionFile: path.join(claudeDir, 'state', 'runtime-projection.json'),
|
|
72
|
+
graphStatusFile: path.join(claudeDir, 'state', 'graph-status.json'),
|
|
72
73
|
outboxDir: path.join(claudeDir, 'state', 'outbox'),
|
|
73
74
|
settingsFile: path.join(claudeDir, 'settings.json'),
|
|
74
75
|
versionFile: path.join(claudeDir, '.build-harness-version'),
|
|
@@ -11,6 +11,7 @@ function runtimePaths(targetDir) {
|
|
|
11
11
|
credentials: t.runtimeCredentialsFile,
|
|
12
12
|
lock: t.runtimeLockFile,
|
|
13
13
|
projection: t.runtimeProjectionFile,
|
|
14
|
+
graphStatus: t.graphStatusFile,
|
|
14
15
|
outboxDir: t.outboxDir,
|
|
15
16
|
buildConfigFile: t.buildConfigFile,
|
|
16
17
|
};
|
|
@@ -161,6 +162,25 @@ export function readLock(targetDir) {
|
|
|
161
162
|
return { manifestHash: null, syncedAt: null, fileCount: 0 };
|
|
162
163
|
}
|
|
163
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Lectura fail-open del marcador de desfase de grafo (.claude/state/graph-status.json),
|
|
167
|
+
* escrito por `runtime_graph_note_stale` (hooks/build/lib/runtime-client.sh) cuando el hub
|
|
168
|
+
* rechaza `claim` o una propuesta con `409 stale_graph` [#63]. Sin fichero (nunca hubo
|
|
169
|
+
* desfase) o con JSON ilegible, ambos campos quedan `null` — nunca lanza.
|
|
170
|
+
*/
|
|
171
|
+
export function readGraphStatus(targetDir) {
|
|
172
|
+
const p = runtimePaths(targetDir).graphStatus;
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
175
|
+
return {
|
|
176
|
+
local: typeof parsed?.local === 'number' ? parsed.local : null,
|
|
177
|
+
server: typeof parsed?.server === 'number' ? parsed.server : null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return { local: null, server: null };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
164
184
|
/** Estadísticas fail-open de la cola offline (.claude/state/outbox/*.json, sin dotfiles). */
|
|
165
185
|
export function outboxStats(targetDir) {
|
|
166
186
|
const dir = runtimePaths(targetDir).outboxDir;
|
package/docs/commands.md
CHANGED
|
@@ -129,16 +129,17 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
|
|
|
129
129
|
|---|---|
|
|
130
130
|
| `/build:prototype` | Genera o amplía el **prototipo HTML de referencia** (el `DESIGN_SOURCE`) en `docs/05-prototipo/` (`DESIGN.md` + `tokens.css` + `manifest.json` + un HTML autocontenido por pantalla). Adaptador delgado: **delega** en la skill `prototyping-screens`. Dos modos — **greenfield** (`/build:prototype`): inventario de pantallas desde PRD/mapa/historias → confirmación humana → dirección estética (manual de marca o 2-3 variantes a elección humana) → generación por lotes con auto-verificación visual; **feature** (`/build:prototype <épica>`): pantallas nuevas coherentes con el UX/UI **ya implementado**, con **precondición dura** (app corriendo + MCP de inspección de UI: extrae CSS computado real, screenshots en 3 viewports y estructura; sin degradación estática). Es **outer-loop** (antes de abrir slices). **La skill genera; el humano aprueba**: las pantallas nacen `borrador`, solo las `aprobada` son fuente de verdad (las lee `ux-fidelity-reviewer` vía `manifest.json`) y `design_source.confirmed` sigue siendo humano. |
|
|
131
131
|
|
|
132
|
-
### `/build:claim`, `/build:status`, `/build:escalate` — superficie de agente del runtime (beta, opt-in)
|
|
132
|
+
### `/build:claim`, `/build:status`, `/build:escalate`, `/build:epic` — superficie de agente del runtime (beta, opt-in)
|
|
133
133
|
|
|
134
134
|
Adaptadores delgados sobre `slice-ops.sh`; en modo `legacy` (default) devuelven `rc 3` y remiten al
|
|
135
135
|
protocolo del fichero (`/build:slice`) — no rompen el flujo normal si el proyecto no está migrado.
|
|
136
136
|
|
|
137
137
|
| Slash command | Propósito |
|
|
138
138
|
|---|---|
|
|
139
|
-
| `/build:claim
|
|
139
|
+
| `/build:claim` | Pide la siguiente tarea al runtime (`POST /tasks/next`): sincroniza contexto, reporta hashes locales (detección de drift) y reclama por lease. Si trae `CHECKPOINT`, continúa desde ahí — **nunca reinicia** un slice de otro agente. **No acepta épica dirigida**: el hub reparte por orden de cola y `claim --epic` falla explícito con `rc 2` (issue #39 — el servidor ignoraba el `epic_code` y entregaba otra épica como si fuera la pedida, dejando un lease huérfano). |
|
|
140
140
|
| `/build:status` | Informe de solo lectura: modo, slice activo, gates, wiring failing, versión de contexto, lease y cola de eventos pendientes. Nunca transiciona nada. |
|
|
141
141
|
| `/build:escalate <razón>` | Registra un bloqueo en el runtime (`escalation_raised`) y devuelve la decisión a un humano — recortar, diferir o desbloquear **nunca** lo decide el modelo (mismo principio que la regla 8 de METODOLOGIA §10). |
|
|
142
|
+
| `/build:epic` | Propone una épica **incremental de construcción** al hub y, tras la aprobación humana, la escribe en `docs/03-backlog/epicas.md` con el `EP-XXX` que asignó el hub (`/build:epic --check`). El agente **no elige** el código: elegirlo mirando el fichero es lo que hace colisionar a dos terminales. La descomposición inicial del backlog sigue siendo de discovery. |
|
|
142
143
|
|
|
143
144
|
---
|
|
144
145
|
|
package/docs/getting-started.md
CHANGED
|
@@ -24,6 +24,14 @@ trycore-build doctor # verifica requisitos y hooks
|
|
|
24
24
|
|
|
25
25
|
Eso es el ciclo completo. Lo de abajo explica cada paso.
|
|
26
26
|
|
|
27
|
+
> **¿Conectado a un IA Hub?** El pipeline es **el mismo**; cambia dónde vive el estado y quién
|
|
28
|
+
> valida las transiciones. Añades cuatro comandos a la caja de herramientas — `/build:claim` (pide
|
|
29
|
+
> trabajo), `/build:status` (informe), `/build:epic` (propone una épica) y `/build:escalate`
|
|
30
|
+
> (registra un bloqueo) — y el arranque lleva las flags de runtime. En `legacy` no estorban:
|
|
31
|
+
> `claim`, `epic` y `escalate` devuelven `rc 3` y te remiten al flujo del fichero, y `status`
|
|
32
|
+
> informa de que la fuente de verdad es `build-state.json`.
|
|
33
|
+
> Instalación y activación → [`docs/runtime/guia-modo-dual-y-migracion.md`](runtime/guia-modo-dual-y-migracion.md).
|
|
34
|
+
|
|
27
35
|
---
|
|
28
36
|
|
|
29
37
|
## Requisitos duros
|
|
@@ -48,7 +56,9 @@ npm i -g @fission-ai/openspec @trycore/spec-build-harness
|
|
|
48
56
|
|
|
49
57
|
## 2 · `trycore-build init` (terminal)
|
|
50
58
|
|
|
51
|
-
Desde la raíz de tu proyecto. Es **idempotente** (re-correrlo es seguro) y siembra: **14 agentes**, **16 skills** (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens` + 10 `openspec-*`), **10 comandos `/opsx:*`** + **
|
|
59
|
+
Desde la raíz de tu proyecto. Es **idempotente** (re-correrlo es seguro) y siembra: **14 agentes**, **16 skills** (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens` + 10 `openspec-*`), **10 comandos `/opsx:*`** + **13 comandos `/build:*`** (`onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front` + `claim`, `status`, `escalate`, `epic`, que son la superficie del modo runtime), **19 hooks**, el estado vacío `.claude/state/build-state.json` (gitignored, nunca se sobreescribe) y el bloque `<!-- BEGIN trycore-build-harness -->` en tu `CLAUDE.md` con `{{placeholders}}` sin resolver.
|
|
60
|
+
|
|
61
|
+
> **¿Ya tenías el arnés instalado?** Entonces esto es un `update`, no un `init` — el CLI lo detecta solo por `.claude/.build-harness-version`. Corre `npm i -g @trycore/spec-build-harness@latest` y luego `trycore-build update`; **no** repitas `/build:onboard`, tu dominio ya está parametrizado. La tabla «¿Qué camino me toca?» de [`INSTALL.md`](../INSTALL.md) cubre los cuatro casos.
|
|
52
62
|
|
|
53
63
|
```bash
|
|
54
64
|
trycore-build init
|
package/docs/hooks.md
CHANGED
|
@@ -35,7 +35,7 @@ Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarad
|
|
|
35
35
|
| `dual-compare.sh` | `Stop` | `.*` | (Beta, solo modo `dual`.) Detecta cuando la proyección del runtime diverge del fichero local (épica/fase/gates ya resueltos) y escala vía el mismo canal que `/build:escalate`. Es el instrumento de medición del piloto que condiciona el corte a `runtime` — nunca bloquea el cierre de sesión. | No |
|
|
36
36
|
| `session-stop.sh` | `Stop` | `.*` | (Beta.) Deja `outbox/.flush-request` y asegura el daemon `heartbeat.sh` — el flush de la cola offline lo ejecuta el daemon, no el hook. No-op en `legacy`. | No |
|
|
37
37
|
| `context-sync.sh` | invocado por `session-start.sh` y, pre-claim, por las skills (no registrado como hook) | — | (Beta.) Sincronización de contexto *content-addressed*: compara el `manifest_hash` esperado contra el lock local; si difiere, descarga solo los archivos gobernados (`config/`, `rules/`, `docs-cache/`) que cambiaron, por hash. Nunca escribe fuera de esos prefijos. Fail-open: sin runtime, sin `python3` o con manifiesto ilegible, deja el lock anterior intacto. | No |
|
|
38
|
-
| `heartbeat.sh` | daemon singleton por repo, lanzado por `session-start.sh` (no registrado como hook) | — | (Beta.) Renueva el lease (`PUT /leases/renew`) y despacha la cola offline mientras viva al menos una sesión interesada (`.claude/state/heartbeat-sessions.json`). No existe evento Timer en Claude Code; por eso el latido es un daemon, no un hook. |
|
|
38
|
+
| `heartbeat.sh` | daemon singleton por repo, lanzado por `session-start.sh` (no registrado como hook) | — | (Beta.) Renueva el lease (`PUT /leases/renew`), **refresca la caché de proyección desde `GET /agent/context` con su propia cadencia** (`runtime.context_refresh_s`, default 45 s; solo en modo `runtime`) y despacha la cola offline mientras viva al menos una sesión interesada (`.claude/state/heartbeat-sessions.json`). No existe evento Timer en Claude Code; por eso el latido es un daemon, no un hook. |
|
|
39
39
|
|
|
40
40
|
> Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y quince informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`. Los 4 bloqueantes **nunca** tocan la red (ni en `legacy` ni en `dual`/`runtime`) — ratchet estático en `scripts/tests/test-hooks-runtime.sh`.
|
|
41
41
|
>
|
|
@@ -193,6 +193,16 @@ tick (`TRYCORE_HEARTBEAT_TICK_S`, default 5s): consume `outbox/.flush-request` y
|
|
|
193
193
|
cada `lease_ttl_s/3` (mínimo 10s), `PUT /leases/renew`. **Sin lease no se apaga**: los eventos de
|
|
194
194
|
ámbito proyecto (pre-claim) necesitan despachador igual.
|
|
195
195
|
|
|
196
|
+
**Refresco de contexto (#61)**. Cada `runtime.context_refresh_s` segundos (default 45; `0` lo
|
|
197
|
+
desactiva; override por `TRYCORE_CONTEXT_REFRESH_S`) el daemon hace `GET /agent/context` y
|
|
198
|
+
reescribe la caché de proyección. Es la única vía por la que una terminal **abierta** se entera
|
|
199
|
+
de algo nuevo: `session-start.sh` hidrata una vez y `slice-ops.sh` solo en `claim`/`status`, y el
|
|
200
|
+
`claim` ocurre una vez por slice. La cadencia es propia y **no** el tick de 5 s: renovar un lease
|
|
201
|
+
es barato, pedir la proyección entera del proyecto no. Si el refresco falla, la caché anterior
|
|
202
|
+
queda **intacta** y se marca `context_stale: true` en `heartbeat-status.json` (lo muestra
|
|
203
|
+
`slice-ops.sh status`): quedarse sin proyección convertiría al agente en huérfano, que es
|
|
204
|
+
justo lo que arregla el PR #60. Solo corre en modo `runtime`.
|
|
205
|
+
|
|
196
206
|
---
|
|
197
207
|
|
|
198
208
|
## La cadena de comando única (sin doble disparo entre canales)
|