@trycore/spec-build-harness 0.14.2 → 0.16.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 +1 -1
- package/INSTALL.md +1 -1
- package/METODOLOGIA.md +15 -4
- package/README.md +43 -6
- package/VERSION +1 -1
- package/agents/build/build-orchestrator.md +3 -0
- package/commands/build/front.md +8 -4
- package/commands/build/graph-sync.md +129 -0
- package/dist/commands/init.js +3 -0
- package/docs/commands.md +11 -2
- package/docs/getting-started.md +1 -1
- package/docs/hooks.md +4 -2
- package/docs/runtime/guia-modo-dual-y-migracion.md +79 -3
- package/docs/runtime/protocolo-cliente-runtime.md +66 -7
- package/hooks/build/lib/config.sh +56 -8
- package/hooks/build/lib/harness-meta.sh +72 -0
- package/hooks/build/lib/runtime-client.sh +120 -14
- package/hooks/build/lib/runtime-ops.sh +160 -27
- package/hooks/build/release-ops.sh +38 -12
- package/hooks/build/session-start.sh +5 -61
- package/hooks/build/slice-ops.sh +615 -41
- package/package.json +1 -1
- package/scripts/lib/front-plan.py +55 -6
- package/scripts/lib/graph-bundle.py +118 -3
- package/scripts/smoke-test.sh +1 -1
- package/scripts/tests/test-config.sh +40 -0
- package/scripts/tests/test-front-plan.sh +63 -0
- package/scripts/tests/test-install.sh +2 -1
- package/scripts/tests/test-runtime-client.sh +49 -3
- package/scripts/tests/test-skill-ops.sh +516 -27
- package/scripts/tests/test-worktree-identity.sh +207 -0
- package/skills/managing-parallel-front/SKILL.md +35 -8
|
@@ -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.16.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
|
@@ -8,7 +8,7 @@ evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
|
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| Contexto | sección Construcción de CLAUDE.md, `openspec/project.md` | raíz / `openspec/` |
|
|
10
10
|
| Estado (modo `legacy`, default) | `build-state.json` (+schema, README) | `.claude/state/` |
|
|
11
|
-
| Cliente runtime (modo `dual`/`runtime`, opt-in — beta) | `runtime.credentials` (0600), `context.lock`, `runtime-projection.json`, `outbox/`; `slice-ops.sh`/`release-ops.sh` (
|
|
11
|
+
| Cliente runtime (modo `dual`/`runtime`, opt-in — beta) | `runtime.credentials` (0600), `context.lock`, `runtime-projection.json`, `outbox/`; `slice-ops.sh`/`release-ops.sh` (20 + 3 subcomandos) | `.claude/state/`, `.claude/hooks/build/` |
|
|
12
12
|
| Agentes | 14 agentes de build | `.claude/agents/build/` |
|
|
13
13
|
| Hooks | settings.json + 19 scripts (15 registrados + 4 invocados: `reconcile-build-state.py`, `context-sync.sh`, `heartbeat.sh` como daemon, `statusline-bridge.sh` como comando `statusLine`) | `.claude/settings.json`, `.claude/hooks/build/` |
|
|
14
14
|
| 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) · `prototyping-screens` (prototipo HTML de referencia) | `.claude/skills/` |
|
package/INSTALL.md
CHANGED
|
@@ -107,7 +107,7 @@ Qué hace `init`:
|
|
|
107
107
|
1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
|
|
108
108
|
2. **Siembra los assets** en rutas nativas de Claude Code:
|
|
109
109
|
- `.claude/agents/build/` — 14 agentes.
|
|
110
|
-
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (
|
|
110
|
+
- `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (14 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`, `/build:graph-sync`). Los cinco últimos son la **superficie de agente del modo runtime** (§9): en `legacy`, `claim`, `escalate`, `epic` y `graph-sync` devuelven `rc 3` remitiendo al flujo del fichero, y `status` informa de que la fuente de verdad es `build-state.json`.
|
|
111
111
|
- `.claude/skills/` — 16 skills (`building-a-slice`, `building-a-micro-change`, `releasing-a-version`, `managing-parallel-front`, `setup-architecture`, `prototyping-screens`, `openspec-*`).
|
|
112
112
|
- `.claude/hooks/build/` — 19 hooks (bash + python; 6 son del cliente runtime opt-in — §9).
|
|
113
113
|
3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
|
package/METODOLOGIA.md
CHANGED
|
@@ -332,15 +332,26 @@ worktree— y coordina la selección, la construcción y el merge desde el estad
|
|
|
332
332
|
(`parallel_front`). Es un concepto de **outer-loop**: no es una fase nueva del pipeline por-épica, es
|
|
333
333
|
orquestación **entre** épicas.
|
|
334
334
|
|
|
335
|
+
**Compuerta cero: un agente por worktree, un token por agente.** En modo `runtime`, cada worktree
|
|
336
|
+
se da de alta como **agente propio** con un token que el ADMIN emitió **para él**
|
|
337
|
+
(`slice-ops.sh register`, que lo pide por stdin). No es un trámite: el hub deriva el `agent_key`
|
|
338
|
+
del `agent_id` grabado en el token, así que dos worktrees con el mismo token son **un solo agente**
|
|
339
|
+
y el segundo `claim` devuelve el slice del primero. Y un worktree **sin** credenciales propias no
|
|
340
|
+
queda huérfano — hereda las del clon principal y **suplanta** a ese agente. Por eso toda escritura
|
|
341
|
+
desde un árbol sin identidad propia aborta con `rc 9` (issue #74).
|
|
342
|
+
|
|
335
343
|
**Compuertas del front:**
|
|
336
344
|
|
|
337
345
|
1. **Foundational-first (G1).** Ninguna épica `layer: foundational` entra jamás al front: el
|
|
338
346
|
cimiento se construye secuencial, antes que el negocio (§1-bis.2, §3.1). Solo épicas
|
|
339
347
|
`layer: business` son candidatas a paralelizarse.
|
|
340
|
-
2. **Disjunción por `files_scope` (G2).** `scripts/lib/front-plan.py` calcula, a
|
|
341
|
-
declarados en `files_scope` de cada épica candidata, qué subconjunto es
|
|
342
|
-
(`selected`, va al front) y cuál se solapa (`serialized`, espera y se
|
|
343
|
-
secuencial). Sin `files_scope` declarado, una épica no es candidata al
|
|
348
|
+
2. **Disjunción por `files_scope` y por el grafo (G2).** `scripts/lib/front-plan.py` calcula, a
|
|
349
|
+
partir de los globs declarados en `files_scope` de cada épica candidata, qué subconjunto es
|
|
350
|
+
mutuamente disjunto (`selected`, va al front) y cuál se solapa (`serialized`, espera y se
|
|
351
|
+
construye después, secuencial). Sin `files_scope` declarado, una épica **no es candidata** al
|
|
352
|
+
front: el script la serializa (fail-closed). Tampoco son paralelizables dos épicas unidas por
|
|
353
|
+
un camino en el DAG de `depends_on`, aunque sus ficheros sean disjuntos — la de abajo
|
|
354
|
+
construiría sobre lo que la de arriba todavía no ha mergeado.
|
|
344
355
|
3. **Merge en orden + re-smoke (G3).** El merge de los worktrees sigue un `merge_order`
|
|
345
356
|
determinista; tras **cada** merge se re-corre el `journey_smoke` completo sobre el árbol
|
|
346
357
|
principal (no basta con el smoke local del worktree). Un solape no capturado por la disjunción
|
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
|
|
|
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
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. |
|
|
83
|
+
| `/build:graph-sync` | Propone el **re-sync del grafo** del hub desde los docs de discovery (modo `runtime`): el hub calcula el delta y un humano lo aprueba — nada se aplica sin esa aprobación. Con `--check` lee el veredicto. Sin esto, el grafo del hub se quedaba en la foto del import inicial y los eventos de las épicas posteriores rebotaban. |
|
|
83
84
|
| `/opsx:*` (10) | Ciclo OpenSpec: `explore` · `new` · `continue` · `apply` · `verify` · `archive` · `bulk-archive` · `ff` · `onboard` · `sync`. Detalle → [`docs/commands.md`](docs/commands.md). |
|
|
84
85
|
|
|
85
86
|
## Gestión de contexto
|
|
@@ -120,14 +121,21 @@ Cuando hay **≥ 2 épicas de negocio (`layer: business`)** listas (DoR pasado)
|
|
|
120
121
|
archivos**, el comando **`/build:front`** (skill `managing-parallel-front`) las construye en
|
|
121
122
|
paralelo, cada una en su propio `git worktree`/rama/PR — el inner loop no cambia: cada worktree
|
|
122
123
|
mantiene su `active_slice` singular y su propio `build-state.json`. Tres compuertas gobiernan el
|
|
123
|
-
front:
|
|
124
|
+
front (más una compuerta cero, de identidad, cuando se corre contra el hub):
|
|
124
125
|
|
|
126
|
+
0. **Un agente por worktree, un token por agente** (modo `runtime`). Cada árbol se da de alta con
|
|
127
|
+
`slice-ops.sh register` y un token que el ADMIN emitió **para él**: el hub deriva el `agent_key`
|
|
128
|
+
del token, así que dos worktrees con el mismo token son un solo agente. Un árbol sin dar de alta
|
|
129
|
+
hereda las credenciales del clon principal y lo suplantaría — por eso toda escritura desde él
|
|
130
|
+
aborta con `rc 9`.
|
|
125
131
|
1. **Foundational-first.** Ninguna épica `layer: foundational` entra jamás al front; si una aparece
|
|
126
132
|
mientras el front está activo, el front pasa a `status: "draining"` (termina lo que ya empezó, no
|
|
127
133
|
admite épicas nuevas) hasta vaciarse.
|
|
128
|
-
2. **Disjunción por `files_scope
|
|
129
|
-
declarados en cada épica, qué candidatas son mutuamente disjuntas
|
|
130
|
-
esperar (`serialized`) por solaparse.
|
|
134
|
+
2. **Disjunción por `files_scope` y por el grafo.** `scripts/lib/front-plan.py` calcula, por
|
|
135
|
+
conjuntos de globs declarados en cada épica, qué candidatas son mutuamente disjuntas
|
|
136
|
+
(`selected`) y cuáles deben esperar (`serialized`) por solaparse. Sin `files_scope` declarado una
|
|
137
|
+
épica **no es candidata** (se serializa), y dos épicas unidas por un camino en el DAG de
|
|
138
|
+
`depends_on` tampoco van juntas aunque sus ficheros sean disjuntos.
|
|
131
139
|
3. **Merge en orden + re-smoke.** El merge de los worktrees sigue un `merge_order` determinista; tras
|
|
132
140
|
cada merge se re-corre el `journey_smoke` completo en el árbol principal. Un conflicto no
|
|
133
141
|
detectado por la disjunción declarada lo caza el re-smoke: la épica perdedora se serializa
|
|
@@ -143,7 +151,7 @@ trycore-spec-build-harness/
|
|
|
143
151
|
├── agents/build/ ← 14 agentes revisores (segunda opinión, contexto limpio)
|
|
144
152
|
├── commands/
|
|
145
153
|
│ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
|
|
146
|
-
│ └── build/ ←
|
|
154
|
+
│ └── build/ ← 14 comandos /build:* (onboard, reflect, architect, prototype, slice, release, work, resume, front, claim, status, escalate, epic, graph-sync)
|
|
147
155
|
├── 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)
|
|
148
156
|
├── 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)
|
|
149
157
|
├── state/ ← máquina de estado legacy: build-state.json + schema + README
|
|
@@ -183,7 +191,36 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
183
191
|
|
|
184
192
|
## Roadmap
|
|
185
193
|
|
|
186
|
-
- ✅ **v0.
|
|
194
|
+
- ✅ **v0.16.0 (actual) — identidad por árbol de trabajo** (#74) — construir dos épicas en paralelo
|
|
195
|
+
en la misma máquina, una por `git worktree`, ya es una operación segura. El reparto nunca fue el
|
|
196
|
+
problema del hub —ya entrega dos épicas a dos agentes sin objeción—: el bloqueo estaba en el
|
|
197
|
+
cliente. El estado del arnés está en `.gitignore` y no viaja a un worktree, así que el árbol nuevo
|
|
198
|
+
**heredaba en silencio** las credenciales del clon principal y lo suplantaba: reclamaba con su
|
|
199
|
+
`agent_key`, reportaba los sha del árbol equivocado (cegando la detección de drift justo en el
|
|
200
|
+
escenario paralelo) y compartía su heartbeat. Ahora toda **escritura** desde un árbol sin dar de
|
|
201
|
+
alta aborta con **`rc 9`** antes de abrir un socket (las lecturas siguen, y `status` avisa con
|
|
202
|
+
`identidad: ⚠ HEREDADA`), y el alta es explícita: **`slice-ops.sh register`**, con el token por
|
|
203
|
+
**stdin** —nunca por argumento—, siembra local antes de tocar la red, verificación de que el
|
|
204
|
+
`agent_key` **difiera** del principal y rollback total ante cualquier fallo. Dato que corrige la
|
|
205
|
+
documentación anterior: `POST /agents/register` **no crea identidad** —el `agent_key` es el
|
|
206
|
+
`agent_id` grabado en el token por el ADMIN—, así que es **un token por agente**, no uno por
|
|
207
|
+
proyecto. Además, `front-plan.py` cierra tres fallos silenciosos: `layer` normalizado (el bundle
|
|
208
|
+
del grafo emite `FOUNDATIONAL` y la exclusión foundational-first podía no disparar),
|
|
209
|
+
**fail-closed** sin `files_scope` declarado, y serialización de los pares con camino en el DAG de
|
|
210
|
+
`depends_on`.
|
|
211
|
+
- ✅ **v0.15.0 — el arnés propone el re-sync de su grafo** (EP-OR-17) — el grafo del hub se sembraba
|
|
212
|
+
una vez, con el import de admin de `/build:onboard`, y ahí se quedaba: las épicas que discovery
|
|
213
|
+
escribía después no tenían por dónde entrar y **todos** los eventos de un slice cuya épica falta
|
|
214
|
+
rebotan. Comando **`/build:graph-sync`** (+ `--check`) y subcomandos `graph-sync`/
|
|
215
|
+
`graph-sync-status`: el arnés construye el bundle desde los docs, el hub calcula el delta y un
|
|
216
|
+
**humano aprueba** en la consola — nada se aplica sin esa aprobación; tras ella, las historias
|
|
217
|
+
nuevas entran solas al reparto del claim. `client_event_id` determinista para que el reintento
|
|
218
|
+
deduplique en vez de dejar dos propuestas del mismo grafo esperando. En el camino, dos defectos
|
|
219
|
+
vivos desde antes: el `409` se leía en la raíz y con un nombre que no existe en el backend (el
|
|
220
|
+
claim no reintentaba ante grafo rancio ni ante drift), y el **espejo del modo `dual` era un 422 el
|
|
221
|
+
100 % de las veces** (mandaba un cuerpo que el contrato no tiene). Total: **14 comandos
|
|
222
|
+
`/build:*`**.
|
|
223
|
+
- ✅ **v0.14.0 — contexto vivo, épicas propuestas y versión de grafo** (#61, #62, #63) —
|
|
187
224
|
tres capacidades del cliente del runtime que componen entre sí. **(1)** El daemon de heartbeat
|
|
188
225
|
refresca `GET /agent/context` con **cadencia propia** (`runtime.context_refresh_s`, default 45 s,
|
|
189
226
|
`0` la desactiva): antes la caché se hidrataba al arrancar la sesión y en cada `claim` —y el
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.16.0
|
|
@@ -23,6 +23,9 @@ Eres el único que reporta gates y transiciona fases; los reviewers solo emiten
|
|
|
23
23
|
## Coexistencia con el front paralelo (outer-loop)
|
|
24
24
|
Si hay un front paralelo abierto, el paralelismo lo gobierna la skill
|
|
25
25
|
`managing-parallel-front`; cada worktree corre su propio inner loop con `active_slice` singular.
|
|
26
|
+
En modo `runtime`, cada worktree es además un **agente propio** con su token del ADMIN (alta con
|
|
27
|
+
`slice-ops.sh register`): si un árbol no está dado de alta, sus escrituras salen con `rc 9` porque
|
|
28
|
+
estaría operando con la identidad del clon principal — eso se arregla registrando, no reintentando.
|
|
26
29
|
**Regla de drenado:** si se necesita abrir una épica `layer=foundational`, primero pon
|
|
27
30
|
`parallel_front.status="draining"` (termina las en curso, no admite nuevas) y espera a cerrarlo.
|
|
28
31
|
|
package/commands/build/front.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "BUILD: Front"
|
|
3
|
-
description: Prepara y coordina un front paralelo de épicas NO fundacionales y disjuntas en archivos, cada una en su worktree/rama/PR con su propio agente registrado. Delega en la skill managing-parallel-front (selección disjunta local vía scripts/lib/front-plan.py, worktrees, merge en orden con re-smoke reportado por cada worktree). Planificar, abrir, drenar y cerrar el front son actos humanos en la consola.
|
|
3
|
+
description: Prepara y coordina un front paralelo de épicas NO fundacionales y disjuntas en archivos, cada una en su worktree/rama/PR con su propio agente registrado (un token por agente, alta con slice-ops.sh register). Delega en la skill managing-parallel-front (selección disjunta local vía scripts/lib/front-plan.py, worktrees, merge en orden con re-smoke reportado por cada worktree). Planificar, abrir, drenar y cerrar el front son actos humanos en la consola.
|
|
4
4
|
category: Workflow
|
|
5
5
|
tags: [build-harness, outer-loop, front-paralelo, trycore]
|
|
6
6
|
---
|
|
@@ -10,11 +10,15 @@ tags: [build-harness, outer-loop, front-paralelo, trycore]
|
|
|
10
10
|
Delega en la skill **managing-parallel-front**. Resumen:
|
|
11
11
|
1. Verifica precondiciones (scaffold confirmado; sin épica foundational abierta) con
|
|
12
12
|
`bash .claude/hooks/build/slice-ops.sh status`.
|
|
13
|
-
2. Reúne candidatas no fundacionales listas (DoR pasado) con `layer` y
|
|
13
|
+
2. Reúne candidatas no fundacionales listas (DoR pasado) con `layer`, `files_scope` y
|
|
14
|
+
`depends_on`. Sin `files_scope` declarado la épica **no es candidata** (se serializa).
|
|
14
15
|
3. Propón el conjunto disjunto y el `merge_order` (`scripts/lib/front-plan.py`, local) y
|
|
15
16
|
**preséntaselo a una persona**: abrir el front es acto de gobierno, en la consola del hub.
|
|
16
|
-
4. Un worktree por épica aprobada, cada uno
|
|
17
|
-
|
|
17
|
+
4. Un worktree por épica aprobada, cada uno dado de alta como **agente propio** con **su
|
|
18
|
+
propio token** emitido por el ADMIN: `trycore-build init` (sin `--runtime-token`) y luego
|
|
19
|
+
`bash .claude/hooks/build/slice-ops.sh register` (pide el token por stdin). Sin ese alta,
|
|
20
|
+
toda escritura desde el worktree sale con **rc 9**: estaría suplantando al clon principal.
|
|
21
|
+
Después, construye con `/build:slice`.
|
|
18
22
|
5. Tras cada merge, re-smoke y reporte desde el worktree:
|
|
19
23
|
`bash .claude/hooks/build/release-ops.sh front-integration <front_id> --merge-status … --resmoke …`.
|
|
20
24
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "BUILD: Graph Sync"
|
|
3
|
+
description: Propone al hub el re-sync del grafo del proyecto desde los docs de discovery (epicas.md + HU-*.md). El hub calcula el delta y un humano lo aprueba en la consola; nada se aplica sin esa aprobación. Con --check no propone nada: solo consulta el veredicto de las propuestas ya enviadas. Adaptador delgado sobre slice-ops.sh graph-sync/graph-sync-status.
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [build-harness, runtime, backlog, grafo, trycore]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /build:graph-sync — re-sincronizar el grafo del hub con los docs (la aprobación es humana)
|
|
9
|
+
|
|
10
|
+
El grafo del hub se siembra una vez, con el **import de admin** de `/build:onboard`. A partir de
|
|
11
|
+
ahí discovery sigue escribiendo: épicas nuevas en `docs/03-backlog/epicas.md`, historias nuevas
|
|
12
|
+
en las `HU-*.md`. Sin este comando, nada de eso llega al hub — y el síntoma es concreto: **todos
|
|
13
|
+
los eventos de un slice cuya épica no está en el grafo rebotan** con «la épica no existe en el
|
|
14
|
+
grafo del proyecto».
|
|
15
|
+
|
|
16
|
+
**Regla dura**: el arnés **propone**, un humano **aprueba**. Este comando no muta el grafo; deja
|
|
17
|
+
una propuesta con su delta calculado, y hasta que alguien la apruebe en la consola del hub no
|
|
18
|
+
cambia ni una arista.
|
|
19
|
+
|
|
20
|
+
**Entrada (opcional):** `--check`. Sin argumento, el comando **propone** el re-sync.
|
|
21
|
+
|
|
22
|
+
## Despacho por argumento (léelo antes que nada)
|
|
23
|
+
|
|
24
|
+
- **Con `--check`** → ve directo al **§0** y luego **salta al §4**. `--check` es una consulta de
|
|
25
|
+
**solo lectura**: no construyas ningún bundle y no propongas nada (§1–§3 no se ejecutan, ni
|
|
26
|
+
siquiera "por si acaso"). Proponer aquí crearía una segunda propuesta del mismo grafo cada vez
|
|
27
|
+
que alguien comprueba el estado.
|
|
28
|
+
- **Sin argumento** → §0 → §1 → §2 → §3. El §4 se recorre después, cuando el humano haya
|
|
29
|
+
resuelto la propuesta en la consola (normalmente en otra invocación, con `--check`).
|
|
30
|
+
|
|
31
|
+
## 0. Precondición
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash .claude/hooks/build/slice-ops.sh mode
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `legacy` → **STOP**: el grafo vive en los documentos y no hay hub con el que sincronizarlo.
|
|
38
|
+
- `dual` → **STOP** para proponer, pero aquí **sí hay hub**: el fichero local es el primario y el
|
|
39
|
+
grafo entra por el import de admin. No lo diagnostiques como un problema de conexión. El
|
|
40
|
+
`--dry-run` del §2 sí funciona: úsalo para revisar el grafo antes del corte a `runtime`.
|
|
41
|
+
- `runtime` → sigue.
|
|
42
|
+
|
|
43
|
+
## 1. Construir el JSON de épicas desde los docs
|
|
44
|
+
|
|
45
|
+
Lee `docs/03-backlog/epicas.md` y las `HU-*.md` y escribe un fichero temporal con **todo** el
|
|
46
|
+
backlog vigente, no solo lo nuevo: el hub compara el bundle contra su grafo y calcula el delta,
|
|
47
|
+
así que mandar un subconjunto no borra nada pero tampoco describe el proyecto.
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{"project_ref": "<nombre del proyecto>",
|
|
51
|
+
"epics": [{"code": "EP-001", "title": "…", "layer": "foundational",
|
|
52
|
+
"files_scope": ["src/core/**"], "depends_on": [],
|
|
53
|
+
"stories": [{"id": "HU-001", "title": "…"}]},
|
|
54
|
+
{"code": "EP-002", "title": "…", "layer": "business",
|
|
55
|
+
"files_scope": ["src/pagos/**"], "depends_on": ["EP-001"],
|
|
56
|
+
"stories": [{"id": "HU-011", "title": "…"}]}],
|
|
57
|
+
"release_lines": [{"id": "R1-mvp", "epics": ["EP-001", "EP-002"]}]}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Es el **mismo formato** que la fase de grafo de `/build:onboard`: si ya lo generaste allí,
|
|
61
|
+
reutilízalo. A diferencia de las propuestas de épica, aquí el `code` **sí** viaja: el re-sync es
|
|
62
|
+
la puerta de lo que ya tiene identidad en los documentos.
|
|
63
|
+
|
|
64
|
+
**No inventes ninguna épica ni ninguna capa.** Si un dato falta en los docs, falta en el grafo:
|
|
65
|
+
se corrige en discovery, no aquí.
|
|
66
|
+
|
|
67
|
+
## 2. Revisar antes de proponer (`--dry-run`)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bash .claude/hooks/build/slice-ops.sh graph-sync \
|
|
71
|
+
--file /tmp/epics.json --from-docs docs/03-backlog/epicas.md --dry-run
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Pasa siempre `--from-docs`**: relee el campo `**Depende de**` de cada sección de épica y une
|
|
75
|
+
ese grafo al del JSON. Es la única lectura fiable de las dependencias, que la metodología escribe
|
|
76
|
+
como prosa; sin él, un backlog lleno de dependencias sale con `depends_on: []` y el Lienzo del
|
|
77
|
+
hub queda sin una sola arista.
|
|
78
|
+
|
|
79
|
+
`--dry-run` imprime el bundle ya proyectado al contrato del hub y **no envía nada**. Revísalo con
|
|
80
|
+
el usuario: número de épicas, capas, aristas.
|
|
81
|
+
|
|
82
|
+
Un `rc 2` aquí no es un fallo del comando: es el bundle incumpliendo el contrato del hub, con un
|
|
83
|
+
motivo por línea que nombra la épica y el campo (`layer` ausente, historia sin `code`, techos
|
|
84
|
+
excedidos). Corrígelo **en los docs de discovery** y repite. El hub rechaza el grafo entero ante
|
|
85
|
+
una sola épica mal formada, así que estas comprobaciones se hacen aquí para no gastar un viaje.
|
|
86
|
+
|
|
87
|
+
## 3. Proponer
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bash .claude/hooks/build/slice-ops.sh graph-sync \
|
|
91
|
+
--file /tmp/epics.json --from-docs docs/03-backlog/epicas.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| rc | Significado | Qué haces |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `0` | Propuesta creada (el comando enseña el `id` y el delta), **o** el grafo ya estaba al día | Si hay `id`, dile al usuario que queda **pendiente de aprobación humana** en la consola. Si dice «ya está al día», no hay nada que hacer |
|
|
97
|
+
| `5` | Encolada, aún sin entregar (sin conexión, o el daemon estaba drenando la cola) | Avisa de que se despachará sola y de que `--check` la sigue |
|
|
98
|
+
| `6` | El hub la rechazó (422 con el motivo, 404, o falta `project_id`) | Muestra la razón; **no** reintentes en bucle |
|
|
99
|
+
| `2` | El bundle no cumple el contrato, o falta `--file` | Corrige lo que indique el mensaje, en los docs |
|
|
100
|
+
| `3` | `legacy`/`dual` | Vuelve al paso 0 |
|
|
101
|
+
|
|
102
|
+
Un **409 de grafo rancio** no lo verás como error: el comando refresca el contexto y reintenta
|
|
103
|
+
una vez con el mismo identificador, para que el hub deduplique en vez de crear una segunda
|
|
104
|
+
propuesta del mismo grafo.
|
|
105
|
+
|
|
106
|
+
## 4. Después de la decisión — `/build:graph-sync --check`
|
|
107
|
+
|
|
108
|
+
**Aquí aterriza `--check`.** Es mecánico y de solo lectura:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
bash .claude/hooks/build/slice-ops.sh graph-sync-status
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- `APPROVED` (con la versión de grafo aplicada) → **no hay nada más que hacer**. Las historias
|
|
115
|
+
nuevas entran solas al reparto del claim: no hay que reclamar de otra forma ni tocar el estado.
|
|
116
|
+
- `REJECTED` (con el motivo que escribió el ADMIN) → **léeselo al usuario**. El motivo existe
|
|
117
|
+
precisamente para que esta terminal lo lea. Corrige en discovery y vuelve a proponer.
|
|
118
|
+
- `PROPOSED` → sigue esperando decisión humana. No propongas otra vez.
|
|
119
|
+
- `QUEUED` → aún sin despachar; el daemon de heartbeat la lleva.
|
|
120
|
+
|
|
121
|
+
## Guardrails
|
|
122
|
+
|
|
123
|
+
- El arnés **propone**; la aprobación es humana (gobierno, METODOLOGIA §10 regla 8). Ni el
|
|
124
|
+
modelo ni el arnés aprueban un cambio de grafo.
|
|
125
|
+
- **No edites `docs/03-backlog/epicas.md` para «arreglar» el bundle.** El fichero es la fuente;
|
|
126
|
+
si el bundle no cumple, lo que está mal es la fuente, y se corrige con el usuario delante.
|
|
127
|
+
- **No propongas dos veces sin mirar `--check`.** Dos propuestas del mismo grafo esperando
|
|
128
|
+
aprobación es exactamente el ruido que este carril evita.
|
|
129
|
+
- Un `REJECTED` **no** se resuelve reintentando: se resuelve leyendo el motivo.
|
package/dist/commands/init.js
CHANGED
|
@@ -230,6 +230,9 @@ function syncGitignoreBlock(targetDir, mode) {
|
|
|
230
230
|
// sin esto, un consumidor los commitea por descuido.
|
|
231
231
|
'.claude/state/epic-proposals.json',
|
|
232
232
|
'.claude/state/graph-status.json',
|
|
233
|
+
// [EP-OR-17] El ledger del re-sync del grafo es lo mismo: el rastro local de una
|
|
234
|
+
// conversación con el hub, no estado del proyecto.
|
|
235
|
+
'.claude/state/graph-sync-proposals.json',
|
|
233
236
|
// [Ronda final · C5] Estado del daemon de heartbeat (EP-OR-08-B): un pid y unos ppids
|
|
234
237
|
// solo significan algo en LA máquina que los escribió. Commiteados, otro clon recibe un
|
|
235
238
|
// pidfile ajeno que el relanzamiento da por bueno hasta el `kill -0`, y un
|
package/docs/commands.md
CHANGED
|
@@ -121,7 +121,7 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
|
|
|
121
121
|
|
|
122
122
|
| Slash command | Propósito |
|
|
123
123
|
|---|---|
|
|
124
|
-
| `/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`. |
|
|
124
|
+
| `/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`, que serializa también las candidatas sin `files_scope` declarado y los pares con camino en el DAG de `depends_on`) y mergea en orden con re-smoke. En modo `runtime`, **cada worktree se da de alta como agente propio** con su token del ADMIN (`slice-ops.sh register`): sin ese alta, toda escritura desde el árbol sale con `rc 9` porque estaría firmando con la identidad del clon principal. Úsalo solo con ≥2 épicas no fundacionales disjuntas listas; para una sola épica, usa `/build:slice`. |
|
|
125
125
|
|
|
126
126
|
### `/build:prototype` — prototipo HTML de referencia (fuente de diseño)
|
|
127
127
|
|
|
@@ -129,17 +129,26 @@ 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`, `/build:epic` — superficie de agente del runtime (beta, opt-in)
|
|
132
|
+
### `/build:claim`, `/build:status`, `/build:escalate`, `/build:epic`, `/build:graph-sync` — 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
|
+
> **Un árbol de trabajo, un agente.** Estos comandos operan con la identidad de
|
|
138
|
+
> `.claude/state/runtime.credentials`. Ese fichero está en `.gitignore` y **no viaja** a un
|
|
139
|
+
> `git worktree`, así que un árbol nuevo hereda la del clon principal y lo suplantaría: por eso
|
|
140
|
+
> toda **escritura** desde un árbol sin dar de alta aborta con **`rc 9`**. El alta no es un slash
|
|
141
|
+
> command —pide el token por stdin y necesita un terminal humano—: se corre una vez por worktree
|
|
142
|
+
> con `bash .claude/hooks/build/slice-ops.sh register`. Ver
|
|
143
|
+
> [guía runtime → «Varios agentes en la misma máquina»](runtime/guia-modo-dual-y-migracion.md).
|
|
144
|
+
|
|
137
145
|
| Slash command | Propósito |
|
|
138
146
|
|---|---|
|
|
139
147
|
| `/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
148
|
| `/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
149
|
| `/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
150
|
| `/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. |
|
|
151
|
+
| `/build:graph-sync` | Propone al hub el **re-sync del grafo** desde los docs de discovery (`epicas.md` + `HU-*.md`): el hub calcula el delta y un humano lo aprueba en la consola — **nada se aplica** sin esa aprobación. Existe porque el grafo del hub se sembraba una vez, con el import de admin, y todo lo que discovery escribía después no tenía por dónde entrar: el síntoma es que **cada evento de un slice cuya épica falta en el grafo rebota**. Con `--check` solo consulta el veredicto (`APPROVED` con la versión aplicada, o `REJECTED` con el motivo que escribió el ADMIN). Tras la aprobación no hay nada que hacer: las historias nuevas entran solas al reparto del claim. |
|
|
143
152
|
|
|
144
153
|
---
|
|
145
154
|
|
package/docs/getting-started.md
CHANGED
|
@@ -56,7 +56,7 @@ npm i -g @fission-ai/openspec @trycore/spec-build-harness
|
|
|
56
56
|
|
|
57
57
|
## 2 · `trycore-build init` (terminal)
|
|
58
58
|
|
|
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:*`** + **
|
|
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:*`** + **14 comandos `/build:*`** (`onboard`, `reflect`, `architect`, `prototype`, `slice`, `release`, `work`, `resume`, `front` + `claim`, `status`, `escalate`, `epic`, `graph-sync`, 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
60
|
|
|
61
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.
|
|
62
62
|
|
package/docs/hooks.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hooks del arnés de construcción
|
|
2
2
|
|
|
3
|
-
Este documento describe los **19 hooks** que `@trycore/spec-build-harness` instala en el proyecto del consumidor. Todos viven en `hooks/build/` (un script bash o python por hook, más los helpers compartidos `lib/state-io.sh`, `lib/config.sh`, `lib/runtime-client.sh`, `lib/runtime-ops.sh`, `lib/agent-context.sh`, `lib/projection.sh`) y se cablean a los eventos de Claude Code mediante una **cadena de comando única**, idéntica en ambos canales de instalación.
|
|
3
|
+
Este documento describe los **19 hooks** que `@trycore/spec-build-harness` instala en el proyecto del consumidor. Todos viven en `hooks/build/` (un script bash o python por hook, más los helpers compartidos `lib/state-io.sh`, `lib/config.sh`, `lib/runtime-client.sh`, `lib/runtime-ops.sh`, `lib/agent-context.sh`, `lib/projection.sh`, `lib/harness-meta.sh`) y se cablean a los eventos de Claude Code mediante una **cadena de comando única**, idéntica en ambos canales de instalación.
|
|
4
4
|
|
|
5
5
|
Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarado, el scaffold y la fuente de diseño (bloqueantes), inyectan y re-anclan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, vigilan la presión de contexto y escriben handoff automático, y recuerdan validar trazabilidad, gates abiertos, reflexionar al cerrar un slice y correr el Release Gate cuando se acumulan épicas sin auditar. No reemplazan a los agentes ni a las skills; los **complementan** liberando capacidad de razonamiento del modelo y poniendo barandillas mecánicas donde un olvido cuesta caro.
|
|
6
6
|
|
|
@@ -41,7 +41,9 @@ Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarad
|
|
|
41
41
|
>
|
|
42
42
|
> `lib/state-io.sh` (`legacy`), `lib/config.sh` (`runtime.mode`, umbrales), `lib/runtime-client.sh`
|
|
43
43
|
> (cliente HTTP + cola offline), `lib/runtime-ops.sh` (helpers de `slice-ops.sh`/`release-ops.sh`),
|
|
44
|
-
> `lib/agent-context.sh` (normaliza `GET /agent/context`)
|
|
44
|
+
> `lib/agent-context.sh` (normaliza `GET /agent/context`), `lib/harness-meta.sh` (versión del arnés
|
|
45
|
+
> y catálogo de asset types, compartidos por el registro de sesión y el alta de un worktree) y
|
|
46
|
+
> `lib/projection.sh` (lee esa caché,
|
|
45
47
|
> sin red) **no son hooks**: son los helpers compartidos que los scripts de arriba `source`an.
|
|
46
48
|
|
|
47
49
|
---
|
|
@@ -120,12 +120,13 @@ arman peticiones a mano. En `dual`, cada transición también se **espeja** al s
|
|
|
120
120
|
identidad del fichero (que sigue mandando). Si el espejo falla (red caída, servidor rechaza), el
|
|
121
121
|
trabajo local **nunca se bloquea** — se avisa y queda como discrepancia para el comparador.
|
|
122
122
|
|
|
123
|
-
`slice-ops.sh` tiene **
|
|
124
|
-
leer un log o depurar, no para que los teclees):
|
|
123
|
+
`slice-ops.sh` tiene **20 subcomandos** (los conducen las skills; los listamos para que puedas
|
|
124
|
+
leer un log o depurar, no para que los teclees — `register` es la excepción: ese lo tecleas tú):
|
|
125
125
|
|
|
126
126
|
| Subcomando | Para qué |
|
|
127
127
|
|---|---|
|
|
128
128
|
| `mode` | imprime `legacy`\|`dual`\|`runtime` |
|
|
129
|
+
| `register` | da de alta **este árbol de trabajo** como agente propio, con su token (lo pide por stdin). Solo hace falta en un `git worktree` — ver «Varios agentes en la misma máquina» |
|
|
129
130
|
| `claim` | reclama trabajo (el hub reparte por orden de cola; sin épica dirigida) |
|
|
130
131
|
| `next-step` | deriva la siguiente acción desde la caché de proyección |
|
|
131
132
|
| `phase` | reporta la fase del pipeline (emite la cadena que falte; idempotente) |
|
|
@@ -136,12 +137,87 @@ leer un log o depurar, no para que los teclees):
|
|
|
136
137
|
| `fact` | reporta un hecho de proyecto confirmado |
|
|
137
138
|
| `propose-asset` | propone un documento al plano de contexto (lo publica un ADMIN) |
|
|
138
139
|
| `propose-epic` · `epic-status` · `epic-writeback` | proponen una épica, consultan su estado y proyectan la aprobada a `epicas.md` |
|
|
140
|
+
| `graph-sync` · `graph-sync-status` | proponen el re-sync del grafo desde los docs de discovery y leen el veredicto humano (solo en `runtime`) |
|
|
139
141
|
| `status` · `escalate` | informe local del agente y registro de un bloqueo |
|
|
140
142
|
|
|
141
143
|
Del lado release, `release-ops.sh` añade `verdict` y `front-integration`.
|
|
142
144
|
|
|
143
145
|
**Códigos de salida** (sobre estos ramifica la prosa de las skills): `0` ok · `2` uso · `3` legacy ·
|
|
144
|
-
`4` sin slice · `5` offline · `6` rechazado · `7` sin trabajo
|
|
146
|
+
`4` sin slice · `5` offline · `6` rechazado · `7` sin trabajo · `8` tipo no espejable ·
|
|
147
|
+
`9` identidad heredada (este árbol no está dado de alta — ver abajo).
|
|
148
|
+
|
|
149
|
+
### Varios agentes en la misma máquina (0.16.0)
|
|
150
|
+
|
|
151
|
+
Si abres un `git worktree` por épica para construir en paralelo (`/build:front`), **cada árbol
|
|
152
|
+
necesita su propio token de agente**, emitido por el ADMIN para él. No es burocracia:
|
|
153
|
+
|
|
154
|
+
- El hub **no crea identidad** al registrar. El `agent_key` es el `agent_id` que va grabado en el
|
|
155
|
+
token, así que dos árboles con el mismo token son **un solo agente** — y el segundo `claim` te
|
|
156
|
+
devuelve el slice que ya tiene reclamado el primero.
|
|
157
|
+
- El estado del arnés está en `.gitignore` y **no viaja** al worktree. Sin credenciales propias, el
|
|
158
|
+
árbol nuevo hereda las del clon principal y **suplanta** a ese agente: reclamaría con su
|
|
159
|
+
`agent_key`, reportaría los hashes del árbol equivocado y compartiría su heartbeat.
|
|
160
|
+
|
|
161
|
+
Por eso, en un árbol sin dar de alta, **toda escritura** (`claim`, `phase`, `gate`, `submit`,
|
|
162
|
+
`archive`, `wiring`, `progress`, `checkpoint`, `fact`, `propose-*`, `graph-sync`, `escalate` y
|
|
163
|
+
`release-ops.sh front-integration`) aborta con `rc 9` **antes de tocar la red**. Las lecturas siguen
|
|
164
|
+
funcionando —son el diagnóstico de ese árbol— y `status` lo dice en su primera línea:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
identidad: ⚠ HEREDADA del clon principal (/ruta/al/clon) — solo lectura
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
El alta es un comando, en el terminal, una vez por worktree:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
cd .wt/ep-003
|
|
174
|
+
trycore-build init # tiende .claude/ (sin --runtime-token)
|
|
175
|
+
bash .claude/hooks/build/slice-ops.sh register # pide el token por stdin
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`register` **no acepta el token por argumento** (`--token` sale con `rc 2`): en argv quedaría en el
|
|
179
|
+
historial del shell y en `ps`. Por lo mismo lo corres tú y no un hook — las tool calls no tienen
|
|
180
|
+
TTY. Antes de tocar la red siembra el fichero local (si no, el registro habría escrito sobre las
|
|
181
|
+
credenciales del clon principal), y después verifica contra el hub que el `agent_key` obtenido
|
|
182
|
+
**difiere** del suyo; si coincide, lo rechaza y te dice que pidas al ADMIN un token con `agent_id`
|
|
183
|
+
distinto. Ante cualquier fallo revierte: el árbol queda con identidad propia y verificada, o
|
|
184
|
+
exactamente como estaba. El fichero del clon principal no se toca nunca.
|
|
185
|
+
|
|
186
|
+
Para rotar el token de un árbol ya registrado: `register --force`.
|
|
187
|
+
|
|
188
|
+
### Qué espeja el modo `dual` — y qué no (0.15.0)
|
|
189
|
+
|
|
190
|
+
El espejo cubre la **enumeración normativa** del hub, ni más ni menos: `slice_opened`,
|
|
191
|
+
`phase_advanced`, `phase_reverted`, `gate_verdict`, `wiring_item_updated`,
|
|
192
|
+
`wiring_checklist_seeded`, `checkpoint_recorded`, `progress_noted` y `handoff_recorded`. Todo eso
|
|
193
|
+
viaja como `{"transitions": [{client_event_id, epic_code, event_type, payload}]}`, con la épica
|
|
194
|
+
del `build-state.json` local en el **sobre**, y el hub responde un reporte **por elemento** cuyo
|
|
195
|
+
motivo de rechazo el cliente te enseña tal cual.
|
|
196
|
+
|
|
197
|
+
Lo que **no** se espeja, y por qué — son huecos del **hub**, no del arnés, y el cliente los corta
|
|
198
|
+
en local (`rc 8`) en vez de gastar una llamada para recibir un rechazo:
|
|
199
|
+
|
|
200
|
+
| No espejable | Motivo |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `slice_escalated` · `slice_archived` · `slice_submitted` | Existen en el catálogo del hub pero no heredan de `_TransicionDeEspejo`: la ingesta espejo no los acepta. Una discrepancia dual sigue siendo **visible** donde la lees —el aviso del hook `Stop`—, pero no queda registrada en el hub. |
|
|
203
|
+
| `release_verdict_reported` · `front_integration_reported` | No existen en el catálogo del hub, y la superficie síncrona de los veredictos de release es de **gobierno humano**. En `dual` el veredicto se anota en local y el comando te lo dice con esas palabras; quedará reflejado al pasar a `runtime`. |
|
|
204
|
+
|
|
205
|
+
Cerrar estos huecos requiere un tipo nuevo en el catálogo del hub con su rama de reducer: es
|
|
206
|
+
trabajo hub-side. Mientras tanto el arnés lo **nombra** en vez de fallar en silencio.
|
|
207
|
+
|
|
208
|
+
### Cuando el hub no conoce tu épica (0.15.0)
|
|
209
|
+
|
|
210
|
+
Si el espejo rechaza con «la épica no existe en el grafo del proyecto», el grafo del hub se quedó
|
|
211
|
+
en la foto del import inicial. La salida es proponer el re-sync:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
/build:graph-sync # construye el bundle desde los docs y lo propone
|
|
215
|
+
/build:graph-sync --check # lee el veredicto: APPROVED, o REJECTED con su motivo
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
En `dual` el carril de propuesta no opera (el fichero es primario y el grafo entra por el import
|
|
219
|
+
de admin), pero `slice-ops.sh graph-sync --file epics.json --dry-run` **sí** funciona: úsalo para
|
|
220
|
+
revisar el grafo antes del corte a `runtime`.
|
|
145
221
|
|
|
146
222
|
### Lo que aportó la 0.14.0
|
|
147
223
|
|