@trycore/spec-build-harness 0.2.0 → 0.5.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 (40) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/GOVERNANCE.md +24 -5
  3. package/INSTALL.md +11 -6
  4. package/METODOLOGIA.md +18 -4
  5. package/README.md +12 -7
  6. package/VERSION +1 -1
  7. package/agents/build/build-orchestrator.md +6 -1
  8. package/agents/build/dor-dod-gatekeeper.md +13 -6
  9. package/agents/build/ux-fidelity-reviewer.md +61 -0
  10. package/commands/build/onboard.md +20 -4
  11. package/commands/build/reflect.md +163 -0
  12. package/dist/commands/doctor.js +35 -0
  13. package/dist/commands/init.js +32 -9
  14. package/dist/commands/status.js +4 -0
  15. package/dist/commands/uninstall.js +4 -1
  16. package/dist/lib/settings-merge.js +2 -2
  17. package/dist/lib/state-seed.js +1 -0
  18. package/docs/agents.md +2 -1
  19. package/docs/commands.md +10 -4
  20. package/docs/customization/lsp-extensions.md +90 -0
  21. package/docs/customization/mcp-extensions.md +3 -0
  22. package/docs/getting-started.md +14 -5
  23. package/docs/hooks.md +30 -8
  24. package/hooks/build/design-source-guard.sh +52 -0
  25. package/hooks/build/lint-typecheck.sh +21 -1
  26. package/hooks/build/reflect-nudge.sh +29 -0
  27. package/hooks/build-harness.json +8 -0
  28. package/package.json +2 -1
  29. package/skills/building-a-micro-change/SKILL.md +78 -0
  30. package/skills/building-a-slice/SKILL.md +26 -1
  31. package/skills/building-a-slice/references/dod.md +4 -0
  32. package/skills/building-a-slice/references/dor.md +5 -2
  33. package/skills/building-a-slice/references/gitflow.md +3 -1
  34. package/skills/building-a-slice/references/mcp-map.md +1 -0
  35. package/state/README.md +21 -0
  36. package/state/build-state.schema.json +19 -1
  37. package/state/build-state.template.json +8 -0
  38. package/templates/CLAUDE.md.template +8 -1
  39. package/templates/settings-hooks.template.json +1 -1
  40. package/docs/superpowers/specs/2026-06-02-scaffold-gate-design.md +0 -87
@@ -66,6 +66,11 @@ export async function doctor(opts) {
66
66
  const st = JSON.parse(fs.readFileSync(t.stateFile, 'utf8'));
67
67
  const confirmed = st?.scaffold?.confirmed === true;
68
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
+ }
69
74
  }
70
75
  catch {
71
76
  console.log('Scaffold (Paso 1): ⚠ build-state.json malformado');
@@ -83,6 +88,14 @@ export async function doctor(opts) {
83
88
  console.log(' Si además instalaste el plugin nativo, la cadena de comando es idéntica:');
84
89
  console.log(' Claude Code deduplica y el hook dispara UNA vez. No requiere acción.');
85
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
+ }
86
99
  console.log('═══════════════════════════════════════');
87
100
  if (dep.missingHard.length > 0) {
88
101
  console.error(`\n✗ Faltan requisitos duros: ${dep.missingHard.join(', ')}. Instálalos antes de operar el arnés.`);
@@ -90,3 +103,25 @@ export async function doctor(opts) {
90
103
  }
91
104
  console.log('\n✓ Requisitos satisfechos.');
92
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);
@@ -51,6 +51,10 @@ export async function status(opts) {
51
51
  console.log(' Slice activo: ninguno');
52
52
  }
53
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
+ }
54
58
  }
55
59
  catch {
56
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', 'scaffold-guard.sh'] },
21
+ { event: 'PreToolUse', matcher: 'Write|Edit|MultiEdit', scripts: ['stack-guard.sh', 'scaffold-guard.sh', 'design-source-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 = [
@@ -11,6 +11,7 @@ const EMPTY_STATE = {
11
11
  version: '1.0',
12
12
  harness_phase: 'authoring',
13
13
  scaffold: { confirmed: false, confirmed_by: null, confirmed_at: null, notes: '' },
14
+ design_source: { applies: false, confirmed: false, confirmed_by: null, confirmed_at: null, source: '', notes: '' },
14
15
  active_slice: null,
15
16
  history: [],
16
17
  releases: [],
package/docs/agents.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agentes de construcción (`agents/build/`)
2
2
 
3
- Los **10 agentes** de `@trycore/spec-build-harness` ejecutan los *gates* del arnés de
3
+ Los **11 agentes** de `@trycore/spec-build-harness` ejecutan los *gates* del arnés de
4
4
  construcción de dos loops. Ninguno edita código de producto: son read-only sobre el
5
5
  repositorio (algunos ejecutan tests o levantan la app), diagnostican y **devuelven el
6
6
  veredicto al `build-orchestrator`**, que es quien propone la escritura del estado
@@ -27,6 +27,7 @@ veredicto al `build-orchestrator`**, que es quien propone la escritura del estad
27
27
  | 8 | `api-contract-tester` | sonnet | Contrato / datos | Gate `api` — pruebas de contrato (Newman/Postman) |
28
28
  | 9 | `data-consistency-checker` | sonnet | Contrato / datos | Gate `data` — invariantes y consistencia de datos |
29
29
  | 10 | `change-epic-coherence` | sonnet | Trazabilidad | Gate `coherence_link` — enlace change↔épica↔HU |
30
+ | 11 | `ux-fidelity-reviewer` | sonnet | Inner loop · smoke | Gate `fidelity` — fidelidad visual a la fuente de diseño declarada |
30
31
 
31
32
  ---
32
33
 
package/docs/commands.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Esta referencia cubre los **dos planos de operación** del arnés de construcción:
4
4
 
5
- 1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.1.0) — instala, actualiza, diagnostica y desinstala el arnés en el proyecto consumidor. Captura el **stack mecánico**.
6
- 2. Los **slash commands de Claude Code** (`/opsx:*` + `/build:onboard`) — operan el pipeline de dos loops y resuelven la parametrización **semántica** del dominio.
5
+ 1. El **CLI `trycore-build`** (binario Node, paquete `@trycore/spec-build-harness` v0.3.0) — instala, actualiza, diagnostica y desinstala el arnés en el proyecto consumidor. Captura el **stack mecánico**.
6
+ 2. Los **slash commands de Claude Code** (`/opsx:*` + `/build:onboard` + `/build:reflect`) — operan el pipeline de dos loops, resuelven la parametrización **semántica** del dominio y capturan el conocimiento aprendido por slice.
7
7
 
8
8
  > **División de responsabilidades del onboarding (dos capas).** Un binario Node **no puede** escribir la auto-memory de Claude. Por eso `trycore-build init` siembra archivos y captura el stack mecánico (lenguaje/deps, package manager, runtime, ruta del PRD), y el slash command `/build:onboard` —ejecutado por Claude— lee el PRD, pregunta por PII / capa de servicios externos-IA / capa determinista / secretos / decisiones de alto impacto, resuelve los `{{placeholders}}` del bloque marcado de `CLAUDE.md` y escribe la auto-memory.
9
9
 
@@ -52,7 +52,7 @@ Solo `init` y `update` aceptan flags. `status`, `uninstall` y `doctor` toman ún
52
52
 
53
53
  ## 2. Slash commands de Claude Code
54
54
 
55
- El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → `/build:onboard`.
55
+ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y `.claude/commands/build/` → `/build:onboard` y `/build:reflect`.
56
56
 
57
57
  ### `/opsx:*` — pipeline OpenSpec
58
58
 
@@ -75,6 +75,12 @@ El canal CLI namespacea por subcarpeta: `.claude/commands/opsx/` → `/opsx:*` y
75
75
  |---|---|
76
76
  | `/build:onboard` | Parametriza el dominio del arnés: capa de servicios externos/IA, lógica determinista, PII, secretos y decisiones de alto impacto. Lee el PRD, pregunta vía AskUserQuestion, rellena el bloque marcado de `CLAUDE.md` y escribe la auto-memory. **Complementa** a `trycore-build init` (que ya sembró archivos y el stack mecánico). |
77
77
 
78
+ ### `/build:reflect` — reflexión post-slice (ciclo autocorrectivo)
79
+
80
+ | Slash command | Propósito |
81
+ |---|---|
82
+ | `/build:reflect` | Tras archivar un slice, detecta **convenciones aprendidas** / errores recurrentes (a partir del change, el diff y la sesión) y **propone** viñetas para el bloque `trycore-build-learnings` de `CLAUDE.md`; las aplica **solo tras tu aprobación** y estampa el slice como reflexionado (`reflected: true`). Lo **sugiere** el hook `reflect-nudge.sh` al cerrar sesión, pero puedes invocarlo cuando quieras. No toca el bloque de dominio ni sus `{{placeholders}}` (eso es de `/build:onboard`). |
83
+
78
84
  ---
79
85
 
80
86
  ## 3. Caveat de canales (CLI vs. plugin)
@@ -84,7 +90,7 @@ El arnés se distribuye por **dos canales** que coexisten, pero **namespacean di
84
90
  | Aspecto | Canal **CLI** (canónico) | Canal **Plugin** nativo |
85
91
  |---|---|---|
86
92
  | Instalación | `npm i -g @trycore/spec-build-harness` → `trycore-build init` | `/plugin marketplace add <repo-github>` → `/plugin install trycore-spec-build-harness@trycore-build` |
87
- | Namespace de comandos | Por subcarpeta: `/opsx:*` y `/build:onboard` | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
93
+ | Namespace de comandos | Por subcarpeta: `/opsx:*`, `/build:onboard` y `/build:reflect` | Por nombre del plugin: `/trycore-spec-build-harness:*` (por diseño de Claude Code) |
88
94
  | Referencia a agentes | Por su nombre (p. ej. `build-orchestrator`) | Bajo el nombre del plugin |
89
95
  | Cross-references internas | ✔ Escritas para este canal (skills invocan `/opsx:*`, agentes por nombre) | Pueden no resolver según están escritas |
90
96
  | Recomendación | **Usar este canal para operar un proyecto** | Conveniencia a nivel usuario |
@@ -0,0 +1,90 @@
1
+ # Extensión LSP del arnés (opt-in, agnóstico)
2
+
3
+ > Guía de personalización para **consumidores** de `@trycore/spec-build-harness`. Explica cómo
4
+ > aprovechar **LSP** (Language Server Protocol) para que Claude Code navegue tu código con
5
+ > **precisión de símbolo** en vez de búsqueda de texto. **Todo lo de aquí es opcional.** El core es
6
+ > 100 % agnóstico: ningún gate, skill ni agente **depende** de LSP. Sin LSP, el arnés funciona igual
7
+ > navegando con `grep`/`glob` + lectura dirigida.
8
+
9
+ ## TL;DR
10
+
11
+ - **LSP = precisión de compilador** para Claude Code: seguir la definición exacta de un símbolo,
12
+ distinguir homónimos, listar referencias cruzadas. En monorepos tipados, `grep` devuelve ruido y
13
+ falsos positivos; LSP no.
14
+ - **Mayor ROI en lenguajes tipados**: Java/Kotlin, C#, C/C++, PHP, TypeScript. `trycore-build doctor`
15
+ detecta señales de estos stacks y **sugiere** activarlo (es una nota informativa, **no** un
16
+ requisito).
17
+ - **Complementa, no reemplaza** a MCP: LSP es precisión **intra-repo** (símbolos del código); MCP es
18
+ conectividad a sistemas **externos** (Jira, BD, telemetría). Ver `mcp-extensions.md`.
19
+ - Regla de oro: **opt-in**. Lo habilitas **tú** en tu entorno; el arnés solo lo recomienda donde
20
+ rinde.
21
+
22
+ ## Por qué LSP y no solo `grep`
23
+
24
+ El arnés define cada gate por su **resultado verificable**, no por la herramienta que lo produce
25
+ (misma filosofía que las extensiones MCP). Pero la fase de **exploración/navegación** del código —que
26
+ alimenta a los agentes de coherencia, diseño y arquitectura— mejora drásticamente con precisión
27
+ semántica:
28
+
29
+ | Tarea de navegación | Con `grep`/`glob` | Con LSP habilitado |
30
+ |---|---|---|
31
+ | "¿Dónde se define este método?" | Coincidencias de texto; homónimos mezclados. | La **definición exacta**, sin ruido. |
32
+ | "¿Quién usa este símbolo?" | Falsos positivos (comentarios, strings, nombres parecidos). | Referencias **reales** del compilador. |
33
+ | "Trazar símbolo → test" | Lectura manual encadenada. | Salto directo definición↔referencias. |
34
+ | "¿Hay duplicación / código muerto?" | Difícil de afirmar con texto. | Referencias vacías = candidato a muerto. |
35
+
36
+ Por eso, en stacks tipados grandes, LSP es la inversión de mayor ROI para la fase de exploración: el
37
+ subagente que **lee** (mapea el subsistema) devuelve una síntesis más fiel, protegiendo la ventana de
38
+ contexto para la fase de **edición**.
39
+
40
+ ## Cuándo rinde (por stack)
41
+
42
+ | Stack | Señal que detecta `doctor` | Por qué rinde LSP |
43
+ |---|---|---|
44
+ | Java / Kotlin | `pom.xml`, `build.gradle(.kts)` | Tipado fuerte + dependencias profundas (Spring): `grep` se ahoga. |
45
+ | C# / .NET | `*.csproj`, `*.sln` | Símbolos y namespaces densos; refactors guiados por tipo. |
46
+ | C / C++ | (no autodetectado; CMake/Make ambiguos) | Macros y headers hacen inútil la búsqueda de texto. |
47
+ | PHP | `composer.json` | Autoload + magia dinámica: seguir definiciones reales ayuda. |
48
+ | TypeScript | `tsconfig.json` | Tipos estructurales; el LSP de TS distingue lo que `grep` no. |
49
+
50
+ En lenguajes **dinámicos sin tipos** (p.ej. scripts sueltos), el ROI de LSP es menor; ahí `grep`/`glob`
51
+ suele bastar y el arnés no lo sugiere.
52
+
53
+ ## Cómo activarlo (en TU entorno)
54
+
55
+ LSP es una capacidad **del cliente Claude Code**, no algo que el arnés instale o versione:
56
+
57
+ 1. Instala el **language server** de tu stack en tu máquina/imagen de dev (cada lenguaje tiene el
58
+ suyo; sigue la doc del servidor que elijas).
59
+ 2. Habilita la integración LSP en **tu** configuración de Claude Code (a nivel proyecto o usuario),
60
+ no en el arnés.
61
+ 3. A partir de ahí, Claude Code puede navegar con precisión semántica durante la fase de exploración
62
+ de `building-a-slice` y para los agentes `coherence-three-way`, `simple-design-reviewer` y
63
+ `stack-guardian`.
64
+
65
+ > El arnés nunca levanta el servidor por ti ni versiona su configuración: la frontera (qué servidor,
66
+ > con qué permisos) es **tuya**.
67
+
68
+ ## Relación con MCP
69
+
70
+ No son lo mismo y conviven:
71
+
72
+ - **LSP** → precisión de **símbolo** dentro del repo (definiciones, referencias, jerarquía de tipos).
73
+ - **MCP** → conectividad a **sistemas externos** fuera del repo (tickets, BD, navegador, carga).
74
+
75
+ Un mismo proyecto puede usar ambos: LSP para entender el código tipado, MCP (opt-in) para acelerar
76
+ gates que necesitan evidencia externa. Detalle de MCP por gate en `mcp-extensions.md` y el mapa
77
+ operativo en `skills/building-a-slice/references/mcp-map.md`.
78
+
79
+ ## Reglas de uso
80
+
81
+ 1. **Opt-in.** El arnés solo **sugiere** LSP (vía `doctor`); habilitarlo es decisión del consumidor.
82
+ 2. **No es dependencia.** Ningún gate exige LSP. Si no está, la navegación cae a `grep`/`glob` y los
83
+ gates se cierran igual por su resultado verificable.
84
+ 3. **Portabilidad.** No escribas en agentes/skills/hooks del arnés instrucciones que **requieran**
85
+ LSP: manténlo como acelerador documentado.
86
+
87
+ ---
88
+
89
+ **Fuente de verdad.** Si algo aquí contradice la metodología Trycore (`METODOLOGIA.md`), **gana la
90
+ metodología**. Para extensiones MCP (y la relación MCP↔LSP por gate), ver `mcp-extensions.md`.
@@ -6,6 +6,9 @@
6
6
  > gate, skill ni agente **depende** de un MCP concreto. Si tu entorno no expone ningún MCP, el arnés
7
7
  > funciona igual cubriendo cada fase con sus alternativas CLI/test.
8
8
 
9
+ > **LSP tiene guía dedicada.** Este documento cubre sobre todo **MCP**. Para **LSP** (precisión de
10
+ > símbolo en stacks tipados: por qué, cuándo y cómo), ver **`lsp-extensions.md`**.
11
+
9
12
  ## TL;DR
10
13
 
11
14
  - Los gates **pueden apoyarse** en MCP/LSP como **aceleradores**, nunca como dependencia dura.
@@ -73,12 +73,12 @@ trycore-build init
73
73
 
74
74
  `init` es **idempotente** (re-correrlo es seguro) y siembra:
75
75
 
76
- - **10 agentes** en `.claude/agents/build/` (build-orchestrator, dor-dod-gatekeeper,
76
+ - **11 agentes** en `.claude/agents/build/` (build-orchestrator, dor-dod-gatekeeper,
77
77
  security-reviewer, simple-design-reviewer, ux-krug-reviewer, coherence-three-way, stack-guardian,
78
- api-contract-tester, data-consistency-checker, change-epic-coherence).
79
- - **Comandos** `/opsx:*` (10) en `.claude/commands/opsx/` + `/build:onboard` en `.claude/commands/build/`.
78
+ api-contract-tester, data-consistency-checker, change-epic-coherence, ux-fidelity-reviewer).
79
+ - **Comandos** `/opsx:*` (10) en `.claude/commands/opsx/` + `/build:onboard` y `/build:reflect` en `.claude/commands/build/`.
80
80
  - **12 skills** en `.claude/skills/` (`building-a-slice`, `releasing-a-version`, 10 `openspec-*`).
81
- - **6 hooks** bash en `.claude/hooks/build/` + permisos mínimos en `settings.json`.
81
+ - **9 hooks** bash en `.claude/hooks/build/` + permisos mínimos en `settings.json`.
82
82
  - **Estado**: `.claude/state/build-state.json` (sembrado **vacío** y **nunca** sobreescrito; va al
83
83
  `.gitignore`), más el schema y el README versionados.
84
84
  - **Config**: `.claude/config/stack-allowlist.json` (artefacto del consumidor; lo siembra el CLI y
@@ -136,10 +136,14 @@ trycore-build doctor
136
136
  Comprueba:
137
137
 
138
138
  - Los **3 requisitos duros** (`git`, `python3`, `openspec`) — **falla con exit 1** si falta alguno.
139
- - Que los **6 hooks** tengan bit ejecutable (si no, sugiere `trycore-build init --copy` o `chmod +x`).
139
+ - Que los **9 hooks** tengan bit ejecutable (si no, sugiere `trycore-build init --copy` o `chmod +x`).
140
140
  - **Doble canal**: si detecta hooks del arnés en `settings.json` (canal CLI) y además instalaste el
141
141
  plugin, te recuerda que la cadena de comando es idéntica en ambos canales y Claude Code
142
142
  **deduplica** → el hook dispara **una sola vez**. No requiere acción.
143
+ - **Reflexión y LSP (informativo, no falla):** reporta cuántos slices archivados están **sin
144
+ reflexionar** (sugiere `/build:reflect`) y, si detecta un stack tipado (`pom.xml`,
145
+ `build.gradle`, `*.csproj`, `tsconfig.json`…), sugiere activar **LSP**
146
+ (ver `docs/customization/lsp-extensions.md`).
143
147
 
144
148
  Si ves `✓ Requisitos satisfechos.`, continúa.
145
149
 
@@ -211,6 +215,10 @@ Notas clave del inner loop:
211
215
  (bloquea commits/push directos a `main` — integras solo por **PR**).
212
216
  - **El enlace change↔épica** va en el bloque `## Trazabilidad` del `proposal.md`, **nunca** en
213
217
  frontmatter YAML (rompe `openspec validate`).
218
+ - **Reflexión (ciclo autocorrectivo).** Tras archivar el slice, `/build:reflect` puede capturar las
219
+ convenciones aprendidas / errores recurrentes en el bloque `trycore-build-learnings` de
220
+ `CLAUDE.md` (con tu aprobación). Lo **sugiere** el hook `reflect-nudge.sh` al cerrar la sesión;
221
+ es opcional y no bloquea.
214
222
  - Objetivo: **≤ ~20 min por épica**, y producto que **camina end-to-end en todo momento**.
215
223
 
216
224
  ---
@@ -252,6 +260,7 @@ trycore-build doctor # paso 3
252
260
  # en Claude Code:
253
261
  /build:onboard # paso 4
254
262
  skill building-a-slice # EP-XXX: DoR → /opsx:new → TDD → smoke → DoD → PR # paso 5
263
+ /build:reflect # opcional: captura aprendizajes del slice recién archivado
255
264
  # y cuando cierres una línea de release del Story Map:
256
265
  skill releasing-a-version # paso 6
257
266
  ```
package/docs/hooks.md CHANGED
@@ -1,23 +1,26 @@
1
1
  # Hooks del arnés de construcción
2
2
 
3
- Este documento describe los **6 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 **9 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.
4
4
 
5
- Los hooks son el sistema nervioso del arnés: vigilan GitFlow y el stack declarado (bloqueantes), inyectan el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, y recuerdan validar trazabilidad y gates abiertos. 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 el estado del build al iniciar la sesión, delegan estilo/typecheck a herramientas, y recuerdan validar trazabilidad, gates abiertos y reflexionar al cerrar un slice. 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 6 hooks
9
+ ## Resumen de los 9 hooks
10
10
 
11
11
  | Hook | Evento | Matcher | Qué hace | ¿Bloqueante? |
12
12
  |---|---|---|---|---|
13
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 |
14
14
  | `gitflow-guard.sh` | `PreToolUse` | `Bash` | Enforce GitHub Flow estricto sobre `git commit` / `git push`. | **Sí (exit 2)** |
15
15
  | `stack-guard.sh` | `PreToolUse` | `Write\|Edit\|MultiEdit` | Bloquea dependencias en `package.json` fuera de la allowlist del stack del PRD. | **Sí (exit 2)** |
16
+ | `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
+ | `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)** |
16
18
  | `lint-typecheck.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Corre prettier/eslint/tsc sobre el archivo `.ts`/`.tsx` editado. | No |
17
19
  | `coherence-flag.sh` | `PostToolUse` | `Write\|Edit\|MultiEdit` | Recuerda validar la trazabilidad de un `proposal.md` de OpenSpec recién tocado. | No |
18
20
  | `build-gate-check.sh` | `Stop` | `.*` | Al cerrar el turno, avisa si el slice activo tiene gates abiertos. | No |
21
+ | `reflect-nudge.sh` | `Stop` | `.*` | Al cerrar el turno, sugiere `/build:reflect` si hay slice(s) archivado(s) sin reflexionar (`reflected != true`). | No |
19
22
 
20
- > Dos bloqueantes (`gitflow-guard`, `stack-guard`) y cuatro informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
23
+ > Cuatro bloqueantes (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) y cinco informativos. Los bloqueantes usan `exit 2`: Claude Code devuelve el `stderr` al modelo y aborta la herramienta; el resto siempre sale `0`.
21
24
 
22
25
  ---
23
26
 
@@ -58,7 +61,11 @@ Tras editar un archivo `.ts`/`.tsx`, delega el estilo a las herramientas para no
58
61
 
59
62
  - `prettier --write` sobre el archivo.
60
63
  - `eslint --fix` sobre el archivo (primeras 20 líneas de salida a `stderr`).
61
- - `tsc --noEmit` y filtra los errores que mencionan el archivo editado.
64
+ - `tsc --noEmit --incremental` con un `tsBuildInfoFile` persistente, y filtra los errores que mencionan el archivo editado.
65
+
66
+ **Typecheck incremental (desde v0.3.x).** `tsc` siempre recorre todo el grafo de tipos del proyecto, no solo el archivo editado. Para que ese coste no escale con el tamaño del repo en cada edición, el hook usa `--incremental` con un `tsBuildInfoFile` en `node_modules/.cache/trycore-build/tsbuildinfo`: el **primer** typecheck de la sesión paga `O(repo)` y cada edición posterior paga `~O(delta)`. El cache vive bajo `node_modules/` (que el consumidor casi siempre tiene gitignored), así que no contamina el repo. El typecheck va envuelto en `timeout 60` **si** `timeout`/`gtimeout` (coreutils) está disponible; si no, corre sin límite (al ser no bloqueante, agotar el tiempo solo omite el reporte de esa edición).
67
+
68
+ > **Caveat `composite`:** en proyectos con `composite: true` en `tsconfig.json`, el typecheck canónico es `tsc -b`. Aquí `--noEmit` prevalece (igual que antes) y los posibles errores de configuración se suprimen (`|| true`); el reporte de tipos puede quedar vacío. No es una regresión respecto al comportamiento previo.
62
69
 
63
70
  Nunca bloquea (siempre `exit 0`); reporta a `stderr` como información. Inerte si no hay `package.json` o si el archivo no es `.ts`/`.tsx`.
64
71
 
@@ -70,6 +77,18 @@ Cuando se edita un `openspec/changes/**/proposal.md`, imprime un recordatorio: c
70
77
 
71
78
  Al cerrar el turno, si hay un slice activo con gates en `false`, avisa por `stderr` qué gates quedan abiertos y recuerda **no archivar ni abrir PR** hasta cerrarlos (ver skill `building-a-slice` / `dod.md`). Inerte si no hay `package.json`, ni estado, ni `python3`.
72
79
 
80
+ ### 7. `scaffold-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante** (desde v0.2.0)
81
+
82
+ Refuerza el **scaffold como "Paso 1 fundamental"**. Bloquea con `exit 2` la escritura de **código de slice** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data`— mientras `scaffold.confirmed` no sea `true` en `build-state.json`. **Permite** crear el scaffold (sin slice activo, o en fases `dor`/`change`). El arnés **exige** el scaffold pero **no lo genera**; la confirmación es **explícita** (vía `building-a-slice` Fase 0 / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido (solo bloquea si la entrada parece código de slice).
83
+
84
+ ### 8. `reflect-nudge.sh` — `Stop` · no bloqueante (desde v0.3.0)
85
+
86
+ Cierra el **ciclo autocorrectivo**. Al terminar el turno, si en `history[]` hay slice(s) archivado(s) con `reflected != true`, imprime un *nudge* sugiriendo ejecutar `/build:reflect` para capturar las convenciones aprendidas (y errores recurrentes) en el bloque `trycore-build-learnings` de `CLAUDE.md`. **Nunca bloquea** el cierre de sesión: si falta `python3` o el estado, sale `0` en silencio (**fail-open**). El razonamiento —qué se aprendió— vive en el comando `/build:reflect`, no en el hook; este solo recuerda. Tras reflexionar y estampar `reflected: true`, el nudge calla.
87
+
88
+ ### 9. `design-source-guard.sh` — `PreToolUse` · Write/Edit/MultiEdit · **bloqueante** (desde v0.5.0)
89
+
90
+ Refuerza el **seguro de fuente de diseño** (espejo de `scaffold-guard`, para slices con UI). Bloquea con `exit 2` la escritura de **código de un slice con UI** —cuando `active_slice.phase` ∈ `red`/`green`/`refactor`/`smoke`/`api`/`data` **y** `active_slice.gates.fidelity === false` (marcado UI-pendiente por la DoR)— mientras el proyecto tenga UI (`design_source.applies === true`) y `design_source.confirmed` no sea `true`. **Permite** todo lo demás: slices sin UI (`gates.fidelity === null` o ausente), proyectos sin UI (`applies !== true`), fases de planificación (`dor`/`change`), o fuente ya confirmada. El arnés **exige** la fuente de diseño pero **no genera** el prototipo; la confirmación es **explícita** (vía `building-a-slice` Fase 0-bis / `dor-dod-gatekeeper`), nunca auto-detectada. Guarda `python3` fail-closed dirigido. En la práctica, como `design_source` es gate de proyecto, solo muerde la **primera** construcción de UI sin fuente declarada.
91
+
73
92
  ---
74
93
 
75
94
  ## La cadena de comando única (sin doble disparo entre canales)
@@ -93,8 +112,8 @@ Como la cadena es **carácter por carácter idéntica** en ambos canales, si el
93
112
 
94
113
  Los hooks parsean el JSON del evento con `python3`. Qué pasa si **falta** `python3` depende de si el hook es bloqueante:
95
114
 
96
- - **Bloqueantes** (`gitflow-guard`, `stack-guard`) → **fail-closed dirigido**: si no pueden analizar el comando/edición, solo bloquean (`exit 2`) cuando la entrada *cruda* parece relevante (un `git commit`/`push`, o una edición que menciona `package.json`); en cualquier otro caso salen `0`. Esto evita falsos negativos peligrosos sin frenar el trabajo no relacionado. El mensaje pide instalar `python3` (verificable con `trycore-build doctor`).
97
- - **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0`.
115
+ - **Bloqueantes** (`gitflow-guard`, `stack-guard`, `scaffold-guard`, `design-source-guard`) → **fail-closed dirigido**: si no pueden analizar el comando/edición, solo bloquean (`exit 2`) cuando la entrada *cruda* parece relevante (un `git commit`/`push`, una edición que menciona `package.json`, o código de slice sin scaffold/fuente de diseño confirmados); en cualquier otro caso salen `0`. Esto evita falsos negativos peligrosos sin frenar el trabajo no relacionado. El mensaje pide instalar `python3` (verificable con `trycore-build doctor`).
116
+ - **No bloqueantes** (`load-build-state`, `lint-typecheck`, `coherence-flag`, `build-gate-check`, `reflect-nudge`) → si falta `python3`, simplemente **omiten** su trabajo y salen `0` (fail-open).
98
117
 
99
118
  > `python3` es un **requisito duro**: `trycore-build init` y `trycore-build doctor` **fallan** si no está presente, justo porque toda la cadena de hooks depende de él para leer el JSON del evento.
100
119
 
@@ -111,7 +130,10 @@ Los hooks que tocan el código construido se **auto-arman**: permanecen **inerte
111
130
  | `stack-guard.sh` | Inerte si no existe la allowlist; sin `package.json` que comparar, no hay violación que detectar. |
112
131
  | `lint-typecheck.sh` | Inerte (sale `0` de inmediato). |
113
132
  | `coherence-flag.sh` | Funciona siempre (depende de OpenSpec, no del código). |
133
+ | `scaffold-guard.sh` | Permite (sin slice en fases de código no hay nada que bloquear; también permite crear el scaffold). |
134
+ | `design-source-guard.sh` | Permite (sin slice UI en fases de código, o sin `design_source.applies=true`, no hay nada que bloquear). |
114
135
  | `build-gate-check.sh` | Inerte (sale `0` de inmediato). |
136
+ | `reflect-nudge.sh` | Silencioso (en `authoring` no hay slices archivados que reflexionar). |
115
137
 
116
138
  Así el arnés convive sin fricción con la fase de *discovery* y se "enciende" cuando empieza la construcción real.
117
139
 
@@ -125,7 +147,7 @@ Una sola definición de hooks, expresada en dos archivos espejo según el canal:
125
147
 
126
148
  `trycore-build init` hace un **merge idempotente y aditivo** en el `settings.json` del consumidor (lógica en `src/lib/settings-merge.ts`; espejo documental en `templates/settings-hooks.template.json`):
127
149
 
128
- - Agrega las 5 agrupaciones de hooks (las 6 invocaciones: `PostToolUse` agrupa `lint-typecheck` + `coherence-flag`) sin pisar lo que ya exista.
150
+ - Agrega las 5 agrupaciones de hooks (las 9 invocaciones: `PreToolUse·Write` agrupa `stack-guard` + `scaffold-guard` + `design-source-guard`; `PostToolUse·Write` agrupa `lint-typecheck` + `coherence-flag`; `Stop` agrupa `build-gate-check` + `reflect-nudge`) sin pisar lo que ya exista.
129
151
  - Agrega **permisos mínimos y enumerados** (sin `mcp__*` ni rutas absolutas):
130
152
 
131
153
  ```
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # design-source-guard.sh — PreToolUse · Write/Edit/MultiEdit
3
+ # Backstop determinista del "seguro de fuente de diseño": no se escribe código de un slice
4
+ # CON UI sin que la fuente de diseño del proyecto esté confirmada. Espejo de scaffold-guard.sh.
5
+ # Bloquea (exit 2) SOLO si: hay active_slice en fase de código (red/green/refactor/smoke/api/data),
6
+ # el proyecto tiene UI (design_source.applies===true), el slice está marcado UI-pendiente
7
+ # (gates.fidelity===false) y design_source.confirmed!==true.
8
+ # AUTO-ARME: si no existe build-state.json, exit 0.
9
+ set -uo pipefail
10
+
11
+ ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
12
+ STATE="$ROOT/.claude/state/build-state.json"
13
+ [ -f "$STATE" ] || exit 0
14
+
15
+ INPUT="$(cat)" # consumir stdin (protocolo de hook); la decisión es por estado.
16
+
17
+ # Guarda python3 [H4]: si falta, no podemos leer el estado de forma fiable. Fail-closed.
18
+ if ! command -v python3 >/dev/null 2>&1; then
19
+ echo "⛔ design-source-guard: python3 no disponible; no puedo verificar el gate de fuente de diseño. Instala python3 (trycore-build doctor)." >&2
20
+ exit 2
21
+ fi
22
+
23
+ VERDICT="$(STATE="$STATE" python3 <<'PY' 2>/dev/null
24
+ import os, sys, json
25
+ try:
26
+ d = json.load(open(os.environ["STATE"]))
27
+ except Exception:
28
+ sys.exit(0) # estado ilegible -> no bloquear (auto-arme)
29
+ slice_ = d.get("active_slice")
30
+ if not slice_:
31
+ sys.exit(0) # sin slice: permitir declarar / planificar
32
+ code_phases = {"red", "green", "refactor", "smoke", "api", "data"}
33
+ if slice_.get("phase") not in code_phases:
34
+ sys.exit(0) # fases dor/change/pr/archived: permitido
35
+ ds = d.get("design_source") or {}
36
+ if ds.get("applies") is not True:
37
+ sys.exit(0) # proyecto sin UI (o indeterminado): mecanismo apagado
38
+ if (slice_.get("gates") or {}).get("fidelity") is not False:
39
+ sys.exit(0) # solo UI-pendiente (false) cuenta; null/ausente -> no bloquear
40
+ if ds.get("confirmed") is True:
41
+ sys.exit(0) # fuente de diseño confirmada: permitido
42
+ print("BLOCK")
43
+ PY
44
+ )"
45
+
46
+ if [ "$VERDICT" = "BLOCK" ]; then
47
+ echo "⛔ design-source-guard: el slice con UI está en fase de código pero la fuente de diseño NO está confirmada." >&2
48
+ echo " Declara y confirma primero el DESIGN_SOURCE del proyecto (ver building-a-slice Fase 0-bis / DoR)." >&2
49
+ echo " El arnés NO genera el prototipo: declara la fuente (prototipo/export) y confírmala." >&2
50
+ exit 2
51
+ fi
52
+ exit 0
@@ -3,6 +3,9 @@
3
3
  # AUTO-ARME: inerte mientras no exista package.json (fase authoring).
4
4
  # No bloquea: reporta lint/format/typecheck del archivo editado para liberar
5
5
  # capacidad de razonamiento del modelo (estilo delegado a herramientas).
6
+ # El typecheck es INCREMENTAL (--incremental + tsBuildInfoFile persistente): el primer
7
+ # run de la sesión paga O(repo) y cada edición posterior paga ~O(delta), para que el
8
+ # coste por edición no escale con el tamaño del repo.
6
9
  set -uo pipefail
7
10
 
8
11
  ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
@@ -29,5 +32,22 @@ HAS() { [ -d node_modules ] && [ -x "node_modules/.bin/$1" ]; }
29
32
 
30
33
  if HAS prettier; then node_modules/.bin/prettier --write "$FILE" >/dev/null 2>&1 || true; fi
31
34
  if HAS eslint; then node_modules/.bin/eslint --fix "$FILE" 2>&1 | sed -n '1,20p' >&2 || true; fi
32
- if HAS tsc; then node_modules/.bin/tsc --noEmit 2>&1 | grep -F "$FILE" | sed -n '1,20p' >&2 || true; fi
35
+
36
+ # Typecheck INCREMENTAL del proyecto. `tsc` siempre recorre todo el grafo de tipos, pero con
37
+ # --incremental + un tsBuildInfoFile persistente, tras el primer run cada edición paga solo el delta.
38
+ # El .tsbuildinfo vive bajo node_modules/ (que el consumidor casi siempre tiene gitignored) → no
39
+ # contamina el repo. Filtramos la salida al archivo editado (grep -F) y la capamos a 20 líneas.
40
+ # timeout OPCIONAL: si coreutils está disponible acota un tsconfig patológico; si no, corre sin
41
+ # límite (el hook nunca bloquea, así que agotar el timeout solo omite el reporte de esta edición).
42
+ # Caveat: proyectos con `composite: true` deben usar `tsc -b`; aquí --noEmit prevalece como hoy
43
+ # y los errores se suprimen (|| true), igual que antes de v0.3.x.
44
+ if HAS tsc; then
45
+ TSBI="node_modules/.cache/trycore-build/tsbuildinfo"
46
+ mkdir -p "$(dirname "$TSBI")" 2>/dev/null || true
47
+ TSC_TIMEOUT=""
48
+ if command -v timeout >/dev/null 2>&1; then TSC_TIMEOUT="timeout 60"
49
+ elif command -v gtimeout >/dev/null 2>&1; then TSC_TIMEOUT="gtimeout 60"; fi
50
+ $TSC_TIMEOUT node_modules/.bin/tsc --noEmit --incremental --tsBuildInfoFile "$TSBI" 2>&1 \
51
+ | grep -F "$FILE" | sed -n '1,20p' >&2 || true
52
+ fi
33
53
  exit 0
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # reflect-nudge.sh — Stop
3
+ # Ciclo autocorrectivo (reflexión post-sesión): NUNCA bloquea el cierre de sesión.
4
+ # Sugiere /build:reflect SOLO si hay slice(s) archivado(s) sin reflexionar (reflected != true).
5
+ # Determinista y barato: el razonamiento (qué se aprendió) lo hace el MODELO en /build:reflect.
6
+ set -uo pipefail
7
+
8
+ ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-$(pwd)}")"
9
+ STATE="$ROOT/.claude/state/build-state.json"
10
+ [ -f "$STATE" ] || exit 0
11
+ command -v python3 >/dev/null 2>&1 || exit 0 # fail-open: jamás impide cerrar sesión
12
+
13
+ # Mensaje a STDOUT (no stderr): un Stop hook con exit 0 no debe bloquear el cierre;
14
+ # el `2>/dev/null` suprime SOLO trazas de python, nunca el nudge.
15
+ python3 - "$STATE" <<'PY' 2>/dev/null || true
16
+ import json, sys
17
+ try:
18
+ d = json.load(open(sys.argv[1]))
19
+ except Exception:
20
+ sys.exit(0)
21
+ hist = d.get("history") or []
22
+ pend = [h for h in hist if isinstance(h, dict) and h.get("reflected") is not True]
23
+ if pend:
24
+ n = len(pend)
25
+ plural = "s" if n != 1 else ""
26
+ print(f"💡 Reflexión pendiente: {n} slice{plural} archivado{plural} sin capturar aprendizajes.")
27
+ print(" Ejecuta /build:reflect para proponer convenciones aprendidas a CLAUDE.md (se aplican tras tu aprobación).")
28
+ PY
29
+ exit 0
@@ -31,6 +31,10 @@
31
31
  {
32
32
  "type": "command",
33
33
  "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/scaffold-guard.sh\""
34
+ },
35
+ {
36
+ "type": "command",
37
+ "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/design-source-guard.sh\""
34
38
  }
35
39
  ]
36
40
  }
@@ -57,6 +61,10 @@
57
61
  {
58
62
  "type": "command",
59
63
  "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/build-gate-check.sh\""
64
+ },
65
+ {
66
+ "type": "command",
67
+ "command": "\"${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR/.claude}/hooks/build/reflect-nudge.sh\""
60
68
  }
61
69
  ]
62
70
  }