@saulwade/swl-ses 2.6.0 → 2.6.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 (207) hide show
  1. package/CLAUDE.md +197 -197
  2. package/README.md +600 -600
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +932 -932
  8. package/agentes/backend-csharp-swl.md +420 -420
  9. package/agentes/backend-go-swl.md +390 -390
  10. package/agentes/backend-java-swl.md +281 -281
  11. package/agentes/backend-rust-swl.md +364 -364
  12. package/agentes/backend-workers-swl.md +482 -482
  13. package/agentes/cloud-infra-swl.md +509 -509
  14. package/agentes/consolidador-swl.md +541 -541
  15. package/agentes/depurador-swl.md +352 -352
  16. package/agentes/devops-ci-swl.md +400 -400
  17. package/agentes/disenador-ui-swl.md +569 -569
  18. package/agentes/documentador-swl.md +345 -345
  19. package/agentes/frontend-angular-swl.md +621 -621
  20. package/agentes/frontend-css-swl.md +716 -716
  21. package/agentes/frontend-react-swl.md +692 -692
  22. package/agentes/frontend-swl.md +496 -496
  23. package/agentes/frontend-tailwind-swl.md +826 -826
  24. package/agentes/investigador-swl.md +432 -432
  25. package/agentes/investigador-ux-swl.md +505 -505
  26. package/agentes/migrador-swl.md +442 -442
  27. package/agentes/mobile-android-swl.md +511 -511
  28. package/agentes/mobile-cross-swl.md +541 -541
  29. package/agentes/mobile-ios-swl.md +502 -502
  30. package/agentes/mobile-testing-swl.md +302 -302
  31. package/agentes/nemesis-auditor-swl.md +285 -285
  32. package/agentes/observabilidad-swl.md +438 -438
  33. package/agentes/pagos-swl.md +310 -310
  34. package/agentes/perfilador-usuario-swl.md +321 -321
  35. package/agentes/planificador-swl.md +399 -399
  36. package/agentes/producto-prd-swl.md +589 -589
  37. package/agentes/red-team-swl.md +218 -218
  38. package/agentes/release-manager-swl.md +590 -590
  39. package/agentes/rendimiento-swl.md +713 -713
  40. package/agentes/revisor-angular-swl.md +278 -278
  41. package/agentes/revisor-csharp-swl.md +264 -264
  42. package/agentes/revisor-go-swl.md +259 -259
  43. package/agentes/revisor-java-swl.md +257 -257
  44. package/agentes/revisor-kotlin-swl.md +273 -273
  45. package/agentes/revisor-nextjs-swl.md +281 -281
  46. package/agentes/revisor-php-swl.md +271 -271
  47. package/agentes/revisor-react-swl.md +278 -278
  48. package/agentes/revisor-rust-swl.md +346 -346
  49. package/agentes/revisor-seguridad-swl.md +399 -399
  50. package/agentes/revisor-swift-swl.md +268 -268
  51. package/agentes/revisor-typescript-swl.md +346 -346
  52. package/agentes/tdd-qa-swl.md +393 -393
  53. package/comandos/swl/actualizar.md +174 -174
  54. package/comandos/swl/adoptar-proyecto.md +265 -265
  55. package/comandos/swl/aprender.md +836 -836
  56. package/comandos/swl/aprobar-plan.md +146 -146
  57. package/comandos/swl/auditar-deps.md +134 -134
  58. package/comandos/swl/autoresearch.md +264 -264
  59. package/comandos/swl/ayuda.md +224 -224
  60. package/comandos/swl/brainstorm.md +51 -51
  61. package/comandos/swl/briefing.md +119 -119
  62. package/comandos/swl/checkpoint.md +325 -325
  63. package/comandos/swl/claudemd.md +234 -234
  64. package/comandos/swl/compactar.md +310 -310
  65. package/comandos/swl/configurar-ci.md +235 -235
  66. package/comandos/swl/contexto.md +110 -110
  67. package/comandos/swl/contribuir.md +233 -233
  68. package/comandos/swl/crear-skill.md +292 -292
  69. package/comandos/swl/cron.md +194 -194
  70. package/comandos/swl/discutir-fase.md +169 -169
  71. package/comandos/swl/ejecutar-fase.md +233 -233
  72. package/comandos/swl/evaluar-skill.md +520 -520
  73. package/comandos/swl/evolucion-continua.md +73 -73
  74. package/comandos/swl/evolucionar.md +267 -267
  75. package/comandos/swl/exportar-vault.md +583 -583
  76. package/comandos/swl/fix.md +118 -118
  77. package/comandos/swl/gateway.md +158 -158
  78. package/comandos/swl/inbox.md +116 -116
  79. package/comandos/swl/instalar.md +220 -220
  80. package/comandos/swl/instintos.md +86 -86
  81. package/comandos/swl/mapear-codebase.md +312 -312
  82. package/comandos/swl/mcp-status.md +175 -175
  83. package/comandos/swl/modelo.md +100 -100
  84. package/comandos/swl/nemesis.md +433 -433
  85. package/comandos/swl/notificaciones.md +299 -299
  86. package/comandos/swl/nuevo-proyecto.md +251 -251
  87. package/comandos/swl/planear-fase.md +263 -263
  88. package/comandos/swl/plugins.md +256 -256
  89. package/comandos/swl/predecir.md +169 -169
  90. package/comandos/swl/reflect-skills.md +125 -125
  91. package/comandos/swl/release.md +450 -450
  92. package/comandos/swl/revisar-impacto.md +201 -201
  93. package/comandos/swl/revisar.md +330 -330
  94. package/comandos/swl/seguridad.md +189 -189
  95. package/comandos/swl/sesiones.md +200 -200
  96. package/comandos/swl/skill-search.md +113 -113
  97. package/comandos/swl/status.md +345 -345
  98. package/comandos/swl/verificar.md +817 -817
  99. package/comandos/swl/wiki.md +620 -620
  100. package/gateway/cron/jobs.example.json +12 -12
  101. package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -294
  102. package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
  103. package/habilidades/changelog-generator/SKILL.md +174 -174
  104. package/habilidades/compactacion-contexto/SKILL.md +2 -1
  105. package/habilidades/contenedores-docker/SKILL.md +4 -2
  106. package/habilidades/doubt-driven-review/SKILL.md +207 -207
  107. package/habilidades/drift-detection/SKILL.md +1 -1
  108. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  109. package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
  110. package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
  111. package/habilidades/harness-claude-code/SKILL.md +314 -314
  112. package/habilidades/instalar-sistema/SKILL.md +227 -227
  113. package/habilidades/planear-fase/SKILL.md +358 -358
  114. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  115. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  116. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
  117. package/habilidades/release-semver/SKILL.md +2 -2
  118. package/habilidades/tdd-workflow/SKILL.md +749 -749
  119. package/hooks/agente-lifecycle.js +1 -1
  120. package/hooks/audit-trail.js +1 -1
  121. package/hooks/auto-consolidacion.js +1 -1
  122. package/hooks/captura-acciones-post.js +1 -1
  123. package/hooks/captura-acciones-session.js +1 -1
  124. package/hooks/captura-feedback-usuario.js +1 -1
  125. package/hooks/contexto-iteracion.js +1 -1
  126. package/hooks/contexto-subagente.js +68 -68
  127. package/hooks/degradacion-instintos.js +1 -1
  128. package/hooks/grafo-contexto.js +1 -1
  129. package/hooks/guardrail-modelo.js +1 -1
  130. package/hooks/inbox-aviso.js +1 -1
  131. package/hooks/inyeccion-contexto.js +1 -1
  132. package/hooks/lib/agent-matcher.js +1 -1
  133. package/hooks/lib/agent-routing.js +1 -1
  134. package/hooks/lib/captura-acciones.js +1 -1
  135. package/hooks/lib/etapa-metricas.js +1 -1
  136. package/hooks/lib/evolution-tracker.js +1 -1
  137. package/hooks/lib/gateway-notify.js +193 -193
  138. package/hooks/lib/mcp-health.js +1 -1
  139. package/hooks/lib/notificacion-formato.js +58 -0
  140. package/hooks/lib/nudge-tracker.js +1 -1
  141. package/hooks/lib/otlp-exporter.js +1 -1
  142. package/hooks/lib/propose-step.js +1 -1
  143. package/hooks/lib/raiz-proyecto.js +127 -102
  144. package/hooks/lib/run-log.js +1 -1
  145. package/hooks/lib/singleton-guard.js +20 -13
  146. package/hooks/lib/telegram-cliente.js +11 -3
  147. package/hooks/notificacion-telegram.js +13 -3
  148. package/hooks/preservar-estado-pre-compact.js +1 -1
  149. package/hooks/registro-turnos.js +1 -1
  150. package/hooks/resumen-sesion.js +1 -1
  151. package/hooks/risk-scoring.js +1 -1
  152. package/hooks/session-briefing.js +1 -1
  153. package/hooks/spec-gate.js +1 -1
  154. package/hooks/sugerir-regenerar-inventario.js +1 -1
  155. package/hooks/tdd-gate.js +1 -1
  156. package/hooks/telemetria-agentes.js +1 -1
  157. package/hooks/telemetria-skill-routing.js +1 -1
  158. package/hooks/tracking-costos.js +1 -1
  159. package/hooks/validar-formato-post-subagente.js +1 -1
  160. package/hooks/validar-intent-spec.js +1 -1
  161. package/hooks/validar-planning-paths.js +1 -1
  162. package/llms.txt +29 -29
  163. package/manifiestos/canonical-hashes.json +5588 -5257
  164. package/manifiestos/hooks-config.json +469 -469
  165. package/manifiestos/invariantes-criticos.json +30 -30
  166. package/manifiestos/modulos.json +1429 -1428
  167. package/manifiestos/skills-lock.json +1275 -1275
  168. package/package.json +94 -94
  169. package/plugin.json +369 -369
  170. package/scripts/auditar-clases-conocidas.js +134 -134
  171. package/scripts/bootstrap-instintos.js +85 -14
  172. package/scripts/canario-hooks.js +166 -166
  173. package/scripts/cli/autonomia.js +23 -23
  174. package/scripts/cli/benchmark-memoria.js +37 -37
  175. package/scripts/cli/ciclo-autonomo.js +73 -73
  176. package/scripts/cli/ciclo-fase-b.js +102 -102
  177. package/scripts/cli/guardrail-metrics.js +39 -39
  178. package/scripts/cli/memoria-search.js +69 -69
  179. package/scripts/cli/nudge-accionar.js +39 -39
  180. package/scripts/cli/run-eval.js +38 -38
  181. package/scripts/doctor.js +26 -3
  182. package/scripts/evidencia-valor.js +101 -101
  183. package/scripts/field-report.js +16 -16
  184. package/scripts/instalador.js +13 -0
  185. package/scripts/lib/activar-hooks-proyecto.js +116 -116
  186. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -174
  187. package/scripts/lib/ciclo-autonomo/config.js +165 -165
  188. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -174
  189. package/scripts/lib/ciclo-autonomo/fallback.js +77 -77
  190. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -139
  191. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -112
  192. package/scripts/lib/ciclo-autonomo/index.js +301 -301
  193. package/scripts/lib/ciclo-autonomo/lock.js +124 -124
  194. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -122
  195. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -240
  196. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -248
  197. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -190
  198. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -535
  199. package/scripts/lib/evidencia-valor.js +228 -228
  200. package/scripts/lib/expandir-targets.js +71 -71
  201. package/scripts/lib/limpiar-basura-global.js +161 -0
  202. package/scripts/lib/toml-merge.js +204 -204
  203. package/scripts/mcp-server/auth.js +105 -105
  204. package/scripts/mcp-server/cache.js +106 -106
  205. package/scripts/tui/pantallas/install-wizard.js +403 -403
  206. package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +0 -53
  207. package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +0 -372
@@ -1,228 +1,228 @@
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
- /**
48
- * Diagnóstico de POR QUÉ no hay telemetría: los hooks solo se registran a
49
- * nivel PROYECTO (ADR-0009 — un install --global copia los archivos pero NO
50
- * los registra en ~/.claude/settings.json, para no correr 49 hooks en cada
51
- * sesión de cualquier proyecto). Sin registro local, nudges.jsonl y
52
- * skill-metrics.json jamás aparecerán — el panel debe decir CÓMO activarlo,
53
- * no solo "no disponible" (reporte de campo SIGM, 2026-07-09).
54
- */
55
- function hooksRegistradosEnProyecto(raiz) {
56
- try {
57
- const s = JSON.parse(fs.readFileSync(path.join(raiz, '.claude', 'settings.json'), 'utf8'));
58
- return Object.values(s.hooks || {}).some(m => Array.isArray(m) && m.length > 0);
59
- } catch { return false; }
60
- }
61
-
62
- const HINT_ACTIVACION = 'hooks sin registrar a nivel proyecto (el install --global no los registra por diseño, ADR-0009) — activar SIN duplicar componentes con: npx -y @saulwade/swl-ses@latest install --target claude --solo-hooks (en la raíz del proyecto)';
63
-
64
- function leerNudges(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
65
- const ruta = path.join(raiz, '.planning', 'evolution', 'nudges.jsonl');
66
- if (!fs.existsSync(ruta)) {
67
- const razon = hooksRegistradosEnProyecto(raiz)
68
- ? 'sin nudges.jsonl aún (hooks registrados; se acumula con el uso de sesiones)'
69
- : HINT_ACTIVACION;
70
- return { disponible: false, razon, eventos: [] };
71
- }
72
- const corte = hoy.getTime() - ventanaDias * DIA_MS;
73
- const eventos = [];
74
- for (const linea of fs.readFileSync(ruta, 'utf8').split(/\r?\n/)) {
75
- if (!linea.trim()) continue;
76
- try {
77
- const e = JSON.parse(linea);
78
- const t = Date.parse(e.timestamp || e.ts || '');
79
- if (!Number.isNaN(t) && t >= corte) eventos.push(e);
80
- } catch { /* línea corrupta: se omite, no revienta el panel */ }
81
- }
82
- return { disponible: true, eventos };
83
- }
84
-
85
- function intercepciones(raiz, opts = {}) {
86
- const { disponible, razon, eventos } = leerNudges(raiz, opts);
87
- if (!disponible) return { disponible, razon, procedencia: 'hook-event (nudges.jsonl)', total: 0, porTipo: {} };
88
- const porTipo = {};
89
- let intervenciones = 0;
90
- for (const e of eventos) {
91
- const tipo = String(e.tipo || 'desconocido');
92
- porTipo[tipo] = (porTipo[tipo] || 0) + 1;
93
- if (TIPOS_INTERVENCION.has(tipo) || e.blocking === true || e.estado === 'critico') intervenciones++;
94
- }
95
- return {
96
- disponible: true,
97
- procedencia: 'hook-event (nudges.jsonl)',
98
- caracter: 'evidencia',
99
- totalEventos: eventos.length,
100
- intervenciones,
101
- porTipo,
102
- };
103
- }
104
-
105
- // ─── 2. Presión de defectos (git) ────────────────────────────────────────────
106
-
107
- function ejecutarGit(raiz, args) {
108
- return execFileSync('git', args, { cwd: raiz, encoding: 'utf8' });
109
- }
110
-
111
- function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecutarGit } = {}) {
112
- let sujetos;
113
- try {
114
- const desde = new Date(hoy.getTime() - ventanaDias * DIA_MS).toISOString();
115
- sujetos = git(raiz, ['log', `--since=${desde}`, '--pretty=format:%ad|%s', '--date=iso-strict'])
116
- .split(/\r?\n/).filter(Boolean);
117
- } catch (err) {
118
- return { disponible: false, razon: `git no disponible: ${err.message.split(/\r?\n/)[0]}` };
119
- }
120
- const esFix = s => /^(fix|hotfix)(\(|!|:)/.test(s);
121
- const esFeat = s => /^feat(\(|!|:)/.test(s);
122
- const fixes = sujetos.filter(l => esFix(l.split('|').slice(1).join('|')));
123
- const feats = sujetos.filter(l => esFeat(l.split('|').slice(1).join('|')));
124
-
125
- // Escape aproximado: fixes con fecha POSTERIOR a la última verificación de
126
- // fase (VERIFICACION.md más reciente). Sin fases verificadas → no aplica.
127
- // La fecha se toma de GIT (autoría del último commit que tocó el archivo),
128
- // no del mtime del filesystem: tras un clone/checkout todos los archivos
129
- // comparten el mtime de la operación y el proxy daba falso negativo
130
- // (reporte de campo SIGM, 2026-07-09). Fallback a mtime fuera de git.
131
- let escapes = { disponible: false, razon: 'sin VERIFICACION.md en .planning/fases/ (proyecto sin ciclo GSD verificado)' };
132
- try {
133
- const dirFases = path.join(raiz, '.planning', 'fases');
134
- let ultimaVerificacion = 0;
135
- if (fs.existsSync(dirFases)) {
136
- for (const fase of fs.readdirSync(dirFases)) {
137
- const v = path.join(dirFases, fase, 'VERIFICACION.md');
138
- if (!fs.existsSync(v)) continue;
139
- let fecha = 0;
140
- try {
141
- const rel = path.relative(raiz, v);
142
- const ct = parseInt(git(raiz, ['log', '-1', '--format=%ct', '--', rel]).trim(), 10);
143
- if (Number.isFinite(ct) && ct > 0) fecha = ct * 1000;
144
- } catch { /* fuera de git o archivo sin commits → fallback mtime */ }
145
- if (fecha === 0) fecha = fs.statSync(v).mtimeMs;
146
- ultimaVerificacion = Math.max(ultimaVerificacion, fecha);
147
- }
148
- }
149
- if (ultimaVerificacion > 0) {
150
- const post = fixes.filter(l => {
151
- const t = Date.parse(l.split('|')[0]);
152
- return !Number.isNaN(t) && t > ultimaVerificacion;
153
- });
154
- escapes = {
155
- disponible: true,
156
- fixesTrasUltimaVerificacion: post.length,
157
- ultimaVerificacion: new Date(ultimaVerificacion).toLocaleDateString('sv'),
158
- nota: 'lo que el harness NO atrapó — la métrica que lo castiga',
159
- };
160
- }
161
- } catch { /* best-effort */ }
162
-
163
- return {
164
- disponible: true,
165
- procedencia: 'git log (independiente del sistema)',
166
- caracter: 'proxy',
167
- commits: sujetos.length,
168
- fixes: fixes.length,
169
- feats: feats.length,
170
- ratioFixFeat: feats.length > 0 ? +(fixes.length / feats.length).toFixed(2) : null,
171
- escapes,
172
- };
173
- }
174
-
175
- // ─── 3 y 4. DORA/CI y uso (reuso de fuentes existentes) ─────────────────────
176
-
177
- function doraCi(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
178
- try {
179
- const { calcularDora } = require('./metricas-dora.js');
180
- const d = calcularDora(raiz, { ventanaDias, hoy });
181
- return { disponible: true, procedencia: 'git + GitHub Actions (gh)', caracter: 'evidencia (tendencia; correlación, no causalidad)', dora: d };
182
- } catch (err) {
183
- return { disponible: false, razon: `metricas-dora no disponible: ${err.message.split(/\r?\n/)[0]}` };
184
- }
185
- }
186
-
187
- function usoSistema(raiz) {
188
- const ruta = path.join(raiz, '.planning', 'skill-metrics.json');
189
- if (!fs.existsSync(ruta)) {
190
- const razon = hooksRegistradosEnProyecto(raiz)
191
- ? 'sin skill-metrics.json aún (hooks registrados; se acumula con el uso de skills)'
192
- : HINT_ACTIVACION;
193
- return { disponible: false, razon };
194
- }
195
- try {
196
- // Forma real: { version, entries: [{skill, estado, timestamp}, ...] }
197
- // (eventos individuales del hook telemetria-skill-routing) — agregamos aquí.
198
- const m = JSON.parse(fs.readFileSync(ruta, 'utf8'));
199
- const eventos = Object.values(m.entries || {});
200
- const porSkill = {};
201
- for (const e of eventos) {
202
- if (!e || !e.skill) continue;
203
- porSkill[e.skill] = (porSkill[e.skill] || 0) + 1;
204
- }
205
- const top = Object.entries(porSkill).sort((a, b) => b[1] - a[1]).slice(0, 5)
206
- .map(([k, n]) => `${k}:${n}`);
207
- return { disponible: true, procedencia: 'telemetria-skill-routing', caracter: 'contexto (solo correlación)', invocaciones: eventos.length, top5: top };
208
- } catch (err) {
209
- return { disponible: false, razon: `skill-metrics ilegible: ${err.message}` };
210
- }
211
- }
212
-
213
- // ─── Panel ────────────────────────────────────────────────────────────────────
214
-
215
- function panelEvidencia(raiz = process.cwd(), opts = {}) {
216
- const ventanaDias = opts.ventanaDias || 30;
217
- return {
218
- generado: new Date().toLocaleDateString('sv'),
219
- ventanaDias,
220
- frontera: 'Datos 100% locales del proyecto (.planning/ + git). El repo madre no lee nada de aquí (docs/evidencia-valor.md).',
221
- intercepciones: intercepciones(raiz, { ventanaDias, hoy: opts.hoy }),
222
- defectos: presionDefectos(raiz, { ventanaDias, hoy: opts.hoy, git: opts.git }),
223
- doraCi: doraCi(raiz, { ventanaDias, hoy: opts.hoy }),
224
- uso: usoSistema(raiz),
225
- };
226
- }
227
-
228
- module.exports = { panelEvidencia, intercepciones, presionDefectos, leerNudges, usoSistema, TIPOS_INTERVENCION };
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
+ /**
48
+ * Diagnóstico de POR QUÉ no hay telemetría: los hooks solo se registran a
49
+ * nivel PROYECTO (ADR-0009 — un install --global copia los archivos pero NO
50
+ * los registra en ~/.claude/settings.json, para no correr 49 hooks en cada
51
+ * sesión de cualquier proyecto). Sin registro local, nudges.jsonl y
52
+ * skill-metrics.json jamás aparecerán — el panel debe decir CÓMO activarlo,
53
+ * no solo "no disponible" (reporte de campo SIGM, 2026-07-09).
54
+ */
55
+ function hooksRegistradosEnProyecto(raiz) {
56
+ try {
57
+ const s = JSON.parse(fs.readFileSync(path.join(raiz, '.claude', 'settings.json'), 'utf8'));
58
+ return Object.values(s.hooks || {}).some(m => Array.isArray(m) && m.length > 0);
59
+ } catch { return false; }
60
+ }
61
+
62
+ const HINT_ACTIVACION = 'hooks sin registrar a nivel proyecto (el install --global no los registra por diseño, ADR-0009) — activar SIN duplicar componentes con: npx -y @saulwade/swl-ses@latest install --target claude --solo-hooks (en la raíz del proyecto)';
63
+
64
+ function leerNudges(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
65
+ const ruta = path.join(raiz, '.planning', 'evolution', 'nudges.jsonl');
66
+ if (!fs.existsSync(ruta)) {
67
+ const razon = hooksRegistradosEnProyecto(raiz)
68
+ ? 'sin nudges.jsonl aún (hooks registrados; se acumula con el uso de sesiones)'
69
+ : HINT_ACTIVACION;
70
+ return { disponible: false, razon, eventos: [] };
71
+ }
72
+ const corte = hoy.getTime() - ventanaDias * DIA_MS;
73
+ const eventos = [];
74
+ for (const linea of fs.readFileSync(ruta, 'utf8').split(/\r?\n/)) {
75
+ if (!linea.trim()) continue;
76
+ try {
77
+ const e = JSON.parse(linea);
78
+ const t = Date.parse(e.timestamp || e.ts || '');
79
+ if (!Number.isNaN(t) && t >= corte) eventos.push(e);
80
+ } catch { /* línea corrupta: se omite, no revienta el panel */ }
81
+ }
82
+ return { disponible: true, eventos };
83
+ }
84
+
85
+ function intercepciones(raiz, opts = {}) {
86
+ const { disponible, razon, eventos } = leerNudges(raiz, opts);
87
+ if (!disponible) return { disponible, razon, procedencia: 'hook-event (nudges.jsonl)', total: 0, porTipo: {} };
88
+ const porTipo = {};
89
+ let intervenciones = 0;
90
+ for (const e of eventos) {
91
+ const tipo = String(e.tipo || 'desconocido');
92
+ porTipo[tipo] = (porTipo[tipo] || 0) + 1;
93
+ if (TIPOS_INTERVENCION.has(tipo) || e.blocking === true || e.estado === 'critico') intervenciones++;
94
+ }
95
+ return {
96
+ disponible: true,
97
+ procedencia: 'hook-event (nudges.jsonl)',
98
+ caracter: 'evidencia',
99
+ totalEventos: eventos.length,
100
+ intervenciones,
101
+ porTipo,
102
+ };
103
+ }
104
+
105
+ // ─── 2. Presión de defectos (git) ────────────────────────────────────────────
106
+
107
+ function ejecutarGit(raiz, args) {
108
+ return execFileSync('git', args, { cwd: raiz, encoding: 'utf8' });
109
+ }
110
+
111
+ function presionDefectos(raiz, { ventanaDias = 30, hoy = new Date(), git = ejecutarGit } = {}) {
112
+ let sujetos;
113
+ try {
114
+ const desde = new Date(hoy.getTime() - ventanaDias * DIA_MS).toISOString();
115
+ sujetos = git(raiz, ['log', `--since=${desde}`, '--pretty=format:%ad|%s', '--date=iso-strict'])
116
+ .split(/\r?\n/).filter(Boolean);
117
+ } catch (err) {
118
+ return { disponible: false, razon: `git no disponible: ${err.message.split(/\r?\n/)[0]}` };
119
+ }
120
+ const esFix = s => /^(fix|hotfix)(\(|!|:)/.test(s);
121
+ const esFeat = s => /^feat(\(|!|:)/.test(s);
122
+ const fixes = sujetos.filter(l => esFix(l.split('|').slice(1).join('|')));
123
+ const feats = sujetos.filter(l => esFeat(l.split('|').slice(1).join('|')));
124
+
125
+ // Escape aproximado: fixes con fecha POSTERIOR a la última verificación de
126
+ // fase (VERIFICACION.md más reciente). Sin fases verificadas → no aplica.
127
+ // La fecha se toma de GIT (autoría del último commit que tocó el archivo),
128
+ // no del mtime del filesystem: tras un clone/checkout todos los archivos
129
+ // comparten el mtime de la operación y el proxy daba falso negativo
130
+ // (reporte de campo SIGM, 2026-07-09). Fallback a mtime fuera de git.
131
+ let escapes = { disponible: false, razon: 'sin VERIFICACION.md en .planning/fases/ (proyecto sin ciclo GSD verificado)' };
132
+ try {
133
+ const dirFases = path.join(raiz, '.planning', 'fases');
134
+ let ultimaVerificacion = 0;
135
+ if (fs.existsSync(dirFases)) {
136
+ for (const fase of fs.readdirSync(dirFases)) {
137
+ const v = path.join(dirFases, fase, 'VERIFICACION.md');
138
+ if (!fs.existsSync(v)) continue;
139
+ let fecha = 0;
140
+ try {
141
+ const rel = path.relative(raiz, v);
142
+ const ct = parseInt(git(raiz, ['log', '-1', '--format=%ct', '--', rel]).trim(), 10);
143
+ if (Number.isFinite(ct) && ct > 0) fecha = ct * 1000;
144
+ } catch { /* fuera de git o archivo sin commits → fallback mtime */ }
145
+ if (fecha === 0) fecha = fs.statSync(v).mtimeMs;
146
+ ultimaVerificacion = Math.max(ultimaVerificacion, fecha);
147
+ }
148
+ }
149
+ if (ultimaVerificacion > 0) {
150
+ const post = fixes.filter(l => {
151
+ const t = Date.parse(l.split('|')[0]);
152
+ return !Number.isNaN(t) && t > ultimaVerificacion;
153
+ });
154
+ escapes = {
155
+ disponible: true,
156
+ fixesTrasUltimaVerificacion: post.length,
157
+ ultimaVerificacion: new Date(ultimaVerificacion).toLocaleDateString('sv'),
158
+ nota: 'lo que el harness NO atrapó — la métrica que lo castiga',
159
+ };
160
+ }
161
+ } catch { /* best-effort */ }
162
+
163
+ return {
164
+ disponible: true,
165
+ procedencia: 'git log (independiente del sistema)',
166
+ caracter: 'proxy',
167
+ commits: sujetos.length,
168
+ fixes: fixes.length,
169
+ feats: feats.length,
170
+ ratioFixFeat: feats.length > 0 ? +(fixes.length / feats.length).toFixed(2) : null,
171
+ escapes,
172
+ };
173
+ }
174
+
175
+ // ─── 3 y 4. DORA/CI y uso (reuso de fuentes existentes) ─────────────────────
176
+
177
+ function doraCi(raiz, { ventanaDias = 30, hoy = new Date() } = {}) {
178
+ try {
179
+ const { calcularDora } = require('./metricas-dora.js');
180
+ const d = calcularDora(raiz, { ventanaDias, hoy });
181
+ return { disponible: true, procedencia: 'git + GitHub Actions (gh)', caracter: 'evidencia (tendencia; correlación, no causalidad)', dora: d };
182
+ } catch (err) {
183
+ return { disponible: false, razon: `metricas-dora no disponible: ${err.message.split(/\r?\n/)[0]}` };
184
+ }
185
+ }
186
+
187
+ function usoSistema(raiz) {
188
+ const ruta = path.join(raiz, '.planning', 'skill-metrics.json');
189
+ if (!fs.existsSync(ruta)) {
190
+ const razon = hooksRegistradosEnProyecto(raiz)
191
+ ? 'sin skill-metrics.json aún (hooks registrados; se acumula con el uso de skills)'
192
+ : HINT_ACTIVACION;
193
+ return { disponible: false, razon };
194
+ }
195
+ try {
196
+ // Forma real: { version, entries: [{skill, estado, timestamp}, ...] }
197
+ // (eventos individuales del hook telemetria-skill-routing) — agregamos aquí.
198
+ const m = JSON.parse(fs.readFileSync(ruta, 'utf8'));
199
+ const eventos = Object.values(m.entries || {});
200
+ const porSkill = {};
201
+ for (const e of eventos) {
202
+ if (!e || !e.skill) continue;
203
+ porSkill[e.skill] = (porSkill[e.skill] || 0) + 1;
204
+ }
205
+ const top = Object.entries(porSkill).sort((a, b) => b[1] - a[1]).slice(0, 5)
206
+ .map(([k, n]) => `${k}:${n}`);
207
+ return { disponible: true, procedencia: 'telemetria-skill-routing', caracter: 'contexto (solo correlación)', invocaciones: eventos.length, top5: top };
208
+ } catch (err) {
209
+ return { disponible: false, razon: `skill-metrics ilegible: ${err.message}` };
210
+ }
211
+ }
212
+
213
+ // ─── Panel ────────────────────────────────────────────────────────────────────
214
+
215
+ function panelEvidencia(raiz = process.cwd(), opts = {}) {
216
+ const ventanaDias = opts.ventanaDias || 30;
217
+ return {
218
+ generado: new Date().toLocaleDateString('sv'),
219
+ ventanaDias,
220
+ frontera: 'Datos 100% locales del proyecto (.planning/ + git). El repo madre no lee nada de aquí (docs/evidencia-valor.md).',
221
+ intercepciones: intercepciones(raiz, { ventanaDias, hoy: opts.hoy }),
222
+ defectos: presionDefectos(raiz, { ventanaDias, hoy: opts.hoy, git: opts.git }),
223
+ doraCi: doraCi(raiz, { ventanaDias, hoy: opts.hoy }),
224
+ uso: usoSistema(raiz),
225
+ };
226
+ }
227
+
228
+ module.exports = { panelEvidencia, intercepciones, presionDefectos, leerNudges, usoSistema, TIPOS_INTERVENCION };
@@ -1,71 +1,71 @@
1
- 'use strict';
2
-
3
- /**
4
- * Expansión de --target (CSV) y --all-runtimes para multi-target install.
5
- *
6
- * ADR-0019 Sub-fase 2.5.
7
- *
8
- * Extraído de bin/swl-ses.js para permitir tests unitarios. NUNCA debe tener
9
- * side effects fuera de logging — solo manipula la lista de strings.
10
- *
11
- * @module scripts/lib/expandir-targets
12
- */
13
-
14
- const { listarRuntimesInstalables, RUNTIMES } = require('./detectar-runtime');
15
-
16
- /**
17
- * Expande las opciones del CLI a un array de target IDs.
18
- *
19
- * Reglas:
20
- * - `all_runtimes` tiene prioridad sobre `target`. Si ambos vienen, se loggea
21
- * aviso (vía `logger.warn`) y se usa `all_runtimes`.
22
- * - `target='a,b,c'` → ['a','b','c'].
23
- * - `target='claude'` → ['claude'].
24
- * - Sin nada → ['claude'] (default histórico, backward-compat).
25
- * - Duplicados se deduplican preservando el primer orden.
26
- * - Targets desconocidos se omiten con aviso (logger.warn).
27
- *
28
- * @param {object} opciones - Objeto con `target` (string|undefined) y `all_runtimes` (bool).
29
- * @param {object} [logger] - Sustituible para tests. Default console.
30
- * @returns {{ targets: string[], omitidos: string[], errores: string[] }}
31
- */
32
- function expandirTargets(opciones, logger) {
33
- const log = logger || console;
34
- const omitidos = [];
35
- const errores = [];
36
-
37
- let candidatos;
38
- if (opciones.all_runtimes) {
39
- if (opciones.target && typeof opciones.target === 'string') {
40
- log.warn && log.warn('[expandir-targets] --all-runtimes tiene prioridad sobre --target; se ignora --target.');
41
- }
42
- candidatos = listarRuntimesInstalables();
43
- } else if (typeof opciones.target === 'string' && opciones.target.includes(',')) {
44
- candidatos = opciones.target.split(',').map(s => s.trim()).filter(Boolean);
45
- } else if (typeof opciones.target === 'string' && opciones.target.length > 0) {
46
- candidatos = [opciones.target.trim()];
47
- } else {
48
- candidatos = ['claude']; // backward-compat
49
- }
50
-
51
- const visto = new Set();
52
- const targets = [];
53
- for (const t of candidatos) {
54
- if (visto.has(t)) continue;
55
- visto.add(t);
56
- if (!RUNTIMES[t]) {
57
- log.warn && log.warn(`[expandir-targets] Target desconocido omitido: "${t}". Disponibles: ${Object.keys(RUNTIMES).join(', ')}`);
58
- omitidos.push(t);
59
- continue;
60
- }
61
- targets.push(t);
62
- }
63
-
64
- if (targets.length === 0) {
65
- errores.push('Ningún target válido tras expansión.');
66
- }
67
-
68
- return { targets, omitidos, errores };
69
- }
70
-
71
- module.exports = { expandirTargets };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Expansión de --target (CSV) y --all-runtimes para multi-target install.
5
+ *
6
+ * ADR-0019 Sub-fase 2.5.
7
+ *
8
+ * Extraído de bin/swl-ses.js para permitir tests unitarios. NUNCA debe tener
9
+ * side effects fuera de logging — solo manipula la lista de strings.
10
+ *
11
+ * @module scripts/lib/expandir-targets
12
+ */
13
+
14
+ const { listarRuntimesInstalables, RUNTIMES } = require('./detectar-runtime');
15
+
16
+ /**
17
+ * Expande las opciones del CLI a un array de target IDs.
18
+ *
19
+ * Reglas:
20
+ * - `all_runtimes` tiene prioridad sobre `target`. Si ambos vienen, se loggea
21
+ * aviso (vía `logger.warn`) y se usa `all_runtimes`.
22
+ * - `target='a,b,c'` → ['a','b','c'].
23
+ * - `target='claude'` → ['claude'].
24
+ * - Sin nada → ['claude'] (default histórico, backward-compat).
25
+ * - Duplicados se deduplican preservando el primer orden.
26
+ * - Targets desconocidos se omiten con aviso (logger.warn).
27
+ *
28
+ * @param {object} opciones - Objeto con `target` (string|undefined) y `all_runtimes` (bool).
29
+ * @param {object} [logger] - Sustituible para tests. Default console.
30
+ * @returns {{ targets: string[], omitidos: string[], errores: string[] }}
31
+ */
32
+ function expandirTargets(opciones, logger) {
33
+ const log = logger || console;
34
+ const omitidos = [];
35
+ const errores = [];
36
+
37
+ let candidatos;
38
+ if (opciones.all_runtimes) {
39
+ if (opciones.target && typeof opciones.target === 'string') {
40
+ log.warn && log.warn('[expandir-targets] --all-runtimes tiene prioridad sobre --target; se ignora --target.');
41
+ }
42
+ candidatos = listarRuntimesInstalables();
43
+ } else if (typeof opciones.target === 'string' && opciones.target.includes(',')) {
44
+ candidatos = opciones.target.split(',').map(s => s.trim()).filter(Boolean);
45
+ } else if (typeof opciones.target === 'string' && opciones.target.length > 0) {
46
+ candidatos = [opciones.target.trim()];
47
+ } else {
48
+ candidatos = ['claude']; // backward-compat
49
+ }
50
+
51
+ const visto = new Set();
52
+ const targets = [];
53
+ for (const t of candidatos) {
54
+ if (visto.has(t)) continue;
55
+ visto.add(t);
56
+ if (!RUNTIMES[t]) {
57
+ log.warn && log.warn(`[expandir-targets] Target desconocido omitido: "${t}". Disponibles: ${Object.keys(RUNTIMES).join(', ')}`);
58
+ omitidos.push(t);
59
+ continue;
60
+ }
61
+ targets.push(t);
62
+ }
63
+
64
+ if (targets.length === 0) {
65
+ errores.push('Ningún target válido tras expansión.');
66
+ }
67
+
68
+ return { targets, omitidos, errores };
69
+ }
70
+
71
+ module.exports = { expandirTargets };