@saulwade/swl-ses 1.3.7 → 1.4.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 (129) hide show
  1. package/CLAUDE.md +12 -4
  2. package/README.md +1 -1
  3. package/bin/swl-mcp-server.js +187 -187
  4. package/bin/swl-webhook-server.js +198 -0
  5. package/comandos/swl/.evolved.json +22 -22
  6. package/comandos/swl/adoptar-proyecto.md +21 -1
  7. package/comandos/swl/claudemd.md +14 -1
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/exportar-vault.md +207 -7
  10. package/comandos/swl/nuevo-proyecto.md +24 -2
  11. package/gateway/adapters/base.js +109 -0
  12. package/gateway/adapters/discord.js +167 -0
  13. package/gateway/adapters/email.js +221 -0
  14. package/gateway/adapters/slack.js +192 -0
  15. package/gateway/adapters/telegram.js +183 -0
  16. package/gateway/adapters/webhook.js +113 -0
  17. package/gateway/adapters/whatsapp.js +214 -0
  18. package/gateway/agent-executor.js +322 -0
  19. package/gateway/command-relay.js +271 -0
  20. package/gateway/cron/jobs.js +263 -0
  21. package/gateway/cron/scheduler.js +322 -0
  22. package/gateway/cron/store.js +335 -0
  23. package/gateway/index.js +320 -0
  24. package/gateway/lib/event-channel.js +191 -0
  25. package/gateway/session.js +131 -0
  26. package/gateway/webhook-server.js +324 -0
  27. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  28. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  29. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  30. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  31. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  32. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  33. package/habilidades/eval-framework/SKILL.md +212 -212
  34. package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
  35. package/habilidades/harness-claude-code/SKILL.md +299 -299
  36. package/habilidades/infra-github-actions/SKILL.md +166 -166
  37. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  38. package/habilidades/manejo-errores/.evolved.json +8 -8
  39. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  40. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  41. package/habilidades/nextjs-testing/SKILL.md +89 -5
  42. package/habilidades/node-experto/SKILL.md +37 -1
  43. package/habilidades/patrones-python/SKILL.md +229 -229
  44. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  45. package/habilidades/planear-fase/SKILL.md +319 -319
  46. package/habilidades/react-experto/SKILL.md +45 -4
  47. package/habilidades/release-semver/.evolved.json +8 -8
  48. package/habilidades/swl-claudemd/SKILL.md +15 -1
  49. package/habilidades/tdd-workflow/SKILL.md +36 -4
  50. package/habilidades/testing-python/SKILL.md +340 -340
  51. package/hooks/claudemd-bloat-detector.js +161 -161
  52. package/hooks/inyeccion-contexto.js +8 -3
  53. package/hooks/lib/agent-routing.js +107 -107
  54. package/hooks/lib/auto-consolidator.js +335 -335
  55. package/hooks/lib/error-classifier.js +308 -308
  56. package/hooks/lib/merkle-audit.js +96 -96
  57. package/hooks/lib/provenance-tracker.js +191 -191
  58. package/hooks/lib/rate-limit-ip.js +177 -0
  59. package/hooks/lib/rate-limit-tracker.js +253 -253
  60. package/hooks/lib/resource-quota.js +122 -122
  61. package/hooks/lib/retry-jitter.js +165 -165
  62. package/hooks/lib/skill-auditor.js +588 -588
  63. package/hooks/lib/sync-status.js +228 -228
  64. package/hooks/lib/taint-tracker.js +107 -107
  65. package/hooks/lib/text-similarity.js +241 -241
  66. package/hooks/lib/toon-compressor.js +245 -245
  67. package/hooks/lib/webhook-dedup.js +184 -0
  68. package/hooks/lib/webhook-verify.js +123 -0
  69. package/hooks/proteccion-rutas.js +120 -15
  70. package/hooks/registro-turnos.js +209 -209
  71. package/hooks/sugerir-regenerar-inventario.js +170 -170
  72. package/hooks/validar-formato-post-subagente.js +140 -140
  73. package/hooks/validar-memoria-hook.js +218 -218
  74. package/instintos/prompt-appendices.yaml +57 -57
  75. package/manifiestos/agent-output-schemas.json +57 -57
  76. package/manifiestos/modulos.json +1 -0
  77. package/manifiestos/skills-lock.json +37 -37
  78. package/package.json +5 -3
  79. package/plantillas/auditor-veto-template.md +105 -105
  80. package/plantillas/github-workflows/README.md +47 -47
  81. package/plantillas/github-workflows/release-please.yml +44 -44
  82. package/plantillas/github-workflows/swl-ci.yml +107 -107
  83. package/plantillas/github-workflows/swl-security.yml +51 -51
  84. package/plugin.json +1 -1
  85. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  86. package/reglas/arreglar-al-detectar.md +147 -147
  87. package/reglas/fragmentos-compartidos.md +152 -152
  88. package/reglas/harness-claude-code.md +213 -213
  89. package/reglas/usar-context7.md +226 -226
  90. package/reglas/usar-sistema-swl.md +251 -0
  91. package/schemas/diary-entry.schema.json +80 -80
  92. package/scripts/benchmark-memoria.js +167 -167
  93. package/scripts/comandos/skills.js +251 -2
  94. package/scripts/configurar-branch-protection.js +418 -418
  95. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  96. package/scripts/field-report.js +199 -199
  97. package/scripts/generar-checklists-consolidados.js +273 -273
  98. package/scripts/generar-inventario.js +420 -420
  99. package/scripts/generar-matriz-lenguajes.js +271 -271
  100. package/scripts/lib/artefactos-python.js +43 -43
  101. package/scripts/lib/benchmark-metrics.js +160 -160
  102. package/scripts/lib/budget-enforcer.js +252 -252
  103. package/scripts/lib/configurar-ci.js +380 -380
  104. package/scripts/lib/contadores-inventario.js +217 -217
  105. package/scripts/lib/detectar-stack-detallado.js +307 -307
  106. package/scripts/lib/diary-entry.js +234 -234
  107. package/scripts/lib/eval-metrics-store.js +218 -218
  108. package/scripts/lib/eval-quality.js +171 -171
  109. package/scripts/lib/eval-schemas.js +144 -144
  110. package/scripts/lib/eval-self-correct.js +106 -106
  111. package/scripts/lib/eval-validator.js +185 -185
  112. package/scripts/lib/jaccard-similarity.js +98 -98
  113. package/scripts/lib/longmemeval-runner.js +125 -125
  114. package/scripts/lib/npm-version.js +261 -261
  115. package/scripts/lib/paquetes-conocidos.js +50 -50
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/limpiar-artefactos-python.js +131 -131
  121. package/scripts/mcp-server/README.md +128 -128
  122. package/scripts/mcp-server/handlers.js +206 -206
  123. package/scripts/migrar-csv-a-array.js +168 -168
  124. package/scripts/migrar-fase-dominio.js +201 -201
  125. package/scripts/publicar.js +511 -511
  126. package/scripts/run-eval.js +141 -141
  127. package/scripts/validar-manifest.js +195 -195
  128. package/scripts/validar-userland-vacio.js +110 -110
  129. package/scripts/verificar-release.js +110 -0
@@ -1,199 +1,199 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * field-report.js — Generador de reportes de validación en campo.
6
- *
7
- * Analiza sesiones JSONL de Claude Code para extraer métricas reales de uso
8
- * de skills y agentes SWL en proyectos de usuario.
9
- *
10
- * Uso:
11
- * node scripts/field-report.js # Todas las sesiones
12
- * node scripts/field-report.js --days=30 # Últimos 30 días
13
- * node scripts/field-report.js --project=/path # Proyecto específico
14
- * node scripts/field-report.js --output=json # Salida JSON
15
- *
16
- * Métricas extraídas:
17
- * - Skills invocados (Skill("nombre"))
18
- * - Agentes delegados (Agent con description)
19
- * - Comandos /swl:* ejecutados
20
- * - Proyectos donde se usó swl-ses
21
- * - Frecuencia de uso por componente
22
- * - Skills nunca usados (candidatos a poda)
23
- *
24
- * @module scripts/field-report
25
- */
26
-
27
- const fs = require('fs');
28
- const path = require('path');
29
- const os = require('os');
30
-
31
- // ── Argumentos ────────────────────────────────────────────────────────
32
-
33
- const args = process.argv.slice(2).reduce((acc, arg) => {
34
- const [k, v] = arg.replace(/^--/, '').split('=');
35
- acc[k] = v || true;
36
- return acc;
37
- }, {});
38
-
39
- const DAYS = parseInt(args.days) || 0;
40
- const PROJECT = args.project || null;
41
- const OUTPUT = args.output || 'md';
42
- const CLAUDE_DIR = path.join(os.homedir(), '.claude', 'projects');
43
-
44
- // ── Explorar sesiones ─────────────────────────────────────────────────
45
-
46
- function buscarSesiones() {
47
- const sesiones = [];
48
- if (!fs.existsSync(CLAUDE_DIR)) {
49
- console.error(`No se encontró ${CLAUDE_DIR}`);
50
- return sesiones;
51
- }
52
-
53
- const proyectos = fs.readdirSync(CLAUDE_DIR).filter(d =>
54
- fs.statSync(path.join(CLAUDE_DIR, d)).isDirectory()
55
- );
56
-
57
- for (const proy of proyectos) {
58
- if (PROJECT && !proy.includes(PROJECT.replace(/[/\\:]/g, '-'))) continue;
59
- const dir = path.join(CLAUDE_DIR, proy);
60
- const archivos = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl'));
61
- for (const archivo of archivos) {
62
- const ruta = path.join(dir, archivo);
63
- const stat = fs.statSync(ruta);
64
- if (DAYS > 0) {
65
- const hace = Date.now() - DAYS * 86400000;
66
- if (stat.mtimeMs < hace) continue;
67
- }
68
- sesiones.push({ proyecto: proy, ruta, fecha: stat.mtime });
69
- }
70
- }
71
-
72
- return sesiones;
73
- }
74
-
75
- function analizarSesion(ruta) {
76
- const stats = { skills: {}, agentes: {}, comandos: {}, toolCalls: 0 };
77
- try {
78
- const contenido = fs.readFileSync(ruta, 'utf8');
79
- const lineas = contenido.split('\n').filter(Boolean);
80
-
81
- for (const linea of lineas) {
82
- try {
83
- const entry = JSON.parse(linea);
84
- const msg = entry.message || entry;
85
-
86
- // Buscar Skill("nombre") en contenido
87
- const texto = JSON.stringify(msg);
88
- const skillMatches = texto.matchAll(/Skill\s*\(\s*["']([^"']+)["']\s*\)/g);
89
- for (const m of skillMatches) {
90
- stats.skills[m[1]] = (stats.skills[m[1]] || 0) + 1;
91
- }
92
-
93
- // Buscar agentes delegados
94
- const agentMatches = texto.matchAll(/"description"\s*:\s*"([^"]*swl[^"]*)"/gi);
95
- for (const m of agentMatches) {
96
- const nombre = m[1].substring(0, 60);
97
- stats.agentes[nombre] = (stats.agentes[nombre] || 0) + 1;
98
- }
99
-
100
- // Buscar comandos /swl:*
101
- const cmdMatches = texto.matchAll(/\/swl:([a-z-]+)/g);
102
- for (const m of cmdMatches) {
103
- const cmd = `/swl:${m[1]}`;
104
- stats.comandos[cmd] = (stats.comandos[cmd] || 0) + 1;
105
- }
106
-
107
- // Contar tool calls
108
- if (msg.type === 'tool_use' || msg.tool_name) stats.toolCalls++;
109
- } catch { /* línea JSONL inválida, ignorar */ }
110
- }
111
- } catch { /* archivo ilegible, ignorar */ }
112
-
113
- return stats;
114
- }
115
-
116
- // ── Generar reporte ───────────────────────────────────────────────────
117
-
118
- function generarReporte() {
119
- const sesiones = buscarSesiones();
120
- if (sesiones.length === 0) {
121
- console.log('No se encontraron sesiones para analizar.');
122
- console.log(`Directorio buscado: ${CLAUDE_DIR}`);
123
- return;
124
- }
125
-
126
- const totales = { skills: {}, agentes: {}, comandos: {}, sesiones: sesiones.length, proyectos: new Set(), toolCalls: 0 };
127
-
128
- for (const sesion of sesiones) {
129
- totales.proyectos.add(sesion.proyecto);
130
- const stats = analizarSesion(sesion.ruta);
131
- totales.toolCalls += stats.toolCalls;
132
-
133
- for (const [k, v] of Object.entries(stats.skills)) totales.skills[k] = (totales.skills[k] || 0) + v;
134
- for (const [k, v] of Object.entries(stats.agentes)) totales.agentes[k] = (totales.agentes[k] || 0) + v;
135
- for (const [k, v] of Object.entries(stats.comandos)) totales.comandos[k] = (totales.comandos[k] || 0) + v;
136
- }
137
-
138
- // Cargar inventario de skills
139
- const skillsDir = path.join(__dirname, '..', 'habilidades');
140
- const todosSkills = fs.existsSync(skillsDir)
141
- ? fs.readdirSync(skillsDir).filter(d => fs.statSync(path.join(skillsDir, d)).isDirectory())
142
- : [];
143
-
144
- const skillsUsados = new Set(Object.keys(totales.skills));
145
- const skillsNoUsados = todosSkills.filter(s => !skillsUsados.has(s));
146
-
147
- if (OUTPUT === 'json') {
148
- console.log(JSON.stringify({
149
- fecha: new Date().toISOString(),
150
- sesiones: totales.sesiones,
151
- proyectos: [...totales.proyectos],
152
- toolCalls: totales.toolCalls,
153
- skills: Object.entries(totales.skills).sort((a, b) => b[1] - a[1]),
154
- agentes: Object.entries(totales.agentes).sort((a, b) => b[1] - a[1]),
155
- comandos: Object.entries(totales.comandos).sort((a, b) => b[1] - a[1]),
156
- skillsNoUsados,
157
- cobertura: `${skillsUsados.size}/${todosSkills.length}`,
158
- }, null, 2));
159
- return;
160
- }
161
-
162
- // Markdown
163
- const sorted = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1]);
164
-
165
- console.log('# Reporte de Validación en Campo — swl-ses');
166
- console.log(`\n**Fecha**: ${new Date().toISOString().slice(0, 10)}`);
167
- console.log(`**Sesiones analizadas**: ${totales.sesiones}`);
168
- console.log(`**Proyectos**: ${totales.proyectos.size}`);
169
- console.log(`**Tool calls totales**: ${totales.toolCalls}`);
170
- console.log(`**Cobertura de skills**: ${skillsUsados.size}/${todosSkills.length} (${Math.round(skillsUsados.size / todosSkills.length * 100)}%)`);
171
-
172
- console.log('\n## Skills más usados\n');
173
- console.log('| Skill | Invocaciones |');
174
- console.log('|-------|-------------|');
175
- for (const [s, c] of sorted(totales.skills).slice(0, 30)) {
176
- console.log(`| ${s} | ${c} |`);
177
- }
178
-
179
- console.log('\n## Comandos /swl:* más usados\n');
180
- console.log('| Comando | Invocaciones |');
181
- console.log('|---------|-------------|');
182
- for (const [s, c] of sorted(totales.comandos).slice(0, 20)) {
183
- console.log(`| ${s} | ${c} |`);
184
- }
185
-
186
- if (skillsNoUsados.length > 0) {
187
- console.log('\n## Skills nunca usados (candidatos a evaluación)\n');
188
- console.log('| Skill |');
189
- console.log('|-------|');
190
- for (const s of skillsNoUsados.sort()) {
191
- console.log(`| ${s} |`);
192
- }
193
- }
194
-
195
- console.log('\n---');
196
- console.log(`\n> Generado por \`node scripts/field-report.js\``);
197
- }
198
-
199
- generarReporte();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * field-report.js — Generador de reportes de validación en campo.
6
+ *
7
+ * Analiza sesiones JSONL de Claude Code para extraer métricas reales de uso
8
+ * de skills y agentes SWL en proyectos de usuario.
9
+ *
10
+ * Uso:
11
+ * node scripts/field-report.js # Todas las sesiones
12
+ * node scripts/field-report.js --days=30 # Últimos 30 días
13
+ * node scripts/field-report.js --project=/path # Proyecto específico
14
+ * node scripts/field-report.js --output=json # Salida JSON
15
+ *
16
+ * Métricas extraídas:
17
+ * - Skills invocados (Skill("nombre"))
18
+ * - Agentes delegados (Agent con description)
19
+ * - Comandos /swl:* ejecutados
20
+ * - Proyectos donde se usó swl-ses
21
+ * - Frecuencia de uso por componente
22
+ * - Skills nunca usados (candidatos a poda)
23
+ *
24
+ * @module scripts/field-report
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const os = require('os');
30
+
31
+ // ── Argumentos ────────────────────────────────────────────────────────
32
+
33
+ const args = process.argv.slice(2).reduce((acc, arg) => {
34
+ const [k, v] = arg.replace(/^--/, '').split('=');
35
+ acc[k] = v || true;
36
+ return acc;
37
+ }, {});
38
+
39
+ const DAYS = parseInt(args.days) || 0;
40
+ const PROJECT = args.project || null;
41
+ const OUTPUT = args.output || 'md';
42
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude', 'projects');
43
+
44
+ // ── Explorar sesiones ─────────────────────────────────────────────────
45
+
46
+ function buscarSesiones() {
47
+ const sesiones = [];
48
+ if (!fs.existsSync(CLAUDE_DIR)) {
49
+ console.error(`No se encontró ${CLAUDE_DIR}`);
50
+ return sesiones;
51
+ }
52
+
53
+ const proyectos = fs.readdirSync(CLAUDE_DIR).filter(d =>
54
+ fs.statSync(path.join(CLAUDE_DIR, d)).isDirectory()
55
+ );
56
+
57
+ for (const proy of proyectos) {
58
+ if (PROJECT && !proy.includes(PROJECT.replace(/[/\\:]/g, '-'))) continue;
59
+ const dir = path.join(CLAUDE_DIR, proy);
60
+ const archivos = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl'));
61
+ for (const archivo of archivos) {
62
+ const ruta = path.join(dir, archivo);
63
+ const stat = fs.statSync(ruta);
64
+ if (DAYS > 0) {
65
+ const hace = Date.now() - DAYS * 86400000;
66
+ if (stat.mtimeMs < hace) continue;
67
+ }
68
+ sesiones.push({ proyecto: proy, ruta, fecha: stat.mtime });
69
+ }
70
+ }
71
+
72
+ return sesiones;
73
+ }
74
+
75
+ function analizarSesion(ruta) {
76
+ const stats = { skills: {}, agentes: {}, comandos: {}, toolCalls: 0 };
77
+ try {
78
+ const contenido = fs.readFileSync(ruta, 'utf8');
79
+ const lineas = contenido.split('\n').filter(Boolean);
80
+
81
+ for (const linea of lineas) {
82
+ try {
83
+ const entry = JSON.parse(linea);
84
+ const msg = entry.message || entry;
85
+
86
+ // Buscar Skill("nombre") en contenido
87
+ const texto = JSON.stringify(msg);
88
+ const skillMatches = texto.matchAll(/Skill\s*\(\s*["']([^"']+)["']\s*\)/g);
89
+ for (const m of skillMatches) {
90
+ stats.skills[m[1]] = (stats.skills[m[1]] || 0) + 1;
91
+ }
92
+
93
+ // Buscar agentes delegados
94
+ const agentMatches = texto.matchAll(/"description"\s*:\s*"([^"]*swl[^"]*)"/gi);
95
+ for (const m of agentMatches) {
96
+ const nombre = m[1].substring(0, 60);
97
+ stats.agentes[nombre] = (stats.agentes[nombre] || 0) + 1;
98
+ }
99
+
100
+ // Buscar comandos /swl:*
101
+ const cmdMatches = texto.matchAll(/\/swl:([a-z-]+)/g);
102
+ for (const m of cmdMatches) {
103
+ const cmd = `/swl:${m[1]}`;
104
+ stats.comandos[cmd] = (stats.comandos[cmd] || 0) + 1;
105
+ }
106
+
107
+ // Contar tool calls
108
+ if (msg.type === 'tool_use' || msg.tool_name) stats.toolCalls++;
109
+ } catch { /* línea JSONL inválida, ignorar */ }
110
+ }
111
+ } catch { /* archivo ilegible, ignorar */ }
112
+
113
+ return stats;
114
+ }
115
+
116
+ // ── Generar reporte ───────────────────────────────────────────────────
117
+
118
+ function generarReporte() {
119
+ const sesiones = buscarSesiones();
120
+ if (sesiones.length === 0) {
121
+ console.log('No se encontraron sesiones para analizar.');
122
+ console.log(`Directorio buscado: ${CLAUDE_DIR}`);
123
+ return;
124
+ }
125
+
126
+ const totales = { skills: {}, agentes: {}, comandos: {}, sesiones: sesiones.length, proyectos: new Set(), toolCalls: 0 };
127
+
128
+ for (const sesion of sesiones) {
129
+ totales.proyectos.add(sesion.proyecto);
130
+ const stats = analizarSesion(sesion.ruta);
131
+ totales.toolCalls += stats.toolCalls;
132
+
133
+ for (const [k, v] of Object.entries(stats.skills)) totales.skills[k] = (totales.skills[k] || 0) + v;
134
+ for (const [k, v] of Object.entries(stats.agentes)) totales.agentes[k] = (totales.agentes[k] || 0) + v;
135
+ for (const [k, v] of Object.entries(stats.comandos)) totales.comandos[k] = (totales.comandos[k] || 0) + v;
136
+ }
137
+
138
+ // Cargar inventario de skills
139
+ const skillsDir = path.join(__dirname, '..', 'habilidades');
140
+ const todosSkills = fs.existsSync(skillsDir)
141
+ ? fs.readdirSync(skillsDir).filter(d => fs.statSync(path.join(skillsDir, d)).isDirectory())
142
+ : [];
143
+
144
+ const skillsUsados = new Set(Object.keys(totales.skills));
145
+ const skillsNoUsados = todosSkills.filter(s => !skillsUsados.has(s));
146
+
147
+ if (OUTPUT === 'json') {
148
+ console.log(JSON.stringify({
149
+ fecha: new Date().toISOString(),
150
+ sesiones: totales.sesiones,
151
+ proyectos: [...totales.proyectos],
152
+ toolCalls: totales.toolCalls,
153
+ skills: Object.entries(totales.skills).sort((a, b) => b[1] - a[1]),
154
+ agentes: Object.entries(totales.agentes).sort((a, b) => b[1] - a[1]),
155
+ comandos: Object.entries(totales.comandos).sort((a, b) => b[1] - a[1]),
156
+ skillsNoUsados,
157
+ cobertura: `${skillsUsados.size}/${todosSkills.length}`,
158
+ }, null, 2));
159
+ return;
160
+ }
161
+
162
+ // Markdown
163
+ const sorted = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1]);
164
+
165
+ console.log('# Reporte de Validación en Campo — swl-ses');
166
+ console.log(`\n**Fecha**: ${new Date().toISOString().slice(0, 10)}`);
167
+ console.log(`**Sesiones analizadas**: ${totales.sesiones}`);
168
+ console.log(`**Proyectos**: ${totales.proyectos.size}`);
169
+ console.log(`**Tool calls totales**: ${totales.toolCalls}`);
170
+ console.log(`**Cobertura de skills**: ${skillsUsados.size}/${todosSkills.length} (${Math.round(skillsUsados.size / todosSkills.length * 100)}%)`);
171
+
172
+ console.log('\n## Skills más usados\n');
173
+ console.log('| Skill | Invocaciones |');
174
+ console.log('|-------|-------------|');
175
+ for (const [s, c] of sorted(totales.skills).slice(0, 30)) {
176
+ console.log(`| ${s} | ${c} |`);
177
+ }
178
+
179
+ console.log('\n## Comandos /swl:* más usados\n');
180
+ console.log('| Comando | Invocaciones |');
181
+ console.log('|---------|-------------|');
182
+ for (const [s, c] of sorted(totales.comandos).slice(0, 20)) {
183
+ console.log(`| ${s} | ${c} |`);
184
+ }
185
+
186
+ if (skillsNoUsados.length > 0) {
187
+ console.log('\n## Skills nunca usados (candidatos a evaluación)\n');
188
+ console.log('| Skill |');
189
+ console.log('|-------|');
190
+ for (const s of skillsNoUsados.sort()) {
191
+ console.log(`| ${s} |`);
192
+ }
193
+ }
194
+
195
+ console.log('\n---');
196
+ console.log(`\n> Generado por \`node scripts/field-report.js\``);
197
+ }
198
+
199
+ generarReporte();