@saulwade/swl-ses 2.5.3 → 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 (214) hide show
  1. package/CLAUDE.md +9 -9
  2. package/README.md +37 -37
  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 -908
  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/bin/swl-ses.js +32 -7
  54. package/comandos/swl/actualizar.md +3 -3
  55. package/comandos/swl/aprender.md +13 -0
  56. package/comandos/swl/deuda-codigo.md +97 -97
  57. package/comandos/swl/evaluar-skill.md +18 -3
  58. package/comandos/swl/evolucion-continua.md +73 -0
  59. package/comandos/swl/evolucionar.md +13 -0
  60. package/comandos/swl/instalar.md +4 -4
  61. package/comandos/swl/notificaciones.md +1 -1
  62. package/comandos/swl/status.md +2 -2
  63. package/gateway/cron/jobs.example.json +12 -0
  64. package/habilidades/auto-evolucion-protocolo/SKILL.md +19 -1
  65. package/habilidades/autoresearch/SKILL.md +3 -2
  66. package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
  67. package/habilidades/benchmark-memoria/SKILL.md +7 -7
  68. package/habilidades/changelog-generator/SKILL.md +1 -1
  69. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  70. package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
  71. package/habilidades/compactacion-contexto/SKILL.md +2 -1
  72. package/habilidades/contenedores-docker/SKILL.md +4 -2
  73. package/habilidades/context-builder/SKILL.md +4 -0
  74. package/habilidades/doubt-driven-review/SKILL.md +17 -1
  75. package/habilidades/drift-detection/SKILL.md +6 -1
  76. package/habilidades/ejecutar-fase/SKILL.md +6 -6
  77. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  78. package/habilidades/eval-framework/SKILL.md +8 -3
  79. package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
  80. package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
  81. package/habilidades/harness-claude-code/SKILL.md +7 -3
  82. package/habilidades/infra-github-actions/SKILL.md +4 -3
  83. package/habilidades/instalar-sistema/SKILL.md +5 -1
  84. package/habilidades/memoria-busqueda/SKILL.md +31 -39
  85. package/habilidades/planear-fase/SKILL.md +9 -1
  86. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  87. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  88. package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
  89. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
  90. package/habilidades/release-semver/SKILL.md +2 -2
  91. package/habilidades/swl-claudemd/SKILL.md +6 -7
  92. package/habilidades/swl-dashboard/SKILL.md +11 -43
  93. package/habilidades/tdd-workflow/SKILL.md +12 -7
  94. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  95. package/hooks/agente-lifecycle.js +2 -1
  96. package/hooks/aiisms-detector.js +13 -4
  97. package/hooks/audit-trail.js +2 -1
  98. package/hooks/auto-consolidacion.js +2 -1
  99. package/hooks/captura-acciones-post.js +2 -1
  100. package/hooks/captura-acciones-session.js +2 -1
  101. package/hooks/captura-feedback-usuario.js +3 -2
  102. package/hooks/claudemd-bloat-detector.js +12 -3
  103. package/hooks/claudemd-duplicacion-detector.js +13 -3
  104. package/hooks/contexto-iteracion.js +2 -1
  105. package/hooks/contexto-subagente.js +68 -68
  106. package/hooks/degradacion-instintos.js +2 -1
  107. package/hooks/extraccion-aprendizajes.js +109 -15
  108. package/hooks/grafo-contexto.js +2 -1
  109. package/hooks/guardrail-modelo.js +2 -1
  110. package/hooks/inbox-aviso.js +2 -1
  111. package/hooks/inyeccion-contexto.js +2 -1
  112. package/hooks/lib/agent-matcher.js +2 -1
  113. package/hooks/lib/agent-routing.js +2 -1
  114. package/hooks/lib/autonomia.js +5 -3
  115. package/hooks/lib/captura-acciones.js +2 -1
  116. package/hooks/lib/consolidation-lock.js +21 -10
  117. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  118. package/hooks/lib/etapa-metricas.js +2 -1
  119. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  120. package/hooks/lib/evolution-tracker.js +2 -1
  121. package/hooks/lib/gateway-notify.js +17 -3
  122. package/hooks/lib/loop-telemetry.js +5 -4
  123. package/hooks/lib/mcp-health.js +2 -1
  124. package/hooks/lib/memory-search.js +4 -0
  125. package/hooks/lib/merkle-audit.js +58 -6
  126. package/hooks/lib/notificacion-formato.js +58 -0
  127. package/hooks/lib/nudge-tracker.js +2 -1
  128. package/hooks/lib/otlp-exporter.js +2 -1
  129. package/hooks/lib/propose-step.js +3 -2
  130. package/hooks/lib/raiz-proyecto.js +127 -0
  131. package/hooks/lib/run-log.js +2 -1
  132. package/hooks/lib/singleton-guard.js +225 -27
  133. package/hooks/lib/telegram-cliente.js +28 -11
  134. package/hooks/notificacion-telegram.js +13 -3
  135. package/hooks/preservar-estado-pre-compact.js +2 -1
  136. package/hooks/proteccion-rutas.js +59 -3
  137. package/hooks/registro-turnos.js +2 -1
  138. package/hooks/resumen-sesion.js +2 -1
  139. package/hooks/risk-scoring.js +2 -1
  140. package/hooks/rotar-audit-auto.js +46 -20
  141. package/hooks/session-briefing.js +127 -1
  142. package/hooks/spec-gate.js +2 -1
  143. package/hooks/sugerir-contribuir.js +6 -3
  144. package/hooks/sugerir-regenerar-inventario.js +3 -2
  145. package/hooks/tdd-gate.js +2 -1
  146. package/hooks/telemetria-agentes.js +2 -1
  147. package/hooks/telemetria-skill-routing.js +2 -1
  148. package/hooks/tracking-costos.js +4 -3
  149. package/hooks/validar-formato-post-subagente.js +2 -1
  150. package/hooks/validar-intent-spec.js +2 -1
  151. package/hooks/validar-memoria-hook.js +13 -3
  152. package/hooks/validar-planning-paths.js +2 -1
  153. package/instintos/perfil-usuario.yaml +506 -3
  154. package/instintos/proyecto.yaml +78 -0
  155. package/llms.txt +29 -29
  156. package/manifiestos/canonical-hashes.json +5588 -4925
  157. package/manifiestos/hooks-config.json +469 -469
  158. package/manifiestos/invariantes-criticos.json +30 -30
  159. package/manifiestos/modulos.json +1429 -1423
  160. package/manifiestos/planning-paths.json +1 -0
  161. package/manifiestos/skills-lock.json +1275 -1275
  162. package/package.json +94 -95
  163. package/plugin.json +369 -369
  164. package/scripts/actualizar.js +3 -0
  165. package/scripts/auditar-clases-conocidas.js +134 -106
  166. package/scripts/benchmark-memoria.js +1 -0
  167. package/scripts/bootstrap-instintos.js +85 -14
  168. package/scripts/canario-hooks.js +166 -166
  169. package/scripts/cli/autonomia.js +23 -0
  170. package/scripts/cli/benchmark-memoria.js +37 -0
  171. package/scripts/cli/ciclo-autonomo.js +73 -0
  172. package/scripts/cli/ciclo-fase-b.js +102 -0
  173. package/scripts/cli/guardrail-metrics.js +39 -0
  174. package/scripts/cli/loop-telemetry.js +4 -2
  175. package/scripts/cli/memoria-search.js +69 -0
  176. package/scripts/cli/nudge-accionar.js +39 -0
  177. package/scripts/cli/run-eval.js +38 -0
  178. package/scripts/cli/run-skill-evals.js +13 -2
  179. package/scripts/derivar-feature-list.js +15 -14
  180. package/scripts/desinstalar.js +11 -0
  181. package/scripts/doctor.js +50 -13
  182. package/scripts/evidencia-valor.js +101 -101
  183. package/scripts/field-report.js +16 -16
  184. package/scripts/instalador.js +98 -7
  185. package/scripts/lib/activar-hooks-proyecto.js +116 -104
  186. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  187. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  188. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  189. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  190. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  191. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  192. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  193. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  194. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  195. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  196. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  197. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  198. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  199. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  200. package/scripts/lib/estado.js +9 -0
  201. package/scripts/lib/evidencia-valor.js +228 -228
  202. package/scripts/lib/expandir-targets.js +71 -71
  203. package/scripts/lib/gitignore-manifest.js +8 -1
  204. package/scripts/lib/hooks-settings.js +45 -0
  205. package/scripts/lib/limpiar-basura-global.js +161 -0
  206. package/scripts/lib/toml-merge.js +204 -204
  207. package/scripts/mcp-server/auth.js +105 -105
  208. package/scripts/mcp-server/cache.js +106 -106
  209. package/scripts/rotar-audit-logs.js +48 -2
  210. package/scripts/run-eval.js +1 -0
  211. package/scripts/run-skill-evals.js +287 -8
  212. package/scripts/smoke-test.js +16 -8
  213. package/scripts/tui/pantallas/install-wizard.js +69 -13
  214. package/scripts/validar.js +40 -1
@@ -15,8 +15,10 @@
15
15
  * node scripts/run-skill-evals.js <nombre-skill> — imprime plan
16
16
  * node scripts/run-skill-evals.js <nombre-skill> --json — plan JSON
17
17
  * node scripts/run-skill-evals.js <nombre-skill> --record-baseline --score=N
18
- * node scripts/run-skill-evals.js <nombre-skill> --record-after --score=N \
19
- * [--hypothesis="razon-causal"] [--check-invariants]
18
+ * node scripts/run-skill-evals.js <nombre-skill> --emitir-evaluacion \
19
+ * --score=N --producido-por=<actor> [--badge=B] — artefacto de procedencia
20
+ * node scripts/run-skill-evals.js <nombre-skill> --record-after \
21
+ * [--hypothesis="razon-causal"] [--check-invariants] [--evaluacion=<path>]
20
22
  * node scripts/run-skill-evals.js <nombre-skill> --record-revert --score=N
21
23
  * node scripts/run-skill-evals.js --list — lista skills con evals
22
24
  *
@@ -25,16 +27,31 @@
25
27
  * --check-invariants valida safety invariants declarados en el frontmatter
26
28
  * antes de aceptar el commit (operador Commit §3.2.2).
27
29
  *
30
+ * Anti-gaming (DT-AGP-SCORE-AUTODECLARADO, cerrada 2026-07-10 — regla
31
+ * `seguridad-agentes.md § Anti-gaming`): el score que APRUEBA una evolución
32
+ * (--record-after) NO se acepta de un flag arbitrario. Debe venir de un
33
+ * artefacto de evaluación con procedencia (.planning/evolution/evaluaciones/
34
+ * <target>.json) emitido por /swl:evaluar-skill vía --emitir-evaluacion:
35
+ * fechado, con `producido_por` ≠ target, y fresco (≤7 días). Si además se
36
+ * pasa --score, debe COINCIDIR con el del artefacto. Todo evento registra el
37
+ * actor que produjo el score — el evaluado nunca aprueba su propia evidencia.
38
+ *
28
39
  * Zero-deps.
29
40
  */
30
41
 
31
- const fs = require('fs');
32
- const path = require('path');
42
+ const fs = require('fs');
43
+ const path = require('path');
44
+ const crypto = require('crypto');
33
45
  const { validarInvariantesDeAgente } = require('./lib/agp-frontmatter');
34
46
 
35
47
  const CWD = process.cwd();
36
48
  const EVOL_DIR = path.join(CWD, '.planning', 'evolution');
37
49
  const EVOL_LOG = path.join(EVOL_DIR, 'evoluciones.jsonl');
50
+ const EVALUACIONES_DIR = path.join(EVOL_DIR, 'evaluaciones');
51
+
52
+ /** Ventana de frescura del artefacto de evaluación (ms): 7 días. Una
53
+ * evaluación más vieja no puede aprobar una mutación nueva — se re-corre. */
54
+ const FRESCURA_EVALUACION_MS = 7 * 24 * 3600 * 1000;
38
55
 
39
56
  function ensureDir(d) { try { fs.mkdirSync(d, { recursive: true }); } catch { /* ignore */ } }
40
57
 
@@ -49,6 +66,36 @@ function resolverEvals(nombre) {
49
66
  return null;
50
67
  }
51
68
 
69
+ /** Resuelve el archivo de contenido del target (SKILL.md o el .md del agente). */
70
+ function resolverContenidoTarget(nombre) {
71
+ const candidatos = [
72
+ path.join(CWD, 'habilidades', nombre, 'SKILL.md'),
73
+ path.join(CWD, 'agentes', `${nombre}.md`),
74
+ ];
75
+ for (const c of candidatos) {
76
+ if (fs.existsSync(c)) return c;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * SHA256 del contenido evaluado (EOL normalizado — CRLF/LF no cambia el hash,
83
+ * clase C2). Liga el artefacto de evaluación a la versión EXACTA evaluada: si
84
+ * el skill/agente muta en la ventana de frescura entre --emitir-evaluacion y
85
+ * --record-after, el hash deja de coincidir y la aprobación se rechaza
86
+ * (cierra la ventana TOCTOU del anti-gaming, nemesis iter-1 S3-2).
87
+ */
88
+ function hashContenidoTarget(nombre) {
89
+ const ruta = resolverContenidoTarget(nombre);
90
+ if (!ruta) return null;
91
+ try {
92
+ const txt = fs.readFileSync(ruta, 'utf8').replace(/\r\n/g, '\n');
93
+ return crypto.createHash('sha256').update(txt).digest('hex');
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
52
99
  function listarSkillsConEvals() {
53
100
  const out = [];
54
101
  try {
@@ -107,6 +154,171 @@ function registrarEvento(tipo, detalles) {
107
154
  }
108
155
  }
109
156
 
157
+ /**
158
+ /** Escritura atómica de JSON con require tolerante (repo madre vs destino). */
159
+ function escribirJSONAtomico(ruta, obj) {
160
+ let atomicWriteJSON = null;
161
+ try {
162
+ ({ atomicWriteJSON } = require(path.join(__dirname, '..', 'hooks', 'lib', 'atomic-write.js')));
163
+ } catch {
164
+ try { ({ atomicWriteJSON } = require(path.join(__dirname, 'atomic-write.js'))); } catch { /* fallback abajo */ }
165
+ }
166
+ if (atomicWriteJSON) atomicWriteJSON(ruta, obj);
167
+ else fs.writeFileSync(ruta, JSON.stringify(obj, null, 2) + '\n', 'utf8');
168
+ }
169
+
170
+ /**
171
+ * Marca un artefacto de evaluación como consumido tras un --record-after
172
+ * exitoso: una evaluación aprueba UNA aplicación. Impide el replay del mismo
173
+ * artefacto para inflar el contador de evoluciones (nemesis iter-1 S3-1).
174
+ */
175
+ function marcarEvaluacionConsumida(ruta) {
176
+ try {
177
+ const art = JSON.parse(fs.readFileSync(ruta, 'utf8'));
178
+ art.consumido_en = new Date().toISOString();
179
+ escribirJSONAtomico(ruta, art);
180
+ } catch (e) {
181
+ // Fail-loud (regla anti-fallback-silencioso): si el marcado falla (disco
182
+ // lleno, permisos), el evento `aplicada` YA quedó registrado y el artefacto
183
+ // sigue sin `consumido_en` → replayable. Avisar en vez de tragarlo, para
184
+ // que el usuario borre el artefacto a mano (re-auditoría nemesis iter-2).
185
+ process.stderr.write(
186
+ `[run-skill-evals] AVISO: no se pudo marcar el artefacto como consumido (${e.message}).\n` +
187
+ ` El artefacto ${ruta} sigue siendo replayable — bórralo manualmente para evitar un doble --record-after.\n`
188
+ );
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Escribe el artefacto de evaluación con procedencia (modo --emitir-evaluacion).
194
+ * Lo invoca /swl:evaluar-skill al cerrar su reporte; es la ÚNICA fuente
195
+ * válida del score que --record-after acepta.
196
+ */
197
+ function emitirEvaluacion({ target, score, badge, producidoPor }) {
198
+ ensureDir(EVALUACIONES_DIR);
199
+ const ruta = path.join(EVALUACIONES_DIR, `${target}.json`);
200
+
201
+ // Emisión concurrente (nemesis iter-1 S3-7): si ya existe un artefacto para
202
+ // este target emitido hace segundos (otra evaluación en curso, regla
203
+ // sesiones-paralelas), avisar antes de sobrescribirlo — el last-write-wins
204
+ // silencioso podía tirar una evaluación fresca justo antes de leerse.
205
+ try {
206
+ if (fs.existsSync(ruta)) {
207
+ const previo = JSON.parse(fs.readFileSync(ruta, 'utf8'));
208
+ const edadMs = Date.now() - Date.parse(previo.fecha || '');
209
+ if (Number.isFinite(edadMs) && edadMs >= 0 && edadMs < 10_000) {
210
+ process.stderr.write(
211
+ `[run-skill-evals] AVISO: sobrescribiendo un artefacto de evaluación de "${target}" ` +
212
+ `emitido hace ${(edadMs / 1000).toFixed(1)}s (posible evaluación concurrente). ` +
213
+ `Verifica que no haya dos /swl:evaluar-skill corriendo sobre el mismo target.\n`
214
+ );
215
+ }
216
+ }
217
+ } catch { /* artefacto ilegible: se sobrescribe sin aviso */ }
218
+
219
+ const artefacto = {
220
+ target,
221
+ score,
222
+ badge: badge || null,
223
+ producido_por: producidoPor,
224
+ fecha: new Date().toISOString(),
225
+ hash_contenido: hashContenidoTarget(target),
226
+ };
227
+ escribirJSONAtomico(ruta, artefacto);
228
+ return { ruta, artefacto };
229
+ }
230
+
231
+ /**
232
+ * Carga y valida el artefacto de evaluación para --record-after.
233
+ * Devuelve { ok, artefacto?, ruta?, error?, codigo? } — codigo 8 = artefacto
234
+ * ausente/inválido/vencido/auto-calificado; el caller decide el exit.
235
+ */
236
+ function cargarEvaluacion(target, rutaOverride) {
237
+ const ruta = rutaOverride
238
+ ? path.resolve(CWD, rutaOverride)
239
+ : path.join(EVALUACIONES_DIR, `${target}.json`);
240
+
241
+ if (!fs.existsSync(ruta)) {
242
+ return {
243
+ ok: false,
244
+ codigo: 8,
245
+ error: `FAIL: --record-after exige un artefacto de evaluación con procedencia y no existe:\n` +
246
+ ` ${path.relative(CWD, ruta)}\n` +
247
+ `Genéralo corriendo /swl:evaluar-skill ${target} (emite el artefacto vía --emitir-evaluacion).\n` +
248
+ `El score que aprueba una evolución no puede declararlo el propio evaluado (DT-AGP-SCORE-AUTODECLARADO).`,
249
+ };
250
+ }
251
+
252
+ let artefacto;
253
+ try {
254
+ artefacto = JSON.parse(fs.readFileSync(ruta, 'utf8'));
255
+ } catch (e) {
256
+ return { ok: false, codigo: 8, error: `FAIL: artefacto de evaluación ilegible (${e.message}): ${ruta}` };
257
+ }
258
+
259
+ if (artefacto.target !== target) {
260
+ return { ok: false, codigo: 8, error: `FAIL: el artefacto de evaluación es de "${artefacto.target}", no de "${target}".` };
261
+ }
262
+ const scoreArtefacto = Number(artefacto.score);
263
+ if (!Number.isFinite(scoreArtefacto) || scoreArtefacto < 0 || scoreArtefacto > 100) {
264
+ return { ok: false, codigo: 8, error: `FAIL: score inválido en el artefacto de evaluación: ${artefacto.score}` };
265
+ }
266
+ const productor = String(artefacto.producido_por || '').trim();
267
+ if (!productor) {
268
+ return { ok: false, codigo: 8, error: 'FAIL: el artefacto de evaluación no declara `producido_por` — sin procedencia no hay aprobación.' };
269
+ }
270
+ if (productor === target) {
271
+ return {
272
+ ok: false,
273
+ codigo: 8,
274
+ error: `FAIL: auto-calificación rechazada — el artefacto declara producido_por="${productor}" igual al target evaluado.\n` +
275
+ `La evidencia que aprueba una evolución debe venir de un actor independiente (regla seguridad-agentes § Anti-gaming).`,
276
+ };
277
+ }
278
+ const fecha = Date.parse(artefacto.fecha || '');
279
+ if (!Number.isFinite(fecha)) {
280
+ return { ok: false, codigo: 8, error: 'FAIL: el artefacto de evaluación no tiene `fecha` válida.' };
281
+ }
282
+ if (Date.now() - fecha > FRESCURA_EVALUACION_MS) {
283
+ return {
284
+ ok: false,
285
+ codigo: 8,
286
+ error: `FAIL: artefacto de evaluación vencido (viejo, >7 días: ${artefacto.fecha}).\n` +
287
+ `Re-corre /swl:evaluar-skill ${target} — una evaluación vieja no aprueba una mutación nueva.`,
288
+ };
289
+ }
290
+
291
+ // Anti-replay: una evaluación aprueba UNA aplicación. Si ya se consumió
292
+ // en un --record-after previo, rechazar (nemesis iter-1 S3-1) — evita que
293
+ // el mismo artefacto infle el contador de evoluciones.
294
+ if (artefacto.consumido_en) {
295
+ return {
296
+ ok: false,
297
+ codigo: 8,
298
+ error: `FAIL: el artefacto de evaluación ya se consumió (${artefacto.consumido_en}).\n` +
299
+ `Una evaluación aprueba una sola aplicación — re-corre /swl:evaluar-skill ${target}.`,
300
+ };
301
+ }
302
+
303
+ // Binding de contenido: el artefacto aprueba la versión EXACTA que se evaluó.
304
+ // Si el target mutó entre --emitir-evaluacion y --record-after, el hash no
305
+ // coincide y la aprobación se rechaza (cierra la ventana TOCTOU, nemesis S3-2).
306
+ // Se exige cuando el artefacto lo declara Y el contenido del target resuelve.
307
+ if (artefacto.hash_contenido) {
308
+ const hashActual = hashContenidoTarget(target);
309
+ if (hashActual && hashActual !== artefacto.hash_contenido) {
310
+ return {
311
+ ok: false,
312
+ codigo: 8,
313
+ error: `FAIL: el contenido de "${target}" cambió tras la evaluación (hash no coincide).\n` +
314
+ `El artefacto aprueba una versión distinta a la actual — re-corre /swl:evaluar-skill ${target}.`,
315
+ };
316
+ }
317
+ }
318
+
319
+ return { ok: true, artefacto, ruta };
320
+ }
321
+
110
322
  // Nota: el parser YAML y el validador de safety invariants (AGP Commit operator)
111
323
  // viven en scripts/lib/agp-frontmatter.js para reutilizarse desde tests y hooks.
112
324
 
@@ -122,11 +334,18 @@ const flags = {
122
334
  recordAfter: argv.includes('--record-after'),
123
335
  recordRevert: argv.includes('--record-revert'),
124
336
  checkInvariants: argv.includes('--check-invariants'),
337
+ emitirEvaluacion: argv.includes('--emitir-evaluacion'),
125
338
  };
126
339
  const scoreArg = argv.find(a => a.startsWith('--score='));
127
340
  const score = scoreArg ? parseFloat(scoreArg.split('=')[1]) : null;
128
341
  const hypothesisArg = argv.find(a => a.startsWith('--hypothesis='));
129
342
  const hypothesis = hypothesisArg ? hypothesisArg.slice('--hypothesis='.length) : null;
343
+ const producidoPorArg = argv.find(a => a.startsWith('--producido-por='));
344
+ const producidoPor = producidoPorArg ? producidoPorArg.slice('--producido-por='.length).trim() : null;
345
+ const badgeArg = argv.find(a => a.startsWith('--badge='));
346
+ const badge = badgeArg ? badgeArg.slice('--badge='.length) : null;
347
+ const evaluacionArg = argv.find(a => a.startsWith('--evaluacion='));
348
+ const evaluacionPath = evaluacionArg ? evaluacionArg.slice('--evaluacion='.length) : null;
130
349
  const nombre = argv.find(a => !a.startsWith('--'));
131
350
 
132
351
  if (flags.list) {
@@ -172,8 +391,35 @@ if (errors.length) {
172
391
  process.exit(4);
173
392
  }
174
393
 
394
+ // Modo --emitir-evaluacion: /swl:evaluar-skill persiste aquí el artefacto de
395
+ // procedencia que --record-after exigirá (DT-AGP-SCORE-AUTODECLARADO).
396
+ if (flags.emitirEvaluacion) {
397
+ if (score === null || Number.isNaN(score) || score < 0 || score > 100) {
398
+ console.error('FAIL: --emitir-evaluacion requiere --score=N (0-100)');
399
+ process.exit(5);
400
+ }
401
+ if (!producidoPor) {
402
+ console.error('FAIL: --emitir-evaluacion requiere --producido-por=<actor> (comando/agente que corrió la evaluación).');
403
+ console.error(' Sin procedencia declarada el artefacto no puede aprobar evoluciones.');
404
+ process.exit(5);
405
+ }
406
+ const { ruta } = emitirEvaluacion({ target: nombre, score, badge, producidoPor });
407
+ registrarEvento('evaluacion', {
408
+ target: nombre,
409
+ score,
410
+ badge: badge || null,
411
+ producido_por: producidoPor,
412
+ evaluacion_path: path.relative(CWD, ruta),
413
+ });
414
+ console.log(`evaluación emitida: target=${nombre} score=${score} producido_por=${producidoPor}`);
415
+ console.log(`artefacto: ${path.relative(CWD, ruta)}`);
416
+ process.exit(0);
417
+ }
418
+
175
419
  if (flags.recordBaseline || flags.recordAfter || flags.recordRevert) {
176
- if (score === null || Number.isNaN(score)) {
420
+ // El score de --record-after viene del artefacto de evaluación (abajo);
421
+ // baseline y revert siguen requiriendo --score explícito.
422
+ if (!flags.recordAfter && (score === null || Number.isNaN(score))) {
177
423
  console.error('FAIL: --record-* requiere --score=N (0-100)');
178
424
  process.exit(5);
179
425
  }
@@ -181,6 +427,25 @@ if (flags.recordBaseline || flags.recordAfter || flags.recordRevert) {
181
427
  : flags.recordAfter ? 'aplicada'
182
428
  : 'revertida';
183
429
 
430
+ // Anti-gaming (--record-after): el score que aprueba la evolución se lee
431
+ // del artefacto con procedencia; un --score explícito debe COINCIDIR.
432
+ let evaluacion = null;
433
+ if (flags.recordAfter) {
434
+ const carga = cargarEvaluacion(nombre, evaluacionPath);
435
+ if (!carga.ok) {
436
+ console.error(carga.error);
437
+ process.exit(carga.codigo);
438
+ }
439
+ evaluacion = carga;
440
+ const scoreArtefacto = Number(carga.artefacto.score);
441
+ if (score !== null && !Number.isNaN(score) && Math.abs(score - scoreArtefacto) > 0.01) {
442
+ console.error(`FAIL: --score=${score} discrepa del artefacto de evaluación (${scoreArtefacto}).`);
443
+ console.error(` El número que se registra es el del artefacto — no se acepta un score paralelo.`);
444
+ process.exit(9);
445
+ }
446
+ }
447
+ const scoreEfectivo = flags.recordAfter ? Number(evaluacion.artefacto.score) : score;
448
+
184
449
  let invariantes_ok = null;
185
450
  if (flags.checkInvariants && (flags.recordAfter || flags.recordRevert)) {
186
451
  const resultado = validarInvariantesDeAgente(nombre);
@@ -192,7 +457,7 @@ if (flags.recordBaseline || flags.recordAfter || flags.recordRevert) {
192
457
  }
193
458
  registrarEvento(tipo, {
194
459
  target: nombre,
195
- score,
460
+ score: scoreEfectivo,
196
461
  evals_total: data.evals.length,
197
462
  evals_path: path.relative(CWD, evalsPath),
198
463
  hypothesis,
@@ -206,18 +471,32 @@ if (flags.recordBaseline || flags.recordAfter || flags.recordRevert) {
206
471
 
207
472
  const payload = {
208
473
  target: nombre,
209
- score,
474
+ score: scoreEfectivo,
210
475
  evals_total: data.evals.length,
211
476
  evals_path: path.relative(CWD, evalsPath),
212
477
  };
213
478
  if (hypothesis) payload.hypothesis = hypothesis;
214
479
  if (invariantes_ok !== null) payload.invariantes_ok = invariantes_ok;
480
+ if (evaluacion) {
481
+ // Procedencia del score (DT-AGP-SCORE-AUTODECLARADO): quién lo produjo
482
+ // y de qué artefacto sale — el detector de validar.js exige estos campos
483
+ // en toda entrada `aplicada`.
484
+ payload.producido_por = evaluacion.artefacto.producido_por;
485
+ payload.evaluacion_path = path.relative(CWD, evaluacion.ruta);
486
+ payload.evaluacion_fecha = evaluacion.artefacto.fecha;
487
+ }
215
488
 
216
489
  const ok = registrarEvento(tipo, payload);
217
490
  if (ok) {
491
+ // Consumir el artefacto tras un --record-after exitoso: una evaluación
492
+ // aprueba UNA aplicación (anti-replay, nemesis S3-1).
493
+ if (flags.recordAfter && evaluacion) {
494
+ marcarEvaluacionConsumida(evaluacion.ruta);
495
+ }
218
496
  const sufijo = hypothesis ? ` hypothesis="${hypothesis.slice(0, 40)}${hypothesis.length > 40 ? '...' : ''}"` : '';
219
497
  const invSufijo = invariantes_ok === true ? ' invariantes_ok=true' : '';
220
- console.log(`registrado: ${tipo} score=${score} target=${nombre}${sufijo}${invSufijo}`);
498
+ const procSufijo = evaluacion ? ` producido_por=${evaluacion.artefacto.producido_por}` : '';
499
+ console.log(`registrado: ${tipo} score=${scoreEfectivo} target=${nombre}${sufijo}${invSufijo}${procSufijo}`);
221
500
  process.exit(0);
222
501
  } else {
223
502
  process.exit(6);
@@ -53,9 +53,10 @@ function archivosEsperadosEnPerfil(perfil) {
53
53
  for (const modKey of modulosPerfil) {
54
54
  const mod = modulos.modulos[modKey];
55
55
  if (!mod) continue;
56
- // Módulos tipo 'scripts' son herramientas de desarrollo del repo; el instalador
57
- // NO los copia al runtime del usuario. Omitir del smoke test.
58
- if (mod.tipo === "scripts") continue;
56
+ // Histórico: los módulos tipo 'scripts' se OMITÍAN del smoke ese punto
57
+ // ciego escondió que el instalador depositaba sus archivos (incluidos
58
+ // hooks y comandos) en rutas.base (DT-MODULOS-TIPO-SCRIPTS, 2026-07-10).
59
+ // Hoy no queda ningún módulo tipo 'scripts'; todos se verifican.
59
60
  for (const a of mod.archivos || []) archivos.add(a);
60
61
  }
61
62
  return archivos;
@@ -98,12 +99,19 @@ function traducirRuta(rel) {
98
99
  .replace(/^reglas\//, "rules/");
99
100
  }
100
101
  if (rel.startsWith("hooks/lib/")) {
101
- // El instalador aplana hooks/lib/X.js hooks/X.js en el destino.
102
- return rel.replace(/^hooks\/lib\//, "hooks/");
102
+ // El instalador preserva el subárbol lib/ (contrato require('./lib/X')
103
+ // de los hooks — el aplanado dejaba esos require muertos en installs
104
+ // frescos; restaurado 2026-07-10).
105
+ return rel;
103
106
  }
104
- // Archivos scripts/ declarados en módulos de tipo 'hooks' (ej. hooks-observabilidad,
105
- // hooks-multica). El instalador toma path.basename y los coloca en rutas.hooks,
106
- // aplanando scripts/lib/X.js y scripts/X.py a hooks/X.*.
107
+ // scripts/lib/* se une al MISMO lib/ de hooks (los fallbacks tolerantes
108
+ // de check-update/session-briefing esperan './lib/X'). Preserva subdirs
109
+ // (lib/ciclo-autonomo/...).
110
+ if (rel.startsWith("scripts/lib/")) {
111
+ return "hooks/lib/" + rel.slice("scripts/lib/".length);
112
+ }
113
+ // Resto de scripts/ declarados en módulos de hooks: aplanados por basename
114
+ // en la raíz de hooks (rotar-audit-logs.js, *.py, etc.).
107
115
  if (rel.startsWith("scripts/")) {
108
116
  return "hooks/" + path.posix.basename(rel);
109
117
  }
@@ -10,10 +10,10 @@
10
10
  * 1. Runtime destino (Claude/Codex/OpenCode/etc.)
11
11
  * 2. Perfil (core / backend-python / fullstack-* / completo / ...)
12
12
  * 3. Scope (proyecto / global)
13
- * 4. Flags adicionales (with-mcp, dry-run)
14
- * 5. Stacks de lenguajes (solo si perfil = completo)
15
- * 6. Preview del plan
16
- * 7. Confirmación final
13
+ * 4. Flags adicionales (with-mcp, dry-run, solo-hooks)
14
+ * 5. Módulos opt-in (fuera de todos los perfiles DT-MODULOS-OPT-IN)
15
+ * 6. Stacks de lenguajes (solo si perfil = completo)
16
+ * 7. Preview del plan + confirmación final
17
17
  *
18
18
  * Devuelve el objeto de opciones para `scripts/instalador.js` o null si el
19
19
  * usuario canceló en cualquier paso.
@@ -112,12 +112,43 @@ const FLAGS = [
112
112
  descripcion: 'Útil para inspeccionar qué se haría sin tocar el filesystem.',
113
113
  preseleccionado: false,
114
114
  },
115
+ {
116
+ valor: 'solo-hooks',
117
+ etiqueta: 'Solo hooks (telemetría de proyecto, sin copiar componentes)',
118
+ descripcion: 'Registra los hooks del scope GLOBAL en el settings.json de ESTE proyecto — evita duplicar agentes/comandos en la UI. Ignora perfil, scope y módulos. Solo target Claude Code.',
119
+ preseleccionado: false,
120
+ },
115
121
  ];
116
122
 
117
123
  // ---------------------------------------------------------------------------
118
124
  // Helpers
119
125
  // ---------------------------------------------------------------------------
120
126
 
127
+ const MODULOS_PATH = path.join(RAIZ, 'manifiestos', 'modulos.json');
128
+
129
+ /**
130
+ * Módulos `opt_in: true` de modulos.json — fuera de todos los perfiles,
131
+ * instalables solo por selección explícita (DT-MODULOS-OPT-IN). El wizard
132
+ * los ofrece como paso propio; el CLI equivalente es `--with a,b`.
133
+ *
134
+ * @returns {Array<{valor: string, etiqueta: string, descripcion: string}>}
135
+ */
136
+ function cargarModulosOptIn() {
137
+ try {
138
+ const data = JSON.parse(fs.readFileSync(MODULOS_PATH, 'utf8'));
139
+ const modulos = data.modulos || {};
140
+ return Object.entries(modulos)
141
+ .filter(([, m]) => m && m.opt_in === true)
142
+ .map(([nombre, m]) => ({
143
+ valor: nombre,
144
+ etiqueta: nombre,
145
+ descripcion: String(m.descripcion || '').slice(0, 140),
146
+ }));
147
+ } catch (_) {
148
+ return [];
149
+ }
150
+ }
151
+
121
152
  function cargarPerfiles() {
122
153
  try {
123
154
  const data = JSON.parse(fs.readFileSync(PERFILES_PATH, 'utf8'));
@@ -181,6 +212,13 @@ function pantallaPreview(opciones, perfiles) {
181
212
  filas.push(['Stacks extra', opciones.stacks.join(', ')]);
182
213
  }
183
214
 
215
+ if (opciones['solo-hooks']) {
216
+ filas.push(['Solo hooks', 'sí — registra hooks del scope global en el proyecto, sin copiar componentes']);
217
+ }
218
+ if (opciones.with) {
219
+ filas.push(['Módulos opt-in', opciones.with]);
220
+ }
221
+
184
222
  const perfilInfo = perfiles[opciones.profile];
185
223
  if (perfilInfo?.modulos) {
186
224
  filas.push(['Módulos', String(perfilInfo.modulos.length)]);
@@ -247,7 +285,7 @@ async function ejecutarWizardInstall() {
247
285
  // (fix 2026-07-09: era selector único; el flujo multi-runtime obligaba a
248
286
  // repetir el wizard completo por cada runtime).
249
287
  let targets = null;
250
- let tituloPaso1 = 'Paso 1/6 — ¿En qué runtime(s) instalas SWL? (espacio para marcar)';
288
+ let tituloPaso1 = 'Paso 1/7 — ¿En qué runtime(s) instalas SWL? (espacio para marcar)';
251
289
  for (;;) {
252
290
  targets = await selectorMulti({ titulo: tituloPaso1, items: RUNTIMES });
253
291
  if (targets === null) return { exito: false, cancelado: true };
@@ -259,13 +297,13 @@ async function ejecutarWizardInstall() {
259
297
  targets = [RUNTIMES[0].valor];
260
298
  break;
261
299
  }
262
- tituloPaso1 = 'Paso 1/6 — Marca AL MENOS un runtime con espacio (Esc para cancelar)';
300
+ tituloPaso1 = 'Paso 1/7 — Marca AL MENOS un runtime con espacio (Esc para cancelar)';
263
301
  }
264
302
  const target = targets[0]; // compat: preview/ejecutores leen .target
265
303
 
266
304
  // 2. Perfil
267
305
  const profile = await selectorUnico({
268
- titulo: 'Paso 2/6 — ¿Qué perfil instalas?',
306
+ titulo: 'Paso 2/7 — ¿Qué perfil instalas?',
269
307
  items: construirItemsPerfil(perfiles),
270
308
  indiceDefault: 0,
271
309
  });
@@ -273,7 +311,7 @@ async function ejecutarWizardInstall() {
273
311
 
274
312
  // 3. Scope
275
313
  const scope = await selectorUnico({
276
- titulo: 'Paso 3/6 — ¿Dónde se instala?',
314
+ titulo: 'Paso 3/7 — ¿Dónde se instala?',
277
315
  items: SCOPES,
278
316
  indiceDefault: 0,
279
317
  });
@@ -281,16 +319,31 @@ async function ejecutarWizardInstall() {
281
319
 
282
320
  // 4. Flags
283
321
  const flagsSeleccionados = await selectorMulti({
284
- titulo: 'Paso 4/6 — Flags adicionales (espacio para toggle):',
322
+ titulo: 'Paso 4/7 — Flags adicionales (espacio para toggle):',
285
323
  items: FLAGS,
286
324
  });
287
325
  if (flagsSeleccionados === null) return { exito: false, cancelado: true };
288
326
 
289
- // 5. Stacks (solo si perfil = completo)
327
+ // 5. Módulos opt-in (DT-MODULOS-OPT-IN) fuera de todos los perfiles;
328
+ // solo se instalan por selección explícita. Se omite en modo solo-hooks
329
+ // (ese camino no copia componentes).
330
+ const soloHooks = flagsSeleccionados.includes('solo-hooks');
331
+ let modulosOptIn = [];
332
+ const itemsOptIn = soloHooks ? [] : cargarModulosOptIn();
333
+ if (itemsOptIn.length > 0) {
334
+ const elegidos = await selectorMulti({
335
+ titulo: 'Paso 5/7 — Módulos opt-in (fuera del perfil; espacio para marcar, Enter para omitir):',
336
+ items: itemsOptIn,
337
+ });
338
+ if (elegidos === null) return { exito: false, cancelado: true };
339
+ modulosOptIn = elegidos;
340
+ }
341
+
342
+ // 6. Stacks (solo si perfil = completo)
290
343
  let stacks = [];
291
- if (profile === 'completo') {
344
+ if (profile === 'completo' && !soloHooks) {
292
345
  const elegidos = await selectorMulti({
293
- titulo: 'Paso 5/6 — Stacks de lenguajes adicionales (perfil completo):',
346
+ titulo: 'Paso 6/7 — Stacks de lenguajes adicionales (perfil completo):',
294
347
  items: STACKS_ADICIONALES,
295
348
  });
296
349
  if (elegidos === null) return { exito: false, cancelado: true };
@@ -304,6 +357,8 @@ async function ejecutarWizardInstall() {
304
357
  global: scope === 'global',
305
358
  with_mcp: flagsSeleccionados.includes('with-mcp'),
306
359
  dry_run: flagsSeleccionados.includes('dry-run'),
360
+ 'solo-hooks': soloHooks,
361
+ with: modulosOptIn.join(',') || undefined,
307
362
  all_langs: stacks.length > 0,
308
363
  stacks,
309
364
  };
@@ -314,7 +369,7 @@ async function ejecutarWizardInstall() {
314
369
 
315
370
  // 7. Confirmación final (selector único con 2 opciones para claridad visual)
316
371
  const confirmacion = await selectorUnico({
317
- titulo: 'Paso 6/6 — Confirmar e iniciar instalación',
372
+ titulo: 'Paso 7/7 — Confirmar e iniciar instalación',
318
373
  items: [
319
374
  {
320
375
  valor: 'si',
@@ -344,4 +399,5 @@ module.exports = {
344
399
  FLAGS,
345
400
  cargarPerfiles,
346
401
  construirItemsPerfil,
402
+ cargarModulosOptIn,
347
403
  };
@@ -92,7 +92,7 @@ verificar(comandos.length >= 9, `Comandos: ${comandos.length} (mínimo 9)`);
92
92
  // proyecto (node scripts/ | node hooks/ | require('./scripts|hooks/...')).
93
93
  // Esas rutas no existen downstream; deben usar el subcomando del CLI.
94
94
  // Ver docs/invocacion-cli-cross-scope.md.
95
- const { auditarInvocacionesComandos } = require('./lib/auditar-invocaciones-comandos');
95
+ const { auditarInvocacionesComandos, auditarInvocacionesSkills } = require('./lib/auditar-invocaciones-comandos');
96
96
  const auditInvoc = auditarInvocacionesComandos(path.join(RAIZ, 'comandos', 'swl'));
97
97
  verificar(
98
98
  auditInvoc.ok,
@@ -101,6 +101,17 @@ verificar(
101
101
  : `Cross-scope: ${auditInvoc.violaciones.length} invocación(es) relativa(s) — ${auditInvoc.violaciones.map(v => `${v.comando}.md:${v.linea}`).join(', ')}`
102
102
  );
103
103
 
104
+ // 7b-bis. Mismo gate sobre skills user-facing (DT-PATHS-DESTINO, cierre
105
+ // 2026-07-10): solo fences ejecutables (bash/sh/sin lenguaje); los bloques js
106
+ // de API llevan clarificador de layout. Skills host-only en SKILLS_SOLO_MADRE.
107
+ const auditInvocSkills = auditarInvocacionesSkills(path.join(RAIZ, 'habilidades'));
108
+ verificar(
109
+ auditInvocSkills.ok,
110
+ auditInvocSkills.ok
111
+ ? `Cross-scope skills: ${auditInvocSkills.escaneados} skills sin rutas relativas al proyecto`
112
+ : `Cross-scope skills: ${auditInvocSkills.violaciones.length} invocación(es) relativa(s) — ${auditInvocSkills.violaciones.map(v => `${v.comando}:${v.linea}`).join(', ')}`
113
+ );
114
+
104
115
  // 7c. Gate inverso de evolución (Fase 16, REQ-16-03): el FUENTE no debe portar
105
116
  // marcadores evolved (frontmatter evolved:true ni sidecar .evolved.json). Un
106
117
  // marcador en el repo madre es shipped-evolved espurio que congela el componente
@@ -166,6 +177,34 @@ try {
166
177
  }
167
178
  } catch { /* ignore */ }
168
179
 
180
+ // 15. Procedencia de evoluciones aplicadas (DT-AGP-SCORE-AUTODECLARADO):
181
+ // toda entrada tipo:aplicada en evoluciones.jsonl debe llevar la cadena de
182
+ // procedencia del score (producido_por ≠ target + evaluacion_path). Cutoff
183
+ // 2026-07-10: fecha en que --record-after empezó a exigir el artefacto —
184
+ // entradas previas son legacy sin contrato.
185
+ try {
186
+ const evolLog = path.join(RAIZ, '.planning', 'evolution', 'evoluciones.jsonl');
187
+ if (fs.existsSync(evolLog)) {
188
+ const CUTOFF_PROCEDENCIA = Date.parse('2026-07-10T00:00:00Z');
189
+ const sinProcedencia = fs.readFileSync(evolLog, 'utf-8')
190
+ .split(/\r?\n/)
191
+ .filter(Boolean)
192
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
193
+ .filter(e => e && e.tipo === 'aplicada' && Date.parse(e.ts || '') >= CUTOFF_PROCEDENCIA)
194
+ .filter(e =>
195
+ !e.producido_por ||
196
+ e.producido_por === e.target ||
197
+ !e.evaluacion_path
198
+ );
199
+ verificar(
200
+ sinProcedencia.length === 0,
201
+ sinProcedencia.length === 0
202
+ ? 'Evoluciones aplicadas: cadena de procedencia completa (anti-gaming)'
203
+ : `Evoluciones aplicadas SIN procedencia válida: ${sinProcedencia.map(e => `${e.target}@${e.ts}`).join(', ')} — el score debe venir del artefacto de /swl:evaluar-skill`
204
+ );
205
+ }
206
+ } catch { /* log ilegible: no bloquear la validación estructural */ }
207
+
169
208
  console.log('\n' + '='.repeat(40));
170
209
  if (errores === 0) {
171
210
  console.log('Todas las validaciones pasaron.');