@saulwade/swl-ses 2.5.3 → 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 (181) hide show
  1. package/CLAUDE.md +192 -192
  2. package/README.md +600 -600
  3. package/agentes/auto-evolucion-swl.md +27 -3
  4. package/bin/swl-ses.js +32 -7
  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 +312 -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/swl-claudemd/SKILL.md +6 -7
  72. package/habilidades/swl-dashboard/SKILL.md +11 -43
  73. package/habilidades/tdd-workflow/SKILL.md +749 -744
  74. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  75. package/hooks/agente-lifecycle.js +2 -1
  76. package/hooks/aiisms-detector.js +13 -4
  77. package/hooks/audit-trail.js +2 -1
  78. package/hooks/auto-consolidacion.js +2 -1
  79. package/hooks/captura-acciones-post.js +2 -1
  80. package/hooks/captura-acciones-session.js +2 -1
  81. package/hooks/captura-feedback-usuario.js +3 -2
  82. package/hooks/claudemd-bloat-detector.js +12 -3
  83. package/hooks/claudemd-duplicacion-detector.js +13 -3
  84. package/hooks/contexto-iteracion.js +2 -1
  85. package/hooks/degradacion-instintos.js +2 -1
  86. package/hooks/extraccion-aprendizajes.js +109 -15
  87. package/hooks/grafo-contexto.js +2 -1
  88. package/hooks/guardrail-modelo.js +2 -1
  89. package/hooks/inbox-aviso.js +2 -1
  90. package/hooks/inyeccion-contexto.js +2 -1
  91. package/hooks/lib/agent-matcher.js +2 -1
  92. package/hooks/lib/agent-routing.js +2 -1
  93. package/hooks/lib/autonomia.js +5 -3
  94. package/hooks/lib/captura-acciones.js +2 -1
  95. package/hooks/lib/consolidation-lock.js +21 -10
  96. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  97. package/hooks/lib/etapa-metricas.js +2 -1
  98. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  99. package/hooks/lib/evolution-tracker.js +2 -1
  100. package/hooks/lib/gateway-notify.js +193 -179
  101. package/hooks/lib/loop-telemetry.js +5 -4
  102. package/hooks/lib/mcp-health.js +2 -1
  103. package/hooks/lib/memory-search.js +4 -0
  104. package/hooks/lib/merkle-audit.js +58 -6
  105. package/hooks/lib/nudge-tracker.js +2 -1
  106. package/hooks/lib/otlp-exporter.js +2 -1
  107. package/hooks/lib/propose-step.js +3 -2
  108. package/hooks/lib/raiz-proyecto.js +102 -0
  109. package/hooks/lib/run-log.js +2 -1
  110. package/hooks/lib/singleton-guard.js +218 -27
  111. package/hooks/lib/telegram-cliente.js +17 -8
  112. package/hooks/preservar-estado-pre-compact.js +2 -1
  113. package/hooks/proteccion-rutas.js +59 -3
  114. package/hooks/registro-turnos.js +2 -1
  115. package/hooks/resumen-sesion.js +2 -1
  116. package/hooks/risk-scoring.js +2 -1
  117. package/hooks/rotar-audit-auto.js +46 -20
  118. package/hooks/session-briefing.js +127 -1
  119. package/hooks/spec-gate.js +2 -1
  120. package/hooks/sugerir-contribuir.js +6 -3
  121. package/hooks/sugerir-regenerar-inventario.js +3 -2
  122. package/hooks/tdd-gate.js +2 -1
  123. package/hooks/telemetria-agentes.js +2 -1
  124. package/hooks/telemetria-skill-routing.js +2 -1
  125. package/hooks/tracking-costos.js +4 -3
  126. package/hooks/validar-formato-post-subagente.js +2 -1
  127. package/hooks/validar-intent-spec.js +2 -1
  128. package/hooks/validar-memoria-hook.js +13 -3
  129. package/hooks/validar-planning-paths.js +2 -1
  130. package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
  131. package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
  132. package/instintos/perfil-usuario.yaml +506 -3
  133. package/instintos/proyecto.yaml +78 -0
  134. package/llms.txt +2 -2
  135. package/manifiestos/canonical-hashes.json +335 -3
  136. package/manifiestos/modulos.json +19 -14
  137. package/manifiestos/planning-paths.json +1 -0
  138. package/manifiestos/skills-lock.json +50 -50
  139. package/package.json +2 -3
  140. package/plugin.json +2 -2
  141. package/scripts/actualizar.js +3 -0
  142. package/scripts/auditar-clases-conocidas.js +32 -4
  143. package/scripts/benchmark-memoria.js +1 -0
  144. package/scripts/cli/autonomia.js +23 -0
  145. package/scripts/cli/benchmark-memoria.js +37 -0
  146. package/scripts/cli/ciclo-autonomo.js +73 -0
  147. package/scripts/cli/ciclo-fase-b.js +102 -0
  148. package/scripts/cli/guardrail-metrics.js +39 -0
  149. package/scripts/cli/loop-telemetry.js +4 -2
  150. package/scripts/cli/memoria-search.js +69 -0
  151. package/scripts/cli/nudge-accionar.js +39 -0
  152. package/scripts/cli/run-eval.js +38 -0
  153. package/scripts/cli/run-skill-evals.js +13 -2
  154. package/scripts/derivar-feature-list.js +15 -14
  155. package/scripts/desinstalar.js +11 -0
  156. package/scripts/doctor.js +24 -10
  157. package/scripts/instalador.js +85 -7
  158. package/scripts/lib/activar-hooks-proyecto.js +12 -0
  159. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  160. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  161. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  162. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  163. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  164. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  165. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  166. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  167. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  168. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  169. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  170. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  171. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  172. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  173. package/scripts/lib/estado.js +9 -0
  174. package/scripts/lib/gitignore-manifest.js +8 -1
  175. package/scripts/lib/hooks-settings.js +45 -0
  176. package/scripts/rotar-audit-logs.js +48 -2
  177. package/scripts/run-eval.js +1 -0
  178. package/scripts/run-skill-evals.js +287 -8
  179. package/scripts/smoke-test.js +16 -8
  180. package/scripts/tui/pantallas/install-wizard.js +403 -347
  181. package/scripts/validar.js +40 -1
@@ -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,
@@ -164,6 +164,28 @@ async function install(opciones) {
164
164
  || perfilDeState
165
165
  || 'completo';
166
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
+
167
189
  console.log(`swl-ses install v${VERSION}`);
168
190
  console.log(`${'='.repeat(40)}`);
169
191
 
@@ -230,7 +252,7 @@ async function install(opciones) {
230
252
  try {
231
253
  resolucion = resolverPerfil(perfil, {
232
254
  target,
233
- with: opciones.with,
255
+ with: withCombinado.join(',') || undefined,
234
256
  without: opciones.without,
235
257
  });
236
258
  } catch (err) {
@@ -575,6 +597,9 @@ async function install(opciones) {
575
597
  // desde el cwd actual del doctor). Fix v1.4.3.
576
598
  allLangs: allLangs === true,
577
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)),
578
603
  });
579
604
 
580
605
  let instalados = 0;
@@ -727,6 +752,19 @@ async function install(opciones) {
727
752
  registrarComponente(estado, modulo);
728
753
  }
729
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
+
730
768
  // 7. Registrar hooks en settings.json del runtime
731
769
  // Los hooks solo se registran a nivel PROYECTO (no global) para evitar
732
770
  // ejecucion duplicada cuando Claude Code combina hooks de ambos scopes.
@@ -738,8 +776,15 @@ async function install(opciones) {
738
776
  // No filtrar por tipo de módulo porque hooks como grafo-contexto.js
739
777
  // pueden pertenecer a módulos con tipo distinto a "hooks".
740
778
  const hooksConfig = cargarHooksConfig();
741
- const hookFilesInstalados = resolucion.archivos
742
- .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)])
743
788
  .map(a => path.basename(a.origen));
744
789
 
745
790
  if (hookFilesInstalados.length > 0) {
@@ -748,12 +793,22 @@ async function install(opciones) {
748
793
  console.log(' Los hooks se activan al instalar a nivel proyecto con: npx @saulwade/swl-ses@latest install --target claude');
749
794
  } else {
750
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])];
751
806
  try {
752
807
  const resultado = registrarHooks({
753
808
  settingsPath,
754
809
  hooksDir: rutas.hooks,
755
810
  esGlobal: false,
756
- hookFiles: hookFilesInstalados,
811
+ hookFiles: hookFilesFinal,
757
812
  });
758
813
  hooksRegistrados = resultado.registrados;
759
814
  hooksPreservados = resultado.preservados;
@@ -1172,12 +1227,35 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1172
1227
  } else {
1173
1228
  destino = path.join(dirDestino, nombreArchivo);
1174
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]);
1175
1250
  } else {
1176
1251
  destino = path.join(dirDestino, nombreArchivo);
1177
1252
  }
1178
1253
 
1179
- // Seguridad: validar ruta
1180
- 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));
1181
1259
 
1182
1260
  // Verificar archivo sensible
1183
1261
  if (esArchivoSensible(nombreArchivo)) {
@@ -91,6 +91,17 @@ function activarHooksProyecto(opciones = {}) {
91
91
  optimizaciones = (opt && opt.registradas) || 0;
92
92
  } catch { /* opcional — no bloquea la activación */ }
93
93
 
94
+ // 4. Sembrar el .gitignore del proyecto: encender la telemetría hace que
95
+ // los hooks ESCRIBAN (.planning/METRICAS.md, audit*.jsonl, evolution/...)
96
+ // — sin la sección swl-ses esos archivos runtime aparecen como cambios
97
+ // pendientes en el editor del usuario (caso real sistema-verificacion-oic,
98
+ // 2026-07-10: 19 archivos sin trackear tras activar hooks).
99
+ let gitignorePath = null;
100
+ try {
101
+ const { actualizarGitignore } = deps.gitignoreManifest || require('./gitignore-manifest');
102
+ gitignorePath = actualizarGitignore(dirProyecto);
103
+ } catch { /* no bloquea la activación — se reporta null */ }
104
+
94
105
  return {
95
106
  ok: true,
96
107
  settingsPath,
@@ -98,6 +109,7 @@ function activarHooksProyecto(opciones = {}) {
98
109
  registrados: r.registrados,
99
110
  preservados: r.preservados,
100
111
  optimizaciones,
112
+ gitignorePath,
101
113
  };
102
114
  }
103
115
 
@@ -87,17 +87,107 @@ function auditarInvocacionesComandos(comandosDir) {
87
87
  return { ok: violaciones.length === 0, violaciones, escaneados };
88
88
  }
89
89
 
90
- module.exports = { auditarInvocacionesComandos, SELF_DEV };
90
+ // Skills que corren SOLO en el repo madre (host-only declarado en su propio
91
+ // frontmatter/cuerpo): sus `node scripts/...` son legítimos porque el CWD ES
92
+ // el repo (DT-PATHS-DESTINO, cierre 2026-07-10).
93
+ const SKILLS_SOLO_MADRE = new Set([
94
+ 'doc-sync',
95
+ 'release-semver',
96
+ 'validacion-ci-sistema',
97
+ ]);
98
+
99
+ // Lenguajes de fence que se auditan en skills: solo instrucciones ejecutables
100
+ // de shell. Los bloques js/javascript documentan APIs de módulos (referencia,
101
+ // no comandos a ejecutar downstream) y llevan su clarificador de layout.
102
+ const LENGUAJES_EJECUTABLES = new Set(['', 'bash', 'sh', 'shell']);
103
+
104
+ /**
105
+ * Igual que auditarInvocacionesComandos pero sobre habilidades/<skill>/SKILL.md.
106
+ * Solo audita fences ejecutables (bash/sh/shell/sin lenguaje).
107
+ * @param {string} [habilidadesDir]
108
+ * @returns {{ok:boolean, violaciones:Array<{comando:string,linea:number,texto:string,patron:string}>, escaneados:number}}
109
+ */
110
+ function auditarInvocacionesSkills(habilidadesDir) {
111
+ const dir = habilidadesDir || path.join(__dirname, '..', '..', 'habilidades');
112
+ const violaciones = [];
113
+ let escaneados = 0;
114
+
115
+ let skills;
116
+ try {
117
+ skills = fs.readdirSync(dir, { withFileTypes: true })
118
+ .filter((d) => d.isDirectory())
119
+ .map((d) => d.name);
120
+ } catch {
121
+ return { ok: true, violaciones, escaneados: 0 };
122
+ }
123
+
124
+ for (const skill of skills) {
125
+ if (SKILLS_SOLO_MADRE.has(skill)) continue;
126
+ const ruta = path.join(dir, skill, 'SKILL.md');
127
+ let contenido;
128
+ try {
129
+ contenido = fs.readFileSync(ruta, 'utf8');
130
+ } catch {
131
+ continue;
132
+ }
133
+ escaneados++;
134
+
135
+ const lineas = contenido.split('\n');
136
+ let dentroCodigo = false;
137
+ let lenguaje = '';
138
+ let marcadores = 0;
139
+
140
+ for (let i = 0; i < lineas.length; i++) {
141
+ const linea = lineas[i];
142
+ const fence = linea.match(/^\s*```\s*(\S*)/);
143
+ if (fence) {
144
+ marcadores++;
145
+ dentroCodigo = !dentroCodigo;
146
+ lenguaje = dentroCodigo ? fence[1].toLowerCase() : '';
147
+ continue;
148
+ }
149
+ if (!dentroCodigo || !LENGUAJES_EJECUTABLES.has(lenguaje)) continue;
150
+ for (const { re, desc } of PATRONES) {
151
+ if (re.test(linea)) {
152
+ violaciones.push({
153
+ comando: `habilidades/${skill}/SKILL.md`,
154
+ linea: i + 1,
155
+ texto: linea.trim(),
156
+ patron: desc,
157
+ });
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ // Fences desbalanceados (conteo impar de ```): el toggle booleano quedó
163
+ // invertido para el resto del archivo y pudo silenciar violaciones reales.
164
+ // Se reporta como violación para que el scan NO termine en verde falso.
165
+ if (marcadores % 2 !== 0) {
166
+ violaciones.push({
167
+ comando: `habilidades/${skill}/SKILL.md`,
168
+ linea: 0,
169
+ texto: `${marcadores} marcadores \`\`\` (impar) — fence sin cerrar o anidado`,
170
+ patron: 'fences desbalanceados (el scan de invocaciones pudo quedar ciego)',
171
+ });
172
+ }
173
+ }
174
+
175
+ return { ok: violaciones.length === 0, violaciones, escaneados };
176
+ }
177
+
178
+ module.exports = { auditarInvocacionesComandos, auditarInvocacionesSkills, SELF_DEV, SKILLS_SOLO_MADRE };
91
179
 
92
180
  if (require.main === module) {
93
181
  const r = auditarInvocacionesComandos();
94
- if (r.ok) {
95
- console.log(`[OK] invocaciones-comandos — ${r.escaneados} comandos user-facing sin rutas relativas al proyecto`);
182
+ const rs = auditarInvocacionesSkills();
183
+ if (r.ok && rs.ok) {
184
+ console.log(`[OK] invocaciones-comandos — ${r.escaneados} comandos + ${rs.escaneados} skills user-facing sin rutas relativas al proyecto`);
96
185
  process.exit(0);
97
186
  }
98
- console.error(`[FALLA] invocaciones-comandos ${r.violaciones.length} invocación(es) relativa(s) al proyecto en comandos user-facing:`);
99
- for (const v of r.violaciones) {
100
- console.error(` ${v.comando}.md:${v.linea} [${v.patron}] ${v.texto}`);
187
+ const todas = [...r.violaciones, ...rs.violaciones];
188
+ console.error(`[FALLA] invocaciones-comandos ${todas.length} invocación(es) relativa(s) al proyecto en componentes user-facing:`);
189
+ for (const v of todas) {
190
+ console.error(` ${v.comando}:${v.linea} [${v.patron}] ${v.texto}`);
101
191
  }
102
192
  console.error('\nUsa el patrón cross-scope (docs/invocacion-cli-cross-scope.md): swl-ses <sub> / npx ... <sub> / node scripts/cli/<sub>.js');
103
193
  process.exit(1);