@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.
- package/.claude-plugin/plugin.json +1 -1
- package/GOVERNANCE.md +24 -5
- package/INSTALL.md +11 -6
- package/METODOLOGIA.md +18 -4
- package/README.md +12 -7
- package/VERSION +1 -1
- package/agents/build/build-orchestrator.md +6 -1
- package/agents/build/dor-dod-gatekeeper.md +13 -6
- package/agents/build/ux-fidelity-reviewer.md +61 -0
- package/commands/build/onboard.md +20 -4
- package/commands/build/reflect.md +163 -0
- package/dist/commands/doctor.js +35 -0
- package/dist/commands/init.js +32 -9
- package/dist/commands/status.js +4 -0
- package/dist/commands/uninstall.js +4 -1
- package/dist/lib/settings-merge.js +2 -2
- package/dist/lib/state-seed.js +1 -0
- package/docs/agents.md +2 -1
- package/docs/commands.md +10 -4
- package/docs/customization/lsp-extensions.md +90 -0
- package/docs/customization/mcp-extensions.md +3 -0
- package/docs/getting-started.md +14 -5
- package/docs/hooks.md +30 -8
- package/hooks/build/design-source-guard.sh +52 -0
- package/hooks/build/lint-typecheck.sh +21 -1
- package/hooks/build/reflect-nudge.sh +29 -0
- package/hooks/build-harness.json +8 -0
- package/package.json +2 -1
- package/skills/building-a-micro-change/SKILL.md +78 -0
- package/skills/building-a-slice/SKILL.md +26 -1
- package/skills/building-a-slice/references/dod.md +4 -0
- package/skills/building-a-slice/references/dor.md +5 -2
- package/skills/building-a-slice/references/gitflow.md +3 -1
- package/skills/building-a-slice/references/mcp-map.md +1 -0
- package/state/README.md +21 -0
- package/state/build-state.schema.json +19 -1
- package/state/build-state.template.json +8 -0
- package/templates/CLAUDE.md.template +8 -1
- package/templates/settings-hooks.template.json +1 -1
- package/docs/superpowers/specs/2026-06-02-scaffold-gate-design.md +0 -87
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
package/dist/commands/status.js
CHANGED
|
@@ -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 = [
|
package/dist/lib/state-seed.js
CHANGED
|
@@ -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 **
|
|
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.
|
|
6
|
-
2. Los **slash commands de Claude Code** (`/opsx:*` + `/build:onboard`) — operan el pipeline de dos loops
|
|
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
|
|
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.
|
package/docs/getting-started.md
CHANGED
|
@@ -73,12 +73,12 @@ trycore-build init
|
|
|
73
73
|
|
|
74
74
|
`init` es **idempotente** (re-correrlo es seguro) y siembra:
|
|
75
75
|
|
|
76
|
-
- **
|
|
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
|
-
- **
|
|
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 **
|
|
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 **
|
|
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
|
|
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
|
|
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
|
-
>
|
|
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`,
|
|
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
|
|
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
|
-
|
|
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
|
package/hooks/build-harness.json
CHANGED
|
@@ -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
|
}
|