@saulwade/swl-ses 1.9.0 → 2.0.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.md +8 -8
- package/README.md +12 -12
- package/agentes/accesibilidad-wcag-swl.md +3 -3
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/disenador-ui-swl.md +6 -5
- package/agentes/frontend-angular-swl.md +2 -2
- package/agentes/frontend-css-swl.md +2 -2
- package/agentes/frontend-react-swl.md +4 -4
- package/agentes/frontend-swl.md +6 -6
- package/agentes/investigador-ux-swl.md +5 -5
- package/agentes/orquestador-swl.md +7 -7
- package/agentes/perfilador-usuario-swl.md +308 -308
- package/agentes/producto-prd-swl.md +1 -1
- package/agentes/red-team-swl.md +218 -218
- package/agentes/tdd-qa-swl.md +17 -1
- package/comandos/swl/actualizar.md +1 -1
- package/comandos/swl/aprender.md +2 -2
- package/comandos/swl/aprobar-plan.md +152 -0
- package/comandos/swl/ayuda.md +3 -3
- package/comandos/swl/discutir-fase.md +20 -2
- package/comandos/swl/ejecutar-fase.md +53 -6
- package/comandos/swl/evolucionar.md +1 -1
- package/comandos/swl/inbox.md +1 -1
- package/comandos/swl/instalar.md +1 -1
- package/comandos/swl/nemesis.md +1 -1
- package/comandos/swl/planear-fase.md +17 -1
- package/comandos/swl/plugins.md +1 -1
- package/comandos/swl/release.md +1 -1
- package/comandos/swl/status.md +279 -0
- package/comandos/swl/verificar.md +26 -1
- package/habilidades/ai-runtime-security/SKILL.md +1 -1
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
- package/habilidades/benchmark-memoria/SKILL.md +1 -1
- package/habilidades/calidad-contract-testing/SKILL.md +165 -0
- package/habilidades/changelog-generator/SKILL.md +9 -2
- package/habilidades/changelog-generator/scripts/parse-commits.js +11 -1
- package/habilidades/diagrama-arquitectura/SKILL.md +1 -1
- package/habilidades/drift-detection/SKILL.md +179 -179
- package/habilidades/ejecutar-fase/SKILL.md +64 -14
- package/habilidades/estructura-proyecto-claude/SKILL.md +17 -14
- package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +34 -23
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +70 -53
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -77
- package/habilidades/extractor-de-aprendizajes/SKILL.md +9 -5
- package/habilidades/harness-claude-code/SKILL.md +10 -7
- package/{reglas/harness-claude-code.md → habilidades/harness-claude-code/recursos/disciplina-harness-regla.md} +2 -2
- package/habilidades/instalar-sistema/SKILL.md +3 -3
- package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +1 -1
- package/habilidades/perfil-usuario/SKILL.md +200 -200
- package/habilidades/planear-fase/SKILL.md +25 -4
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +1 -1
- package/habilidades/proceso-ddia-streaming/SKILL.md +4 -4
- package/habilidades/proceso-debate-adversarial/SKILL.md +2 -2
- package/habilidades/protocolo-revision-swl/SKILL.md +1 -1
- package/habilidades/seguridad-skills-ia/SKILL.md +1 -1
- package/habilidades/swl-claudemd/SKILL.md +50 -210
- package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -0
- package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -0
- package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -0
- package/habilidades/swl-dashboard/SKILL.md +9 -9
- package/habilidades/swl-revisar-impacto/SKILL.md +1 -1
- package/habilidades/tdd-workflow/SKILL.md +45 -5
- package/habilidades/validacion-ci-sistema/SKILL.md +3 -3
- package/hooks/calidad-pre-commit.js +340 -3
- package/hooks/ciclo-evolucion-subagente.js +26 -0
- package/hooks/ciclo-evolucion.js +26 -0
- package/hooks/extraccion-aprendizajes.js +13 -0
- package/hooks/lib/ciclo-evolucion.js +47 -0
- package/hooks/{auto-evolucion.js → lib/etapa-auto-evolucion.js} +701 -700
- package/hooks/{metricas-evolucion.js → lib/etapa-metricas.js} +388 -376
- package/hooks/{actualizar-perfil-usuario.js → lib/etapa-perfil-usuario.js} +376 -364
- package/hooks/lib/evolution-tracker.js +24 -3
- package/hooks/spec-gate.js +211 -0
- package/hooks/tdd-gate.js +241 -0
- package/hooks/validar-intent-spec.js +30 -10
- package/llms.txt +6 -6
- package/manifiestos/hooks-config.json +26 -17
- package/manifiestos/modulos.json +17 -14
- package/manifiestos/skills-lock.json +63 -56
- package/package.json +2 -2
- package/plugin.json +6 -10
- package/reglas/accesibilidad.md +10 -0
- package/reglas/api-diseno.md +9 -0
- package/reglas/auditorias-documentales-estructurales.md +7 -0
- package/reglas/cloud-infra.md +8 -0
- package/reglas/fragmentos-compartidos.md +5 -0
- package/reglas/gobernanza.md +4 -4
- package/reglas/hooks.md +6 -0
- package/reglas/intent-engineering.md +4 -0
- package/reglas/markitdown.md +8 -0
- package/reglas/memoria-consolidada.md +1 -1
- package/reglas/patrones.md +6 -0
- package/reglas/registro-componentes-nuevos.md +10 -1
- package/reglas/seguridad-agentes.md +1 -1
- package/reglas/skills-estandar.md +6 -0
- package/reglas/testing.md +7 -0
- package/reglas/tests-cleanup.md +4 -0
- package/reglas/usar-sistema-swl.md +1 -1
- package/scripts/lib/gitignore-manifest.js +29 -1
- package/scripts/lib/plan-lock.js +275 -0
- package/scripts/migrar-fase-dominio.js +0 -1
- package/scripts/verificar-trazabilidad.js +292 -0
- package/agentes/ux-disenador-swl.md +0 -503
- package/comandos/swl/dashboard.md +0 -146
- package/comandos/swl/evolucion-estado.md +0 -191
- package/comandos/swl/metricas.md +0 -376
- package/comandos/swl/salud.md +0 -481
- package/reglas/verificar-citas-temporales.md +0 -139
|
@@ -64,6 +64,7 @@ const EXCLUDED_FILENAME_PATTERNS = [
|
|
|
64
64
|
/\.rej$/, // patch reject
|
|
65
65
|
/\.merge_file_/, // merge tools (kdiff3, etc.)
|
|
66
66
|
/~$/, // editores tipo Emacs/Vim
|
|
67
|
+
/\.evolved-diff\.(md|txt)$/, // diffs de merge (no son componentes; .md legacy)
|
|
67
68
|
];
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -407,7 +408,13 @@ const DIFF_NOISY_THRESHOLD = 50;
|
|
|
407
408
|
*
|
|
408
409
|
* Estrategia: toma el archivo nuevo como base y re-aplica los campos de
|
|
409
410
|
* evolución (frontmatter evolved-*). Las mutaciones de contenido se preservan
|
|
410
|
-
* generando un archivo
|
|
411
|
+
* generando un archivo `.evolved-diff.txt` que Claude puede re-aplicar.
|
|
412
|
+
*
|
|
413
|
+
* Extensión `.txt` (no `.md`) deliberada: el diff vive junto al componente
|
|
414
|
+
* evolucionado (incluyendo `commands/`), pero el harness de Claude Code indexa
|
|
415
|
+
* todo `.md` dentro de `commands/` como slash-command — un `aprender.evolved-diff.md`
|
|
416
|
+
* aparecería como `/swl:aprender.evolved-diff`. Con `.txt` el harness no lo indexa
|
|
417
|
+
* y `scanEvolved` (que solo recorre `.md`) tampoco lo confunde con un componente.
|
|
411
418
|
*
|
|
412
419
|
* Comparación: solo el body (post-frontmatter) se compara línea-a-línea.
|
|
413
420
|
* El frontmatter SIEMPRE diverge (el destino tiene campos `evolved-*` que el
|
|
@@ -415,7 +422,8 @@ const DIFF_NOISY_THRESHOLD = 50;
|
|
|
415
422
|
* contarlo como mutación genera ruido por desplazamiento.
|
|
416
423
|
*
|
|
417
424
|
* Limpieza: cuando un merge posterior elimina la divergencia (diffs vacíos),
|
|
418
|
-
* borra el `.evolved-diff.
|
|
425
|
+
* borra el `.evolved-diff.txt` huérfano de sesiones previas si existe (y el
|
|
426
|
+
* `.evolved-diff.md` legacy de versiones anteriores a esta corrección).
|
|
419
427
|
*
|
|
420
428
|
* Cap defensivo: si tras alinear correctamente el body aún hay más de
|
|
421
429
|
* `DIFF_NOISY_THRESHOLD` líneas distintas, genera un resumen estadístico
|
|
@@ -470,7 +478,17 @@ function mergeEvolved(destino, origen, versionNueva) {
|
|
|
470
478
|
}
|
|
471
479
|
}
|
|
472
480
|
|
|
473
|
-
const diffPath = destino.replace(/\.md$/, '.evolved-diff.
|
|
481
|
+
const diffPath = destino.replace(/\.md$/, '.evolved-diff.txt');
|
|
482
|
+
// Legacy: versiones previas escribían el diff como `.evolved-diff.md`, que
|
|
483
|
+
// el harness indexaba como slash-command. Se limpia siempre que se toca el
|
|
484
|
+
// componente, exista o no divergencia nueva.
|
|
485
|
+
const diffPathLegacy = destino.replace(/\.md$/, '.evolved-diff.md');
|
|
486
|
+
const limpiarLegacy = () => {
|
|
487
|
+
if (fs.existsSync(diffPathLegacy)) {
|
|
488
|
+
try { fs.unlinkSync(diffPathLegacy); return true; } catch { /* best-effort */ }
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
};
|
|
474
492
|
|
|
475
493
|
if (diffs.length === 0) {
|
|
476
494
|
// Sin diferencias reales — limpiar diff huérfano si existe (de sesión
|
|
@@ -486,6 +504,7 @@ function mergeEvolved(destino, origen, versionNueva) {
|
|
|
486
504
|
// el merge sigue siendo válido.
|
|
487
505
|
}
|
|
488
506
|
}
|
|
507
|
+
if (limpiarLegacy()) cleanedDiff = true;
|
|
489
508
|
|
|
490
509
|
// force: true — `mergeEvolved` solo se invoca en contexto de update
|
|
491
510
|
// intencional. El skip de isPackageRoot() aplica a la primera marca
|
|
@@ -557,6 +576,8 @@ function mergeEvolved(destino, origen, versionNueva) {
|
|
|
557
576
|
].join('\n');
|
|
558
577
|
|
|
559
578
|
atomicWriteSync(diffPath, diffContent, 'utf8');
|
|
579
|
+
// Si existía el `.evolved-diff.md` legacy, eliminarlo: el `.txt` lo reemplaza.
|
|
580
|
+
limpiarLegacy();
|
|
560
581
|
|
|
561
582
|
return { merged: true, diffPath, diffsCount: diffs.length, truncated };
|
|
562
583
|
} catch (err) {
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: spec-gate.js (Gate G0 — SDD)
|
|
6
|
+
* Tipo: PreToolUse (matcher: Write|Edit|MultiEdit)
|
|
7
|
+
*
|
|
8
|
+
* Detecta escrituras de CÓDIGO FUENTE sin fase activa con PLAN aprobado.
|
|
9
|
+
* Implementa el gate G0 del flujo SPEC→PLAN→TEST→CODE→VERIFY (Revisión
|
|
10
|
+
* Evolutiva 03 §5.2): "ningún Write/Edit a código fuente sin fase activa
|
|
11
|
+
* con PLAN aprobado".
|
|
12
|
+
*
|
|
13
|
+
* Modo actual: WARN-ONLY (ADR-0034, decisión D-07 de 10-CONTEXTO.md).
|
|
14
|
+
* - Emite nudge `kind: spec-gate` a .planning/evolution/nudges.jsonl.
|
|
15
|
+
* - NUNCA exit 2. La promoción a blocking se decide vía ADR tras ~2
|
|
16
|
+
* semanas de calibración sin falsos positivos (patrón ADR-0027→0033).
|
|
17
|
+
*
|
|
18
|
+
* Concepto "fase activa": `.planning/locks/fase-activa.json`, escrito por
|
|
19
|
+
* /swl:aprobar-plan al firmar un plan y eliminado por /swl:ejecutar-fase al
|
|
20
|
+
* cerrar la fase. Formato:
|
|
21
|
+
* { numero, planPath, sha256, aprobadoEn, aprobadoPor }
|
|
22
|
+
* El hook re-computa el SHA256 del plan referenciado y lo compara con el del
|
|
23
|
+
* archivo de fase activa — verificación autocontenida (sin dependencia de
|
|
24
|
+
* scripts/lib/, que puede no estar distribuido en proyectos destino).
|
|
25
|
+
*
|
|
26
|
+
* Zero-config: en proyectos sin `.planning/` el hook sale 0 sin ruido.
|
|
27
|
+
* Opt-out: SWL_SPEC_GATE=0 desactiva completamente el hook.
|
|
28
|
+
*
|
|
29
|
+
* Modelo de amenaza (heredado de plan-lock.js): detecta omisión accidental
|
|
30
|
+
* del ciclo GSD y mutación post-firma; NO resiste a un adversario con
|
|
31
|
+
* escritura al repo (puede regenerar fase-activa.json).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const crypto = require('crypto');
|
|
37
|
+
|
|
38
|
+
const RUTAS_EXCLUIDAS = [
|
|
39
|
+
'/temp/',
|
|
40
|
+
'/node_modules/',
|
|
41
|
+
'/respositorios-git/',
|
|
42
|
+
'/_userland/',
|
|
43
|
+
'/.planning/',
|
|
44
|
+
'/.claude/',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clasificador de código fuente. Reutiliza esArchivoFuente() de
|
|
49
|
+
* calidad-pre-commit.js (misma definición que el gate G3) con fallback
|
|
50
|
+
* defensivo si el módulo no está disponible en el destino.
|
|
51
|
+
*/
|
|
52
|
+
function cargarClasificador() {
|
|
53
|
+
try {
|
|
54
|
+
const calidad = require(path.join(__dirname, 'calidad-pre-commit.js'));
|
|
55
|
+
if (typeof calidad.esArchivoFuente === 'function') return calidad.esArchivoFuente;
|
|
56
|
+
} catch (_) {
|
|
57
|
+
// Fallback mínimo: extensiones de código de los lenguajes soportados,
|
|
58
|
+
// excluyendo tests por sufijo/directorio.
|
|
59
|
+
}
|
|
60
|
+
return function esArchivoFuenteFallback(ruta) {
|
|
61
|
+
const normal = ruta.replace(/\\/g, '/');
|
|
62
|
+
if (/\.(test|spec)\.[jt]sx?$/.test(normal)) return false;
|
|
63
|
+
if (/(^|\/)(tests?|__tests__|spec)\//.test(normal)) return false;
|
|
64
|
+
if (/\.(md|json|ya?ml|toml|ini|cfg|conf|txt|svg|lock)$/i.test(normal)) return false;
|
|
65
|
+
return /\.(jsx?|tsx?|py|go|rs|java|kt|cs|rb|php|swift|c|cc|cpp|h|hpp)$/i.test(normal);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Evalúa el estado de la fase activa del proyecto en `cwd`.
|
|
71
|
+
* @returns {{ ok: boolean, motivo: string|null, numero?: number }}
|
|
72
|
+
* ok=true → hay fase activa con plan íntegro (no advertir)
|
|
73
|
+
* ok=false → motivo describe por qué no hay cobertura de spec
|
|
74
|
+
*/
|
|
75
|
+
function evaluarFaseActiva(cwd) {
|
|
76
|
+
const rutaFaseActiva = path.join(cwd, '.planning', 'locks', 'fase-activa.json');
|
|
77
|
+
if (!fs.existsSync(rutaFaseActiva)) {
|
|
78
|
+
return { ok: false, motivo: 'sin fase activa (no existe .planning/locks/fase-activa.json)' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let faseActiva;
|
|
82
|
+
try {
|
|
83
|
+
faseActiva = JSON.parse(fs.readFileSync(rutaFaseActiva, 'utf-8'));
|
|
84
|
+
} catch (_) {
|
|
85
|
+
return { ok: false, motivo: 'fase-activa.json corrupto (JSON inválido)' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!faseActiva || typeof faseActiva.planPath !== 'string' || typeof faseActiva.sha256 !== 'string') {
|
|
89
|
+
return { ok: false, motivo: 'fase-activa.json sin planPath/sha256' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const planAbs = path.isAbsolute(faseActiva.planPath)
|
|
93
|
+
? faseActiva.planPath
|
|
94
|
+
: path.join(cwd, faseActiva.planPath);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(planAbs)) {
|
|
97
|
+
return { ok: false, motivo: `el plan de la fase activa no existe (${faseActiva.planPath})` };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let hashActual;
|
|
101
|
+
try {
|
|
102
|
+
hashActual = crypto.createHash('sha256').update(fs.readFileSync(planAbs)).digest('hex');
|
|
103
|
+
} catch (_) {
|
|
104
|
+
return { ok: false, motivo: 'no se pudo leer el plan de la fase activa' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (hashActual !== faseActiva.sha256) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
motivo: `el plan de la fase activa fue mutado tras la firma (${faseActiva.planPath})`,
|
|
111
|
+
numero: faseActiva.numero,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { ok: true, motivo: null, numero: faseActiva.numero };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Decide si la escritura amerita advertencia G0.
|
|
120
|
+
* @returns {null | { motivo: string }} null = silencio
|
|
121
|
+
*/
|
|
122
|
+
function evaluarEscritura({ cwd, toolName, filePath, esArchivoFuente }) {
|
|
123
|
+
if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) return null;
|
|
124
|
+
if (!filePath) return null;
|
|
125
|
+
|
|
126
|
+
// Zero-config: sin .planning/ no hay ciclo GSD que vigilar.
|
|
127
|
+
if (!fs.existsSync(path.join(cwd, '.planning'))) return null;
|
|
128
|
+
|
|
129
|
+
const normal = filePath.replace(/\\/g, '/');
|
|
130
|
+
if (RUTAS_EXCLUIDAS.some((ex) => normal.includes(ex))) return null;
|
|
131
|
+
if (!esArchivoFuente(normal)) return null;
|
|
132
|
+
|
|
133
|
+
const fase = evaluarFaseActiva(cwd);
|
|
134
|
+
if (fase.ok) return null;
|
|
135
|
+
|
|
136
|
+
return { motivo: fase.motivo };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function emitirNudge({ cwd, filePath, motivo }) {
|
|
140
|
+
const rutaRelativa = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
141
|
+
let emit;
|
|
142
|
+
try {
|
|
143
|
+
({ emit } = require(path.join(__dirname, 'lib', 'nudge-tracker.js')));
|
|
144
|
+
} catch (_) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
emit({
|
|
149
|
+
kind: 'spec-gate',
|
|
150
|
+
target: rutaRelativa,
|
|
151
|
+
source: 'hooks/spec-gate.js',
|
|
152
|
+
message:
|
|
153
|
+
`Gate G0 (warn): escritura de código fuente ${rutaRelativa} ${motivo}. ` +
|
|
154
|
+
`El flujo SDD espera una fase activa con PLAN aprobado (/swl:aprobar-plan N) ` +
|
|
155
|
+
`antes de escribir código. Si es un fix trivial o exploración, ignora este aviso ` +
|
|
156
|
+
`— se usa para calibrar la promoción a blocking (ADR-0034). Opt-out: SWL_SPEC_GATE=0.`,
|
|
157
|
+
data: { archivo: rutaRelativa, motivo, gate: 'G0' },
|
|
158
|
+
mutation_category: 'optimize',
|
|
159
|
+
risk_level: 'low',
|
|
160
|
+
});
|
|
161
|
+
} catch (_) {
|
|
162
|
+
// La observabilidad nunca bloquea el trabajo productivo.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function main() {
|
|
167
|
+
if (process.env.SWL_SPEC_GATE === '0') process.exit(0);
|
|
168
|
+
|
|
169
|
+
let hookInput = '';
|
|
170
|
+
try {
|
|
171
|
+
hookInput = fs.readFileSync(0, 'utf-8');
|
|
172
|
+
} catch (_) {
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let evento;
|
|
177
|
+
try {
|
|
178
|
+
evento = JSON.parse(hookInput);
|
|
179
|
+
} catch (_) {
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const cwd = process.cwd();
|
|
184
|
+
const toolName = evento?.tool_name;
|
|
185
|
+
const filePath = evento?.tool_input?.file_path;
|
|
186
|
+
|
|
187
|
+
let resultado = null;
|
|
188
|
+
try {
|
|
189
|
+
resultado = evaluarEscritura({
|
|
190
|
+
cwd,
|
|
191
|
+
toolName,
|
|
192
|
+
filePath,
|
|
193
|
+
esArchivoFuente: cargarClasificador(),
|
|
194
|
+
});
|
|
195
|
+
} catch (_) {
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (resultado) {
|
|
200
|
+
emitirNudge({ cwd, filePath, motivo: resultado.motivo });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// WARN-ONLY (ADR-0034): jamás exit 2 en este modo.
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (require.main === module) {
|
|
208
|
+
main();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = { evaluarFaseActiva, evaluarEscritura, RUTAS_EXCLUIDAS };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: tdd-gate.js (Gate G2 — TDD con evidencia RED)
|
|
6
|
+
* Tipo: PreToolUse (matcher: Bash)
|
|
7
|
+
*
|
|
8
|
+
* Ante un `git commit` de feature (código fuente + tests en el diff staged),
|
|
9
|
+
* verifica que exista evidencia de ciclo RED en la telemetría de loops
|
|
10
|
+
* (`iteraciones.tsv` dentro de `.planning/loops/tdd-…`, escrita por
|
|
11
|
+
* ejecutar-fase / tdd-workflow). Cierra F-TDD-6 de la Revisión Evolutiva 03:
|
|
12
|
+
* "TDD declarativo sin evidencia — el RED no deja rastro".
|
|
13
|
+
*
|
|
14
|
+
* Modo actual: WARN-ONLY (ADR-0035, decisión D-07 de 10-CONTEXTO.md).
|
|
15
|
+
* - Emite nudge `kind: tdd-red-evidence`; NUNCA exit 2 en este modo.
|
|
16
|
+
*
|
|
17
|
+
* Alcance deliberado (sin doble nudge ni falsos dominios):
|
|
18
|
+
* - Solo aplica con FASE ACTIVA (`.planning/locks/fase-activa.json`) — el
|
|
19
|
+
* enforcement TDD es parte del ciclo GSD, no de commits sueltos.
|
|
20
|
+
* - Solo cuando el diff staged toca fuente Y tests: el caso "fuente sin
|
|
21
|
+
* tests" es dominio del gate G3 (calidad-pre-commit) y ya tiene nudge.
|
|
22
|
+
* - Respeta el opt-out declarado del CONTEXTO (`**TDD**: off — razón: ...`).
|
|
23
|
+
* - Prefijos exentos: docs:/chore:/style: (mismos que G3).
|
|
24
|
+
*
|
|
25
|
+
* Evidencia RED válida: corrida `tdd-*` cuya `iteraciones.tsv` fue modificada
|
|
26
|
+
* dentro de la ventana (default 24h, `SWL_TDD_GATE_VENTANA_H`) con una fila
|
|
27
|
+
* de estado `baseline` con métrica > 0, o cuya descripción contenga "RED".
|
|
28
|
+
*
|
|
29
|
+
* Opt-out: SWL_TDD_GATE=0 desactiva completamente el hook.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { execFileSync } = require('child_process');
|
|
35
|
+
|
|
36
|
+
const VENTANA_HORAS_DEFAULT = 24;
|
|
37
|
+
|
|
38
|
+
/** ¿El comando es un git commit? (ignora git status/log/diff y comandos no-git) */
|
|
39
|
+
function esGitCommit(comando) {
|
|
40
|
+
if (typeof comando !== 'string') return false;
|
|
41
|
+
// Cinturón: un comando legítimo de commit no excede unos KB. Acotar evita
|
|
42
|
+
// costo lineal patológico del regex ante input gigante (hardening, no ReDoS).
|
|
43
|
+
if (comando.length > 10000) return false;
|
|
44
|
+
return /\bgit\b[^|&;]*\bcommit\b/.test(comando);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Lee el numero de fase activa y verifica el opt-out del CONTEXTO. */
|
|
48
|
+
function estadoTddDeFaseActiva(cwd) {
|
|
49
|
+
const rutaFaseActiva = path.join(cwd, '.planning', 'locks', 'fase-activa.json');
|
|
50
|
+
if (!fs.existsSync(rutaFaseActiva)) return { aplica: false, motivo: 'sin-fase-activa' };
|
|
51
|
+
|
|
52
|
+
let faseActiva;
|
|
53
|
+
try {
|
|
54
|
+
faseActiva = JSON.parse(fs.readFileSync(rutaFaseActiva, 'utf-8'));
|
|
55
|
+
} catch (_) {
|
|
56
|
+
return { aplica: false, motivo: 'fase-activa-corrupta' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const numero = Number(faseActiva?.numero);
|
|
60
|
+
if (!Number.isFinite(numero)) return { aplica: false, motivo: 'fase-activa-sin-numero' };
|
|
61
|
+
|
|
62
|
+
const nn = String(numero).padStart(2, '0');
|
|
63
|
+
const contextoPath = path.join(cwd, '.planning', 'fases', `${nn}-CONTEXTO.md`);
|
|
64
|
+
if (fs.existsSync(contextoPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const contexto = fs.readFileSync(contextoPath, 'utf-8');
|
|
67
|
+
if (/\*\*TDD\*\*:\s*off/i.test(contexto)) {
|
|
68
|
+
return { aplica: false, motivo: 'tdd-off-declarado' };
|
|
69
|
+
}
|
|
70
|
+
} catch (_) {
|
|
71
|
+
// CONTEXTO ilegible: asumir TDD on (default)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { aplica: true, numero: nn };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Archivos staged del repo en cwd (lista vacía si no es repo o falla git). */
|
|
78
|
+
function archivosStaged(cwd) {
|
|
79
|
+
try {
|
|
80
|
+
const out = execFileSync('git', ['diff', '--cached', '--name-only'], {
|
|
81
|
+
cwd,
|
|
82
|
+
encoding: 'utf8',
|
|
83
|
+
timeout: 5000,
|
|
84
|
+
});
|
|
85
|
+
return out.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
86
|
+
} catch (_) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Busca evidencia RED en `.planning/loops/tdd-*` dentro de la ventana.
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
function hayEvidenciaRed(cwd, ventanaHoras = VENTANA_HORAS_DEFAULT) {
|
|
96
|
+
const loopsDir = path.join(cwd, '.planning', 'loops');
|
|
97
|
+
if (!fs.existsSync(loopsDir)) return false;
|
|
98
|
+
|
|
99
|
+
let corridas;
|
|
100
|
+
try {
|
|
101
|
+
corridas = fs.readdirSync(loopsDir).filter((d) => d.startsWith('tdd-'));
|
|
102
|
+
} catch (_) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const limite = Date.now() - ventanaHoras * 3600 * 1000;
|
|
107
|
+
|
|
108
|
+
for (const corrida of corridas) {
|
|
109
|
+
const tsvPath = path.join(loopsDir, corrida, 'iteraciones.tsv');
|
|
110
|
+
let stat;
|
|
111
|
+
try {
|
|
112
|
+
stat = fs.statSync(tsvPath);
|
|
113
|
+
} catch (_) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (stat.mtimeMs < limite) continue;
|
|
117
|
+
|
|
118
|
+
let contenido;
|
|
119
|
+
try {
|
|
120
|
+
contenido = fs.readFileSync(tsvPath, 'utf-8');
|
|
121
|
+
} catch (_) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const linea of contenido.split('\n')) {
|
|
126
|
+
if (linea.startsWith('#') || linea.startsWith('iteracion')) continue;
|
|
127
|
+
const cols = linea.split('\t');
|
|
128
|
+
if (cols.length < 6) continue;
|
|
129
|
+
const metrica = Number(cols[2]);
|
|
130
|
+
const estado = (cols[4] || '').trim();
|
|
131
|
+
const descripcion = cols[5] || '';
|
|
132
|
+
if ((estado === 'baseline' && metrica > 0) || /\bRED\b/.test(descripcion)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Decide si el commit amerita advertencia G2.
|
|
142
|
+
* @returns {null | { fuentes: string[], tests: string[] }}
|
|
143
|
+
*/
|
|
144
|
+
function evaluarCommit({ cwd, comando, helpers }) {
|
|
145
|
+
if (!esGitCommit(comando)) return null;
|
|
146
|
+
|
|
147
|
+
const mensaje = helpers.extraerMensajeCommit(comando);
|
|
148
|
+
if (helpers.esPrefijoExentoDeTests(mensaje)) return null;
|
|
149
|
+
|
|
150
|
+
const fase = estadoTddDeFaseActiva(cwd);
|
|
151
|
+
if (!fase.aplica) return null;
|
|
152
|
+
|
|
153
|
+
const staged = archivosStaged(cwd);
|
|
154
|
+
const fuentes = staged.filter(helpers.esArchivoFuente);
|
|
155
|
+
const tests = staged.filter(helpers.esArchivoTest);
|
|
156
|
+
// Solo el caso feature-con-tests: "fuente sin tests" es dominio de G3.
|
|
157
|
+
if (fuentes.length === 0 || tests.length === 0) return null;
|
|
158
|
+
|
|
159
|
+
const ventana = Number(process.env.SWL_TDD_GATE_VENTANA_H) || VENTANA_HORAS_DEFAULT;
|
|
160
|
+
if (hayEvidenciaRed(cwd, ventana)) return null;
|
|
161
|
+
|
|
162
|
+
return { fuentes, tests };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function emitirNudge({ cwd, hallazgo }) {
|
|
166
|
+
let emit;
|
|
167
|
+
try {
|
|
168
|
+
({ emit } = require(path.join(__dirname, 'lib', 'nudge-tracker.js')));
|
|
169
|
+
} catch (_) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const muestra = hallazgo.tests.slice(0, 2).join(', ');
|
|
174
|
+
emit({
|
|
175
|
+
kind: 'tdd-red-evidence',
|
|
176
|
+
target: hallazgo.tests[0],
|
|
177
|
+
source: 'hooks/tdd-gate.js',
|
|
178
|
+
message:
|
|
179
|
+
`Gate G2 (warn): commit de feature con tests (${muestra}) sin evidencia de ciclo ` +
|
|
180
|
+
`RED en .planning/loops/tdd-*/ — el TDD exige registrar el test fallando ANTES de ` +
|
|
181
|
+
`implementar (Skill("tdd-workflow") § Evidencia RED en telemetría). Este aviso ` +
|
|
182
|
+
`calibra la promoción a blocking (ADR-0035). Opt-out: SWL_TDD_GATE=0 o ` +
|
|
183
|
+
`'**TDD**: off — razón: ...' en el CONTEXTO de la fase.`,
|
|
184
|
+
data: { fuentes: hallazgo.fuentes, tests: hallazgo.tests, gate: 'G2' },
|
|
185
|
+
mutation_category: 'optimize',
|
|
186
|
+
risk_level: 'low',
|
|
187
|
+
});
|
|
188
|
+
} catch (_) {
|
|
189
|
+
// La observabilidad nunca bloquea el trabajo productivo.
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function main() {
|
|
194
|
+
if (process.env.SWL_TDD_GATE === '0') process.exit(0);
|
|
195
|
+
|
|
196
|
+
let hookInput = '';
|
|
197
|
+
try {
|
|
198
|
+
hookInput = fs.readFileSync(0, 'utf-8');
|
|
199
|
+
} catch (_) {
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let evento;
|
|
204
|
+
try {
|
|
205
|
+
evento = JSON.parse(hookInput);
|
|
206
|
+
} catch (_) {
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (evento?.tool_name !== 'Bash') process.exit(0);
|
|
211
|
+
const comando = evento?.tool_input?.command;
|
|
212
|
+
const cwd = process.cwd();
|
|
213
|
+
|
|
214
|
+
// Zero-config: sin .planning/ no hay ciclo GSD que vigilar.
|
|
215
|
+
if (!fs.existsSync(path.join(cwd, '.planning'))) process.exit(0);
|
|
216
|
+
|
|
217
|
+
let helpers;
|
|
218
|
+
try {
|
|
219
|
+
helpers = require(path.join(__dirname, 'calidad-pre-commit.js'));
|
|
220
|
+
} catch (_) {
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let hallazgo = null;
|
|
225
|
+
try {
|
|
226
|
+
hallazgo = evaluarCommit({ cwd, comando, helpers });
|
|
227
|
+
} catch (_) {
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (hallazgo) emitirNudge({ cwd, hallazgo });
|
|
232
|
+
|
|
233
|
+
// WARN-ONLY (ADR-0035): jamás exit 2 en este modo.
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (require.main === module) {
|
|
238
|
+
main();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = { esGitCommit, estadoTddDeFaseActiva, hayEvidenciaRed, evaluarCommit };
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
* - fragmentos: [_intent-spec]
|
|
15
15
|
* - maxTurnos (Parte 8 — Stop Rules)
|
|
16
16
|
*
|
|
17
|
-
* Comportamiento:
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
17
|
+
* Comportamiento (modo blocking desde ADR-0033):
|
|
18
|
+
* - BLOQUEA (exit 2) cuando un agente ALTO tiene frontmatter incompleto.
|
|
19
|
+
* El Write/Edit ya ocurrió (PostToolUse); el exit 2 surface el error a
|
|
20
|
+
* Claude para que complete el intent spec. Sigue emitiendo el nudge para
|
|
21
|
+
* trazabilidad antes de bloquear.
|
|
22
|
+
* - Sale 0 (sin ruido) cuando el agente ALTO está completo o no es ALTO.
|
|
20
23
|
* - Solo se dispara con archivos `agentes/*.md` (no fragmentos `_*.md`)
|
|
21
24
|
* - Respeta exclusiones: temp/, node_modules/, respositorios-git/, _userland/
|
|
22
25
|
*
|
|
@@ -34,9 +37,10 @@
|
|
|
34
37
|
* accionado: false
|
|
35
38
|
* }
|
|
36
39
|
*
|
|
37
|
-
* Origen: ADR-0027 (
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* Origen: ADR-0027 (warn-only) → ADR-0033 (promoción a blocking, 2026-06-11).
|
|
41
|
+
* Promoción justificada: 24 días estable en warn-only, 8/8 agentes ALTO con
|
|
42
|
+
* frontmatter completo al momento de la promoción (cero falsos positivos sobre
|
|
43
|
+
* el estado actual). Opt-out preservado: SWL_INTENT_SPEC=0.
|
|
40
44
|
*/
|
|
41
45
|
|
|
42
46
|
const fs = require('fs');
|
|
@@ -216,7 +220,23 @@ try {
|
|
|
216
220
|
}
|
|
217
221
|
fs.appendFileSync(nudgesFile, JSON.stringify(nudge) + '\n');
|
|
218
222
|
} catch (_) {
|
|
219
|
-
// Si falla persistir, no romper el flujo del
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
// Si falla persistir, no romper el flujo del bloqueo
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Modo blocking (ADR-0033) ─────────────────────────────────────────────
|
|
227
|
+
// El agente ALTO tiene frontmatter incompleto: bloquear (exit 2) y surface el
|
|
228
|
+
// detalle a Claude por stderr para que complete el intent spec. Opt-out global
|
|
229
|
+
// SWL_INTENT_SPEC=0 ya cortó arriba; aquí el bloqueo es activo.
|
|
230
|
+
const razon = [
|
|
231
|
+
`Intent spec incompleto en agente ALTO riesgo: ${nombreAgente}`,
|
|
232
|
+
`Faltan campos obligatorios: ${faltan.join(', ')}.`,
|
|
233
|
+
``,
|
|
234
|
+
`Completa el frontmatter según reglas/intent-engineering.md:`,
|
|
235
|
+
` strategy, healthMetrics, steering, hardGuardrails, maxTurnos,`,
|
|
236
|
+
` y fragmentos: [_intent-spec].`,
|
|
237
|
+
`Carga Skill("proceso-intent-engineering") para la guía completa.`,
|
|
238
|
+
``,
|
|
239
|
+
`Para omitir esta verificación: SWL_INTENT_SPEC=0 (registra la excepción).`,
|
|
240
|
+
].join('\n');
|
|
241
|
+
process.stderr.write(razon);
|
|
242
|
+
process.exit(2);
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# swl-ses (@saulwade/swl-ses)
|
|
2
2
|
|
|
3
|
-
> Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code.
|
|
3
|
+
> Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. 60 agentes, 182 habilidades, 43 comandos, 29 reglas base y 46 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 2.0.0.
|
|
4
4
|
|
|
5
5
|
Archivo generado por `node scripts/generar-inventario.js` — no editar a mano. Las cifras se sincronizan con INVENTARIO.md en cada regeneración.
|
|
6
6
|
|
|
@@ -16,11 +16,11 @@ Archivo generado por `node scripts/generar-inventario.js` — no editar a mano.
|
|
|
16
16
|
|
|
17
17
|
## Componentes
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
19
|
+
- 60 agentes especializados en `agentes/` (orquestación, implementación por stack, revisión, calidad, diseño)
|
|
20
|
+
- 182 habilidades cargables bajo demanda en `habilidades/` (conocimiento operacional con divulgación progresiva)
|
|
21
|
+
- 43 comandos `/swl:*` en `comandos/swl/` (ciclo GSD, calidad, release, diagnóstico)
|
|
22
|
+
- 29 reglas base + 40 reglas por lenguaje en `reglas/` (políticas obligatorias por matcher)
|
|
23
|
+
- 46 hooks en `hooks/` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)
|
|
24
24
|
|
|
25
25
|
## Opcional
|
|
26
26
|
|
|
@@ -168,19 +168,10 @@
|
|
|
168
168
|
"maxConsecutiveFailures": 5,
|
|
169
169
|
"degradeOnFailure": "skip"
|
|
170
170
|
},
|
|
171
|
-
"
|
|
171
|
+
"ciclo-evolucion.js": {
|
|
172
172
|
"event": "Stop",
|
|
173
173
|
"matcher": "",
|
|
174
|
-
"description": "
|
|
175
|
-
"blocking": false,
|
|
176
|
-
"async": true,
|
|
177
|
-
"maxConsecutiveFailures": 5,
|
|
178
|
-
"degradeOnFailure": "skip"
|
|
179
|
-
},
|
|
180
|
-
"metricas-evolucion.js": {
|
|
181
|
-
"event": "Stop",
|
|
182
|
-
"matcher": "",
|
|
183
|
-
"description": "Consolida métricas del ciclo de evolución (nudges accionados/pendientes, instintos, fallos de agentes, evoluciones aplicadas/revertidas) y actualiza .planning/evolution/metricas.json con health_score compuesto. Dispara escalamiento a alertas persistentes si >=5 nudges del mismo tipo ignorados en 14d.",
|
|
174
|
+
"description": "Ciclo de evolución (evento Stop): en un solo proceso ejecuta las sub-etapas de métricas (consolida .planning/evolution/metricas.json con health_score; escala alertas persistentes si >=5 nudges del mismo tipo ignorados en 14d) y perfil de usuario (acumula dirty-bit en .planning/user-profile/; nudge a perfilador-usuario-swl al cruzar umbral). Fusión de metricas-evolucion.js + actualizar-perfil-usuario.js (Fase 11, D-12). Lógica en hooks/lib/etapa-{metricas,perfil-usuario}.js.",
|
|
184
175
|
"blocking": false,
|
|
185
176
|
"async": true,
|
|
186
177
|
"maxConsecutiveFailures": 5,
|
|
@@ -195,10 +186,10 @@
|
|
|
195
186
|
"maxConsecutiveFailures": 5,
|
|
196
187
|
"degradeOnFailure": "skip"
|
|
197
188
|
},
|
|
198
|
-
"
|
|
189
|
+
"ciclo-evolucion-subagente.js": {
|
|
199
190
|
"event": "SubagentStop",
|
|
200
191
|
"matcher": "",
|
|
201
|
-
"description": "
|
|
192
|
+
"description": "Ciclo de evolución (evento SubagentStop): registra cada terminación de subagente SWL en .planning/auto-evolution/agentes.jsonl y emite nudge a /swl:evolucionar cuando un agente acumula fallos, volumen de runs, loop o drift (ventana 14 días, throttle 24h). Renombrado de auto-evolucion.js (Fase 11, D-12). Lógica en hooks/lib/etapa-auto-evolucion.js.",
|
|
202
193
|
"blocking": false,
|
|
203
194
|
"async": true,
|
|
204
195
|
"maxConsecutiveFailures": 5,
|
|
@@ -374,14 +365,32 @@
|
|
|
374
365
|
"maxConsecutiveFailures": 5,
|
|
375
366
|
"degradeOnFailure": "skip"
|
|
376
367
|
},
|
|
368
|
+
"tdd-gate.js": {
|
|
369
|
+
"event": "PreToolUse",
|
|
370
|
+
"matcher": "Bash",
|
|
371
|
+
"description": "Gate G2 (TDD): ante git commit de feature (fuente+tests staged) verifica evidencia de ciclo RED en .planning/loops/tdd-* (telemetria escrita por ejecutar-fase/tdd-workflow). Sin evidencia emite nudge kind:tdd-red-evidence (warn-only, NUNCA exit 2 en este modo). Solo aplica con fase activa (ciclo GSD); 'fuente sin tests' es dominio de G3 (sin doble nudge). Respeta opt-out declarado del CONTEXTO (**TDD**: off) y prefijos docs:/chore:/style:. Ventana de evidencia: 24h (SWL_TDD_GATE_VENTANA_H). Opt-out: SWL_TDD_GATE=0. Promocion a blocking via ADR-0035 (D-07).",
|
|
372
|
+
"blocking": false,
|
|
373
|
+
"async": false,
|
|
374
|
+
"maxConsecutiveFailures": 5,
|
|
375
|
+
"degradeOnFailure": "warn"
|
|
376
|
+
},
|
|
377
|
+
"spec-gate.js": {
|
|
378
|
+
"event": "PreToolUse",
|
|
379
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
380
|
+
"description": "Gate G0 (SDD): detecta Write/Edit a codigo fuente sin fase activa con PLAN aprobado. Lee .planning/locks/fase-activa.json (escrito por /swl:aprobar-plan) y re-computa SHA256 del plan referenciado; sin fase activa o plan mutado emite nudge kind:spec-gate (warn-only, NUNCA exit 2 en este modo). Zero-config: sin .planning/ sale silencioso. Excluye tests/generados/docs (clasificador esArchivoFuente de calidad-pre-commit) y temp/, node_modules/, _userland/, respositorios-git/. Opt-out: SWL_SPEC_GATE=0. Promocion a blocking via ADR-0034 tras ~2 semanas de calibracion (D-07).",
|
|
381
|
+
"blocking": false,
|
|
382
|
+
"async": false,
|
|
383
|
+
"maxConsecutiveFailures": 5,
|
|
384
|
+
"degradeOnFailure": "warn"
|
|
385
|
+
},
|
|
377
386
|
"validar-intent-spec.js": {
|
|
378
387
|
"event": "PostToolUse",
|
|
379
388
|
"matcher": "Write|Edit|MultiEdit",
|
|
380
|
-
"description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolution/nudges.jsonl
|
|
381
|
-
"blocking":
|
|
382
|
-
"async":
|
|
389
|
+
"description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolution/nudges.jsonl y BLOQUEA (exit 2) si faltan campos. Excluye temp/, node_modules/, respositorios-git/, _userland/, .planning/. Opt-out: SWL_INTENT_SPEC=0. ADR-0027 (warn-only) -> ADR-0033 (blocking, 2026-06-11).",
|
|
390
|
+
"blocking": true,
|
|
391
|
+
"async": false,
|
|
383
392
|
"maxConsecutiveFailures": 5,
|
|
384
|
-
"degradeOnFailure": "
|
|
393
|
+
"degradeOnFailure": "warn"
|
|
385
394
|
},
|
|
386
395
|
"notificacion-telegram.js": {
|
|
387
396
|
"event": "Stop",
|