@saulwade/swl-ses 2.5.2 → 2.6.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 +194 -192
- package/README.md +600 -600
- package/agentes/auto-evolucion-swl.md +27 -3
- package/bin/swl-ses.js +32 -6
- package/comandos/swl/actualizar.md +174 -174
- package/comandos/swl/adoptar-proyecto.md +265 -265
- package/comandos/swl/aprender.md +836 -823
- package/comandos/swl/aprobar-plan.md +146 -146
- package/comandos/swl/auditar-deps.md +134 -134
- package/comandos/swl/autoresearch.md +264 -264
- package/comandos/swl/ayuda.md +224 -224
- package/comandos/swl/brainstorm.md +51 -51
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/checkpoint.md +325 -325
- package/comandos/swl/claudemd.md +234 -234
- package/comandos/swl/compactar.md +310 -310
- package/comandos/swl/configurar-ci.md +235 -235
- package/comandos/swl/contexto.md +110 -110
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/crear-skill.md +292 -292
- package/comandos/swl/cron.md +194 -194
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/discutir-fase.md +169 -169
- package/comandos/swl/ejecutar-fase.md +233 -233
- package/comandos/swl/evaluar-skill.md +520 -505
- package/comandos/swl/evolucion-continua.md +73 -0
- package/comandos/swl/evolucionar.md +267 -254
- package/comandos/swl/exportar-vault.md +583 -583
- package/comandos/swl/fix.md +118 -118
- package/comandos/swl/gateway.md +158 -158
- package/comandos/swl/inbox.md +116 -116
- package/comandos/swl/instalar.md +220 -220
- package/comandos/swl/instintos.md +86 -86
- package/comandos/swl/mapear-codebase.md +312 -312
- package/comandos/swl/mcp-status.md +175 -175
- package/comandos/swl/modelo.md +100 -100
- package/comandos/swl/nemesis.md +433 -433
- package/comandos/swl/notificaciones.md +299 -299
- package/comandos/swl/nuevo-proyecto.md +251 -251
- package/comandos/swl/planear-fase.md +263 -263
- package/comandos/swl/plugins.md +256 -256
- package/comandos/swl/predecir.md +169 -169
- package/comandos/swl/reflect-skills.md +125 -125
- package/comandos/swl/release.md +450 -450
- package/comandos/swl/revisar-impacto.md +201 -201
- package/comandos/swl/revisar.md +330 -330
- package/comandos/swl/seguridad.md +189 -189
- package/comandos/swl/sesiones.md +200 -200
- package/comandos/swl/skill-search.md +113 -113
- package/comandos/swl/status.md +343 -343
- package/comandos/swl/verificar.md +817 -817
- package/comandos/swl/wiki.md +620 -620
- package/gateway/cron/jobs.example.json +12 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -276
- package/habilidades/autoresearch/SKILL.md +3 -2
- package/habilidades/benchmark-memoria/SKILL.md +7 -7
- package/habilidades/changelog-generator/SKILL.md +174 -174
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
- package/habilidades/context-builder/SKILL.md +4 -0
- package/habilidades/doubt-driven-review/SKILL.md +207 -191
- package/habilidades/drift-detection/SKILL.md +6 -1
- package/habilidades/ejecutar-fase/SKILL.md +6 -6
- package/habilidades/eval-framework/SKILL.md +8 -3
- package/habilidades/harness-claude-code/SKILL.md +314 -308
- package/habilidades/infra-github-actions/SKILL.md +4 -3
- package/habilidades/instalar-sistema/SKILL.md +227 -223
- package/habilidades/memoria-busqueda/SKILL.md +31 -39
- package/habilidades/planear-fase/SKILL.md +358 -350
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
- package/habilidades/release-semver/SKILL.md +4 -2
- package/habilidades/swl-claudemd/SKILL.md +6 -7
- package/habilidades/swl-dashboard/SKILL.md +11 -43
- package/habilidades/tdd-workflow/SKILL.md +749 -744
- package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
- package/hooks/agente-lifecycle.js +2 -1
- package/hooks/aiisms-detector.js +13 -4
- package/hooks/audit-trail.js +2 -1
- package/hooks/auto-consolidacion.js +2 -1
- package/hooks/captura-acciones-post.js +2 -1
- package/hooks/captura-acciones-session.js +2 -1
- package/hooks/captura-feedback-usuario.js +3 -2
- package/hooks/claudemd-bloat-detector.js +12 -3
- package/hooks/claudemd-duplicacion-detector.js +13 -3
- package/hooks/contexto-iteracion.js +2 -1
- package/hooks/degradacion-instintos.js +2 -1
- package/hooks/extraccion-aprendizajes.js +109 -15
- package/hooks/grafo-contexto.js +2 -1
- package/hooks/guardrail-modelo.js +2 -1
- package/hooks/inbox-aviso.js +2 -1
- package/hooks/inyeccion-contexto.js +2 -1
- package/hooks/lib/agent-matcher.js +2 -1
- package/hooks/lib/agent-routing.js +2 -1
- package/hooks/lib/autonomia.js +5 -3
- package/hooks/lib/captura-acciones.js +2 -1
- package/hooks/lib/consolidation-lock.js +21 -10
- package/hooks/lib/etapa-auto-evolucion.js +10 -4
- package/hooks/lib/etapa-metricas.js +2 -1
- package/hooks/lib/etapa-perfil-usuario.js +20 -4
- package/hooks/lib/evolution-tracker.js +2 -1
- package/hooks/lib/gateway-notify.js +193 -179
- package/hooks/lib/loop-telemetry.js +5 -4
- package/hooks/lib/mcp-health.js +2 -1
- package/hooks/lib/memory-search.js +4 -0
- package/hooks/lib/merkle-audit.js +58 -6
- package/hooks/lib/nudge-tracker.js +2 -1
- package/hooks/lib/otlp-exporter.js +2 -1
- package/hooks/lib/propose-step.js +3 -2
- package/hooks/lib/raiz-proyecto.js +102 -0
- package/hooks/lib/run-log.js +2 -1
- package/hooks/lib/singleton-guard.js +218 -27
- package/hooks/lib/telegram-cliente.js +17 -8
- package/hooks/preservar-estado-pre-compact.js +2 -1
- package/hooks/proteccion-rutas.js +59 -3
- package/hooks/registro-turnos.js +2 -1
- package/hooks/resumen-sesion.js +2 -1
- package/hooks/risk-scoring.js +2 -1
- package/hooks/rotar-audit-auto.js +46 -20
- package/hooks/session-briefing.js +127 -1
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +6 -3
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/hooks/telemetria-agentes.js +2 -1
- package/hooks/telemetria-skill-routing.js +2 -1
- package/hooks/tracking-costos.js +4 -3
- package/hooks/validar-formato-post-subagente.js +2 -1
- package/hooks/validar-intent-spec.js +2 -1
- package/hooks/validar-memoria-hook.js +13 -3
- package/hooks/validar-planning-paths.js +2 -1
- package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
- package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
- package/instintos/perfil-usuario.yaml +506 -3
- package/instintos/proyecto.yaml +78 -0
- package/llms.txt +2 -2
- package/manifiestos/canonical-hashes.json +664 -2
- package/manifiestos/modulos.json +19 -14
- package/manifiestos/planning-paths.json +1 -0
- package/manifiestos/skills-lock.json +53 -53
- package/package.json +2 -3
- package/plugin.json +2 -2
- package/scripts/actualizar.js +3 -0
- package/scripts/auditar-clases-conocidas.js +32 -4
- package/scripts/benchmark-memoria.js +1 -0
- package/scripts/cli/autonomia.js +23 -0
- package/scripts/cli/benchmark-memoria.js +37 -0
- package/scripts/cli/ciclo-autonomo.js +73 -0
- package/scripts/cli/ciclo-fase-b.js +102 -0
- package/scripts/cli/guardrail-metrics.js +39 -0
- package/scripts/cli/loop-telemetry.js +4 -2
- package/scripts/cli/memoria-search.js +69 -0
- package/scripts/cli/nudge-accionar.js +39 -0
- package/scripts/cli/run-eval.js +38 -0
- package/scripts/cli/run-skill-evals.js +13 -2
- package/scripts/derivar-feature-list.js +15 -14
- package/scripts/desinstalar.js +11 -0
- package/scripts/doctor.js +24 -10
- package/scripts/instalador.js +106 -7
- package/scripts/lib/activar-hooks-proyecto.js +116 -0
- package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
- package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
- package/scripts/lib/ciclo-autonomo/config.js +165 -0
- package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
- package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
- package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
- package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
- package/scripts/lib/ciclo-autonomo/index.js +301 -0
- package/scripts/lib/ciclo-autonomo/lock.js +124 -0
- package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
- package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
- package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
- package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
- package/scripts/lib/estado.js +9 -0
- package/scripts/lib/evidencia-valor.js +1 -1
- package/scripts/lib/gitignore-manifest.js +8 -1
- package/scripts/lib/hooks-settings.js +45 -0
- package/scripts/rotar-audit-logs.js +48 -2
- package/scripts/run-eval.js +1 -0
- package/scripts/run-skill-evals.js +287 -8
- package/scripts/smoke-test.js +16 -8
- package/scripts/tui/pantallas/install-wizard.js +403 -347
- package/scripts/validar.js +40 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Subcomando `swl-ses ciclo-fase-b [status] [--dry-run]` — CLI headless de la
|
|
5
|
+
* Fase B del ciclo autónomo (Fase 22, T-07, ADR-0042 gate G-B, REQ-22-07).
|
|
6
|
+
*
|
|
7
|
+
* Espeja el patrón de `scripts/cli/ciclo-autonomo.js` (contrato del bin: cada
|
|
8
|
+
* subcomando exporta UNA función(opciones) sin efectos al require).
|
|
9
|
+
*
|
|
10
|
+
* (nada) | --dry-run → corre `runner-fase-b.correrFaseB` con `dryRun:true`
|
|
11
|
+
* SIEMPRE, se pase o no el flag. En Fase 22 el runner NUNCA invoca LLM ni
|
|
12
|
+
* gasta bajo ninguna combinación de argumentos — el modo real
|
|
13
|
+
* (`dryRun:false` + `invocador` cableado) llega en Fase 24; hasta
|
|
14
|
+
* entonces `--dry-run` es un no-op explícito frente al default.
|
|
15
|
+
* status → reporta `fase_b_habilitada`, `last_run_fase_b`, candidatos
|
|
16
|
+
* pendientes (conteo tolerante de `evolution/candidatos-fase-b.jsonl` —
|
|
17
|
+
* NUNCA `candidatos.detectar`, que tiene efecto de escritura; `status`
|
|
18
|
+
* jamás muta disco) y el estado de presupuesto (`gasto_mes`,
|
|
19
|
+
* `presupuesto_usd_mes`, `pausado_por_presupuesto`) vía
|
|
20
|
+
* `presupuesto.estado`.
|
|
21
|
+
*
|
|
22
|
+
* Uso: swl-ses ciclo-fase-b [status] [--dry-run]
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
const { correrFaseB } = require('../lib/ciclo-autonomo/runner-fase-b');
|
|
29
|
+
const config = require('../lib/ciclo-autonomo/config');
|
|
30
|
+
const presupuesto = require('../lib/ciclo-autonomo/presupuesto');
|
|
31
|
+
|
|
32
|
+
const RUTA_CANDIDATOS_RELATIVA = path.join('.planning', 'evolution', 'candidatos-fase-b.jsonl');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Conteo tolerante de líneas JSON válidas en `candidatos-fase-b.jsonl`.
|
|
36
|
+
* `status` es de solo lectura: nunca invoca `candidatos.detectar` (que
|
|
37
|
+
* persiste candidatos nuevos) — solo cuenta lo que YA quedó registrado por
|
|
38
|
+
* una corrida previa de la Fase A o del runner.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} baseDir - Directorio raíz del proyecto.
|
|
41
|
+
* @returns {number}
|
|
42
|
+
*/
|
|
43
|
+
function contarCandidatosPendientes(baseDir) {
|
|
44
|
+
let crudo;
|
|
45
|
+
try {
|
|
46
|
+
crudo = fs.readFileSync(path.join(baseDir, RUTA_CANDIDATOS_RELATIVA), 'utf8');
|
|
47
|
+
} catch (_) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
return crudo.split(/\r\n|\n/)
|
|
51
|
+
.filter((linea) => linea.trim() !== '')
|
|
52
|
+
.filter((linea) => {
|
|
53
|
+
try {
|
|
54
|
+
JSON.parse(linea);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (_) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}).length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cicloFaseB(opciones = {}) {
|
|
63
|
+
const accion = (opciones._args || [])[0];
|
|
64
|
+
const baseDir = opciones.dir || process.cwd();
|
|
65
|
+
|
|
66
|
+
if (accion === 'status') {
|
|
67
|
+
const cfg = config.leerConfig(baseDir);
|
|
68
|
+
const presu = presupuesto.estado(baseDir);
|
|
69
|
+
const estado = {
|
|
70
|
+
fase_b_habilitada: config.estaFaseBHabilitada(baseDir),
|
|
71
|
+
last_run_fase_b: cfg.last_run_fase_b,
|
|
72
|
+
candidatos_pendientes: contarCandidatosPendientes(baseDir),
|
|
73
|
+
gasto_mes: presu.gasto_mes,
|
|
74
|
+
presupuesto_usd_mes: presu.tope,
|
|
75
|
+
pausado_por_presupuesto: presu.pausado_por_presupuesto,
|
|
76
|
+
};
|
|
77
|
+
console.log(`[ciclo-fase-b] fase_b_habilitada: ${estado.fase_b_habilitada}`);
|
|
78
|
+
console.log(`[ciclo-fase-b] last_run_fase_b: ${estado.last_run_fase_b || 'nunca'}`);
|
|
79
|
+
console.log(`[ciclo-fase-b] candidatos_pendientes: ${estado.candidatos_pendientes}`);
|
|
80
|
+
console.log(`[ciclo-fase-b] gasto_mes: $${estado.gasto_mes} / $${estado.presupuesto_usd_mes} · pausado_por_presupuesto: ${estado.pausado_por_presupuesto}`);
|
|
81
|
+
return estado;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (accion !== undefined && accion !== '--dry-run') {
|
|
85
|
+
console.error(`[ciclo-fase-b] acción desconocida: "${accion}". Uso: swl-ses ciclo-fase-b [status] [--dry-run]`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fase 22: dryRun forzado a true sin importar el flag — el runner valida
|
|
91
|
+
// toda la tubería (lock, catch-up, guard, credencial, presupuesto) y
|
|
92
|
+
// registra la corrida SIN invocar LLM. El modo real llega en Fase 24.
|
|
93
|
+
const resultado = correrFaseB(baseDir, { dryRun: true });
|
|
94
|
+
if (!resultado.ejecutado) {
|
|
95
|
+
console.log(`[ciclo-fase-b] sin corrida: ${resultado.razon}`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(`[ciclo-fase-b] corrida dry-run completada — candidatos: ${resultado.candidatos}`);
|
|
98
|
+
}
|
|
99
|
+
return resultado;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = cicloFaseB;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper CLI `swl-ses guardrail-metrics` — métricas y diagnóstico de los
|
|
5
|
+
* guardrails de la sesión (bridge en tmpdir por sessionId). Reemplaza el
|
|
6
|
+
* `node -e "require('./hooks/lib/guardrail-metrics')..."` relativo al
|
|
7
|
+
* proyecto que swl-dashboard instruía — roto downstream (ver
|
|
8
|
+
* docs/invocacion-cli-cross-scope.md).
|
|
9
|
+
*
|
|
10
|
+
* Uso: swl-ses guardrail-metrics [--session=ID] [--json]
|
|
11
|
+
* --session default: env CLAUDE_SESSION_ID o "default"
|
|
12
|
+
* --json imprime {metricas, advertencias} crudo
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { getMetrics, formatearResumen, diagnosticar } = require('../../hooks/lib/guardrail-metrics');
|
|
16
|
+
|
|
17
|
+
function guardrailMetrics(opciones = {}) {
|
|
18
|
+
const sessionId = (opciones.session != null && opciones.session !== true)
|
|
19
|
+
? String(opciones.session)
|
|
20
|
+
: (process.env.CLAUDE_SESSION_ID || 'default');
|
|
21
|
+
|
|
22
|
+
const metricas = getMetrics(sessionId);
|
|
23
|
+
const advertencias = diagnosticar(metricas);
|
|
24
|
+
|
|
25
|
+
if (opciones.json) {
|
|
26
|
+
console.log(JSON.stringify({ metricas, advertencias }, null, 2));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(formatearResumen(metricas));
|
|
31
|
+
if (advertencias.length > 0) {
|
|
32
|
+
console.log('\n⚠ Diagnóstico:');
|
|
33
|
+
for (const a of advertencias) console.log(' - ' + a);
|
|
34
|
+
} else {
|
|
35
|
+
console.log('\n✓ Guardrails sin anomalías detectadas.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = guardrailMetrics;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const fs = require('node:fs');
|
|
17
17
|
const path = require('node:path');
|
|
18
18
|
const lt = require('../../hooks/lib/loop-telemetry');
|
|
19
|
+
const { raizProyecto } = require('../../hooks/lib/raiz-proyecto');
|
|
19
20
|
|
|
20
21
|
// Parseo no degradante: si el flag está presente pero su JSON es inválido,
|
|
21
22
|
// reporta y devuelve {ok:false} en vez de silenciar a fallback (anti-fallback
|
|
@@ -89,8 +90,9 @@ function loopTelemetry(opciones = {}) {
|
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
// listar (default)
|
|
93
|
-
|
|
93
|
+
// listar (default) — misma ancla que iniciar/registrar (raíz del proyecto),
|
|
94
|
+
// o listaría un .planning/loops/ distinto al que las corridas escriben.
|
|
95
|
+
const base = path.join(raizProyecto(process.cwd()), lt.DIR_LOOPS);
|
|
94
96
|
let dirs = [];
|
|
95
97
|
try {
|
|
96
98
|
dirs = fs
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper CLI `swl-ses memoria-search <op>` — búsqueda en la memoria del
|
|
5
|
+
* proyecto (.planning/sessions/, APRENDIZAJES.md, diary). Reemplaza los
|
|
6
|
+
* `require('./hooks/lib/memory-search')` relativos al proyecto que
|
|
7
|
+
* memoria-busqueda y benchmark-memoria instruían — rotos downstream (ver
|
|
8
|
+
* docs/invocacion-cli-cross-scope.md).
|
|
9
|
+
*
|
|
10
|
+
* Ops:
|
|
11
|
+
* search --query="..." [--limit=N] [--tipo=aprendizaje|sesion|instinto]
|
|
12
|
+
* [--desde=YYYY-MM-DD] [--hasta=YYYY-MM-DD] (default)
|
|
13
|
+
* timeline --ids=id1,id2,...
|
|
14
|
+
* fetch --id=<id>
|
|
15
|
+
*
|
|
16
|
+
* Salida: JSON a stdout. La base es la RAÍZ del proyecto (raizProyecto),
|
|
17
|
+
* no el cwd crudo — misma semántica que los hooks (DT-HOOKS-RAIZ-GIT).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ms = require('../../hooks/lib/memory-search');
|
|
21
|
+
const { raizProyecto } = require('../../hooks/lib/raiz-proyecto');
|
|
22
|
+
|
|
23
|
+
function memoriaSearch(opciones = {}) {
|
|
24
|
+
const op = (opciones._args && opciones._args[0]) || 'search';
|
|
25
|
+
const base = raizProyecto(process.cwd());
|
|
26
|
+
|
|
27
|
+
if (op === 'search') {
|
|
28
|
+
const query = opciones.query != null && opciones.query !== true ? String(opciones.query) : '';
|
|
29
|
+
if (!query) {
|
|
30
|
+
console.error('Error: falta --query="<términos>"');
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const filtros = {};
|
|
35
|
+
const limit = parseInt(opciones.limit, 10);
|
|
36
|
+
if (!isNaN(limit) && limit > 0) filtros.limit = limit;
|
|
37
|
+
if (opciones.tipo != null && opciones.tipo !== true) filtros.tipo = String(opciones.tipo);
|
|
38
|
+
if (opciones.desde != null && opciones.desde !== true) filtros.fecha_desde = String(opciones.desde);
|
|
39
|
+
if (opciones.hasta != null && opciones.hasta !== true) filtros.fecha_hasta = String(opciones.hasta);
|
|
40
|
+
console.log(JSON.stringify(ms.search(base, query, filtros), null, 2));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (op === 'timeline') {
|
|
45
|
+
const ids = String(opciones.ids || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
46
|
+
if (ids.length === 0) {
|
|
47
|
+
console.error('Error: falta --ids=id1,id2,...');
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(JSON.stringify(ms.timeline(base, ids), null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (op === 'fetch') {
|
|
56
|
+
if (opciones.id == null || opciones.id === true) {
|
|
57
|
+
console.error('Error: falta --id=<id>');
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log(JSON.stringify(ms.fetch(base, String(opciones.id)), null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.error(`Error: op desconocida "${op}" (usa search|timeline|fetch)`);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = memoriaSearch;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Subcomando `swl-ses nudge-accionar <id> --por <consumidor>` — cierra el
|
|
5
|
+
* ciclo de un nudge (T-09, Fase 21 / ADR-0041).
|
|
6
|
+
*
|
|
7
|
+
* Lo invocan los comandos /swl:aprender y /swl:evolucionar (y el agente
|
|
8
|
+
* auto-evolucion-swl) tras atender la sugerencia de un nudge, para que
|
|
9
|
+
* `evolution/nudges.jsonl` registre quién lo accionó. Sin este cierre los
|
|
10
|
+
* nudges se acumulan sin consumidor (174 emitidos / 0 accionados al crear
|
|
11
|
+
* la Fase 21).
|
|
12
|
+
*
|
|
13
|
+
* Uso: swl-ses nudge-accionar <id> [--por <consumidor>]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { marcarAccionado } = require('../lib/ciclo-autonomo/higiene-nudges');
|
|
17
|
+
|
|
18
|
+
function nudgeAccionar(opciones = {}) {
|
|
19
|
+
const id = (opciones._args || [])[0];
|
|
20
|
+
const baseDir = opciones.dir || process.cwd();
|
|
21
|
+
|
|
22
|
+
if (!id) {
|
|
23
|
+
console.error('[nudge-accionar] falta el id. Uso: swl-ses nudge-accionar <id> [--por <consumidor>]');
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const por = opciones.por || opciones.by || 'desconocido';
|
|
29
|
+
const resultado = marcarAccionado(baseDir, { id, por });
|
|
30
|
+
|
|
31
|
+
if (resultado.marcados === 0) {
|
|
32
|
+
console.error(`[nudge-accionar] nudge "${id}" no encontrado o ya accionado`);
|
|
33
|
+
} else {
|
|
34
|
+
console.log(`[nudge-accionar] nudge "${id}" marcado como accionado por "${por}"`);
|
|
35
|
+
}
|
|
36
|
+
return resultado;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = nudgeAccionar;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrapper CLI `swl-ses run-eval` — ejecuta un eval del framework de validación
|
|
5
|
+
* de outputs estructurados (scripts/run-eval.js). Reemplaza el
|
|
6
|
+
* `node scripts/run-eval.js` relativo al proyecto que eval-framework
|
|
7
|
+
* instruía — roto downstream (ver docs/invocacion-cli-cross-scope.md).
|
|
8
|
+
*
|
|
9
|
+
* Uso:
|
|
10
|
+
* swl-ses run-eval <ruta-eval.json>
|
|
11
|
+
* swl-ses run-eval --rebuild-aggregate
|
|
12
|
+
*
|
|
13
|
+
* ADVERTENCIA (nemesis iter-1 S3-6): el main() envuelto llama process.exit()
|
|
14
|
+
* en todas sus ramas — este wrapper TERMINA el proceso host. Correcto vía
|
|
15
|
+
* `bin/swl-ses.js` (proceso fresco por invocación); NO invocar in-process
|
|
16
|
+
* (batch runners, tests que compongan varios wrappers) o matará el proceso.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { main } = require('../run-eval.js');
|
|
20
|
+
|
|
21
|
+
function runEval(opciones = {}) {
|
|
22
|
+
const args = [];
|
|
23
|
+
if (opciones['rebuild-aggregate'] || opciones.rebuild_aggregate) {
|
|
24
|
+
args.push('--rebuild-aggregate');
|
|
25
|
+
} else if (opciones._args && opciones._args[0]) {
|
|
26
|
+
args.push(String(opciones._args[0]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const argvOriginal = process.argv;
|
|
30
|
+
process.argv = ['node', 'run-eval.js', ...args];
|
|
31
|
+
try {
|
|
32
|
+
main();
|
|
33
|
+
} finally {
|
|
34
|
+
process.argv = argvOriginal;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = runEval;
|
|
@@ -11,10 +11,17 @@
|
|
|
11
11
|
* --list → listar evals del skill
|
|
12
12
|
* --json → output JSON
|
|
13
13
|
* --record-baseline → registrar score baseline
|
|
14
|
-
* --record-after → registrar score post-cambio
|
|
15
|
-
*
|
|
14
|
+
* --record-after → registrar score post-cambio (el score se LEE del
|
|
15
|
+
* artefacto de evaluación con procedencia — ver abajo)
|
|
16
|
+
* --score=<N> → score numérico (baseline/revert; en record-after
|
|
17
|
+
* solo como verificación de coincidencia)
|
|
16
18
|
* --hypothesis=<texto> → hipótesis de la mutación
|
|
17
19
|
* --invariantes-ok → marcar invariantes como OK
|
|
20
|
+
* --emitir-evaluacion → escribir artefacto de procedencia (lo invoca
|
|
21
|
+
* /swl:evaluar-skill; exige --score y --producido-por)
|
|
22
|
+
* --producido-por=<a> → actor que corrió la evaluación (≠ target)
|
|
23
|
+
* --badge=<b> → badge asignado (opcional, acompaña emitir)
|
|
24
|
+
* --evaluacion=<path> → override del path del artefacto en record-after
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
27
|
const path = require('path');
|
|
@@ -32,6 +39,10 @@ function runSkillEvals(opciones) {
|
|
|
32
39
|
if (opciones && opciones.score !== undefined) args.push(`--score=${opciones.score}`);
|
|
33
40
|
if (opciones && opciones.hypothesis) args.push(`--hypothesis=${opciones.hypothesis}`);
|
|
34
41
|
if (opciones && opciones['invariantes-ok']) args.push('--invariantes-ok');
|
|
42
|
+
if (opciones && opciones['emitir-evaluacion']) args.push('--emitir-evaluacion');
|
|
43
|
+
if (opciones && opciones['producido-por']) args.push(`--producido-por=${opciones['producido-por']}`);
|
|
44
|
+
if (opciones && opciones.badge) args.push(`--badge=${opciones.badge}`);
|
|
45
|
+
if (opciones && opciones.evaluacion) args.push(`--evaluacion=${opciones.evaluacion}`);
|
|
35
46
|
|
|
36
47
|
const argvOriginal = process.argv;
|
|
37
48
|
process.argv = ['node', 'run-skill-evals.js', ...args];
|
|
@@ -102,22 +102,23 @@ function parsearTablaResumen(contenido, fases) {
|
|
|
102
102
|
|
|
103
103
|
const celdas = trim.split('|').map(c => c.trim()).filter(c => c !== '');
|
|
104
104
|
|
|
105
|
-
// Detectar header
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
// Detectar header — en CADA línea de tabla, no solo en la primera del
|
|
106
|
+
// archivo: el HOJA-RUTA real acumula varias tablas de fase (una por
|
|
107
|
+
// iniciativa) y fijar los índices de la primera ignoraba las demás en
|
|
108
|
+
// silencio (--check daba verde con fases ausentes del JSON).
|
|
109
|
+
const lowered = celdas.map(c => c.toLowerCase());
|
|
110
|
+
const fa = lowered.findIndex(c => c === 'fase' || c === 'phase' || c === '#');
|
|
111
|
+
const no = lowered.findIndex(c => c === 'nombre' || c === 'name');
|
|
112
|
+
const es = lowered.findIndex(c => c === 'estado' || c === 'status');
|
|
113
|
+
if (fa !== -1 && no !== -1 && es !== -1) {
|
|
114
|
+
idxFase = fa;
|
|
115
|
+
idxNombre = no;
|
|
116
|
+
idxObjetivo = lowered.findIndex(c => c === 'objetivo' || c === 'objective');
|
|
117
|
+
idxEstado = es;
|
|
118
|
+
enTabla = true;
|
|
119
119
|
continue;
|
|
120
120
|
}
|
|
121
|
+
if (idxFase === -1) continue;
|
|
121
122
|
|
|
122
123
|
// Línea separadora (|---|---|)
|
|
123
124
|
if (celdas.every(c => /^-+$/.test(c.replace(/:/g, '')))) continue;
|
package/scripts/desinstalar.js
CHANGED
|
@@ -140,6 +140,17 @@ function uninstall(opciones) {
|
|
|
140
140
|
` - ${archivo.destino}`
|
|
141
141
|
);
|
|
142
142
|
eliminados++;
|
|
143
|
+
// Subdirectorios creados por el instalador (hooks/lib/, rules/<lang>/)
|
|
144
|
+
// quedan vacíos tras borrar sus archivos: rmdir no-recursivo — solo
|
|
145
|
+
// cae si el dir quedó realmente vacío; cualquier contenido del usuario
|
|
146
|
+
// lo preserva (ENOTEMPTY ignorado). Acotado a rutas.base: un
|
|
147
|
+
// install-state manipulado no puede dirigir el rmdir fuera del árbol
|
|
148
|
+
// gestionado (endurecimiento, revisor-seguridad 2026-07-10).
|
|
149
|
+
try {
|
|
150
|
+
const padre = path.resolve(path.dirname(archivo.destino));
|
|
151
|
+
const baseGestionada = path.resolve(rutas.base);
|
|
152
|
+
if (padre.startsWith(baseGestionada + path.sep)) fs.rmdirSync(padre);
|
|
153
|
+
} catch (_) { /* no vacío o raíz gestionada */ }
|
|
143
154
|
} catch (err) {
|
|
144
155
|
_emitir(
|
|
145
156
|
{ tipo: 'error', archivo: archivo.destino, mensaje: err.message },
|
package/scripts/doctor.js
CHANGED
|
@@ -31,7 +31,13 @@ async function obtenerVersionRemota() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function doctor(opciones = {}) {
|
|
34
|
-
|
|
34
|
+
// Reparar exige intención EXPLÍCITA (--force). El default previo
|
|
35
|
+
// (`|| !ES_TTY`) auto-reparaba en headless: los reparadores invocan al
|
|
36
|
+
// INSTALADOR contra el cwd — en un checkout del repo eso materializaba
|
|
37
|
+
// copias dentro del fuente, reescribía settings.json y disparaba
|
|
38
|
+
// `git rm --cached` masivo vía limpiarTracked (incidente 2026-07-10,
|
|
39
|
+
// tests/CI). Sin TTY y sin --force: doctor solo DIAGNOSTICA.
|
|
40
|
+
const force = !!opciones.force;
|
|
35
41
|
const cwd = process.cwd();
|
|
36
42
|
|
|
37
43
|
let errores = 0;
|
|
@@ -337,14 +343,18 @@ async function doctor(opciones = {}) {
|
|
|
337
343
|
});
|
|
338
344
|
}
|
|
339
345
|
if (resultado.incorrectos.length > 0) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
// NO reparar en la fase de diagnóstico: repararHooks muta
|
|
347
|
+
// settings.json. Debe pasar por el gate --force como todo lo
|
|
348
|
+
// demás (incidente 2026-07-10 — reparación no consentida).
|
|
349
|
+
console.log(formatearAdvertencia(`Hooks ${runtime.nombre}`, `${resultado.incorrectos.length} hooks con problemas`));
|
|
350
|
+
advertencias++;
|
|
351
|
+
reparables.push({
|
|
352
|
+
descripcion: `Reparar ${resultado.incorrectos.length} hooks con formato incorrecto en settings.json`,
|
|
353
|
+
reparar: () => {
|
|
354
|
+
const reparacion = repararHooks(settingsPath);
|
|
355
|
+
return `${reparacion.reparados} hooks reparados en settings.json`;
|
|
356
|
+
},
|
|
357
|
+
});
|
|
348
358
|
}
|
|
349
359
|
}
|
|
350
360
|
}
|
|
@@ -683,10 +693,14 @@ async function doctor(opciones = {}) {
|
|
|
683
693
|
console.log('');
|
|
684
694
|
|
|
685
695
|
let proceder = force;
|
|
686
|
-
if (!proceder) {
|
|
696
|
+
if (!proceder && ES_TTY) {
|
|
687
697
|
proceder = await preguntarSiNo('¿Reparar automáticamente?');
|
|
688
698
|
console.log('');
|
|
689
699
|
}
|
|
700
|
+
if (!proceder && !ES_TTY) {
|
|
701
|
+
console.log(` ${color.dim('Modo no interactivo: reparación omitida. Ejecuta `swl-ses doctor --force` para reparar.')}`);
|
|
702
|
+
console.log('');
|
|
703
|
+
}
|
|
690
704
|
|
|
691
705
|
if (proceder) {
|
|
692
706
|
let reparados = 0;
|
package/scripts/instalador.js
CHANGED
|
@@ -21,7 +21,7 @@ const { preguntarSiNo, ES_TTY } = require('./lib/ui');
|
|
|
21
21
|
const { resolverPerfil, generarPlan } = require('./lib/manifiestos');
|
|
22
22
|
const { crearEstado, registrarArchivo, registrarComponente, guardarEstado, cargarEstado } = require('./lib/estado');
|
|
23
23
|
const { validarRuta, esArchivoSensible, filtrarSecretos } = require('./lib/seguridad');
|
|
24
|
-
const { registrarHooks, registrarOptimizaciones, rutaSettings, cargarHooksConfig } = require('./lib/hooks-settings');
|
|
24
|
+
const { registrarHooks, registrarOptimizaciones, rutaSettings, cargarHooksConfig, hooksSWLRegistrados } = require('./lib/hooks-settings');
|
|
25
25
|
const { obtenerTransformador } = require('./lib/transformadores');
|
|
26
26
|
const {
|
|
27
27
|
esProtegido,
|
|
@@ -84,6 +84,27 @@ function nombreLegibleEvolucion(rutaAbs) {
|
|
|
84
84
|
* El comportamiento sin TUI no cambia.
|
|
85
85
|
*/
|
|
86
86
|
async function install(opciones) {
|
|
87
|
+
// Camino "solo hooks" (DT-HOOKS-SOLO-PROYECTO): registra en el settings.json
|
|
88
|
+
// del PROYECTO los hooks del scope GLOBAL ya instalado, sin copiar ningún
|
|
89
|
+
// componente — evita duplicar agentes/comandos en la UI de Claude Code
|
|
90
|
+
// (que lee ambos scopes) solo para encender la telemetría.
|
|
91
|
+
if (opciones['solo-hooks'] || opciones.solo_hooks) {
|
|
92
|
+
const { activarHooksProyecto } = require('./lib/activar-hooks-proyecto');
|
|
93
|
+
const r = activarHooksProyecto({ target: opciones.target || 'claude' });
|
|
94
|
+
if (!r.ok) {
|
|
95
|
+
console.error(`\n✘ --solo-hooks: ${r.error}`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return r;
|
|
98
|
+
}
|
|
99
|
+
console.log('\n✔ Hooks activados a nivel proyecto SIN copiar componentes:');
|
|
100
|
+
console.log(` settings: ${r.settingsPath}`);
|
|
101
|
+
console.log(` hooks de: ${r.hooksDirGlobal} (scope global — update global los refresca aquí también)`);
|
|
102
|
+
console.log(` registrados: ${r.registrados} | externos preservados: ${r.preservados}`);
|
|
103
|
+
console.log('\n La telemetría (.planning/evolution/nudges.jsonl, skill-metrics.json)');
|
|
104
|
+
console.log(' empieza a acumularse desde la siguiente sesión de Claude en este proyecto.');
|
|
105
|
+
return r;
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
// Leer manifest .swl-ses si existe — sus valores son defaults que el CLI puede sobreescribir
|
|
88
109
|
const manifest = leerManifest(process.cwd());
|
|
89
110
|
if (manifest && !opciones.target && !opciones.profile) {
|
|
@@ -143,6 +164,28 @@ async function install(opciones) {
|
|
|
143
164
|
|| perfilDeState
|
|
144
165
|
|| 'completo';
|
|
145
166
|
|
|
167
|
+
// DT-MODULOS-OPT-IN: preservar los módulos extra (--with) registrados por
|
|
168
|
+
// instalaciones previas en el install-state. Corre SIEMPRE (independiente
|
|
169
|
+
// de si el usuario pasó --profile): sin esto, cada update/reinstalación
|
|
170
|
+
// perdía en silencio los módulos opt-in (p. ej. reglas-personales).
|
|
171
|
+
let extrasDeState = [];
|
|
172
|
+
try {
|
|
173
|
+
const runtimeParaState = obtenerRuntime(target);
|
|
174
|
+
const dirDestino = esGlobal ? runtimeParaState.global : path.resolve(runtimeParaState.local);
|
|
175
|
+
const stateExistente = fs.existsSync(dirDestino) ? cargarEstado(dirDestino) : null;
|
|
176
|
+
if (stateExistente && Array.isArray(stateExistente.modulosExtra) && stateExistente.modulosExtra.length > 0) {
|
|
177
|
+
extrasDeState = stateExistente.modulosExtra;
|
|
178
|
+
console.log(`[install-state] Preservando módulos extra previos: ${extrasDeState.join(', ')}`);
|
|
179
|
+
console.log(`[install-state] Para quitarlos pasa --without <modulo> explícito.`);
|
|
180
|
+
}
|
|
181
|
+
} catch { /* state ilegible: seguir solo con --with del CLI */ }
|
|
182
|
+
|
|
183
|
+
// Unión de los extras del CLI (--with) y los preservados del state.
|
|
184
|
+
const withCombinado = [
|
|
185
|
+
...String(opciones.with || '').split(',').map(s => s.trim()).filter(Boolean),
|
|
186
|
+
...extrasDeState,
|
|
187
|
+
].filter((m, i, arr) => arr.indexOf(m) === i);
|
|
188
|
+
|
|
146
189
|
console.log(`swl-ses install v${VERSION}`);
|
|
147
190
|
console.log(`${'='.repeat(40)}`);
|
|
148
191
|
|
|
@@ -209,7 +252,7 @@ async function install(opciones) {
|
|
|
209
252
|
try {
|
|
210
253
|
resolucion = resolverPerfil(perfil, {
|
|
211
254
|
target,
|
|
212
|
-
with:
|
|
255
|
+
with: withCombinado.join(',') || undefined,
|
|
213
256
|
without: opciones.without,
|
|
214
257
|
});
|
|
215
258
|
} catch (err) {
|
|
@@ -554,6 +597,9 @@ async function install(opciones) {
|
|
|
554
597
|
// desde el cwd actual del doctor). Fix v1.4.3.
|
|
555
598
|
allLangs: allLangs === true,
|
|
556
599
|
stackInstalado: stackDetectado instanceof Set ? [...stackDetectado] : null,
|
|
600
|
+
// Solo los extras que el perfil realmente resolvió (existen y no fueron
|
|
601
|
+
// excluidos con --without) — DT-MODULOS-OPT-IN.
|
|
602
|
+
modulosExtra: withCombinado.filter(m => resolucion.modulos.includes(m)),
|
|
557
603
|
});
|
|
558
604
|
|
|
559
605
|
let instalados = 0;
|
|
@@ -706,6 +752,19 @@ async function install(opciones) {
|
|
|
706
752
|
registrarComponente(estado, modulo);
|
|
707
753
|
}
|
|
708
754
|
|
|
755
|
+
// Persistencia INTERMEDIA (DT-INSTALL-STATE-TRANSACCIONAL, nemesis sub1-S3):
|
|
756
|
+
// todos los archivos ya están en disco. Sin este save, una interrupción
|
|
757
|
+
// durante el registro de hooks (settings.json) o la generación de
|
|
758
|
+
// CLAUDE.md/AGENTS.md dejaría archivos copiados que el install-state NO
|
|
759
|
+
// registra → huérfanos que ni doctor ni uninstall ven. guardarEstado es
|
|
760
|
+
// atómico e idempotente; el save final (abajo) fija hooksRegistrados.
|
|
761
|
+
// Best-effort: un fallo aquí no aborta la instalación.
|
|
762
|
+
try {
|
|
763
|
+
guardarEstado(estado, rutas.base);
|
|
764
|
+
} catch (err) {
|
|
765
|
+
console.error(` ! Aviso: no se pudo persistir el estado intermedio: ${err.message}`);
|
|
766
|
+
}
|
|
767
|
+
|
|
709
768
|
// 7. Registrar hooks en settings.json del runtime
|
|
710
769
|
// Los hooks solo se registran a nivel PROYECTO (no global) para evitar
|
|
711
770
|
// ejecucion duplicada cuando Claude Code combina hooks de ambos scopes.
|
|
@@ -717,8 +776,15 @@ async function install(opciones) {
|
|
|
717
776
|
// No filtrar por tipo de módulo porque hooks como grafo-contexto.js
|
|
718
777
|
// pueden pertenecer a módulos con tipo distinto a "hooks".
|
|
719
778
|
const hooksConfig = cargarHooksConfig();
|
|
720
|
-
|
|
721
|
-
|
|
779
|
+
// Registrar SOLO los hooks realmente copiados (estado.archivosInstalados),
|
|
780
|
+
// no los del PLAN (resolucion.archivos) — un hook cuyo .js falló al copiar
|
|
781
|
+
// (throw, colisión, origen inexistente) no debe quedar en settings.json
|
|
782
|
+
// apuntando a un archivo ausente: el wrapper traga el MODULE_NOT_FOUND y
|
|
783
|
+
// el hook muere en silencio (DT-REGISTRAR-HOOKS-PLAN, nemesis sub1-S2).
|
|
784
|
+
// Los hooks nunca entran a la rama `evolucionado` (no son componentes
|
|
785
|
+
// evolucionables) → un hook exitoso siempre queda en archivosInstalados.
|
|
786
|
+
const hookFilesInstalados = (estado.archivosInstalados || [])
|
|
787
|
+
.filter(a => (a.origen || '').endsWith('.js') && hooksConfig[path.basename(a.origen)])
|
|
722
788
|
.map(a => path.basename(a.origen));
|
|
723
789
|
|
|
724
790
|
if (hookFilesInstalados.length > 0) {
|
|
@@ -727,12 +793,22 @@ async function install(opciones) {
|
|
|
727
793
|
console.log(' Los hooks se activan al instalar a nivel proyecto con: npx @saulwade/swl-ses@latest install --target claude');
|
|
728
794
|
} else {
|
|
729
795
|
const settingsPath = rutaSettings(runtime, rutas.base);
|
|
796
|
+
// UNIÓN con los hooks SWL YA registrados cuyo .js sigue en disco:
|
|
797
|
+
// registrarHooks reconstruye la sección SWL de settings.json, así que
|
|
798
|
+
// pasar solo lo recién copiado DESREGISTRARÍA los hooks pre-existentes
|
|
799
|
+
// que colisionaron sin --force pero siguen funcionando (regresión
|
|
800
|
+
// detectada por la re-auditoría nemesis iter-2). El plan-base ya no se
|
|
801
|
+
// usa: solo entra lo copiado ∪ lo pre-registrado-con-archivo-en-disco.
|
|
802
|
+
const preRegistrados = hooksSWLRegistrados(settingsPath).filter(
|
|
803
|
+
nombre => cargarHooksConfig()[nombre] && fs.existsSync(path.join(rutas.hooks, nombre))
|
|
804
|
+
);
|
|
805
|
+
const hookFilesFinal = [...new Set([...hookFilesInstalados, ...preRegistrados])];
|
|
730
806
|
try {
|
|
731
807
|
const resultado = registrarHooks({
|
|
732
808
|
settingsPath,
|
|
733
809
|
hooksDir: rutas.hooks,
|
|
734
810
|
esGlobal: false,
|
|
735
|
-
hookFiles:
|
|
811
|
+
hookFiles: hookFilesFinal,
|
|
736
812
|
});
|
|
737
813
|
hooksRegistrados = resultado.registrados;
|
|
738
814
|
hooksPreservados = resultado.preservados;
|
|
@@ -1151,12 +1227,35 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
|
|
|
1151
1227
|
} else {
|
|
1152
1228
|
destino = path.join(dirDestino, nombreArchivo);
|
|
1153
1229
|
}
|
|
1230
|
+
} else if (
|
|
1231
|
+
archivo.tipo === 'hooks' &&
|
|
1232
|
+
archivo.rutaRelativa &&
|
|
1233
|
+
(archivo.rutaRelativa.startsWith('hooks/lib/') || archivo.rutaRelativa.startsWith('scripts/lib/'))
|
|
1234
|
+
) {
|
|
1235
|
+
// Contrato de layout de hooks: los hooks requieren sus libs como
|
|
1236
|
+
// './lib/X' (y check-update/session-briefing usan ese fallback
|
|
1237
|
+
// explícito). El aplanado total dejaba TODOS esos require() muertos en
|
|
1238
|
+
// instalaciones frescas — MODULE_NOT_FOUND tragado por el wrapper de
|
|
1239
|
+
// settings; los installs de campo sobrevivían por el lib/ residual de
|
|
1240
|
+
// versiones previas. Preservar el subárbol lib/ (incluye subdirs como
|
|
1241
|
+
// lib/ciclo-autonomo/) restaura el contrato. scripts/ NO-lib sigue
|
|
1242
|
+
// aplanado en la raíz de hooks (status quo: rotar-audit-logs, etc.).
|
|
1243
|
+
const resto = archivo.rutaRelativa.replace(/^(?:hooks|scripts)\/lib\//, '');
|
|
1244
|
+
const partes = resto.split('/');
|
|
1245
|
+
const subdirLib = path.join(dirDestino, 'lib', ...partes.slice(0, -1));
|
|
1246
|
+
if (!fs.existsSync(subdirLib)) {
|
|
1247
|
+
fs.mkdirSync(subdirLib, { recursive: true });
|
|
1248
|
+
}
|
|
1249
|
+
destino = path.join(subdirLib, partes[partes.length - 1]);
|
|
1154
1250
|
} else {
|
|
1155
1251
|
destino = path.join(dirDestino, nombreArchivo);
|
|
1156
1252
|
}
|
|
1157
1253
|
|
|
1158
|
-
// Seguridad: validar
|
|
1159
|
-
|
|
1254
|
+
// Seguridad: validar el DESTINO calculado completo, no solo el basename —
|
|
1255
|
+
// cubre también las ramas con subdirectorios (reglas/lenguajes, hooks/lib)
|
|
1256
|
+
// contra un rutaRelativa con traversal (endurecimiento, revisor-seguridad
|
|
1257
|
+
// 2026-07-10; el manifiesto es artefacto confiable, defensa en profundidad).
|
|
1258
|
+
validarRuta(dirDestino, path.relative(dirDestino, destino));
|
|
1160
1259
|
|
|
1161
1260
|
// Verificar archivo sensible
|
|
1162
1261
|
if (esArchivoSensible(nombreArchivo)) {
|