@saulwade/swl-ses 2.4.3 → 2.5.1

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 (197) hide show
  1. package/CLAUDE.md +194 -241
  2. package/README.md +600 -597
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/abogado-diablo-swl.md +145 -0
  6. package/agentes/accesibilidad-wcag-swl.md +690 -690
  7. package/agentes/arquitecto-swl.md +267 -267
  8. package/agentes/auto-evolucion-swl.md +908 -908
  9. package/agentes/backend-api-swl.md +1 -1
  10. package/agentes/backend-csharp-swl.md +420 -420
  11. package/agentes/backend-go-swl.md +390 -390
  12. package/agentes/backend-java-swl.md +281 -281
  13. package/agentes/backend-node-swl.md +1 -1
  14. package/agentes/backend-python-swl.md +1 -1
  15. package/agentes/backend-rust-swl.md +364 -364
  16. package/agentes/backend-workers-swl.md +482 -482
  17. package/agentes/cloud-infra-swl.md +509 -509
  18. package/agentes/consolidador-swl.md +541 -541
  19. package/agentes/datos-swl.md +1 -1
  20. package/agentes/depurador-swl.md +352 -352
  21. package/agentes/devops-ci-swl.md +400 -400
  22. package/agentes/disenador-ui-swl.md +569 -569
  23. package/agentes/documentador-swl.md +345 -345
  24. package/agentes/frontend-angular-swl.md +621 -621
  25. package/agentes/frontend-css-swl.md +716 -716
  26. package/agentes/frontend-react-swl.md +692 -692
  27. package/agentes/frontend-swl.md +496 -496
  28. package/agentes/frontend-tailwind-swl.md +826 -826
  29. package/agentes/gh-fix-ci-swl.md +6 -1
  30. package/agentes/implementador-swl.md +1 -1
  31. package/agentes/investigador-swl.md +432 -432
  32. package/agentes/investigador-ux-swl.md +505 -505
  33. package/agentes/llm-apps-swl.md +1 -1
  34. package/agentes/migrador-swl.md +442 -442
  35. package/agentes/mobile-android-swl.md +511 -511
  36. package/agentes/mobile-cross-swl.md +541 -541
  37. package/agentes/mobile-ios-swl.md +502 -502
  38. package/agentes/mobile-testing-swl.md +302 -302
  39. package/agentes/nemesis-auditor-swl.md +285 -285
  40. package/agentes/notificador-swl.md +1 -1
  41. package/agentes/observabilidad-swl.md +438 -438
  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 +10 -1
  50. package/agentes/revisor-angular-swl.md +278 -278
  51. package/agentes/revisor-codigo-swl.md +1 -1
  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 +1 -1
  64. package/agentes/tdd-qa-swl.md +393 -393
  65. package/bin/lib/bot-comandos.js +1 -1
  66. package/bin/swl-ses.js +6 -0
  67. package/comandos/swl/adoptar-proyecto.md +14 -2
  68. package/comandos/swl/configurar-ci.md +8 -1
  69. package/comandos/swl/deuda-codigo.md +97 -97
  70. package/comandos/swl/discutir-fase.md +22 -118
  71. package/comandos/swl/fix.md +118 -0
  72. package/comandos/swl/nuevo-proyecto.md +54 -3
  73. package/comandos/swl/predecir.md +32 -2
  74. package/comandos/swl/seguridad.md +189 -0
  75. package/comandos/swl/status.md +5 -3
  76. package/habilidades/aprendizaje-continuo/SKILL.md +3 -1
  77. package/habilidades/discutir-fase/SKILL.md +84 -81
  78. package/habilidades/discutir-fase/recursos/plantilla-contexto.md +136 -0
  79. package/habilidades/doc-sync/SKILL.md +3 -1
  80. package/habilidades/doubt-driven-review/SKILL.md +15 -1
  81. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  82. package/habilidades/estructura-proyecto-claude/SKILL.md +11 -2
  83. package/habilidades/harness-claude-code/SKILL.md +3 -1
  84. package/habilidades/instalar-sistema/SKILL.md +3 -1
  85. package/habilidades/meta-reglas-extendido/SKILL.md +92 -0
  86. package/habilidades/meta-reglas-extendido/recursos/analisis-previo-tareas-grandes.md +186 -0
  87. package/habilidades/meta-reglas-extendido/recursos/analizar-directorios-antes-de-escribir.md +235 -0
  88. package/habilidades/meta-reglas-extendido/recursos/api-diseno.md +413 -0
  89. package/habilidades/meta-reglas-extendido/recursos/arquitectura.md +491 -0
  90. package/habilidades/meta-reglas-extendido/recursos/arreglar-al-detectar.md +264 -0
  91. package/habilidades/meta-reglas-extendido/recursos/debatir-antes-de-aceptar.md +152 -0
  92. package/habilidades/meta-reglas-extendido/recursos/git-workflow.md +259 -0
  93. package/habilidades/meta-reglas-extendido/recursos/gobernanza.md +291 -0
  94. package/habilidades/meta-reglas-extendido/recursos/memoria-consolidada.md +263 -0
  95. package/habilidades/meta-reglas-extendido/recursos/seguridad-agentes.md +443 -0
  96. package/habilidades/meta-reglas-extendido/recursos/sesiones-paralelas.md +190 -0
  97. package/habilidades/meta-reglas-extendido/recursos/sin-duplicacion-reglas-globales.md +179 -0
  98. package/habilidades/meta-reglas-extendido/recursos/skills-estandar.md +394 -0
  99. package/habilidades/meta-reglas-extendido/recursos/usar-code-review-graph.md +156 -0
  100. package/habilidades/meta-reglas-extendido/recursos/usar-context7.md +236 -0
  101. package/habilidades/meta-reglas-extendido/recursos/usar-sistema-swl.md +253 -0
  102. package/habilidades/meta-reglas-extendido/recursos/verificar-citas-normativas.md +527 -0
  103. package/habilidades/meta-skills-estandar/SKILL.md +3 -1
  104. package/habilidades/nuevo-proyecto/SKILL.md +20 -3
  105. package/habilidades/php-experto/SKILL.md +10 -3
  106. package/habilidades/{filament-admin/SKILL.md → php-experto/recursos/filament-admin.md} +23 -39
  107. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  108. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  109. package/habilidades/proceso-debate-adversarial/recursos/personas.md +5 -4
  110. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -0
  111. package/hooks/check-update.js +19 -10
  112. package/hooks/contexto-subagente.js +68 -68
  113. package/hooks/degradacion-instintos.js +1 -1
  114. package/hooks/extraccion-aprendizajes.js +2 -2
  115. package/hooks/lib/briefing.js +3 -3
  116. package/hooks/lib/nudge-tracker.js +1 -1
  117. package/hooks/lib/otlp-exporter.js +1 -1
  118. package/hooks/lib/webhook-dedup.js +1 -1
  119. package/hooks/session-briefing.js +1 -1
  120. package/llms.txt +6 -6
  121. package/manifiestos/canonical-hashes.json +713 -52
  122. package/manifiestos/hooks-config.json +469 -469
  123. package/manifiestos/invariantes-criticos.json +30 -30
  124. package/manifiestos/modulos.json +168 -135
  125. package/manifiestos/perfiles.json +0 -2
  126. package/manifiestos/skills-lock.json +49 -56
  127. package/package.json +7 -5
  128. package/plantillas/github-workflows/README.md +15 -1
  129. package/plantillas/github-workflows/swl-devsecops.yml +70 -0
  130. package/plugin.json +5 -5
  131. package/reglas/analisis-previo-tareas-grandes.md +30 -156
  132. package/reglas/analizar-directorios-antes-de-escribir.md +30 -211
  133. package/reglas/api-diseno.md +28 -398
  134. package/reglas/arquitectura.md +35 -456
  135. package/reglas/arreglar-al-detectar.md +30 -230
  136. package/reglas/debatir-antes-de-aceptar.md +30 -143
  137. package/reglas/docs.md +7 -0
  138. package/reglas/estilo-codigo.md +9 -0
  139. package/reglas/fragmentos-compartidos.md +6 -0
  140. package/reglas/git-workflow.md +44 -240
  141. package/reglas/gobernanza.md +23 -262
  142. package/reglas/memoria-consolidada.md +34 -228
  143. package/reglas/performance.md +8 -0
  144. package/reglas/pruebas.md +12 -0
  145. package/reglas/seguridad-agentes.md +37 -418
  146. package/reglas/seguridad.md +12 -0
  147. package/reglas/sesiones-paralelas.md +29 -162
  148. package/reglas/sin-duplicacion-reglas-globales.md +25 -166
  149. package/reglas/skills-estandar.md +23 -373
  150. package/reglas/usar-code-review-graph.md +31 -140
  151. package/reglas/usar-context7.md +30 -208
  152. package/reglas/usar-sistema-swl.md +47 -242
  153. package/reglas/verificar-citas-normativas.md +47 -537
  154. package/scripts/actualizar.js +253 -253
  155. package/scripts/audit-tools/auditar-relleno-inventario.js +145 -0
  156. package/scripts/auditar-clases-conocidas.js +106 -0
  157. package/scripts/bootstrap-instintos.js +2 -2
  158. package/scripts/canario-hooks.js +166 -0
  159. package/scripts/cli/configurar-ci.js +2 -1
  160. package/scripts/evidencia-valor.js +93 -0
  161. package/scripts/field-report.js +1 -1
  162. package/scripts/generar-comandos.js +143 -0
  163. package/scripts/generar-inventario.js +236 -23
  164. package/scripts/generar-matriz-lenguajes.js +1 -1
  165. package/scripts/instalador.js +15 -1
  166. package/scripts/lib/configurar-ci.js +10 -3
  167. package/scripts/lib/diary-entry.js +3 -1
  168. package/scripts/lib/drift-detector.js +1 -1
  169. package/scripts/lib/evidencia-valor.js +189 -0
  170. package/scripts/lib/expandir-targets.js +71 -71
  171. package/scripts/lib/frontmatter-md.js +63 -0
  172. package/scripts/lib/parsear-opciones.js +2 -0
  173. package/scripts/lib/prune-componentes.js +180 -0
  174. package/scripts/lib/reglas-globales-conocidas.json +16 -2
  175. package/scripts/lib/scoring-instintos.js +2 -2
  176. package/scripts/lib/toml-merge.js +204 -204
  177. package/scripts/lib/transformadores/claude.js +1 -1
  178. package/scripts/lib/transformadores/codex.js +1 -1
  179. package/scripts/lib/transformadores/copilot.js +1 -1
  180. package/scripts/lib/transformadores/cursor.js +1 -1
  181. package/scripts/lib/transformadores/gemini.js +22 -2
  182. package/scripts/lib/transformadores/opencode.js +1 -1
  183. package/scripts/mcp-server/auth.js +105 -105
  184. package/scripts/mcp-server/cache.js +106 -106
  185. package/scripts/prune.js +102 -0
  186. package/scripts/publicar.js +18 -2
  187. package/scripts/tui/pantallas/inspect.js +175 -175
  188. package/scripts/tui/pantallas/uninstall-wizard.js +210 -210
  189. package/scripts/tui/pantallas/update-wizard.js +234 -234
  190. package/scripts/tui/pantallas/welcome.js +189 -189
  191. package/habilidades/paid-media-tracking/SKILL.md +0 -269
  192. package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +0 -220
  193. package/habilidades/paid-media-tracking/recursos/google-ads-api.md +0 -215
  194. package/habilidades/tracking-measurement/SKILL.md +0 -239
  195. package/habilidades/tracking-measurement/recursos/consent-mode.md +0 -231
  196. package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +0 -216
  197. package/habilidades/tracking-measurement/recursos/meta-capi.md +0 -262
@@ -44,18 +44,47 @@ function leerFrontmatter(ruta) {
44
44
  const match = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---/);
45
45
  if (!match) return {};
46
46
  const fm = {};
47
- for (const linea of match[1].split(/\r?\n/)) {
47
+ const lineas = match[1].split(/\r?\n/);
48
+ for (let i = 0; i < lineas.length; i++) {
49
+ const linea = lineas[i];
50
+ if (/^\s/.test(linea)) continue; // continuación de multilínea, la consume el bloque de abajo
48
51
  const idx = linea.indexOf(':');
49
- if (idx > 0) {
50
- const key = linea.slice(0, idx).trim();
51
- const val = linea.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
52
- fm[key] = val;
52
+ if (idx <= 0) continue;
53
+ const key = linea.slice(0, idx).trim();
54
+ let val = linea.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
55
+ // Escalares plegados YAML (`description: >` / `|`): juntar las líneas indentadas siguientes.
56
+ if (val === '>' || val === '|' || val === '>-' || val === '|-' || val === '') {
57
+ const partes = [];
58
+ for (let j = i + 1; j < lineas.length && /^\s+\S/.test(lineas[j]); j++) {
59
+ partes.push(lineas[j].trim());
60
+ }
61
+ if (partes.length > 0) val = partes.join(' ');
53
62
  }
63
+ fm[key] = val;
54
64
  }
55
65
  return fm;
56
66
  } catch { return {}; }
57
67
  }
58
68
 
69
+ /** Recorta una descripción a n caracteres en límite de palabra, colapsando whitespace. */
70
+ function resumir(texto, n = 110) {
71
+ const limpio = String(texto || '').replace(/\s+/g, ' ').replace(/\|/g, '/').trim();
72
+ if (limpio.length <= n) return limpio;
73
+ const corte = limpio.lastIndexOf(' ', n);
74
+ return limpio.slice(0, corte > 40 ? corte : n) + '…';
75
+ }
76
+
77
+ function contarArchivosRec(dir, ext) {
78
+ let total = 0;
79
+ try {
80
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
81
+ if (e.isDirectory()) total += contarArchivosRec(path.join(dir, e.name), ext);
82
+ else if (e.name.endsWith(ext)) total++;
83
+ }
84
+ } catch { /* dir inexistente → 0 */ }
85
+ return total;
86
+ }
87
+
59
88
  function contarArchivos(dir, ext) {
60
89
  try {
61
90
  return fs.readdirSync(dir).filter(f => f.endsWith(ext)).length;
@@ -94,6 +123,12 @@ function recolectarAgentes() {
94
123
  version: fm.version || '?',
95
124
  model: fm.model || '?',
96
125
  nivelRiesgo: fm.nivelRiesgo || '?',
126
+ description: fm.description || '',
127
+ skillsInvocables: (fm.skillsInvocables || '')
128
+ .replace(/^\[|\]$/g, '')
129
+ .split(',')
130
+ .map(s => s.trim())
131
+ .filter(Boolean),
97
132
  };
98
133
  });
99
134
  }
@@ -105,7 +140,24 @@ function recolectarSkills() {
105
140
  );
106
141
  return dirs.map(d => {
107
142
  const fm = leerFrontmatter(path.join(dir, d, 'SKILL.md'));
108
- return { dir: d, name: fm.name || d, description: (fm.description || '').substring(0, 80) };
143
+ return {
144
+ dir: d,
145
+ name: fm.name || d,
146
+ version: fm.version || '—',
147
+ description: fm.description || '',
148
+ };
149
+ });
150
+ }
151
+
152
+ function recolectarComandos() {
153
+ const dir = path.join(RAIZ, 'comandos', 'swl');
154
+ return fs.readdirSync(dir).filter(f => f.endsWith('.md')).map(f => {
155
+ const fm = leerFrontmatter(path.join(dir, f));
156
+ return {
157
+ archivo: f,
158
+ name: fm.name || `swl:${f.replace('.md', '')}`,
159
+ description: fm.description || '',
160
+ };
109
161
  });
110
162
  }
111
163
 
@@ -139,19 +191,38 @@ function generarInventario() {
139
191
  const skills = recolectarSkills();
140
192
  const hooks = recolectarHooks();
141
193
  const libs = recolectarLibs();
142
- const comandos = contarArchivos(path.join(RAIZ, 'comandos', 'swl'), '.md');
194
+ const comandos = recolectarComandos();
143
195
  const reglasBase = contarArchivos(path.join(RAIZ, 'reglas'), '.md');
144
196
  const reglasLang = fs.readdirSync(path.join(RAIZ, 'reglas', 'lenguajes'))
145
197
  .filter(d => fs.statSync(path.join(RAIZ, 'reglas', 'lenguajes', d)).isDirectory())
146
198
  .reduce((acc, d) => acc + contarArchivos(path.join(RAIZ, 'reglas', 'lenguajes', d), '.md'), 0);
147
- const schemas = contarArchivos(path.join(RAIZ, 'schemas'), '.json');
148
- const scripts = contarArchivos(path.join(RAIZ, 'scripts', 'lib'), '.js') + contarArchivos(path.join(RAIZ, 'scripts', 'lib'), '.py');
199
+ const schemasList = fs.readdirSync(path.join(RAIZ, 'schemas')).filter(f => f.endsWith('.json')).sort();
200
+ const scriptsLibList = fs.readdirSync(path.join(RAIZ, 'scripts', 'lib'))
201
+ .filter(f => f.endsWith('.js') || f.endsWith('.py')).sort();
202
+ // Tipos antes omitidos del inventario (auditoría 2026-07-08)
203
+ const fragmentos = fs.readdirSync(path.join(RAIZ, 'agentes')).filter(f => f.startsWith('_') && f.endsWith('.md'));
204
+ const gatewayJs = contarArchivosRec(path.join(RAIZ, 'gateway'), '.js');
205
+ const manifiestosJson = contarArchivos(path.join(RAIZ, 'manifiestos'), '.json');
206
+ const contextos = contarArchivos(path.join(RAIZ, 'contextos'), '.md');
207
+ const plantillasTotal = contarArchivosRec(path.join(RAIZ, 'plantillas'), '.md') + contarArchivosRec(path.join(RAIZ, 'plantillas'), '.yml');
208
+ const scriptsTop = contarArchivos(path.join(RAIZ, 'scripts'), '.js');
209
+
210
+ // Mapa skill → agentes que la declaran en skillsInvocables
211
+ const skillAAgentes = new Map();
212
+ for (const a of agentes) {
213
+ for (const s of a.skillsInvocables) {
214
+ if (!skillAAgentes.has(s)) skillAAgentes.set(s, []);
215
+ skillAAgentes.get(s).push(a.name);
216
+ }
217
+ }
149
218
 
150
219
  const lines = [];
151
220
  lines.push(`# INVENTARIO.md — Referencia completa de componentes SWL v${pkg.version}`);
152
221
  lines.push('');
153
222
  lines.push('> Archivo generado automáticamente por `node scripts/generar-inventario.js`.');
154
223
  lines.push('> NO editar manualmente — se regenera con cada release.');
224
+ lines.push('> Índice de descubrimiento: cada catálogo incluye la descripción del');
225
+ lines.push('> componente para decidir qué usar sin abrir su archivo.');
155
226
  lines.push('');
156
227
  lines.push('## Resumen');
157
228
  lines.push('');
@@ -159,13 +230,19 @@ function generarInventario() {
159
230
  lines.push('|-----------|----------|');
160
231
  lines.push(`| Agentes SWL | ${agentes.length} |`);
161
232
  lines.push(`| Habilidades | ${skills.length} |`);
162
- lines.push(`| Comandos | ${comandos} |`);
233
+ lines.push(`| Comandos | ${comandos.length} |`);
163
234
  lines.push(`| Reglas base | ${reglasBase} |`);
164
235
  lines.push(`| Reglas por lenguaje | ${reglasLang} |`);
165
236
  lines.push(`| Hooks | ${hooks.length} |`);
166
237
  lines.push(`| Librerías de hooks | ${libs.length} |`);
167
- lines.push(`| Schemas | ${schemas} |`);
168
- lines.push(`| Scripts lib | ${scripts} |`);
238
+ lines.push(`| Schemas | ${schemasList.length} |`);
239
+ lines.push(`| Scripts lib | ${scriptsLibList.length} |`);
240
+ lines.push(`| Scripts top-level | ${scriptsTop} |`);
241
+ lines.push(`| Fragmentos de agentes | ${fragmentos.length} |`);
242
+ lines.push(`| Gateway (archivos .js) | ${gatewayJs} |`);
243
+ lines.push(`| Manifiestos | ${manifiestosJson} |`);
244
+ lines.push(`| Contextos | ${contextos} |`);
245
+ lines.push(`| Plantillas | ${plantillasTotal} |`);
169
246
  lines.push('');
170
247
 
171
248
  // Equipos
@@ -174,22 +251,47 @@ function generarInventario() {
174
251
  lines.push('| Equipo | Líder | Miembros paralelos | Cuándo |');
175
252
  lines.push('|--------|-------|-------------------|--------|');
176
253
  lines.push('| Discovery | investigador-swl | investigador-ux-swl, producto-prd-swl | Proyecto nuevo |');
177
- lines.push('| Arquitectura | arquitecto-swl | rendimiento-swl, revisor-seguridad-swl | Decisión de diseño |');
254
+ lines.push('| Arquitectura | arquitecto-swl | rendimiento-swl, revisor-seguridad-swl, abogado-diablo-swl | Decisión de diseño |');
178
255
  lines.push('| Frontend | frontend-swl | frontend-react-swl, frontend-css-swl, frontend-tailwind-swl | Feature UI compleja |');
179
256
  lines.push('| Backend | backend-python-swl | backend-api-swl, backend-workers-swl | Feature backend compleja |');
180
257
  lines.push('| Mobile | mobile-cross-swl | mobile-android-swl, mobile-ios-swl, mobile-testing-swl | Feature mobile |');
181
258
  lines.push('| Calidad | tdd-qa-swl | revisor-codigo-swl, revisor-seguridad-swl, accesibilidad-wcag-swl | Pre-release |');
182
- lines.push('| Operaciones | devops-ci-swl | cloud-infra-swl, observabilidad-swl, sre-swl | Deploy, infra, confiabilidad |');
259
+ lines.push('| Auditoría profunda | nemesis-auditor-swl | feynman-auditor-swl, state-inconsistency-auditor-swl | Lógica compleja con estado acoplado |');
260
+ lines.push('| Operaciones | devops-ci-swl | cloud-infra-swl, observabilidad-swl, sre-swl, gh-fix-ci-swl | Deploy, infra, confiabilidad, fallas CI |');
183
261
  lines.push('| Cierre | consolidador-swl | documentador-swl, release-manager-swl, notificador-swl | Fase completada |');
184
262
  lines.push('');
185
263
 
186
264
  // Agentes
187
265
  lines.push(`## Agentes SWL — Registro completo (${agentes.length})`);
188
266
  lines.push('');
189
- lines.push('| Agente | Versión | Modelo | Riesgo |');
190
- lines.push('|--------|---------|--------|--------|');
267
+ lines.push('| Agente | Versión | Modelo | Riesgo | Qué hace / cuándo invocarlo |');
268
+ lines.push('|--------|---------|--------|--------|------------------------------|');
191
269
  for (const a of agentes.sort((x, y) => x.name.localeCompare(y.name))) {
192
- lines.push(`| ${a.name} | ${a.version} | ${a.model} | ${a.nivelRiesgo} |`);
270
+ lines.push(`| ${a.name} | ${a.version} | ${a.model} | ${a.nivelRiesgo} | ${resumir(a.description, 160)} |`);
271
+ }
272
+ lines.push('');
273
+
274
+ // Habilidades — catálogo consultable (antes solo se contaban)
275
+ lines.push(`## Habilidades — Catálogo completo (${skills.length})`);
276
+ lines.push('');
277
+ lines.push('La columna "Agentes" lista quién la declara en `skillsInvocables`;');
278
+ lines.push('"—" significa que se invoca por descripción o desde comandos.');
279
+ lines.push('');
280
+ lines.push('| Skill | Versión | Descripción | Agentes |');
281
+ lines.push('|-------|---------|-------------|---------|');
282
+ for (const s of [...skills].sort((x, y) => x.name.localeCompare(y.name))) {
283
+ const invocadores = (skillAAgentes.get(s.name) || []).join(', ') || '—';
284
+ lines.push(`| ${s.name} | ${s.version} | ${resumir(s.description, 140)} | ${invocadores} |`);
285
+ }
286
+ lines.push('');
287
+
288
+ // Comandos — catálogo consultable (antes solo se contaban)
289
+ lines.push(`## Comandos /swl:* — Catálogo completo (${comandos.length})`);
290
+ lines.push('');
291
+ lines.push('| Comando | Descripción |');
292
+ lines.push('|---------|-------------|');
293
+ for (const c of [...comandos].sort((x, y) => x.name.localeCompare(y.name))) {
294
+ lines.push(`| /${c.name} | ${resumir(c.description, 180)} |`);
193
295
  }
194
296
  lines.push('');
195
297
 
@@ -243,6 +345,26 @@ function generarInventario() {
243
345
  }
244
346
  lines.push('');
245
347
 
348
+ // Schemas — superficie de contrato del sistema
349
+ lines.push(`## Schemas (${schemasList.length})`);
350
+ lines.push('');
351
+ lines.push('| Schema | Archivo |');
352
+ lines.push('|--------|---------|');
353
+ for (const s of schemasList) {
354
+ lines.push(`| ${s.replace('.schema.json', '').replace('.json', '')} | \`schemas/${s}\` |`);
355
+ }
356
+ lines.push('');
357
+
358
+ // Scripts lib — enumerados (antes solo se contaban)
359
+ lines.push(`## Librerías de scripts (scripts/lib/) — ${scriptsLibList.length}`);
360
+ lines.push('');
361
+ lines.push('| Librería | Archivo |');
362
+ lines.push('|----------|---------|');
363
+ for (const s of scriptsLibList) {
364
+ lines.push(`| ${s.replace(/\.(js|py)$/, '')} | \`scripts/lib/${s}\` |`);
365
+ }
366
+ lines.push('');
367
+
246
368
  return lines.join('\n');
247
369
  }
248
370
 
@@ -353,7 +475,9 @@ function generarSalud() {
353
475
  const lines = [];
354
476
  lines.push('# Reporte de Salud del Sistema SWL');
355
477
  lines.push('');
356
- lines.push(`**Generado**: ${new Date().toISOString().slice(0, 10)}`);
478
+ // Fecha LOCAL (no UTC): en horario de México la tarde ya es "mañana" en UTC
479
+ // y el reporte quedaba con fecha futura. 'sv' formatea como YYYY-MM-DD.
480
+ lines.push(`**Generado**: ${new Date().toLocaleDateString('sv')}`);
357
481
  lines.push(`**Versión del sistema**: ${pkg.version}`);
358
482
  lines.push('');
359
483
  lines.push('> Archivo generado automáticamente por `node scripts/generar-inventario.js --salud`.');
@@ -463,11 +587,12 @@ function generarLlmsTxt() {
463
587
  L.push('');
464
588
  L.push('## Componentes');
465
589
  L.push('');
466
- L.push(`- ${agentes} agentes especializados en \`agentes/\` (orquestación, implementación por stack, revisión, calidad, diseño)`);
467
- L.push(`- ${skills} habilidades cargables bajo demanda en \`habilidades/\` (conocimiento operacional con divulgación progresiva)`);
468
- L.push(`- ${comandos} comandos \`/swl:*\` en \`comandos/swl/\` (ciclo GSD, calidad, release, diagnóstico)`);
469
- L.push(`- ${reglasBase} reglas base + ${reglasLang} reglas por lenguaje en \`reglas/\` (políticas obligatorias por matcher)`);
470
- L.push(`- ${hooks} hooks en \`hooks/\` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)`);
590
+ // llmstxt.org: cada entrada de sección debe ser un link markdown navegable.
591
+ L.push(`- [${agentes} agentes especializados](AGENTS.md): orquestación, implementación por stack, revisión, calidad, diseño fuente en \`agentes/\``);
592
+ L.push(`- [${skills} habilidades cargables bajo demanda](INVENTARIO.md): conocimiento operacional con divulgación progresiva — fuente en \`habilidades/\``);
593
+ L.push(`- [${comandos} comandos /swl:*](COMANDOS.md): ciclo GSD, calidad, release, diagnóstico — fuente en \`comandos/swl/\``);
594
+ L.push(`- [${reglasBase} reglas base + ${reglasLang} por lenguaje](INVENTARIO.md): políticas obligatorias por matcher — fuente en \`reglas/\``);
595
+ L.push(`- [${hooks} hooks](INVENTARIO.md): telemetría, validación, seguridad; zero-deps, escrituras atómicas — fuente en \`hooks/\``);
471
596
  L.push('');
472
597
  L.push('## Opcional');
473
598
  L.push('');
@@ -479,6 +604,83 @@ function generarLlmsTxt() {
479
604
  return L.join('\n');
480
605
  }
481
606
 
607
+ // ── MAPEO_SKILLS_AGENTES.md ───────────────────────────────────────────
608
+ // Antes se mantenía a mano y era el peor infractor de drift del sistema
609
+ // (auditoría 2026-07-08: mencionaba skills retiradas, mapeos falsos en 3/5
610
+ // agentes muestreados y totales stale, pese a ser insumo del gate de release).
611
+ // Se deriva por completo de los skillsInvocables del frontmatter de agentes.
612
+ function generarMapeo() {
613
+ const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, 'package.json'), 'utf8'));
614
+ const agentes = recolectarAgentes();
615
+ const skills = recolectarSkills();
616
+ const nombresSkill = new Set(skills.map(s => s.name));
617
+
618
+ const L = [];
619
+ L.push(`# MAPEO_SKILLS_AGENTES.md — v${pkg.version}`);
620
+ L.push('');
621
+ L.push('> Archivo generado automáticamente por `node scripts/generar-inventario.js`.');
622
+ L.push('> NO editar manualmente — se deriva de los `skillsInvocables` declarados');
623
+ L.push('> en el frontmatter de `agentes/*.md`. El mapa inverso (skill → agentes)');
624
+ L.push('> vive en `INVENTARIO.md § Habilidades — Catálogo completo`.');
625
+ L.push('');
626
+ L.push(`Total de agentes: ${agentes.length}`);
627
+ L.push(`Total de skills: ${skills.length}`);
628
+ L.push('');
629
+ L.push('## Agente → skills que declara invocables');
630
+ L.push('');
631
+ L.push('| Agente | # | skillsInvocables |');
632
+ L.push('|--------|---|------------------|');
633
+ // Skills de la colección oficial Anthropic (opt-in, instaladas en
634
+ // .claude/skills/ vía `swl-ses skills add anthropics/skills`) — referencias
635
+ // legítimas aunque no vivan en habilidades/. Ver MANUAL_USO § Anthropic Skills.
636
+ const SKILLS_ANTHROPIC = new Set([
637
+ 'pdf', 'docx', 'xlsx', 'pptx', 'frontend-design', 'web-artifacts-builder',
638
+ 'canvas-design', 'algorithmic-art', 'theme-factory', 'slack-gif-creator',
639
+ 'claude-api', 'mcp-builder', 'webapp-testing', 'doc-coauthoring',
640
+ 'internal-comms', 'brand-guidelines', 'skill-creator',
641
+ ]);
642
+ const externas = new Set();
643
+ const inexistentes = [];
644
+ for (const a of [...agentes].sort((x, y) => x.name.localeCompare(y.name))) {
645
+ for (const s of a.skillsInvocables) {
646
+ if (s === 'todos') continue; // centinela: el agente puede invocar cualquier skill
647
+ if (nombresSkill.has(s)) continue;
648
+ if (SKILLS_ANTHROPIC.has(s)) externas.add(s);
649
+ else inexistentes.push(`${a.name} → ${s}`);
650
+ }
651
+ const etiqueta = a.skillsInvocables.includes('todos')
652
+ ? 'todos (acceso a cualquier skill)'
653
+ : (a.skillsInvocables.join(', ') || '—');
654
+ L.push(`| ${a.name} | ${a.skillsInvocables.includes('todos') ? skills.length : a.skillsInvocables.length} | ${etiqueta} |`);
655
+ }
656
+ L.push('');
657
+ if (externas.size > 0) {
658
+ L.push(`## Skills externas referenciadas (colección Anthropic, opt-in) — ${externas.size}`);
659
+ L.push('');
660
+ L.push('No viven en `habilidades/`; se instalan con `swl-ses skills add anthropics/skills`.');
661
+ L.push('Si la colección no está instalada, el agente degrada sin fallar.');
662
+ L.push('');
663
+ L.push([...externas].sort().map(s => `\`${s}\``).join(', '));
664
+ L.push('');
665
+ }
666
+ if (inexistentes.length > 0) {
667
+ L.push('## ⚠ Referencias a skills inexistentes (corregir en el frontmatter del agente)');
668
+ L.push('');
669
+ for (const i of inexistentes) L.push(`- ${i}`);
670
+ L.push('');
671
+ }
672
+ const declaradas = new Set(agentes.flatMap(a => a.skillsInvocables));
673
+ const sinAgente = skills.filter(s => !declaradas.has(s.name));
674
+ L.push(`## Skills sin agente declarado (${sinAgente.length})`);
675
+ L.push('');
676
+ L.push('No es error: se invocan por descripción (matching nativo del runtime),');
677
+ L.push('desde comandos `/swl:*`, o son infraestructura del sistema.');
678
+ L.push('');
679
+ L.push(sinAgente.map(s => `\`${s.name}\``).join(', '));
680
+ L.push('');
681
+ return L.join('\n');
682
+ }
683
+
482
684
  // ── Ejecutar ──────────────────────────────────────────────────────────
483
685
 
484
686
  if (AMBOS || SOLO_INV) {
@@ -513,3 +715,14 @@ if (AMBOS || SOLO_LLMS) {
513
715
  console.log('llms.txt generado correctamente.');
514
716
  }
515
717
  }
718
+
719
+ if (AMBOS || args.has('--mapeo')) {
720
+ const mapeo = generarMapeo();
721
+ if (DRY) {
722
+ console.log('\n=== MAPEO_SKILLS_AGENTES.md (dry-run) ===\n');
723
+ console.log(mapeo.substring(0, 1500) + '\n...(truncado)');
724
+ } else {
725
+ atomicWriteSync(path.join(RAIZ, '.planning', 'MAPEO_SKILLS_AGENTES.md'), mapeo, 'utf8');
726
+ console.log('MAPEO_SKILLS_AGENTES.md generado correctamente.');
727
+ }
728
+ }
@@ -207,7 +207,7 @@ function renderResumen() {
207
207
  // ---------------------------------------------------------------------------
208
208
 
209
209
  function main() {
210
- const fecha = new Date().toISOString().slice(0, 10);
210
+ const fecha = new Date().toLocaleDateString('sv'); // local, no UTC (regla CLAUDE.md Code style)
211
211
  const contenido = `# Matriz de cobertura SWL × lenguaje
212
212
 
213
213
  > **Generado automáticamente** por \`scripts/generar-matriz-lenguajes.js\`.
@@ -857,7 +857,20 @@ async function install(opciones) {
857
857
  accion: resMerge.accion,
858
858
  });
859
859
  } else {
860
- // Comportamiento clásico: sobrescribir archivo completo (codex/gemini/copilot/opencode)
860
+ // Comportamiento clásico: sobrescribir archivo completo (codex/gemini/copilot/opencode).
861
+ // GUARD anti-clobber (bug 2026-07-08): si el destino YA existe y NO fue
862
+ // generado por SWL (sin el marcador "Generado por swl-ses"), es contenido
863
+ // del usuario o documentación canónica del proyecto — sobreescribirlo es
864
+ // pérdida de datos (caso real: install --target opencode pisó el AGENTS.md
865
+ // canónico del repo madre, 420 líneas). Preservar y avisar; el usuario
866
+ // puede borrar el archivo manualmente si quiere el generado.
867
+ const yaExiste = fs.existsSync(rutaInstrucciones);
868
+ const esGeneradoSwl = yaExiste
869
+ ? fs.readFileSync(rutaInstrucciones, 'utf-8').includes('Generado por swl-ses')
870
+ : false;
871
+ if (yaExiste && !esGeneradoSwl) {
872
+ console.log(` = ${instrucciones.rutaRelativa} NO modificado — existe contenido propio del proyecto (sin marcador "Generado por swl-ses"). Borra el archivo y reinstala si quieres el generado.`);
873
+ } else {
861
874
  if (!fs.existsSync(dirInstrucciones)) {
862
875
  fs.mkdirSync(dirInstrucciones, { recursive: true });
863
876
  }
@@ -868,6 +881,7 @@ async function install(opciones) {
868
881
  destino: rutaInstrucciones,
869
882
  tipo: 'instrucciones',
870
883
  });
884
+ } // cierre del guard anti-clobber
871
885
  }
872
886
  }
873
887
  } catch (err) {
@@ -44,9 +44,10 @@ const WORKFLOWS_DIR = '.github/workflows';
44
44
 
45
45
  /** Workflows que este módulo gestiona (identificados por nombre de archivo). */
46
46
  const WORKFLOWS_SWL = {
47
- security: 'swl-security.yml',
48
- ci: 'swl-ci.yml',
49
- release: 'release-please.yml',
47
+ security: 'swl-security.yml',
48
+ ci: 'swl-ci.yml',
49
+ release: 'release-please.yml',
50
+ devsecops: 'swl-devsecops.yml',
50
51
  };
51
52
 
52
53
  /** Secrets que requiere cada workflow. */
@@ -54,6 +55,9 @@ const SECRETS_REQUERIDOS = {
54
55
  'swl-security.yml': ['CLAUDE_API_KEY'],
55
56
  'swl-ci.yml': [],
56
57
  'release-please.yml': [],
58
+ // GITLEAKS_LICENSE solo es necesaria en repos de organización (gratuita
59
+ // para individuos) — no se lista como requerida para no bloquear el caso común.
60
+ 'swl-devsecops.yml': [],
57
61
  };
58
62
 
59
63
  // ---------------------------------------------------------------------------
@@ -121,6 +125,7 @@ function listarPlantillas() {
121
125
  * @param {boolean} [opciones.withSecurity=true] Instalar swl-security.yml
122
126
  * @param {boolean} [opciones.withCi=true] Instalar swl-ci.yml
123
127
  * @param {boolean} [opciones.withReleasePlease=false] Instalar release-please.yml
128
+ * @param {boolean} [opciones.withDevsecops=false] Instalar swl-devsecops.yml (gitleaks + audit de deps)
124
129
  * @param {boolean} [opciones.dryRun=false] Solo mostrar cambios, no copiar
125
130
  * @param {boolean} [opciones.force=false] Sobreescribir archivos existentes
126
131
  * @param {string} [opciones._proyectoPathOverride] Path del proyecto (tests)
@@ -130,6 +135,7 @@ function init({
130
135
  withSecurity = true,
131
136
  withCi = true,
132
137
  withReleasePlease = false,
138
+ withDevsecops = false,
133
139
  dryRun = false,
134
140
  force = false,
135
141
  _proyectoPathOverride,
@@ -141,6 +147,7 @@ function init({
141
147
  if (withSecurity) seleccionados.push(WORKFLOWS_SWL.security);
142
148
  if (withCi) seleccionados.push(WORKFLOWS_SWL.ci);
143
149
  if (withReleasePlease) seleccionados.push(WORKFLOWS_SWL.release);
150
+ if (withDevsecops) seleccionados.push(WORKFLOWS_SWL.devsecops);
144
151
 
145
152
  const copiados = [];
146
153
  const existentes = [];
@@ -38,7 +38,9 @@ function nowIso() {
38
38
  }
39
39
 
40
40
  function todayDate() {
41
- return nowIso().slice(0, 10);
41
+ // Fecha LOCAL, no UTC: desde México la tarde ya es "mañana" en UTC y el id
42
+ // del diary quedaba fechado en el futuro (regla CLAUDE.md § Code style).
43
+ return new Date().toLocaleDateString('sv');
42
44
  }
43
45
 
44
46
  function shortHash(input) {
@@ -367,7 +367,7 @@ function generarLineaTemporal({
367
367
  for (let i = dias - 1; i >= 0; i--) {
368
368
  const inicioDia = ahora - (i + 1) * 24 * 3600 * 1000;
369
369
  const finDia = ahora - i * 24 * 3600 * 1000;
370
- const fecha = new Date(inicioDia).toISOString().slice(0, 10);
370
+ const fecha = new Date(inicioDia).toLocaleDateString('sv');
371
371
 
372
372
  const del_dia = eventos.filter(e => {
373
373
  const ms = aMs(e.timestamp ?? e.ts ?? e.inicio ?? e.created_at);
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * evidencia-valor.js — Evidencia de valor del harness EN CAMPO (downstream).
5
+ *
6
+ * En un proyecto real no hay A/B (nadie desarrolla la misma feature dos
7
+ * veces); la evidencia creíble son HECHOS producidos por actores
8
+ * independientes del sistema, nunca scores autodeclarados (regla
9
+ * anti-gaming de seguridad-agentes). Cuatro fuentes, cada una con
10
+ * procedencia explícita:
11
+ *
12
+ * 1. Intercepciones de gates — eventos durables de `.planning/evolution/
13
+ * nudges.jsonl` (hook disparó una intervención). Evidencia directa:
14
+ * cada bloqueo/alerta con causa real es un defecto prevenido contable.
15
+ * 2. Presión de defectos — git: ratio commits fix/feat en la ventana
16
+ * + fixes POSTERIORES a la última verificación de fase (escape rate
17
+ * aproximado — la métrica que CASTIGA al harness). Proxy honesto.
18
+ * 3. DORA-lite + CI — reusa scripts/lib/metricas-dora.js
19
+ * (deployment frequency, lead time, change failure rate, MTTR vía gh).
20
+ * Producido por git/GitHub, no por el sistema.
21
+ * 4. Uso del sistema — .planning/skill-metrics.json. Solo
22
+ * contexto (correlación, jamás causalidad).
23
+ *
24
+ * FRONTERA DE DATOS (documentada en docs/evidencia-valor.md): TODO se lee
25
+ * LOCAL, del `.planning/` y el git del propio proyecto downstream donde
26
+ * corre el comando. El repo madre NUNCA lee estos datos; el único canal
27
+ * hacia el autor es el field-report opt-in con agregados anónimos.
28
+ *
29
+ * Zero-deps. Funciones puras respecto a raíz/inyecciones para testeo.
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const { execFileSync } = require('child_process');
35
+
36
+ const DIA_MS = 24 * 60 * 60 * 1000;
37
+
38
+ /** Tipos de nudge que constituyen una intervención del sistema (evidencia). */
39
+ const TIPOS_INTERVENCION = new Set([
40
+ 'bloqueo', 'bloqueo-hook', 'secreto-detectado', 'calidad-pre-commit',
41
+ 'drift-detectado', 'recovery-activado', 'riesgo-alto', 'proteccion-rutas',
42
+ 'prompt-injection', 'veto', 'gate-fallido',
43
+ ]);
44
+
45
+ // ─── 1. Intercepciones (nudges.jsonl) ────────────────────────────────────────
46
+
47
+ function leerNudges(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
48
+ const ruta = path.join(raiz, '.planning', 'evolution', 'nudges.jsonl');
49
+ if (!fs.existsSync(ruta)) return { disponible: false, razon: 'sin nudges.jsonl (proyecto sin actividad de hooks o telemetría desactivada)', eventos: [] };
50
+ const corte = hoy.getTime() - ventanaDias * DIA_MS;
51
+ const eventos = [];
52
+ for (const linea of fs.readFileSync(ruta, 'utf8').split(/\r?\n/)) {
53
+ if (!linea.trim()) continue;
54
+ try {
55
+ const e = JSON.parse(linea);
56
+ const t = Date.parse(e.timestamp || e.ts || '');
57
+ if (!Number.isNaN(t) && t >= corte) eventos.push(e);
58
+ } catch { /* línea corrupta: se omite, no revienta el panel */ }
59
+ }
60
+ return { disponible: true, eventos };
61
+ }
62
+
63
+ function intercepciones(raiz, opts = {}) {
64
+ const { disponible, razon, eventos } = leerNudges(raiz, opts);
65
+ if (!disponible) return { disponible, razon, procedencia: 'hook-event (nudges.jsonl)', total: 0, porTipo: {} };
66
+ const porTipo = {};
67
+ let intervenciones = 0;
68
+ for (const e of eventos) {
69
+ const tipo = String(e.tipo || 'desconocido');
70
+ porTipo[tipo] = (porTipo[tipo] || 0) + 1;
71
+ if (TIPOS_INTERVENCION.has(tipo) || e.blocking === true || e.estado === 'critico') intervenciones++;
72
+ }
73
+ return {
74
+ disponible: true,
75
+ procedencia: 'hook-event (nudges.jsonl)',
76
+ caracter: 'evidencia',
77
+ totalEventos: eventos.length,
78
+ intervenciones,
79
+ porTipo,
80
+ };
81
+ }
82
+
83
+ // ─── 2. Presión de defectos (git) ────────────────────────────────────────────
84
+
85
+ function ejecutarGit(raiz, args) {
86
+ return execFileSync('git', args, { cwd: raiz, encoding: 'utf8' });
87
+ }
88
+
89
+ function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecutarGit } = {}) {
90
+ let sujetos;
91
+ try {
92
+ const desde = new Date(hoy.getTime() - ventanaDias * DIA_MS).toISOString();
93
+ sujetos = git(raiz, ['log', `--since=${desde}`, '--pretty=format:%ad|%s', '--date=iso-strict'])
94
+ .split(/\r?\n/).filter(Boolean);
95
+ } catch (err) {
96
+ return { disponible: false, razon: `git no disponible: ${err.message.split(/\r?\n/)[0]}` };
97
+ }
98
+ const esFix = s => /^(fix|hotfix)(\(|!|:)/.test(s);
99
+ const esFeat = s => /^feat(\(|!|:)/.test(s);
100
+ const fixes = sujetos.filter(l => esFix(l.split('|').slice(1).join('|')));
101
+ const feats = sujetos.filter(l => esFeat(l.split('|').slice(1).join('|')));
102
+
103
+ // Escape aproximado: fixes con fecha POSTERIOR a la última verificación de
104
+ // fase (VERIFICACION.md más reciente). Sin fases verificadas → no aplica.
105
+ let escapes = { disponible: false, razon: 'sin VERIFICACION.md en .planning/fases/ (proyecto sin ciclo GSD verificado)' };
106
+ try {
107
+ const dirFases = path.join(raiz, '.planning', 'fases');
108
+ let ultimaVerificacion = 0;
109
+ if (fs.existsSync(dirFases)) {
110
+ for (const fase of fs.readdirSync(dirFases)) {
111
+ const v = path.join(dirFases, fase, 'VERIFICACION.md');
112
+ if (fs.existsSync(v)) ultimaVerificacion = Math.max(ultimaVerificacion, fs.statSync(v).mtimeMs);
113
+ }
114
+ }
115
+ if (ultimaVerificacion > 0) {
116
+ const post = fixes.filter(l => {
117
+ const t = Date.parse(l.split('|')[0]);
118
+ return !Number.isNaN(t) && t > ultimaVerificacion;
119
+ });
120
+ escapes = {
121
+ disponible: true,
122
+ fixesTrasUltimaVerificacion: post.length,
123
+ ultimaVerificacion: new Date(ultimaVerificacion).toLocaleDateString('sv'),
124
+ nota: 'lo que el harness NO atrapó — la métrica que lo castiga',
125
+ };
126
+ }
127
+ } catch { /* best-effort */ }
128
+
129
+ return {
130
+ disponible: true,
131
+ procedencia: 'git log (independiente del sistema)',
132
+ caracter: 'proxy',
133
+ commits: sujetos.length,
134
+ fixes: fixes.length,
135
+ feats: feats.length,
136
+ ratioFixFeat: feats.length > 0 ? +(fixes.length / feats.length).toFixed(2) : null,
137
+ escapes,
138
+ };
139
+ }
140
+
141
+ // ─── 3 y 4. DORA/CI y uso (reuso de fuentes existentes) ─────────────────────
142
+
143
+ function doraCi(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
144
+ try {
145
+ const { calcularDora } = require('./metricas-dora.js');
146
+ const d = calcularDora(raiz, { ventanaDias, hoy });
147
+ return { disponible: true, procedencia: 'git + GitHub Actions (gh)', caracter: 'evidencia (tendencia; correlación, no causalidad)', dora: d };
148
+ } catch (err) {
149
+ return { disponible: false, razon: `metricas-dora no disponible: ${err.message.split(/\r?\n/)[0]}` };
150
+ }
151
+ }
152
+
153
+ function usoSistema(raiz) {
154
+ const ruta = path.join(raiz, '.planning', 'skill-metrics.json');
155
+ if (!fs.existsSync(ruta)) return { disponible: false, razon: 'sin skill-metrics.json (telemetría de routing sin datos)' };
156
+ try {
157
+ // Forma real: { version, entries: [{skill, estado, timestamp}, ...] }
158
+ // (eventos individuales del hook telemetria-skill-routing) — agregamos aquí.
159
+ const m = JSON.parse(fs.readFileSync(ruta, 'utf8'));
160
+ const eventos = Object.values(m.entries || {});
161
+ const porSkill = {};
162
+ for (const e of eventos) {
163
+ if (!e || !e.skill) continue;
164
+ porSkill[e.skill] = (porSkill[e.skill] || 0) + 1;
165
+ }
166
+ const top = Object.entries(porSkill).sort((a, b) => b[1] - a[1]).slice(0, 5)
167
+ .map(([k, n]) => `${k}:${n}`);
168
+ return { disponible: true, procedencia: 'telemetria-skill-routing', caracter: 'contexto (solo correlación)', invocaciones: eventos.length, top5: top };
169
+ } catch (err) {
170
+ return { disponible: false, razon: `skill-metrics ilegible: ${err.message}` };
171
+ }
172
+ }
173
+
174
+ // ─── Panel ────────────────────────────────────────────────────────────────────
175
+
176
+ function panelEvidencia(raiz = process.cwd(), opts = {}) {
177
+ const ventanaDias = opts.ventanaDias || 30;
178
+ return {
179
+ generado: new Date().toLocaleDateString('sv'),
180
+ ventanaDias,
181
+ frontera: 'Datos 100% locales del proyecto (.planning/ + git). El repo madre no lee nada de aquí (docs/evidencia-valor.md).',
182
+ intercepciones: intercepciones(raiz, { ventanaDias, hoy: opts.hoy }),
183
+ defectos: presionDefectos(raiz, { ventanaDias, hoy: opts.hoy, git: opts.git }),
184
+ doraCi: doraCi(raiz, { ventanaDias, hoy: opts.hoy }),
185
+ uso: usoSistema(raiz),
186
+ };
187
+ }
188
+
189
+ module.exports = { panelEvidencia, intercepciones, presionDefectos, leerNudges, usoSistema, TIPOS_INTERVENCION };