@trycore/spec-build-harness 0.8.5 → 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.
Files changed (106) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +27 -4
  3. package/INSTALL.md +27 -5
  4. package/METODOLOGIA.md +55 -5
  5. package/README.md +39 -6
  6. package/VERSION +1 -1
  7. package/agents/build/build-orchestrator.md +33 -7
  8. package/agents/build/dor-dod-gatekeeper.md +13 -5
  9. package/agents/build/wiring-adversarial-verifier.md +52 -5
  10. package/commands/build/architect.md +1 -1
  11. package/commands/build/claim.md +46 -0
  12. package/commands/build/escalate.md +36 -0
  13. package/commands/build/front.md +9 -3
  14. package/commands/build/onboard.md +59 -14
  15. package/commands/build/prototype.md +3 -2
  16. package/commands/build/reflect.md +60 -40
  17. package/commands/build/release.md +10 -7
  18. package/commands/build/resume.md +33 -13
  19. package/commands/build/slice.md +32 -27
  20. package/commands/build/status.md +35 -0
  21. package/commands/build/work.md +11 -8
  22. package/config/build-config.template.json +4 -0
  23. package/dist/cli.js +22 -0
  24. package/dist/commands/doctor.js +42 -0
  25. package/dist/commands/init.js +84 -1
  26. package/dist/commands/migrate.js +48 -0
  27. package/dist/commands/status.js +34 -0
  28. package/dist/lib/normalize.js +276 -0
  29. package/dist/lib/paths.js +6 -0
  30. package/dist/lib/runtime-client.js +196 -0
  31. package/dist/lib/settings-merge.js +3 -3
  32. package/dist/lib/state-bundle.js +46 -0
  33. package/docs/commands.md +25 -8
  34. package/docs/getting-started.md +1 -0
  35. package/docs/hooks.md +114 -27
  36. package/docs/runtime/guia-modo-dual-y-migracion.md +136 -0
  37. package/docs/runtime/plan-migracion-harness-v0.9.md +11 -0
  38. package/docs/runtime/protocolo-cliente-runtime.md +109 -34
  39. package/hooks/build/build-gate-check.sh +21 -0
  40. package/hooks/build/context-monitor.sh +82 -15
  41. package/hooks/build/context-sync.sh +192 -0
  42. package/hooks/build/design-source-guard.sh +30 -2
  43. package/hooks/build/dual-compare.sh +92 -0
  44. package/hooks/build/event-emitter.sh +75 -0
  45. package/hooks/build/gitflow-guard.sh +164 -14
  46. package/hooks/build/heartbeat.sh +259 -0
  47. package/hooks/build/lib/agent-context.sh +139 -0
  48. package/hooks/build/lib/config.sh +27 -0
  49. package/hooks/build/lib/projection.sh +71 -0
  50. package/hooks/build/lib/runtime-client.sh +465 -0
  51. package/hooks/build/lib/runtime-ops.sh +221 -0
  52. package/hooks/build/lib/state-io.sh +5 -18
  53. package/hooks/build/load-build-state.sh +64 -2
  54. package/hooks/build/reflect-nudge.sh +15 -0
  55. package/hooks/build/release-gate-nudge.sh +15 -0
  56. package/hooks/build/release-ops.sh +164 -0
  57. package/hooks/build/scaffold-guard.sh +29 -2
  58. package/hooks/build/session-start.sh +103 -0
  59. package/hooks/build/session-stop.sh +22 -0
  60. package/hooks/build/slice-ops.sh +877 -0
  61. package/hooks/build/stack-guard.sh +8 -0
  62. package/hooks/build/statusline-bridge.sh +24 -3
  63. package/hooks/build-harness.json +16 -0
  64. package/package.json +3 -3
  65. package/scripts/check-agnostic.sh +3 -1
  66. package/scripts/check-pack-clean.sh +31 -0
  67. package/scripts/check-runtime-purity.sh +43 -0
  68. package/scripts/lib/front-plan.py +4 -0
  69. package/scripts/lib/graph-bundle.py +133 -0
  70. package/scripts/runtime-purity-allow.txt +5 -0
  71. package/scripts/smoke-test.sh +1 -1
  72. package/scripts/tests/lib/http-stub.py +46 -0
  73. package/scripts/tests/test-baseline-verdict.sh +92 -0
  74. package/scripts/tests/test-config.sh +25 -0
  75. package/scripts/tests/test-hooks-runtime.sh +853 -0
  76. package/scripts/tests/test-install.sh +57 -0
  77. package/scripts/tests/test-runtime-client.sh +298 -0
  78. package/scripts/tests/test-schema.sh +29 -1
  79. package/scripts/tests/test-skill-ops.sh +847 -0
  80. package/skills/building-a-micro-change/SKILL.md +22 -4
  81. package/skills/building-a-slice/SKILL.md +55 -21
  82. package/skills/building-a-slice/assets/baseline-verdict.sh +172 -0
  83. package/skills/building-a-slice/references/dod.md +12 -3
  84. package/skills/building-a-slice/references/dor.md +3 -2
  85. package/skills/building-a-slice/references/evidence-budget.md +51 -0
  86. package/skills/building-a-slice/references/exploration-fanout.md +1 -1
  87. package/skills/building-a-slice/references/gitflow.md +1 -1
  88. package/skills/building-a-slice/references/regression-baseline.md +67 -0
  89. package/skills/building-a-slice/references/runtime-protocol.md +75 -0
  90. package/skills/building-a-slice/references/state-protocol.md +12 -1
  91. package/skills/building-a-slice/workflows/README.md +7 -3
  92. package/skills/building-a-slice/workflows/explore-fanout.workflow.js +3 -3
  93. package/skills/building-a-slice/workflows/wiring-verify.workflow.js +26 -4
  94. package/skills/managing-parallel-front/SKILL.md +32 -16
  95. package/skills/openspec-archive-change/SKILL.md +15 -0
  96. package/skills/prototyping-screens/SKILL.md +9 -5
  97. package/skills/releasing-a-version/SKILL.md +25 -16
  98. package/skills/releasing-a-version/references/release-dod.md +7 -5
  99. package/skills/releasing-a-version/workflows/README.md +2 -1
  100. package/skills/releasing-a-version/workflows/release-gate.workflow.js +6 -5
  101. package/skills/setup-architecture/SKILL.md +4 -2
  102. package/state/README.md +16 -1
  103. package/state/build-state.schema.json +2 -1
  104. package/templates/CLAUDE.md.template +16 -0
  105. package/templates/settings-hooks.template.json +8 -4
  106. package/internal/skills/auditar-arnes/SKILL.md +0 -29
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "BUILD: Front"
3
- description: Abre y coordina un front paralelo de épicas NO fundacionales y disjuntas en archivos, cada una en su worktree/rama/PR. Delega en la skill managing-parallel-front (selección disjunta vía scripts/lib/front-plan.py, worktrees, merge en orden con re-smoke).
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.
4
4
  category: Workflow
5
5
  tags: [build-harness, outer-loop, front-paralelo, trycore]
6
6
  ---
@@ -8,8 +8,14 @@ tags: [build-harness, outer-loop, front-paralelo, trycore]
8
8
  # /build:front — Front paralelo inter-épica
9
9
 
10
10
  Delega en la skill **managing-parallel-front**. Resumen:
11
- 1. Verifica precondiciones (scaffold confirmado; sin épica foundational abierta).
11
+ 1. Verifica precondiciones (scaffold confirmado; sin épica foundational abierta) con
12
+ `bash .claude/hooks/build/slice-ops.sh status`.
12
13
  2. Reúne candidatas no fundacionales listas (DoR pasado) con `layer` y `files_scope`.
13
- 3. Selecciona el conjunto disjunto (`scripts/lib/front-plan.py`), abre worktrees, construye y mergea en orden con re-smoke.
14
+ 3. Propón el conjunto disjunto y el `merge_order` (`scripts/lib/front-plan.py`, local) y
15
+ **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 registrado como **agente propio**
17
+ (`trycore-build init` con el mismo token de proyecto); construye con `/build:slice`.
18
+ 5. Tras cada merge, re-smoke y reporte desde el worktree:
19
+ `bash .claude/hooks/build/release-ops.sh front-integration <front_id> --merge-status … --resmoke …`.
14
20
 
15
21
  Úsalo solo cuando haya ≥2 épicas no fundacionales disjuntas listas. Para una sola épica, usa `/build:slice`.
@@ -68,21 +68,21 @@ Voy a leer tu PRD/openspec para proponer valores y confirmar contigo:
68
68
  visual de verdad (prototipo, export de diseño o mockups) y cómo localizar cada pantalla; si no,
69
69
  "N/A". Si hay UI pero **no existe** fuente aún, indica que `/build:prototype` puede generarla
70
70
  tras el onboarding (prototipo HTML de referencia en `docs/05-prototipo/`; la confirmación
71
- sigue siendo humana). (La escritura del estado `design_source` en `build-state.json` se hace
72
- en la Fase 3c.)
71
+ sigue siendo humana). (El reporte del hecho `design_source` se hace en la Fase 3c.)
73
72
  3. Si un punto no aplica al proyecto, registra explícitamente "no aplica" (no lo dejes como `{{...}}`).
74
73
 
75
74
  ---
76
75
 
77
76
  ## Fase 2b: Clasificar la capa de las épicas (cimiento vs negocio)
78
77
 
79
- **Primero, resuelve `project_kind`** (lee `.claude/state/build-state.json`):
78
+ **Primero, resuelve `project_kind`** (léelo de `bash .claude/hooks/build/slice-ops.sh status`):
80
79
  - `"brownfield"` → el mecanismo del caparazón es **N/A**: no preguntes ni exijas nada de la
81
80
  Fase 2c; clasifica capas como siempre y sigue.
82
81
  - `null` / ausente (detección ambigua del CLI) → pregunta **una sola vez** vía AskUserQuestion:
83
82
  *"¿Este proyecto es una app nueva (greenfield: el caparazón — menús, layout, homepage, login,
84
- redirecciones — aún no existe) o ya construida (brownfield)?"*. Escribe `project_kind` y
85
- `project_kind_source: "human"` en el estado (valida contra el schema tras escribir).
83
+ redirecciones — aún no existe) o ya construida (brownfield)?"*. Repórtalo:
84
+ `bash .claude/hooks/build/slice-ops.sh fact project-kind <greenfield|brownfield> --source human`
85
+ (rc 3 = modo legacy: escríbelo en el fichero con su protocolo).
86
86
  - `"greenfield"` → tras clasificar capas (abajo), continúa a la **Fase 2c**.
87
87
 
88
88
  El factor que más reduce el consumo de contexto por slice es que el **cimiento** ya esté construido y
@@ -124,11 +124,16 @@ Si `project_kind !== "greenfield"`, salta esta fase (N/A total).
124
124
  - **Rechaza** → **STOP** de la fase: deja `foundation.epic: null`, instruye crearla en
125
125
  discovery (`/trycore:*`) con la checklist como alcance. El gate del DoR bloqueará las
126
126
  épicas de negocio igual hasta que exista y se archive.
127
- 3. **Persistir en el estado**: escribe `foundation.required: true`, `foundation.checklist[]`
128
- (todos los ítems con su `applies`, `evidence: ""`), `foundation.epic` (o `null`). Escribe
129
- **solo** los campos del schema (`foundation` es `additionalProperties: false`) y **valida
130
- contra `build-state.schema.json` tras escribir** (aborta si no valida). Una transición = una
131
- escritura.
127
+ 3. **Reportar el hecho**: escribe la checklist podada en un fichero temporal
128
+ (`[{"id","applies","evidence":""}, …]`) y repórtala en una sola transición:
129
+
130
+ ```bash
131
+ bash .claude/hooks/build/slice-ops.sh fact foundation \
132
+ --required true --epic EP-XXX --checklist-file /tmp/foundation-checklist.json
133
+ ```
134
+
135
+ `--required false` cuando el humano podó todos los ítems (no hay caparazón exigible).
136
+ En modo legacy (rc 3) se escribe en el fichero con su protocolo.
132
137
 
133
138
  ---
134
139
 
@@ -167,16 +172,22 @@ Si el usuario lo desea y existe `package.json` en el proyecto:
167
172
 
168
173
  ## Fase 3c: (Si hay UI) Confirmar la fuente de diseño en el estado
169
174
 
170
- Espejo de la confirmación de scaffold, para `design_source` en `build-state.json`:
175
+ Espejo de la confirmación de scaffold, para el hecho `design_source`:
171
176
  - Si el producto **tiene UI**: setea `design_source.applies=true`, `source` (el puntero confirmado) y
172
177
  `confirmed=true` **solo si** el usuario confirma que la fuente de diseño existe (con `confirmed_by`,
173
178
  `confirmed_at`). El prototipo puede generarse con `/build:prototype` (skill `prototyping-screens`);
174
179
  `confirmed` sigue siendo exclusivamente humano.
175
180
  - Si **no hay UI**: setea `design_source.applies=false` (el mecanismo de fidelidad queda N/A).
176
181
 
177
- Escribe **solo** los campos del schema (`applies`, `source`, `confirmed`, `confirmed_by`, `confirmed_at`,
178
- `notes`; el objeto es `additionalProperties:false`) y **valida contra `build-state.schema.json` tras escribir**
179
- (aborta si no valida). Guardarraíl: **una transición = una escritura**; no toques otros campos del estado.
182
+ Repórtalo en una sola transición:
183
+
184
+ ```bash
185
+ bash .claude/hooks/build/slice-ops.sh fact design-source \
186
+ --applies true --source "<ruta/URL confirmada>" --confirmed true --by "<quién confirmó>"
187
+ ```
188
+
189
+ Sin UI: `--applies false` (el mecanismo de fidelidad queda N/A). Guardarraíl: **una transición =
190
+ una llamada**; `--confirmed true` exige `--by`: confirmar es acto humano.
180
191
 
181
192
  ---
182
193
 
@@ -225,6 +236,40 @@ CLAUDE.md actualizado. Auto-memory escrita. Los agentes de calidad ya leen tu do
225
236
 
226
237
  ---
227
238
 
239
+ ## Fase 5: (Con runtime) Preparar el bundle de grafo para el ADMIN
240
+
241
+ El grafo del backlog (épicas, capas, `files_scope`, dependencias, historias, líneas de release) vive
242
+ en el runtime. **Importarlo es un acto de gobierno**: la superficie exige sesión de ADMIN y rechaza
243
+ el token de agente. El arnés **prepara y valida**; una persona sube.
244
+
245
+ 1. Extrae el grafo de los artefactos de discovery ya leídos (`docs/03-backlog/epicas.md`,
246
+ `docs/02-user-story-map/`, `docs/04-historias/`) a un JSON:
247
+
248
+ ```json
249
+ {"project_ref": "<nombre del proyecto>",
250
+ "epics": [{"code": "EP-001", "title": "…", "layer": "foundational",
251
+ "files_scope": ["src/core/**"], "depends_on": [],
252
+ "stories": [{"id": "HU-001", "title": "…"}]}],
253
+ "release_lines": [{"id": "R1-mvp", "epics": ["EP-001"]}]}
254
+ ```
255
+
256
+ 2. Normalízalo y valídalo (determinista, nunca sube nada):
257
+
258
+ ```bash
259
+ python3 .claude/scripts/lib/graph-bundle.py < /tmp/epics.json > /tmp/graph-bundle.json
260
+ ```
261
+
262
+ 3. **Lee los avisos** (`warnings` del bundle y stderr): capa ausente, dependencia inexistente,
263
+ ciclo, línea de release que referencia una épica desconocida. Corrígelos en discovery — el arnés
264
+ **no inventa** el grafo ni edita `epicas.md` (salvo el carve-out de la épica caparazón, Fase 2c).
265
+
266
+ 4. **Entrega el fichero a un ADMIN** con esta instrucción literal: *«súbelo en la consola del hub,
267
+ pantalla de import del proyecto»*. El import es idempotente y **reanuda** si falló a medias.
268
+
269
+ 5. Modo `legacy` o sin credenciales: salta esta fase; el grafo sigue viviendo en los documentos.
270
+
271
+ ---
272
+
228
273
  ## Guardrails
229
274
 
230
275
  - No avances sin confirmar los 7 puntos. Si el usuario omite alguno, repregunta o marca "no aplica".
@@ -18,5 +18,6 @@ Delega en la skill **prototyping-screens**. Uso:
18
18
 
19
19
  Es **outer-loop**: córrelo antes de abrir slices (como `/build:architect`). Produce
20
20
  `docs/05-prototipo/` (DESIGN.md, tokens.css, manifest.json, pantallas/) y, en greenfield con
21
- aprobación humana, registra `design_source` en `build-state.json`. **La skill genera; el humano
22
- aprueba**: `design_source.confirmed` y el `estado: "aprobada"` del manifest nunca se auto-marcan.
21
+ aprobación humana, reporta el hecho `design_source` al runtime
22
+ (`slice-ops.sh fact design-source …`). **La skill genera; el humano aprueba**: la confirmación de la
23
+ fuente de diseño y el `estado: "aprobada"` del manifest nunca se auto-marcan.
@@ -33,12 +33,41 @@ Stop aquí si no está instalado o si falta `python3`.
33
33
 
34
34
  ## Fase 1: Identificar slices sin reflexionar
35
35
 
36
- Lee `.claude/state/build-state.json` y filtra las entradas de `history[]` con `reflected != true`:
36
+ El origen del dato depende del **modo** (igual que consume `reflect-nudge.sh`): en `runtime` puro el
37
+ fichero local NO se lee — puede faltar o quedar fósil (`runtime-protocol.md`) — y el cómputo lo hace el
38
+ servidor sobre **todo el proyecto**; en `legacy`/`dual` el fichero sigue siendo primario.
37
39
 
38
40
  ```bash
39
- python3 - <<'PY'
41
+ MODE="$(bash .claude/hooks/build/slice-ops.sh mode)"
42
+ if [ "$MODE" = "runtime" ]; then
43
+ # runtime: NO se lee el fichero legacy (fósil/ausente). El servidor ya agregó el nudge
44
+ # "reflect" sobre todo el proyecto; lo leemos de la línea `nudges:` de `status`, el mismo
45
+ # dato que consume reflect-nudge.sh.
46
+ bash .claude/hooks/build/slice-ops.sh status | python3 - <<'PY'
47
+ import json, sys
48
+ nudges = []
49
+ for line in sys.stdin:
50
+ if line.startswith("nudges:"):
51
+ try:
52
+ nudges = json.loads(line.split(":", 1)[1].strip())
53
+ except Exception:
54
+ nudges = []
55
+ break
56
+ pend = [n.get("message") for n in nudges
57
+ if isinstance(n, dict) and n.get("kind") == "reflect" and n.get("message")]
58
+ if pend:
59
+ for m in pend:
60
+ print(m)
61
+ print("TOTAL_RUNTIME", len(pend))
62
+ else:
63
+ print("TOTAL 0")
64
+ PY
65
+ else
66
+ # legacy o dual: el fichero es primario en ambos modos (en dual, reconcile-build-state.py
67
+ # lo mantiene sincronizado — runtime-protocol.md); filtra `history[]` con `reflected != true`.
68
+ python3 - <<'PY'
40
69
  import json, os, sys
41
- p = ".claude/state/build-state.json"
70
+ p = ".claude/state/build-state.json" # legacy: ruta local, no runtime
42
71
  if not os.path.exists(p):
43
72
  print("NO_STATE"); sys.exit(0) # estado ausente → nada que reflexionar, salir
44
73
  try:
@@ -50,13 +79,21 @@ for h in pend:
50
79
  print(h.get("epica"), "·", h.get("openspec_change"), "·", h.get("branch"), "·", ",".join(h.get("hus") or []))
51
80
  print("TOTAL", len(pend))
52
81
  PY
82
+ fi
53
83
  ```
54
84
 
55
- **Si `NO_STATE`:** no hay estado → nada que reflexionar; termina. **Si `CORRUPT_STATE`:** el estado está
56
- corrupto → repórtalo y **detente sin escribir** (no estampes ni edites). **Si `TOTAL 0`:** informa "No hay
57
- slices pendientes de reflexión ✅" y termina. No inventes trabajo.
85
+ **Si `NO_STATE`** (solo aplica en `legacy`/`dual`): no hay estado → nada que reflexionar; termina.
86
+ **Si `CORRUPT_STATE`:** el estado está corrupto → repórtalo y **detente sin escribir** (no estampes ni
87
+ edites). **Si `TOTAL 0`:** informa "No hay slices pendientes de reflexión ✅" y termina. No inventes
88
+ trabajo.
89
+
90
+ **En `runtime`** el servidor solo entrega el **conteo agregado** (`TOTAL_RUNTIME`), sin `epica`/
91
+ `openspec_change`/`branch` por slice (el cliente no puede enumerarlos hoy). Si `TOTAL_RUNTIME > 0`,
92
+ dile al humano cuántos hay pendientes según el runtime y **pregúntale con AskUserQuestion** qué slice
93
+ (`EP-XXX` + `openspec_change`) reflexionar primero; no adivines ni asumas "el más reciente" sin ese dato.
58
94
 
59
- Si hay varios, procésalos **de uno en uno** (el más reciente primero), o pregunta al usuario cuál.
95
+ Si tienes el detalle por slice (`legacy`/`dual`) y hay varios, procésalos **de uno en uno** (el más
96
+ reciente primero), o pregunta al usuario cuál.
60
97
 
61
98
  ---
62
99
 
@@ -119,42 +156,25 @@ Para las viñetas aprobadas, **inserta** (no reemplaces) dentro del bloque marca
119
156
 
120
157
  ## Fase 5: Estampar el slice como reflexionado
121
158
 
122
- Marca el/los slice(s) procesado(s) en `history[]` para que el nudge calle. Usa la hora UTC real:
159
+ El estampado es una transición de dominio más: la hace el ejecutable, no un `json.dump` a mano.
123
160
 
124
161
  ```bash
125
- NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
126
- python3 - "$NOW" "<openspec_change>" <<'PY'
127
- import json, sys, os, tempfile
128
- now, change = sys.argv[1], sys.argv[2]
129
- p = ".claude/state/build-state.json"
130
- try:
131
- d = json.load(open(p))
132
- except Exception as e:
133
- print("ABORT: estado ilegible, no estampo:", e); sys.exit(1)
134
- for h in d.get("history") or []:
135
- if isinstance(h, dict) and h.get("openspec_change") == change:
136
- h["reflected"] = True
137
- h["reflected_at"] = now
138
- # Validación contra el schema ANTES de persistir (si jsonschema está disponible); aborta si no valida.
139
- try:
140
- import jsonschema
141
- schema = json.load(open(".claude/state/build-state.schema.json"))
142
- jsonschema.Draft202012Validator(schema).validate(d)
143
- except ImportError:
144
- pass # sin jsonschema: se omite la validación profunda (no se relaja la escritura atómica)
145
- except Exception as e:
146
- print("ABORT: el estado modificado NO valida contra el schema, no escribo:", e); sys.exit(1)
147
- # Escritura ATÓMICA (una transición = una escritura): tmp + os.replace.
148
- fd, tmp = tempfile.mkstemp(dir=os.path.dirname(p) or ".", prefix=".build-state.", suffix=".tmp")
149
- with os.fdopen(fd, "w") as out:
150
- json.dump(d, out, indent=2, ensure_ascii=False)
151
- os.replace(tmp, p)
152
- print("estampado:", change)
153
- PY
162
+ MODE="$(bash .claude/hooks/build/slice-ops.sh mode)"
163
+ if [ "$MODE" = "legacy" ]; then
164
+ # Modo legacy (v0.8.5): se estampa `reflected`/`reflected_at` en la entrada de history[]
165
+ # del fichero, con escritura atómica y validación contra el schema local. Protocolo en
166
+ # skills/building-a-slice/references/state-protocol.md.
167
+ echo "legacy: estampa reflected=true en la entrada de history[] del fichero"
168
+ else
169
+ bash .claude/hooks/build/slice-ops.sh progress \
170
+ "reflexión completada de <EP-XXX> (<openspec_change>): <N> aprendizajes aplicados"
171
+ bash .claude/hooks/build/slice-ops.sh fact harness-phase building >/dev/null 2>&1 || true
172
+ fi
154
173
  ```
155
174
 
156
- Estampa **aunque no haya habido aprendizajes** (reflexionar y no encontrar nada también cierra el
157
- ciclo). Repite por cada slice procesado.
175
+ En `dual`/`runtime` el hecho «este slice ya se reflexionó» vive en el event stream del runtime:
176
+ el nudge de `reflect-nudge.sh` lo computa el servidor sobre **todo** el proyecto, no sobre este
177
+ clon (por eso el nudge dejó de mentir con 31/33 slices sin estampar).
158
178
 
159
179
  ---
160
180
 
@@ -165,7 +185,7 @@ ciclo). Repite por cada slice procesado.
165
185
 
166
186
  Slice: <EP-XXX> (<openspec_change>)
167
187
  Aprendizajes: <N agregados a CLAUDE.md> · <M a auto-memory> · <0 si solo se estampó>
168
- Estampado: reflected=true
188
+ Estampado: reportado al runtime (o reflected=true en modo legacy)
169
189
 
170
190
  Pendientes de reflexión restantes: <K>
171
191
  ```
@@ -27,9 +27,9 @@ Stop si no está instalado o falta `python3`.
27
27
 
28
28
  ## 2. Identificar la release y sus épicas
29
29
 
30
- Lee `build-state.json` y cruza con `docs/02-user-story-map/` para resolver qué épicas componen la release.
31
- Crea/actualiza la entrada en `releases[]` con `status: pending` (lo escribe `releasing-a-version`, única
32
- escritora de `releases[]`).
30
+ Consulta el estado con `bash .claude/hooks/build/slice-ops.sh status` y cruza con
31
+ `docs/02-user-story-map/` para resolver qué épicas componen la release. No hay entrada que crear: los
32
+ veredictos que reporte `releasing-a-version` pueblan la línea de release en el runtime.
33
33
 
34
34
  ---
35
35
 
@@ -67,10 +67,12 @@ reviewer. Es el gate **no negociable**: sin él, no hay release.
67
67
 
68
68
  ## 6. Síntesis y escritura
69
69
 
70
- - Todos los gates ✓ (o `null` cuando N/A) **y** `integration` → `releases[].status: passed`; escribe
71
- `gates` y `updated_by: releasing-a-version` (valida contra `build-state.schema.json` antes de persistir;
72
- una escritura por entrada). **Parciales no promueven a `passed`.**
73
- - Algo `status: failed` con los hallazgos bloqueantes; el humano los corrige **como un slice normal**
70
+ - Reporta **cada** gate medido con
71
+ `bash .claude/hooks/build/release-ops.sh verdict <line> <gate> <pass|fail|na> --evidence-file f`
72
+ (`na` cuando no aplica). El **agregado** lo hace el runtime: **parciales no promueven a `passed`**.
73
+ - Con los seis reportados y en verde, recuérdale al humano que **el cierre es suyo**
74
+ (`release-ops.sh close-hint <line>`): el agente no cierra releases.
75
+ - Algo ✗ → los hallazgos bloqueantes se corrigen **como un slice normal**
74
76
  (`/build:slice` / `building-a-slice`) y se **re-corre** el Release Gate.
75
77
 
76
78
  ---
@@ -81,4 +83,5 @@ reviewer. Es el gate **no negociable**: sin él, no hay release.
81
83
  vez por release (`O(releases)`).
82
84
  - **No dupliques el inner loop**: no se hace TDD ni se cierran gates por slice.
83
85
  - **`integration` con deps reales es obligatorio**; no se acepta con todo stubbeado.
86
+ - **El agente reporta, el humano cierra** (spec §2): ninguna llamada de cierre con token de agente.
84
87
  - Delega en `releasing-a-version`; **no** la reimplementa. Si algo contradice `METODOLOGIA.md`, gana la metodología.
@@ -1,29 +1,49 @@
1
1
  ---
2
2
  name: "BUILD: Resume"
3
- description: Rehidrata el slice activo desde disco tras un reinicio de contexto (reconcilia estado, muestra continuidad y la siguiente acción).
3
+ description: Rehidrata el slice activo tras un reinicio de contexto — refresca la proyección desde el runtime (o reconcilia el fichero en modo legacy), muestra la continuidad y la siguiente acción determinista.
4
4
  category: Workflow
5
5
  tags: [build-harness, resume, rehydrate, trycore]
6
6
  ---
7
7
 
8
8
  # /build:resume — Retomar sin pérdida
9
9
 
10
- Objetivo: reconstruir el contexto de construcción **desde disco**, no desde la conversación.
10
+ Objetivo: reconstruir el contexto de construcción **desde fuera de la conversación** — del
11
+ runtime en `dual`/`runtime`, del disco en `legacy`.
11
12
 
12
13
  ## Pasos
13
14
 
14
- 1. **Reconciliar**: `python3 .claude/hooks/build/reconcile-build-state.py .claude/state/build-state.json`
15
- (degrada wiring sin evidencia; anota branch drift; fail-open).
15
+ 1. **Averigua el modo y refresca**:
16
16
 
17
- 2. **Leer estado**: `.claude/state/build-state.json`. Extraer `active_slice`, sus `gates`, `wiring_checklist`, `progress_log`, `session_continuity`, y `parallel_front` si existe.
17
+ ```bash
18
+ MODE="$(bash .claude/hooks/build/slice-ops.sh mode)"
19
+ bash .claude/hooks/build/slice-ops.sh status
20
+ ```
18
21
 
19
- 3. **Determinar la siguiente acción por prioridad** (la primera que aplique):
20
- 1. `session_continuity.resume_hint` presente ejecutarla.
21
- 2. Items de `wiring_checklist` en `failing` → cablear el primero (con prueba real; no marcar passing sin evidencia).
22
- 3. `sub_slices` con `status!=done` construir el siguiente.
23
- 4. Según `active_slice.phase` → continuar el pipeline (delegar en la skill `building-a-slice`).
22
+ - `legacy` reconcilia el fichero como en v0.8.5: `python3 .claude/hooks/build/reconcile-build-state.py .claude/state/build-state.json`
23
+ (degrada wiring sin evidencia; anota branch drift; fail-open) y sigue
24
+ `building-a-slice/references/state-protocol.md`.
25
+ - `dual|runtime` `status` ya trae slice, fase, gates, wiring `failing`, contexto, lease y
26
+ cola pendiente.
24
27
 
25
- 4. **Si hay `parallel_front`**: delegar en la skill `managing-parallel-front` (Task 12+).
28
+ 2. **Determina la siguiente acción**:
26
29
 
27
- 5. Registrar un hito en `progress_log[]` (`by: /build:resume`).
30
+ ```bash
31
+ bash .claude/hooks/build/slice-ops.sh next-step
32
+ ```
28
33
 
29
- **Regla dura:** mientras quede un item `failing`, el slice NO está terminado. Nunca marques `passing` sin evidencia de ejecución real.
34
+ La prioridad es determinista y la calcula el cliente: `resume_hint` items de wiring en
35
+ `failing` → sub-slice pendiente → primer gate abierto por fase. **Ejecútala.**
36
+
37
+ 3. **Si el `status` muestra `⚠ lease` (lease perdido)**: haz `slice-ops.sh checkpoint --note …`,
38
+ **detén** el trabajo y vuelve a reclamar (`slice-ops.sh claim`) antes de cerrar nada más.
39
+
40
+ 4. **Si el `status` muestra la proyección `⚠ stale`** (sin refrescar) y el runtime no responde:
41
+ sigue trabajando con lo que hay — offline nunca bloquea — y no cierres gates a ciegas.
42
+
43
+ 5. **Front paralelo**: si este worktree pertenece a un front, delega en la skill
44
+ `managing-parallel-front`.
45
+
46
+ 6. Deja el hito: `bash .claude/hooks/build/slice-ops.sh progress "retomado por /build:resume: <qué sigue>"`.
47
+
48
+ **Regla dura:** mientras quede un item `failing`, el slice NO está terminado. Nunca marques
49
+ `passing` sin evidencia de ejecución real (`--evidence-file`).
@@ -29,25 +29,20 @@ Stop si no está instalado o falta `python3`.
29
29
  ## 2. Leer el estado y decidir punto de entrada
30
30
 
31
31
  ```bash
32
- python3 - <<'PY'
33
- import json, os, sys
34
- p = ".claude/state/build-state.json"
35
- if not os.path.exists(p): print("NO_STATE"); sys.exit(0)
36
- try: d = json.load(open(p))
37
- except Exception as e: print("CORRUPT_STATE", e); sys.exit(0)
38
- s = d.get("active_slice")
39
- if not s:
40
- print("START dor")
41
- else:
42
- g = s.get("gates", {})
43
- abierto = next((k for k, v in g.items() if v is False), None)
44
- print(f"RESUME {s.get('epica')} fase={s.get('phase')} primer_gate_abierto={abierto}")
45
- PY
32
+ MODE="$(bash .claude/hooks/build/slice-ops.sh mode)"
33
+ bash .claude/hooks/build/slice-ops.sh status
46
34
  ```
47
35
 
48
- - `NO_STATE`/`CORRUPT_STATE` → reporta y detente (no escribas).
49
- - `START dor` no hay slice activo: arranca en **dor** con la épica objetivo.
50
- - `RESUME …` ya hay un slice activo: **reanuda en su primer gate abierto** (no abras otro: el modelo es
36
+ - `MODE=legacy` → el fichero es primario: sigue `building-a-slice/references/state-protocol.md`
37
+ (comportamiento v0.8.5 exacto, sin red).
38
+ - `MODE=dual|runtime`el estado vive en el runtime
39
+ (`building-a-slice/references/runtime-protocol.md`).
40
+ - **Sin slice activo** (`slice: ninguno reclamado`): en `runtime`, recláma­lo con
41
+ `bash .claude/hooks/build/slice-ops.sh claim --epic EP-XXX` — `rc 7` = sin trabajo disponible,
42
+ `rc 6` = rechazo del servidor (muestra su razón, no reintentes). En `legacy|dual` (rc 3) abre el
43
+ slice por **dor** con el protocolo del fichero.
44
+ - **Con slice activo** → **reanuda donde toca**:
45
+ `bash .claude/hooks/build/slice-ops.sh next-step` (no abras otro: el modelo es
51
46
  secuencial, un solo slice activo).
52
47
 
53
48
  ---
@@ -56,14 +51,19 @@ PY
56
51
 
57
52
  Antes de escribir código de slice, verifica los gates de proyecto:
58
53
 
59
- - `scaffold.confirmed` debe ser `true`. Si es `false` **STOP**: delega en la **Fase 0** de `building-a-slice`
60
- (pregunta explícita; el arnés **no genera** el scaffold). No abras el slice.
61
- - Si el proyecto tiene UI, `design_source.confirmed` debe ser `true` (Fase 0-bis). Si no → **STOP** igual.
62
- - **Caparazón (solo greenfield)**: si `project_kind === "greenfield"` y `foundation.required === true`
63
- y la épica objetivo es `layer: business` mientras `foundation.completed_at` no esté estampado (la
64
- caparazón `foundation.epic` archivada con evidencia) **STOP**: solo la épica caparazón
65
- (`foundation.epic`) u otra fundacional puede abrir. Lo valida en detalle el `dor-dod-gatekeeper`
66
- (criterio 7-bis). Brownfield N/A, no preguntes.
54
+ Los tres hechos salen de la línea `hechos:` que imprime `slice-ops.sh status` (`scaffold_confirmed`,
55
+ `design_source_confirmed`, `project_kind`, `foundation_done` del runtime en `dual`/`runtime`; del
56
+ fichero en `legacy`):
57
+
58
+ - **scaffold** confirmado (`scaffold_confirmed`). Si no **STOP**: delega en la **Fase 0** de
59
+ `building-a-slice` (pregunta explícita; el arnés **no genera** el scaffold). No abras el slice.
60
+ - Si el proyecto tiene UI, **fuente de diseño** confirmada (`design_source_confirmed`, Fase 0-bis). Si
61
+ no**STOP** igual.
62
+ - **Caparazón (solo greenfield)**: si `project_kind` es `greenfield` y `foundation_done` es `false`, y
63
+ la épica objetivo es `layer: business` → **STOP**: solo la épica caparazón u otra fundacional puede
64
+ abrir. `status` no identifica **cuál** es la épica caparazón (eso vive hoy en el fichero legacy,
65
+ leído directo por `dor-dod-gatekeeper`); pregunta o consúltalo ahí. Lo valida en detalle el
66
+ `dor-dod-gatekeeper` (criterio 7-bis). Brownfield → N/A, no preguntes.
67
67
 
68
68
  El hook `scaffold-guard.sh` respalda esto en tiempo real.
69
69
 
@@ -91,8 +91,13 @@ archivada, recuerda el default del Release Gate (ver `/build:release`).
91
91
 
92
92
  ## Guardrails
93
93
 
94
- - **Un solo slice activo** (secuencial). No abras un segundo mientras haya `active_slice`.
95
- - **Orden estricto de gates**; un gate no se salta. `dod` exige `wiring_verified: true`.
94
+ - **Un solo slice activo** (secuencial). No abras un segundo mientras haya uno reclamado; en
95
+ `runtime` el propio claim es idempotente y devuelve el mismo slice con el lease vigente.
96
+ - **Todo acto de dominio pasa por `slice-ops.sh`**; en modo `runtime` no se escribe el fichero local.
97
+ - **Orden estricto de gates**; un gate no se salta. `dod` exige `wiring_verified: true`. El bucle
98
+ adversarial tiene condición de parada: **máximo 2 pasadas completas por slice** (pasadas 2+
99
+ incrementales por `verified_at_sha`; lo diferido se declara en el PR y lo custodia el Release
100
+ Gate — ver METODOLOGIA §1-bis).
96
101
  - **Sin scaffold confirmado, no hay slice** (el arnés lo exige pero no lo genera).
97
102
  - **Agnóstico**: este comando no asume dominio; lo específico entra por `/build:onboard` y `stack-allowlist.json`.
98
103
  - Si algo contradice `METODOLOGIA.md`, **gana la metodología**.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: "BUILD: Status"
3
+ description: Informe del agente y su trabajo — modo, proyecto, slice activo, gates, wiring failing, versión de contexto, lease y cola de eventos pendientes. Solo lectura, nunca bloquea. Adaptador delgado sobre slice-ops.sh status.
4
+ category: Workflow
5
+ tags: [build-harness, runtime, status, trycore]
6
+ ---
7
+
8
+ # /build:status — ¿Dónde estamos?
9
+
10
+ ## 1. Informe
11
+
12
+ ```bash
13
+ bash .claude/hooks/build/slice-ops.sh status
14
+ ```
15
+
16
+ Refresca la proyección desde `/agent/context` y muestra: **modo**, proyecto y agente, slice activo
17
+ con fase/gates/wiring, rama y change, versión de contexto (`✓ vN` o `⚠ stale`), lease y número de
18
+ eventos pendientes en la cola. En modo `legacy` dice que la fuente de verdad es el fichero local y
19
+ no consulta nada.
20
+
21
+ Con `--no-refresh` no toca la red (útil sin conexión).
22
+
23
+ ## 2. Leer las señales
24
+
25
+ - `⚠ stale` → la proyección no se pudo refrescar: los guards siguen operando con lo último
26
+ sincronizado. **No cierres gates a ciegas**; reintenta cuando vuelva el runtime.
27
+ - `⚠ lease perdido` → haz `slice-ops.sh checkpoint --note …`, detén el trabajo y vuelve a reclamar.
28
+ - `cola: N evento(s)` con N creciendo y sin bajar → el daemon no está despachando; revisa
29
+ `.claude/state/heartbeat-status.json` (y, en el sub-slice D, `trycore-build doctor`).
30
+
31
+ ## Guardrails
32
+
33
+ - **Solo lectura**: este comando no transiciona nada.
34
+ - El estado de **la flota** (otros agentes, otros slices, releases y fronts del proyecto) vive en la
35
+ consola del hub: es superficie humana, no de agente.
@@ -6,7 +6,7 @@ tags: [build-harness, router, classify-and-act, trycore]
6
6
  ---
7
7
 
8
8
  **Router puro.** Decide *qué carril* aplica y **delega** en la skill correspondiente. NO ejecuta el pipeline,
9
- NO escribe `build-state.json`, NO crea ramas. Codifica como **ruteo** (no política nueva) el *decision gate*
9
+ NO transiciona el estado del slice, NO crea ramas. Codifica como **ruteo** (no política nueva) el *decision gate*
10
10
  del micro-change y el default del Release Gate de `METODOLOGIA.md` (§2, §4). Si algo contradice la metodología,
11
11
  **gana la metodología**.
12
12
 
@@ -28,12 +28,14 @@ Si `NOT_INSTALLED` → ejecuta `trycore-build init` y vuelve.
28
28
 
29
29
  Aplica las reglas en orden:
30
30
 
31
- 0. **¿Greenfield sin caparazón construido?** — si `build-state.json` tiene
32
- `project_kind: "greenfield"`, `foundation.required: true` y `foundation.completed_at` sin
33
- estampar → **enruta directo a construir la épica caparazón**
34
- (`foundation.epic`) por el carril **`building-a-slice`**. Si `foundation.epic` es `null`,
35
- enruta a `/build:onboard` (Fase 2c) o a discovery para definirla. Ninguna épica de negocio
36
- pasa por delante. Brownfield o `foundation.required` ausente/false esta regla es N/A.
31
+ 0. **¿Greenfield sin caparazón construido?** — consulta los hechos de proyecto con
32
+ `bash .claude/hooks/build/slice-ops.sh status` (línea `hechos:`): si `project_kind` es
33
+ `greenfield` y `foundation_done` es `false` → **enruta directo a construir la épica caparazón**
34
+ por el carril **`building-a-slice`**. `status` no trae **cuál** es esa épica (`foundation.epic`
35
+ vive hoy en el fichero legacy, leído directo por `dor-dod-gatekeeper`); si no la encuentras o
36
+ está sin definir, enruta a `/build:onboard` (Fase 2c) o a discovery para definirla. Ninguna épica de
37
+ negocio pasa por delante. Brownfield, o caparazón no requerida (`foundation_done` ya `true`) →
38
+ esta regla es N/A.
37
39
  Esta regla gatea **aperturas de slice** (épicas): el carril de **mantenimiento**
38
40
  (`building-a-micro-change`, regla 1) no se bloquea — un typo/copy/config fix sigue su carril
39
41
  normal aun sin caparazón construida.
@@ -70,7 +72,8 @@ modelo/invariantes de datos** → **deja de ser micro-change** y se enruta a **`
70
72
 
71
73
  ## Guardrails
72
74
 
73
- - **Solo ruteo.** No corres el pipeline, no tocas el estado, no creas ramas: eso es de las skills destino.
75
+ - **Solo ruteo.** No corres el pipeline, no transicionas el estado (ni por `slice-ops.sh` ni por el
76
+ fichero), no creas ramas: eso es de las skills destino.
74
77
  - **Ante la duda, épica.** Nunca degrades un cambio con límite duro a micro-change.
75
78
  - **No dupliques gates** ni saltes el orden: las skills destino los gobiernan.
76
79
  - **Agnóstico**: vocabulario genérico del arnés; lo específico del dominio entra por `/build:onboard`.
@@ -4,5 +4,9 @@
4
4
  "critical_pct": 25,
5
5
  "auto_checkpoint": false,
6
6
  "stale_seconds": 60
7
+ },
8
+ "runtime": {
9
+ "mode": "legacy",
10
+ "stale_seconds": 900
7
11
  }
8
12
  }
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { update } from './commands/update.js';
7
7
  import { status } from './commands/status.js';
8
8
  import { uninstall } from './commands/uninstall.js';
9
9
  import { doctor } from './commands/doctor.js';
10
+ import { migrate } from './commands/migrate.js';
10
11
  const program = new Command();
11
12
  program
12
13
  .name('trycore-build')
@@ -24,6 +25,9 @@ program
24
25
  .option('--runtime <semver>', 'Semver del runtime (ej. ">=18.18")')
25
26
  .option('--prd-path <path>', 'Ruta#ancla del PRD técnico (fuente del allowlist)')
26
27
  .option('--yes', 'No interactivo: usa defaults para el stack')
28
+ .option('--runtime-url <url>', 'URL del Agent Orchestrator Runtime (con --runtime-token, registra el agente)')
29
+ .option('--runtime-token <token>', 'Token de proyecto emitido por un ADMIN en el hub')
30
+ .option('--runtime-mode <mode>', 'legacy|dual|runtime tras registrar (default: dual)')
27
31
  .action(async (directory, opts) => {
28
32
  try {
29
33
  await init({
@@ -38,6 +42,9 @@ program
38
42
  prdPath: opts.prdPath,
39
43
  yes: Boolean(opts.yes),
40
44
  },
45
+ runtimeUrl: opts.runtimeUrl,
46
+ runtimeToken: opts.runtimeToken,
47
+ runtimeMode: opts.runtimeMode,
41
48
  });
42
49
  }
43
50
  catch (err) {
@@ -99,6 +106,21 @@ program
99
106
  process.exit(1);
100
107
  }
101
108
  });
109
+ program
110
+ .command('migrate')
111
+ .description('Prepara el bundle de estado (history/releases/hechos) de build-state.json para que un ADMIN lo importe en el hub. Nunca sube nada.')
112
+ .argument('[directory]', 'Directorio del proyecto', '.')
113
+ .option('--out <path>', 'Ruta de salida del bundle (default: .claude/state/migration-bundle.json)')
114
+ .option('--project-ref <ref>', 'Identificador de proyecto en el hub (default: nombre del directorio)')
115
+ .action(async (directory, opts) => {
116
+ try {
117
+ await migrate({ targetDir: directory, outFile: opts.out, projectRef: opts.projectRef });
118
+ }
119
+ catch (err) {
120
+ console.error('✗ Error:', err.message);
121
+ process.exit(1);
122
+ }
123
+ });
102
124
  program.parseAsync(process.argv).catch((err) => {
103
125
  console.error('✗ Error inesperado:', err);
104
126
  process.exit(1);