@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/desinstalar.js
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* swl-ses uninstall
|
|
5
5
|
* Desinstala componentes SWL del runtime especificado.
|
|
6
|
+
*
|
|
7
|
+
* Contrato adicional: opciones.onProgress(evento)
|
|
8
|
+
*
|
|
9
|
+
* Si se pasa una función onProgress, el desinstalador emite eventos
|
|
10
|
+
* estructurados en vez de imprimir cada archivo a stdout:
|
|
11
|
+
* { tipo: 'archivo-eliminado', componente, archivo, destino }
|
|
12
|
+
* { tipo: 'bloque-eliminado', archivo, etiqueta }
|
|
13
|
+
* { tipo: 'archivo-no-encontrado', archivo }
|
|
14
|
+
* { tipo: 'error', archivo, mensaje }
|
|
15
|
+
* { tipo: 'log', linea }
|
|
16
|
+
*
|
|
17
|
+
* Backward compat: si onProgress no se pasa, los console.log son idénticos
|
|
18
|
+
* a antes. El CLI clásico (uninstall sin TUI) no cambia.
|
|
19
|
+
*
|
|
20
|
+
* Devuelve { eliminados, noEncontrados, bloquesEliminados } para que el
|
|
21
|
+
* caller (TUI) pueda mostrar resumen sin re-parsear stdout.
|
|
6
22
|
*/
|
|
7
23
|
|
|
8
24
|
const fs = require('fs');
|
|
@@ -15,23 +31,40 @@ const { eliminarBloque } = require('./lib/append-con-marcadores');
|
|
|
15
31
|
function uninstall(opciones) {
|
|
16
32
|
const target = opciones.target || 'claude';
|
|
17
33
|
const esGlobal = opciones.global || false;
|
|
34
|
+
const onProgress = typeof opciones.onProgress === 'function' ? opciones.onProgress : null;
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
function _emitir(evento, mensajeFallback) {
|
|
37
|
+
if (onProgress) {
|
|
38
|
+
onProgress(evento);
|
|
39
|
+
} else if (mensajeFallback) {
|
|
40
|
+
console.log(mensajeFallback);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!onProgress) {
|
|
45
|
+
console.log('swl-ses uninstall');
|
|
46
|
+
console.log('='.repeat(40));
|
|
47
|
+
}
|
|
21
48
|
|
|
22
49
|
const runtime = obtenerRuntime(target);
|
|
23
50
|
const rutas = calcularRutas(target, { global: esGlobal });
|
|
24
51
|
|
|
25
52
|
const estado = cargarEstado(rutas.base);
|
|
26
53
|
if (!estado) {
|
|
54
|
+
if (onProgress) {
|
|
55
|
+
onProgress({ tipo: 'log', linea: `No se encontró instalación SWL en ${rutas.base}` });
|
|
56
|
+
return { eliminados: 0, noEncontrados: 0, bloquesEliminados: 0, error: 'instalacion-no-encontrada' };
|
|
57
|
+
}
|
|
27
58
|
console.log(`No se encontró instalación SWL en ${rutas.base}`);
|
|
28
59
|
process.exit(1);
|
|
29
60
|
}
|
|
30
61
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
if (!onProgress) {
|
|
63
|
+
console.log(`Target: ${target} (${runtime.nombre})`);
|
|
64
|
+
console.log(`Ubicación: ${rutas.base}`);
|
|
65
|
+
console.log(`Archivos a eliminar: ${estado.archivosInstalados.length}`);
|
|
66
|
+
console.log('');
|
|
67
|
+
}
|
|
35
68
|
|
|
36
69
|
let eliminados = 0;
|
|
37
70
|
let noEncontrados = 0;
|
|
@@ -56,10 +89,17 @@ function uninstall(opciones) {
|
|
|
56
89
|
if (accionInstall === 'creado') {
|
|
57
90
|
try {
|
|
58
91
|
fs.unlinkSync(archivo.destino);
|
|
59
|
-
|
|
92
|
+
_emitir(
|
|
93
|
+
{ tipo: 'archivo-eliminado', componente: archivo.tipo, archivo: archivo.destino, motivo: 'creado-por-swl' },
|
|
94
|
+
` - ${archivo.destino} (creado por swl-ses, eliminación completa)`
|
|
95
|
+
);
|
|
60
96
|
eliminados++;
|
|
61
97
|
} catch (err) {
|
|
62
|
-
|
|
98
|
+
_emitir(
|
|
99
|
+
{ tipo: 'error', archivo: archivo.destino, mensaje: err.message },
|
|
100
|
+
null
|
|
101
|
+
);
|
|
102
|
+
if (!onProgress) console.error(` ! Error eliminando ${archivo.destino}: ${err.message}`);
|
|
63
103
|
}
|
|
64
104
|
} else {
|
|
65
105
|
try {
|
|
@@ -70,12 +110,19 @@ function uninstall(opciones) {
|
|
|
70
110
|
'sin-cambios': 'sin bloque SWL presente (ya limpio)',
|
|
71
111
|
'error': `error: ${r.detalle || 'desconocido'}`,
|
|
72
112
|
}[r.accion] || r.accion;
|
|
73
|
-
|
|
113
|
+
_emitir(
|
|
114
|
+
{ tipo: 'bloque-eliminado', archivo: archivo.destino, etiqueta, accion: r.accion },
|
|
115
|
+
` ~ ${archivo.destino} (${etiqueta})`
|
|
116
|
+
);
|
|
74
117
|
if (r.accion === 'bloque-eliminado' || r.accion === 'archivo-eliminado') {
|
|
75
118
|
bloquesEliminados++;
|
|
76
119
|
}
|
|
77
120
|
} catch (err) {
|
|
78
|
-
|
|
121
|
+
_emitir(
|
|
122
|
+
{ tipo: 'error', archivo: archivo.destino, mensaje: err.message },
|
|
123
|
+
null
|
|
124
|
+
);
|
|
125
|
+
if (!onProgress) console.error(` ! Error limpiando bloque en ${archivo.destino}: ${err.message}`);
|
|
79
126
|
}
|
|
80
127
|
}
|
|
81
128
|
continue;
|
|
@@ -88,26 +135,47 @@ function uninstall(opciones) {
|
|
|
88
135
|
} else {
|
|
89
136
|
fs.unlinkSync(archivo.destino);
|
|
90
137
|
}
|
|
91
|
-
|
|
138
|
+
_emitir(
|
|
139
|
+
{ tipo: 'archivo-eliminado', componente: archivo.tipo, archivo: archivo.destino },
|
|
140
|
+
` - ${archivo.destino}`
|
|
141
|
+
);
|
|
92
142
|
eliminados++;
|
|
93
143
|
} catch (err) {
|
|
94
|
-
|
|
144
|
+
_emitir(
|
|
145
|
+
{ tipo: 'error', archivo: archivo.destino, mensaje: err.message },
|
|
146
|
+
null
|
|
147
|
+
);
|
|
148
|
+
if (!onProgress) console.error(` ! Error eliminando ${archivo.destino}: ${err.message}`);
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
|
|
98
152
|
// Desregistrar hooks de settings.json
|
|
153
|
+
let hooksDesregistrados = 0;
|
|
154
|
+
let hooksPreservados = 0;
|
|
99
155
|
if (runtime.soportaHooks) {
|
|
100
156
|
const settingsPath = rutaSettings(runtime, rutas.base);
|
|
101
157
|
try {
|
|
102
158
|
const resultado = desregistrarHooks(settingsPath);
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
hooksDesregistrados = resultado.eliminados || 0;
|
|
160
|
+
hooksPreservados = resultado.preservados || 0;
|
|
161
|
+
if (hooksDesregistrados > 0) {
|
|
162
|
+
_emitir(
|
|
163
|
+
{ tipo: 'hooks-desregistrados', cuenta: hooksDesregistrados },
|
|
164
|
+
` - Hooks desregistrados de settings.json: ${hooksDesregistrados}`
|
|
165
|
+
);
|
|
105
166
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
167
|
+
if (hooksPreservados > 0) {
|
|
168
|
+
_emitir(
|
|
169
|
+
{ tipo: 'hooks-preservados', cuenta: hooksPreservados },
|
|
170
|
+
` (${hooksPreservados} hooks externos preservados)`
|
|
171
|
+
);
|
|
108
172
|
}
|
|
109
173
|
} catch (err) {
|
|
110
|
-
|
|
174
|
+
_emitir(
|
|
175
|
+
{ tipo: 'error', mensaje: `Error desregistrando hooks: ${err.message}` },
|
|
176
|
+
null
|
|
177
|
+
);
|
|
178
|
+
if (!onProgress) console.error(` ! Error desregistrando hooks: ${err.message}`);
|
|
111
179
|
}
|
|
112
180
|
}
|
|
113
181
|
|
|
@@ -117,14 +185,27 @@ function uninstall(opciones) {
|
|
|
117
185
|
fs.unlinkSync(estadoPath);
|
|
118
186
|
}
|
|
119
187
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
188
|
+
if (!onProgress) {
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log(`Eliminados: ${eliminados}`);
|
|
191
|
+
if (bloquesEliminados > 0) {
|
|
192
|
+
console.log(`Bloques SWL quitados (contenido de usuario preservado): ${bloquesEliminados}`);
|
|
193
|
+
}
|
|
194
|
+
console.log(`No encontrados: ${noEncontrados}`);
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log('Nota: .planning/ y _userland/ no se eliminan (contienen datos del proyecto).');
|
|
124
197
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
eliminados,
|
|
201
|
+
noEncontrados,
|
|
202
|
+
bloquesEliminados,
|
|
203
|
+
hooksDesregistrados,
|
|
204
|
+
hooksPreservados,
|
|
205
|
+
target,
|
|
206
|
+
scope: esGlobal ? 'global' : 'proyecto',
|
|
207
|
+
ubicacion: rutas.base,
|
|
208
|
+
};
|
|
128
209
|
}
|
|
129
210
|
|
|
130
211
|
module.exports = uninstall;
|
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* detectar-aprendizajes-duplicados.js
|
|
6
|
-
*
|
|
7
|
-
* Detecta pares de entradas en `.planning/APRENDIZAJES.md` con alta similitud
|
|
8
|
-
* de tokens (Jaccard > umbral). Útil para identificar candidatos a fusionar
|
|
9
|
-
* cuando el hook de auto-extracción genera entradas redundantes.
|
|
10
|
-
*
|
|
11
|
-
* Patrón adoptado de `temp/agentmemory-main/src/functions/auto-forget.ts`
|
|
12
|
-
* (contradiction detection con Jaccard >= 0.9). Aquí se usa con threshold
|
|
13
|
-
* configurable más bajo (0.6 default) porque queremos sugerir, no auto-borrar.
|
|
14
|
-
*
|
|
15
|
-
* NO modifica APRENDIZAJES.md. Solo reporta. La acción de fusión queda en
|
|
16
|
-
* manos del usuario o de un comando separado (`/swl:aprender consolidar`).
|
|
17
|
-
*
|
|
18
|
-
* Uso:
|
|
19
|
-
* node scripts/detectar-aprendizajes-duplicados.js [threshold]
|
|
20
|
-
*
|
|
21
|
-
* Argumentos:
|
|
22
|
-
* threshold - Similitud mínima para reportar (default: 0.6, rango [0, 1]).
|
|
23
|
-
*
|
|
24
|
-
* Exit codes:
|
|
25
|
-
* 0 - Ejecución OK (haya o no duplicados)
|
|
26
|
-
* 1 - Error de I/O o parseo
|
|
27
|
-
*
|
|
28
|
-
* Output: tabla legible en stdout. Si se detectan ≥ 1 duplicados, también
|
|
29
|
-
* imprime sugerencia para revisar/consolidar.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
const fs = require('fs');
|
|
33
|
-
const path = require('path');
|
|
34
|
-
|
|
35
|
-
const { tokenize, jaccard } = require('./lib/jaccard-similarity');
|
|
36
|
-
|
|
37
|
-
const RUTA_APRENDIZAJES = path.join(process.cwd(), '.planning', 'APRENDIZAJES.md');
|
|
38
|
-
const DEFAULT_THRESHOLD = 0.6;
|
|
39
|
-
const MAX_PARES_REPORTADOS = 30;
|
|
40
|
-
|
|
41
|
-
function parsearEntradas(contenido) {
|
|
42
|
-
const lineas = contenido.split('\n');
|
|
43
|
-
const entradas = [];
|
|
44
|
-
let actual = null;
|
|
45
|
-
|
|
46
|
-
for (let i = 0; i < lineas.length; i++) {
|
|
47
|
-
const linea = lineas[i];
|
|
48
|
-
if (linea.startsWith('## ')) {
|
|
49
|
-
if (actual) entradas.push(actual);
|
|
50
|
-
actual = {
|
|
51
|
-
lineaInicio: i + 1,
|
|
52
|
-
titulo: linea.slice(3).trim(),
|
|
53
|
-
contenido: '',
|
|
54
|
-
};
|
|
55
|
-
} else if (actual) {
|
|
56
|
-
actual.contenido += linea + '\n';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (actual) entradas.push(actual);
|
|
60
|
-
|
|
61
|
-
// Filtrar entradas vacías o triviales (< 50 chars de contenido real)
|
|
62
|
-
return entradas.filter(e => e.contenido.replace(/\s/g, '').length >= 50);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function detectarDuplicados(entradas, threshold) {
|
|
66
|
-
const tokensCache = entradas.map(e => tokenize(e.titulo + ' ' + e.contenido));
|
|
67
|
-
const pares = [];
|
|
68
|
-
|
|
69
|
-
for (let i = 0; i < entradas.length; i++) {
|
|
70
|
-
for (let j = i + 1; j < entradas.length; j++) {
|
|
71
|
-
const sim = jaccard(tokensCache[i], tokensCache[j]);
|
|
72
|
-
if (sim >= threshold) {
|
|
73
|
-
pares.push({
|
|
74
|
-
entradaA: entradas[i],
|
|
75
|
-
entradaB: entradas[j],
|
|
76
|
-
similitud: sim,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
pares.sort((a, b) => b.similitud - a.similitud);
|
|
83
|
-
return pares;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function reportarTexto(pares) {
|
|
87
|
-
if (pares.length === 0) {
|
|
88
|
-
console.log('Sin duplicados detectados sobre el umbral.');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(`Pares con similitud Jaccard ≥ umbral: ${pares.length}`);
|
|
93
|
-
console.log('');
|
|
94
|
-
|
|
95
|
-
const limite = Math.min(pares.length, MAX_PARES_REPORTADOS);
|
|
96
|
-
for (let i = 0; i < limite; i++) {
|
|
97
|
-
const p = pares[i];
|
|
98
|
-
console.log(` [${(p.similitud * 100).toFixed(1)}%] ` +
|
|
99
|
-
`L${p.entradaA.lineaInicio} ↔ L${p.entradaB.lineaInicio}`);
|
|
100
|
-
console.log(' A: ' + p.entradaA.titulo.slice(0, 80));
|
|
101
|
-
console.log(' B: ' + p.entradaB.titulo.slice(0, 80));
|
|
102
|
-
console.log('');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (pares.length > limite) {
|
|
106
|
-
console.log(` ... ${pares.length - limite} pares adicionales no mostrados`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log('Sugerencia: revisa los pares con mayor similitud y considera ' +
|
|
110
|
-
'fusionarlos en una sola entrada con `/swl:aprender consolidar` o manualmente.');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function main() {
|
|
114
|
-
const threshold = parseFloat(process.argv[2]) || DEFAULT_THRESHOLD;
|
|
115
|
-
|
|
116
|
-
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
117
|
-
console.error(`Threshold inválido: ${process.argv[2]}. Usar valor en [0, 1].`);
|
|
118
|
-
process.exit(1);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!fs.existsSync(RUTA_APRENDIZAJES)) {
|
|
122
|
-
console.error(`No existe ${RUTA_APRENDIZAJES}.`);
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let contenido;
|
|
127
|
-
try {
|
|
128
|
-
contenido = fs.readFileSync(RUTA_APRENDIZAJES, 'utf8');
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error(`Error leyendo ${RUTA_APRENDIZAJES}: ${err.message}`);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const entradas = parsearEntradas(contenido);
|
|
135
|
-
console.log(`Entradas encontradas: ${entradas.length}`);
|
|
136
|
-
console.log(`Threshold de similitud: ${threshold}`);
|
|
137
|
-
console.log('');
|
|
138
|
-
|
|
139
|
-
const pares = detectarDuplicados(entradas, threshold);
|
|
140
|
-
reportarTexto(pares);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (require.main === module) {
|
|
144
|
-
main();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
module.exports = {
|
|
148
|
-
parsearEntradas,
|
|
149
|
-
detectarDuplicados,
|
|
150
|
-
reportarTexto,
|
|
151
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* detectar-aprendizajes-duplicados.js
|
|
6
|
+
*
|
|
7
|
+
* Detecta pares de entradas en `.planning/APRENDIZAJES.md` con alta similitud
|
|
8
|
+
* de tokens (Jaccard > umbral). Útil para identificar candidatos a fusionar
|
|
9
|
+
* cuando el hook de auto-extracción genera entradas redundantes.
|
|
10
|
+
*
|
|
11
|
+
* Patrón adoptado de `temp/agentmemory-main/src/functions/auto-forget.ts`
|
|
12
|
+
* (contradiction detection con Jaccard >= 0.9). Aquí se usa con threshold
|
|
13
|
+
* configurable más bajo (0.6 default) porque queremos sugerir, no auto-borrar.
|
|
14
|
+
*
|
|
15
|
+
* NO modifica APRENDIZAJES.md. Solo reporta. La acción de fusión queda en
|
|
16
|
+
* manos del usuario o de un comando separado (`/swl:aprender consolidar`).
|
|
17
|
+
*
|
|
18
|
+
* Uso:
|
|
19
|
+
* node scripts/detectar-aprendizajes-duplicados.js [threshold]
|
|
20
|
+
*
|
|
21
|
+
* Argumentos:
|
|
22
|
+
* threshold - Similitud mínima para reportar (default: 0.6, rango [0, 1]).
|
|
23
|
+
*
|
|
24
|
+
* Exit codes:
|
|
25
|
+
* 0 - Ejecución OK (haya o no duplicados)
|
|
26
|
+
* 1 - Error de I/O o parseo
|
|
27
|
+
*
|
|
28
|
+
* Output: tabla legible en stdout. Si se detectan ≥ 1 duplicados, también
|
|
29
|
+
* imprime sugerencia para revisar/consolidar.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
|
|
35
|
+
const { tokenize, jaccard } = require('./lib/jaccard-similarity');
|
|
36
|
+
|
|
37
|
+
const RUTA_APRENDIZAJES = path.join(process.cwd(), '.planning', 'APRENDIZAJES.md');
|
|
38
|
+
const DEFAULT_THRESHOLD = 0.6;
|
|
39
|
+
const MAX_PARES_REPORTADOS = 30;
|
|
40
|
+
|
|
41
|
+
function parsearEntradas(contenido) {
|
|
42
|
+
const lineas = contenido.split('\n');
|
|
43
|
+
const entradas = [];
|
|
44
|
+
let actual = null;
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < lineas.length; i++) {
|
|
47
|
+
const linea = lineas[i];
|
|
48
|
+
if (linea.startsWith('## ')) {
|
|
49
|
+
if (actual) entradas.push(actual);
|
|
50
|
+
actual = {
|
|
51
|
+
lineaInicio: i + 1,
|
|
52
|
+
titulo: linea.slice(3).trim(),
|
|
53
|
+
contenido: '',
|
|
54
|
+
};
|
|
55
|
+
} else if (actual) {
|
|
56
|
+
actual.contenido += linea + '\n';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (actual) entradas.push(actual);
|
|
60
|
+
|
|
61
|
+
// Filtrar entradas vacías o triviales (< 50 chars de contenido real)
|
|
62
|
+
return entradas.filter(e => e.contenido.replace(/\s/g, '').length >= 50);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function detectarDuplicados(entradas, threshold) {
|
|
66
|
+
const tokensCache = entradas.map(e => tokenize(e.titulo + ' ' + e.contenido));
|
|
67
|
+
const pares = [];
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < entradas.length; i++) {
|
|
70
|
+
for (let j = i + 1; j < entradas.length; j++) {
|
|
71
|
+
const sim = jaccard(tokensCache[i], tokensCache[j]);
|
|
72
|
+
if (sim >= threshold) {
|
|
73
|
+
pares.push({
|
|
74
|
+
entradaA: entradas[i],
|
|
75
|
+
entradaB: entradas[j],
|
|
76
|
+
similitud: sim,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pares.sort((a, b) => b.similitud - a.similitud);
|
|
83
|
+
return pares;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function reportarTexto(pares) {
|
|
87
|
+
if (pares.length === 0) {
|
|
88
|
+
console.log('Sin duplicados detectados sobre el umbral.');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`Pares con similitud Jaccard ≥ umbral: ${pares.length}`);
|
|
93
|
+
console.log('');
|
|
94
|
+
|
|
95
|
+
const limite = Math.min(pares.length, MAX_PARES_REPORTADOS);
|
|
96
|
+
for (let i = 0; i < limite; i++) {
|
|
97
|
+
const p = pares[i];
|
|
98
|
+
console.log(` [${(p.similitud * 100).toFixed(1)}%] ` +
|
|
99
|
+
`L${p.entradaA.lineaInicio} ↔ L${p.entradaB.lineaInicio}`);
|
|
100
|
+
console.log(' A: ' + p.entradaA.titulo.slice(0, 80));
|
|
101
|
+
console.log(' B: ' + p.entradaB.titulo.slice(0, 80));
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (pares.length > limite) {
|
|
106
|
+
console.log(` ... ${pares.length - limite} pares adicionales no mostrados`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log('Sugerencia: revisa los pares con mayor similitud y considera ' +
|
|
110
|
+
'fusionarlos en una sola entrada con `/swl:aprender consolidar` o manualmente.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function main() {
|
|
114
|
+
const threshold = parseFloat(process.argv[2]) || DEFAULT_THRESHOLD;
|
|
115
|
+
|
|
116
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
117
|
+
console.error(`Threshold inválido: ${process.argv[2]}. Usar valor en [0, 1].`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(RUTA_APRENDIZAJES)) {
|
|
122
|
+
console.error(`No existe ${RUTA_APRENDIZAJES}.`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let contenido;
|
|
127
|
+
try {
|
|
128
|
+
contenido = fs.readFileSync(RUTA_APRENDIZAJES, 'utf8');
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(`Error leyendo ${RUTA_APRENDIZAJES}: ${err.message}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const entradas = parsearEntradas(contenido);
|
|
135
|
+
console.log(`Entradas encontradas: ${entradas.length}`);
|
|
136
|
+
console.log(`Threshold de similitud: ${threshold}`);
|
|
137
|
+
console.log('');
|
|
138
|
+
|
|
139
|
+
const pares = detectarDuplicados(entradas, threshold);
|
|
140
|
+
reportarTexto(pares);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (require.main === module) {
|
|
144
|
+
main();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
parsearEntradas,
|
|
149
|
+
detectarDuplicados,
|
|
150
|
+
reportarTexto,
|
|
151
|
+
};
|
package/scripts/doctor.js
CHANGED
|
@@ -50,6 +50,33 @@ async function doctor(opciones = {}) {
|
|
|
50
50
|
advertencias++;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// 0a. Estado del hook automático check-update.js (ADR-0021 opt-in visibility).
|
|
54
|
+
// Permite al usuario diagnosticar si el hook está ejecutándose correctamente
|
|
55
|
+
// sin tener que esperar a que aparezca una nueva versión real.
|
|
56
|
+
try {
|
|
57
|
+
const os = require('os');
|
|
58
|
+
// SWL_UPDATE_FLAG_PATH permite a los tests aislar el flag por test (evita
|
|
59
|
+
// race entre tests paralelos sobre el flag único de producción).
|
|
60
|
+
const flagPath = process.env.SWL_UPDATE_FLAG_PATH || path.join(os.tmpdir(), 'swl-ses-update-check.json');
|
|
61
|
+
if (fs.existsSync(flagPath)) {
|
|
62
|
+
const flag = JSON.parse(fs.readFileSync(flagPath, 'utf8'));
|
|
63
|
+
const horas = ((Date.now() - flag.timestamp) / 3600000).toFixed(1);
|
|
64
|
+
const detalle = `local=${flag.local}, remota=${flag.remota || '?'}, hace ${horas}h`;
|
|
65
|
+
if (flag.hayNueva) {
|
|
66
|
+
console.log(formatearAdvertencia('Hook check-update', `${detalle} — hay versión nueva pendiente`));
|
|
67
|
+
advertencias++;
|
|
68
|
+
} else {
|
|
69
|
+
console.log(formatearPaso('Hook check-update', detalle));
|
|
70
|
+
ok++;
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.log(formatearAdvertencia('Hook check-update', 'Sin flag de throttle — el hook nunca se ejecutó en este equipo'));
|
|
74
|
+
console.log(` ${color.dim('Diagnóstico: SWL_FORCE_UPDATE_NOTIFICATION=1 + abre Claude Code para forzar una ejecución.')}`);
|
|
75
|
+
advertencias++;
|
|
76
|
+
}
|
|
77
|
+
} catch { /* nunca bloquear el doctor por este check */ }
|
|
78
|
+
console.log('');
|
|
79
|
+
|
|
53
80
|
// 0b. Verificar drift entre versionSistema instalado y versión del binario CLI
|
|
54
81
|
// ejecutándose (bug D v1.3.5).
|
|
55
82
|
//
|