@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.
Files changed (183) hide show
  1. package/CLAUDE.md +194 -192
  2. package/README.md +600 -600
  3. package/agentes/auto-evolucion-swl.md +27 -3
  4. package/bin/swl-ses.js +32 -6
  5. package/comandos/swl/actualizar.md +174 -174
  6. package/comandos/swl/adoptar-proyecto.md +265 -265
  7. package/comandos/swl/aprender.md +836 -823
  8. package/comandos/swl/aprobar-plan.md +146 -146
  9. package/comandos/swl/auditar-deps.md +134 -134
  10. package/comandos/swl/autoresearch.md +264 -264
  11. package/comandos/swl/ayuda.md +224 -224
  12. package/comandos/swl/brainstorm.md +51 -51
  13. package/comandos/swl/briefing.md +119 -119
  14. package/comandos/swl/checkpoint.md +325 -325
  15. package/comandos/swl/claudemd.md +234 -234
  16. package/comandos/swl/compactar.md +310 -310
  17. package/comandos/swl/configurar-ci.md +235 -235
  18. package/comandos/swl/contexto.md +110 -110
  19. package/comandos/swl/contribuir.md +233 -233
  20. package/comandos/swl/crear-skill.md +292 -292
  21. package/comandos/swl/cron.md +194 -194
  22. package/comandos/swl/deuda-codigo.md +97 -97
  23. package/comandos/swl/discutir-fase.md +169 -169
  24. package/comandos/swl/ejecutar-fase.md +233 -233
  25. package/comandos/swl/evaluar-skill.md +520 -505
  26. package/comandos/swl/evolucion-continua.md +73 -0
  27. package/comandos/swl/evolucionar.md +267 -254
  28. package/comandos/swl/exportar-vault.md +583 -583
  29. package/comandos/swl/fix.md +118 -118
  30. package/comandos/swl/gateway.md +158 -158
  31. package/comandos/swl/inbox.md +116 -116
  32. package/comandos/swl/instalar.md +220 -220
  33. package/comandos/swl/instintos.md +86 -86
  34. package/comandos/swl/mapear-codebase.md +312 -312
  35. package/comandos/swl/mcp-status.md +175 -175
  36. package/comandos/swl/modelo.md +100 -100
  37. package/comandos/swl/nemesis.md +433 -433
  38. package/comandos/swl/notificaciones.md +299 -299
  39. package/comandos/swl/nuevo-proyecto.md +251 -251
  40. package/comandos/swl/planear-fase.md +263 -263
  41. package/comandos/swl/plugins.md +256 -256
  42. package/comandos/swl/predecir.md +169 -169
  43. package/comandos/swl/reflect-skills.md +125 -125
  44. package/comandos/swl/release.md +450 -450
  45. package/comandos/swl/revisar-impacto.md +201 -201
  46. package/comandos/swl/revisar.md +330 -330
  47. package/comandos/swl/seguridad.md +189 -189
  48. package/comandos/swl/sesiones.md +200 -200
  49. package/comandos/swl/skill-search.md +113 -113
  50. package/comandos/swl/status.md +343 -343
  51. package/comandos/swl/verificar.md +817 -817
  52. package/comandos/swl/wiki.md +620 -620
  53. package/gateway/cron/jobs.example.json +12 -0
  54. package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -276
  55. package/habilidades/autoresearch/SKILL.md +3 -2
  56. package/habilidades/benchmark-memoria/SKILL.md +7 -7
  57. package/habilidades/changelog-generator/SKILL.md +174 -174
  58. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  59. package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
  60. package/habilidades/context-builder/SKILL.md +4 -0
  61. package/habilidades/doubt-driven-review/SKILL.md +207 -191
  62. package/habilidades/drift-detection/SKILL.md +6 -1
  63. package/habilidades/ejecutar-fase/SKILL.md +6 -6
  64. package/habilidades/eval-framework/SKILL.md +8 -3
  65. package/habilidades/harness-claude-code/SKILL.md +314 -308
  66. package/habilidades/infra-github-actions/SKILL.md +4 -3
  67. package/habilidades/instalar-sistema/SKILL.md +227 -223
  68. package/habilidades/memoria-busqueda/SKILL.md +31 -39
  69. package/habilidades/planear-fase/SKILL.md +358 -350
  70. package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
  71. package/habilidades/release-semver/SKILL.md +4 -2
  72. package/habilidades/swl-claudemd/SKILL.md +6 -7
  73. package/habilidades/swl-dashboard/SKILL.md +11 -43
  74. package/habilidades/tdd-workflow/SKILL.md +749 -744
  75. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  76. package/hooks/agente-lifecycle.js +2 -1
  77. package/hooks/aiisms-detector.js +13 -4
  78. package/hooks/audit-trail.js +2 -1
  79. package/hooks/auto-consolidacion.js +2 -1
  80. package/hooks/captura-acciones-post.js +2 -1
  81. package/hooks/captura-acciones-session.js +2 -1
  82. package/hooks/captura-feedback-usuario.js +3 -2
  83. package/hooks/claudemd-bloat-detector.js +12 -3
  84. package/hooks/claudemd-duplicacion-detector.js +13 -3
  85. package/hooks/contexto-iteracion.js +2 -1
  86. package/hooks/degradacion-instintos.js +2 -1
  87. package/hooks/extraccion-aprendizajes.js +109 -15
  88. package/hooks/grafo-contexto.js +2 -1
  89. package/hooks/guardrail-modelo.js +2 -1
  90. package/hooks/inbox-aviso.js +2 -1
  91. package/hooks/inyeccion-contexto.js +2 -1
  92. package/hooks/lib/agent-matcher.js +2 -1
  93. package/hooks/lib/agent-routing.js +2 -1
  94. package/hooks/lib/autonomia.js +5 -3
  95. package/hooks/lib/captura-acciones.js +2 -1
  96. package/hooks/lib/consolidation-lock.js +21 -10
  97. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  98. package/hooks/lib/etapa-metricas.js +2 -1
  99. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  100. package/hooks/lib/evolution-tracker.js +2 -1
  101. package/hooks/lib/gateway-notify.js +193 -179
  102. package/hooks/lib/loop-telemetry.js +5 -4
  103. package/hooks/lib/mcp-health.js +2 -1
  104. package/hooks/lib/memory-search.js +4 -0
  105. package/hooks/lib/merkle-audit.js +58 -6
  106. package/hooks/lib/nudge-tracker.js +2 -1
  107. package/hooks/lib/otlp-exporter.js +2 -1
  108. package/hooks/lib/propose-step.js +3 -2
  109. package/hooks/lib/raiz-proyecto.js +102 -0
  110. package/hooks/lib/run-log.js +2 -1
  111. package/hooks/lib/singleton-guard.js +218 -27
  112. package/hooks/lib/telegram-cliente.js +17 -8
  113. package/hooks/preservar-estado-pre-compact.js +2 -1
  114. package/hooks/proteccion-rutas.js +59 -3
  115. package/hooks/registro-turnos.js +2 -1
  116. package/hooks/resumen-sesion.js +2 -1
  117. package/hooks/risk-scoring.js +2 -1
  118. package/hooks/rotar-audit-auto.js +46 -20
  119. package/hooks/session-briefing.js +127 -1
  120. package/hooks/spec-gate.js +2 -1
  121. package/hooks/sugerir-contribuir.js +6 -3
  122. package/hooks/sugerir-regenerar-inventario.js +3 -2
  123. package/hooks/tdd-gate.js +2 -1
  124. package/hooks/telemetria-agentes.js +2 -1
  125. package/hooks/telemetria-skill-routing.js +2 -1
  126. package/hooks/tracking-costos.js +4 -3
  127. package/hooks/validar-formato-post-subagente.js +2 -1
  128. package/hooks/validar-intent-spec.js +2 -1
  129. package/hooks/validar-memoria-hook.js +13 -3
  130. package/hooks/validar-planning-paths.js +2 -1
  131. package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
  132. package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
  133. package/instintos/perfil-usuario.yaml +506 -3
  134. package/instintos/proyecto.yaml +78 -0
  135. package/llms.txt +2 -2
  136. package/manifiestos/canonical-hashes.json +664 -2
  137. package/manifiestos/modulos.json +19 -14
  138. package/manifiestos/planning-paths.json +1 -0
  139. package/manifiestos/skills-lock.json +53 -53
  140. package/package.json +2 -3
  141. package/plugin.json +2 -2
  142. package/scripts/actualizar.js +3 -0
  143. package/scripts/auditar-clases-conocidas.js +32 -4
  144. package/scripts/benchmark-memoria.js +1 -0
  145. package/scripts/cli/autonomia.js +23 -0
  146. package/scripts/cli/benchmark-memoria.js +37 -0
  147. package/scripts/cli/ciclo-autonomo.js +73 -0
  148. package/scripts/cli/ciclo-fase-b.js +102 -0
  149. package/scripts/cli/guardrail-metrics.js +39 -0
  150. package/scripts/cli/loop-telemetry.js +4 -2
  151. package/scripts/cli/memoria-search.js +69 -0
  152. package/scripts/cli/nudge-accionar.js +39 -0
  153. package/scripts/cli/run-eval.js +38 -0
  154. package/scripts/cli/run-skill-evals.js +13 -2
  155. package/scripts/derivar-feature-list.js +15 -14
  156. package/scripts/desinstalar.js +11 -0
  157. package/scripts/doctor.js +24 -10
  158. package/scripts/instalador.js +106 -7
  159. package/scripts/lib/activar-hooks-proyecto.js +116 -0
  160. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  161. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  162. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  163. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  164. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  165. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  166. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  167. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  168. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  169. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  170. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  171. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  172. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  173. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  174. package/scripts/lib/estado.js +9 -0
  175. package/scripts/lib/evidencia-valor.js +1 -1
  176. package/scripts/lib/gitignore-manifest.js +8 -1
  177. package/scripts/lib/hooks-settings.js +45 -0
  178. package/scripts/rotar-audit-logs.js +48 -2
  179. package/scripts/run-eval.js +1 -0
  180. package/scripts/run-skill-evals.js +287 -8
  181. package/scripts/smoke-test.js +16 -8
  182. package/scripts/tui/pantallas/install-wizard.js +403 -347
  183. 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
- const base = path.join(process.cwd(), lt.DIR_LOOPS);
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
- * --score=<N> → score numérico (acompaña --record-*)
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
- if (idxFase === -1) {
107
- const lowered = celdas.map(c => c.toLowerCase());
108
- const fa = lowered.findIndex(c => c === 'fase' || c === 'phase' || c === '#');
109
- const no = lowered.findIndex(c => c === 'nombre' || c === 'name');
110
- const ob = lowered.findIndex(c => c === 'objetivo' || c === 'objective');
111
- const es = lowered.findIndex(c => c === 'estado' || c === 'status');
112
- if (fa !== -1 && no !== -1 && es !== -1) {
113
- idxFase = fa;
114
- idxNombre = no;
115
- idxObjetivo = ob;
116
- idxEstado = es;
117
- enTabla = true;
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;
@@ -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
- const force = opciones.force || !ES_TTY;
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
- const reparacion = repararHooks(settingsPath);
341
- if (reparacion.reparados > 0) {
342
- console.log(formatearPaso(`Hooks ${runtime.nombre}`, `${reparacion.reparados} hooks reparados automáticamente`));
343
- ok++;
344
- } else {
345
- console.log(formatearAdvertencia(`Hooks ${runtime.nombre}`, `${resultado.incorrectos.length} hooks con problemas`));
346
- advertencias++;
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;
@@ -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: opciones.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
- const hookFilesInstalados = resolucion.archivos
721
- .filter(a => (a.rutaRelativa || a.origen).endsWith('.js') && hooksConfig[path.basename(a.origen)])
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: hookFilesInstalados,
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 ruta
1159
- validarRuta(dirDestino, nombreArchivo);
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)) {