@saulwade/swl-ses 1.3.4 → 1.3.5
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 +1 -1
- package/README.md +1 -1
- package/bin/swl-mcp-server.js +187 -187
- package/bin/swl-ses.js +4 -62
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/adoptar-proyecto.md +207 -207
- package/comandos/swl/contribuir.md +233 -233
- 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/eval-framework/SKILL.md +212 -212
- package/habilidades/extractor-de-aprendizajes/SKILL.md +321 -321
- 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/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- 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/release-semver/.evolved.json +8 -8
- package/habilidades/swl-claudemd/SKILL.md +220 -220
- package/habilidades/testing-python/SKILL.md +340 -340
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/extraccion-aprendizajes.js +19 -12
- 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/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/skills-lock.json +1093 -1093
- package/package.json +1 -1
- 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 +1 -1
- 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/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +77 -3
- 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 +38 -1
- 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/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +136 -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/transformadores/claude.js +200 -200
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- 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/validar-manifest.js +195 -195
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/verificar-release.js +5 -1
|
@@ -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,55 @@ async function doctor(opciones = {}) {
|
|
|
50
50
|
advertencias++;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// 0b. Verificar drift entre versionSistema instalado y versión del binario CLI
|
|
54
|
+
// ejecutándose (bug D v1.3.5).
|
|
55
|
+
//
|
|
56
|
+
// El check de la sección 0 solo compara VERSION (binario) vs versionRemota (npm).
|
|
57
|
+
// Pero existe otro drift: la versión instalada físicamente en ~/.claude/ (de un
|
|
58
|
+
// install previo) puede estar por debajo del binario CLI ejecutándose. En ese
|
|
59
|
+
// caso `update` no detecta nada porque VERSION === versionRemota, mientras
|
|
60
|
+
// `versionSistema` del install-state se queda atrás y los archivos copiados
|
|
61
|
+
// a destino mantienen contenido de la versión vieja.
|
|
62
|
+
//
|
|
63
|
+
// Síntoma del usuario: `/swl:claudemd` falla porque ~/.claude/commands/swl/
|
|
64
|
+
// claudemd.md tiene `node scripts/X.js` (patrón v1.3.2) mientras el binario
|
|
65
|
+
// CLI ya es v1.3.4 y los slash commands de v1.3.3+ usan subcomandos del CLI.
|
|
66
|
+
const driftsDetectados = [];
|
|
67
|
+
try {
|
|
68
|
+
const runtimesParaDrift = detectarRuntimes();
|
|
69
|
+
for (const runtime of runtimesParaDrift) {
|
|
70
|
+
for (const dir of [runtime.global, path.resolve(runtime.local)]) {
|
|
71
|
+
if (!fs.existsSync(dir)) continue;
|
|
72
|
+
const estado = cargarEstado(dir);
|
|
73
|
+
if (!estado || !estado.versionSistema) continue;
|
|
74
|
+
|
|
75
|
+
if (estado.versionSistema !== VERSION) {
|
|
76
|
+
driftsDetectados.push({
|
|
77
|
+
runtime: runtime.nombre,
|
|
78
|
+
runtimeId: runtime.id,
|
|
79
|
+
scope: dir === runtime.global ? 'global' : 'proyecto',
|
|
80
|
+
dir,
|
|
81
|
+
versionInstalada: estado.versionSistema,
|
|
82
|
+
perfil: estado.perfil || 'desconocido',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch { /* nunca bloquear el doctor por este check */ }
|
|
88
|
+
|
|
89
|
+
if (driftsDetectados.length > 0) {
|
|
90
|
+
for (const drift of driftsDetectados) {
|
|
91
|
+
console.log(formatearAdvertencia(
|
|
92
|
+
`Drift ${drift.runtime}`,
|
|
93
|
+
`Binario CLI v${VERSION}, pero ${drift.scope} instalado en v${drift.versionInstalada}`
|
|
94
|
+
));
|
|
95
|
+
console.log(` ${color.dim('Los archivos en disco pueden tener contenido viejo aunque el binario sea nuevo.')}`);
|
|
96
|
+
console.log(` ${color.dim('Para sincronizar:')} ${color.amarillo(`npx @saulwade/swl-ses@latest install --${drift.scope === 'global' ? 'global' : 'local'} --profile ${drift.perfil} --force`)}`);
|
|
97
|
+
advertencias++;
|
|
98
|
+
}
|
|
99
|
+
console.log('');
|
|
100
|
+
}
|
|
101
|
+
|
|
53
102
|
// 1. Verificar Node.js
|
|
54
103
|
const nodeVersion = process.version;
|
|
55
104
|
const major = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
@@ -272,9 +321,11 @@ async function doctor(opciones = {}) {
|
|
|
272
321
|
advertencias++;
|
|
273
322
|
}
|
|
274
323
|
|
|
275
|
-
// 5b-2. Verificar conteo de componentes contra perfil esperado
|
|
324
|
+
// 5b-2. Verificar conteo de componentes contra perfil esperado.
|
|
325
|
+
// Bug H v1.3.5: antes solo verificaba runtime.local; instalaciones globales
|
|
326
|
+
// (~/.claude/) quedaban sin check de conteo. Ahora itera ambos scopes.
|
|
276
327
|
for (const runtime of runtimes) {
|
|
277
|
-
const dir
|
|
328
|
+
for (const dir of [runtime.global, path.resolve(runtime.local)]) {
|
|
278
329
|
if (!fs.existsSync(dir)) continue;
|
|
279
330
|
const estado = cargarEstado(dir);
|
|
280
331
|
if (!estado || !estado.perfil) continue;
|
|
@@ -310,12 +361,25 @@ async function doctor(opciones = {}) {
|
|
|
310
361
|
|
|
311
362
|
// Comparar tipos principales
|
|
312
363
|
const discrepancias = [];
|
|
364
|
+
const excedentes = [];
|
|
313
365
|
for (const [tipo, esperado] of Object.entries(esperadosPorTipo)) {
|
|
314
366
|
const instalado = instaladosPorTipo[tipo] || 0;
|
|
315
367
|
if (instalado < esperado) {
|
|
316
368
|
discrepancias.push(`${tipo}: ${instalado}/${esperado}`);
|
|
317
369
|
}
|
|
318
370
|
}
|
|
371
|
+
// Bug E v1.3.5: detectar excedente — archivos del perfil completo que
|
|
372
|
+
// quedaron tras un cambio de perfil (ej: completo → core sin uninstall
|
|
373
|
+
// previo). El install no los borra, así que conviven con contenido viejo
|
|
374
|
+
// hasta que un install con el perfil correcto los sobreescriba. Sin
|
|
375
|
+
// esta advertencia, el usuario veía "Sistema en perfecto estado" mientras
|
|
376
|
+
// commands/swl/claudemd.md (parte de completo) seguía con contenido v1.3.2.
|
|
377
|
+
for (const [tipo, instalado] of Object.entries(instaladosPorTipo)) {
|
|
378
|
+
const esperado = esperadosPorTipo[tipo] || 0;
|
|
379
|
+
if (instalado > esperado) {
|
|
380
|
+
excedentes.push(`${tipo}: ${instalado} (perfil esperaba ${esperado})`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
319
383
|
|
|
320
384
|
const totalEsperado = resolucion.archivos.length;
|
|
321
385
|
const totalInstalado = estado.archivosInstalados.length;
|
|
@@ -323,9 +387,18 @@ async function doctor(opciones = {}) {
|
|
|
323
387
|
.map(([t, n]) => `${instaladosPorTipo[t] || 0}/${n} ${t}`)
|
|
324
388
|
.join(', ');
|
|
325
389
|
|
|
326
|
-
if (discrepancias.length === 0) {
|
|
390
|
+
if (discrepancias.length === 0 && excedentes.length === 0) {
|
|
327
391
|
console.log(formatearPaso(`Conteo ${runtime.nombre}`, `${totalInstalado} archivos (${detalle})`));
|
|
328
392
|
ok++;
|
|
393
|
+
} else if (excedentes.length > 0 && discrepancias.length === 0) {
|
|
394
|
+
console.log(formatearAdvertencia(
|
|
395
|
+
`Conteo ${runtime.nombre}`,
|
|
396
|
+
`${totalInstalado} archivos — excedente del perfil ${estado.perfil}: ${excedentes.join(', ')}`
|
|
397
|
+
));
|
|
398
|
+
console.log(` ${color.dim('Posible residuo de instalación previa con perfil más amplio.')}`);
|
|
399
|
+
console.log(` ${color.dim('Sugerencia: si el perfil declarado es correcto, desinstala primero y reinstala limpio:')}`);
|
|
400
|
+
console.log(` ${color.dim(`swl-ses uninstall${estado.global ? ' --global' : ''} && swl-ses install --profile ${estado.perfil}${estado.global ? ' --global' : ''}`)}`);
|
|
401
|
+
advertencias++;
|
|
329
402
|
} else {
|
|
330
403
|
console.log(formatearAdvertencia(`Conteo ${runtime.nombre}`, `${totalInstalado} archivos — faltan: ${discrepancias.join(', ')}`));
|
|
331
404
|
advertencias++;
|
|
@@ -348,6 +421,7 @@ async function doctor(opciones = {}) {
|
|
|
348
421
|
console.log(formatearAdvertencia(`Conteo ${runtime.nombre}`, `No se pudo verificar: ${err.message}`));
|
|
349
422
|
advertencias++;
|
|
350
423
|
}
|
|
424
|
+
}
|
|
351
425
|
}
|
|
352
426
|
|
|
353
427
|
// 5c. Verificar optimizaciones de rendimiento en settings.json
|