@saulwade/swl-ses 2.2.4 → 2.3.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.
@@ -199,11 +199,38 @@ function resumen(cwd) {
199
199
  };
200
200
  }
201
201
 
202
+ const MS_DIA = 86400000;
203
+
204
+ /**
205
+ * Skills "fríos": tuvieron uso previo pero su último uso es más antiguo que `dias`.
206
+ * Los skills NUNCA usados NO aparecen (obtenerMetricas solo incluye skills con ≥1
207
+ * entrada) — eso es bloat, dominio distinto a staleness (Fase 20 / análisis self-learning).
208
+ *
209
+ * @param {string} cwd
210
+ * @param {Date} [hoy=new Date()]
211
+ * @param {{dias?: number}} [opts] umbral en días (default SWL_SKILL_STALE_DIAS o 90)
212
+ * @returns {Array<{skill: string, ultimoUso: string, diasInactivo: number}>}
213
+ * ordenado por diasInactivo descendente.
214
+ */
215
+ function skillsStale(cwd, hoy = new Date(), opts = {}) {
216
+ const dias = Number(opts.dias) || Number(process.env.SWL_SKILL_STALE_DIAS) || 90;
217
+ const ahora = (hoy instanceof Date ? hoy : new Date(hoy)).getTime();
218
+ const out = [];
219
+ for (const m of obtenerMetricas(cwd)) {
220
+ if (!m.ultimoUso) continue;
221
+ const t = new Date(m.ultimoUso).getTime();
222
+ if (Number.isNaN(t)) continue;
223
+ const diasInactivo = Math.floor((ahora - t) / MS_DIA);
224
+ if (diasInactivo > dias) out.push({ skill: m.skill, ultimoUso: m.ultimoUso, diasInactivo });
225
+ }
226
+ return out.sort((a, b) => b.diasInactivo - a.diasInactivo);
227
+ }
228
+
202
229
  // ---------------------------------------------------------------------------
203
230
  // Exports
204
231
  // ---------------------------------------------------------------------------
205
232
 
206
- module.exports = { registrarUso, obtenerMetricas, topFallidos, topLentos, resumen };
233
+ module.exports = { registrarUso, obtenerMetricas, topFallidos, topLentos, resumen, skillsStale };
207
234
 
208
235
  // ---------------------------------------------------------------------------
209
236
  // CLI dual-use
@@ -218,6 +245,19 @@ if (require.main === module) {
218
245
  process.exit(0);
219
246
  }
220
247
 
248
+ if (cmd === '--stale' || cmd === 'stale') {
249
+ const stale = skillsStale(cwd);
250
+ if (stale.length === 0) {
251
+ process.stdout.write('Sin skills fríos (todos con uso reciente o sin métricas).\n');
252
+ } else {
253
+ process.stdout.write('\nSkills fríos (uso previo, inactivos > umbral):\n');
254
+ for (const s of stale) {
255
+ process.stdout.write(` ${s.skill.padEnd(36)} ${String(s.diasInactivo).padStart(4)}d sin uso → /swl:evaluar-skill ${s.skill}\n`);
256
+ }
257
+ }
258
+ process.exit(0);
259
+ }
260
+
221
261
  // Modo resumen (default)
222
262
  const r = resumen(cwd);
223
263
  process.stdout.write('\nMétricas de habilidades SWL\n');