@saulwade/swl-ses 2.5.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/CLAUDE.md +192 -192
  2. package/README.md +600 -600
  3. package/agentes/auto-evolucion-swl.md +27 -3
  4. package/bin/swl-ses.js +32 -7
  5. package/comandos/swl/actualizar.md +174 -174
  6. package/comandos/swl/adoptar-proyecto.md +265 -265
  7. package/comandos/swl/aprender.md +836 -823
  8. package/comandos/swl/aprobar-plan.md +146 -146
  9. package/comandos/swl/auditar-deps.md +134 -134
  10. package/comandos/swl/autoresearch.md +264 -264
  11. package/comandos/swl/ayuda.md +224 -224
  12. package/comandos/swl/brainstorm.md +51 -51
  13. package/comandos/swl/briefing.md +119 -119
  14. package/comandos/swl/checkpoint.md +325 -325
  15. package/comandos/swl/claudemd.md +234 -234
  16. package/comandos/swl/compactar.md +310 -310
  17. package/comandos/swl/configurar-ci.md +235 -235
  18. package/comandos/swl/contexto.md +110 -110
  19. package/comandos/swl/contribuir.md +233 -233
  20. package/comandos/swl/crear-skill.md +292 -292
  21. package/comandos/swl/cron.md +194 -194
  22. package/comandos/swl/deuda-codigo.md +97 -97
  23. package/comandos/swl/discutir-fase.md +169 -169
  24. package/comandos/swl/ejecutar-fase.md +233 -233
  25. package/comandos/swl/evaluar-skill.md +520 -505
  26. package/comandos/swl/evolucion-continua.md +73 -0
  27. package/comandos/swl/evolucionar.md +267 -254
  28. package/comandos/swl/exportar-vault.md +583 -583
  29. package/comandos/swl/fix.md +118 -118
  30. package/comandos/swl/gateway.md +158 -158
  31. package/comandos/swl/inbox.md +116 -116
  32. package/comandos/swl/instalar.md +220 -220
  33. package/comandos/swl/instintos.md +86 -86
  34. package/comandos/swl/mapear-codebase.md +312 -312
  35. package/comandos/swl/mcp-status.md +175 -175
  36. package/comandos/swl/modelo.md +100 -100
  37. package/comandos/swl/nemesis.md +433 -433
  38. package/comandos/swl/notificaciones.md +299 -299
  39. package/comandos/swl/nuevo-proyecto.md +251 -251
  40. package/comandos/swl/planear-fase.md +263 -263
  41. package/comandos/swl/plugins.md +256 -256
  42. package/comandos/swl/predecir.md +169 -169
  43. package/comandos/swl/reflect-skills.md +125 -125
  44. package/comandos/swl/release.md +450 -450
  45. package/comandos/swl/revisar-impacto.md +201 -201
  46. package/comandos/swl/revisar.md +330 -330
  47. package/comandos/swl/seguridad.md +189 -189
  48. package/comandos/swl/sesiones.md +200 -200
  49. package/comandos/swl/skill-search.md +113 -113
  50. package/comandos/swl/status.md +343 -343
  51. package/comandos/swl/verificar.md +817 -817
  52. package/comandos/swl/wiki.md +620 -620
  53. package/gateway/cron/jobs.example.json +12 -0
  54. package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -276
  55. package/habilidades/autoresearch/SKILL.md +3 -2
  56. package/habilidades/benchmark-memoria/SKILL.md +7 -7
  57. package/habilidades/changelog-generator/SKILL.md +174 -174
  58. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  59. package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
  60. package/habilidades/context-builder/SKILL.md +4 -0
  61. package/habilidades/doubt-driven-review/SKILL.md +207 -191
  62. package/habilidades/drift-detection/SKILL.md +6 -1
  63. package/habilidades/ejecutar-fase/SKILL.md +6 -6
  64. package/habilidades/eval-framework/SKILL.md +8 -3
  65. package/habilidades/harness-claude-code/SKILL.md +312 -308
  66. package/habilidades/infra-github-actions/SKILL.md +4 -3
  67. package/habilidades/instalar-sistema/SKILL.md +227 -223
  68. package/habilidades/memoria-busqueda/SKILL.md +31 -39
  69. package/habilidades/planear-fase/SKILL.md +358 -350
  70. package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
  71. package/habilidades/swl-claudemd/SKILL.md +6 -7
  72. package/habilidades/swl-dashboard/SKILL.md +11 -43
  73. package/habilidades/tdd-workflow/SKILL.md +749 -744
  74. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  75. package/hooks/agente-lifecycle.js +2 -1
  76. package/hooks/aiisms-detector.js +13 -4
  77. package/hooks/audit-trail.js +2 -1
  78. package/hooks/auto-consolidacion.js +2 -1
  79. package/hooks/captura-acciones-post.js +2 -1
  80. package/hooks/captura-acciones-session.js +2 -1
  81. package/hooks/captura-feedback-usuario.js +3 -2
  82. package/hooks/claudemd-bloat-detector.js +12 -3
  83. package/hooks/claudemd-duplicacion-detector.js +13 -3
  84. package/hooks/contexto-iteracion.js +2 -1
  85. package/hooks/degradacion-instintos.js +2 -1
  86. package/hooks/extraccion-aprendizajes.js +109 -15
  87. package/hooks/grafo-contexto.js +2 -1
  88. package/hooks/guardrail-modelo.js +2 -1
  89. package/hooks/inbox-aviso.js +2 -1
  90. package/hooks/inyeccion-contexto.js +2 -1
  91. package/hooks/lib/agent-matcher.js +2 -1
  92. package/hooks/lib/agent-routing.js +2 -1
  93. package/hooks/lib/autonomia.js +5 -3
  94. package/hooks/lib/captura-acciones.js +2 -1
  95. package/hooks/lib/consolidation-lock.js +21 -10
  96. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  97. package/hooks/lib/etapa-metricas.js +2 -1
  98. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  99. package/hooks/lib/evolution-tracker.js +2 -1
  100. package/hooks/lib/gateway-notify.js +193 -179
  101. package/hooks/lib/loop-telemetry.js +5 -4
  102. package/hooks/lib/mcp-health.js +2 -1
  103. package/hooks/lib/memory-search.js +4 -0
  104. package/hooks/lib/merkle-audit.js +58 -6
  105. package/hooks/lib/nudge-tracker.js +2 -1
  106. package/hooks/lib/otlp-exporter.js +2 -1
  107. package/hooks/lib/propose-step.js +3 -2
  108. package/hooks/lib/raiz-proyecto.js +102 -0
  109. package/hooks/lib/run-log.js +2 -1
  110. package/hooks/lib/singleton-guard.js +218 -27
  111. package/hooks/lib/telegram-cliente.js +17 -8
  112. package/hooks/preservar-estado-pre-compact.js +2 -1
  113. package/hooks/proteccion-rutas.js +59 -3
  114. package/hooks/registro-turnos.js +2 -1
  115. package/hooks/resumen-sesion.js +2 -1
  116. package/hooks/risk-scoring.js +2 -1
  117. package/hooks/rotar-audit-auto.js +46 -20
  118. package/hooks/session-briefing.js +127 -1
  119. package/hooks/spec-gate.js +2 -1
  120. package/hooks/sugerir-contribuir.js +6 -3
  121. package/hooks/sugerir-regenerar-inventario.js +3 -2
  122. package/hooks/tdd-gate.js +2 -1
  123. package/hooks/telemetria-agentes.js +2 -1
  124. package/hooks/telemetria-skill-routing.js +2 -1
  125. package/hooks/tracking-costos.js +4 -3
  126. package/hooks/validar-formato-post-subagente.js +2 -1
  127. package/hooks/validar-intent-spec.js +2 -1
  128. package/hooks/validar-memoria-hook.js +13 -3
  129. package/hooks/validar-planning-paths.js +2 -1
  130. package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
  131. package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
  132. package/instintos/perfil-usuario.yaml +506 -3
  133. package/instintos/proyecto.yaml +78 -0
  134. package/llms.txt +2 -2
  135. package/manifiestos/canonical-hashes.json +335 -3
  136. package/manifiestos/modulos.json +19 -14
  137. package/manifiestos/planning-paths.json +1 -0
  138. package/manifiestos/skills-lock.json +50 -50
  139. package/package.json +2 -3
  140. package/plugin.json +2 -2
  141. package/scripts/actualizar.js +3 -0
  142. package/scripts/auditar-clases-conocidas.js +32 -4
  143. package/scripts/benchmark-memoria.js +1 -0
  144. package/scripts/cli/autonomia.js +23 -0
  145. package/scripts/cli/benchmark-memoria.js +37 -0
  146. package/scripts/cli/ciclo-autonomo.js +73 -0
  147. package/scripts/cli/ciclo-fase-b.js +102 -0
  148. package/scripts/cli/guardrail-metrics.js +39 -0
  149. package/scripts/cli/loop-telemetry.js +4 -2
  150. package/scripts/cli/memoria-search.js +69 -0
  151. package/scripts/cli/nudge-accionar.js +39 -0
  152. package/scripts/cli/run-eval.js +38 -0
  153. package/scripts/cli/run-skill-evals.js +13 -2
  154. package/scripts/derivar-feature-list.js +15 -14
  155. package/scripts/desinstalar.js +11 -0
  156. package/scripts/doctor.js +24 -10
  157. package/scripts/instalador.js +85 -7
  158. package/scripts/lib/activar-hooks-proyecto.js +12 -0
  159. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  160. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  161. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  162. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  163. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  164. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  165. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  166. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  167. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  168. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  169. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  170. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  171. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  172. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  173. package/scripts/lib/estado.js +9 -0
  174. package/scripts/lib/gitignore-manifest.js +8 -1
  175. package/scripts/lib/hooks-settings.js +45 -0
  176. package/scripts/rotar-audit-logs.js +48 -2
  177. package/scripts/run-eval.js +1 -0
  178. package/scripts/run-skill-evals.js +287 -8
  179. package/scripts/smoke-test.js +16 -8
  180. package/scripts/tui/pantallas/install-wizard.js +403 -347
  181. package/scripts/validar.js +40 -1
@@ -24,6 +24,19 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const zlib = require('zlib');
27
+ // Escritura atómica (CLAUDE.md § Code style): truncar el archivo de auditoría
28
+ // con writeFileSync plano puede corromperlo si la rotación se interrumpe a
29
+ // mitad (nemesis iter-1 S3-4). Require tolerante: repo madre vs destino aplanado.
30
+ let atomicWriteSync;
31
+ try {
32
+ ({ atomicWriteSync } = require(path.join(__dirname, '..', 'hooks', 'lib', 'atomic-write.js')));
33
+ } catch {
34
+ try {
35
+ ({ atomicWriteSync } = require(path.join(__dirname, 'atomic-write.js')));
36
+ } catch {
37
+ atomicWriteSync = (ruta, contenido) => fs.writeFileSync(ruta, contenido);
38
+ }
39
+ }
27
40
 
28
41
  const ARGS = process.argv.slice(2);
29
42
  const DRY_RUN = ARGS.includes('--dry-run');
@@ -38,7 +51,9 @@ const ARCHIVOS = [
38
51
  { nombre: 'audit-merkle', ruta: path.join(RAIZ, 'audit-merkle.jsonl'), campoTs: 'ts' },
39
52
  ];
40
53
 
41
- const DESTINO_DIR = path.join(RAIZ, 'archivo', 'audit');
54
+ // Canónico post-ADR-0031: dirs runtime de .planning/ en inglés ('archive',
55
+ // no 'archivo'). El shim del instalador § 6c-bis migra los dirs viejos.
56
+ const DESTINO_DIR = path.join(RAIZ, 'archive', 'audit');
42
57
 
43
58
  function ahoraMs() {
44
59
  return Date.now();
@@ -85,7 +100,10 @@ function appendGzipped(destinoPath, lineas) {
85
100
 
86
101
  function escribirActivo(ruta, lineas) {
87
102
  const contenido = lineas.length > 0 ? lineas.join('\n') + '\n' : '';
88
- fs.writeFileSync(ruta, contenido, { encoding: 'utf8' });
103
+ // Atómico: si el proceso muere a mitad, el archivo activo queda íntegro
104
+ // (la versión previa o la nueva completa, nunca truncado). Los .gz de los
105
+ // grupos-mes se escriben ANTES de esta truncación (orden en rotarArchivo).
106
+ atomicWriteSync(ruta, contenido);
89
107
  }
90
108
 
91
109
  function rotarArchivo(spec) {
@@ -133,6 +151,34 @@ function rotarArchivo(spec) {
133
151
  const destinoPath = path.join(DESTINO_DIR, `${spec.nombre}-${mesKey}.jsonl.gz`);
134
152
  appendGzipped(destinoPath, lineas);
135
153
  }
154
+ // Checkpoint de cadena Merkle (nemesis iter-1 S3-5): tras truncar, el
155
+ // archivo activo empieza en una entrada cuyo `prev` apunta a una entrada
156
+ // ya archivada (no al génesis). Persistir ese `prev` como `hashInicial`
157
+ // permite a verificarCadena(activo, {hashInicial}) validar el activo como
158
+ // continuación de la cadena archivada, sin falso positivo de rotura.
159
+ // Solo aplica a archivos encadenados (la primera retenida trae `prev`).
160
+ if (retenidas.length > 0) {
161
+ // leerJSONL ya parseó cada registro en `.obj` — sin re-parse.
162
+ const primeraRetenida = retenidas[0].obj;
163
+ if (primeraRetenida && typeof primeraRetenida.prev === 'string') {
164
+ const checkpointPath = path.join(DESTINO_DIR, `.checkpoint-${spec.nombre}.json`);
165
+ // `hashInicial` = prev de la 1a retenida = hash de la ÚLTIMA archivada.
166
+ // NOTA para un consumidor productivo: este checkpoint es un ANCLA de
167
+ // conveniencia, NO una raíz de confianza. verificarCadena(activo,
168
+ // {hashInicial}) prueba la consistencia INTERNA del activo, no que
169
+ // continúe la cadena archivada — para eso el consumidor debe recomputar
170
+ // el hash de la última entrada del .gz y compararlo con este hashInicial
171
+ // (re-auditoría nemesis iter-2). Coherente con el modelo pre-existente
172
+ // (SHA256 sin clave, sin raíz publicada).
173
+ atomicWriteSync(checkpointPath, JSON.stringify({
174
+ archivo: spec.nombre,
175
+ hashInicial: primeraRetenida.prev,
176
+ archivadas: archivadas.length,
177
+ // Fecha LOCAL (no UTC) — clase C1.
178
+ rotado: new Date().toLocaleDateString('sv'),
179
+ }, null, 2) + '\n');
180
+ }
181
+ }
136
182
  escribirActivo(spec.ruta, retenidas.map((r) => r.raw));
137
183
  }
138
184
 
@@ -138,4 +138,5 @@ if (require.main === module) {
138
138
 
139
139
  module.exports = {
140
140
  ejecutarEval,
141
+ main,
141
142
  };
@@ -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
  }