@saulwade/swl-ses 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +225 -209
- package/README.md +578 -561
- package/agentes/arquitecto-swl.md +33 -1
- package/agentes/nemesis-auditor-swl.md +59 -19
- package/bin/swl-mcp-server.js +214 -214
- package/bin/swl-ses.js +49 -7
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +230 -56
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/SKILL.md +207 -4
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
- package/habilidades/node-experto/SKILL.md +94 -4
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/tdd-workflow/SKILL.md +121 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/check-update.js +31 -3
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/extraccion-aprendizajes.js +11 -0
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1324 -1321
- package/manifiestos/skills-lock.json +1142 -1114
- package/package.json +5 -4
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +355 -351
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/registro-componentes-nuevos.md +192 -0
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/actualizar.js +110 -1
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -489
- package/scripts/desinstalar.js +105 -24
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +27 -0
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/instalador.js +55 -4
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/mcp_config.py +127 -0
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +3 -0
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/codex.js +375 -375
- package/scripts/lib/transformadores/cursor.js +359 -359
- package/scripts/lib/ui.js +148 -22
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-orchestrator.py +8 -18
- package/scripts/mcp-pool-manager.py +12 -23
- package/scripts/mcp-server/README.md +170 -170
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/mcp-server/telemetry.js +78 -78
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/tui/componentes/selector-multi.js +189 -0
- package/scripts/tui/componentes/selector-unico.js +158 -0
- package/scripts/tui/ejecutores.js +375 -0
- package/scripts/tui/index.js +162 -0
- package/scripts/tui/lib/colores.js +129 -0
- package/scripts/tui/lib/render.js +264 -0
- package/scripts/tui/lib/teclas.js +113 -0
- package/scripts/tui/pantallas/inspect.js +173 -0
- package/scripts/tui/pantallas/install-wizard.js +334 -0
- package/scripts/tui/pantallas/menu-principal.js +52 -0
- package/scripts/tui/pantallas/progreso.js +274 -0
- package/scripts/tui/pantallas/resumen.js +132 -0
- package/scripts/tui/pantallas/uninstall-wizard.js +208 -0
- package/scripts/tui/pantallas/update-wizard.js +232 -0
- package/scripts/tui/pantallas/welcome.js +187 -0
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/verificar-docs-vs-codigo.js +425 -0
package/scripts/lib/ui.js
CHANGED
|
@@ -82,6 +82,25 @@ function encabezado(titulo, version) {
|
|
|
82
82
|
// Spinner
|
|
83
83
|
// ---------------------------------------------------------------------------
|
|
84
84
|
|
|
85
|
+
// Registro global de spinners activos. Permite que las funciones interactivas
|
|
86
|
+
// (preguntarSiNo, preguntarOpcion, preguntarTexto) pausen cualquier spinner en
|
|
87
|
+
// curso antes de crear un readline, evitando que el setInterval del spinner
|
|
88
|
+
// sobreescriba el prompt con `\r`. Origen: bug observado en `swl-ses update`
|
|
89
|
+
// cuando el instalador hace preguntarSiNo dentro del spinner del actualizador.
|
|
90
|
+
const _spinnersActivos = new Set();
|
|
91
|
+
|
|
92
|
+
function _pausarSpinnersActivos() {
|
|
93
|
+
for (const sp of _spinnersActivos) {
|
|
94
|
+
sp._pausar();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _reanudarSpinnersActivos() {
|
|
99
|
+
for (const sp of _spinnersActivos) {
|
|
100
|
+
sp._reanudar();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
85
104
|
function spinner(mensaje) {
|
|
86
105
|
if (!SOPORTA_COLOR) {
|
|
87
106
|
// Sin TTY: solo mostrar mensaje estático
|
|
@@ -91,49 +110,96 @@ function spinner(mensaje) {
|
|
|
91
110
|
exito: (msg) => { console.log(` [OK] ${msg}`); },
|
|
92
111
|
fallo: (msg) => { console.log(` [ERROR] ${msg}`); },
|
|
93
112
|
detener: () => {},
|
|
113
|
+
_pausar: () => {},
|
|
114
|
+
_reanudar: () => {},
|
|
94
115
|
};
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
let frameIdx = 0;
|
|
98
119
|
let textoActual = mensaje;
|
|
99
120
|
let activo = true;
|
|
121
|
+
let pausado = false;
|
|
122
|
+
let intervalo = null;
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
if (!activo) return;
|
|
124
|
+
function _tick() {
|
|
125
|
+
if (!activo || pausado) return;
|
|
103
126
|
const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
|
104
127
|
process.stdout.write(`\r ${color.cyan(frame)} ${textoActual} `);
|
|
105
128
|
frameIdx++;
|
|
106
|
-
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _arrancarIntervalo() {
|
|
132
|
+
if (intervalo) return;
|
|
133
|
+
intervalo = setInterval(_tick, 80);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _detenerIntervalo() {
|
|
137
|
+
if (intervalo) {
|
|
138
|
+
clearInterval(intervalo);
|
|
139
|
+
intervalo = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
107
142
|
|
|
108
143
|
function limpiarLinea() {
|
|
109
144
|
process.stdout.write('\r\x1b[2K');
|
|
110
145
|
}
|
|
111
146
|
|
|
147
|
+
_arrancarIntervalo();
|
|
148
|
+
|
|
149
|
+
// Declarado antes del handle para que las funciones de terminación puedan
|
|
150
|
+
// removerlo. Origen: F-1 nemesis iter-1 — sin remove, cada spinner deja un
|
|
151
|
+
// listener muerto en `process` y un loop con N runtimes acumula warnings
|
|
152
|
+
// de MaxListenersExceeded.
|
|
153
|
+
const exitHandler = () => { handle.detener(); };
|
|
154
|
+
|
|
112
155
|
const handle = {
|
|
113
156
|
actualizar(msg) {
|
|
114
157
|
textoActual = msg;
|
|
115
158
|
},
|
|
116
159
|
exito(msg) {
|
|
117
160
|
activo = false;
|
|
118
|
-
|
|
161
|
+
_detenerIntervalo();
|
|
162
|
+
_spinnersActivos.delete(handle);
|
|
163
|
+
process.removeListener('exit', exitHandler);
|
|
119
164
|
limpiarLinea();
|
|
120
165
|
console.log(` ${color.verde(icono.check)} ${msg}`);
|
|
121
166
|
},
|
|
122
167
|
fallo(msg) {
|
|
123
168
|
activo = false;
|
|
124
|
-
|
|
169
|
+
_detenerIntervalo();
|
|
170
|
+
_spinnersActivos.delete(handle);
|
|
171
|
+
process.removeListener('exit', exitHandler);
|
|
125
172
|
limpiarLinea();
|
|
126
173
|
console.log(` ${color.rojo(icono.cross)} ${msg}`);
|
|
127
174
|
},
|
|
128
175
|
detener() {
|
|
129
176
|
activo = false;
|
|
130
|
-
|
|
177
|
+
_detenerIntervalo();
|
|
178
|
+
_spinnersActivos.delete(handle);
|
|
179
|
+
process.removeListener('exit', exitHandler);
|
|
180
|
+
limpiarLinea();
|
|
181
|
+
},
|
|
182
|
+
_pausar() {
|
|
183
|
+
// Pausa el tick para que un readline pueda renderizar el prompt sin
|
|
184
|
+
// que el spinner lo sobreescriba con `\r`. Limpia la línea para que
|
|
185
|
+
// el prompt empiece en una columna libre.
|
|
186
|
+
if (pausado) return;
|
|
187
|
+
pausado = true;
|
|
188
|
+
_detenerIntervalo();
|
|
131
189
|
limpiarLinea();
|
|
132
190
|
},
|
|
191
|
+
_reanudar() {
|
|
192
|
+
// Reanuda el tick tras el cierre del readline. Solo si el spinner sigue
|
|
193
|
+
// lógicamente activo (no fue detenido durante la pausa).
|
|
194
|
+
if (!pausado || !activo) return;
|
|
195
|
+
pausado = false;
|
|
196
|
+
_arrancarIntervalo();
|
|
197
|
+
},
|
|
133
198
|
};
|
|
134
199
|
|
|
135
|
-
|
|
136
|
-
|
|
200
|
+
_spinnersActivos.add(handle);
|
|
201
|
+
|
|
202
|
+
// Limpieza si el proceso muere antes de exito/fallo/detener
|
|
137
203
|
process.once('exit', exitHandler);
|
|
138
204
|
|
|
139
205
|
return handle;
|
|
@@ -151,19 +217,36 @@ function preguntarSiNo(mensaje, valorDefault = true) {
|
|
|
151
217
|
}
|
|
152
218
|
|
|
153
219
|
const hint = valorDefault ? 'S/n' : 's/N';
|
|
220
|
+
_pausarSpinnersActivos();
|
|
154
221
|
const rl = readline.createInterface({
|
|
155
222
|
input: process.stdin,
|
|
156
223
|
output: process.stdout,
|
|
157
224
|
});
|
|
158
225
|
|
|
226
|
+
// El evento 'close' es la única ruta de finalización. Se garantiza que
|
|
227
|
+
// se dispare incluso si el callback de question no se ejecuta (Ctrl+C,
|
|
228
|
+
// EOF, error en stdin). Esto evita el bug donde los spinners quedaban
|
|
229
|
+
// permanentemente pausados tras un cierre prematuro del readline.
|
|
230
|
+
let valorFinal = valorDefault;
|
|
231
|
+
let resuelto = false;
|
|
232
|
+
|
|
233
|
+
function finalizar() {
|
|
234
|
+
if (resuelto) return;
|
|
235
|
+
resuelto = true;
|
|
236
|
+
_reanudarSpinnersActivos();
|
|
237
|
+
resolve(valorFinal);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
rl.on('close', finalizar);
|
|
241
|
+
|
|
159
242
|
rl.question(` ${mensaje} [${hint}] `, (respuesta) => {
|
|
160
|
-
rl.close();
|
|
161
243
|
const r = respuesta.trim().toLowerCase();
|
|
162
244
|
if (r === '') {
|
|
163
|
-
|
|
245
|
+
valorFinal = valorDefault;
|
|
164
246
|
} else {
|
|
165
|
-
|
|
247
|
+
valorFinal = r === 's' || r === 'si' || r === 'sí' || r === 'y' || r === 'yes';
|
|
166
248
|
}
|
|
249
|
+
rl.close(); // dispara 'close' → finalizar(); idempotente vía `resuelto`
|
|
167
250
|
});
|
|
168
251
|
});
|
|
169
252
|
}
|
|
@@ -185,6 +268,11 @@ function preguntarOpcion(titulo, opciones, opts = {}) {
|
|
|
185
268
|
return;
|
|
186
269
|
}
|
|
187
270
|
|
|
271
|
+
// Pausar antes de emitir el menú con console.log — si hay spinner activo,
|
|
272
|
+
// un tick a 80ms puede sobreescribir la primera línea del menú. Origen:
|
|
273
|
+
// F-2 nemesis iter-1.
|
|
274
|
+
_pausarSpinnersActivos();
|
|
275
|
+
|
|
188
276
|
console.log('');
|
|
189
277
|
console.log(` ${titulo}`);
|
|
190
278
|
console.log('');
|
|
@@ -197,21 +285,36 @@ function preguntarOpcion(titulo, opciones, opts = {}) {
|
|
|
197
285
|
|
|
198
286
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
199
287
|
const hint = `[1-${opciones.length}, Enter=${opciones[indiceDefault].valor}]`;
|
|
288
|
+
|
|
289
|
+
// Ver preguntarSiNo: 'close' garantiza reanudación incluso si el callback
|
|
290
|
+
// de question no se invoca (Ctrl+C, EOF, error).
|
|
291
|
+
let valorFinal = opciones[indiceDefault].valor;
|
|
292
|
+
let resuelto = false;
|
|
293
|
+
|
|
294
|
+
function finalizar() {
|
|
295
|
+
if (resuelto) return;
|
|
296
|
+
resuelto = true;
|
|
297
|
+
_reanudarSpinnersActivos();
|
|
298
|
+
resolve(valorFinal);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
rl.on('close', finalizar);
|
|
302
|
+
|
|
200
303
|
rl.question(` Tu elección ${hint}: `, (respuesta) => {
|
|
201
|
-
rl.close();
|
|
202
304
|
const r = respuesta.trim();
|
|
203
305
|
if (r === '') {
|
|
204
|
-
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const n = parseInt(r, 10);
|
|
208
|
-
if (Number.isFinite(n) && n >= 1 && n <= opciones.length) {
|
|
209
|
-
resolve(opciones[n - 1].valor);
|
|
306
|
+
valorFinal = opciones[indiceDefault].valor;
|
|
210
307
|
} else {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
308
|
+
const n = parseInt(r, 10);
|
|
309
|
+
if (Number.isFinite(n) && n >= 1 && n <= opciones.length) {
|
|
310
|
+
valorFinal = opciones[n - 1].valor;
|
|
311
|
+
} else {
|
|
312
|
+
// Permitir también escribir el valor directo si coincide
|
|
313
|
+
const match = opciones.find(o => o.valor === r);
|
|
314
|
+
valorFinal = match ? match.valor : opciones[indiceDefault].valor;
|
|
315
|
+
}
|
|
214
316
|
}
|
|
317
|
+
rl.close();
|
|
215
318
|
});
|
|
216
319
|
});
|
|
217
320
|
}
|
|
@@ -230,10 +333,26 @@ function preguntarTexto(mensaje, valorDefault = '') {
|
|
|
230
333
|
return;
|
|
231
334
|
}
|
|
232
335
|
const hint = valorDefault ? ` [${valorDefault}]` : '';
|
|
336
|
+
_pausarSpinnersActivos();
|
|
233
337
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
338
|
+
|
|
339
|
+
// Ver preguntarSiNo: 'close' garantiza reanudación incluso si el callback
|
|
340
|
+
// de question no se invoca (Ctrl+C, EOF, error).
|
|
341
|
+
let valorFinal = valorDefault;
|
|
342
|
+
let resuelto = false;
|
|
343
|
+
|
|
344
|
+
function finalizar() {
|
|
345
|
+
if (resuelto) return;
|
|
346
|
+
resuelto = true;
|
|
347
|
+
_reanudarSpinnersActivos();
|
|
348
|
+
resolve(valorFinal);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
rl.on('close', finalizar);
|
|
352
|
+
|
|
234
353
|
rl.question(` ${mensaje}${hint}: `, (respuesta) => {
|
|
354
|
+
valorFinal = respuesta.trim() || valorDefault;
|
|
235
355
|
rl.close();
|
|
236
|
-
resolve(respuesta.trim() || valorDefault);
|
|
237
356
|
});
|
|
238
357
|
});
|
|
239
358
|
}
|
|
@@ -245,6 +364,13 @@ function preguntarTexto(mensaje, valorDefault = '') {
|
|
|
245
364
|
module.exports = {
|
|
246
365
|
SOPORTA_COLOR,
|
|
247
366
|
ES_TTY,
|
|
367
|
+
// Exports internos para tests del fix de race spinner/prompt. NO usar en
|
|
368
|
+
// código de producción — la API estable es spinner() + preguntar*.
|
|
369
|
+
__internalForTesting: {
|
|
370
|
+
spinnersActivos: _spinnersActivos,
|
|
371
|
+
pausarSpinnersActivos: _pausarSpinnersActivos,
|
|
372
|
+
reanudarSpinnersActivos: _reanudarSpinnersActivos,
|
|
373
|
+
},
|
|
248
374
|
color,
|
|
249
375
|
icono,
|
|
250
376
|
formatearPaso,
|
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* limpiar-artefactos-python.js — limpia caché Python antes de empaquetar.
|
|
6
|
-
*
|
|
7
|
-
* Se ejecuta como `prepack` antes de `npm pack` y `npm publish` para evitar
|
|
8
|
-
* que artefactos locales (.pyc, __pycache__/) contaminen el tarball publicado.
|
|
9
|
-
*
|
|
10
|
-
* Reglas de seguridad (defensa en profundidad):
|
|
11
|
-
* 1. Aborta si cwd no coincide con la raíz del package.json del repo.
|
|
12
|
-
* 2. Profundidad máxima de recursión: 3 niveles desde la raíz.
|
|
13
|
-
* 3. Allowlist explícita de directorios a EXCLUIR de la búsqueda
|
|
14
|
-
* (node_modules, .git, temp, .planning, respositorios-git, _userland).
|
|
15
|
-
* 4. Solo elimina directorios cuyo nombre coincide exactamente con el
|
|
16
|
-
* conjunto cerrado: __pycache__, .pytest_cache, .mypy_cache, .ruff_cache.
|
|
17
|
-
* 5. Solo elimina archivos sueltos con extensiones .pyc, .pyo.
|
|
18
|
-
* 6. En CI no-interactivo respeta el flag SWL_PREPACK_DRY=1 (no borra,
|
|
19
|
-
* solo lista).
|
|
20
|
-
*
|
|
21
|
-
* Exit codes:
|
|
22
|
-
* 0 — OK (limpieza ejecutada o nada que limpiar)
|
|
23
|
-
* 1 — error de invariante (cwd incorrecto, package.json no encontrado)
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
const fs = require('fs');
|
|
27
|
-
const path = require('path');
|
|
28
|
-
|
|
29
|
-
const { NOMBRES_VALIDOS } = require('./lib/paquetes-conocidos');
|
|
30
|
-
const {
|
|
31
|
-
DIRS_ARTEFACTOS_PYTHON: DIRS_A_LIMPIAR,
|
|
32
|
-
EXTS_ARTEFACTOS_PYTHON: EXTS_A_LIMPIAR,
|
|
33
|
-
} = require('./lib/artefactos-python');
|
|
34
|
-
|
|
35
|
-
const PROFUNDIDAD_MAX = 3;
|
|
36
|
-
const DIRS_EXCLUIDOS = new Set([
|
|
37
|
-
'node_modules', '.git', 'temp', '.planning', 'respositorios-git',
|
|
38
|
-
'_userland', 'tests', '.github',
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
const dryRun = process.env.SWL_PREPACK_DRY === '1';
|
|
42
|
-
|
|
43
|
-
function log(msg) { process.stdout.write(`[prepack] ${msg}\n`); }
|
|
44
|
-
function err(msg) { process.stderr.write(`[prepack] ERROR: ${msg}\n`); }
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Verifica que el cwd actual contiene el package.json del repo swl-ses.
|
|
48
|
-
* Esto evita que el script borre directorios en la máquina del usuario si
|
|
49
|
-
* algún día se invocara desde un cwd incorrecto (ej. dentro de un tarball
|
|
50
|
-
* extraído por npm en .npm/_cacache).
|
|
51
|
-
*/
|
|
52
|
-
function verificarRaiz() {
|
|
53
|
-
const cwd = process.cwd();
|
|
54
|
-
const pkgPath = path.join(cwd, 'package.json');
|
|
55
|
-
if (!fs.existsSync(pkgPath)) {
|
|
56
|
-
err(`no existe package.json en cwd: ${cwd}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
60
|
-
if (!NOMBRES_VALIDOS.includes(pkg.name)) {
|
|
61
|
-
err(`package.json en ${cwd} no corresponde a swl-ses (name: ${pkg.name}). Abortando por seguridad.`);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
return cwd;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Recorre el árbol desde root con profundidad limitada y elimina los
|
|
69
|
-
* artefactos Python detectados. No sigue symlinks.
|
|
70
|
-
*/
|
|
71
|
-
function limpiar(root, profundidad = 0) {
|
|
72
|
-
if (profundidad > PROFUNDIDAD_MAX) return { dirs: 0, files: 0 };
|
|
73
|
-
|
|
74
|
-
let dirsEliminados = 0;
|
|
75
|
-
let filesEliminados = 0;
|
|
76
|
-
|
|
77
|
-
let entries;
|
|
78
|
-
try {
|
|
79
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
80
|
-
} catch (e) {
|
|
81
|
-
err(`no se pudo leer ${root}: ${e.message}`);
|
|
82
|
-
return { dirs: 0, files: 0 };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
for (const entry of entries) {
|
|
86
|
-
if (entry.isSymbolicLink()) continue;
|
|
87
|
-
const fullPath = path.join(root, entry.name);
|
|
88
|
-
|
|
89
|
-
if (entry.isDirectory()) {
|
|
90
|
-
if (DIRS_A_LIMPIAR.has(entry.name)) {
|
|
91
|
-
if (dryRun) {
|
|
92
|
-
log(`[dry-run] borraría dir: ${path.relative(process.cwd(), fullPath)}`);
|
|
93
|
-
} else {
|
|
94
|
-
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
95
|
-
log(`borrado dir: ${path.relative(process.cwd(), fullPath)}`);
|
|
96
|
-
}
|
|
97
|
-
dirsEliminados++;
|
|
98
|
-
} else if (!DIRS_EXCLUIDOS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
99
|
-
const sub = limpiar(fullPath, profundidad + 1);
|
|
100
|
-
dirsEliminados += sub.dirs;
|
|
101
|
-
filesEliminados += sub.files;
|
|
102
|
-
}
|
|
103
|
-
} else if (entry.isFile()) {
|
|
104
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
105
|
-
if (EXTS_A_LIMPIAR.has(ext)) {
|
|
106
|
-
if (dryRun) {
|
|
107
|
-
log(`[dry-run] borraría archivo: ${path.relative(process.cwd(), fullPath)}`);
|
|
108
|
-
} else {
|
|
109
|
-
fs.rmSync(fullPath, { force: true });
|
|
110
|
-
}
|
|
111
|
-
filesEliminados++;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return { dirs: dirsEliminados, files: filesEliminados };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function main() {
|
|
120
|
-
const root = verificarRaiz();
|
|
121
|
-
const result = limpiar(root);
|
|
122
|
-
|
|
123
|
-
if (result.dirs === 0 && result.files === 0) {
|
|
124
|
-
log('sin artefactos Python que limpiar.');
|
|
125
|
-
} else {
|
|
126
|
-
const accion = dryRun ? 'detectados' : 'eliminados';
|
|
127
|
-
log(`${accion}: ${result.dirs} directorios + ${result.files} archivos.`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* limpiar-artefactos-python.js — limpia caché Python antes de empaquetar.
|
|
6
|
+
*
|
|
7
|
+
* Se ejecuta como `prepack` antes de `npm pack` y `npm publish` para evitar
|
|
8
|
+
* que artefactos locales (.pyc, __pycache__/) contaminen el tarball publicado.
|
|
9
|
+
*
|
|
10
|
+
* Reglas de seguridad (defensa en profundidad):
|
|
11
|
+
* 1. Aborta si cwd no coincide con la raíz del package.json del repo.
|
|
12
|
+
* 2. Profundidad máxima de recursión: 3 niveles desde la raíz.
|
|
13
|
+
* 3. Allowlist explícita de directorios a EXCLUIR de la búsqueda
|
|
14
|
+
* (node_modules, .git, temp, .planning, respositorios-git, _userland).
|
|
15
|
+
* 4. Solo elimina directorios cuyo nombre coincide exactamente con el
|
|
16
|
+
* conjunto cerrado: __pycache__, .pytest_cache, .mypy_cache, .ruff_cache.
|
|
17
|
+
* 5. Solo elimina archivos sueltos con extensiones .pyc, .pyo.
|
|
18
|
+
* 6. En CI no-interactivo respeta el flag SWL_PREPACK_DRY=1 (no borra,
|
|
19
|
+
* solo lista).
|
|
20
|
+
*
|
|
21
|
+
* Exit codes:
|
|
22
|
+
* 0 — OK (limpieza ejecutada o nada que limpiar)
|
|
23
|
+
* 1 — error de invariante (cwd incorrecto, package.json no encontrado)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const { NOMBRES_VALIDOS } = require('./lib/paquetes-conocidos');
|
|
30
|
+
const {
|
|
31
|
+
DIRS_ARTEFACTOS_PYTHON: DIRS_A_LIMPIAR,
|
|
32
|
+
EXTS_ARTEFACTOS_PYTHON: EXTS_A_LIMPIAR,
|
|
33
|
+
} = require('./lib/artefactos-python');
|
|
34
|
+
|
|
35
|
+
const PROFUNDIDAD_MAX = 3;
|
|
36
|
+
const DIRS_EXCLUIDOS = new Set([
|
|
37
|
+
'node_modules', '.git', 'temp', '.planning', 'respositorios-git',
|
|
38
|
+
'_userland', 'tests', '.github',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const dryRun = process.env.SWL_PREPACK_DRY === '1';
|
|
42
|
+
|
|
43
|
+
function log(msg) { process.stdout.write(`[prepack] ${msg}\n`); }
|
|
44
|
+
function err(msg) { process.stderr.write(`[prepack] ERROR: ${msg}\n`); }
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Verifica que el cwd actual contiene el package.json del repo swl-ses.
|
|
48
|
+
* Esto evita que el script borre directorios en la máquina del usuario si
|
|
49
|
+
* algún día se invocara desde un cwd incorrecto (ej. dentro de un tarball
|
|
50
|
+
* extraído por npm en .npm/_cacache).
|
|
51
|
+
*/
|
|
52
|
+
function verificarRaiz() {
|
|
53
|
+
const cwd = process.cwd();
|
|
54
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
55
|
+
if (!fs.existsSync(pkgPath)) {
|
|
56
|
+
err(`no existe package.json en cwd: ${cwd}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
60
|
+
if (!NOMBRES_VALIDOS.includes(pkg.name)) {
|
|
61
|
+
err(`package.json en ${cwd} no corresponde a swl-ses (name: ${pkg.name}). Abortando por seguridad.`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return cwd;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Recorre el árbol desde root con profundidad limitada y elimina los
|
|
69
|
+
* artefactos Python detectados. No sigue symlinks.
|
|
70
|
+
*/
|
|
71
|
+
function limpiar(root, profundidad = 0) {
|
|
72
|
+
if (profundidad > PROFUNDIDAD_MAX) return { dirs: 0, files: 0 };
|
|
73
|
+
|
|
74
|
+
let dirsEliminados = 0;
|
|
75
|
+
let filesEliminados = 0;
|
|
76
|
+
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
80
|
+
} catch (e) {
|
|
81
|
+
err(`no se pudo leer ${root}: ${e.message}`);
|
|
82
|
+
return { dirs: 0, files: 0 };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.isSymbolicLink()) continue;
|
|
87
|
+
const fullPath = path.join(root, entry.name);
|
|
88
|
+
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
if (DIRS_A_LIMPIAR.has(entry.name)) {
|
|
91
|
+
if (dryRun) {
|
|
92
|
+
log(`[dry-run] borraría dir: ${path.relative(process.cwd(), fullPath)}`);
|
|
93
|
+
} else {
|
|
94
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
95
|
+
log(`borrado dir: ${path.relative(process.cwd(), fullPath)}`);
|
|
96
|
+
}
|
|
97
|
+
dirsEliminados++;
|
|
98
|
+
} else if (!DIRS_EXCLUIDOS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
99
|
+
const sub = limpiar(fullPath, profundidad + 1);
|
|
100
|
+
dirsEliminados += sub.dirs;
|
|
101
|
+
filesEliminados += sub.files;
|
|
102
|
+
}
|
|
103
|
+
} else if (entry.isFile()) {
|
|
104
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
105
|
+
if (EXTS_A_LIMPIAR.has(ext)) {
|
|
106
|
+
if (dryRun) {
|
|
107
|
+
log(`[dry-run] borraría archivo: ${path.relative(process.cwd(), fullPath)}`);
|
|
108
|
+
} else {
|
|
109
|
+
fs.rmSync(fullPath, { force: true });
|
|
110
|
+
}
|
|
111
|
+
filesEliminados++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { dirs: dirsEliminados, files: filesEliminados };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function main() {
|
|
120
|
+
const root = verificarRaiz();
|
|
121
|
+
const result = limpiar(root);
|
|
122
|
+
|
|
123
|
+
if (result.dirs === 0 && result.files === 0) {
|
|
124
|
+
log('sin artefactos Python que limpiar.');
|
|
125
|
+
} else {
|
|
126
|
+
const accion = dryRun ? 'detectados' : 'eliminados';
|
|
127
|
+
log(`${accion}: ${result.dirs} directorios + ${result.files} archivos.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main();
|
|
@@ -30,6 +30,10 @@ from datetime import datetime, timezone
|
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
from typing import Any
|
|
32
32
|
|
|
33
|
+
# Importar helper compartido sin convertir scripts/ en paquete.
|
|
34
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent / 'lib'))
|
|
35
|
+
from mcp_config import cargar_config_mcp # noqa: E402
|
|
36
|
+
|
|
33
37
|
# ---------------------------------------------------------------------------
|
|
34
38
|
# Dependencias — raw mcp SDK
|
|
35
39
|
# ---------------------------------------------------------------------------
|
|
@@ -45,32 +49,18 @@ except ImportError:
|
|
|
45
49
|
# Constantes
|
|
46
50
|
# ---------------------------------------------------------------------------
|
|
47
51
|
|
|
48
|
-
SETTINGS_CANDIDATES = [
|
|
49
|
-
'.claude/settings.local.json',
|
|
50
|
-
'.claude/settings.json',
|
|
51
|
-
'mcp-servers.json',
|
|
52
|
-
]
|
|
53
|
-
|
|
54
52
|
TIMEOUT_S = 12
|
|
55
53
|
SNAPSHOT_FILE = Path('.planning') / 'mcp-snapshot.json'
|
|
56
54
|
|
|
57
55
|
# ---------------------------------------------------------------------------
|
|
58
|
-
# Carga de config
|
|
56
|
+
# Carga de config — delega a scripts/lib/mcp_config.py para deep merge
|
|
57
|
+
# unificado con mcp-pool-manager.py. Antes hacia first-wins y rompia el
|
|
58
|
+
# health check cuando settings.local.json contenia overrides parciales.
|
|
59
59
|
# ---------------------------------------------------------------------------
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def _cargar_config(cwd: Path, config_path: str | None = None) -> dict:
|
|
63
|
-
|
|
64
|
-
for p in candidates:
|
|
65
|
-
if p.exists():
|
|
66
|
-
try:
|
|
67
|
-
data = json.loads(p.read_text(encoding='utf-8'))
|
|
68
|
-
servers = data.get('mcpServers', {})
|
|
69
|
-
if servers:
|
|
70
|
-
return servers
|
|
71
|
-
except Exception:
|
|
72
|
-
continue
|
|
73
|
-
return {}
|
|
63
|
+
return cargar_config_mcp(cwd, config_path)
|
|
74
64
|
|
|
75
65
|
|
|
76
66
|
def _build_env(cfg: dict) -> dict | None:
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
Conecta a servidores MCP configurados en .claude/settings.json y expone
|
|
5
5
|
sus herramientas para uso por agentes y scripts de swl-ses.
|
|
6
6
|
|
|
7
|
-
Lee la configuracion
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Lee la configuracion fusionando las capas con deep merge (semantica de
|
|
8
|
+
Claude Code): mcp-servers.json -> .claude/settings.json -> .claude/settings.local.json.
|
|
9
|
+
La capa mas profunda sobrescribe por clave; env se mergea por sub-clave.
|
|
10
|
+
Esto permite que settings.local.json contenga overrides parciales (p.ej.
|
|
11
|
+
solo OBSIDIAN_API_KEY) sin perder command/args de la capa compartida.
|
|
11
12
|
|
|
12
13
|
Uso:
|
|
13
14
|
python scripts/mcp-pool-manager.py list-servers
|
|
@@ -30,6 +31,10 @@ import time
|
|
|
30
31
|
from pathlib import Path
|
|
31
32
|
from typing import Any
|
|
32
33
|
|
|
34
|
+
# Importar helper compartido sin convertir scripts/ en paquete.
|
|
35
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent / 'lib'))
|
|
36
|
+
from mcp_config import cargar_config_mcp # noqa: E402
|
|
37
|
+
|
|
33
38
|
# ---------------------------------------------------------------------------
|
|
34
39
|
# Dependencias — raw mcp SDK (disponible en entornos con Claude Code)
|
|
35
40
|
# ---------------------------------------------------------------------------
|
|
@@ -45,33 +50,17 @@ except ImportError:
|
|
|
45
50
|
# Constantes
|
|
46
51
|
# ---------------------------------------------------------------------------
|
|
47
52
|
|
|
48
|
-
SETTINGS_CANDIDATES = [
|
|
49
|
-
'.claude/settings.local.json',
|
|
50
|
-
'.claude/settings.json',
|
|
51
|
-
'mcp-servers.json',
|
|
52
|
-
]
|
|
53
|
-
|
|
54
53
|
TIMEOUT_CONNECT_S = 10 # segundos maximo para conectar a un servidor
|
|
55
54
|
TIMEOUT_CALL_S = 30 # segundos maximo para llamar una herramienta
|
|
56
55
|
|
|
57
56
|
# ---------------------------------------------------------------------------
|
|
58
|
-
# Carga de configuracion
|
|
57
|
+
# Carga de configuracion — delega a scripts/lib/mcp_config.py
|
|
59
58
|
# ---------------------------------------------------------------------------
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
def _cargar_config(cwd: Path, config_path: str | None = None) -> dict:
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
for p in candidates:
|
|
66
|
-
if p.exists():
|
|
67
|
-
try:
|
|
68
|
-
data = json.loads(p.read_text(encoding='utf-8'))
|
|
69
|
-
servers = data.get('mcpServers', {})
|
|
70
|
-
if servers:
|
|
71
|
-
return servers
|
|
72
|
-
except Exception:
|
|
73
|
-
continue
|
|
74
|
-
return {}
|
|
62
|
+
"""Devuelve los servidores MCP fusionados de todas las capas."""
|
|
63
|
+
return cargar_config_mcp(cwd, config_path)
|
|
75
64
|
|
|
76
65
|
|
|
77
66
|
def _get_server(servers: dict, nombre: str) -> dict:
|