@saulwade/swl-ses 2.5.3 → 2.6.1

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 (214) hide show
  1. package/CLAUDE.md +9 -9
  2. package/README.md +37 -37
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +932 -908
  8. package/agentes/backend-csharp-swl.md +420 -420
  9. package/agentes/backend-go-swl.md +390 -390
  10. package/agentes/backend-java-swl.md +281 -281
  11. package/agentes/backend-rust-swl.md +364 -364
  12. package/agentes/backend-workers-swl.md +482 -482
  13. package/agentes/cloud-infra-swl.md +509 -509
  14. package/agentes/consolidador-swl.md +541 -541
  15. package/agentes/depurador-swl.md +352 -352
  16. package/agentes/devops-ci-swl.md +400 -400
  17. package/agentes/disenador-ui-swl.md +569 -569
  18. package/agentes/documentador-swl.md +345 -345
  19. package/agentes/frontend-angular-swl.md +621 -621
  20. package/agentes/frontend-css-swl.md +716 -716
  21. package/agentes/frontend-react-swl.md +692 -692
  22. package/agentes/frontend-swl.md +496 -496
  23. package/agentes/frontend-tailwind-swl.md +826 -826
  24. package/agentes/investigador-swl.md +432 -432
  25. package/agentes/investigador-ux-swl.md +505 -505
  26. package/agentes/migrador-swl.md +442 -442
  27. package/agentes/mobile-android-swl.md +511 -511
  28. package/agentes/mobile-cross-swl.md +541 -541
  29. package/agentes/mobile-ios-swl.md +502 -502
  30. package/agentes/mobile-testing-swl.md +302 -302
  31. package/agentes/nemesis-auditor-swl.md +285 -285
  32. package/agentes/observabilidad-swl.md +438 -438
  33. package/agentes/pagos-swl.md +310 -310
  34. package/agentes/perfilador-usuario-swl.md +321 -321
  35. package/agentes/planificador-swl.md +399 -399
  36. package/agentes/producto-prd-swl.md +589 -589
  37. package/agentes/red-team-swl.md +218 -218
  38. package/agentes/release-manager-swl.md +590 -590
  39. package/agentes/rendimiento-swl.md +713 -713
  40. package/agentes/revisor-angular-swl.md +278 -278
  41. package/agentes/revisor-csharp-swl.md +264 -264
  42. package/agentes/revisor-go-swl.md +259 -259
  43. package/agentes/revisor-java-swl.md +257 -257
  44. package/agentes/revisor-kotlin-swl.md +273 -273
  45. package/agentes/revisor-nextjs-swl.md +281 -281
  46. package/agentes/revisor-php-swl.md +271 -271
  47. package/agentes/revisor-react-swl.md +278 -278
  48. package/agentes/revisor-rust-swl.md +346 -346
  49. package/agentes/revisor-seguridad-swl.md +399 -399
  50. package/agentes/revisor-swift-swl.md +268 -268
  51. package/agentes/revisor-typescript-swl.md +346 -346
  52. package/agentes/tdd-qa-swl.md +393 -393
  53. package/bin/swl-ses.js +32 -7
  54. package/comandos/swl/actualizar.md +3 -3
  55. package/comandos/swl/aprender.md +13 -0
  56. package/comandos/swl/deuda-codigo.md +97 -97
  57. package/comandos/swl/evaluar-skill.md +18 -3
  58. package/comandos/swl/evolucion-continua.md +73 -0
  59. package/comandos/swl/evolucionar.md +13 -0
  60. package/comandos/swl/instalar.md +4 -4
  61. package/comandos/swl/notificaciones.md +1 -1
  62. package/comandos/swl/status.md +2 -2
  63. package/gateway/cron/jobs.example.json +12 -0
  64. package/habilidades/auto-evolucion-protocolo/SKILL.md +19 -1
  65. package/habilidades/autoresearch/SKILL.md +3 -2
  66. package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
  67. package/habilidades/benchmark-memoria/SKILL.md +7 -7
  68. package/habilidades/changelog-generator/SKILL.md +1 -1
  69. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  70. package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
  71. package/habilidades/compactacion-contexto/SKILL.md +2 -1
  72. package/habilidades/contenedores-docker/SKILL.md +4 -2
  73. package/habilidades/context-builder/SKILL.md +4 -0
  74. package/habilidades/doubt-driven-review/SKILL.md +17 -1
  75. package/habilidades/drift-detection/SKILL.md +6 -1
  76. package/habilidades/ejecutar-fase/SKILL.md +6 -6
  77. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  78. package/habilidades/eval-framework/SKILL.md +8 -3
  79. package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
  80. package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
  81. package/habilidades/harness-claude-code/SKILL.md +7 -3
  82. package/habilidades/infra-github-actions/SKILL.md +4 -3
  83. package/habilidades/instalar-sistema/SKILL.md +5 -1
  84. package/habilidades/memoria-busqueda/SKILL.md +31 -39
  85. package/habilidades/planear-fase/SKILL.md +9 -1
  86. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  87. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  88. package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
  89. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
  90. package/habilidades/release-semver/SKILL.md +2 -2
  91. package/habilidades/swl-claudemd/SKILL.md +6 -7
  92. package/habilidades/swl-dashboard/SKILL.md +11 -43
  93. package/habilidades/tdd-workflow/SKILL.md +12 -7
  94. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  95. package/hooks/agente-lifecycle.js +2 -1
  96. package/hooks/aiisms-detector.js +13 -4
  97. package/hooks/audit-trail.js +2 -1
  98. package/hooks/auto-consolidacion.js +2 -1
  99. package/hooks/captura-acciones-post.js +2 -1
  100. package/hooks/captura-acciones-session.js +2 -1
  101. package/hooks/captura-feedback-usuario.js +3 -2
  102. package/hooks/claudemd-bloat-detector.js +12 -3
  103. package/hooks/claudemd-duplicacion-detector.js +13 -3
  104. package/hooks/contexto-iteracion.js +2 -1
  105. package/hooks/contexto-subagente.js +68 -68
  106. package/hooks/degradacion-instintos.js +2 -1
  107. package/hooks/extraccion-aprendizajes.js +109 -15
  108. package/hooks/grafo-contexto.js +2 -1
  109. package/hooks/guardrail-modelo.js +2 -1
  110. package/hooks/inbox-aviso.js +2 -1
  111. package/hooks/inyeccion-contexto.js +2 -1
  112. package/hooks/lib/agent-matcher.js +2 -1
  113. package/hooks/lib/agent-routing.js +2 -1
  114. package/hooks/lib/autonomia.js +5 -3
  115. package/hooks/lib/captura-acciones.js +2 -1
  116. package/hooks/lib/consolidation-lock.js +21 -10
  117. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  118. package/hooks/lib/etapa-metricas.js +2 -1
  119. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  120. package/hooks/lib/evolution-tracker.js +2 -1
  121. package/hooks/lib/gateway-notify.js +17 -3
  122. package/hooks/lib/loop-telemetry.js +5 -4
  123. package/hooks/lib/mcp-health.js +2 -1
  124. package/hooks/lib/memory-search.js +4 -0
  125. package/hooks/lib/merkle-audit.js +58 -6
  126. package/hooks/lib/notificacion-formato.js +58 -0
  127. package/hooks/lib/nudge-tracker.js +2 -1
  128. package/hooks/lib/otlp-exporter.js +2 -1
  129. package/hooks/lib/propose-step.js +3 -2
  130. package/hooks/lib/raiz-proyecto.js +127 -0
  131. package/hooks/lib/run-log.js +2 -1
  132. package/hooks/lib/singleton-guard.js +225 -27
  133. package/hooks/lib/telegram-cliente.js +28 -11
  134. package/hooks/notificacion-telegram.js +13 -3
  135. package/hooks/preservar-estado-pre-compact.js +2 -1
  136. package/hooks/proteccion-rutas.js +59 -3
  137. package/hooks/registro-turnos.js +2 -1
  138. package/hooks/resumen-sesion.js +2 -1
  139. package/hooks/risk-scoring.js +2 -1
  140. package/hooks/rotar-audit-auto.js +46 -20
  141. package/hooks/session-briefing.js +127 -1
  142. package/hooks/spec-gate.js +2 -1
  143. package/hooks/sugerir-contribuir.js +6 -3
  144. package/hooks/sugerir-regenerar-inventario.js +3 -2
  145. package/hooks/tdd-gate.js +2 -1
  146. package/hooks/telemetria-agentes.js +2 -1
  147. package/hooks/telemetria-skill-routing.js +2 -1
  148. package/hooks/tracking-costos.js +4 -3
  149. package/hooks/validar-formato-post-subagente.js +2 -1
  150. package/hooks/validar-intent-spec.js +2 -1
  151. package/hooks/validar-memoria-hook.js +13 -3
  152. package/hooks/validar-planning-paths.js +2 -1
  153. package/instintos/perfil-usuario.yaml +506 -3
  154. package/instintos/proyecto.yaml +78 -0
  155. package/llms.txt +29 -29
  156. package/manifiestos/canonical-hashes.json +5588 -4925
  157. package/manifiestos/hooks-config.json +469 -469
  158. package/manifiestos/invariantes-criticos.json +30 -30
  159. package/manifiestos/modulos.json +1429 -1423
  160. package/manifiestos/planning-paths.json +1 -0
  161. package/manifiestos/skills-lock.json +1275 -1275
  162. package/package.json +94 -95
  163. package/plugin.json +369 -369
  164. package/scripts/actualizar.js +3 -0
  165. package/scripts/auditar-clases-conocidas.js +134 -106
  166. package/scripts/benchmark-memoria.js +1 -0
  167. package/scripts/bootstrap-instintos.js +85 -14
  168. package/scripts/canario-hooks.js +166 -166
  169. package/scripts/cli/autonomia.js +23 -0
  170. package/scripts/cli/benchmark-memoria.js +37 -0
  171. package/scripts/cli/ciclo-autonomo.js +73 -0
  172. package/scripts/cli/ciclo-fase-b.js +102 -0
  173. package/scripts/cli/guardrail-metrics.js +39 -0
  174. package/scripts/cli/loop-telemetry.js +4 -2
  175. package/scripts/cli/memoria-search.js +69 -0
  176. package/scripts/cli/nudge-accionar.js +39 -0
  177. package/scripts/cli/run-eval.js +38 -0
  178. package/scripts/cli/run-skill-evals.js +13 -2
  179. package/scripts/derivar-feature-list.js +15 -14
  180. package/scripts/desinstalar.js +11 -0
  181. package/scripts/doctor.js +50 -13
  182. package/scripts/evidencia-valor.js +101 -101
  183. package/scripts/field-report.js +16 -16
  184. package/scripts/instalador.js +98 -7
  185. package/scripts/lib/activar-hooks-proyecto.js +116 -104
  186. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  187. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  188. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  189. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  190. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  191. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  192. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  193. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  194. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  195. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  196. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  197. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  198. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  199. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  200. package/scripts/lib/estado.js +9 -0
  201. package/scripts/lib/evidencia-valor.js +228 -228
  202. package/scripts/lib/expandir-targets.js +71 -71
  203. package/scripts/lib/gitignore-manifest.js +8 -1
  204. package/scripts/lib/hooks-settings.js +45 -0
  205. package/scripts/lib/limpiar-basura-global.js +161 -0
  206. package/scripts/lib/toml-merge.js +204 -204
  207. package/scripts/mcp-server/auth.js +105 -105
  208. package/scripts/mcp-server/cache.js +106 -106
  209. package/scripts/rotar-audit-logs.js +48 -2
  210. package/scripts/run-eval.js +1 -0
  211. package/scripts/run-skill-evals.js +287 -8
  212. package/scripts/smoke-test.js +16 -8
  213. package/scripts/tui/pantallas/install-wizard.js +69 -13
  214. package/scripts/validar.js +40 -1
@@ -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,
@@ -41,6 +41,7 @@ const { canonicalHash } = require('./lib/canonical-hash');
41
41
  const { auditarEscritura } = require('./lib/audit-evolved');
42
42
  const { detectarStack, filtrarReglasPorStack } = require('./lib/detectar-stack');
43
43
  const { actualizarGitignore, entradasParaRuntime, limpiarTracked, leerManifest, escribirManifest } = require('./lib/gitignore-manifest');
44
+ const { limpiarBasuraGlobal } = require('./lib/limpiar-basura-global');
44
45
 
45
46
  const RAIZ_PKG = path.resolve(__dirname, '..');
46
47
  const VERSION = require('../package.json').version;
@@ -164,6 +165,28 @@ async function install(opciones) {
164
165
  || perfilDeState
165
166
  || 'completo';
166
167
 
168
+ // DT-MODULOS-OPT-IN: preservar los módulos extra (--with) registrados por
169
+ // instalaciones previas en el install-state. Corre SIEMPRE (independiente
170
+ // de si el usuario pasó --profile): sin esto, cada update/reinstalación
171
+ // perdía en silencio los módulos opt-in (p. ej. reglas-personales).
172
+ let extrasDeState = [];
173
+ try {
174
+ const runtimeParaState = obtenerRuntime(target);
175
+ const dirDestino = esGlobal ? runtimeParaState.global : path.resolve(runtimeParaState.local);
176
+ const stateExistente = fs.existsSync(dirDestino) ? cargarEstado(dirDestino) : null;
177
+ if (stateExistente && Array.isArray(stateExistente.modulosExtra) && stateExistente.modulosExtra.length > 0) {
178
+ extrasDeState = stateExistente.modulosExtra;
179
+ console.log(`[install-state] Preservando módulos extra previos: ${extrasDeState.join(', ')}`);
180
+ console.log(`[install-state] Para quitarlos pasa --without <modulo> explícito.`);
181
+ }
182
+ } catch { /* state ilegible: seguir solo con --with del CLI */ }
183
+
184
+ // Unión de los extras del CLI (--with) y los preservados del state.
185
+ const withCombinado = [
186
+ ...String(opciones.with || '').split(',').map(s => s.trim()).filter(Boolean),
187
+ ...extrasDeState,
188
+ ].filter((m, i, arr) => arr.indexOf(m) === i);
189
+
167
190
  console.log(`swl-ses install v${VERSION}`);
168
191
  console.log(`${'='.repeat(40)}`);
169
192
 
@@ -230,7 +253,7 @@ async function install(opciones) {
230
253
  try {
231
254
  resolucion = resolverPerfil(perfil, {
232
255
  target,
233
- with: opciones.with,
256
+ with: withCombinado.join(',') || undefined,
234
257
  without: opciones.without,
235
258
  });
236
259
  } catch (err) {
@@ -330,6 +353,18 @@ async function install(opciones) {
330
353
  }
331
354
  }
332
355
 
356
+ // Scope global: purgar directorios basura sembrados en el HOME por versiones
357
+ // previas: ~/.planning/ scaffold (doctor confundía el HOME con un proyecto por
358
+ // ~/_userland) y ~/_userland/ vacío. Conservador: solo el .planning con la
359
+ // huella del scaffold y el _userland sin plugins reales, nunca contenido del
360
+ // usuario. Ver scripts/lib/limpiar-basura-global.js.
361
+ if (esGlobal) {
362
+ const basura = limpiarBasuraGlobal();
363
+ if (basura.eliminados.length > 0) {
364
+ console.log(`\n[global] Basura purgada del HOME: ${basura.eliminados.join(', ')}.`);
365
+ }
366
+ }
367
+
333
368
  // 4. Detectar _userland/
334
369
  const userlandAgentes = path.join(process.cwd(), '_userland', 'agentes');
335
370
  const userlandHabilidades = path.join(process.cwd(), '_userland', 'habilidades');
@@ -575,6 +610,9 @@ async function install(opciones) {
575
610
  // desde el cwd actual del doctor). Fix v1.4.3.
576
611
  allLangs: allLangs === true,
577
612
  stackInstalado: stackDetectado instanceof Set ? [...stackDetectado] : null,
613
+ // Solo los extras que el perfil realmente resolvió (existen y no fueron
614
+ // excluidos con --without) — DT-MODULOS-OPT-IN.
615
+ modulosExtra: withCombinado.filter(m => resolucion.modulos.includes(m)),
578
616
  });
579
617
 
580
618
  let instalados = 0;
@@ -727,6 +765,19 @@ async function install(opciones) {
727
765
  registrarComponente(estado, modulo);
728
766
  }
729
767
 
768
+ // Persistencia INTERMEDIA (DT-INSTALL-STATE-TRANSACCIONAL, nemesis sub1-S3):
769
+ // todos los archivos ya están en disco. Sin este save, una interrupción
770
+ // durante el registro de hooks (settings.json) o la generación de
771
+ // CLAUDE.md/AGENTS.md dejaría archivos copiados que el install-state NO
772
+ // registra → huérfanos que ni doctor ni uninstall ven. guardarEstado es
773
+ // atómico e idempotente; el save final (abajo) fija hooksRegistrados.
774
+ // Best-effort: un fallo aquí no aborta la instalación.
775
+ try {
776
+ guardarEstado(estado, rutas.base);
777
+ } catch (err) {
778
+ console.error(` ! Aviso: no se pudo persistir el estado intermedio: ${err.message}`);
779
+ }
780
+
730
781
  // 7. Registrar hooks en settings.json del runtime
731
782
  // Los hooks solo se registran a nivel PROYECTO (no global) para evitar
732
783
  // ejecucion duplicada cuando Claude Code combina hooks de ambos scopes.
@@ -738,8 +789,15 @@ async function install(opciones) {
738
789
  // No filtrar por tipo de módulo porque hooks como grafo-contexto.js
739
790
  // pueden pertenecer a módulos con tipo distinto a "hooks".
740
791
  const hooksConfig = cargarHooksConfig();
741
- const hookFilesInstalados = resolucion.archivos
742
- .filter(a => (a.rutaRelativa || a.origen).endsWith('.js') && hooksConfig[path.basename(a.origen)])
792
+ // Registrar SOLO los hooks realmente copiados (estado.archivosInstalados),
793
+ // no los del PLAN (resolucion.archivos) un hook cuyo .js falló al copiar
794
+ // (throw, colisión, origen inexistente) no debe quedar en settings.json
795
+ // apuntando a un archivo ausente: el wrapper traga el MODULE_NOT_FOUND y
796
+ // el hook muere en silencio (DT-REGISTRAR-HOOKS-PLAN, nemesis sub1-S2).
797
+ // Los hooks nunca entran a la rama `evolucionado` (no son componentes
798
+ // evolucionables) → un hook exitoso siempre queda en archivosInstalados.
799
+ const hookFilesInstalados = (estado.archivosInstalados || [])
800
+ .filter(a => (a.origen || '').endsWith('.js') && hooksConfig[path.basename(a.origen)])
743
801
  .map(a => path.basename(a.origen));
744
802
 
745
803
  if (hookFilesInstalados.length > 0) {
@@ -748,12 +806,22 @@ async function install(opciones) {
748
806
  console.log(' Los hooks se activan al instalar a nivel proyecto con: npx @saulwade/swl-ses@latest install --target claude');
749
807
  } else {
750
808
  const settingsPath = rutaSettings(runtime, rutas.base);
809
+ // UNIÓN con los hooks SWL YA registrados cuyo .js sigue en disco:
810
+ // registrarHooks reconstruye la sección SWL de settings.json, así que
811
+ // pasar solo lo recién copiado DESREGISTRARÍA los hooks pre-existentes
812
+ // que colisionaron sin --force pero siguen funcionando (regresión
813
+ // detectada por la re-auditoría nemesis iter-2). El plan-base ya no se
814
+ // usa: solo entra lo copiado ∪ lo pre-registrado-con-archivo-en-disco.
815
+ const preRegistrados = hooksSWLRegistrados(settingsPath).filter(
816
+ nombre => cargarHooksConfig()[nombre] && fs.existsSync(path.join(rutas.hooks, nombre))
817
+ );
818
+ const hookFilesFinal = [...new Set([...hookFilesInstalados, ...preRegistrados])];
751
819
  try {
752
820
  const resultado = registrarHooks({
753
821
  settingsPath,
754
822
  hooksDir: rutas.hooks,
755
823
  esGlobal: false,
756
- hookFiles: hookFilesInstalados,
824
+ hookFiles: hookFilesFinal,
757
825
  });
758
826
  hooksRegistrados = resultado.registrados;
759
827
  hooksPreservados = resultado.preservados;
@@ -1172,12 +1240,35 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1172
1240
  } else {
1173
1241
  destino = path.join(dirDestino, nombreArchivo);
1174
1242
  }
1243
+ } else if (
1244
+ archivo.tipo === 'hooks' &&
1245
+ archivo.rutaRelativa &&
1246
+ (archivo.rutaRelativa.startsWith('hooks/lib/') || archivo.rutaRelativa.startsWith('scripts/lib/'))
1247
+ ) {
1248
+ // Contrato de layout de hooks: los hooks requieren sus libs como
1249
+ // './lib/X' (y check-update/session-briefing usan ese fallback
1250
+ // explícito). El aplanado total dejaba TODOS esos require() muertos en
1251
+ // instalaciones frescas — MODULE_NOT_FOUND tragado por el wrapper de
1252
+ // settings; los installs de campo sobrevivían por el lib/ residual de
1253
+ // versiones previas. Preservar el subárbol lib/ (incluye subdirs como
1254
+ // lib/ciclo-autonomo/) restaura el contrato. scripts/ NO-lib sigue
1255
+ // aplanado en la raíz de hooks (status quo: rotar-audit-logs, etc.).
1256
+ const resto = archivo.rutaRelativa.replace(/^(?:hooks|scripts)\/lib\//, '');
1257
+ const partes = resto.split('/');
1258
+ const subdirLib = path.join(dirDestino, 'lib', ...partes.slice(0, -1));
1259
+ if (!fs.existsSync(subdirLib)) {
1260
+ fs.mkdirSync(subdirLib, { recursive: true });
1261
+ }
1262
+ destino = path.join(subdirLib, partes[partes.length - 1]);
1175
1263
  } else {
1176
1264
  destino = path.join(dirDestino, nombreArchivo);
1177
1265
  }
1178
1266
 
1179
- // Seguridad: validar ruta
1180
- validarRuta(dirDestino, nombreArchivo);
1267
+ // Seguridad: validar el DESTINO calculado completo, no solo el basename —
1268
+ // cubre también las ramas con subdirectorios (reglas/lenguajes, hooks/lib)
1269
+ // contra un rutaRelativa con traversal (endurecimiento, revisor-seguridad
1270
+ // 2026-07-10; el manifiesto es artefacto confiable, defensa en profundidad).
1271
+ validarRuta(dirDestino, path.relative(dirDestino, destino));
1181
1272
 
1182
1273
  // Verificar archivo sensible
1183
1274
  if (esArchivoSensible(nombreArchivo)) {
@@ -1,104 +1,116 @@
1
- 'use strict';
2
-
3
- /**
4
- * activar-hooks-proyecto.js — Camino "solo hooks" (DT-HOOKS-SOLO-PROYECTO).
5
- *
6
- * Registra en el `.claude/settings.json` del PROYECTO los hooks del scope
7
- * GLOBAL ya instalado, sin copiar ningún componente. Cierra el dilema del
8
- * install local: los hooks solo funcionan registrados a nivel proyecto
9
- * (ADR-0009), pero el install local completo duplica agentes/comandos en la
10
- * UI de Claude Code (lee ambos scopes). Con este camino:
11
- *
12
- * - Los comandos del settings apuntan con ruta ABSOLUTA al hooks/ global
13
- * (esGlobal: true en registrarHooks) → cero archivos copiados.
14
- * - `swl-ses update` del global refresca los hooks de TODOS los proyectos
15
- * activados así, gratis (apuntan a los mismos archivos).
16
- * - Hooks externos (no-SWL) del settings del proyecto se preservan
17
- * (garantía de registrarHooks).
18
- *
19
- * Uso: `swl-ses install --target claude --solo-hooks` (en la raíz del
20
- * proyecto). Requiere instalación global previa del runtime.
21
- */
22
-
23
- const fs = require('fs');
24
- const path = require('path');
25
-
26
- const RUNTIMES_SOPORTADOS = new Set(['claude', 'openclaude']);
27
-
28
- /**
29
- * @param {object} opciones
30
- * @param {string} [opciones.target='claude'] Runtime (claude|openclaude — formato de settings de Claude Code)
31
- * @param {string} [opciones.dirProyecto=cwd] Raíz del proyecto destino
32
- * @param {string} [opciones.hooksDirGlobal] Override del hooks/ global (tests)
33
- * @param {object} [opciones.deps] Inyección para tests: { registrarHooks, registrarOptimizaciones, cargarHooksConfig, scopesReales, obtenerRuntime }
34
- * @returns {{ok: boolean, error?: string, settingsPath?: string, hooksDirGlobal?: string, registrados?: number, preservados?: number}}
35
- */
36
- function activarHooksProyecto(opciones = {}) {
37
- const target = opciones.target || 'claude';
38
- if (!RUNTIMES_SOPORTADOS.has(target)) {
39
- return { ok: false, error: `--solo-hooks soporta por ahora targets con settings formato Claude Code (claude, openclaude); recibido: "${target}".` };
40
- }
41
-
42
- const deps = opciones.deps || {};
43
- const hs = require('./hooks-settings');
44
- const registrarHooks = deps.registrarHooks || hs.registrarHooks;
45
- const registrarOptimizaciones = deps.registrarOptimizaciones || hs.registrarOptimizaciones;
46
- const cargarHooksConfig = deps.cargarHooksConfig || hs.cargarHooksConfig;
47
-
48
- // 1. Localizar el hooks/ del scope GLOBAL del runtime.
49
- let hooksDirGlobal = opciones.hooksDirGlobal;
50
- if (!hooksDirGlobal) {
51
- try {
52
- const dr = require('./detectar-runtime');
53
- const obtenerRuntime = deps.obtenerRuntime || dr.obtenerRuntime;
54
- const scopesReales = deps.scopesReales || dr.scopesReales;
55
- const runtime = obtenerRuntime(target);
56
- const global = scopesReales(runtime).find(s => s.esGlobal);
57
- if (!global) return { ok: false, error: `No se pudo resolver el scope global del runtime "${target}".` };
58
- hooksDirGlobal = path.join(global.dir, 'hooks');
59
- } catch (err) {
60
- return { ok: false, error: `No se pudo detectar el runtime "${target}": ${err.message}` };
61
- }
62
- }
63
- if (!fs.existsSync(hooksDirGlobal)) {
64
- return { ok: false, error: `No hay instalación global con hooks en ${hooksDirGlobal}. Instala primero el global: npx -y @saulwade/swl-ses@latest install --target ${target} --global --perfil completo` };
65
- }
66
-
67
- // 2. Hooks disponibles = archivos del global ∩ hooks-config del paquete.
68
- const hooksConfig = cargarHooksConfig();
69
- const hookFiles = fs.readdirSync(hooksDirGlobal)
70
- .filter(f => f.endsWith('.js') && hooksConfig[f]);
71
- if (hookFiles.length === 0) {
72
- return { ok: false, error: `El hooks/ global (${hooksDirGlobal}) no contiene hooks reconocidos por hooks-config.json — instalación global corrupta o muy antigua; corre update primero.` };
73
- }
74
-
75
- // 3. Registrar en el settings.json del PROYECTO, apuntando al global.
76
- const dirProyecto = opciones.dirProyecto || process.cwd();
77
- const dirClaude = path.join(dirProyecto, '.claude');
78
- fs.mkdirSync(dirClaude, { recursive: true });
79
- const settingsPath = path.join(dirClaude, 'settings.json');
80
-
81
- const r = registrarHooks({
82
- settingsPath,
83
- hooksDir: hooksDirGlobal,
84
- esGlobal: true, // semántica: comandos con ruta ABSOLUTA al hooksDir (que es el global)
85
- hookFiles,
86
- });
87
-
88
- let optimizaciones = 0;
89
- try {
90
- const opt = registrarOptimizaciones(settingsPath);
91
- optimizaciones = (opt && opt.registradas) || 0;
92
- } catch { /* opcional — no bloquea la activación */ }
93
-
94
- return {
95
- ok: true,
96
- settingsPath,
97
- hooksDirGlobal,
98
- registrados: r.registrados,
99
- preservados: r.preservados,
100
- optimizaciones,
101
- };
102
- }
103
-
104
- module.exports = { activarHooksProyecto, RUNTIMES_SOPORTADOS };
1
+ 'use strict';
2
+
3
+ /**
4
+ * activar-hooks-proyecto.js — Camino "solo hooks" (DT-HOOKS-SOLO-PROYECTO).
5
+ *
6
+ * Registra en el `.claude/settings.json` del PROYECTO los hooks del scope
7
+ * GLOBAL ya instalado, sin copiar ningún componente. Cierra el dilema del
8
+ * install local: los hooks solo funcionan registrados a nivel proyecto
9
+ * (ADR-0009), pero el install local completo duplica agentes/comandos en la
10
+ * UI de Claude Code (lee ambos scopes). Con este camino:
11
+ *
12
+ * - Los comandos del settings apuntan con ruta ABSOLUTA al hooks/ global
13
+ * (esGlobal: true en registrarHooks) → cero archivos copiados.
14
+ * - `swl-ses update` del global refresca los hooks de TODOS los proyectos
15
+ * activados así, gratis (apuntan a los mismos archivos).
16
+ * - Hooks externos (no-SWL) del settings del proyecto se preservan
17
+ * (garantía de registrarHooks).
18
+ *
19
+ * Uso: `swl-ses install --target claude --solo-hooks` (en la raíz del
20
+ * proyecto). Requiere instalación global previa del runtime.
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ const RUNTIMES_SOPORTADOS = new Set(['claude', 'openclaude']);
27
+
28
+ /**
29
+ * @param {object} opciones
30
+ * @param {string} [opciones.target='claude'] Runtime (claude|openclaude — formato de settings de Claude Code)
31
+ * @param {string} [opciones.dirProyecto=cwd] Raíz del proyecto destino
32
+ * @param {string} [opciones.hooksDirGlobal] Override del hooks/ global (tests)
33
+ * @param {object} [opciones.deps] Inyección para tests: { registrarHooks, registrarOptimizaciones, cargarHooksConfig, scopesReales, obtenerRuntime }
34
+ * @returns {{ok: boolean, error?: string, settingsPath?: string, hooksDirGlobal?: string, registrados?: number, preservados?: number}}
35
+ */
36
+ function activarHooksProyecto(opciones = {}) {
37
+ const target = opciones.target || 'claude';
38
+ if (!RUNTIMES_SOPORTADOS.has(target)) {
39
+ return { ok: false, error: `--solo-hooks soporta por ahora targets con settings formato Claude Code (claude, openclaude); recibido: "${target}".` };
40
+ }
41
+
42
+ const deps = opciones.deps || {};
43
+ const hs = require('./hooks-settings');
44
+ const registrarHooks = deps.registrarHooks || hs.registrarHooks;
45
+ const registrarOptimizaciones = deps.registrarOptimizaciones || hs.registrarOptimizaciones;
46
+ const cargarHooksConfig = deps.cargarHooksConfig || hs.cargarHooksConfig;
47
+
48
+ // 1. Localizar el hooks/ del scope GLOBAL del runtime.
49
+ let hooksDirGlobal = opciones.hooksDirGlobal;
50
+ if (!hooksDirGlobal) {
51
+ try {
52
+ const dr = require('./detectar-runtime');
53
+ const obtenerRuntime = deps.obtenerRuntime || dr.obtenerRuntime;
54
+ const scopesReales = deps.scopesReales || dr.scopesReales;
55
+ const runtime = obtenerRuntime(target);
56
+ const global = scopesReales(runtime).find(s => s.esGlobal);
57
+ if (!global) return { ok: false, error: `No se pudo resolver el scope global del runtime "${target}".` };
58
+ hooksDirGlobal = path.join(global.dir, 'hooks');
59
+ } catch (err) {
60
+ return { ok: false, error: `No se pudo detectar el runtime "${target}": ${err.message}` };
61
+ }
62
+ }
63
+ if (!fs.existsSync(hooksDirGlobal)) {
64
+ return { ok: false, error: `No hay instalación global con hooks en ${hooksDirGlobal}. Instala primero el global: npx -y @saulwade/swl-ses@latest install --target ${target} --global --perfil completo` };
65
+ }
66
+
67
+ // 2. Hooks disponibles = archivos del global ∩ hooks-config del paquete.
68
+ const hooksConfig = cargarHooksConfig();
69
+ const hookFiles = fs.readdirSync(hooksDirGlobal)
70
+ .filter(f => f.endsWith('.js') && hooksConfig[f]);
71
+ if (hookFiles.length === 0) {
72
+ return { ok: false, error: `El hooks/ global (${hooksDirGlobal}) no contiene hooks reconocidos por hooks-config.json — instalación global corrupta o muy antigua; corre update primero.` };
73
+ }
74
+
75
+ // 3. Registrar en el settings.json del PROYECTO, apuntando al global.
76
+ const dirProyecto = opciones.dirProyecto || process.cwd();
77
+ const dirClaude = path.join(dirProyecto, '.claude');
78
+ fs.mkdirSync(dirClaude, { recursive: true });
79
+ const settingsPath = path.join(dirClaude, 'settings.json');
80
+
81
+ const r = registrarHooks({
82
+ settingsPath,
83
+ hooksDir: hooksDirGlobal,
84
+ esGlobal: true, // semántica: comandos con ruta ABSOLUTA al hooksDir (que es el global)
85
+ hookFiles,
86
+ });
87
+
88
+ let optimizaciones = 0;
89
+ try {
90
+ const opt = registrarOptimizaciones(settingsPath);
91
+ optimizaciones = (opt && opt.registradas) || 0;
92
+ } catch { /* opcional — no bloquea la activación */ }
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
+
105
+ return {
106
+ ok: true,
107
+ settingsPath,
108
+ hooksDirGlobal,
109
+ registrados: r.registrados,
110
+ preservados: r.preservados,
111
+ optimizaciones,
112
+ gitignorePath,
113
+ };
114
+ }
115
+
116
+ module.exports = { activarHooksProyecto, RUNTIMES_SOPORTADOS };
@@ -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);