@trycore/spec-build-harness 0.1.0 → 0.4.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.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "trycore-spec-build-harness",
4
4
  "displayName": "Trycore — Spec & Build Harness",
5
- "version": "0.1.0",
5
+ "version": "0.4.0",
6
6
  "description": "Arnés de construcción de dos loops (slice por épica + release gate) para Claude Code, con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
7
7
  "author": {
8
8
  "name": "Trycore",
package/GOVERNANCE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Gobernanza del arnés de construcción `.claude`
2
2
 
3
- Versión: ver `.claude/.build-harness-version` (`1.0.0`). El arnés es **soporte cognitivo vivo**:
3
+ Versión del paquete: ver `.claude/.build-harness-version`. El arnés es **soporte cognitivo vivo**:
4
4
  evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
5
5
 
6
6
  ## Componentes y dueño
@@ -9,14 +9,15 @@ evoluciona con el modelo y el proyecto. No es "instalar y olvidar".
9
9
  | Contexto | sección Construcción de CLAUDE.md, `openspec/project.md` | raíz / `openspec/` |
10
10
  | Estado | `build-state.json` (+schema, README) | `.claude/state/` |
11
11
  | Agentes | 10 agentes de build | `.claude/agents/build/` |
12
- | Hooks | settings.json + 6 scripts | `.claude/settings.json`, `.claude/hooks/build/` |
13
- | Skill | `building-a-slice` + 10 references | `.claude/skills/building-a-slice/` |
12
+ | Hooks | settings.json + 8 scripts | `.claude/settings.json`, `.claude/hooks/build/` |
13
+ | Skill | `building-a-slice` (+10 refs) · `building-a-micro-change` (carril ligero de mantenimiento) | `.claude/skills/` |
14
14
  | Config | allowlist de stack | `.claude/config/stack-allowlist.json` |
15
15
 
16
16
  ## Fases de activación (`harness_phase`)
17
17
  - **authoring** (actual): aún no hay `package.json`. Activos: `gitflow-guard`, `load-build-state`,
18
18
  `coherence-flag`; agentes de coherencia/stack/DoR/DoD operan sobre texto. Los hooks de
19
- lint/typecheck/stack(package.json)/tests están **armados** pero inertes (`[ -f package.json ] || exit 0`).
19
+ lint/typecheck/stack(package.json)/tests están **armados** pero inertes (`[ -f package.json ] || exit 0`);
20
+ `scaffold-guard` permite (sin slice en fases de código que bloquear) y `reflect-nudge` calla (sin historial que reflexionar).
20
21
  - **active**: al aparecer `package.json`, `load-build-state.sh` cambia la fase y los hooks armados
21
22
  empiezan a disparar solos. Sin intervención manual.
22
23
 
@@ -40,6 +41,16 @@ Editar `stack-allowlist.json` SOLO si la sección de requisitos técnicos del PR
40
41
  ### Bitácora de excepciones de stack
41
42
  - _(vacío)_ — registrar fecha, dependencia, justificación y aprobador.
42
43
 
44
+ ## Bitácora de cambios de metodología
45
+ Cambios a la política de construcción (unidad de trabajo, gates, DoR/DoD). Aprueba el DRI; van por PR.
46
+
47
+ - **2026-06-02 · v0.4.0** — Carril `building-a-micro-change` + DoR proporcional. El mantenimiento que
48
+ no es producto nuevo (typo, bump de dep permitida, copy/config/docs, fix de pocas líneas) deja de
49
+ modelarse como épica y usa un carril ligero (`fix/*`|`chore/*` → PR, sin `active_slice`), con
50
+ límites duros que lo escalan a épica (dep nueva, API nueva, lógica de dominio/datos). El DoR escala
51
+ el nº de escenarios G/W/T con `complejidad` en vez de exigir 3–5 fijos. La regla "épica = unidad"
52
+ se mantiene intacta para producto. Origen: auditoría del arnés vs. crítica de sobre-configuración.
53
+
43
54
  ## Extensiones futuras (no implementadas)
44
55
  - **Construcción en paralelo** de slices con `superpowers:using-git-worktrees` + orquestación
45
56
  multi-worktree (hoy el modelo es **secuencial** por decisión de proyecto). El `build-state.json`
package/INSTALL.md CHANGED
@@ -11,7 +11,7 @@ Es el **compañero** de [`@trycore/spec-product-flow`](https://www.npmjs.com/pac
11
11
  | CLI (bin) | `trycore-build` |
12
12
  | Plugin | `trycore-spec-build-harness` |
13
13
  | Marketplace | `trycore-build` |
14
- | Versión | `0.1.0` |
14
+ | Versión | `0.3.0` |
15
15
 
16
16
  Hay **dos canales** de instalación: el **CLI npm** (canónico, recomendado para operar en un
17
17
  proyecto) y el **plugin nativo** de Claude Code (conveniencia a nivel usuario). Lee el
@@ -28,7 +28,7 @@ npm install -g @trycore/spec-build-harness
28
28
  Esto expone el binario `trycore-build`. Comprueba la versión:
29
29
 
30
30
  ```bash
31
- trycore-build --version # → 0.1.0
31
+ trycore-build --version # → 0.3.0
32
32
  trycore-build --help
33
33
  ```
34
34
 
@@ -85,9 +85,9 @@ Qué hace `init`:
85
85
  1. **Verifica requisitos duros** (a menos que uses `--skip-doctor`).
86
86
  2. **Siembra los assets** en rutas nativas de Claude Code:
87
87
  - `.claude/agents/build/` — 10 agentes.
88
- - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`).
88
+ - `.claude/commands/opsx/` (10 comandos `/opsx:*`) y `.claude/commands/build/` (`/build:onboard`, `/build:reflect`).
89
89
  - `.claude/skills/` — 12 skills (`building-a-slice`, `releasing-a-version`, `openspec-*`).
90
- - `.claude/hooks/build/` — 6 hooks bash.
90
+ - `.claude/hooks/build/` — 8 hooks bash.
91
91
  3. **Siembra el estado**: `state/build-state.schema.json` y `state/README.md` se versionan;
92
92
  `state/build-state.json` se siembra **vacío y nunca se sobrescribe** (va al `.gitignore`).
93
93
  4. **Siembra `config/stack-allowlist.json`** (artefacto del consumidor; lo puebla `/build:onboard`).
@@ -184,6 +184,11 @@ Qué resuelve:
184
184
 
185
185
  Si un punto no aplica, se registra explícitamente "no aplica" (no se deja como `{{...}}`).
186
186
 
187
+ > **Durante la construcción** (no en la instalación), el comando `/build:reflect` captura las
188
+ > convenciones aprendidas de cada slice cerrado en el bloque `trycore-build-learnings` de
189
+ > `CLAUDE.md` —con tu aprobación— y lo **sugiere** el hook `reflect-nudge.sh` al cerrar sesión.
190
+ > Detalle en `docs/commands.md`.
191
+
187
192
  ---
188
193
 
189
194
  ## 5. Alternativa: plugin nativo (con caveat de canales)
@@ -228,7 +233,7 @@ Ambos paquetes coexisten en el mismo `.claude/` **sin colisión**, porque usan *
228
233
 
229
234
  | | Discovery — `spec-product-flow` | Construcción — `spec-build-harness` |
230
235
  |---|---|---|
231
- | Comandos | `/trycore:*` | `/opsx:*` + `/build:onboard` |
236
+ | Comandos | `/trycore:*` | `/opsx:*` + `/build:onboard` + `/build:reflect` |
232
237
  | Marca de versión | `.trycore-version` | `.build-harness-version` |
233
238
  | Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
234
239
 
package/METODOLOGIA.md CHANGED
@@ -28,7 +28,7 @@ Ambos paquetes **coexisten en el mismo `.claude/` sin colisión**, con namespace
28
28
 
29
29
  | Eje | Discovery (`spec-product-flow`) | Construcción (`spec-build-harness`) |
30
30
  |---|---|---|
31
- | Comandos | `trycore/` (`/trycore:*`) | `opsx/` + `build/` (`/opsx:*`, `/build:onboard`) |
31
+ | Comandos | `trycore/` (`/trycore:*`) | `opsx/` + `build/` (`/opsx:*`, `/build:onboard`, `/build:reflect`) |
32
32
  | Sentinela de versión | `.trycore-version` | `.build-harness-version` |
33
33
  | Bloque en `CLAUDE.md` | `<!-- BEGIN trycore-vertical -->` | `<!-- BEGIN trycore-build-harness -->` |
34
34
 
@@ -65,6 +65,15 @@ por slice. Y el inner loop **no** dispara reviewers pesados. Cada gate vive en e
65
65
 
66
66
  ## 2. La regla del "esqueleto que camina"
67
67
 
68
+ > **Paso 1 fundamental — el scaffold (precondición, NO generada por el arnés).** Antes del primer
69
+ > slice debe **existir** un *scaffold* runnable: el esqueleto del proyecto que arranca vacío (el
70
+ > script de build/dev corre sin error). El arnés lo **exige y bloquea** —gate de proyecto
71
+ > `scaffold.confirmed`, que solo pasa a `true` por **confirmación explícita** (no por auto-detección
72
+ > de `package.json`), respaldado por el hook determinista `scaffold-guard.sh`— pero **no lo genera**
73
+ > (es agnóstico al stack: el equipo lo crea según el PRD/allowlist). El *esqueleto que camina* se
74
+ > construye **encima** del scaffold: el scaffold es el shell vacío que arranca; el walking skeleton
75
+ > es el primer journey real más delgado. Sin scaffold confirmado no se abre ningún slice.
76
+
68
77
  El arnés prohíbe construir capas horizontales aisladas que "se juntan al final" (el anti-patrón que
69
78
  hace que todos los gates por-slice estén en verde y el producto aun así no funcione de punta a
70
79
  punta). En su lugar:
@@ -81,6 +90,15 @@ Consecuencia operativa: **la unidad de construcción es la épica, no la HU suel
81
90
  épica = un OpenSpec change = una rama = un PR. Las HU que cubre la épica son su *alcance interno* y
82
91
  se listan en `active_slice.hus[]`. Construir por HU individual es sobre-ingeniería.
83
92
 
93
+ > **Carril micro-change (mantenimiento).** Esta regla rige la **construcción de producto**. El
94
+ > **mantenimiento** que no es capacidad nueva —un typo, un ajuste de copy/config/docs, un bump de
95
+ > dependencia ya permitida, o un fix de pocas líneas sin nueva capacidad— **no se modela como
96
+ > épica**: usa el carril ligero de la skill `building-a-micro-change` (`fix/*`|`chore/*` → cambio →
97
+ > PR, sin abrir `active_slice`). No es una excepción a la regla, sino mantenimiento fuera de su
98
+ > alcance. **Límites duros:** si el cambio añade una dependencia nueva, crea un endpoint/API nuevo, o
99
+ > toca lógica de dominio o el modelo/invariantes de datos, **deja de ser micro-change** y se escala a
100
+ > épica. Ante la duda, es una épica.
101
+
84
102
  ---
85
103
 
86
104
  ## 3. Pipeline del inner loop, fase por fase (con sus gates)
@@ -108,7 +126,10 @@ Una épica **no entra a construcción** hasta cumplir todo (lo valida `dor-dod-g
108
126
  - La épica tiene ≥1 HU; todas las que entran se listan en `hus[]`.
109
127
  - Frontmatter completo en cada `docs/04-historias/HU-XXX.md` (`id, titulo, epica, prioridad,
110
128
  complejidad, estado`) con `estado: lista`.
111
- - AC en Given/When/Then por HU: 3–5 escenarios con happy + error + edge.
129
+ - AC en Given/When/Then por HU, **proporcional a `complejidad`** (cubre los modos de fallo que
130
+ *realmente existen*, no una cuota fija): `trivial`/baja → **1–2** (happy + error/edge crítico si
131
+ existe); `media` → **3** (happy + error + edge); `alta` → **3–5** (cobertura completa). Si existe
132
+ rama de error/edge, debe tener su escenario.
112
133
  - Cada HU pasa los 6 criterios **INVEST**.
113
134
  - Dependencias resueltas (las épicas/HU de las que depende están en `history[]` o no bloquean).
114
135
  - Cabe en el stack del PRD (no requiere tecnología fuera de `stack-allowlist.json`).
@@ -142,8 +163,9 @@ vía el hook `stack-guard.sh` en tiempo real, no vía subagente.
142
163
 
143
164
  - `ux` y `api` admiten `null` cuando el slice no tiene UI o endpoints. `null` ≠ abierto: **no
144
165
  bloquea** el DoD.
145
- - En `harness_phase: authoring` (sin `package.json`) se puede hacer `dor` + `change`, pero los gates
146
- de código (`tdd`, `journey_smoke`, `api`, `data`) **no se cierran** hasta scaffoldear.
166
+ - En `harness_phase: authoring` (sin `package.json`) se puede hacer la Fase 0 (crear/confirmar el
167
+ scaffold) + `dor` + `change`, pero los gates de código (`tdd`, `journey_smoke`, `api`, `data`) **no
168
+ se cierran** hasta tener el scaffold confirmado (gate de proyecto `scaffold.confirmed`, ver §2).
147
169
 
148
170
  ---
149
171
 
@@ -341,8 +363,10 @@ El arnés **no escribe** en `docs/`; cuando una HU no cumple DoR, devuelve el tr
341
363
 
342
364
  ## 10. Reglas duras (resumen)
343
365
 
344
- 1. La **épica** es la unidad de construcción: un slice = una épica = un change = una rama = un PR.
345
- Las HU son alcance interno (`hus[]`).
366
+ 1. La **épica** es la unidad de construcción de **producto**: un slice = una épica = un change = una
367
+ rama = un PR. Las HU son alcance interno (`hus[]`). El **mantenimiento** que no es producto nuevo
368
+ (typo, bump, copy, infra/docs, fix de pocas líneas) usa el carril `building-a-micro-change`, fuera
369
+ de esta regla; cruzar un límite duro (dep nueva, API nueva, dominio/datos) lo escala a épica.
346
370
  2. El **esqueleto que camina** nunca deja de caminar: nada de capas horizontales que se juntan al
347
371
  final; `journey_smoke` verde en cada épica.
348
372
  3. **Dos loops sin duplicación**: gates baratos por épica (inner), gates pesados una vez por release
package/README.md CHANGED
@@ -45,7 +45,7 @@ trycore-build init
45
45
  | `trycore-build update` | Refresca assets y schema tras `npm update -g`. No pisa estado ni allowlist. |
46
46
  | `trycore-build status` | Muestra el estado de la instalación y la fase activa del arnés. |
47
47
  | `trycore-build uninstall` | Quita el arnés. **Preserva** `.claude/state/` y `.claude/config/`. |
48
- | `trycore-build doctor` | Verifica requisitos externos (`openspec`/`python3`/`git`), hooks ejecutables y canales. |
48
+ | `trycore-build doctor` | Verifica requisitos externos (`openspec`/`python3`/`git`), hooks ejecutables y canales; reporta slices sin reflexionar y sugiere **LSP** en stacks tipados. |
49
49
 
50
50
  Flags de `init`: `--copy` (copiar en vez de symlinkear), `--yes` (no interactivo), `--skip-doctor`, `--force-init`, `--stack <deps>`, `--pkg-manager <pm>`, `--runtime <semver>`, `--prd-path <path>`.
51
51
 
@@ -59,7 +59,7 @@ El arnés modela la construcción como dos ciclos anidados, cada uno con su skil
59
59
  DoR → OpenSpec change → TDD → journey-smoke → api/data → DoD → PR + archive
60
60
  ```
61
61
 
62
- Por cada épica del backlog: se valida el **Definition of Ready**, se abre un *change* en OpenSpec, se desarrolla con **TDD**, se corre el smoke del journey, se verifican contratos de API y consistencia de datos, se valida el **Definition of Done** y se cierra con PR + archivado del change.
62
+ Por cada épica del backlog: se valida el **Definition of Ready**, se abre un *change* en OpenSpec, se desarrolla con **TDD**, se corre el smoke del journey, se verifican contratos de API y consistencia de datos, se valida el **Definition of Done** y se cierra con PR + archivado del change. Tras archivar, `/build:reflect` puede capturar las convenciones aprendidas del slice (**ciclo autocorrectivo**, opcional; lo sugiere el hook `reflect-nudge.sh`).
63
63
 
64
64
  ### Outer loop — `releasing-a-version` (una vez por release)
65
65
 
@@ -70,6 +70,7 @@ Gate de release que corre **una sola vez por versión** sobre el conjunto de sli
70
70
  | Command | Propósito |
71
71
  |---|---|
72
72
  | `/build:onboard` | Onboarding capa 2: lee el PRD, pregunta por PII/IA/determinismo/secretos, resuelve `{{placeholders}}` y escribe la auto-memory. |
73
+ | `/build:reflect` | Reflexión post-slice: tras archivar, propone convenciones aprendidas / errores recurrentes al bloque `trycore-build-learnings` de `CLAUDE.md` (tras tu aprobación) y marca el slice como reflexionado. |
73
74
  | `/opsx:explore` | Explora el dominio / specs antes de abrir un change. |
74
75
  | `/opsx:new` | Crea un nuevo OpenSpec change. |
75
76
  | `/opsx:continue` | Retoma un change en curso. |
@@ -91,9 +92,9 @@ trycore-spec-build-harness/
91
92
  ├── agents/build/ ← 10 agentes revisores (segunda opinión, contexto limpio)
92
93
  ├── commands/
93
94
  │ ├── opsx/ ← 10 comandos /opsx:* (ciclo OpenSpec)
94
- │ └── build/ ← /build:onboard
95
+ │ └── build/ ← /build:onboard, /build:reflect
95
96
  ├── skills/ ← 12 skills (building-a-slice, releasing-a-version, 10 openspec-*)
96
- ├── hooks/build/ ← 6 hooks bash (gate-check, gitflow-guard, stack-guard, …)
97
+ ├── hooks/build/ ← 8 hooks bash (gate-check, reflect-nudge, scaffold-guard, gitflow-guard, stack-guard, …)
97
98
  ├── state/ ← máquina de estado: build-state.json + schema + README
98
99
  ├── config/ ← stack-allowlist.template.json (artefacto del consumidor)
99
100
  ├── src/ + dist/ ← CLI trycore-build (init/update/status/uninstall/doctor)
@@ -123,7 +124,9 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
123
124
 
124
125
  ## Roadmap
125
126
 
126
- - ✅ **v0.1.0 (actual)** — arnés de dos loops (`building-a-slice` + `releasing-a-version`), 10 agentes, comandos `/opsx:*` + `/build:onboard`, 12 skills, 6 hooks, máquina de estado `build-state.json`, allowlist de stack, CLI `trycore-build` (init/update/status/uninstall/doctor) y plugin nativo. Compañero de `@trycore/spec-product-flow`.
127
+ - ✅ **v0.1.0** — arnés de dos loops (`building-a-slice` + `releasing-a-version`), 10 agentes, comandos `/opsx:*` + `/build:onboard`, 12 skills, 6 hooks, máquina de estado `build-state.json`, allowlist de stack, CLI `trycore-build` (init/update/status/uninstall/doctor) y plugin nativo. Compañero de `@trycore/spec-product-flow`.
128
+ - ✅ **v0.2.0** — scaffold como "Paso 1 fundamental": gate de proyecto `scaffold.confirmed` (confirmación **explícita**, no auto), Fase 0 en `building-a-slice`, criterio duro de DoR y hook `scaffold-guard.sh`. El arnés **exige** el scaffold pero **no lo genera**.
129
+ - ✅ **v0.3.0 (actual)** — **ciclo autocorrectivo** (hook `reflect-nudge.sh` + comando `/build:reflect`: propone convenciones aprendidas al bloque `trycore-build-learnings` de `CLAUDE.md` tras tu aprobación; campos `reflected`/`reflected_at`) y **LSP opt-in** (`docs/customization/lsp-extensions.md` + sugerencia en `doctor` para stacks tipados). Total: **8 hooks**; comandos `/opsx:*` + `/build:onboard` + `/build:reflect`.
127
130
 
128
131
  ## Licencia
129
132
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.4.0
@@ -9,6 +9,13 @@ Eres el **gatekeeper DoR/DoD** del arnés de construcción. Eres read-only sobre
9
9
  proponer la escritura del estado (no editas código de producto).
10
10
 
11
11
  ## Definition of Ready (gate `dor`) — antes de construir
12
+
13
+ **Precondición — Paso 1 fundamental (scaffold):** antes de validar nada más, exige
14
+ `scaffold.confirmed=true` en `build-state.json`. Si es `false`, **NO valides el DoR**: instruye
15
+ confirmar primero que existe un scaffold runnable del proyecto (ver `building-a-slice` Fase 0). El
16
+ arnés **no genera** el scaffold; lo exige. (El hook `scaffold-guard.sh` respalda este bloqueo en
17
+ las fases de código.)
18
+
12
19
  La unidad es la **épica**. Identifica `EP-XXX` en `docs/03-backlog/epicas.md` y el conjunto de HU
13
20
  que la componen (las que tienen `epica: EP-XXX` en `docs/04-historias/`). Pasa SOLO si **todas** se
14
21
  cumplen; lista cada una con ✓/✗:
@@ -16,7 +23,9 @@ cumplen; lista cada una con ✓/✗:
16
23
  2. Tiene **al menos una HU** asociada y todas se enumeran en `hus[]`.
17
24
  3. **Cada HU** de la épica: frontmatter YAML completo (`id, titulo, epica, prioridad, complejidad,
18
25
  estado`) y `estado: lista`.
19
- 4. **Cada HU**: AC en formato **Given/When/Then**, 3–5 escenarios con happy + error + edge.
26
+ 4. **Cada HU**: AC en formato **Given/When/Then**, **proporcional a `complejidad`** (`trivial`/baja
27
+ 1–2; `media` → 3; `alta` → 3–5), cubriendo los modos de fallo que existen (happy + error/edge
28
+ reales). No exijas 3–5 a una HU trivial; sí exige el escenario de toda rama de error/edge que exista.
20
29
  5. **Cada HU** pasa los 6 criterios **INVEST** (si dudas, invoca al agente `invest-validator`).
21
30
  6. Dependencias declaradas (otras épicas/HU) están en `history[]` del estado o marcadas done.
22
31
  7. El alcance de la épica cabe en el stack declarado del PRD (allowlist) (no exige tecnología fuera de la allowlist).
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: "BUILD: Reflect"
3
+ description: Reflexión post-slice (ciclo autocorrectivo). Tras cerrar/archivar un slice, detecta convenciones aprendidas y errores recurrentes, PROPONE viñetas para el bloque de aprendizajes de CLAUDE.md (se aplican SOLO con tu aprobación) y estampa el slice como reflexionado. Lo dispara el hook reflect-nudge.sh, pero puedes invocarlo cuando quieras.
4
+ category: Workflow
5
+ tags: [reflexion, conocimiento, ciclo-autocorrectivo, build-harness, trycore]
6
+ ---
7
+
8
+ Captura el **conocimiento tribal** de un slice recién cerrado antes de que se pierda. Un hook bash no
9
+ puede razonar qué se aprendió; **tú (el modelo) sí**. Tu trabajo: detectar convención nueva o error
10
+ recurrente, **proponérselo al usuario**, y —solo con su aprobación— escribirlo al bloque
11
+ `trycore-build-learnings` de `CLAUDE.md`. Nunca escribes sin confirmación.
12
+
13
+ Esta es la mejora del **pilar "ciclo autocorrectivo"**: el hook `reflect-nudge.sh` (evento `Stop`)
14
+ solo **sugiere** ejecutar esto cuando hay slices archivados sin reflexionar; el razonamiento vive
15
+ aquí, en el modelo.
16
+
17
+ ---
18
+
19
+ ## Preflight
20
+
21
+ ```bash
22
+ test -f .claude/.build-harness-version || echo "NOT_INSTALLED"
23
+ command -v python3 >/dev/null 2>&1 || echo "NO_PYTHON3"
24
+ ```
25
+
26
+ **Si `NOT_INSTALLED`:**
27
+
28
+ > Este proyecto no tiene el arnés de construcción instalado. Ejecuta primero `trycore-build init`.
29
+
30
+ Stop aquí si no está instalado o si falta `python3`.
31
+
32
+ ---
33
+
34
+ ## Fase 1: Identificar slices sin reflexionar
35
+
36
+ Lee `.claude/state/build-state.json` y filtra las entradas de `history[]` con `reflected != true`:
37
+
38
+ ```bash
39
+ python3 - <<'PY'
40
+ import json
41
+ d = json.load(open(".claude/state/build-state.json"))
42
+ pend = [h for h in (d.get("history") or []) if h.get("reflected") is not True]
43
+ for h in pend:
44
+ print(h.get("epica"), "·", h.get("openspec_change"), "·", h.get("branch"), "·", ",".join(h.get("hus") or []))
45
+ print("TOTAL", len(pend))
46
+ PY
47
+ ```
48
+
49
+ **Si `TOTAL 0`:** informa "No hay slices pendientes de reflexión ✅" y termina. No inventes trabajo.
50
+
51
+ Si hay varios, procésalos **de uno en uno** (el más reciente primero), o pregunta al usuario cuál.
52
+
53
+ ---
54
+
55
+ ## Fase 2: Recolectar señal del slice
56
+
57
+ Para el slice elegido, reúne evidencia **real** (no especules):
58
+
59
+ 1. **El change OpenSpec**: `openspec show <openspec_change>` y/o los archivos del change
60
+ (proposal/tasks). ¿Qué se decidió y qué tasks costaron más?
61
+ 2. **El diff de la rama**: `git log --oneline <branch>` y `git diff main...<branch> --stat` (si la
62
+ rama o sus commits existen). ¿Qué patrón de código se repitió? ¿Qué se reescribió varias veces?
63
+ 3. **La sesión actual**: qué hooks se dispararon más de una vez (p.ej. `stack-guard.sh`,
64
+ `gitflow-guard.sh`, `lint-typecheck.sh`), qué gates retrocedieron, qué correcciones se repitieron.
65
+
66
+ Con esa evidencia, detecta **0–3** aprendizajes candidatos, de estas categorías:
67
+
68
+ - **Convención nueva**: un patrón de código/estructura/nombrado que se repitió y conviene fijar para
69
+ el equipo (ej. "los handlers validan en el borde con el esquema X antes de tocar el dominio").
70
+ - **Error recurrente**: algo que un gate o hook atrapó >1 vez y vale la pena prevenir
71
+ (ej. "recordar correr migraciones antes del journey-smoke").
72
+ - **Fricción del propio arnés**: si el aprendizaje es sobre el arnés (un gate confuso, un paso que
73
+ sobra), NO lo escribas a CLAUDE.md — anótalo como candidato para `internal/skills/auditar-arnes`
74
+ y dilo al usuario.
75
+
76
+ Si no hay nada que valga la pena fijar, es válido **no proponer aprendizajes** (solo estampar, Fase 5).
77
+
78
+ ---
79
+
80
+ ## Fase 3: Proponer (con aprobación explícita)
81
+
82
+ Por **cada** aprendizaje candidato, usa **AskUserQuestion** para que el usuario lo apruebe, edite o
83
+ descarte. Presenta cada uno como una **viñeta concreta y accionable**, lista para CLAUDE.md. Ejemplo
84
+ de opciones: "Agregar tal cual" (Recomendado) · "Editar" · "Descartar".
85
+
86
+ **Reglas:**
87
+ - Una viñeta = una convención. Concreta, verificable, en presente imperativo. Nada vago.
88
+ - No propongas conocimiento que ya está en el bloque (léelo antes). No dupliques.
89
+ - Si un aprendizaje es **personal/cross-proyecto** (no de equipo), ofrécelo para **auto-memory**
90
+ (`type: project`/`user`) en vez de CLAUDE.md — pregunta cuál destino.
91
+
92
+ ---
93
+
94
+ ## Fase 4: Aplicar SOLO lo aprobado al bloque de aprendizajes
95
+
96
+ Para las viñetas aprobadas, **inserta** (no reemplaces) dentro del bloque marcado de `CLAUDE.md`,
97
+ **justo antes** de `<!-- END trycore-build-learnings -->`:
98
+
99
+ ```markdown
100
+ - (EP-XXX) <viñeta aprobada>
101
+ ```
102
+
103
+ - Prefija cada viñeta con la épica del slice `(EP-XXX)` para trazabilidad.
104
+ - **NO toques nada fuera del bloque** `trycore-build-learnings`. En particular, NO modifiques el
105
+ bloque `trycore-build-harness` ni sus `{{placeholders}}` de dominio (eso es de `/build:onboard`).
106
+ - Si el bloque `trycore-build-learnings` no existe (instalación vieja), pide correr
107
+ `trycore-build update` antes de continuar.
108
+ - Las viñetas son cambio de equipo: se revisan en PR como cualquier otra línea de `CLAUDE.md`.
109
+
110
+ ---
111
+
112
+ ## Fase 5: Estampar el slice como reflexionado
113
+
114
+ Marca el/los slice(s) procesado(s) en `history[]` para que el nudge calle. Usa la hora UTC real:
115
+
116
+ ```bash
117
+ NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
118
+ python3 - "$NOW" "<openspec_change>" <<'PY'
119
+ import json, sys
120
+ now, change = sys.argv[1], sys.argv[2]
121
+ p = ".claude/state/build-state.json"
122
+ d = json.load(open(p))
123
+ for h in d.get("history") or []:
124
+ if h.get("openspec_change") == change:
125
+ h["reflected"] = True
126
+ h["reflected_at"] = now
127
+ json.dump(d, open(p, "w"), indent=2, ensure_ascii=False)
128
+ print("estampado:", change)
129
+ PY
130
+ ```
131
+
132
+ Estampa **aunque no haya habido aprendizajes** (reflexionar y no encontrar nada también cierra el
133
+ ciclo). Repite por cada slice procesado.
134
+
135
+ ---
136
+
137
+ ## Fase 6: Confirmación
138
+
139
+ ```
140
+ ## ✓ Reflexión completada
141
+
142
+ Slice: <EP-XXX> (<openspec_change>)
143
+ Aprendizajes: <N agregados a CLAUDE.md> · <M a auto-memory> · <0 si solo se estampó>
144
+ Estampado: reflected=true
145
+
146
+ Pendientes de reflexión restantes: <K>
147
+ ```
148
+
149
+ Si quedan slices pendientes (`K > 0`), ofrece continuar con el siguiente.
150
+
151
+ ---
152
+
153
+ ## Guardrails
154
+
155
+ - **Aprobación explícita siempre.** Nunca escribes a CLAUDE.md sin que el usuario apruebe cada
156
+ viñeta. Espeja la regla del scaffold: el arnés propone, el humano confirma.
157
+ - **Evidencia, no invención.** Cada aprendizaje sale del change/diff/sesión reales. Si no hay
158
+ evidencia, no propongas.
159
+ - **No reescribas.** Inserta viñetas; jamás borres ni reemplaces aprendizajes previos del equipo.
160
+ - **No toques el paquete.** No modifiques agentes/skills/hooks del arnés (eso es mantenimiento, ver
161
+ `internal/skills/auditar-arnes`).
162
+ - **Estampa siempre** al terminar, para no re-molestar con el mismo slice.
163
+ - Si algo aquí contradice `METODOLOGIA.md`, **gana la metodología**.
@@ -59,6 +59,26 @@ export async function doctor(opts) {
59
59
  else {
60
60
  console.log(' — sin hooks instalados (corre `trycore-build init`)');
61
61
  }
62
+ // Gate de scaffold (Paso 1 fundamental)
63
+ console.log('');
64
+ if (fs.existsSync(t.stateFile)) {
65
+ try {
66
+ const st = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
67
+ const confirmed = st?.scaffold?.confirmed === true;
68
+ console.log(`Scaffold (Paso 1): ${confirmed ? '✓ confirmado' : '✗ pendiente — confírmalo en building-a-slice (Fase 0) antes de fases de código'}`);
69
+ const hist = Array.isArray(st?.history) ? st.history : [];
70
+ const pend = hist.filter((h) => h?.reflected !== true).length;
71
+ if (pend > 0) {
72
+ console.log(`Reflexión: ${pend} slice(s) archivado(s) sin reflexionar — ejecuta /build:reflect`);
73
+ }
74
+ }
75
+ catch {
76
+ console.log('Scaffold (Paso 1): ⚠ build-state.json malformado');
77
+ }
78
+ }
79
+ else {
80
+ console.log('Scaffold (Paso 1): — (sin build-state.json; corre `trycore-build init`)');
81
+ }
62
82
  // Detección de doble canal (CLI settings.json + plugin) [H3]
63
83
  const settingsHasHooks = fs.existsSync(t.settingsFile) &&
64
84
  /hooks\/build\//.test(fs.readFileSync(t.settingsFile, 'utf8'));
@@ -68,6 +88,14 @@ export async function doctor(opts) {
68
88
  console.log(' Si además instalaste el plugin nativo, la cadena de comando es idéntica:');
69
89
  console.log(' Claude Code deduplica y el hook dispara UNA vez. No requiere acción.');
70
90
  }
91
+ // Sugerencia LSP para stacks tipados (informativa, NO es requisito) [pilar LSP]
92
+ const typed = detectTypedStack(targetDir);
93
+ if (typed) {
94
+ console.log('');
95
+ console.log(`ℹ Stack tipado detectado (${typed}). Considera activar LSP para precisión`);
96
+ console.log(' semántica (seguir símbolos/referencias como un compilador, mejor ROI que grep');
97
+ console.log(' en código tipado) — ver docs/customization/lsp-extensions.md');
98
+ }
71
99
  console.log('═══════════════════════════════════════');
72
100
  if (dep.missingHard.length > 0) {
73
101
  console.error(`\n✗ Faltan requisitos duros: ${dep.missingHard.join(', ')}. Instálalos antes de operar el arnés.`);
@@ -75,3 +103,25 @@ export async function doctor(opts) {
75
103
  }
76
104
  console.log('\n✓ Requisitos satisfechos.');
77
105
  }
106
+ /** Señales de stack tipado donde LSP rinde (informativo; nunca un requisito). */
107
+ function detectTypedStack(dir) {
108
+ const direct = [
109
+ ['pom.xml', 'Java/Maven'],
110
+ ['build.gradle', 'Java/Gradle'],
111
+ ['build.gradle.kts', 'Kotlin/Gradle'],
112
+ ['composer.json', 'PHP/Composer'],
113
+ ['tsconfig.json', 'TypeScript'],
114
+ ];
115
+ for (const [f, label] of direct) {
116
+ if (fs.existsSync(path.join(dir, f)))
117
+ return label;
118
+ }
119
+ try {
120
+ if (fs.readdirSync(dir).some((e) => e.endsWith('.csproj') || e.endsWith('.sln')))
121
+ return 'C#/.NET';
122
+ }
123
+ catch {
124
+ /* dir ilegible: sin sugerencia */
125
+ }
126
+ return null;
127
+ }
@@ -15,6 +15,8 @@ import { captureStack, renderAllowlist } from '../lib/stack-prompt.js';
15
15
  import { checkDeps } from './doctor.js';
16
16
  const BEGIN = '<!-- BEGIN trycore-build-harness ';
17
17
  const END = '<!-- END trycore-build-harness -->';
18
+ const LEARN_BEGIN = '<!-- BEGIN trycore-build-learnings -->';
19
+ const LEARN_END = '<!-- END trycore-build-learnings -->';
18
20
  const GI_BEGIN = '# ── trycore-build-harness (auto)';
19
21
  const GI_END = '# ── /trycore-build-harness';
20
22
  export async function init(opts) {
@@ -87,20 +89,41 @@ function syncClaudeMdBlock(targetDir, version) {
87
89
  const template = fs
88
90
  .readFileSync(ASSETS.templateClaudeMd, 'utf8')
89
91
  .replace(/\{\{BUILD_HARNESS_VERSION\}\}/g, version);
90
- const lines = template.split('\n');
91
- const b = lines.findIndex((l) => l.includes(BEGIN));
92
- const e = lines.findIndex((l) => l.includes(END));
93
- if (b === -1 || e === -1)
94
- throw new Error('CLAUDE.md.template sin markers BEGIN/END trycore-build-harness');
95
- const blockContent = lines.slice(b, e + 1).join('\n');
92
+ // CLAUDE.md nuevo: el template completo ya incluye ambos bloques (arnés + aprendizajes).
96
93
  if (!fs.existsSync(t.claudeMd)) {
97
94
  fs.writeFileSync(t.claudeMd, template, 'utf8');
98
95
  console.log(' ✓ CLAUDE.md creado desde template');
96
+ return;
97
+ }
98
+ // Bloque del arnés: siempre upsert (refresca versión/contenido en cada init/update).
99
+ const harnessBlock = extractBlock(template, BEGIN, END);
100
+ const action = upsertMarkedBlock({ beginMarker: BEGIN, endMarker: END, blockContent: harnessBlock, filePath: t.claudeMd });
101
+ console.log(` ✓ Bloque trycore-build-harness ${action === 'replaced' ? 'actualizado' : 'anexado'} en CLAUDE.md`);
102
+ // Bloque de aprendizajes: SOLO sembrar si falta. NUNCA reemplazar — un update no debe
103
+ // borrar las convenciones que /build:reflect acumuló.
104
+ const current = fs.readFileSync(t.claudeMd, 'utf8');
105
+ if (!current.includes(LEARN_BEGIN)) {
106
+ const learnBlock = extractBlock(template, LEARN_BEGIN, LEARN_END);
107
+ upsertMarkedBlock({ beginMarker: LEARN_BEGIN, endMarker: LEARN_END, blockContent: learnBlock, filePath: t.claudeMd });
108
+ console.log(' ✓ Bloque trycore-build-learnings sembrado en CLAUDE.md');
99
109
  }
100
- else {
101
- const action = upsertMarkedBlock({ beginMarker: BEGIN, endMarker: END, blockContent, filePath: t.claudeMd });
102
- console.log(` ✓ Bloque trycore-build-harness ${action === 'replaced' ? 'actualizado' : 'anexado'} en CLAUDE.md`);
110
+ }
111
+ /** Extrae el bloque [begin..end] inclusive de un texto (para sembrar desde el template). */
112
+ function extractBlock(template, begin, end) {
113
+ const lines = template.split('\n');
114
+ const b = lines.findIndex((l) => l.includes(begin));
115
+ if (b === -1)
116
+ throw new Error(`CLAUDE.md.template sin marker de inicio: ${begin}`);
117
+ let e = -1;
118
+ for (let i = b; i < lines.length; i++) {
119
+ if (lines[i].includes(end)) {
120
+ e = i;
121
+ break;
122
+ }
103
123
  }
124
+ if (e === -1)
125
+ throw new Error(`CLAUDE.md.template sin marker de fin: ${end}`);
126
+ return lines.slice(b, e + 1).join('\n');
104
127
  }
105
128
  function syncGitignoreBlock(targetDir, mode) {
106
129
  const t = targetPaths(targetDir);
@@ -38,6 +38,7 @@ export async function status(opts) {
38
38
  console.log('');
39
39
  console.log('Estado del arnés:');
40
40
  console.log(` Fase: ${st.harness_phase ?? '?'}`);
41
+ console.log(` Scaffold: ${st.scaffold?.confirmed === true ? '✓ confirmado (Paso 1)' : '✗ pendiente (Paso 1)'}`);
41
42
  const slice = st.active_slice;
42
43
  if (slice) {
43
44
  const openGates = Object.entries(slice.gates ?? {})
@@ -50,6 +51,10 @@ export async function status(opts) {
50
51
  console.log(' Slice activo: ninguno');
51
52
  }
52
53
  console.log(` Historial: ${(st.history ?? []).length} slice(s) · Releases: ${(st.releases ?? []).length}`);
54
+ const sinReflexionar = (st.history ?? []).filter((h) => h?.reflected !== true).length;
55
+ if (sinReflexionar > 0) {
56
+ console.log(` Sin reflexionar: ${sinReflexionar} slice(s) — ejecuta /build:reflect`);
57
+ }
53
58
  }
54
59
  catch {
55
60
  console.log(' ⚠ build-state.json malformado');
@@ -30,8 +30,9 @@ export async function uninstall(opts) {
30
30
  // 2) Marca de versión
31
31
  if (fs.existsSync(t.versionFile))
32
32
  fs.rmSync(t.versionFile, { force: true });
33
- // 3) Bloques marcados
33
+ // 3) Bloques marcados (arnés + aprendizajes + .gitignore)
34
34
  removeMarkedBlock(t.claudeMd, BEGIN, END);
35
+ removeMarkedBlock(t.claudeMd, LEARN_BEGIN, LEARN_END);
35
36
  removeMarkedBlock(t.gitignore, GI_BEGIN, GI_END);
36
37
  // 4) Hooks + permisos del arnés en settings.json (por cadena exacta)
37
38
  removeHarnessSettings(t.settingsFile);
@@ -40,6 +41,8 @@ export async function uninstall(opts) {
40
41
  }
41
42
  const BEGIN = '<!-- BEGIN trycore-build-harness ';
42
43
  const END = '<!-- END trycore-build-harness -->';
44
+ const LEARN_BEGIN = '<!-- BEGIN trycore-build-learnings -->';
45
+ const LEARN_END = '<!-- END trycore-build-learnings -->';
43
46
  const GI_BEGIN = '# ── trycore-build-harness (auto)';
44
47
  const GI_END = '# ── /trycore-build-harness';
45
48
  function isSymlink(p) {
@@ -18,9 +18,9 @@ function cmd(script) {
18
18
  const HOOK_SPECS = [
19
19
  { event: 'SessionStart', matcher: 'startup|clear|compact', scripts: ['load-build-state.sh'] },
20
20
  { event: 'PreToolUse', matcher: 'Bash', scripts: ['gitflow-guard.sh'] },
21
- { event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh'] },
21
+ { event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh'] },
22
22
  { event: 'PostToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['lint-typecheck.sh', 'coherence-flag.sh'] },
23
- { event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh'] },
23
+ { event: 'Stop', matcher: '.*', scripts: ['build-gate-check.sh', 'reflect-nudge.sh'] },
24
24
  ];
25
25
  /** Permisos MÍNIMOS y enumerados [H11]. Nunca permisos amplios (mcp__*, additionalDirectories…). */
26
26
  const MIN_PERMISSIONS = [
@@ -10,6 +10,7 @@ import { ensureDir } from './install-engine.js';
10
10
  const EMPTY_STATE = {
11
11
  version: '1.0',
12
12
  harness_phase: 'authoring',
13
+ scaffold: { confirmed: false, confirmed_by: null, confirmed_at: null, notes: '' },
13
14
  active_slice: null,
14
15
  history: [],
15
16
  releases: [],