@trycore/spec-build-harness 0.7.1 → 0.8.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 (44) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +3 -3
  3. package/METODOLOGIA.md +44 -0
  4. package/README.md +59 -5
  5. package/VERSION +1 -1
  6. package/agents/build/build-orchestrator.md +6 -0
  7. package/commands/build/front.md +15 -0
  8. package/commands/build/resume.md +29 -0
  9. package/config/build-config.template.json +8 -0
  10. package/dist/commands/init.js +15 -5
  11. package/dist/commands/status.js +1 -0
  12. package/dist/commands/uninstall.js +2 -1
  13. package/dist/lib/paths.js +7 -0
  14. package/dist/lib/settings-merge.js +29 -2
  15. package/dist/lib/state-seed.js +14 -0
  16. package/docs/commands.md +15 -3
  17. package/docs/decisiones/2026-07-03-gsd-vs-openspec-fork-vs-rama.md +157 -0
  18. package/docs/flujo-harness-funcional.md +42 -0
  19. package/docs/flujo-harness.md +192 -0
  20. package/docs/hooks.md +22 -5
  21. package/hooks/build/build-gate-check.sh +1 -1
  22. package/hooks/build/context-monitor.sh +74 -0
  23. package/hooks/build/design-source-guard.sh +1 -1
  24. package/hooks/build/lib/state-io.sh +55 -0
  25. package/hooks/build/lint-typecheck.sh +1 -1
  26. package/hooks/build/load-build-state.sh +46 -2
  27. package/hooks/build/reconcile-build-state.py +70 -0
  28. package/hooks/build/reflect-nudge.sh +1 -1
  29. package/hooks/build/release-gate-nudge.sh +1 -1
  30. package/hooks/build/scaffold-guard.sh +1 -1
  31. package/hooks/build/stack-guard.sh +1 -1
  32. package/hooks/build/statusline-bridge.sh +32 -0
  33. package/hooks/build-harness.json +24 -0
  34. package/package.json +1 -1
  35. package/scripts/lib/front-plan.py +47 -0
  36. package/scripts/smoke-test.sh +12 -0
  37. package/scripts/tests/test-context-monitor.sh +70 -0
  38. package/scripts/tests/test-front-plan.sh +38 -0
  39. package/scripts/tests/test-install.sh +89 -0
  40. package/scripts/tests/test-reconciler.sh +54 -0
  41. package/scripts/tests/test-schema.sh +58 -0
  42. package/skills/building-a-slice/references/dor.md +2 -0
  43. package/skills/managing-parallel-front/SKILL.md +36 -0
  44. package/state/build-state.schema.json +50 -0
@@ -0,0 +1,157 @@
1
+ # Evaluación: ¿reemplazar el harness OpenSpec por GSD? ¿fork o rama?
2
+
3
+ - **Fecha:** 2026-07-03
4
+ - **Estado:** Decidido — ver **Actualización post-análisis de código** al final. Diseño en [`docs/superpowers/specs/2026-07-03-harness-context-engine-design.md`](../superpowers/specs/2026-07-03-harness-context-engine-design.md)
5
+ - **Autor:** Jhonata.segura (asistido)
6
+ - **Repo evaluado:** `@trycore/spec-build-harness` v0.7.1
7
+ - **Candidato:** `open-gsd/gsd-core` (GSD — "Git. Ship. Done"), MIT
8
+ - **Insumo relacionado:** `gsd-template.md` (PRD "Orquestador Semántico Determinista", análisis de 4 agentes)
9
+
10
+ ---
11
+
12
+ ## 1. Pregunta a resolver
13
+
14
+ Dos preguntas, una depende de la otra:
15
+
16
+ 1. **¿Debemos reemplazar OpenSpec por GSD** como base del harness de construcción, motivado por que "los foros dicen que GSD es más nativo con Claude Code, usa mejor el contexto y es más eficiente"?
17
+ 2. **Si avanzamos, ¿fork de gsd-core o rama en este repo?**
18
+
19
+ Este documento responde ambas con evidencia y deja una recomendación. No modifica arquitectura.
20
+
21
+ ---
22
+
23
+ ## 2. Punto de partida: qué es hoy el harness y qué papel juega OpenSpec
24
+
25
+ `@trycore/spec-build-harness` no *es* OpenSpec: es un **arnés propio** que orquesta a Claude Code con arquitectura de **dos loops** (slice por épica con TDD + gates → release gate), **12 agentes de build** (coherence three-way, security-reviewer, wiring-adversarial-verifier, ux-krug, dor-dod-gatekeeper, stack-guardian, etc.), estado compartido, hooks y una **metodología documentada en español** (`METODOLOGIA.md`, `GOVERNANCE.md`). Es el compañero de `@trycore/spec-product-flow` (Discovery → Construcción).
26
+
27
+ OpenSpec (`@fission-ai/openspec`) es **una pieza dentro**, no el todo. Aporta el **modelo de spec basado en cambios**: `changes/` (propuesta) → implementación → `archive` hacia `specs/`. Se expresa como:
28
+
29
+ - **55 referencias** en el repo (`grep -ril openspec`).
30
+ - Namespace `/opsx:*` — 10 comandos (`new`, `apply`, `archive`, `bulk-archive`, `continue`, `explore`, `ff`, `onboard`, `sync`, `verify`).
31
+ - 8 skills `openspec-*` (todas con `Requires openspec CLI` y licencia MIT — son wrappers del CLI de OpenSpec).
32
+ - Dependencia externa: `npm install -g @fission-ai/openspec`.
33
+
34
+ **Conclusión de esta sección:** "reemplazar OpenSpec" ≠ "reemplazar el harness". OpenSpec es la **capa de gobernanza de spec** (propuesta de cambio → archivo). El valor diferencial de Trycore (dos loops, gates, agentes, metodología, coexistencia con product-flow) **no vive en OpenSpec**.
35
+
36
+ ---
37
+
38
+ ## 3. Qué es realmente GSD (verificado, no de oídas)
39
+
40
+ GSD es un framework de **context-engineering + spec-driven** mucho **más grande y ambicioso** que nuestra capa OpenSpec:
41
+
42
+ - **Loop de 5 fases:** Discuss → Plan → Execute → Verify → Ship, repetido por *milestones*.
43
+ - **~55+ comandos** (`/gsd-*`): incluye planificación con convergencia cross-AI, ejecución en *waves* paralelas, *workstreams*, knowledge graph (`/gsd-graphify`), memory palace temporal (`/gsd-mempalace-*`), auditorías de milestone/UAT/seguridad, code-review, audit-fix, spikes, forensics, etc.
44
+ - **Estado en archivos planos:** `STATE.md`, `CONTEXT.md`, `ROADMAP.md`, `.planning/` — con comandos de validación (`state validate`, `roadmap validate`).
45
+ - **Multi-runtime:** Claude Code, Codex, Gemini CLI, Kimi, Copilot, Cursor, Windsurf… vía instalador (`npx @opengsd/gsd-core@latest`). **No se copian `agents/`/`commands/` a mano.**
46
+ - **Licencia MIT.** Fork legalmente trivial.
47
+ - **Escala/actividad:** ~48K estrellas; v1.34.2 el 2026-04-06; **1.693 commits en 47 releases** desde dic-2025. Es un proyecto que se mueve rápido.
48
+
49
+ ### 3.1 ¿Son ciertas las afirmaciones de los foros?
50
+
51
+ | Afirmación de foro | Veredicto | Matiz |
52
+ |---|---|---|
53
+ | "Más nativo con Claude Code" | **Parcialmente cierto** | Nació Claude-Code-first y es lo más maduro ahí, pero hoy se posiciona **multi-runtime**. "Nativo" real = su patrón de *fresh-context subagents* encaja muy bien con el modelo de subagentes de Claude Code. |
54
+ | "Usa mejor el contexto" | **Cierto en el patrón** | El núcleo — ejecutar research/plan/execute en **subagentes de contexto fresco (~200k)** manteniendo la sesión principal ligera — es una respuesta genuinamente buena al *context rot*. Es la idea que la comunidad realmente elogia. |
55
+ | "Más eficiente" | **No demostrado como número** | El README no publica benchmarks de tokens. La eficiencia proviene del patrón, no de una métrica auditada. Ojo: fresh-context tiene su propio **"impuesto de arranque"** (reinyección) — es exactamente lo que critica tu PRD §2.2. |
56
+
57
+ **Lo importante:** lo que los foros elogian de GSD es un **patrón de ejecución** (subagentes de contexto fresco + estado externalizado), *no* un motor de spec que haga a OpenSpec obsoleto. Ese patrón es **adoptable sin reemplazar OpenSpec**.
58
+
59
+ ---
60
+
61
+ ## 4. Solapamiento, brechas y choque de modelos
62
+
63
+ | Capacidad | Harness actual | GSD | Lectura |
64
+ |---|---|---|---|
65
+ | Gobernanza de spec | OpenSpec `changes/ → specs/` (brownfield, delta por cambio) | `ROADMAP.md` + fases/milestones (`.planning/`) | **Modelos distintos.** OpenSpec = delta de cambio archivable; GSD = roadmap de fases. Migrar es re-conceptualizar, no un swap. |
66
+ | Ejecución / context rot | Slices + orquestador propio | **Waves paralelas en subagentes frescos** | GSD es **más fuerte** aquí. Es la joya a mirar. |
67
+ | Gates de calidad | 12 agentes Trycore + release gate | code-review, audit-fix, secure-phase, verify | **Solapan.** Adoptar GSD nos obliga a re-hospedar o botar nuestros agentes. |
68
+ | TDD / Gitflow / release gate | **Explícito** (nuestro valor) | Ship/PR + verify; TDD/gitflow **no formalizados** | Perdemos rigor si migramos crudo. |
69
+ | Metodología en español + product-flow | **Sí** (diferencial Trycore) | No existe | Se re-implementa igual, migremos o no. |
70
+ | Extensibilidad | Nuestra, total | Vía instalador/capabilities/overlays; **no copiar archivos a mano** | Forkear = pelear contra su modelo de instalación. |
71
+
72
+ **Choque clave:** OpenSpec gobierna **el cambio** (delta propuesto y archivado); GSD gobierna **el plan** (fases de un roadmap). No son intercambiables 1:1; "reemplazar" implicaría rehacer cómo pensamos la unidad de trabajo.
73
+
74
+ ---
75
+
76
+ ## 5. Riesgos de adoptar/forkear GSD
77
+
78
+ 1. **Gobernanza inestable (riesgo alto).** El proyecto original tuvo un incidente (meme-coin) y **el autor original ya no participa**. Hoy conviven `open-gsd/gsd-core`, `open-gsd/get-shit-done-redux` y otros forks. Atarnos a un upstream en plena reorganización de gobernanza es riesgo de cadena de suministro y de dirección.
79
+ 2. **Velocidad de upstream (riesgo de mantenimiento).** 47 releases / 1.693 commits en ~4 meses. Un **fork** nos obliga a un *sync* costoso y perpetuo, o a divergir y perder el motivo de forkear.
80
+ 3. **Superficie enorme.** ~55 comandos y subsistemas (knowledge graph, memory palace, cross-AI). Adoptarlo entero es asumir complejidad que **no pediste** (YAGNI) y que solapa lo nuestro.
81
+ 4. **Modelo de instalación cerrado.** "No copies `agents/`/`commands/`" → un fork/patch integrado con nuestro CLI (`trycore-build init`) va a contracorriente de su diseño.
82
+ 5. **Contradice tu propio PRD.** El consenso de los 4 agentes en `gsd-template.md` es explícito: *"El Orquestador… no reemplaza a GSD ni a Claude Code — se inserta entre ellos"*. Es decir, tu análisis ya descartó "reemplazar".
83
+
84
+ ---
85
+
86
+ ## 6. Opciones de estrategia de repositorio
87
+
88
+ ### Opción A — Rama en este repo (evolución in-place) ✅ recomendada para empezar
89
+ Crear `spike/gsd-eval` (y luego `feature/…`) **en este mismo repo**. Adoptar **el patrón que sí vale de GSD** (ejecución en subagentes de contexto fresco / waves) dentro de nuestra arquitectura, **conservando** OpenSpec como gobernanza de cambio y nuestros gates.
90
+ - **Pro:** control total, mantiene identidad Trycore y coexistencia con product-flow; reversible; publicable como v0.8/v1.0; cero riesgo de gobernanza externa.
91
+ - **Contra:** reimplementamos el patrón (no heredamos el código de GSD).
92
+
93
+ ### Opción B — Fork de `gsd-core`
94
+ Partir de su base MIT y montar gates/metodología encima.
95
+ - **Pro:** heredamos madurez (waves, graph, memory palace).
96
+ - **Contra:** **el más caro y arriesgado** — sync perpetuo con upstream velocísimo, gobernanza inestable, superficie que no queremos, choque con nuestro CLI e instalación. Botamos o duplicamos nuestros 12 agentes y los dos loops.
97
+
98
+ ### Opción C — GSD como dependencia (wrap, igual que hoy con OpenSpec)
99
+ No forkear: invocar el CLI de GSD desde nuestro harness, como orquestamos OpenSpec hoy.
100
+ - **Pro:** bajo mantenimiento; nos beneficiamos de upstream sin cargarlo.
101
+ - **Contra:** dependemos de su CLI/estado; dos modelos de spec conviviendo (OpenSpec + GSD) puede confundir; seguimos expuestos a su gobernanza en runtime.
102
+
103
+ ### Opción D — Greenfield: el "Orquestador" del PRD (visión larga)
104
+ Construir el plano de control determinista + datos semánticos (MCP a nivel símbolo + estado tipado) que describe `gsd-template.md`. GSD queda como capa metodológica, no como reemplazo.
105
+ - **Pro:** ataca la métrica real (tokens/rework/first-time-pass) según tu propio análisis.
106
+ - **Contra:** mayor esfuerzo; fuera del alcance de "una versión nueva del harness" ahora.
107
+
108
+ ---
109
+
110
+ ## 7. Recomendación
111
+
112
+ **No forkear gsd-core y no arrancar OpenSpec a ciegas.** En concreto:
113
+
114
+ 1. **Reencuadrar el objetivo.** Lo que la comunidad elogia de GSD y lo que tú buscas ("mejor uso de contexto, más eficiente") es un **patrón de ejecución** (subagentes de contexto fresco + estado externalizado), **no** un motor de spec que reemplace a OpenSpec. Perseguir ese patrón ≠ reemplazar OpenSpec.
115
+ 2. **Opción A (rama en este repo) para un spike acotado.** Validar el patrón de contexto fresco dentro de nuestra arquitectura, midiendo contra un baseline. Reversible y publicable.
116
+ 3. **Descartar la Opción B (fork)** salvo que decidamos, con datos del spike, comprometer GSD como *motor* y aceptar el costo de mantenimiento/gobernanza. Hoy el riesgo de gobernanza (autor fuera, forks múltiples) lo hace desaconsejable.
117
+ 4. **Mantener la Opción D (orquestador del PRD) como norte de largo plazo**, no como esta release. Coincide con el consenso de tu propio PRD: *insertar entre*, no reemplazar.
118
+
119
+ En una frase: **la próxima versión del harness debería robarle a GSD la *idea* (ejecución en contexto fresco), no el *repositorio*.**
120
+
121
+ ---
122
+
123
+ ## 8. Spike de validación propuesto (si se aprueba avanzar)
124
+
125
+ Rama `spike/gsd-eval`, alcance de días, criterios de éxito medibles:
126
+
127
+ 1. **Baseline.** Correr un slice representativo con el harness actual; registrar tokens de entrada, nº de llamadas a herramientas, rework y first-time-pass.
128
+ 2. **Variante A (patrón GSD, sin GSD).** Reimplementar la ejecución del slice en **subagentes de contexto fresco** (ya tenemos `Agent`/waves nativos de Claude Code) manteniendo OpenSpec + gates. Medir lo mismo.
129
+ 3. **Variante B (GSD como dependencia, opcional).** Instalar `gsd-core` en un proyecto de prueba y comparar su Execute-phase contra nuestro slice, para calibrar cuánto valor real añade su motor vs. reimplementarlo.
130
+ 4. **Decisión.** Con las tres mediciones, elegir entre: (a) adoptar solo el patrón (Opción A), (b) wrap de GSD (Opción C), o (c) invertir en el orquestador (Opción D). Sólo entonces se justificaría —o no— un fork.
131
+
132
+ **Criterio de descarte de fork:** si el spike no muestra ≥30% de mejora de tokens/rework atribuible al *código* de GSD (y no solo al patrón), no forkear.
133
+
134
+ ---
135
+
136
+ ## 9. Fuentes
137
+
138
+ - [open-gsd/gsd-core (GitHub)](https://github.com/open-gsd/gsd-core)
139
+ - [GSD hits 48K stars — Augment Code](https://www.augmentcode.com/learn/gsd-stars-spec-driven-dev-claude-code)
140
+ - [The Anatomy of Claude Code Workflows (GSD deep dive) — codecentric](https://www.codecentric.de/en/knowledge-hub/blog/the-anatomy-of-claude-code-workflows-turning-slash-commands-into-an-ai-development-system)
141
+ - [Superpowers, GSD, and gstack: what each constrains — Ewan Mak (Medium)](https://medium.com/@tentenco/superpowers-gsd-and-gstack-what-each-claude-code-framework-actually-constrains-12a1560960ad)
142
+ - [GSD vs Spec Kit vs OpenSpec vs Taskmaster — Rick Hightower (Medium)](https://medium.com/@richardhightower/agentic-coding-gsd-vs-spec-kit-vs-openspec-vs-taskmaster-ai-where-sdd-tools-diverge-0414dcb97e46)
143
+ - Interno: `gsd-template.md` (PRD "Orquestador Semántico Determinista"), `README.md`, `METODOLOGIA.md`, skills `openspec-*`.
144
+
145
+ ---
146
+
147
+ ## 10. Actualización post-análisis de código (2026-07-03)
148
+
149
+ Se clonó `gsd-core` y se leyó su ingeniería real (5 exploradores, evidencia `file:line`). Confirma y **cierra** las decisiones:
150
+
151
+ 1. **NO forkear — confirmado con datos.** `git log --since=30d` = **861 commits**; sin campo `exports` (no es librería); artefactos compilados en `.gitignore`; instalador monolítico de **11.740 líneas**; 3 sistemas de toggles entrelazados. No hay costura estable para depender. Los patrones valiosos son portables (MIT) sin el motor.
152
+ 2. **El dolor real (reinicio manual) NO lo resuelve GSD tampoco:** GSD deja el `/clear` manual (filosofía #884). Su valor = **aviso temprano (hook a 35%/25%) + handoff sin pérdida**. Eso es lo adoptable, y es pequeño/portable.
153
+ 3. **Nuestro estado tipado ≥ el de GSD** (JSON schema vs Markdown+regex); GSD compensa con **derivación desde disco** — esa *idea* sí vale robarla.
154
+ 4. **Worktrees:** GSD paraleliza solo lo disjunto en archivos + seguro en el DAG, con serializador de solapes. Adoptamos el patrón a nivel **inter-épica** (no fundacionales, disjuntas).
155
+ 5. Lo *hypeado* (memory-palace, knowledge graph) es lo *menos* propio de GSD (MCP/Python externos). Descartado por ahora.
156
+
157
+ **Veredicto final:** rama en este repo (`feature/gsd-context-engine`), robar patrones de contexto + estado-en-disco + worktrees inter-épica. Diseño detallado en el spec enlazado arriba.
@@ -0,0 +1,42 @@
1
+ # Cómo trabaja el arnés — flujo funcional
2
+
3
+ Vista única y sencilla del flujo de trabajo, para explicar a los devs **qué pasa con cada cambio**.
4
+ (Versión detallada/técnica en [flujo-harness.md](./flujo-harness.md).)
5
+
6
+ ```mermaid
7
+ flowchart TD
8
+ A([Llega un trabajo]) --> B{"¿Qué tipo de cambio es?"}
9
+
10
+ B -->|"Arreglo pequeño<br/>(typo, copy, config)"| C["Carril rápido:<br/>rama → cambio → PR"]
11
+ C --> Z([PR listo para mergear])
12
+
13
+ B -->|"Funcionalidad nueva<br/>(una épica)"| D{"¿Está lista para construir?<br/>historias claras, criterios definidos,<br/>base ya construida"}
14
+ D -->|"No"| E["Vuelve a discovery<br/>a completar la historia"]
15
+ D -->|"Sí"| F["Construir con pruebas<br/>(escribo el test, luego el código)"]
16
+
17
+ F --> G{"¿La app funciona<br/>de punta a punta?"}
18
+ G -->|"No"| F
19
+ G -->|"Sí"| H["Revisión final del cambio<br/>+ pantallas iguales al diseño (si hay UI)"]
20
+
21
+ H --> I["Abrir PR y dejar todo enlazado<br/>(historia ↔ cambio ↔ código)"]
22
+ I --> J{"¿Esta épica cierra<br/>una entrega/release?"}
23
+
24
+ J -->|"No, sigue otra épica"| A
25
+ J -->|"Sí"| K["Revisión profunda de la entrega:<br/>seguridad · calidad · UX ·<br/>coherencia · arquitectura"]
26
+
27
+ K --> L{"¿Todo el journey completo<br/>funciona con dependencias reales?"}
28
+ L -->|"No, hay hallazgos"| M["Corregir como un cambio normal"]
29
+ M --> K
30
+ L -->|"Sí"| N([Release lista ✅])
31
+
32
+ classDef q fill:#fff3cd,stroke:#d39e00,color:#000;
33
+ classDef ok fill:#d4edda,stroke:#155724,color:#000;
34
+ class B,D,G,J,L q;
35
+ class N,Z ok;
36
+ ```
37
+
38
+ ## La idea en una frase
39
+
40
+ - **Cambios chicos** van por un carril rápido.
41
+ - **Cada funcionalidad nueva** se construye completa, con pruebas, verificando que la app **funcione de punta a punta** antes de cerrarla.
42
+ - **De vez en cuando** (al cerrar una entrega) se hace una **revisión profunda** de todo lo acumulado antes de liberar.
@@ -0,0 +1,192 @@
1
+ # Flujo del arnés de construcción — `@trycore/spec-build-harness`
2
+
3
+ Diagrama de flujo end-to-end del harness, modelado al estilo BPMN:
4
+
5
+ - **◆ XOR** (rombo) = compuerta **exclusiva** (un solo camino).
6
+ - **⬡ AND** (hexágono) = compuerta **paralela** (fork: todos los caminos; join: convergencia/barrera).
7
+ - **▭ Tarea** con su **gate** entre `[ ]`.
8
+ - Aristas punteadas `-.->` = retroceso (gate monótono que vuelve a `false`).
9
+
10
+ Fuente de verdad: [METODOLOGIA.md](../METODOLOGIA.md). Si algo contradice ese documento, gana la metodología.
11
+
12
+ ---
13
+
14
+ ## 1. Vista global (router → inner loop → outer loop)
15
+
16
+ ```mermaid
17
+ flowchart TD
18
+ START([Trabajo entrante]) --> PRE{{"⬡ Preflight<br/>¿instalado?"}}
19
+ PRE -->|NOT_INSTALLED| INIT[trycore-build init] --> ROUTER
20
+ PRE -->|ok| ROUTER
21
+
22
+ ROUTER[/"/build:work — Router (classify-and-act)<br/>solo ruteo: no toca estado ni ramas"/]
23
+ ROUTER --> GW1{"◆ XOR — Decision gate<br/>¿qué carril?"}
24
+
25
+ %% --- Rama 1: micro-change ---
26
+ GW1 -->|"mantenimiento<br/>sin capacidad nueva"| HARD{"◆ XOR — ¿cruza límite duro?<br/>dep nueva / API nueva / dominio·datos"}
27
+ HARD -->|"sí → escala"| INNER
28
+ HARD -->|no| MICRO["building-a-micro-change<br/>fix/* | chore/* → cambio → PR<br/>(sin abrir active_slice)"]
29
+ MICRO --> ENDM([PR mergeado])
30
+
31
+ %% --- Rama 2: épica / producto nuevo ---
32
+ GW1 -->|"capacidad nueva<br/>o límite duro"| INNER[["INNER LOOP<br/>building-a-slice<br/>(ver §2)"]]
33
+
34
+ %% --- Rama 3: release gate ---
35
+ GW1 -->|"cierra línea de release<br/>o ≥2 épicas archivadas"| OUTER[["OUTER LOOP<br/>releasing-a-version<br/>(ver §3)"]]
36
+
37
+ INNER --> DEC{"◆ XOR — fase 8<br/>¿correr Release Gate ahora?<br/>(default computado, decide humano)"}
38
+ DEC -->|"sí (cierra release<br/>o nudge ≥2)"| OUTER
39
+ DEC -->|no| NEXT([Siguiente épica]) -.-> INNER
40
+
41
+ OUTER --> OUTGW{"◆ XOR — ¿todos los gates ✓?"}
42
+ OUTGW -->|"passed"| REL([Release lista])
43
+ OUTGW -->|"failed (hallazgos)"| FIX["Fix como slice normal<br/>(building-a-slice)"] -.->|re-corre| OUTER
44
+
45
+ classDef xor fill:#fff3cd,stroke:#d39e00,color:#000;
46
+ classDef andg fill:#d1ecf1,stroke:#0c5460,color:#000;
47
+ classDef loop fill:#e2e3f3,stroke:#383d6b,color:#000;
48
+ class GW1,HARD,DEC,OUTGW xor;
49
+ class PRE andg;
50
+ class INNER,OUTER loop;
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 2. Inner loop — `building-a-slice` (pipeline secuencial por épica)
56
+
57
+ Precondición dura: **scaffold confirmado** (`scaffold.confirmed`) antes de abrir cualquier slice.
58
+ Secuencial (un `active_slice` a la vez), divulgación progresiva, ningún gate se salta.
59
+
60
+ ```mermaid
61
+ flowchart TD
62
+ S0([Épica EP-XXX entrante]) --> SCAF{"◆ XOR — gate proyecto<br/>scaffold.confirmed?"}
63
+ SCAF -->|"false"| STOPS["STOP — exige scaffold runnable<br/>(scaffold-guard.sh)"]
64
+ SCAF -->|"true"| F1
65
+
66
+ %% Fase 1: DoR
67
+ F1["F1 · dor — Definition of Ready<br/>delega: dor-dod-gatekeeper"]
68
+ F1 --> DORGW{"◆ XOR — ¿DoR ✓?<br/>épica+HU, AC G/W/T, INVEST,<br/>cimiento, tamaño, stack, fixtures"}
69
+ DORGW -->|"✗"| BACKDISC["Vuelve a discovery<br/>(/trycore:*) — no se abre slice"]
70
+ DORGW -->|"✓ [gate: dor]"| SIZE{"◆ XOR — gate tamaño<br/>>3 HU ó ≥3 capas?"}
71
+
72
+ SIZE -->|"sí"| SUB["Descompone en sub_slices[]<br/>se construyen de a uno<br/>(journey_smoke verde entre cada uno)"]
73
+ SIZE -->|"no (atómica)"| F2
74
+ SUB --> F2
75
+
76
+ %% Fase 2: change
77
+ F2["F2 · change — opsx:new + ## Trazabilidad<br/>delega: change-epic-coherence"]
78
+ F2 --> F2GW{"◆ XOR — ¿enlace change↔épica ✓?"}
79
+ F2GW -->|"✓ [gate: coherence_link]"| F3
80
+ F2GW -.->|"✗ retroceso"| F2
81
+
82
+ %% Fase 3: TDD
83
+ F3["F3 · red→green→refactor<br/>TDD por cada escenario AC de cada HU<br/>delega: superpowers:test-driven-development"]
84
+ F3 --> F3GW{"◆ XOR — ¿suite verde?"}
85
+ F3GW -.->|"✗ retroceso"| F3
86
+ F3GW -->|"✓ [gate: tdd]"| F4
87
+
88
+ %% Fase 4: smoke + fidelity (UI)
89
+ F4["F4 · smoke — journey-hasta-aquí end-to-end<br/>runner determinista fuera-de-chat (sesión virgen)"]
90
+ F4 --> UIGW{"◆ XOR — ¿la épica toca UI?"}
91
+ UIGW -->|"no"| F4OUT["fidelity = null"]
92
+ UIGW -->|"sí"| FID["Verificación visual real (MCP chrome-devtools)<br/>screenshot app vs prototipo<br/>delega: ux-fidelity-reviewer"]
93
+ FID --> FIDGW{"◆ XOR — ¿fidelidad ESTRICTA ✓?<br/>INCONCLUSO = false"}
94
+ FIDGW -.->|"✗ → false"| FID
95
+ FIDGW -->|"✓ [gate: fidelity]"| F4OUT
96
+ F4OUT -->|"[gate: journey_smoke]"| F5FORK
97
+
98
+ %% Fase 5: api / data en paralelo (condicional/null)
99
+ F5FORK{{"⬡ AND — fork (fase api/data)"}}
100
+ F5FORK --> APIB["api — contratos endpoints<br/>Newman 100% (delega: api-contract-tester)<br/>null si no hay endpoints"]
101
+ F5FORK --> DATAB["data — invariantes de datos<br/>(delega: data-consistency-checker)<br/>N/A si no toca datos"]
102
+ APIB --> F5JOIN
103
+ DATAB --> F5JOIN
104
+ F5JOIN{{"⬡ AND — join (convergencia)<br/>[gates: api, data]"}}
105
+
106
+ %% Fase 6: dod — adversarial THEN dod
107
+ F5JOIN --> WIRE["F6a · Verificación ADVERSARIAL independiente<br/>(subagente contexto virgen)<br/>asume slice incompleto e intenta refutarlo<br/>delega: wiring-adversarial-verifier"]
108
+ WIRE --> WIREGW{"◆ XOR — ¿wiring_checklist[] sin items failing?"}
109
+ WIREGW -.->|"✗ huecos (stub/ruta/AC sin test)"| F3
110
+ WIREGW -->|"✓ [gate: wiring_verified]"| DOD["F6b · DoD reducido<br/>delega: dor-dod-gatekeeper<br/>(piso declarativo, no el arreglo)"]
111
+ DOD --> DODGW{"◆ XOR — ¿DoD ✓?<br/>tdd·journey_smoke·coherence_link·<br/>data·api·fidelity·wiring_verified·hooks"}
112
+ DODGW -.->|"✗ retroceso"| F3
113
+ DODGW -->|"✓ [gate: dod]"| F7
114
+
115
+ %% Fase 7: PR + archive
116
+ F7["F7 · pr — abrir PR + archivar change EN EL MISMO PR<br/>opsx:archive + opsx:sync<br/>back-reference en épica y HU"]
117
+ F7 --> ARCH["active_slice → history[] (phase: archived)<br/>active_slice = null"]
118
+ ARCH --> F8([F8 · decisión Release Gate → §1])
119
+
120
+ classDef xor fill:#fff3cd,stroke:#d39e00,color:#000;
121
+ classDef andg fill:#d1ecf1,stroke:#0c5460,color:#000;
122
+ classDef stop fill:#f8d7da,stroke:#842029,color:#000;
123
+ class SCAF,DORGW,SIZE,F2GW,F3GW,UIGW,FIDGW,WIREGW,DODGW xor;
124
+ class F5FORK,F5JOIN andg;
125
+ class STOPS,BACKDISC stop;
126
+ ```
127
+
128
+ > **Disciplina de horizonte largo:** cada iteración nace headless / contexto virgen y reconstruye estado
129
+ > desde disco (`build-state.json` + git + logs). `wiring_checklist[]` (1 item por escenario AC y por punto
130
+ > de integración entre capas) nace `failing` y solo pasa a `passing` **tras prueba real ejecutada**.
131
+ > Mientras quede un item `failing`, el cableado NO está hecho.
132
+
133
+ ---
134
+
135
+ ## 3. Outer loop — `releasing-a-version` (Release Gate)
136
+
137
+ Alcance = **diff acumulado** de todas las épicas de la release (merge anterior → `main`).
138
+ Los reviewers se disparan **en paralelo** y devuelven síntesis (protegen el contexto).
139
+
140
+ ```mermaid
141
+ flowchart TD
142
+ R0([Línea de release identificada]) --> R1["Crear/actualizar releases[] (status: pending)<br/>cruzar con docs/02-user-story-map/"]
143
+ R1 --> FORK{{"⬡ AND — fork (reviewers en paralelo<br/>sobre el diff acumulado)"}}
144
+
145
+ FORK --> SEC["security — sin CRÍTICO/ALTO;<br/>claves server-side; PII no cruda;<br/>salida IA = input no confiable<br/>(security-reviewer)"]
146
+ FORK --> SME["smell — 4 reglas de Beck<br/>+ code smells sin bloqueantes<br/>(simple-design-reviewer)"]
147
+ FORK --> UX["ux — Krug + Lighthouse<br/>(null si sin UI)<br/>(ux-krug-reviewer)"]
148
+ FORK --> COH["coherence — trazabilidad triple<br/>AC↔change↔código, sin huérfanos<br/>(coherence-three-way · opus)"]
149
+ FORK --> ARCH["stack_arch — arquitectura PRD:<br/>capa IA en frontera server-side;<br/>decisión determinista sin IA<br/>(stack-guardian)"]
150
+
151
+ SEC --> JOIN
152
+ SME --> JOIN
153
+ UX --> JOIN
154
+ COH --> JOIN
155
+ ARCH --> JOIN
156
+
157
+ JOIN{{"⬡ AND — join (convergencia de reviewers)"}}
158
+ JOIN --> INTEG["integration — journey COMPLETO end-to-end<br/>con DEPENDENCIAS REALES (no stubs)<br/>skill verify/run + MCP chrome-devtools"]
159
+
160
+ INTEG --> RGW{"◆ XOR — ¿todos los gates ✓ (o null N/A)?"}
161
+ RGW -->|"✓"| PASS["status: passed<br/>escribir gates + updated_by: releasing-a-version"]
162
+ RGW -->|"✗ hallazgos bloqueantes"| FAIL["status: failed"]
163
+ PASS --> RELOK([Release ✅])
164
+ FAIL --> FIXLOOP["Humano corrige como slice normal<br/>(building-a-slice)"]
165
+ FIXLOOP -.->|"re-corre Release Gate"| R1
166
+
167
+ classDef xor fill:#fff3cd,stroke:#d39e00,color:#000;
168
+ classDef andg fill:#d1ecf1,stroke:#0c5460,color:#000;
169
+ classDef gate fill:#d4edda,stroke:#155724,color:#000;
170
+ class RGW xor;
171
+ class FORK,JOIN andg;
172
+ class INTEG gate;
173
+ ```
174
+
175
+ > **`integration` es el gate no negociable.** Sin journey completo con dependencias reales **no hay release**.
176
+ > No se acepta con todo stubbeado.
177
+
178
+ ---
179
+
180
+ ## 4. Leyenda de loops y gates
181
+
182
+ | | Inner loop | Outer loop |
183
+ |---|---|---|
184
+ | Skill | `building-a-slice` | `releasing-a-version` |
185
+ | Unidad | una épica `EP-XXX` | una línea de release |
186
+ | Cadencia | muchas (1 por épica) | pocas (1 por release) |
187
+ | Costo | barato (≤ ~20 min/épica) | pesado (subagentes profundos) |
188
+ | Gates | dor · coherence_link · tdd · journey_smoke · fidelity · api · data · wiring_verified · dod | security · smell · ux · coherence · stack_arch · integration |
189
+ | Estado | `active_slice` + `history[]` | `releases[]` |
190
+
191
+ **Regla de no duplicación:** cada gate vive en exactamente un loop. El outer no hace TDD ni gates por slice;
192
+ el inner no dispara reviewers pesados.
package/docs/hooks.md CHANGED
@@ -1,27 +1,32 @@
1
1
  # Hooks del arnés de construcción
2
2
 
3
- Este documento describe los **10 hooks** que `@trycore/spec-build-harness` instala en el proyecto del consumidor. Todos viven en `hooks/build/` (un script bash por hook) 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 **13 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 el helper compartido `lib/state-io.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
- Los hooks son el sistema nervioso del arnés: vigilan GitFlow, el stack declarado, el scaffold y la fuente de diseño (bloqueantes), inyectan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, 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.
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
7
  ---
8
8
 
9
- ## Resumen de los 10 hooks
9
+ ## Resumen de los 13 hooks
10
10
 
11
11
  | Hook | Evento | Matcher | Qué hace | ¿Bloqueante? |
12
12
  |---|---|---|---|---|
13
- | `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; sincroniza `harness_phase`. | No |
13
+ | `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; sincroniza `harness_phase`. Invoca a `reconcile-build-state.py` antes de leer el estado. | No |
14
+ | `reconcile-build-state.py` | `SessionStart` (invocado por `load-build-state.sh`) | — | 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
+ | `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 |
14
16
  | `gitflow-guard.sh` | `PreToolUse` | `Bash` | Enforce GitHub Flow estricto sobre `git commit` / `git push`. | **Sí (exit 2)** |
15
17
  | `stack-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea dependencias en `package.json` fuera de la allowlist del stack del PRD. | **Sí (exit 2)** |
16
18
  | `scaffold-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea escribir código de slice (fases `red…data`) si el scaffold no está confirmado (`scaffold.confirmed`). | **Sí (exit 2)** |
17
19
  | `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)** |
18
20
  | `lint-typecheck.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Corre prettier/eslint/tsc sobre el archivo `.ts`/`.tsx` editado. | No |
19
21
  | `coherence-flag.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Recuerda validar la trazabilidad de un `proposal.md` de OpenSpec recién tocado. | No |
22
+ | `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 una sola vez por sesión el handoff (`active_slice.session_continuity`) en `build-state.json`. | No |
20
23
  | `build-gate-check.sh` | `Stop` | `.*` | Al cerrar el turno, avisa si el slice activo tiene gates abiertos. | No |
21
24
  | `reflect-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere `/build:reflect` si hay slice(s) archivado(s) sin reflexionar (`reflected != true`). | No |
22
25
  | `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 |
23
26
 
24
- > Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y seis informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
27
+ > Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y nueve informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
28
+ >
29
+ > `lib/state-io.sh` no es un hook: es el **helper compartido** (`state_path`, `config_get`, `state_atomic_patch`) que usan `context-monitor.sh` y otros scripts para resolver la ruta del estado, leer `config/build-config.json` y escribir patches atómicos.
25
30
 
26
31
  ---
27
32
 
@@ -94,6 +99,18 @@ Refuerza el **seguro de fuente de diseño** (espejo de `scaffold-guard`, para sl
94
99
 
95
100
  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**).
96
101
 
102
+ ### 11. `statusline-bridge.sh` — comando `statusLine` · no bloqueante (desde v0.8.0)
103
+
104
+ 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
+
106
+ ### 12. `context-monitor.sh` — `PostToolUse` / `PreCompact` / `Stop` · no bloqueante (desde v0.8.0)
107
+
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`: `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
+
110
+ ### 13. `reconcile-build-state.py` — `SessionStart` (invocado por `load-build-state.sh`) · no bloqueante (desde v0.8.0)
111
+
112
+ 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
+
97
114
  ---
98
115
 
99
116
  ## La cadena de comando única (sin doble disparo entre canales)
@@ -4,7 +4,7 @@
4
4
  # No bloquea: al cerrar el turno, avisa si hay un slice activo con gates abiertos.
5
5
  set -uo pipefail
6
6
 
7
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
7
+ ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
8
8
  [ -f "$ROOT/package.json" ] || exit 0
9
9
  STATE="$ROOT/.claude/state/build-state.json"
10
10
  [ -f "$STATE" ] || exit 0
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ # context-monitor.sh — PostToolUse|PreCompact|Stop. Lee el puente, aplica umbrales,
3
+ # inyecta additionalContext y (en critical) escribe handoff. Fail-open.
4
+ set -uo pipefail
5
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$HERE/lib/state-io.sh"
7
+ payload="$(cat)"; command -v python3 >/dev/null 2>&1 || exit 0
8
+
9
+ read -r SID EVT <<<"$(printf '%s' "$payload" | python3 -c '
10
+ import json,sys
11
+ try: p=json.load(sys.stdin)
12
+ except Exception: p={}
13
+ print(p.get("session_id","default"), p.get("hook_event_name","PostToolUse"))
14
+ ' 2>/dev/null)"
15
+ SID="${SID:-default}"; EVT="${EVT:-PostToolUse}"
16
+
17
+ sanitized_sid="$(printf '%s' "$SID" | tr -c 'A-Za-z0-9_-' '_')"
18
+ BRIDGE="${TMPDIR:-/tmp}/claude-ctx-$sanitized_sid.json"
19
+ [ -f "$BRIDGE" ] || BRIDGE="$(command ls "${TMPDIR:-/tmp}"/claude-ctx-*.json 2>/dev/null | head -1)"
20
+ [ -n "${BRIDGE:-}" ] && [ -f "$BRIDGE" ] || exit 0
21
+
22
+ WARN="$(config_get context.warning_pct 35)"; CRIT="$(config_get context.critical_pct 25)"
23
+ STALE="$(config_get context.stale_seconds 60)"; AUTO="$(config_get context.auto_checkpoint false)"
24
+ STATE="$(state_path)"
25
+
26
+ python3 - "$BRIDGE" "$WARN" "$CRIT" "$STALE" "$EVT" "$STATE" "$AUTO" <<'PY' 2>/dev/null || true
27
+ import json,sys,os,time,tempfile
28
+ bridge,warn,crit,stale,evt,state,auto=sys.argv[1:8]
29
+ warn,crit,stale=int(warn),int(crit),int(stale)
30
+ try: b=json.load(open(bridge))
31
+ except Exception: sys.exit(0)
32
+ if time.time()-b.get("ts",0) > stale: sys.exit(0) # métrica vieja
33
+ rem=b.get("remaining_pct",100)
34
+ sev = "critical" if rem<=crit else ("warning" if rem<=warn else None)
35
+ if not sev: sys.exit(0)
36
+
37
+ # ¿slice activo? handoff solo en critical y una vez por sesión.
38
+ recorded=False
39
+ if sev=="critical" and os.path.exists(state):
40
+ try:
41
+ d=json.load(open(state)); s=d.get("active_slice")
42
+ if s:
43
+ sc=s.setdefault("session_continuity",{})
44
+ if not sc.get("critical_recorded"):
45
+ iso=time.strftime("%Y-%m-%dT%H:%M:%SZ",time.gmtime())
46
+ sc["stopped_at"]=f"context exhaustion at {rem}% ({iso})"
47
+ failing=[w["id"] for w in (s.get("wiring_checklist") or []) if w.get("status")=="failing"]
48
+ sc["resume_hint"]=("cablear: "+", ".join(failing[:6])) if failing else "revisar progreso y continuar"
49
+ sc["critical_recorded"]=True
50
+ sc["auto_continue"]=(auto=="true")
51
+ s.setdefault("progress_log",[]).append(
52
+ {"at":iso,"by":"context-monitor","note":f"handoff auto a {rem}% de contexto restante"})
53
+ dirn=os.path.dirname(state) or "."
54
+ fd,tmp=tempfile.mkstemp(dir=dirn,prefix=".build-state.",suffix=".tmp")
55
+ try:
56
+ with os.fdopen(fd,"w") as o: json.dump(d,o,indent=2,ensure_ascii=False); o.flush(); os.fsync(o.fileno())
57
+ os.replace(tmp,state); recorded=True
58
+ except Exception:
59
+ try: os.unlink(tmp)
60
+ except OSError: pass
61
+ raise
62
+ except Exception: pass
63
+
64
+ if sev=="warning":
65
+ msg=(f"⚠️ Contexto al {rem}% restante. Acércate a un punto natural de corte "
66
+ "(fin de fase/gate). No inicies trabajo complejo nuevo.")
67
+ else:
68
+ tail=(" Handoff escrito en build-state.json (session_continuity)." if recorded else "")
69
+ cont=(" auto_checkpoint=ON: continúa en sesión fresca." if auto=="true" else
70
+ " Avisa al usuario para reiniciar en un punto natural.")
71
+ msg=(f"🛑 Contexto CRÍTICO al {rem}% restante.{tail} El estado ya vive en build-state.json;"
72
+ f" no reescribas handoff manual.{cont}")
73
+ print(json.dumps({"hookSpecificOutput":{"hookEventName":evt,"additionalContext":msg}}))
74
+ PY
@@ -8,7 +8,7 @@
8
8
  # AUTO-ARME: si no existe build-state.json, exit 0.
9
9
  set -uo pipefail
10
10
 
11
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
11
+ ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
12
12
  STATE="$ROOT/.claude/state/build-state.json"
13
13
  [ -f "$STATE" ] || exit 0
14
14
 
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # state-io.sh — helpers compartidos: rutas, lectura de config y patch atómico del estado.
3
+ # Diseño: fail-open. Nunca lanza; ante fallo deja el archivo intacto.
4
+
5
+ state_path() {
6
+ # CLAUDE_PROJECT_DIR es la señal autoritativa que Claude Code fija en los hooks;
7
+ # git rev-parse solo aplica como fallback para invocación manual/dev (sin ese env).
8
+ local root; root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
9
+ echo "$root/.claude/state/build-state.json"
10
+ }
11
+
12
+ # config_get <clave.punteada> <default>
13
+ config_get() {
14
+ local key="$1" def="$2" file
15
+ file="${BUILD_CONFIG_FILE:-$(dirname "$(state_path)")/../config/build-config.json}"
16
+ command -v python3 >/dev/null 2>&1 || { echo "$def"; return; }
17
+ python3 - "$file" "$key" "$def" <<'PY' 2>/dev/null || echo "$def"
18
+ import json,sys
19
+ file,key,default=sys.argv[1],sys.argv[2],sys.argv[3]
20
+ try:
21
+ d=json.load(open(file))
22
+ for part in key.split("."): d=d[part]
23
+ print(d if not isinstance(d,bool) else str(d).lower())
24
+ except Exception:
25
+ print(default)
26
+ PY
27
+ }
28
+
29
+ # state_atomic_patch <state-file> <expr-python-que-muta-`d`>
30
+ # Ejecuta la expresión con `d` = dict del estado; escribe atómico solo si válido.
31
+ state_atomic_patch() {
32
+ local file="$1" expr="$2"
33
+ [ -f "$file" ] || return 0
34
+ command -v python3 >/dev/null 2>&1 || return 0
35
+ python3 - "$file" "$expr" <<'PY' 2>/dev/null || true
36
+ import json,sys,os,tempfile
37
+ path,expr=sys.argv[1],sys.argv[2]
38
+ try:
39
+ d=json.load(open(path))
40
+ if not isinstance(d,dict): sys.exit(0)
41
+ exec(expr, {}, {"d":d})
42
+ dirn=os.path.dirname(path) or "."
43
+ fd,tmp=tempfile.mkstemp(dir=dirn,prefix=".build-state.",suffix=".tmp")
44
+ try:
45
+ with os.fdopen(fd,"w") as out:
46
+ json.dump(d,out,indent=2,ensure_ascii=False); out.flush(); os.fsync(out.fileno())
47
+ os.replace(tmp,path)
48
+ except Exception:
49
+ try: os.unlink(tmp)
50
+ except OSError: pass
51
+ raise
52
+ except Exception:
53
+ pass
54
+ PY
55
+ }
@@ -8,7 +8,7 @@
8
8
  # coste por edición no escale con el tamaño del repo.
9
9
  set -uo pipefail
10
10
 
11
- ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
11
+ ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
12
12
  [ -f "$ROOT/package.json" ] || exit 0 # guard de auto-arme
13
13
 
14
14
  # Guarda python3 [H4]: hook no-bloqueante; sin python3 no podemos extraer el archivo → omite.