@saulwade/swl-ses 2.2.3 → 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.
Files changed (66) hide show
  1. package/CLAUDE.md +4 -4
  2. package/README.md +3 -3
  3. package/agentes/arquitecto-swl.md +1 -1
  4. package/agentes/auto-evolucion-swl.md +908 -908
  5. package/agentes/backend-csharp-swl.md +1 -1
  6. package/agentes/backend-go-swl.md +1 -1
  7. package/agentes/backend-java-swl.md +1 -1
  8. package/agentes/backend-rust-swl.md +1 -1
  9. package/agentes/evals/orquestador-swl.evals.json +1 -1
  10. package/agentes/implementador-swl.md +2 -2
  11. package/agentes/llm-apps-swl.md +1 -1
  12. package/agentes/orquestador-swl.md +7 -7
  13. package/agentes/pagos-swl.md +1 -1
  14. package/agentes/perfilador-usuario-swl.md +321 -321
  15. package/agentes/planificador-swl.md +1 -1
  16. package/agentes/producto-prd-swl.md +1 -1
  17. package/agentes/revisor-seguridad-swl.md +1 -1
  18. package/agentes/sre-swl.md +1 -1
  19. package/comandos/swl/aprobar-plan.md +146 -141
  20. package/comandos/swl/release.md +45 -8
  21. package/comandos/swl/status.md +343 -333
  22. package/comandos/swl/verificar.md +817 -817
  23. package/habilidades/compactacion-contexto/SKILL.md +3 -3
  24. package/habilidades/contenedores-docker/SKILL.md +3 -1
  25. package/habilidades/context-builder/SKILL.md +4 -4
  26. package/habilidades/control-profundidad/SKILL.md +5 -5
  27. package/habilidades/ejecutar-fase/SKILL.md +564 -541
  28. package/habilidades/extractor-de-aprendizajes/SKILL.md +3 -3
  29. package/habilidades/guardrail-semantico/SKILL.md +1 -1
  30. package/habilidades/harness-claude-code/SKILL.md +305 -305
  31. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +1 -1
  32. package/habilidades/planear-fase/SKILL.md +15 -1
  33. package/habilidades/proceso-debate-adversarial/SKILL.md +191 -164
  34. package/habilidades/prompt-engineering/SKILL.md +5 -5
  35. package/habilidades/release-semver/SKILL.md +4 -1
  36. package/habilidades/tdd-workflow/SKILL.md +33 -1
  37. package/habilidades/workflow-claude-code/SKILL.md +6 -6
  38. package/hooks/captura-acciones-post.js +151 -0
  39. package/hooks/captura-acciones-session.js +155 -0
  40. package/hooks/lib/briefing.js +512 -474
  41. package/hooks/lib/captura-acciones.js +392 -0
  42. package/hooks/lib/context-builder.js +2 -2
  43. package/hooks/lib/model-router.js +1 -1
  44. package/hooks/lib/token-estimator.js +2 -0
  45. package/hooks/linea-estado.js +7 -5
  46. package/llms.txt +29 -29
  47. package/manifiestos/canonical-hashes.json +1964 -1310
  48. package/manifiestos/hooks-config.json +18 -0
  49. package/manifiestos/modulos.json +4 -0
  50. package/manifiestos/skills-lock.json +1282 -1282
  51. package/package.json +93 -93
  52. package/plugin.json +371 -371
  53. package/reglas/git-coauthor.md +100 -100
  54. package/reglas/memoria-consolidada.md +41 -0
  55. package/reglas/pruebas.md +15 -0
  56. package/reglas/seguridad-agentes.md +66 -0
  57. package/schemas/agent-frontmatter.schema.json +294 -294
  58. package/schemas/instinct.schema.json +161 -152
  59. package/scripts/bootstrap-instintos.js +25 -7
  60. package/scripts/lib/claude-sessions.js +1 -0
  61. package/scripts/lib/gitignore-manifest.js +44 -11
  62. package/scripts/lib/skill-metrics.js +41 -1
  63. package/scripts/lib/tool-cost-analyzer.js +1 -0
  64. package/scripts/publicar.js +34 -85
  65. package/scripts/verificar-trazabilidad.js +329 -298
  66. package/scripts/vendor/claude-usage/__pycache__/scanner.cpython-314.pyc +0 -0
@@ -1,152 +1,161 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://swl-ses.dev/schemas/instinct.json",
4
- "$schemaVersion": "1.0.0",
5
- "title": "SWL Instinct",
6
- "description": "Schema de validacion para instintos YAML del sistema SWL",
7
- "type": "object",
8
- "required": [
9
- "version",
10
- "instintos"
11
- ],
12
- "properties": {
13
- "version": {
14
- "type": "string"
15
- },
16
- "generado": {
17
- "type": "string"
18
- },
19
- "total_instintos": {
20
- "type": "integer",
21
- "minimum": 0
22
- },
23
- "instintos": {
24
- "type": "array",
25
- "items": {
26
- "type": "object",
27
- "required": [
28
- "id",
29
- "pattern",
30
- "confidence",
31
- "scope",
32
- "status"
33
- ],
34
- "properties": {
35
- "id": {
36
- "type": "string",
37
- "pattern": "^inst-\\d{3,}$",
38
- "description": "Identificador unico secuencial"
39
- },
40
- "pattern": {
41
- "type": "string",
42
- "minLength": 10,
43
- "maxLength": 500,
44
- "description": "Descripcion del patron observado"
45
- },
46
- "confidence": {
47
- "type": "number",
48
- "minimum": 0,
49
- "maximum": 1,
50
- "description": "Nivel de confianza (0.0 bajo, 1.0 certeza)"
51
- },
52
- "scope": {
53
- "type": "string",
54
- "enum": [
55
- "project",
56
- "domain",
57
- "global"
58
- ],
59
- "description": "Alcance del instinto"
60
- },
61
- "source": {
62
- "type": "string",
63
- "description": "ID de sesion o 'manual'"
64
- },
65
- "evidence_count": {
66
- "type": "integer",
67
- "minimum": 0,
68
- "description": "Veces que el patron ha sido observado"
69
- },
70
- "last_validated": {
71
- "type": "string",
72
- "description": "Fecha de ultima validacion (YYYY-MM-DD)"
73
- },
74
- "contradictions": {
75
- "type": "integer",
76
- "minimum": 0,
77
- "description": "Veces que el patron ha sido contradicho"
78
- },
79
- "status": {
80
- "type": "string",
81
- "enum": [
82
- "active",
83
- "degraded",
84
- "promoted",
85
- "archived"
86
- ],
87
- "description": "Estado actual del instinto"
88
- },
89
- "tags": {
90
- "type": "array",
91
- "items": {
92
- "type": "string"
93
- },
94
- "description": "Etiquetas para clasificacion"
95
- },
96
- "created_at": {
97
- "type": "string",
98
- "description": "Fecha de creacion (YYYY-MM-DD)"
99
- },
100
- "decay_half_life_days": {
101
- "type": "integer",
102
- "minimum": 1,
103
- "default": 90,
104
- "description": "Dias para que la confianza decaiga a la mitad. 90 dias por defecto."
105
- },
106
- "helpful_count": {
107
- "type": "integer",
108
- "minimum": 0,
109
- "default": 0,
110
- "description": "Numero de veces que el instinto fue util en una sesion"
111
- },
112
- "harmful_count": {
113
- "type": "integer",
114
- "minimum": 0,
115
- "default": 0,
116
- "description": "Numero de veces que el instinto guio a un resultado incorrecto"
117
- },
118
- "last_validated_at": {
119
- "type": "string",
120
- "description": "Timestamp ISO o YYYY-MM-DD de la ultima validacion via feedback (sustituye a last_validated en sistemas con scoring)"
121
- },
122
- "source_sessions": {
123
- "type": "array",
124
- "items": {
125
- "type": "string"
126
- },
127
- "uniqueItems": true,
128
- "description": "IDs de sesiones que contribuyeron a este instinto (para priorizacion por evidencia multiple)"
129
- },
130
- "source_agents": {
131
- "type": "array",
132
- "items": {
133
- "type": "string"
134
- },
135
- "uniqueItems": true,
136
- "description": "Nombres de agentes que validaron este instinto (cross-agent enrichment)"
137
- },
138
- "maturity": {
139
- "type": "string",
140
- "enum": [
141
- "candidate",
142
- "established",
143
- "proven",
144
- "deprecated"
145
- ],
146
- "description": "Estado de maduracion derivado (computado, no persistente). Ver scripts/lib/scoring-instintos.js"
147
- }
148
- }
149
- }
150
- }
151
- }
152
- }
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://swl-ses.dev/schemas/instinct.json",
4
+ "$schemaVersion": "1.0.0",
5
+ "title": "SWL Instinct",
6
+ "description": "Schema de validacion para instintos YAML del sistema SWL",
7
+ "type": "object",
8
+ "required": [
9
+ "version",
10
+ "instintos"
11
+ ],
12
+ "properties": {
13
+ "version": {
14
+ "type": "string"
15
+ },
16
+ "generado": {
17
+ "type": "string"
18
+ },
19
+ "total_instintos": {
20
+ "type": "integer",
21
+ "minimum": 0
22
+ },
23
+ "instintos": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "object",
27
+ "required": [
28
+ "id",
29
+ "pattern",
30
+ "confidence",
31
+ "scope",
32
+ "status"
33
+ ],
34
+ "properties": {
35
+ "id": {
36
+ "type": "string",
37
+ "pattern": "^inst-\\d{3,}$",
38
+ "description": "Identificador unico secuencial"
39
+ },
40
+ "pattern": {
41
+ "type": "string",
42
+ "minLength": 10,
43
+ "maxLength": 500,
44
+ "description": "Descripcion del patron observado"
45
+ },
46
+ "confidence": {
47
+ "type": "number",
48
+ "minimum": 0,
49
+ "maximum": 1,
50
+ "description": "Nivel de confianza (0.0 bajo, 1.0 certeza)"
51
+ },
52
+ "scope": {
53
+ "type": "string",
54
+ "enum": [
55
+ "project",
56
+ "domain",
57
+ "global"
58
+ ],
59
+ "description": "Alcance del instinto"
60
+ },
61
+ "source": {
62
+ "type": "string",
63
+ "description": "ID de sesion o 'manual'"
64
+ },
65
+ "evidence_count": {
66
+ "type": "integer",
67
+ "minimum": 0,
68
+ "description": "Veces que el patron ha sido observado"
69
+ },
70
+ "last_validated": {
71
+ "type": "string",
72
+ "description": "Fecha de ultima validacion (YYYY-MM-DD)"
73
+ },
74
+ "contradictions": {
75
+ "type": "integer",
76
+ "minimum": 0,
77
+ "description": "Veces que el patron ha sido contradicho"
78
+ },
79
+ "status": {
80
+ "type": "string",
81
+ "enum": [
82
+ "active",
83
+ "degraded",
84
+ "promoted",
85
+ "archived"
86
+ ],
87
+ "description": "Estado actual del instinto"
88
+ },
89
+ "tags": {
90
+ "type": "array",
91
+ "items": {
92
+ "type": "string"
93
+ },
94
+ "description": "Etiquetas para clasificacion"
95
+ },
96
+ "created_at": {
97
+ "type": "string",
98
+ "description": "Fecha de creacion (YYYY-MM-DD)"
99
+ },
100
+ "decay_half_life_days": {
101
+ "type": "integer",
102
+ "minimum": 1,
103
+ "default": 90,
104
+ "description": "Dias para que la confianza decaiga a la mitad. 90 dias por defecto."
105
+ },
106
+ "helpful_count": {
107
+ "type": "integer",
108
+ "minimum": 0,
109
+ "default": 0,
110
+ "description": "Numero de veces que el instinto fue util en una sesion"
111
+ },
112
+ "harmful_count": {
113
+ "type": "integer",
114
+ "minimum": 0,
115
+ "default": 0,
116
+ "description": "Numero de veces que el instinto guio a un resultado incorrecto"
117
+ },
118
+ "last_validated_at": {
119
+ "type": "string",
120
+ "description": "Timestamp ISO o YYYY-MM-DD de la ultima validacion via feedback (sustituye a last_validated en sistemas con scoring)"
121
+ },
122
+ "source_sessions": {
123
+ "type": "array",
124
+ "items": {
125
+ "type": "string"
126
+ },
127
+ "uniqueItems": true,
128
+ "description": "IDs de sesiones que contribuyeron a este instinto (para priorizacion por evidencia multiple)"
129
+ },
130
+ "source_agents": {
131
+ "type": "array",
132
+ "items": {
133
+ "type": "string"
134
+ },
135
+ "uniqueItems": true,
136
+ "description": "Nombres de agentes que validaron este instinto (cross-agent enrichment)"
137
+ },
138
+ "maturity": {
139
+ "type": "string",
140
+ "enum": [
141
+ "candidate",
142
+ "established",
143
+ "proven",
144
+ "deprecated"
145
+ ],
146
+ "description": "Estado de maduracion derivado (computado, no persistente). Ver scripts/lib/scoring-instintos.js"
147
+ },
148
+ "tipo_memoria": {
149
+ "type": "string",
150
+ "enum": [
151
+ "semantic",
152
+ "episodic",
153
+ "procedural"
154
+ ],
155
+ "description": "Eje cognitivo de memoria (ORTOGONAL al canal de almacenamiento): semantic=hechos/decisiones, episodic=experiencia pasada de una sesion, procedural=como manejar un caso. Opcional y backward-compatible: instintos sin el campo siguen validos. Ver reglas/memoria-consolidada.md."
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
@@ -49,15 +49,31 @@ const flags = {
49
49
  // Config de secciones → confidence/tags
50
50
  // ---------------------------------------------------------------------------
51
51
 
52
+ // tipo_memoria (eje cognitivo, Fase 19): patron/anti-patron = "cómo hacer / qué
53
+ // evitar" → procedural; decisión/gotcha/regla = hechos → semantic. episodic NO se
54
+ // infiere del bootstrap (nace de registros de sesión, no de APRENDIZAJES).
52
55
  const SECCIONES = [
53
- { regex: /anti[-\s]?patrones?/i, confidence: 0.7, tags: ['anti-patron'] },
54
- { regex: /patrones?\s+exitosos?/i, confidence: 0.6, tags: ['patron'] },
55
- { regex: /decisiones?\s+de\s+arquitectura/i, confidence: 0.8, tags: ['decision', 'arquitectura'] },
56
- { regex: /gotchas?/i, confidence: 0.6, tags: ['gotcha'] },
57
- { regex: /patrones?/i, confidence: 0.6, tags: ['patron'] },
58
- { regex: /reglas?\s+de\s+proyecto/i, confidence: 0.8, tags: ['regla-proyecto'] },
56
+ { regex: /anti[-\s]?patrones?/i, confidence: 0.7, tags: ['anti-patron'], tipoMemoria: 'procedural' },
57
+ { regex: /patrones?\s+exitosos?/i, confidence: 0.6, tags: ['patron'], tipoMemoria: 'procedural' },
58
+ { regex: /decisiones?\s+de\s+arquitectura/i, confidence: 0.8, tags: ['decision', 'arquitectura'], tipoMemoria: 'semantic' },
59
+ { regex: /gotchas?/i, confidence: 0.6, tags: ['gotcha'], tipoMemoria: 'semantic' },
60
+ { regex: /patrones?/i, confidence: 0.6, tags: ['patron'], tipoMemoria: 'procedural' },
61
+ { regex: /reglas?\s+de\s+proyecto/i, confidence: 0.8, tags: ['regla-proyecto'], tipoMemoria: 'semantic' },
59
62
  ];
60
63
 
64
+ /**
65
+ * Infiere el eje cognitivo de memoria de un instinto por su sección de origen.
66
+ * Heurístico declarado (no autoritativo): default 'semantic' para hechos.
67
+ * @param {string} seccion
68
+ * @returns {'semantic'|'episodic'|'procedural'}
69
+ */
70
+ function inferirTipoMemoria(seccion) {
71
+ for (const s of SECCIONES) {
72
+ if (s.regex.test(seccion || '')) return s.tipoMemoria;
73
+ }
74
+ return 'semantic';
75
+ }
76
+
61
77
  function detectarConfidence(seccion) {
62
78
  for (const s of SECCIONES) {
63
79
  if (s.regex.test(seccion)) return { confidence: s.confidence, tags: [...s.tags] };
@@ -163,6 +179,7 @@ function generarInstintos(entradas) {
163
179
  tags,
164
180
  created_at: hoy,
165
181
  seccion_origen: e.seccion,
182
+ tipo_memoria: inferirTipoMemoria(e.seccion),
166
183
  decay_half_life_days: 90,
167
184
  helpful_count: 0,
168
185
  harmful_count: 0,
@@ -200,6 +217,7 @@ instintos:
200
217
  ` tags: [${i.tags.map(t => `"${t}"`).join(', ')}]`,
201
218
  ` created_at: "${i.created_at}"`,
202
219
  ` seccion_origen: ${escapeYaml(i.seccion_origen)}`,
220
+ ` tipo_memoria: ${i.tipo_memoria}`,
203
221
  ` decay_half_life_days: ${i.decay_half_life_days}`,
204
222
  ` helpful_count: ${i.helpful_count}`,
205
223
  ` harmful_count: ${i.harmful_count}`,
@@ -266,4 +284,4 @@ function main() {
266
284
 
267
285
  if (require.main === module) main();
268
286
 
269
- module.exports = { parsearAprendizajes, generarInstintos, detectarConfidence, main };
287
+ module.exports = { parsearAprendizajes, generarInstintos, detectarConfidence, inferirTipoMemoria, main };
@@ -27,6 +27,7 @@ const TOLERANCIA_FUTURO_MS = 60 * 1000;
27
27
 
28
28
  // Tabla de precios por modelo (USD por token)
29
29
  const PRECIOS_MODELO = {
30
+ 'claude-opus-4-8': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
30
31
  'claude-opus-4-7': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
31
32
  'claude-opus-4-6': { entrada: 15 / 1_000_000, salida: 75 / 1_000_000 },
32
33
  'claude-sonnet-4-6': { entrada: 3 / 1_000_000, salida: 15 / 1_000_000 },
@@ -11,8 +11,9 @@
11
11
  * Todo lo que instala o genera en runtime se ignora en git (framework, hooks,
12
12
  * métricas, sesiones, aprendizajes, trazas, instintos).
13
13
  *
14
- * Solo se commitean: CLAUDE.md, .claude/settings.json, y las plantillas de
15
- * .planning/ que el equipo edita (PROYECTO.md, REQUISITOS.md, etc.).
14
+ * Solo se commitean: CLAUDE.md, .claude/settings.json, instintos/proyecto.yaml
15
+ * (memoria del proyecto), y las plantillas de .planning/ que el equipo edita
16
+ * (PROYECTO.md, REQUISITOS.md, etc.).
16
17
  */
17
18
 
18
19
  const fs = require("fs");
@@ -54,11 +55,8 @@ const ENTRADAS_BASE = [
54
55
  ".swl-ses",
55
56
 
56
57
  // ── Runtime: archivos generados automáticamente por hooks ──────────
57
- // Aprendizajes, métricas, compactación (extraccion-aprendizajes, tracking-costos, context-compressor)
58
- ".planning/APRENDIZAJES.md",
58
+ // Métricas (tracking-costos)
59
59
  ".planning/METRICAS.md",
60
- ".planning/COMPACTACION.md",
61
- ".planning/REPORTE-GRAFO.md",
62
60
 
63
61
  // Audit trail (audit-trail.js, merkle-audit.js)
64
62
  ".planning/*.jsonl",
@@ -88,8 +86,13 @@ const ENTRADAS_BASE = [
88
86
  "!.planning/locks/*PLAN.md.lock",
89
87
  ".planning/user-profile/",
90
88
 
91
- // Instintos modificados automáticamente por hooks (degradacion-instintos.js)
92
- "instintos/",
89
+ // Instintos: global/perfil-usuario/prompt-appendices/autonomia son framework +
90
+ // runtime + personal (no se commitean). proyecto.yaml es memoria del proyecto
91
+ // (hermano YAML de APRENDIZAJES.md, memoria-consolidada § canal 3) → versionable.
92
+ // Patrón dir/* + negación: git no re-incluye archivos bajo un dir ignorado
93
+ // (mismo gotcha documentado en locks/ arriba), por eso se ignora el contenido.
94
+ "instintos/*",
95
+ "!instintos/proyecto.yaml",
93
96
 
94
97
  // Base de datos de uso
95
98
  "usage.db",
@@ -305,8 +308,16 @@ function limpiarTracked(cwd, extras = []) {
305
308
  return resultado; // No es repo git, nada que hacer
306
309
  }
307
310
 
308
- // Obtener todas las entradas gitignore de swl-ses (directorios y archivos)
309
- const entradas = [...new Set([...ENTRADAS_BASE, ...extras])];
311
+ // Obtener todas las entradas gitignore de swl-ses (directorios y archivos).
312
+ // Las negaciones (!...) marcan archivos versionables que NUNCA se des-trackean
313
+ // (instintos/proyecto.yaml, plan-locks G1 *PLAN.md.lock). git ls-files NO honra
314
+ // las negaciones del .gitignore, así que se excluyen explícitamente de la lista
315
+ // a limpiar — de lo contrario el glob padre (instintos/*, locks/*) las arrastraría.
316
+ const todas = [...new Set([...ENTRADAS_BASE, ...extras])];
317
+ const negaciones = todas
318
+ .filter((e) => e.startsWith("!"))
319
+ .map((e) => patronAGlobRegex(e.slice(1)));
320
+ const entradas = todas.filter((e) => !e.startsWith("!"));
310
321
 
311
322
  for (const entrada of entradas) {
312
323
  try {
@@ -322,7 +333,12 @@ function limpiarTracked(cwd, extras = []) {
322
333
 
323
334
  if (!archivosTracked) continue;
324
335
 
325
- const lista = archivosTracked.split("\n").filter(Boolean);
336
+ const lista = archivosTracked
337
+ .split("\n")
338
+ .filter(Boolean)
339
+ // Preservar archivos versionables cubiertos por una negación del gitignore
340
+ // (ej. instintos/proyecto.yaml frente al glob instintos/*).
341
+ .filter((archivo) => !negaciones.some((re) => re.test(archivo)));
326
342
  if (lista.length === 0) continue;
327
343
 
328
344
  // Quitar del índice uno por uno (--cached = solo del índice, no del disco)
@@ -353,6 +369,23 @@ function escapeRegex(str) {
353
369
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
354
370
  }
355
371
 
372
+ /**
373
+ * Convierte un patrón estilo gitignore (sin el '!' inicial) a una RegExp anclada
374
+ * para matchear rutas que devuelve `git ls-files` (siempre con '/' como separador).
375
+ * `*` no cruza '/' (semántica gitignore). Una entrada de directorio matchea también
376
+ * su contenido. Se usa en limpiarTracked para excluir archivos negados del untrack.
377
+ *
378
+ * @param {string} patron Ej. "instintos/proyecto.yaml", ".planning/locks/*PLAN.md.lock"
379
+ * @returns {RegExp}
380
+ */
381
+ function patronAGlobRegex(patron) {
382
+ const cuerpo = patron
383
+ .replace(/\/$/, "")
384
+ .replace(/[.+?^${}()|[\]\\]/g, "\\$&")
385
+ .replace(/\*/g, "[^/]*");
386
+ return new RegExp("^" + cuerpo + "(?:/.*)?$");
387
+ }
388
+
356
389
  module.exports = {
357
390
  actualizarGitignore,
358
391
  entradasParaRuntime,
@@ -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');
@@ -23,6 +23,7 @@ const DB_PATH = path.join(os.homedir(), '.claude', 'usage.db');
23
23
 
24
24
  // Precios estimados por 1M tokens (USD) — actualizados a 2026-04-17
25
25
  const PRECIOS = {
26
+ 'claude-opus-4-8': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
26
27
  'claude-opus-4-7': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
27
28
  'claude-opus-4-6': { input: 15.00, output: 75.00, cache_read: 1.50, cache_creation: 18.75 },
28
29
  'claude-sonnet-4-6': { input: 3.00, output: 15.00, cache_read: 0.30, cache_creation: 3.75 },