@saulwade/swl-ses 2.3.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CLAUDE.md +242 -240
  2. package/README.md +597 -597
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +908 -908
  8. package/agentes/backend-api-swl.md +472 -472
  9. package/agentes/backend-csharp-swl.md +420 -420
  10. package/agentes/backend-go-swl.md +390 -390
  11. package/agentes/backend-java-swl.md +281 -281
  12. package/agentes/backend-node-swl.md +479 -479
  13. package/agentes/backend-python-swl.md +605 -605
  14. package/agentes/backend-rust-swl.md +364 -364
  15. package/agentes/backend-workers-swl.md +482 -482
  16. package/agentes/cloud-infra-swl.md +509 -509
  17. package/agentes/consolidador-swl.md +541 -541
  18. package/agentes/datos-swl.md +605 -605
  19. package/agentes/depurador-swl.md +352 -352
  20. package/agentes/devops-ci-swl.md +400 -400
  21. package/agentes/disenador-ui-swl.md +569 -569
  22. package/agentes/documentador-swl.md +345 -345
  23. package/agentes/frontend-angular-swl.md +621 -621
  24. package/agentes/frontend-css-swl.md +716 -716
  25. package/agentes/frontend-react-swl.md +692 -692
  26. package/agentes/frontend-swl.md +496 -496
  27. package/agentes/frontend-tailwind-swl.md +826 -826
  28. package/agentes/gh-fix-ci-swl.md +275 -275
  29. package/agentes/implementador-swl.md +328 -328
  30. package/agentes/investigador-swl.md +432 -432
  31. package/agentes/investigador-ux-swl.md +505 -505
  32. package/agentes/llm-apps-swl.md +278 -278
  33. package/agentes/migrador-swl.md +442 -442
  34. package/agentes/mobile-android-swl.md +511 -511
  35. package/agentes/mobile-cross-swl.md +541 -541
  36. package/agentes/mobile-ios-swl.md +502 -502
  37. package/agentes/mobile-testing-swl.md +302 -302
  38. package/agentes/nemesis-auditor-swl.md +285 -285
  39. package/agentes/notificador-swl.md +918 -918
  40. package/agentes/observabilidad-swl.md +438 -438
  41. package/agentes/orquestador-swl.md +1053 -1026
  42. package/agentes/pagos-swl.md +310 -310
  43. package/agentes/perfilador-usuario-swl.md +321 -321
  44. package/agentes/planificador-swl.md +399 -399
  45. package/agentes/producto-prd-swl.md +589 -589
  46. package/agentes/red-team-swl.md +218 -218
  47. package/agentes/release-manager-swl.md +590 -590
  48. package/agentes/rendimiento-swl.md +713 -713
  49. package/agentes/resolutor-build-swl.md +245 -245
  50. package/agentes/revisor-angular-swl.md +278 -278
  51. package/agentes/revisor-codigo-swl.md +538 -505
  52. package/agentes/revisor-csharp-swl.md +264 -264
  53. package/agentes/revisor-go-swl.md +259 -259
  54. package/agentes/revisor-java-swl.md +257 -257
  55. package/agentes/revisor-kotlin-swl.md +273 -273
  56. package/agentes/revisor-nextjs-swl.md +281 -281
  57. package/agentes/revisor-php-swl.md +271 -271
  58. package/agentes/revisor-react-swl.md +278 -278
  59. package/agentes/revisor-rust-swl.md +346 -346
  60. package/agentes/revisor-seguridad-swl.md +399 -399
  61. package/agentes/revisor-swift-swl.md +268 -268
  62. package/agentes/revisor-typescript-swl.md +346 -346
  63. package/agentes/sre-swl.md +291 -291
  64. package/agentes/tdd-qa-swl.md +393 -393
  65. package/comandos/swl/contexto.md +0 -2
  66. package/comandos/swl/deuda-codigo.md +97 -0
  67. package/habilidades/angular-moderno/SKILL.md +5 -1
  68. package/habilidades/contenedores-docker/SKILL.md +3 -1
  69. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  70. package/habilidades/harness-claude-code/SKILL.md +5 -4
  71. package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +6 -5
  72. package/habilidades/legacy-code-rescue/SKILL.md +2 -1
  73. package/habilidades/meta-skills-estandar/SKILL.md +1 -1
  74. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
  75. package/habilidades/postgresql-experto/SKILL.md +3 -1
  76. package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
  77. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
  78. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
  79. package/habilidades/tdd-workflow/SKILL.md +3 -1
  80. package/hooks/audit-trail.js +4 -4
  81. package/hooks/calidad-pre-commit.js +15 -11
  82. package/hooks/captura-acciones-post.js +3 -2
  83. package/hooks/captura-acciones-session.js +3 -2
  84. package/hooks/contexto-iteracion.js +2 -1
  85. package/hooks/contexto-subagente.js +68 -0
  86. package/hooks/guardrail-modelo.js +13 -3
  87. package/hooks/inyeccion-contexto.js +12 -12
  88. package/hooks/registro-turnos.js +5 -4
  89. package/hooks/spec-gate.js +2 -1
  90. package/hooks/sugerir-contribuir.js +2 -1
  91. package/hooks/sugerir-regenerar-inventario.js +3 -2
  92. package/hooks/tdd-gate.js +2 -1
  93. package/llms.txt +3 -3
  94. package/manifiestos/canonical-hashes.json +995 -10
  95. package/manifiestos/hook-profiles.json +0 -2
  96. package/manifiestos/hooks-config.json +469 -477
  97. package/manifiestos/invariantes-criticos.json +30 -0
  98. package/manifiestos/modulos.json +1390 -1390
  99. package/manifiestos/skills-lock.json +24 -24
  100. package/package.json +93 -93
  101. package/plugin.json +369 -371
  102. package/reglas/arreglar-al-detectar.md +16 -0
  103. package/reglas/pruebas.md +7 -3
  104. package/schemas/agent-frontmatter.schema.json +3 -1
  105. package/scripts/actualizar.js +253 -254
  106. package/scripts/doctor.js +25 -17
  107. package/scripts/instalador.js +4 -1
  108. package/scripts/lib/detectar-runtime.js +58 -0
  109. package/scripts/lib/expandir-targets.js +71 -71
  110. package/scripts/lib/hooks-settings.js +40 -3
  111. package/scripts/lib/preservar-usuario.js +39 -5
  112. package/scripts/lib/toml-merge.js +204 -204
  113. package/scripts/mcp-server/auth.js +105 -105
  114. package/scripts/mcp-server/cache.js +106 -106
  115. package/scripts/tui/pantallas/inspect.js +175 -173
  116. package/scripts/tui/pantallas/uninstall-wizard.js +210 -208
  117. package/scripts/tui/pantallas/update-wizard.js +234 -232
  118. package/scripts/tui/pantallas/welcome.js +189 -187
  119. package/scripts/validar.js +1 -1
  120. package/scripts/verificar-release.js +51 -0
  121. package/hooks/lib/context-compressor.js +0 -657
  122. package/hooks/linea-estado.js +0 -328
  123. package/hooks/monitor-contexto.js +0 -373
@@ -57,6 +57,22 @@ principio:
57
57
  - Si encuentras código adyacente que se quedó obsoleto por un refactor previo,
58
58
  actualizarlo. No dejar deuda residual.
59
59
 
60
+ ### Fix de clase, no de instancia (barrido por patrón obligatorio)
61
+
62
+ - Si el bug corregido es un PATRÓN (regex incompleto, umbral mal calibrado,
63
+ construcción de path repetida, gate copy-pasteado) y no un typo puntual,
64
+ el MISMO turno incluye un `grep` de todos los hermanos del patrón en el
65
+ codebase y su corrección — o el registro explícito de por qué no aplica.
66
+ - Arreglar solo la instancia que falló deja a los gemelos esperando el peor
67
+ momento para reventar. Evidencia doble (2026-07-03, swl-ses): se corrigió
68
+ el gate de reloj de un test sin barrer la clase → su gemelo tumbó el
69
+ `npm publish` de v2.4.0 horas después (bump de coordinación innecesario);
70
+ se corrigió el scope fantasma en el doctor sin barrer → quedaron 7 sitios
71
+ más con el mismo patrón, incluido un uninstall-wizard que ofrecía borrar
72
+ la instalación global de Claude Code.
73
+ - Es la aplicación a FIXES del "sweep por patrón" que
74
+ `verificar-citas-normativas.md § Familia 2` ya exige para reportes.
75
+
60
76
  ### Al detectar un error ajeno al trabajo actual
61
77
 
62
78
  - NO bypassear (excluir tests del glob, comentar checks, `|| true`,
package/reglas/pruebas.md CHANGED
@@ -86,9 +86,13 @@ Reglas del AAA:
86
86
  latencia en un test:
87
87
  - **Warm-up**: descartar las primeras iteraciones (cold-start de JIT/FS-cache).
88
88
  - **N ≥ 50 muestras** para un p95 estable.
89
- - **Umbral tolerante a carga** (≥2× del presupuesto): detecta una regresión
90
- gruesa sin flaquear por varianza; reportar el p95 observado en el mensaje del
91
- assert para vigilar el presupuesto fino.
89
+ - **Umbral de ORDEN DE MAGNITUD (5-10× del presupuesto)**: el "≥2×" que esta
90
+ regla recomendaba resultó corto en la práctica bajo la carga de
91
+ `npm publish` (suite + smoke + disco simultáneos) el reventó DOS veces
92
+ (p95=208ms vs gate 200 en v2.3.x; p95=531ms vs gate 400=2× tumbó el publish
93
+ v2.4.0 y forzó un republish de coordinación, 2026-07-03). El gate bloqueante
94
+ solo debe atrapar regresiones de orden de magnitud; el presupuesto fino se
95
+ OBSERVA en el p95 impreso en el mensaje del assert, nunca se asevera duro.
92
96
  - **Mejor aún**: medir-y-avisar (benchmark/log) FUERA de la suite bloqueante;
93
97
  el gate duro detecta regresión, no afina el SLA.
94
98
 
@@ -51,6 +51,7 @@
51
51
  "oneOf": [
52
52
  {
53
53
  "enum": [
54
+ "fable",
54
55
  "opus",
55
56
  "sonnet",
56
57
  "haiku",
@@ -61,13 +62,14 @@
61
62
  "pattern": "^claude-(opus|sonnet|haiku|fable)-[0-9]+(-[0-9]+){0,2}$"
62
63
  }
63
64
  ],
64
- "description": "Modelo principal del agente. Se recomienda alias corto (opus/sonnet/haiku), que resuelve al modelo vigente de la familia y se auto-actualiza; 'inherit' para sub-agentes; o nombre completo tipo 'claude-opus-4-8', 'claude-sonnet-5', 'claude-haiku-4-5-20251001'."
65
+ "description": "Modelo principal del agente. Se recomienda alias corto (fable/opus/sonnet/haiku), que resuelve al modelo vigente de la familia y se auto-actualiza; 'inherit' para sub-agentes; o nombre completo tipo 'claude-fable-5', 'claude-opus-4-8', 'claude-sonnet-5', 'claude-haiku-4-5-20251001'."
65
66
  },
66
67
  "modeloAlterno": {
67
68
  "type": "string",
68
69
  "oneOf": [
69
70
  {
70
71
  "enum": [
72
+ "fable",
71
73
  "opus",
72
74
  "sonnet",
73
75
  "haiku",
@@ -1,254 +1,253 @@
1
- 'use strict';
2
-
3
- /**
4
- * swl-ses update
5
- * Actualiza componentes instalados preservando _userland/.
6
- * Interactivo: muestra versiones, pide confirmación, muestra progreso.
7
- * No-interactivo con --force o sin TTY.
8
- *
9
- * Modo --check (read-only):
10
- * Solo muestra el estado actual de versiones (local vs npm vs runtimes instalados).
11
- * No pide confirmación. No actualiza nada. Util para el autor o usuarios que
12
- * quieren verificar si están al dia sin esperar al hook automatico.
13
- */
14
-
15
- const fs = require('fs');
16
- const os = require('os');
17
- const path = require('path');
18
- const { detectarRuntimes } = require('./lib/detectar-runtime');
19
- const { cargarEstado } = require('./lib/estado');
20
- const {
21
- color, icono, encabezado, formatearPaso, formatearAdvertencia,
22
- spinner, preguntarSiNo, lineaSeparadora, ES_TTY,
23
- } = require('./lib/ui');
24
- const { versionRemotaParalela, compararSemver } = require('./lib/npm-version');
25
-
26
- const VERSION_LOCAL = require('../package.json').version;
27
-
28
- /**
29
- * Consulta la versión más reciente publicada en npm. Si la consulta falla
30
- * (sin red, timeout), retorna la versión local como fallback razonable.
31
- */
32
- async function obtenerVersionRemota() {
33
- const r = await versionRemotaParalela({ timeoutMs: 10000 });
34
- return r.version || VERSION_LOCAL;
35
- }
36
-
37
- /**
38
- * Modo --check: muestra el estado actual sin actualizar. Read-only.
39
- *
40
- * Salida:
41
- * - Version del paquete que esta ejecutando este CLI (VERSION_LOCAL).
42
- * - Version mas reciente publicada en npm.
43
- * - Comparacion semver: al dia / adelantado / atras.
44
- * - Estado del hook check-update.js: ultima verificacion automatica.
45
- * - Tabla por runtime instalado (si los detecta) con su versionSistema.
46
- */
47
- async function checkStatus() {
48
- console.log(encabezado('swl-ses update --check', VERSION_LOCAL));
49
-
50
- // Resolver version remota con timeout corto
51
- const remota = await obtenerVersionRemota();
52
- const cmp = compararSemver(VERSION_LOCAL, remota);
53
-
54
- let estadoVersion;
55
- if (cmp === null) {
56
- estadoVersion = color.amarillo('Comparacion no disponible (formato inesperado)');
57
- } else if (cmp === 0) {
58
- estadoVersion = color.verde('Al dia');
59
- } else if (cmp < 0) {
60
- estadoVersion = color.amarillo(`Nueva version disponible (${remota})`);
61
- } else {
62
- estadoVersion = color.dim(`Local mas reciente que npm (${VERSION_LOCAL} > ${remota}, publicacion pendiente)`);
63
- }
64
-
65
- console.log(` Paquete ejecutado: ${color.negrita('v' + VERSION_LOCAL)}`);
66
- console.log(` Version en npm: ${color.negrita('v' + remota)}`);
67
- console.log(` Estado: ${estadoVersion}`);
68
- console.log('');
69
-
70
- // Estado del hook automatico (check-update.js).
71
- // SWL_UPDATE_FLAG_PATH override solo para tests (evita race con flag único).
72
- const flagPath = process.env.SWL_UPDATE_FLAG_PATH || path.join(os.tmpdir(), 'swl-ses-update-check.json');
73
- try {
74
- const flag = JSON.parse(fs.readFileSync(flagPath, 'utf8'));
75
- const horas = ((Date.now() - flag.timestamp) / 3600000).toFixed(1);
76
- console.log(` ${color.negrita('Hook automatico (check-update.js):')}`);
77
- console.log(` Ultima verificacion: hace ${horas}h`);
78
- console.log(` Local registrada: ${flag.local}`);
79
- console.log(` Remota registrada: ${flag.remota || color.dim('(no resuelta)')}`);
80
- console.log(` hayNueva: ${flag.hayNueva ? color.amarillo('si') : color.verde('no')}`);
81
- console.log(` Flag: ${flagPath}`);
82
- } catch {
83
- console.log(` ${color.negrita('Hook automatico (check-update.js):')}`);
84
- console.log(` ${color.dim('Sin flag de throttle — el hook nunca se ha ejecutado en este equipo,')}`);
85
- console.log(` ${color.dim('o el archivo temp/swl-ses-update-check.json fue removido.')}`);
86
- }
87
- console.log('');
88
-
89
- // Estado por runtime instalado
90
- const runtimes = detectarRuntimes();
91
- if (runtimes.length === 0) {
92
- console.log(formatearAdvertencia('Runtimes', 'No se detectaron runtimes con instalacion SWL'));
93
- console.log('');
94
- return;
95
- }
96
-
97
- console.log(` ${color.negrita('Runtimes detectados:')}`);
98
- let totalRuntimes = 0;
99
- for (const runtime of runtimes) {
100
- const dirs = [runtime.global, path.resolve(runtime.local)];
101
- for (const dir of dirs) {
102
- if (!fs.existsSync(dir)) continue;
103
- const estado = cargarEstado(dir);
104
- if (!estado) continue;
105
-
106
- const esGlobal = dir === runtime.global;
107
- const ubicacion = esGlobal ? 'global' : 'proyecto';
108
- const v = estado.versionSistema || 'desconocida';
109
-
110
- const cmpRuntime = compararSemver(v, remota);
111
- let etiqueta;
112
- if (cmpRuntime === null) {
113
- etiqueta = color.dim('(version no reconocida)');
114
- } else if (cmpRuntime === 0) {
115
- etiqueta = color.verde('al dia');
116
- } else if (cmpRuntime < 0) {
117
- etiqueta = color.amarillo(`atrasada (npm: ${remota})`);
118
- } else {
119
- etiqueta = color.dim('adelantada');
120
- }
121
-
122
- console.log(` ${runtime.nombre} (${ubicacion}): v${v} ${etiqueta}`);
123
- totalRuntimes++;
124
- }
125
- }
126
- if (totalRuntimes === 0) {
127
- console.log(` ${color.dim('(ninguno con .swl-install-state.json valido)')}`);
128
- }
129
- console.log('');
130
- console.log(` ${color.dim('Modo --check es read-only. Para actualizar:')} npx -y @saulwade/swl-ses@latest update`);
131
- console.log('');
132
- }
133
-
134
- async function update(opciones) {
135
- // Modo read-only: solo reportar estado, no tocar nada
136
- if (opciones.check) {
137
- await checkStatus();
138
- return;
139
- }
140
-
141
- const force = opciones.force || !ES_TTY;
142
-
143
- // Obtener la versión más reciente del registro npm
144
- const VERSION = await obtenerVersionRemota();
145
-
146
- console.log(encabezado('swl-ses update', VERSION_LOCAL));
147
-
148
- // 1. Detectar runtimes
149
- const runtimes = detectarRuntimes();
150
-
151
- if (runtimes.length === 0) {
152
- console.log(formatearAdvertencia('Runtimes', 'No se detectaron runtimes con instalación SWL'));
153
- console.log(`\n Ejecuta primero: npx @saulwade/swl-ses@latest install --target claude`);
154
- process.exit(1);
155
- }
156
-
157
- let actualizados = 0;
158
- let omitidos = 0;
159
-
160
- // 2. Iterar runtimes
161
- for (const runtime of runtimes) {
162
- const dirs = [runtime.global, path.resolve(runtime.local)];
163
- for (const dir of dirs) {
164
- if (!fs.existsSync(dir)) continue;
165
-
166
- const estado = cargarEstado(dir);
167
- if (!estado) continue;
168
-
169
- const versionInstalada = estado.versionSistema || 'desconocida';
170
- const esGlobal = dir === runtime.global;
171
- const ubicacion = esGlobal ? 'global' : 'proyecto';
172
-
173
- const necesitaActualizar = versionInstalada !== VERSION;
174
- const paqueteDesactualizado = VERSION_LOCAL !== VERSION;
175
-
176
- console.log(` ${color.negrita(`Runtime: ${runtime.nombre}`)} ${color.dim(`(${ubicacion})`)}`);
177
- console.log(` Versión instalada: ${necesitaActualizar ? color.amarillo(versionInstalada) : color.verde(versionInstalada)}`);
178
- console.log(` Versión disponible: ${color.verde(VERSION)}${paqueteDesactualizado ? color.dim(` (ejecutando con v${VERSION_LOCAL})`) : ''}`);
179
- console.log(` Perfil: ${estado.perfil}`);
180
- console.log(` Componentes: ${estado.componentesInstalados.length}`);
181
- console.log(` Archivos: ${estado.archivosInstalados.length}`);
182
- console.log('');
183
-
184
- // Si la versión disponible es más reciente pero el paquete ejecutándose es viejo,
185
- // indicar al usuario que use @latest
186
- if (paqueteDesactualizado && necesitaActualizar) {
187
- console.log(formatearAdvertencia(runtime.nombre,
188
- `Este paquete es v${VERSION_LOCAL} pero la v${VERSION} está disponible.\n` +
189
- ` Para actualizar, ejecuta: npx @saulwade/swl-ses@latest update --force`));
190
- console.log('');
191
- omitidos++;
192
- continue;
193
- }
194
-
195
- // Verificar si ya está actualizado
196
- if (!necesitaActualizar && !force) {
197
- console.log(formatearPaso(runtime.nombre, 'Ya está en la versión más reciente'));
198
- console.log('');
199
- omitidos++;
200
- continue;
201
- }
202
-
203
- // Confirmar
204
- if (!force) {
205
- const confirmar = await preguntarSiNo(
206
- !necesitaActualizar
207
- ? `¿Re-instalar ${runtime.nombre} v${VERSION}?`
208
- : `¿Actualizar ${runtime.nombre} de ${versionInstalada} a ${VERSION}?`
209
- );
210
- console.log('');
211
-
212
- if (!confirmar) {
213
- console.log(formatearAdvertencia(runtime.nombre, 'Actualización cancelada por el usuario'));
214
- console.log('');
215
- omitidos++;
216
- continue;
217
- }
218
- }
219
-
220
- // Ejecutar actualización con spinner
221
- const sp = spinner(`Actualizando ${runtime.nombre}...`);
222
-
223
- try {
224
- const instalador = require('./instalador');
225
- await instalador({
226
- target: runtime.id,
227
- profile: estado.perfil,
228
- global: estado.global,
229
- force: true,
230
- });
231
-
232
- sp.exito(`${runtime.nombre} actualizado a v${VERSION} (${estado.archivosInstalados.length} archivos)`);
233
- actualizados++;
234
- } catch (err) {
235
- sp.fallo(`Error actualizando ${runtime.nombre}: ${err.message}`);
236
- }
237
-
238
- console.log('');
239
- }
240
- }
241
-
242
- // 3. Reporte final
243
- console.log(` ${lineaSeparadora()}`);
244
- if (actualizados > 0) {
245
- console.log(formatearPaso('Resultado', `${actualizados} runtime(s) actualizado(s)${omitidos > 0 ? `, ${omitidos} omitido(s)` : ''}`));
246
- } else if (omitidos > 0) {
247
- console.log(formatearPaso('Resultado', 'Todo está actualizado'));
248
- } else {
249
- console.log(formatearAdvertencia('Resultado', 'Ningún runtime fue actualizado'));
250
- }
251
- console.log('');
252
- }
253
-
254
- module.exports = update;
1
+ 'use strict';
2
+
3
+ /**
4
+ * swl-ses update
5
+ * Actualiza componentes instalados preservando _userland/.
6
+ * Interactivo: muestra versiones, pide confirmación, muestra progreso.
7
+ * No-interactivo con --force o sin TTY.
8
+ *
9
+ * Modo --check (read-only):
10
+ * Solo muestra el estado actual de versiones (local vs npm vs runtimes instalados).
11
+ * No pide confirmación. No actualiza nada. Util para el autor o usuarios que
12
+ * quieren verificar si están al dia sin esperar al hook automatico.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const { detectarRuntimes, scopesReales } = require('./lib/detectar-runtime');
19
+ const { cargarEstado } = require('./lib/estado');
20
+ const {
21
+ color, icono, encabezado, formatearPaso, formatearAdvertencia,
22
+ spinner, preguntarSiNo, lineaSeparadora, ES_TTY,
23
+ } = require('./lib/ui');
24
+ const { versionRemotaParalela, compararSemver } = require('./lib/npm-version');
25
+
26
+ const VERSION_LOCAL = require('../package.json').version;
27
+
28
+ /**
29
+ * Consulta la versión más reciente publicada en npm. Si la consulta falla
30
+ * (sin red, timeout), retorna la versión local como fallback razonable.
31
+ */
32
+ async function obtenerVersionRemota() {
33
+ const r = await versionRemotaParalela({ timeoutMs: 10000 });
34
+ return r.version || VERSION_LOCAL;
35
+ }
36
+
37
+ /**
38
+ * Modo --check: muestra el estado actual sin actualizar. Read-only.
39
+ *
40
+ * Salida:
41
+ * - Version del paquete que esta ejecutando este CLI (VERSION_LOCAL).
42
+ * - Version mas reciente publicada en npm.
43
+ * - Comparacion semver: al dia / adelantado / atras.
44
+ * - Estado del hook check-update.js: ultima verificacion automatica.
45
+ * - Tabla por runtime instalado (si los detecta) con su versionSistema.
46
+ */
47
+ async function checkStatus() {
48
+ console.log(encabezado('swl-ses update --check', VERSION_LOCAL));
49
+
50
+ // Resolver version remota con timeout corto
51
+ const remota = await obtenerVersionRemota();
52
+ const cmp = compararSemver(VERSION_LOCAL, remota);
53
+
54
+ let estadoVersion;
55
+ if (cmp === null) {
56
+ estadoVersion = color.amarillo('Comparacion no disponible (formato inesperado)');
57
+ } else if (cmp === 0) {
58
+ estadoVersion = color.verde('Al dia');
59
+ } else if (cmp < 0) {
60
+ estadoVersion = color.amarillo(`Nueva version disponible (${remota})`);
61
+ } else {
62
+ estadoVersion = color.dim(`Local mas reciente que npm (${VERSION_LOCAL} > ${remota}, publicacion pendiente)`);
63
+ }
64
+
65
+ console.log(` Paquete ejecutado: ${color.negrita('v' + VERSION_LOCAL)}`);
66
+ console.log(` Version en npm: ${color.negrita('v' + remota)}`);
67
+ console.log(` Estado: ${estadoVersion}`);
68
+ console.log('');
69
+
70
+ // Estado del hook automatico (check-update.js).
71
+ // SWL_UPDATE_FLAG_PATH override solo para tests (evita race con flag único).
72
+ const flagPath = process.env.SWL_UPDATE_FLAG_PATH || path.join(os.tmpdir(), 'swl-ses-update-check.json');
73
+ try {
74
+ const flag = JSON.parse(fs.readFileSync(flagPath, 'utf8'));
75
+ const horas = ((Date.now() - flag.timestamp) / 3600000).toFixed(1);
76
+ console.log(` ${color.negrita('Hook automatico (check-update.js):')}`);
77
+ console.log(` Ultima verificacion: hace ${horas}h`);
78
+ console.log(` Local registrada: ${flag.local}`);
79
+ console.log(` Remota registrada: ${flag.remota || color.dim('(no resuelta)')}`);
80
+ console.log(` hayNueva: ${flag.hayNueva ? color.amarillo('si') : color.verde('no')}`);
81
+ console.log(` Flag: ${flagPath}`);
82
+ } catch {
83
+ console.log(` ${color.negrita('Hook automatico (check-update.js):')}`);
84
+ console.log(` ${color.dim('Sin flag de throttle — el hook nunca se ha ejecutado en este equipo,')}`);
85
+ console.log(` ${color.dim('o el archivo temp/swl-ses-update-check.json fue removido.')}`);
86
+ }
87
+ console.log('');
88
+
89
+ // Estado por runtime instalado
90
+ const runtimes = detectarRuntimes();
91
+ if (runtimes.length === 0) {
92
+ console.log(formatearAdvertencia('Runtimes', 'No se detectaron runtimes con instalacion SWL'));
93
+ console.log('');
94
+ return;
95
+ }
96
+
97
+ console.log(` ${color.negrita('Runtimes detectados:')}`);
98
+ let totalRuntimes = 0;
99
+ for (const runtime of runtimes) {
100
+ // Dedup + guard anti-fantasma (ver scopesReales): desde HOME el local
101
+ // colapsa con el global propio o con el de otro runtime.
102
+ for (const { dir, esGlobal } of scopesReales(runtime)) {
103
+ if (!fs.existsSync(dir)) continue;
104
+ const estado = cargarEstado(dir);
105
+ if (!estado) continue;
106
+
107
+ const ubicacion = esGlobal ? 'global' : 'proyecto';
108
+ const v = estado.versionSistema || 'desconocida';
109
+
110
+ const cmpRuntime = compararSemver(v, remota);
111
+ let etiqueta;
112
+ if (cmpRuntime === null) {
113
+ etiqueta = color.dim('(version no reconocida)');
114
+ } else if (cmpRuntime === 0) {
115
+ etiqueta = color.verde('al dia');
116
+ } else if (cmpRuntime < 0) {
117
+ etiqueta = color.amarillo(`atrasada (npm: ${remota})`);
118
+ } else {
119
+ etiqueta = color.dim('adelantada');
120
+ }
121
+
122
+ console.log(` ${runtime.nombre} (${ubicacion}): v${v} ${etiqueta}`);
123
+ totalRuntimes++;
124
+ }
125
+ }
126
+ if (totalRuntimes === 0) {
127
+ console.log(` ${color.dim('(ninguno con .swl-install-state.json valido)')}`);
128
+ }
129
+ console.log('');
130
+ console.log(` ${color.dim('Modo --check es read-only. Para actualizar:')} npx -y @saulwade/swl-ses@latest update`);
131
+ console.log('');
132
+ }
133
+
134
+ async function update(opciones) {
135
+ // Modo read-only: solo reportar estado, no tocar nada
136
+ if (opciones.check) {
137
+ await checkStatus();
138
+ return;
139
+ }
140
+
141
+ const force = opciones.force || !ES_TTY;
142
+
143
+ // Obtener la versión más reciente del registro npm
144
+ const VERSION = await obtenerVersionRemota();
145
+
146
+ console.log(encabezado('swl-ses update', VERSION_LOCAL));
147
+
148
+ // 1. Detectar runtimes
149
+ const runtimes = detectarRuntimes();
150
+
151
+ if (runtimes.length === 0) {
152
+ console.log(formatearAdvertencia('Runtimes', 'No se detectaron runtimes con instalación SWL'));
153
+ console.log(`\n Ejecuta primero: npx @saulwade/swl-ses@latest install --target claude`);
154
+ process.exit(1);
155
+ }
156
+
157
+ let actualizados = 0;
158
+ let omitidos = 0;
159
+
160
+ // 2. Iterar runtimes
161
+ for (const runtime of runtimes) {
162
+ // Dedup + guard anti-fantasma (ver scopesReales).
163
+ for (const { dir, esGlobal } of scopesReales(runtime)) {
164
+ if (!fs.existsSync(dir)) continue;
165
+
166
+ const estado = cargarEstado(dir);
167
+ if (!estado) continue;
168
+
169
+ const versionInstalada = estado.versionSistema || 'desconocida';
170
+ const ubicacion = esGlobal ? 'global' : 'proyecto';
171
+
172
+ const necesitaActualizar = versionInstalada !== VERSION;
173
+ const paqueteDesactualizado = VERSION_LOCAL !== VERSION;
174
+
175
+ console.log(` ${color.negrita(`Runtime: ${runtime.nombre}`)} ${color.dim(`(${ubicacion})`)}`);
176
+ console.log(` Versión instalada: ${necesitaActualizar ? color.amarillo(versionInstalada) : color.verde(versionInstalada)}`);
177
+ console.log(` Versión disponible: ${color.verde(VERSION)}${paqueteDesactualizado ? color.dim(` (ejecutando con v${VERSION_LOCAL})`) : ''}`);
178
+ console.log(` Perfil: ${estado.perfil}`);
179
+ console.log(` Componentes: ${estado.componentesInstalados.length}`);
180
+ console.log(` Archivos: ${estado.archivosInstalados.length}`);
181
+ console.log('');
182
+
183
+ // Si la versión disponible es más reciente pero el paquete ejecutándose es viejo,
184
+ // indicar al usuario que use @latest
185
+ if (paqueteDesactualizado && necesitaActualizar) {
186
+ console.log(formatearAdvertencia(runtime.nombre,
187
+ `Este paquete es v${VERSION_LOCAL} pero la v${VERSION} está disponible.\n` +
188
+ ` Para actualizar, ejecuta: npx @saulwade/swl-ses@latest update --force`));
189
+ console.log('');
190
+ omitidos++;
191
+ continue;
192
+ }
193
+
194
+ // Verificar si ya está actualizado
195
+ if (!necesitaActualizar && !force) {
196
+ console.log(formatearPaso(runtime.nombre, 'Ya está en la versión más reciente'));
197
+ console.log('');
198
+ omitidos++;
199
+ continue;
200
+ }
201
+
202
+ // Confirmar
203
+ if (!force) {
204
+ const confirmar = await preguntarSiNo(
205
+ !necesitaActualizar
206
+ ? `¿Re-instalar ${runtime.nombre} v${VERSION}?`
207
+ : `¿Actualizar ${runtime.nombre} de ${versionInstalada} a ${VERSION}?`
208
+ );
209
+ console.log('');
210
+
211
+ if (!confirmar) {
212
+ console.log(formatearAdvertencia(runtime.nombre, 'Actualización cancelada por el usuario'));
213
+ console.log('');
214
+ omitidos++;
215
+ continue;
216
+ }
217
+ }
218
+
219
+ // Ejecutar actualización con spinner
220
+ const sp = spinner(`Actualizando ${runtime.nombre}...`);
221
+
222
+ try {
223
+ const instalador = require('./instalador');
224
+ await instalador({
225
+ target: runtime.id,
226
+ profile: estado.perfil,
227
+ global: estado.global,
228
+ force: true,
229
+ });
230
+
231
+ sp.exito(`${runtime.nombre} actualizado a v${VERSION} (${estado.archivosInstalados.length} archivos)`);
232
+ actualizados++;
233
+ } catch (err) {
234
+ sp.fallo(`Error actualizando ${runtime.nombre}: ${err.message}`);
235
+ }
236
+
237
+ console.log('');
238
+ }
239
+ }
240
+
241
+ // 3. Reporte final
242
+ console.log(` ${lineaSeparadora()}`);
243
+ if (actualizados > 0) {
244
+ console.log(formatearPaso('Resultado', `${actualizados} runtime(s) actualizado(s)${omitidos > 0 ? `, ${omitidos} omitido(s)` : ''}`));
245
+ } else if (omitidos > 0) {
246
+ console.log(formatearPaso('Resultado', 'Todo está actualizado'));
247
+ } else {
248
+ console.log(formatearAdvertencia('Resultado', 'Ningún runtime fue actualizado'));
249
+ }
250
+ console.log('');
251
+ }
252
+
253
+ module.exports = update;