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.
- package/CLAUDE.md +425 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/accesibilidad-wcag-swl.md +683 -0
- package/agentes/arquitecto-swl.md +210 -0
- package/agentes/auto-evolucion-swl.md +408 -0
- package/agentes/backend-api-swl.md +442 -0
- package/agentes/backend-node-swl.md +439 -0
- package/agentes/backend-python-swl.md +469 -0
- package/agentes/backend-workers-swl.md +444 -0
- package/agentes/cloud-infra-swl.md +466 -0
- package/agentes/consolidador-swl.md +487 -0
- package/agentes/datos-swl.md +568 -0
- package/agentes/depurador-swl.md +301 -0
- package/agentes/devops-ci-swl.md +352 -0
- package/agentes/disenador-ui-swl.md +546 -0
- package/agentes/documentador-swl.md +323 -0
- package/agentes/frontend-angular-swl.md +603 -0
- package/agentes/frontend-css-swl.md +700 -0
- package/agentes/frontend-react-swl.md +672 -0
- package/agentes/frontend-swl.md +483 -0
- package/agentes/frontend-tailwind-swl.md +808 -0
- package/agentes/implementador-swl.md +235 -0
- package/agentes/investigador-swl.md +274 -0
- package/agentes/investigador-ux-swl.md +482 -0
- package/agentes/migrador-swl.md +389 -0
- package/agentes/mobile-android-swl.md +473 -0
- package/agentes/mobile-cross-swl.md +501 -0
- package/agentes/mobile-ios-swl.md +464 -0
- package/agentes/notificador-swl.md +886 -0
- package/agentes/observabilidad-swl.md +408 -0
- package/agentes/orquestador-swl.md +490 -0
- package/agentes/planificador-swl.md +222 -0
- package/agentes/producto-prd-swl.md +565 -0
- package/agentes/release-manager-swl.md +545 -0
- package/agentes/rendimiento-swl.md +691 -0
- package/agentes/revisor-codigo-swl.md +254 -0
- package/agentes/revisor-seguridad-swl.md +316 -0
- package/agentes/tdd-qa-swl.md +323 -0
- package/agentes/ux-disenador-swl.md +498 -0
- package/bin/swl-ses.js +119 -0
- package/comandos/swl/actualizar.md +117 -0
- package/comandos/swl/aprender.md +348 -0
- package/comandos/swl/auditar-deps.md +390 -0
- package/comandos/swl/autoresearch.md +346 -0
- package/comandos/swl/checkpoint.md +296 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/crear-skill.md +609 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +302 -0
- package/comandos/swl/evolucionar.md +377 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/mapear-codebase.md +205 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/release.md +405 -0
- package/comandos/swl/salud.md +382 -0
- package/comandos/swl/verificar.md +292 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
- package/habilidades/angular-avanzado/SKILL.md +491 -0
- package/habilidades/angular-moderno/SKILL.md +326 -0
- package/habilidades/api-rest-diseno/SKILL.md +302 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
- package/habilidades/async-python/SKILL.md +474 -0
- package/habilidades/auth-patrones/SKILL.md +488 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
- package/habilidades/autoresearch/SKILL.md +248 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/checklist-calidad/SKILL.md +247 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +224 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/cloud-aws/SKILL.md +497 -0
- package/habilidades/compactacion-contexto/SKILL.md +201 -0
- package/habilidades/contenedores-docker/SKILL.md +453 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/css-moderno/SKILL.md +463 -0
- package/habilidades/datos-etl/SKILL.md +486 -0
- package/habilidades/dependencias-auditoria/SKILL.md +293 -0
- package/habilidades/deprecacion-migracion/SKILL.md +485 -0
- package/habilidades/design-tokens/SKILL.md +519 -0
- package/habilidades/discutir-fase/SKILL.md +167 -0
- package/habilidades/diseno-responsivo/SKILL.md +326 -0
- package/habilidades/django-experto/SKILL.md +395 -0
- package/habilidades/doc-sync/SKILL.md +259 -0
- package/habilidades/ejecutar-fase/SKILL.md +199 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/event-driven/SKILL.md +580 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
- package/habilidades/fastapi-experto/SKILL.md +368 -0
- package/habilidades/frontend-avanzado/SKILL.md +555 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
- package/habilidades/iam-secretos/SKILL.md +511 -0
- package/habilidades/instalar-sistema/SKILL.md +140 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
- package/habilidades/manejo-errores/SKILL.md +512 -0
- package/habilidades/mapear-codebase/SKILL.md +199 -0
- package/habilidades/microservicios/SKILL.md +473 -0
- package/habilidades/mobile-flutter/SKILL.md +566 -0
- package/habilidades/mobile-react-native/SKILL.md +493 -0
- package/habilidades/monitoring-alertas/SKILL.md +447 -0
- package/habilidades/node-experto/SKILL.md +521 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/nuevo-proyecto/SKILL.md +183 -0
- package/habilidades/patrones-python/SKILL.md +381 -0
- package/habilidades/performance-baseline/SKILL.md +243 -0
- package/habilidades/planear-fase/SKILL.md +184 -0
- package/habilidades/postgresql-experto/SKILL.md +379 -0
- package/habilidades/react-experto/SKILL.md +434 -0
- package/habilidades/react-optimizacion/SKILL.md +328 -0
- package/habilidades/release-semver/SKILL.md +226 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/sql-optimizacion/SKILL.md +314 -0
- package/habilidades/tailwind-experto/SKILL.md +412 -0
- package/habilidades/tdd-workflow/SKILL.md +267 -0
- package/habilidades/testing-python/SKILL.md +350 -0
- package/habilidades/threat-model-lite/SKILL.md +218 -0
- package/habilidades/typescript-avanzado/SKILL.md +454 -0
- package/habilidades/ux-diseno/SKILL.md +488 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificar-trabajo/SKILL.md +208 -0
- package/habilidades/wireframes-flujos/SKILL.md +396 -0
- package/habilidades/workflow-claude-code/SKILL.md +359 -0
- package/hooks/calidad-pre-commit.js +578 -0
- package/hooks/escaneo-secretos.js +302 -0
- package/hooks/extraccion-aprendizajes.js +550 -0
- package/hooks/linea-estado.js +249 -0
- package/hooks/monitor-contexto.js +230 -0
- package/hooks/proteccion-rutas.js +249 -0
- package/manifiestos/hooks-config.json +41 -0
- package/manifiestos/modulos.json +318 -0
- package/manifiestos/perfiles.json +189 -0
- package/package.json +45 -0
- package/plantillas/PROJECT.md +122 -0
- package/plantillas/REQUIREMENTS.md +132 -0
- package/plantillas/ROADMAP.md +143 -0
- package/plantillas/STATE.md +109 -0
- package/plantillas/research/ARCHITECTURE.md +220 -0
- package/plantillas/research/FEATURES.md +175 -0
- package/plantillas/research/PITFALLS.md +299 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/SUMMARY.md +165 -0
- package/plugin.json +144 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +183 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +179 -0
- package/reglas/git-workflow.md +186 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +473 -0
- package/scripts/actualizar.js +51 -0
- package/scripts/desinstalar.js +86 -0
- package/scripts/doctor.js +222 -0
- package/scripts/inicializar.js +89 -0
- package/scripts/instalador.js +333 -0
- package/scripts/lib/detectar-runtime.js +177 -0
- package/scripts/lib/estado.js +112 -0
- package/scripts/lib/hooks-settings.js +283 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/publicar.js +209 -0
- 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
|
+
};
|