swl-ses 3.3.2

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 (177) hide show
  1. package/CLAUDE.md +425 -0
  2. package/_userland/agentes/.gitkeep +0 -0
  3. package/_userland/habilidades/.gitkeep +0 -0
  4. package/agentes/accesibilidad-wcag-swl.md +683 -0
  5. package/agentes/arquitecto-swl.md +210 -0
  6. package/agentes/auto-evolucion-swl.md +408 -0
  7. package/agentes/backend-api-swl.md +442 -0
  8. package/agentes/backend-node-swl.md +439 -0
  9. package/agentes/backend-python-swl.md +469 -0
  10. package/agentes/backend-workers-swl.md +444 -0
  11. package/agentes/cloud-infra-swl.md +466 -0
  12. package/agentes/consolidador-swl.md +487 -0
  13. package/agentes/datos-swl.md +568 -0
  14. package/agentes/depurador-swl.md +301 -0
  15. package/agentes/devops-ci-swl.md +352 -0
  16. package/agentes/disenador-ui-swl.md +546 -0
  17. package/agentes/documentador-swl.md +323 -0
  18. package/agentes/frontend-angular-swl.md +603 -0
  19. package/agentes/frontend-css-swl.md +700 -0
  20. package/agentes/frontend-react-swl.md +672 -0
  21. package/agentes/frontend-swl.md +483 -0
  22. package/agentes/frontend-tailwind-swl.md +808 -0
  23. package/agentes/implementador-swl.md +235 -0
  24. package/agentes/investigador-swl.md +274 -0
  25. package/agentes/investigador-ux-swl.md +482 -0
  26. package/agentes/migrador-swl.md +389 -0
  27. package/agentes/mobile-android-swl.md +473 -0
  28. package/agentes/mobile-cross-swl.md +501 -0
  29. package/agentes/mobile-ios-swl.md +464 -0
  30. package/agentes/notificador-swl.md +886 -0
  31. package/agentes/observabilidad-swl.md +408 -0
  32. package/agentes/orquestador-swl.md +490 -0
  33. package/agentes/planificador-swl.md +222 -0
  34. package/agentes/producto-prd-swl.md +565 -0
  35. package/agentes/release-manager-swl.md +545 -0
  36. package/agentes/rendimiento-swl.md +691 -0
  37. package/agentes/revisor-codigo-swl.md +254 -0
  38. package/agentes/revisor-seguridad-swl.md +316 -0
  39. package/agentes/tdd-qa-swl.md +323 -0
  40. package/agentes/ux-disenador-swl.md +498 -0
  41. package/bin/swl-ses.js +119 -0
  42. package/comandos/swl/actualizar.md +117 -0
  43. package/comandos/swl/aprender.md +348 -0
  44. package/comandos/swl/auditar-deps.md +390 -0
  45. package/comandos/swl/autoresearch.md +346 -0
  46. package/comandos/swl/checkpoint.md +296 -0
  47. package/comandos/swl/compactar.md +283 -0
  48. package/comandos/swl/crear-skill.md +609 -0
  49. package/comandos/swl/discutir-fase.md +230 -0
  50. package/comandos/swl/ejecutar-fase.md +302 -0
  51. package/comandos/swl/evolucionar.md +377 -0
  52. package/comandos/swl/instalar.md +220 -0
  53. package/comandos/swl/mapear-codebase.md +205 -0
  54. package/comandos/swl/nuevo-proyecto.md +154 -0
  55. package/comandos/swl/planear-fase.md +221 -0
  56. package/comandos/swl/release.md +405 -0
  57. package/comandos/swl/salud.md +382 -0
  58. package/comandos/swl/verificar.md +292 -0
  59. package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
  60. package/habilidades/angular-avanzado/SKILL.md +491 -0
  61. package/habilidades/angular-moderno/SKILL.md +326 -0
  62. package/habilidades/api-rest-diseno/SKILL.md +302 -0
  63. package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
  64. package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
  65. package/habilidades/async-python/SKILL.md +474 -0
  66. package/habilidades/auth-patrones/SKILL.md +488 -0
  67. package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
  68. package/habilidades/autoresearch/SKILL.md +248 -0
  69. package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
  70. package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
  71. package/habilidades/checklist-calidad/SKILL.md +247 -0
  72. package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
  73. package/habilidades/checklist-seguridad/SKILL.md +224 -0
  74. package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
  75. package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
  76. package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
  77. package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
  78. package/habilidades/cloud-aws/SKILL.md +497 -0
  79. package/habilidades/compactacion-contexto/SKILL.md +201 -0
  80. package/habilidades/contenedores-docker/SKILL.md +453 -0
  81. package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
  82. package/habilidades/css-moderno/SKILL.md +463 -0
  83. package/habilidades/datos-etl/SKILL.md +486 -0
  84. package/habilidades/dependencias-auditoria/SKILL.md +293 -0
  85. package/habilidades/deprecacion-migracion/SKILL.md +485 -0
  86. package/habilidades/design-tokens/SKILL.md +519 -0
  87. package/habilidades/discutir-fase/SKILL.md +167 -0
  88. package/habilidades/diseno-responsivo/SKILL.md +326 -0
  89. package/habilidades/django-experto/SKILL.md +395 -0
  90. package/habilidades/doc-sync/SKILL.md +259 -0
  91. package/habilidades/ejecutar-fase/SKILL.md +199 -0
  92. package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
  93. package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
  94. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
  95. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
  96. package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
  97. package/habilidades/event-driven/SKILL.md +580 -0
  98. package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
  99. package/habilidades/fastapi-experto/SKILL.md +368 -0
  100. package/habilidades/frontend-avanzado/SKILL.md +555 -0
  101. package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
  102. package/habilidades/iam-secretos/SKILL.md +511 -0
  103. package/habilidades/instalar-sistema/SKILL.md +140 -0
  104. package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
  105. package/habilidades/manejo-errores/SKILL.md +512 -0
  106. package/habilidades/mapear-codebase/SKILL.md +199 -0
  107. package/habilidades/microservicios/SKILL.md +473 -0
  108. package/habilidades/mobile-flutter/SKILL.md +566 -0
  109. package/habilidades/mobile-react-native/SKILL.md +493 -0
  110. package/habilidades/monitoring-alertas/SKILL.md +447 -0
  111. package/habilidades/node-experto/SKILL.md +521 -0
  112. package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
  113. package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
  114. package/habilidades/nuevo-proyecto/SKILL.md +183 -0
  115. package/habilidades/patrones-python/SKILL.md +381 -0
  116. package/habilidades/performance-baseline/SKILL.md +243 -0
  117. package/habilidades/planear-fase/SKILL.md +184 -0
  118. package/habilidades/postgresql-experto/SKILL.md +379 -0
  119. package/habilidades/react-experto/SKILL.md +434 -0
  120. package/habilidades/react-optimizacion/SKILL.md +328 -0
  121. package/habilidades/release-semver/SKILL.md +226 -0
  122. package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
  123. package/habilidades/sql-optimizacion/SKILL.md +314 -0
  124. package/habilidades/tailwind-experto/SKILL.md +412 -0
  125. package/habilidades/tdd-workflow/SKILL.md +267 -0
  126. package/habilidades/testing-python/SKILL.md +350 -0
  127. package/habilidades/threat-model-lite/SKILL.md +218 -0
  128. package/habilidades/typescript-avanzado/SKILL.md +454 -0
  129. package/habilidades/ux-diseno/SKILL.md +488 -0
  130. package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
  131. package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
  132. package/habilidades/verificar-trabajo/SKILL.md +208 -0
  133. package/habilidades/wireframes-flujos/SKILL.md +396 -0
  134. package/habilidades/workflow-claude-code/SKILL.md +359 -0
  135. package/hooks/calidad-pre-commit.js +578 -0
  136. package/hooks/escaneo-secretos.js +302 -0
  137. package/hooks/extraccion-aprendizajes.js +550 -0
  138. package/hooks/linea-estado.js +249 -0
  139. package/hooks/monitor-contexto.js +230 -0
  140. package/hooks/proteccion-rutas.js +249 -0
  141. package/manifiestos/hooks-config.json +41 -0
  142. package/manifiestos/modulos.json +318 -0
  143. package/manifiestos/perfiles.json +189 -0
  144. package/package.json +45 -0
  145. package/plantillas/PROJECT.md +122 -0
  146. package/plantillas/REQUIREMENTS.md +132 -0
  147. package/plantillas/ROADMAP.md +143 -0
  148. package/plantillas/STATE.md +109 -0
  149. package/plantillas/research/ARCHITECTURE.md +220 -0
  150. package/plantillas/research/FEATURES.md +175 -0
  151. package/plantillas/research/PITFALLS.md +299 -0
  152. package/plantillas/research/STACK.md +233 -0
  153. package/plantillas/research/SUMMARY.md +165 -0
  154. package/plugin.json +144 -0
  155. package/reglas/accesibilidad.md +269 -0
  156. package/reglas/api-diseno.md +400 -0
  157. package/reglas/arquitectura.md +183 -0
  158. package/reglas/cloud-infra.md +247 -0
  159. package/reglas/docs.md +245 -0
  160. package/reglas/estilo-codigo.md +179 -0
  161. package/reglas/git-workflow.md +186 -0
  162. package/reglas/performance.md +195 -0
  163. package/reglas/pruebas.md +159 -0
  164. package/reglas/seguridad.md +151 -0
  165. package/reglas/skills-estandar.md +473 -0
  166. package/scripts/actualizar.js +51 -0
  167. package/scripts/desinstalar.js +86 -0
  168. package/scripts/doctor.js +222 -0
  169. package/scripts/inicializar.js +89 -0
  170. package/scripts/instalador.js +333 -0
  171. package/scripts/lib/detectar-runtime.js +177 -0
  172. package/scripts/lib/estado.js +112 -0
  173. package/scripts/lib/hooks-settings.js +283 -0
  174. package/scripts/lib/manifiestos.js +138 -0
  175. package/scripts/lib/seguridad.js +160 -0
  176. package/scripts/publicar.js +209 -0
  177. package/scripts/validar.js +120 -0
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Detecta el runtime de IA disponible en el sistema.
5
+ * Soporta: Claude Code, Copilot, OpenCode, Codex, Gemini CLI
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+
12
+ const RUNTIMES = {
13
+ claude: {
14
+ nombre: 'Claude Code',
15
+ global: path.join(os.homedir(), '.claude'),
16
+ local: '.claude',
17
+ archivosConfig: ['settings.json', 'settings.local.json'],
18
+ dirAgentes: 'agents',
19
+ dirHabilidades: 'skills',
20
+ dirComandos: 'commands',
21
+ dirReglas: 'rules',
22
+ formatoAgente: 'markdown-frontmatter',
23
+ soportaHooks: true,
24
+ hookConfig: 'settings.json',
25
+ },
26
+ copilot: {
27
+ nombre: 'GitHub Copilot',
28
+ global: path.join(os.homedir(), '.copilot'),
29
+ local: '.copilot',
30
+ archivosConfig: ['config.json'],
31
+ dirAgentes: 'agents',
32
+ dirHabilidades: 'skills',
33
+ dirComandos: 'commands',
34
+ dirReglas: 'rules',
35
+ formatoAgente: 'markdown-frontmatter',
36
+ soportaHooks: false,
37
+ experimental: true,
38
+ },
39
+ opencode: {
40
+ nombre: 'OpenCode',
41
+ global: path.join(os.homedir(), '.opencode'),
42
+ local: '.opencode',
43
+ archivosConfig: ['config.ts', 'config.json'],
44
+ dirAgentes: 'agents',
45
+ dirHabilidades: 'skills',
46
+ dirComandos: 'commands',
47
+ dirReglas: 'rules',
48
+ formatoAgente: 'markdown-frontmatter',
49
+ soportaHooks: true,
50
+ hookConfig: 'config.json',
51
+ experimental: true,
52
+ },
53
+ codex: {
54
+ nombre: 'Codex CLI',
55
+ global: path.join(os.homedir(), '.codex'),
56
+ local: '.codex',
57
+ archivosConfig: ['config.toml'],
58
+ dirAgentes: 'agents',
59
+ dirHabilidades: 'skills',
60
+ dirComandos: null,
61
+ dirReglas: null,
62
+ formatoAgente: 'toml',
63
+ soportaHooks: false,
64
+ experimental: true,
65
+ },
66
+ gemini: {
67
+ nombre: 'Gemini CLI',
68
+ global: path.join(os.homedir(), '.gemini'),
69
+ local: '.gemini',
70
+ archivosConfig: ['settings.json'],
71
+ dirAgentes: 'agents',
72
+ dirHabilidades: 'skills',
73
+ dirComandos: 'commands',
74
+ dirReglas: 'rules',
75
+ formatoAgente: 'markdown-frontmatter',
76
+ soportaHooks: true,
77
+ hookConfig: 'settings.json',
78
+ experimental: true,
79
+ },
80
+ };
81
+
82
+ /**
83
+ * Detecta qué runtimes están instalados en el sistema
84
+ */
85
+ function detectarRuntimes() {
86
+ const detectados = [];
87
+
88
+ for (const [id, config] of Object.entries(RUNTIMES)) {
89
+ const globalExiste = fs.existsSync(config.global);
90
+ const localExiste = fs.existsSync(path.resolve(config.local));
91
+
92
+ if (globalExiste || localExiste) {
93
+ detectados.push({
94
+ id,
95
+ ...config,
96
+ globalExiste,
97
+ localExiste,
98
+ });
99
+ }
100
+ }
101
+
102
+ return detectados;
103
+ }
104
+
105
+ /**
106
+ * Obtiene la configuración de un runtime específico
107
+ */
108
+ function obtenerRuntime(id) {
109
+ const runtime = RUNTIMES[id];
110
+ if (!runtime) {
111
+ throw new Error(
112
+ `Runtime desconocido: "${id}". Disponibles: ${Object.keys(RUNTIMES).join(', ')}`
113
+ );
114
+ }
115
+ return { id, ...runtime };
116
+ }
117
+
118
+ /**
119
+ * Calcula las rutas de instalación para un runtime
120
+ */
121
+ function calcularRutas(runtimeId, opciones = {}) {
122
+ const runtime = obtenerRuntime(runtimeId);
123
+ const base = opciones.global ? runtime.global : path.resolve(runtime.local);
124
+
125
+ // Para instalación local, hooks van en ./hooks/ (raíz del proyecto)
126
+ // Para instalación global, hooks van dentro del directorio del runtime
127
+ const hooksDir = runtime.soportaHooks
128
+ ? (opciones.global ? path.join(base, 'hooks') : path.join(base, '..', 'hooks'))
129
+ : null;
130
+
131
+ const rutas = {
132
+ base,
133
+ agentes: runtime.dirAgentes ? path.join(base, runtime.dirAgentes) : null,
134
+ habilidades: runtime.dirHabilidades ? path.join(base, runtime.dirHabilidades) : null,
135
+ comandos: runtime.dirComandos ? path.join(base, runtime.dirComandos, 'swl') : null,
136
+ reglas: runtime.dirReglas ? path.join(base, runtime.dirReglas) : null,
137
+ hooks: hooksDir,
138
+ };
139
+
140
+ return rutas;
141
+ }
142
+
143
+ /**
144
+ * Resumen de detección para mostrar al usuario
145
+ */
146
+ function resumenDeteccion() {
147
+ const detectados = detectarRuntimes();
148
+ const lineas = ['Runtimes detectados:'];
149
+
150
+ if (detectados.length === 0) {
151
+ lineas.push(' (ninguno encontrado)');
152
+ lineas.push('');
153
+ lineas.push('Runtimes soportados:');
154
+ for (const [id, config] of Object.entries(RUNTIMES)) {
155
+ const marca = config.experimental ? ' [experimental]' : '';
156
+ lineas.push(` - ${id}: ${config.nombre}${marca}`);
157
+ }
158
+ } else {
159
+ for (const r of detectados) {
160
+ const ubicacion = [];
161
+ if (r.globalExiste) ubicacion.push('global');
162
+ if (r.localExiste) ubicacion.push('local');
163
+ const marca = r.experimental ? ' [experimental]' : '';
164
+ lineas.push(` + ${r.id}: ${r.nombre} (${ubicacion.join(', ')})${marca}`);
165
+ }
166
+ }
167
+
168
+ return lineas.join('\n');
169
+ }
170
+
171
+ module.exports = {
172
+ RUNTIMES,
173
+ detectarRuntimes,
174
+ obtenerRuntime,
175
+ calcularRutas,
176
+ resumenDeteccion,
177
+ };
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Gestión de estado de instalación SWL.
5
+ * Persiste qué se instaló, dónde y cuándo para soporte de doctor/repair/uninstall.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const NOMBRE_ESTADO = '.swl-install-state.json';
12
+
13
+ /**
14
+ * Crea un nuevo registro de estado de instalación
15
+ */
16
+ function crearEstado(opciones) {
17
+ return {
18
+ version: 2,
19
+ sistema: 'swl-software-engineering-system',
20
+ versionSistema: opciones.versionSistema || '1.0.0',
21
+ target: opciones.target,
22
+ perfil: opciones.perfil,
23
+ rutaBase: opciones.rutaBase,
24
+ global: opciones.global || false,
25
+ componentesInstalados: [],
26
+ archivosInstalados: [],
27
+ hooksRegistrados: 0,
28
+ settingsModificado: false,
29
+ instaladoEn: new Date().toISOString(),
30
+ actualizadoEn: new Date().toISOString(),
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Registra un archivo instalado en el estado
36
+ */
37
+ function registrarArchivo(estado, archivo) {
38
+ estado.archivosInstalados.push({
39
+ origen: archivo.origen,
40
+ destino: archivo.destino,
41
+ tipo: archivo.tipo, // agente, habilidad, comando, regla, hook
42
+ checksum: archivo.checksum || null,
43
+ instaladoEn: new Date().toISOString(),
44
+ });
45
+ estado.actualizadoEn = new Date().toISOString();
46
+ }
47
+
48
+ /**
49
+ * Registra un componente instalado
50
+ */
51
+ function registrarComponente(estado, componente) {
52
+ if (!estado.componentesInstalados.includes(componente)) {
53
+ estado.componentesInstalados.push(componente);
54
+ estado.actualizadoEn = new Date().toISOString();
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Guarda el estado en disco
60
+ */
61
+ function guardarEstado(estado, directorio) {
62
+ const rutaEstado = path.join(directorio, NOMBRE_ESTADO);
63
+ fs.writeFileSync(rutaEstado, JSON.stringify(estado, null, 2), 'utf-8');
64
+ return rutaEstado;
65
+ }
66
+
67
+ /**
68
+ * Carga el estado desde disco
69
+ */
70
+ function cargarEstado(directorio) {
71
+ const rutaEstado = path.join(directorio, NOMBRE_ESTADO);
72
+ if (!fs.existsSync(rutaEstado)) {
73
+ return null;
74
+ }
75
+ try {
76
+ return JSON.parse(fs.readFileSync(rutaEstado, 'utf-8'));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Verifica integridad del estado contra archivos en disco
84
+ */
85
+ function verificarIntegridad(estado) {
86
+ const problemas = [];
87
+
88
+ for (const archivo of estado.archivosInstalados) {
89
+ if (!fs.existsSync(archivo.destino)) {
90
+ problemas.push({
91
+ tipo: 'faltante',
92
+ archivo: archivo.destino,
93
+ mensaje: `Archivo instalado no encontrado: ${archivo.destino}`,
94
+ });
95
+ }
96
+ }
97
+
98
+ return {
99
+ integro: problemas.length === 0,
100
+ problemas,
101
+ };
102
+ }
103
+
104
+ module.exports = {
105
+ NOMBRE_ESTADO,
106
+ crearEstado,
107
+ registrarArchivo,
108
+ registrarComponente,
109
+ guardarEstado,
110
+ cargarEstado,
111
+ verificarIntegridad,
112
+ };
@@ -0,0 +1,283 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Gestión de hooks en settings.json del runtime destino.
5
+ *
6
+ * Funciones para registrar, desregistrar y verificar hooks SWL
7
+ * en la configuración del runtime (Claude Code, Gemini CLI, etc.).
8
+ *
9
+ * Los hooks SWL se identifican por la presencia de "hooks/" + nombre
10
+ * del archivo en el campo `command`.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const RAIZ_PKG = path.resolve(__dirname, '..', '..');
17
+
18
+ /**
19
+ * Carga la configuración de hooks desde manifiestos/hooks-config.json
20
+ * @returns {object} Mapa de nombre_archivo → { event, matcher, description, blocking }
21
+ */
22
+ function cargarHooksConfig() {
23
+ const configPath = path.join(RAIZ_PKG, 'manifiestos', 'hooks-config.json');
24
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
25
+ return config.hooks;
26
+ }
27
+
28
+ /**
29
+ * Lee settings.json del runtime. Si no existe, retorna objeto base.
30
+ * @param {string} settingsPath - Ruta completa al archivo settings.json
31
+ * @returns {object}
32
+ */
33
+ function leerSettings(settingsPath) {
34
+ if (!fs.existsSync(settingsPath)) {
35
+ return {};
36
+ }
37
+ try {
38
+ return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
39
+ } catch {
40
+ return {};
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Escribe settings.json preservando formato legible.
46
+ * @param {string} settingsPath
47
+ * @param {object} settings
48
+ */
49
+ function escribirSettings(settingsPath, settings) {
50
+ const dir = path.dirname(settingsPath);
51
+ if (!fs.existsSync(dir)) {
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ }
54
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
55
+ }
56
+
57
+ /**
58
+ * Determina si un hook entry en settings.json pertenece a SWL.
59
+ * @param {object} entry - { matcher, command }
60
+ * @returns {boolean}
61
+ */
62
+ function esHookSWL(entry) {
63
+ const cmd = entry.command || '';
64
+ // Los hooks SWL usan el patrón: node hooks/nombre-hook.js
65
+ return /node\s+.*hooks\/[a-z-]+\.js/.test(cmd);
66
+ }
67
+
68
+ /**
69
+ * Calcula la ruta del settings.json para un runtime.
70
+ * @param {object} runtime - Configuración del runtime
71
+ * @param {string} base - Directorio base (.claude, ~/.claude, etc.)
72
+ * @returns {string} Ruta completa al settings.json
73
+ */
74
+ function rutaSettings(runtime, base) {
75
+ const configFile = runtime.hookConfig || 'settings.json';
76
+ return path.join(base, configFile);
77
+ }
78
+
79
+ /**
80
+ * Genera el comando para ejecutar un hook desde el directorio de trabajo.
81
+ * @param {string} hookFilename - Nombre del archivo de hook (ej: "escaneo-secretos.js")
82
+ * @param {string} hooksDir - Directorio donde se instalaron los hooks
83
+ * @param {boolean} esGlobal - Si es instalación global
84
+ * @returns {string} Comando ejecutable
85
+ */
86
+ function generarComando(hookFilename, hooksDir, esGlobal) {
87
+ if (esGlobal) {
88
+ // Para instalación global, usar ruta absoluta
89
+ const rutaAbsoluta = path.join(hooksDir, hookFilename);
90
+ return `node "${rutaAbsoluta}"`;
91
+ }
92
+ // Para instalación local, ruta relativa al proyecto
93
+ return `node hooks/${hookFilename}`;
94
+ }
95
+
96
+ /**
97
+ * Registra los hooks SWL en settings.json del runtime.
98
+ * Preserva hooks existentes no-SWL. Reemplaza hooks SWL previos.
99
+ *
100
+ * @param {object} opciones
101
+ * @param {string} opciones.settingsPath - Ruta al settings.json
102
+ * @param {string} opciones.hooksDir - Directorio donde están los hooks
103
+ * @param {boolean} opciones.esGlobal - Si es instalación global
104
+ * @param {string[]} opciones.hookFiles - Lista de archivos de hooks instalados
105
+ * @returns {{ registrados: number, preservados: number }}
106
+ */
107
+ function registrarHooks(opciones) {
108
+ const { settingsPath, hooksDir, esGlobal, hookFiles } = opciones;
109
+ const hooksConfig = cargarHooksConfig();
110
+ const settings = leerSettings(settingsPath);
111
+
112
+ // Inicializar sección hooks si no existe
113
+ if (!settings.hooks) {
114
+ settings.hooks = {};
115
+ }
116
+
117
+ let registrados = 0;
118
+ let preservados = 0;
119
+
120
+ // Agrupar hooks SWL por evento
121
+ const hooksPorEvento = {};
122
+ for (const filename of hookFiles) {
123
+ const config = hooksConfig[filename];
124
+ if (!config) continue;
125
+
126
+ const evento = config.event;
127
+ if (!hooksPorEvento[evento]) {
128
+ hooksPorEvento[evento] = [];
129
+ }
130
+
131
+ hooksPorEvento[evento].push({
132
+ matcher: config.matcher || undefined,
133
+ command: generarComando(filename, hooksDir, esGlobal),
134
+ });
135
+ registrados++;
136
+ }
137
+
138
+ // Para cada tipo de evento, preservar hooks no-SWL y agregar los SWL
139
+ for (const evento of ['PreToolUse', 'PostToolUse']) {
140
+ const existentes = settings.hooks[evento] || [];
141
+ const noSWL = existentes.filter(entry => !esHookSWL(entry));
142
+ preservados += noSWL.length;
143
+
144
+ const swlNuevos = hooksPorEvento[evento] || [];
145
+
146
+ // Limpiar entradas con matcher vacío (Claude Code no necesita la key)
147
+ const swlLimpios = swlNuevos.map(h => {
148
+ const entry = { command: h.command };
149
+ if (h.matcher) entry.matcher = h.matcher;
150
+ return entry;
151
+ });
152
+
153
+ const combinados = [...noSWL, ...swlLimpios];
154
+
155
+ if (combinados.length > 0) {
156
+ settings.hooks[evento] = combinados;
157
+ } else {
158
+ delete settings.hooks[evento];
159
+ }
160
+ }
161
+
162
+ // Limpiar sección hooks si quedó vacía
163
+ if (Object.keys(settings.hooks).length === 0) {
164
+ delete settings.hooks;
165
+ }
166
+
167
+ escribirSettings(settingsPath, settings);
168
+
169
+ return { registrados, preservados };
170
+ }
171
+
172
+ /**
173
+ * Desregistra todos los hooks SWL de settings.json.
174
+ * Preserva hooks no-SWL intactos.
175
+ *
176
+ * @param {string} settingsPath - Ruta al settings.json
177
+ * @returns {{ eliminados: number, preservados: number }}
178
+ */
179
+ function desregistrarHooks(settingsPath) {
180
+ const settings = leerSettings(settingsPath);
181
+
182
+ if (!settings.hooks) {
183
+ return { eliminados: 0, preservados: 0 };
184
+ }
185
+
186
+ let eliminados = 0;
187
+ let preservados = 0;
188
+
189
+ for (const evento of ['PreToolUse', 'PostToolUse']) {
190
+ const existentes = settings.hooks[evento] || [];
191
+ const noSWL = [];
192
+
193
+ for (const entry of existentes) {
194
+ if (esHookSWL(entry)) {
195
+ eliminados++;
196
+ } else {
197
+ noSWL.push(entry);
198
+ preservados++;
199
+ }
200
+ }
201
+
202
+ if (noSWL.length > 0) {
203
+ settings.hooks[evento] = noSWL;
204
+ } else {
205
+ delete settings.hooks[evento];
206
+ }
207
+ }
208
+
209
+ // Limpiar sección hooks si quedó vacía
210
+ if (Object.keys(settings.hooks).length === 0) {
211
+ delete settings.hooks;
212
+ }
213
+
214
+ escribirSettings(settingsPath, settings);
215
+
216
+ return { eliminados, preservados };
217
+ }
218
+
219
+ /**
220
+ * Verifica que los hooks SWL estén correctamente registrados en settings.json.
221
+ *
222
+ * @param {string} settingsPath - Ruta al settings.json
223
+ * @param {string[]} hookFiles - Lista de archivos de hooks que deberían estar registrados
224
+ * @returns {{ ok: boolean, registrados: string[], faltantes: string[], incorrectos: string[] }}
225
+ */
226
+ function verificarHooks(settingsPath, hookFiles) {
227
+ const hooksConfig = cargarHooksConfig();
228
+ const settings = leerSettings(settingsPath);
229
+
230
+ const registrados = [];
231
+ const faltantes = [];
232
+ const incorrectos = [];
233
+
234
+ for (const filename of hookFiles) {
235
+ const config = hooksConfig[filename];
236
+ if (!config) {
237
+ faltantes.push(filename);
238
+ continue;
239
+ }
240
+
241
+ const evento = config.event;
242
+ const entradas = (settings.hooks && settings.hooks[evento]) || [];
243
+
244
+ // Buscar si existe un hook SWL que referencie este archivo
245
+ const encontrado = entradas.find(entry => {
246
+ const cmd = entry.command || '';
247
+ return cmd.includes(filename);
248
+ });
249
+
250
+ if (!encontrado) {
251
+ faltantes.push(filename);
252
+ continue;
253
+ }
254
+
255
+ // Verificar matcher correcto
256
+ const matcherEsperado = config.matcher || undefined;
257
+ const matcherActual = encontrado.matcher || undefined;
258
+
259
+ if (matcherEsperado !== matcherActual) {
260
+ incorrectos.push(`${filename}: matcher esperado="${matcherEsperado || '(ninguno)'}" actual="${matcherActual || '(ninguno)'}"`);
261
+ } else {
262
+ registrados.push(filename);
263
+ }
264
+ }
265
+
266
+ return {
267
+ ok: faltantes.length === 0 && incorrectos.length === 0,
268
+ registrados,
269
+ faltantes,
270
+ incorrectos,
271
+ };
272
+ }
273
+
274
+ module.exports = {
275
+ cargarHooksConfig,
276
+ leerSettings,
277
+ escribirSettings,
278
+ rutaSettings,
279
+ registrarHooks,
280
+ desregistrarHooks,
281
+ verificarHooks,
282
+ esHookSWL,
283
+ };
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Carga y resuelve manifiestos de instalación SWL.
5
+ * Tres niveles: perfiles → módulos → archivos.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const RAIZ_PKG = path.resolve(__dirname, '..', '..');
12
+
13
+ /**
14
+ * Carga todos los manifiestos
15
+ */
16
+ function cargarManifiestos() {
17
+ const perfiles = JSON.parse(
18
+ fs.readFileSync(path.join(RAIZ_PKG, 'manifiestos', 'perfiles.json'), 'utf-8')
19
+ );
20
+ const modulos = JSON.parse(
21
+ fs.readFileSync(path.join(RAIZ_PKG, 'manifiestos', 'modulos.json'), 'utf-8')
22
+ );
23
+ return { perfiles: perfiles.perfiles, modulos: modulos.modulos };
24
+ }
25
+
26
+ /**
27
+ * Resuelve un perfil a lista de archivos con rutas absolutas
28
+ */
29
+ function resolverPerfil(nombrePerfil, opciones = {}) {
30
+ const { perfiles, modulos } = cargarManifiestos();
31
+
32
+ const perfil = perfiles[nombrePerfil];
33
+ if (!perfil) {
34
+ throw new Error(
35
+ `Perfil desconocido: "${nombrePerfil}". Disponibles: ${Object.keys(perfiles).join(', ')}`
36
+ );
37
+ }
38
+
39
+ let modulosSeleccionados = [...perfil.modulos];
40
+
41
+ // Agregar módulos con --with
42
+ if (opciones.with) {
43
+ const extras = opciones.with.split(',').map(s => s.trim());
44
+ for (const extra of extras) {
45
+ if (!modulosSeleccionados.includes(extra)) {
46
+ modulosSeleccionados.push(extra);
47
+ }
48
+ }
49
+ }
50
+
51
+ // Excluir módulos con --without
52
+ if (opciones.without) {
53
+ const excluir = opciones.without.split(',').map(s => s.trim());
54
+ modulosSeleccionados = modulosSeleccionados.filter(m => !excluir.includes(m));
55
+ }
56
+
57
+ // Resolver a archivos
58
+ const archivos = [];
59
+ const modulosResueltos = [];
60
+ const warnings = [];
61
+
62
+ for (const nombreModulo of modulosSeleccionados) {
63
+ const modulo = modulos[nombreModulo];
64
+ if (!modulo) {
65
+ warnings.push(`Módulo desconocido ignorado: "${nombreModulo}"`);
66
+ continue;
67
+ }
68
+
69
+ // Verificar soporte de target
70
+ if (opciones.target && !modulo.targets.includes(opciones.target)) {
71
+ warnings.push(`Módulo "${nombreModulo}" no soporta target "${opciones.target}", omitido`);
72
+ continue;
73
+ }
74
+
75
+ modulosResueltos.push(nombreModulo);
76
+
77
+ for (const rutaRelativa of modulo.archivos) {
78
+ const rutaAbsoluta = path.join(RAIZ_PKG, rutaRelativa);
79
+ archivos.push({
80
+ origen: rutaAbsoluta,
81
+ rutaRelativa,
82
+ tipo: modulo.tipo,
83
+ modulo: nombreModulo,
84
+ });
85
+ }
86
+ }
87
+
88
+ return {
89
+ perfil: nombrePerfil,
90
+ descripcion: perfil.descripcion,
91
+ modulos: modulosResueltos,
92
+ archivos,
93
+ warnings,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Genera un plan de instalación legible para el usuario
99
+ */
100
+ function generarPlan(resolucion, rutasDestino) {
101
+ const lineas = [];
102
+ lineas.push(`Plan de instalación SWL`);
103
+ lineas.push(`${'='.repeat(50)}`);
104
+ lineas.push(`Perfil: ${resolucion.perfil} — ${resolucion.descripcion}`);
105
+ lineas.push(`Módulos: ${resolucion.modulos.length}`);
106
+ lineas.push(`Archivos: ${resolucion.archivos.length}`);
107
+ lineas.push('');
108
+
109
+ // Agrupar por tipo
110
+ const porTipo = {};
111
+ for (const archivo of resolucion.archivos) {
112
+ if (!porTipo[archivo.tipo]) porTipo[archivo.tipo] = [];
113
+ porTipo[archivo.tipo].push(archivo);
114
+ }
115
+
116
+ for (const [tipo, archivosDelTipo] of Object.entries(porTipo)) {
117
+ lineas.push(`[${tipo}] (${archivosDelTipo.length} archivos)`);
118
+ for (const a of archivosDelTipo) {
119
+ lineas.push(` + ${a.rutaRelativa}`);
120
+ }
121
+ lineas.push('');
122
+ }
123
+
124
+ if (resolucion.warnings.length > 0) {
125
+ lineas.push('Advertencias:');
126
+ for (const w of resolucion.warnings) {
127
+ lineas.push(` ! ${w}`);
128
+ }
129
+ }
130
+
131
+ return lineas.join('\n');
132
+ }
133
+
134
+ module.exports = {
135
+ cargarManifiestos,
136
+ resolverPerfil,
137
+ generarPlan,
138
+ };