@trycore/spec-build-harness 0.8.4 → 0.10.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 +43 -5
- package/INSTALL.md +28 -6
- package/METODOLOGIA.md +65 -10
- package/README.md +41 -7
- package/VERSION +1 -1
- package/agents/build/build-orchestrator.md +33 -7
- package/agents/build/dor-dod-gatekeeper.md +17 -6
- package/agents/build/ux-fidelity-reviewer.md +4 -1
- package/agents/build/wiring-adversarial-verifier.md +52 -5
- package/commands/build/architect.md +1 -1
- package/commands/build/claim.md +46 -0
- package/commands/build/escalate.md +36 -0
- package/commands/build/front.md +9 -3
- package/commands/build/onboard.md +63 -14
- package/commands/build/prototype.md +23 -0
- package/commands/build/reflect.md +60 -40
- package/commands/build/release.md +10 -7
- package/commands/build/resume.md +33 -13
- package/commands/build/slice.md +32 -27
- package/commands/build/status.md +35 -0
- package/commands/build/work.md +11 -8
- package/config/build-config.template.json +4 -0
- package/dist/cli.js +22 -0
- package/dist/commands/doctor.js +42 -0
- package/dist/commands/init.js +84 -1
- package/dist/commands/migrate.js +48 -0
- package/dist/commands/status.js +34 -0
- package/dist/lib/normalize.js +276 -0
- package/dist/lib/paths.js +6 -0
- package/dist/lib/runtime-client.js +196 -0
- package/dist/lib/settings-merge.js +3 -3
- package/dist/lib/state-bundle.js +46 -0
- package/docs/commands.md +32 -9
- package/docs/getting-started.md +2 -1
- package/docs/hooks.md +114 -27
- package/docs/runtime/guia-modo-dual-y-migracion.md +136 -0
- package/docs/runtime/plan-migracion-harness-v0.9.md +11 -0
- package/docs/runtime/protocolo-cliente-runtime.md +109 -34
- package/hooks/build/build-gate-check.sh +21 -0
- package/hooks/build/context-monitor.sh +82 -15
- package/hooks/build/context-sync.sh +192 -0
- package/hooks/build/design-source-guard.sh +31 -3
- package/hooks/build/dual-compare.sh +92 -0
- package/hooks/build/event-emitter.sh +75 -0
- package/hooks/build/gitflow-guard.sh +164 -14
- package/hooks/build/heartbeat.sh +259 -0
- package/hooks/build/lib/agent-context.sh +139 -0
- package/hooks/build/lib/config.sh +27 -0
- package/hooks/build/lib/projection.sh +71 -0
- package/hooks/build/lib/runtime-client.sh +465 -0
- package/hooks/build/lib/runtime-ops.sh +221 -0
- package/hooks/build/lib/state-io.sh +5 -18
- package/hooks/build/load-build-state.sh +64 -2
- package/hooks/build/reflect-nudge.sh +15 -0
- package/hooks/build/release-gate-nudge.sh +15 -0
- package/hooks/build/release-ops.sh +164 -0
- package/hooks/build/scaffold-guard.sh +29 -2
- package/hooks/build/session-start.sh +103 -0
- package/hooks/build/session-stop.sh +22 -0
- package/hooks/build/slice-ops.sh +877 -0
- package/hooks/build/stack-guard.sh +8 -0
- package/hooks/build/statusline-bridge.sh +24 -3
- package/hooks/build-harness.json +16 -0
- package/package.json +3 -3
- package/scripts/check-agnostic.sh +3 -1
- package/scripts/check-pack-clean.sh +31 -0
- package/scripts/check-runtime-purity.sh +43 -0
- package/scripts/lib/front-plan.py +4 -0
- package/scripts/lib/graph-bundle.py +133 -0
- package/scripts/runtime-purity-allow.txt +5 -0
- package/scripts/smoke-test.sh +1 -1
- package/scripts/tests/lib/http-stub.py +46 -0
- package/scripts/tests/test-baseline-verdict.sh +92 -0
- package/scripts/tests/test-config.sh +25 -0
- package/scripts/tests/test-hooks-runtime.sh +853 -0
- package/scripts/tests/test-install.sh +57 -0
- package/scripts/tests/test-runtime-client.sh +298 -0
- package/scripts/tests/test-schema.sh +29 -1
- package/scripts/tests/test-skill-ops.sh +847 -0
- package/skills/building-a-micro-change/SKILL.md +22 -4
- package/skills/building-a-slice/SKILL.md +58 -23
- package/skills/building-a-slice/assets/baseline-verdict.sh +172 -0
- package/skills/building-a-slice/references/dod.md +12 -3
- package/skills/building-a-slice/references/dor.md +7 -3
- package/skills/building-a-slice/references/evidence-budget.md +51 -0
- package/skills/building-a-slice/references/exploration-fanout.md +1 -1
- package/skills/building-a-slice/references/gitflow.md +1 -1
- package/skills/building-a-slice/references/regression-baseline.md +67 -0
- package/skills/building-a-slice/references/runtime-protocol.md +75 -0
- package/skills/building-a-slice/references/state-protocol.md +12 -1
- package/skills/building-a-slice/workflows/README.md +7 -3
- package/skills/building-a-slice/workflows/explore-fanout.workflow.js +3 -3
- package/skills/building-a-slice/workflows/wiring-verify.workflow.js +26 -4
- package/skills/managing-parallel-front/SKILL.md +32 -16
- package/skills/openspec-archive-change/SKILL.md +15 -0
- package/skills/prototyping-screens/SKILL.md +104 -0
- package/skills/prototyping-screens/assets/DESIGN.md.template +55 -0
- package/skills/prototyping-screens/assets/manifest.schema.json +70 -0
- package/skills/prototyping-screens/assets/screen.template.html +34 -0
- package/skills/prototyping-screens/references/aesthetic-directions.md +42 -0
- package/skills/prototyping-screens/references/extraction.md +57 -0
- package/skills/prototyping-screens/references/self-check.md +40 -0
- package/skills/releasing-a-version/SKILL.md +25 -16
- package/skills/releasing-a-version/references/release-dod.md +7 -5
- package/skills/releasing-a-version/workflows/README.md +2 -1
- package/skills/releasing-a-version/workflows/release-gate.workflow.js +6 -5
- package/skills/setup-architecture/SKILL.md +4 -2
- package/state/README.md +20 -3
- package/state/build-state.schema.json +3 -2
- package/templates/CLAUDE.md.template +17 -1
- package/templates/settings-hooks.template.json +8 -4
- package/internal/skills/auditar-arnes/SKILL.md +0 -29
package/docs/hooks.md
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# Hooks del arnés de construcción
|
|
2
2
|
|
|
3
|
-
Este documento describe los **
|
|
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.
|
|
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
|
|
|
7
|
+
> **Modo runtime (beta, opt-in — EP-OR-08).** Seis de los 19 hooks (`session-start.sh`,
|
|
8
|
+
> `event-emitter.sh`, `context-sync.sh`, `heartbeat.sh`, `dual-compare.sh`, `session-stop.sh`) son
|
|
9
|
+
> los que hacen del arnés un **cliente del Agent Orchestrator Runtime** cuando el proyecto corre en
|
|
10
|
+
> modo `dual`/`runtime` (`config/build-config.json#runtime.mode`, default `legacy`). En `legacy`
|
|
11
|
+
> son no-op o casi (fail-open: sin URL/token, no tocan red). Guía de uso →
|
|
12
|
+
> `docs/runtime/guia-modo-dual-y-migracion.md`. Contrato de red → `docs/runtime/protocolo-cliente-runtime.md`.
|
|
13
|
+
|
|
7
14
|
---
|
|
8
15
|
|
|
9
|
-
## Resumen de los
|
|
16
|
+
## Resumen de los 19 hooks
|
|
10
17
|
|
|
11
18
|
| Hook | Evento | Matcher | Qué hace | ¿Bloqueante? |
|
|
12
19
|
|---|---|---|---|---|
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
20
|
+
| `session-start.sh` | `SessionStart` | `startup\|clear\|compact` | (Beta.) Solo si el modo no es `legacy`: registra el agente (`POST /agents/register`), refresca `GET /agent/context`, dispara `context-sync.sh` y asegura el daemon `heartbeat.sh`. Escribe la caché de proyección; no inyecta contexto (eso lo hace `load-build-state.sh`, que corre después). En `legacy`, no-op. | No |
|
|
21
|
+
| `load-build-state.sh` | `SessionStart` | `startup\|clear\|compact` | Inyecta al contexto la rama, la fase del arnés, el slice activo y los gates abiertos. En `legacy`, sincroniza `harness_phase` e invoca a `reconcile-build-state.py`; en `dual`/`runtime`, renderiza desde la caché de proyección que escribió `session-start.sh`. | No |
|
|
22
|
+
| `reconcile-build-state.py` | `SessionStart` (invocado por `load-build-state.sh`, solo `legacy`) | — | Ancla `build-state.json` a la realidad de git + tests: degrada a `failing` los items de `wiring_checklist` marcados `passing` sin `evidence`, y anota (sin corregir) el *drift* entre la rama real y `active_slice.branch`. Nunca revierte gates `true→false`. | No |
|
|
15
23
|
| `statusline-bridge.sh` | `statusLine` (comando, no evento de hooks; solo canal CLI) | — | Imprime la línea de estado (`🏗️ build · ctx N%`) y escribe el archivo-puente de contexto (`claude-ctx-<session>.json`) que `context-monitor.sh` consume para calcular la presión de contexto. | No |
|
|
16
24
|
| `gitflow-guard.sh` | `PreToolUse` | `Bash` | Enforce GitHub Flow estricto sobre `git commit` / `git push`. | **Sí (exit 2)** |
|
|
17
25
|
| `stack-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea dependencias en `package.json` fuera de la allowlist del stack del PRD. | **Sí (exit 2)** |
|
|
@@ -19,20 +27,39 @@ Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarad
|
|
|
19
27
|
| `design-source-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea escribir código de un slice **con UI** (`gates.fidelity===false`, fases `red…data`) si la fuente de diseño del proyecto no está confirmada (`design_source.confirmed`). | **Sí (exit 2)** |
|
|
20
28
|
| `lint-typecheck.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Corre prettier/eslint/tsc sobre el archivo `.ts`/`.tsx` editado. | No |
|
|
21
29
|
| `coherence-flag.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Recuerda validar la trazabilidad de un `proposal.md` de OpenSpec recién tocado. | No |
|
|
22
|
-
| `
|
|
30
|
+
| `event-emitter.sh` | `PostToolUse` | `Bash\|Edit\|Write\|MultiEdit\|Task` | (Beta.) Solo si el modo no es `legacy`: encola telemetría (`tool_use_recorded`) en la cola offline — nunca la red en el hilo del hook. Metadatos únicamente (herramienta, ruta relativa, `argv0`); nunca el comando completo ni el fuente. | No |
|
|
31
|
+
| `context-monitor.sh` | `PostToolUse` \| `PreCompact` \| `Stop` | `Bash\|Edit\|Write\|MultiEdit\|Task` (`PostToolUse`) · `.*` (`PreCompact`/`Stop`) | Lee el puente de contexto de `statusline-bridge.sh`, aplica los umbrales `context.warning_pct`/`context.critical_pct` y, si está por debajo, inyecta `additionalContext` de advertencia; en `critical` escribe el handoff una sola vez por sesión — en `build-state.json` (`legacy`) o como evento `handoff_recorded` (`dual`/`runtime`). | No |
|
|
23
32
|
| `build-gate-check.sh` | `Stop` | `.*` | Al cerrar el turno, avisa si el slice activo tiene gates abiertos. | No |
|
|
24
33
|
| `reflect-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere `/build:reflect` si hay slice(s) archivado(s) sin reflexionar (`reflected != true`). | No |
|
|
25
34
|
| `release-gate-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere correr el Release Gate (`/build:release`) cuando hay ≥2 épicas archivadas sin auditar desde el último release. Determinista; solo sugiere. | No |
|
|
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
|
+
| `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
|
+
| `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. |
|
|
26
39
|
|
|
27
|
-
> Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y
|
|
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`.
|
|
28
41
|
>
|
|
29
|
-
> `lib/state-io.sh`
|
|
42
|
+
> `lib/state-io.sh` (`legacy`), `lib/config.sh` (`runtime.mode`, umbrales), `lib/runtime-client.sh`
|
|
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`) y `lib/projection.sh` (lee esa caché,
|
|
45
|
+
> sin red) **no son hooks**: son los helpers compartidos que los scripts de arriba `source`an.
|
|
30
46
|
|
|
31
47
|
---
|
|
32
48
|
|
|
33
49
|
## Detalle por hook
|
|
34
50
|
|
|
35
|
-
### 1. `
|
|
51
|
+
### 1. `session-start.sh` — `SessionStart` · no bloqueante (beta — EP-OR-08)
|
|
52
|
+
|
|
53
|
+
Primer hook del `SessionStart` (antes de `load-build-state.sh`). Si el modo (`runtime_mode`) es
|
|
54
|
+
`legacy`, no hace nada. Si es `dual`/`runtime`: registra el agente (`POST /agents/register`, con
|
|
55
|
+
`asset_types` leído de `.claude/asset-types.json` si existe), refresca `GET /agent/context` y
|
|
56
|
+
**normaliza** esa respuesta a la caché de proyección local (`lib/agent-context.sh` — la única pieza
|
|
57
|
+
que conoce la forma cruda de la respuesta del servidor), dispara `context-sync.sh` y asegura que el
|
|
58
|
+
daemon `heartbeat.sh` esté vivo. Nunca inyecta `additionalContext` directamente — eso lo hace
|
|
59
|
+
`load-build-state.sh`, que corre después en el mismo evento. Fail-open: un registro fallido no
|
|
60
|
+
aborta la sesión, solo deja el modo degradado a lo último sincronizado.
|
|
61
|
+
|
|
62
|
+
### 2. `load-build-state.sh` — `SessionStart` · no bloqueante
|
|
36
63
|
|
|
37
64
|
Se dispara al arrancar, limpiar o compactar la sesión (`matcher: startup|clear|compact`). Su trabajo:
|
|
38
65
|
|
|
@@ -43,7 +70,7 @@ Se dispara al arrancar, limpiar o compactar la sesión (`matcher: startup|clear|
|
|
|
43
70
|
|
|
44
71
|
Es el hook que le da a Claude "memoria de obra" al empezar: sabe en qué historia/épica está, qué change de OpenSpec le corresponde y qué le falta cerrar.
|
|
45
72
|
|
|
46
|
-
###
|
|
73
|
+
### 3. `gitflow-guard.sh` — `PreToolUse` · Bash · **bloqueante**
|
|
47
74
|
|
|
48
75
|
Enforce **GitHub Flow estricto** antes de ejecutar cualquier comando Bash. Parsea el `tool_input.command` del JSON del hook y solo actúa sobre comandos `git` de escritura. Bloquea con `exit 2` en tres casos:
|
|
49
76
|
|
|
@@ -53,7 +80,7 @@ Enforce **GitHub Flow estricto** antes de ejecutar cualquier comando Bash. Parse
|
|
|
53
80
|
|
|
54
81
|
Cualquier otro comando (incluido cualquier `git` que no sea commit/push) sale `0`. La política está alineada con el inner loop (la skill `building-a-slice` cierra cada slice con PR + archive).
|
|
55
82
|
|
|
56
|
-
###
|
|
83
|
+
### 4. `stack-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante**
|
|
57
84
|
|
|
58
85
|
Vigila el **contrato de stack** declarado en el PRD. Solo le importan las ediciones que tocan `package.json`. Compara las dependencias del contenido entrante (`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`) contra `config/stack-allowlist.json` —usando *globs* (`fnmatch`)— y **bloquea con `exit 2`** si aparece alguna dependencia fuera de la allowlist.
|
|
59
86
|
|
|
@@ -61,7 +88,7 @@ Vigila el **contrato de stack** declarado en el PRD. Solo le importan las edicio
|
|
|
61
88
|
- El mensaje guía a actualizar `config/stack-allowlist.json` y dejar nota en `GOVERNANCE.md`, o a consultar al agente `stack-guardian`.
|
|
62
89
|
- La allowlist es **artefacto del consumidor**: el CLI la siembra y `/build:onboard` la puebla a partir del PRD.
|
|
63
90
|
|
|
64
|
-
###
|
|
91
|
+
### 5. `lint-typecheck.sh` — `PostToolUse` · Write/Edit/MultiEdit · no bloqueante
|
|
65
92
|
|
|
66
93
|
Tras editar un archivo `.ts`/`.tsx`, delega el estilo a las herramientas para no gastar tokens del modelo en formateo. Si los binarios existen en `node_modules/.bin/`, corre:
|
|
67
94
|
|
|
@@ -75,42 +102,97 @@ Tras editar un archivo `.ts`/`.tsx`, delega el estilo a las herramientas para no
|
|
|
75
102
|
|
|
76
103
|
Nunca bloquea (siempre `exit 0`); reporta a `stderr` como información. Inerte si no hay `package.json` o si el archivo no es `.ts`/`.tsx`.
|
|
77
104
|
|
|
78
|
-
###
|
|
105
|
+
### 6. `coherence-flag.sh` — `PostToolUse` · Write/Edit/MultiEdit · no bloqueante
|
|
79
106
|
|
|
80
107
|
Cuando se edita un `openspec/changes/**/proposal.md`, imprime un recordatorio: correr el agente `change-epic-coherence` para validar el bloque `## Trazabilidad` (Épica `EP-XXX` / Historias `HU-XXX`) y ejecutar `openspec validate`. Es un *nudge* de coherencia change↔épica, no un control de bloqueo.
|
|
81
108
|
|
|
82
|
-
###
|
|
109
|
+
### 7. `event-emitter.sh` — `PostToolUse` · no bloqueante (beta — EP-OR-08)
|
|
110
|
+
|
|
111
|
+
Solo si el modo no es `legacy`: telemetría **como propiedad del harness**, imposible de omitir por
|
|
112
|
+
el modelo. Tras cada `Bash|Edit|Write|MultiEdit|Task`, encola un evento `tool_use_recorded` en
|
|
113
|
+
`.claude/state/outbox/` (nunca llama a la red en el hilo del hook) y hace la comprobación barata de
|
|
114
|
+
que el daemon `heartbeat.sh` esté vivo (`stat` del pidfile, sin red). Solo guarda metadatos —
|
|
115
|
+
herramienta, ruta relativa, `argv0` — **nunca** el comando completo ni el contenido editado. En
|
|
116
|
+
`legacy`, no-op.
|
|
117
|
+
|
|
118
|
+
### 8. `build-gate-check.sh` — `Stop` · no bloqueante
|
|
83
119
|
|
|
84
120
|
Al cerrar el turno, si hay un slice activo con gates en `false`, avisa por `stderr` qué gates quedan abiertos y recuerda **no archivar ni abrir PR** hasta cerrarlos (ver skill `building-a-slice` / `dod.md`). Inerte si no hay `package.json`, ni estado, ni `python3`.
|
|
85
121
|
|
|
86
|
-
###
|
|
122
|
+
### 9. `scaffold-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante** (desde v0.2.0)
|
|
87
123
|
|
|
88
|
-
Refuerza el **scaffold como "Paso 1 fundamental"**. Bloquea con `exit 2` la escritura de **código de slice** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data`— mientras `scaffold.confirmed` no sea `true`
|
|
124
|
+
Refuerza el **scaffold como "Paso 1 fundamental"**. Bloquea con `exit 2` la escritura de **código de slice** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data`— mientras `scaffold.confirmed` no sea `true`. En `legacy` lo lee de `build-state.json`; en `dual`/`runtime`, de la caché de proyección local (`lib/projection.sh`, sin red — el guard sigue con latencia local). **Permite** crear el scaffold (sin slice activo, o en fases `dor`/`change`). El arnés **exige** el scaffold pero **no lo genera**; la confirmación es **explícita** (vía `building-a-slice` Fase 0 / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido (solo bloquea si la entrada parece código de slice).
|
|
89
125
|
|
|
90
|
-
###
|
|
126
|
+
### 10. `reflect-nudge.sh` — `Stop` · no bloqueante (desde v0.3.0)
|
|
91
127
|
|
|
92
128
|
Cierra el **ciclo autocorrectivo**. Al terminar el turno, si en `history[]` hay slice(s) archivado(s) con `reflected != true`, imprime un *nudge* sugiriendo ejecutar `/build:reflect` para capturar las convenciones aprendidas (y errores recurrentes) en el bloque `trycore-build-learnings` de `CLAUDE.md`. **Nunca bloquea** el cierre de sesión: si falta `python3` o el estado, sale `0` en silencio (**fail-open**). El razonamiento —qué se aprendió— vive en el comando `/build:reflect`, no en el hook; este solo recuerda. Tras reflexionar y estampar `reflected: true`, el nudge calla.
|
|
93
129
|
|
|
94
|
-
###
|
|
130
|
+
### 11. `design-source-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante** (desde v0.5.0)
|
|
95
131
|
|
|
96
|
-
Refuerza el **seguro de fuente de diseño** (espejo de `scaffold-guard`, para slices con UI). Bloquea con `exit 2` la escritura de **código de un slice con UI** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data` **y** `active_slice.gates.fidelity === false` (marcado UI-pendiente por la DoR)— mientras el proyecto tenga UI (`design_source.applies === true`) y `design_source.confirmed` no sea `true`. **Permite** todo lo demás: slices sin UI (`gates.fidelity === null` o ausente), proyectos sin UI (`applies !== true`), fases de planificación (`dor`/`change`), o fuente ya confirmada. El arnés **exige** la fuente de diseño
|
|
132
|
+
Refuerza el **seguro de fuente de diseño** (espejo de `scaffold-guard`, para slices con UI). Bloquea con `exit 2` la escritura de **código de un slice con UI** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data` **y** `active_slice.gates.fidelity === false` (marcado UI-pendiente por la DoR)— mientras el proyecto tenga UI (`design_source.applies === true`) y `design_source.confirmed` no sea `true`. **Permite** todo lo demás: slices sin UI (`gates.fidelity === null` o ausente), proyectos sin UI (`applies !== true`), fases de planificación (`dor`/`change`), o fuente ya confirmada. En `legacy` lo lee de `build-state.json`; en `dual`/`runtime`, de la caché de proyección local (sin red). El arnés **exige** la fuente de diseño y puede **generarla** vía `/build:prototype` (skill `prototyping-screens`; el mensaje de bloqueo lo sugiere); la confirmación sigue siendo **explícita y humana** (vía `building-a-slice` Fase 0-bis / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido. En la práctica, como `design_source` es gate de proyecto, solo muerde la **primera** construcción de UI sin fuente declarada.
|
|
97
133
|
|
|
98
|
-
###
|
|
134
|
+
### 12. `release-gate-nudge.sh` — `Stop` · no bloqueante (desde v0.7.0)
|
|
99
135
|
|
|
100
136
|
Cierra el lazo del **outer loop**. Al terminar el turno, cuenta las épicas **archivadas** (`history[].epica`) que **ningún** release ha cubierto todavía (`releases[].epicas`); si quedan **≥2 épicas sin auditar**, imprime un *nudge* sugiriendo ejecutar `/build:release` (o la skill `releasing-a-version`) para correr los gates pesados **una sola vez** sobre el diff acumulado. Es puramente determinista: **aritmética de conjuntos** (archivadas − cubiertas) independiente de *timestamps*, sin consultar fechas ni el criterio de "cierre de línea de release" (eso lo computa la skill `building-a-slice` en su fase de cierre). **Nunca bloquea** el cierre, **nunca** ejecuta trabajo pesado, **nunca** llama al modelo ni escribe el estado; si falta `python3` o el estado, sale `0` en silencio (**fail-open**).
|
|
101
137
|
|
|
102
|
-
###
|
|
138
|
+
### 13. `dual-compare.sh` — `Stop` · no bloqueante (beta, solo modo `dual` — EP-OR-08)
|
|
139
|
+
|
|
140
|
+
El **comparador dual**: en modo `dual` el fichero es primario y cada transición se espeja al
|
|
141
|
+
servidor (`POST …/mirror/transitions`); este hook detecta cuando el **reducer del servidor**
|
|
142
|
+
llegó a una proyección distinta de lo que dice el fichero local — señal de un espejo rechazado o
|
|
143
|
+
perdido. Refresca la proyección (Stop no es sensible a latencia como PreToolUse) y compara épica,
|
|
144
|
+
fase y gates **ya resueltos** (nunca `null`=pendiente) entre ambos lados. Ante discrepancia, escala
|
|
145
|
+
por el mismo canal que `/build:escalate` — nunca inventa un evento nuevo, nunca bloquea el cierre
|
|
146
|
+
de sesión. Es el instrumento de medición que condiciona el corte a `runtime`: si un piloto real
|
|
147
|
+
corre en `dual` sin que este hook reporte nunca una discrepancia, se justifica apagar `legacy` (ver
|
|
148
|
+
`docs/runtime/plan-migracion-harness-v0.9.md` §2.3). En `legacy`/`runtime`, no-op.
|
|
149
|
+
|
|
150
|
+
### 14. `session-stop.sh` — `Stop` · no bloqueante (beta — EP-OR-08)
|
|
151
|
+
|
|
152
|
+
Al cerrar el turno, si el modo no es `legacy`: deja el sentinela `outbox/.flush-request` y asegura
|
|
153
|
+
que el daemon `heartbeat.sh` esté vivo. El **flush real de la cola offline lo ejecuta el daemon**,
|
|
154
|
+
no este hook (un `Stop` no puede quedarse esperando una respuesta HTTP). No-op en `legacy`.
|
|
155
|
+
|
|
156
|
+
### 15. `statusline-bridge.sh` — comando `statusLine` · no bloqueante (desde v0.8.0)
|
|
103
157
|
|
|
104
158
|
Es el motor de contexto visto desde la barra de estado. No es un hook de evento: es el comando que Claude Code invoca para renderizar el `statusLine` (solo canal CLI; el plugin no puede inyectar `statusLine`). Lee el payload por `stdin`, calcula `remaining_pct` desde `context_window.remaining_percentage` y escribe atómicamente el archivo-puente `claude-ctx-<session_id>.json` (nombre de sesión saneado contra *path traversal*) para que `context-monitor.sh` lo consuma. Imprime siempre una línea (`🏗️ build` o `🏗️ build · ctx N%`); si falta `python3` o el payload no trae el dato, degrada sin romper la barra (**fail-open**).
|
|
105
159
|
|
|
106
|
-
###
|
|
160
|
+
### 16. `context-monitor.sh` — `PostToolUse` / `PreCompact` / `Stop` · no bloqueante (desde v0.8.0)
|
|
107
161
|
|
|
108
|
-
Es la vigilancia de **presión de contexto**. Se dispara tras herramientas (`PostToolUse`, matcher `Bash|Edit|Write|MultiEdit|Task`), antes de compactar (`PreCompact`) y al cerrar el turno (`Stop`). Lee el puente que escribió `statusline-bridge.sh` (ignora lecturas más viejas que `context.stale_seconds`) y compara `remaining_pct` contra `context.warning_pct`/`context.critical_pct` (leídos vía `lib/state-io.sh#config_get`, con defaults 35/25). En `warning` inyecta `additionalContext` pidiendo buscar un punto de corte natural. En `critical`, y **una sola vez por sesión** (`active_slice.session_continuity.critical_recorded`), escribe el handoff en `build-state.json
|
|
162
|
+
Es la vigilancia de **presión de contexto**. Se dispara tras herramientas (`PostToolUse`, matcher `Bash|Edit|Write|MultiEdit|Task`), antes de compactar (`PreCompact`) y al cerrar el turno (`Stop`). Lee el puente que escribió `statusline-bridge.sh` (ignora lecturas más viejas que `context.stale_seconds`) y compara `remaining_pct` contra `context.warning_pct`/`context.critical_pct` (leídos vía `lib/state-io.sh#config_get`, con defaults 35/25). En `warning` inyecta `additionalContext` pidiendo buscar un punto de corte natural. En `critical`, y **una sola vez por sesión** (`active_slice.session_continuity.critical_recorded`), escribe el handoff: en `build-state.json` (`legacy`), como evento `handoff_recorded` (`runtime`), o ambos (`dual`) — `stopped_at`, `resume_hint` (a partir de los items `failing` de `wiring_checklist`) y un hito en `progress_log[]`; si `context.auto_checkpoint` está activo, lo señala para continuar en sesión fresca sin pedir confirmación. **Nunca bloquea**; sin `python3`, sin puente, o con el puente desactualizado, sale `0` en silencio (**fail-open**).
|
|
109
163
|
|
|
110
|
-
###
|
|
164
|
+
### 17. `reconcile-build-state.py` — `SessionStart` (invocado por `load-build-state.sh`, solo `legacy`) · no bloqueante (desde v0.8.0)
|
|
111
165
|
|
|
112
166
|
Es el único hook en Python puro (los demás son bash que delegan fragmentos a `python3`). No se cablea como entrada independiente en `settings.json`/`hooks/build-harness.json`: `load-build-state.sh` lo invoca al arrancar, **antes** de leer el estado, para "anclarlo a la realidad" de git y de los tests. Deriva, nunca lanza: (1) degrada a `failing` cualquier item de `wiring_checklist` marcado `passing` sin `evidence` no vacía (self-heal contra falsos positivos); (2) detecta *drift* entre la rama git real y `active_slice.branch` y lo **anota** en `branch_drift` (no lo corrige); (3) nunca revierte un gate booleano de `true` a `false` (ratchet: solo una señal explícita lo haría). Escribe atómico (`tempfile` + `os.replace`) solo si algo cambió. Fail-open: cualquier error de lectura o parseo devuelve `0` sin tocar el archivo.
|
|
113
167
|
|
|
168
|
+
### 18. `context-sync.sh` — invocado por `session-start.sh` y las skills, no registrado (beta — EP-OR-08)
|
|
169
|
+
|
|
170
|
+
Sincronización de contexto **content-addressed** (`docs/runtime/protocolo-cliente-runtime.md` §4).
|
|
171
|
+
Compara el `manifest_hash` esperado (de `GET /agent/context` o el persistido en las credenciales)
|
|
172
|
+
contra el del lock local (`.claude/state/context.lock`); si coinciden, no hace nada (comparación de
|
|
173
|
+
strings, barata). Si difieren: pide el manifiesto, calcula el diff por `sha256` archivo a archivo y
|
|
174
|
+
descarga **solo** lo cambiado — pero únicamente hacia destinos gobernados (`config/`, `rules/`,
|
|
175
|
+
`docs-cache/`, siempre bajo `.claude/`); cualquier entrada absoluta, con `..` o fuera de esos
|
|
176
|
+
prefijos se rechaza sin escribir. El lock solo sella el hash nuevo si se aplicó el plan **completo**;
|
|
177
|
+
una convergencia parcial deja el lock en el hash anterior para que la próxima sesión reintente lo
|
|
178
|
+
que falta. Escritura atómica (`mkstemp` + `os.replace`). Fail-open total: sin runtime, sin
|
|
179
|
+
`python3` o con manifiesto ilegible, sale `0` dejando el último lock intacto — se sigue trabajando
|
|
180
|
+
con el contexto ya sincronizado.
|
|
181
|
+
|
|
182
|
+
### 19. `heartbeat.sh` — daemon singleton por repo, lanzado por `session-start.sh`, no registrado (beta — EP-OR-08)
|
|
183
|
+
|
|
184
|
+
No existe evento Timer en Claude Code, los hooks son efímeros y un `PostToolUse` con throttle no
|
|
185
|
+
late durante una tool call larga — por eso el latido es un **daemon**, no un hook de evento.
|
|
186
|
+
**Singleton por repo**: `.claude/state/heartbeat.pid` + `.claude/state/heartbeat-sessions.json`
|
|
187
|
+
(ppids de las sesiones interesadas); el arranque se serializa con un mutex de directorio (`mkdir`,
|
|
188
|
+
atómico) con liberación por edad si queda huérfano. Cada `SessionStart` registra su ppid y relanza
|
|
189
|
+
si el pidfile está muerto; cada `PostToolUse` repite la comprobación barata (`stat` + `kill -0`, sin
|
|
190
|
+
red). **Muere** cuando no queda ningún ppid registrado vivo (dos sesiones sobre el mismo repo
|
|
191
|
+
comparten daemon; cerrar la primera no lo mata) — nunca se cuelga de `Stop` ni de `SessionEnd`. Por
|
|
192
|
+
tick (`TRYCORE_HEARTBEAT_TICK_S`, default 5s): consume `outbox/.flush-request` y despacha la cola;
|
|
193
|
+
cada `lease_ttl_s/3` (mínimo 10s), `PUT /leases/renew`. **Sin lease no se apaga**: los eventos de
|
|
194
|
+
ámbito proyecto (pre-claim) necesitan despachador igual.
|
|
195
|
+
|
|
114
196
|
---
|
|
115
197
|
|
|
116
198
|
## La cadena de comando única (sin doble disparo entre canales)
|
|
@@ -135,9 +217,9 @@ Como la cadena es **carácter por carácter idéntica** en ambos canales, si el
|
|
|
135
217
|
Los hooks parsean el JSON del evento con `python3`. Qué pasa si **falta** `python3` depende de si el hook es bloqueante:
|
|
136
218
|
|
|
137
219
|
- **Bloqueantes** (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) → **fail-closed dirigido**: si no pueden analizar el comando/edición, solo bloquean (`exit 2`) cuando la entrada *cruda* parece relevante (un `git commit`/`push`, una edición que menciona `package.json`, o código de slice sin scaffold/fuente de diseño confirmados); en cualquier otro caso salen `0`. Esto evita falsos negativos peligrosos sin frenar el trabajo no relacionado. El mensaje pide instalar `python3` (verificable con `trycore-build doctor`).
|
|
138
|
-
- **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`, `reflect-nudge`, `release-gate-nudge`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0` (fail-open).
|
|
220
|
+
- **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`, `reflect-nudge`, `release-gate-nudge`, y los seis hooks de runtime — `session-start`, `event-emitter`, `context-sync`, `heartbeat`, `dual-compare`, `session-stop`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0` (fail-open).
|
|
139
221
|
|
|
140
|
-
> `python3` es un **requisito duro**: `trycore-build init` y `trycore-build doctor` **fallan** si no está presente, justo porque toda la cadena de hooks depende de él para leer el JSON del evento.
|
|
222
|
+
> `python3` es un **requisito duro**: `trycore-build init` y `trycore-build doctor` **fallan** si no está presente, justo porque toda la cadena de hooks depende de él para leer el JSON del evento (y, en modo runtime, para parsear las respuestas HTTP).
|
|
141
223
|
|
|
142
224
|
---
|
|
143
225
|
|
|
@@ -160,6 +242,11 @@ Los hooks que tocan el código construido se **auto-arman**: permanecen **inerte
|
|
|
160
242
|
|
|
161
243
|
Así el arnés convive sin fricción con la fase de *discovery* y se "enciende" cuando empieza la construcción real.
|
|
162
244
|
|
|
245
|
+
> **Segundo gate, independiente**: los seis hooks de runtime (`session-start`, `event-emitter`,
|
|
246
|
+
> `context-sync`, `heartbeat`, `dual-compare`, `session-stop`) no los gobierna `package.json` sino
|
|
247
|
+
> `config/build-config.json#runtime.mode` — en `legacy` (default) son no-op o casi, sin importar la
|
|
248
|
+
> fase `authoring`/`active`. Los dos gates son ortogonales.
|
|
249
|
+
|
|
163
250
|
---
|
|
164
251
|
|
|
165
252
|
## Cómo se instalan
|
|
@@ -170,7 +257,7 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
|
|
|
170
257
|
|
|
171
258
|
`trycore-build init` hace un **merge idempotente y aditivo** en el `settings.json` del consumidor (lógica en `src/lib/settings-merge.ts`; espejo documental en `templates/settings-hooks.template.json`):
|
|
172
259
|
|
|
173
|
-
- Agrega las
|
|
260
|
+
- Agrega las **7 agrupaciones** de hooks (**17 invocaciones**: `SessionStart` = `session-start` + `load-build-state`; `PreToolUse·Bash` = `gitflow-guard`; `PreToolUse·Write` = `stack-guard` + `scaffold-guard` + `design-source-guard`; `PostToolUse·Write` = `lint-typecheck` + `coherence-flag`; `PostToolUse·Bash|Edit|Write|MultiEdit|Task` = `context-monitor` + `event-emitter`; `PreCompact` = `context-monitor`; `Stop` = `build-gate-check` + `reflect-nudge` + `release-gate-nudge` + `dual-compare` + `context-monitor` + `session-stop`) sin pisar lo que ya exista. Ambos canales (CLI y plugin) cablean las **mismas 17 invocaciones** — un test de sincronización (`scripts/tests/test-hooks-runtime.sh`) compara los 3 ficheros contra un mapa objetivo y rompe si divergen.
|
|
174
261
|
- Agrega **permisos mínimos y enumerados** (sin `mcp__*` ni rutas absolutas):
|
|
175
262
|
|
|
176
263
|
```
|
|
@@ -183,6 +270,6 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
|
|
|
183
270
|
|
|
184
271
|
### Canal plugin — `hooks/build-harness.json`
|
|
185
272
|
|
|
186
|
-
El plugin declara los hooks en `hooks/build-harness.json` (referenciado desde `.claude-plugin/plugin.json` con `"hooks": "./hooks/build-harness.json"`) y usa la **misma cadena de comando
|
|
273
|
+
El plugin declara los hooks en `hooks/build-harness.json` (referenciado desde `.claude-plugin/plugin.json` con `"hooks": "./hooks/build-harness.json"`) y usa la **misma cadena de comando** y las **mismas 17 invocaciones** que el canal CLI — no hay asimetría entre canales. Si un consumidor instala ambos, las cadenas idénticas se deduplican (ver arriba) y cada hook dispara una sola vez.
|
|
187
274
|
|
|
188
275
|
> **Caveat de canales:** el canal CLI es el **canónico** para operar en un proyecto. El plugin namespacea los componentes bajo el nombre del plugin (`/trycore-spec-build-harness:*`) por diseño de Claude Code; las cross-references internas (skills que invocan `/opsx:*`, agentes por nombre) están escritas para el canal CLI. Los **hooks**, en cambio, son idénticos en ambos canales y se deduplican si coexisten.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Guía práctica — modo legacy, dual y runtime (Agent Orchestrator Runtime)
|
|
2
|
+
|
|
3
|
+
> **Estado: beta, opt-in.** Nada de esto cambia el comportamiento de un proyecto que no lo activa
|
|
4
|
+
> explícitamente. `legacy` sigue siendo el default y el único camino con soporte completo hasta que
|
|
5
|
+
> un piloto real confirme el corte (`plan-migracion-harness-v0.9.md` §2). Esta guía es el **cómo**;
|
|
6
|
+
> el contrato de red completo vive en [`protocolo-cliente-runtime.md`](protocolo-cliente-runtime.md)
|
|
7
|
+
> y las reglas de gobierno en [`METODOLOGIA.md`](../../METODOLOGIA.md) §7-bis /
|
|
8
|
+
> [`GOVERNANCE.md`](../../GOVERNANCE.md).
|
|
9
|
+
|
|
10
|
+
## 1. Los tres modos
|
|
11
|
+
|
|
12
|
+
El arnés opera el **mismo** pipeline de dos loops (DoR → change → TDD → smoke → DoD → PR, Release
|
|
13
|
+
Gate) en cualquiera de los tres. Lo que cambia es **dónde vive el estado** y **quién valida las
|
|
14
|
+
transiciones**:
|
|
15
|
+
|
|
16
|
+
| Modo | Fuente de verdad | Quién valida | Cuándo usarlo |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| **`legacy`** (default) | `.claude/state/build-state.json` (fichero local) | Honor system + hooks/agentes locales | Todo proyecto, hoy. Sin cambios. |
|
|
19
|
+
| **`dual`** | El **fichero** sigue siendo primario | Igual que legacy, **más** un espejo al servidor que se compara | Piloto: probar el runtime sin apostar el proyecto a él. |
|
|
20
|
+
| **`runtime`** | El **servidor** (Agent Orchestrator Runtime) | El servidor (reducer server-side) | Solo tras el corte confirmado (no disponible aún — ver §5). |
|
|
21
|
+
|
|
22
|
+
El modo lo decide `TRYCORE_RUNTIME_MODE` (variable de entorno) o, si no está seteada,
|
|
23
|
+
`config/build-config.json#runtime.mode`. Un valor inválido o mal escrito (typo, mayúscula) **cae
|
|
24
|
+
siempre a `legacy`** — nunca hacia la red por error.
|
|
25
|
+
|
|
26
|
+
## 2. Requisitos antes de activar `dual`
|
|
27
|
+
|
|
28
|
+
- Un **Agent Orchestrator Runtime** corriendo y alcanzable por red (repo `trycore-ia-hub`) — esto
|
|
29
|
+
lo provee tu equipo de plataforma, no el arnés.
|
|
30
|
+
- Un **token de proyecto**, emitido por un **ADMIN** en la consola del hub (superficie humana; el
|
|
31
|
+
arnés no puede emitirse tokens a sí mismo).
|
|
32
|
+
- `python3` y `git` (ya son requisitos duros del arnés en cualquier modo).
|
|
33
|
+
|
|
34
|
+
## 3. Activar `dual` en un proyecto nuevo (`init`)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
trycore-build init \
|
|
38
|
+
--runtime-url "https://tu-runtime.example.com" \
|
|
39
|
+
--runtime-token "<token-del-ADMIN>" \
|
|
40
|
+
--runtime-mode dual
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Qué hace, en orden, y **fail-open en cada paso** (un runtime inalcanzable durante `init` nunca
|
|
44
|
+
aborta la instalación; degrada a `legacy` y todo se reintenta en la primera sesión de Claude):
|
|
45
|
+
|
|
46
|
+
1. Guarda las credenciales en `.claude/state/runtime.credentials` (`0600`, gitignored — nunca en
|
|
47
|
+
`settings.json` ni en el repo).
|
|
48
|
+
2. Registra el agente (`POST /agents/register`) con la versión del arnés y los `asset_types` que
|
|
49
|
+
el paquete declara (`asset-types.json`, sembrado en `.claude/`).
|
|
50
|
+
3. Escribe `runtime.mode: "dual"` en `.claude/config/build-config.json`.
|
|
51
|
+
4. Dispara el primer sync de contexto (`context-sync.sh`) — trae `config/`, `rules/` y
|
|
52
|
+
`docs-cache/` gobernados desde el servidor.
|
|
53
|
+
|
|
54
|
+
Si `--runtime-mode` no se especifica pero sí `--runtime-url`/`--runtime-token`, el default es
|
|
55
|
+
`dual` (nunca `runtime` — el corte directo a `runtime` sin pasar por `dual` no está soportado por
|
|
56
|
+
diseño: es exactamente el "big bang" que el plan de migración evita).
|
|
57
|
+
|
|
58
|
+
## 4. Activar `dual` en un proyecto ya instalado (`update`)
|
|
59
|
+
|
|
60
|
+
`trycore-build update` no acepta las flags de runtime directamente hoy — vuelve a correr `init` con
|
|
61
|
+
las mismas flags sobre el proyecto existente; es idempotente (no pisa `build-state.json` ni
|
|
62
|
+
`stack-allowlist.json`, solo añade las credenciales y el modo):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
trycore-build init --runtime-url "..." --runtime-token "..." --runtime-mode dual
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 5. Verificar que quedó bien
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
trycore-build doctor # requisitos + sección "Runtime": token, conectividad, lock, cola offline
|
|
72
|
+
trycore-build status # resumen + sección "Runtime": conexión, proyección, contexto sincronizado
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Dentro de una sesión de Claude, `/build:status` da el mismo informe (más `next-step` derivado) y
|
|
76
|
+
`/build:claim [EP-XXX]` reclama la siguiente tarea directamente contra el runtime.
|
|
77
|
+
|
|
78
|
+
## 6. Qué cambia en el día a día
|
|
79
|
+
|
|
80
|
+
**Nada que tengas que operar tú a mano.** Las skills (`building-a-slice`, `releasing-a-version`,
|
|
81
|
+
`managing-parallel-front`) conducen exactamente el mismo pipeline; por debajo, en vez de
|
|
82
|
+
leer/escribir `build-state.json` directamente, llaman a `slice-ops.sh`/`release-ops.sh` (13
|
|
83
|
+
subcomandos: `claim`, `gate`, `wiring`, `progress`, `checkpoint`, `submit`, `archive`, `fact`,
|
|
84
|
+
`propose-asset`, `status`, `escalate`, y del lado release `verdict`/`front-integration`) — nunca
|
|
85
|
+
arman peticiones a mano. En `dual`, cada transición también se **espeja** al servidor con la
|
|
86
|
+
identidad del fichero (que sigue mandando). Si el espejo falla (red caída, servidor rechaza), el
|
|
87
|
+
trabajo local **nunca se bloquea** — se avisa y queda como discrepancia para el comparador.
|
|
88
|
+
|
|
89
|
+
**El comparador dual** (`hooks/build/dual-compare.sh`, hook `Stop`) corre después de cada turno:
|
|
90
|
+
compara la proyección del servidor contra el fichero local (épica, fase, gates ya resueltos) y, si
|
|
91
|
+
divergen, lo escala vía `/build:escalate` — nunca bloquea el cierre de sesión. Es el instrumento
|
|
92
|
+
que mide si el piloto va "sin discrepancias" (§7).
|
|
93
|
+
|
|
94
|
+
## 7. Migrar el estado histórico
|
|
95
|
+
|
|
96
|
+
Si el proyecto ya tiene slices archivados/releases en `build-state.json` y quieres que el runtime
|
|
97
|
+
los conozca (proyecciones consultables, no solo lo que ocurra de aquí en adelante):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
trycore-build migrate --project-ref "<nombre-del-proyecto-en-el-hub>"
|
|
101
|
+
# escribe .claude/state/migration-bundle.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Esto **normaliza y valida en local** — nunca sube nada. Entrega el fichero resultante a un ADMIN
|
|
105
|
+
con la instrucción: *«súbelo en la consola del hub, pantalla de import histórico del proyecto»*. El
|
|
106
|
+
import es idempotente y reanuda si falló a medias; las entradas no mapeables se importan igual como
|
|
107
|
+
`legacy_imported` con el payload original — nada se pierde, nada bloquea.
|
|
108
|
+
|
|
109
|
+
## 8. Volver a `legacy` (rollback)
|
|
110
|
+
|
|
111
|
+
`dual` nunca dejó de escribir el fichero local — es la fuente primaria todo el tiempo. Para volver:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# edita .claude/config/build-config.json → "runtime": { "mode": "legacy" }
|
|
115
|
+
# o, para una sesión puntual sin editar el fichero:
|
|
116
|
+
TRYCORE_RUNTIME_MODE=legacy claude
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
No hay nada que deshacer del lado del fichero: nunca dejó de ser la verdad. Las credenciales
|
|
120
|
+
(`runtime.credentials`) pueden quedarse — en modo `legacy` el arnés no las toca.
|
|
121
|
+
|
|
122
|
+
## 9. Cuándo se corta a `runtime` (y qué NO hacer todavía)
|
|
123
|
+
|
|
124
|
+
El modo `runtime` puro (servidor como única fuente, sin espejo local) y el **retiro físico** de la
|
|
125
|
+
maquinaria legacy (`build-state.schema.json`, `hooks/build/lib/state-io.sh`, `src/lib/state-seed.ts`)
|
|
126
|
+
están condicionados a que un **piloto real** corra en `dual` durante **1 sprint sin discrepancias**
|
|
127
|
+
reportadas por `dual-compare.sh` (`plan-migracion-harness-v0.9.md` §2.3). Hasta entonces:
|
|
128
|
+
|
|
129
|
+
- No apagues `legacy` en ningún proyecto real.
|
|
130
|
+
- No trates `build-state.json`/`state-io.sh` como deprecados ni los borres.
|
|
131
|
+
- No reescribas METODOLOGIA/CLAUDE.md/`state/README.md` como si el runtime fuera canónico — eso
|
|
132
|
+
es, literalmente, declarar que el piloto ya pasó (la publicación de `0.9.0` en npm con el
|
|
133
|
+
cliente en beta/opt-in **no** es esa declaración; es la que hace falta para que el piloto
|
|
134
|
+
arranque).
|
|
135
|
+
|
|
136
|
+
Este documento se actualizará cuando el piloto confirme el corte.
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
> Repo: `trycore-spec-build-harness` (v0.8.1 → v0.9.0). Contraparte servidor: módulo `orchestrator` de **trycore-ia-hub** (PRD Agent Orchestrator Runtime v2.1, ADR-0012 del hub, épica EP-OR-08). Contrato de red: [protocolo-cliente-runtime.md](protocolo-cliente-runtime.md).
|
|
4
4
|
>
|
|
5
5
|
> **Principio rector:** la metodología no cambia — cambia el medio. Los dos loops, las fases, los gates y los veredictos son los mismos; lo que se moviliza es *dónde vive el estado* (del JSON local al runtime) y *quién valida las transiciones* (del prompt/honor system al servidor). Regla de corte: **hechos y transiciones de dominio → runtime · observación y enforcement del working tree → harness · protocolo de estado → cliente API.**
|
|
6
|
+
>
|
|
7
|
+
> **Estado del código (actualizado):** sub-slices **A** (cimiento de red), **B** (hooks cliente),
|
|
8
|
+
> **C** (skills/comandos sobre el runtime) y **D** (CLI `trycore-build`) — completos, mergeados en
|
|
9
|
+
> `main`. **E** (comparador dual + ratchets de la checklist §3, alcance acotado a lo aditivo/seguro)
|
|
10
|
+
> — completo, mergeado. **Publicado en npm como `0.9.0`** (beta/opt-in; `legacy` sigue siendo el
|
|
11
|
+
> default). **Lo que sigue pendiente y NO se ha hecho**: el piloto real en modo `dual` (§2, ítem
|
|
12
|
+
> 1-3 de abajo), el retiro físico de la maquinaria legacy y las enmiendas normativas de
|
|
13
|
+
> METODOLOGIA/CLAUDE.md/`state/README.md` que declararían el runtime canónico — todo eso depende
|
|
14
|
+
> de que el piloto corra un sprint sin discrepancias (§2.3) y será una release **posterior** a
|
|
15
|
+
> `0.9.0`, no esta. Guía de uso del código ya construido →
|
|
16
|
+
> [`guia-modo-dual-y-migracion.md`](guia-modo-dual-y-migracion.md).
|
|
6
17
|
|
|
7
18
|
## 1. Matriz de movilización por artefacto
|
|
8
19
|
|