@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.
- package/CLAUDE.md +192 -192
- package/README.md +600 -600
- package/agentes/auto-evolucion-swl.md +27 -3
- package/bin/swl-ses.js +32 -7
- package/comandos/swl/actualizar.md +174 -174
- package/comandos/swl/adoptar-proyecto.md +265 -265
- package/comandos/swl/aprender.md +836 -823
- package/comandos/swl/aprobar-plan.md +146 -146
- package/comandos/swl/auditar-deps.md +134 -134
- package/comandos/swl/autoresearch.md +264 -264
- package/comandos/swl/ayuda.md +224 -224
- package/comandos/swl/brainstorm.md +51 -51
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/checkpoint.md +325 -325
- package/comandos/swl/claudemd.md +234 -234
- package/comandos/swl/compactar.md +310 -310
- package/comandos/swl/configurar-ci.md +235 -235
- package/comandos/swl/contexto.md +110 -110
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/crear-skill.md +292 -292
- package/comandos/swl/cron.md +194 -194
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/discutir-fase.md +169 -169
- package/comandos/swl/ejecutar-fase.md +233 -233
- package/comandos/swl/evaluar-skill.md +520 -505
- package/comandos/swl/evolucion-continua.md +73 -0
- package/comandos/swl/evolucionar.md +267 -254
- package/comandos/swl/exportar-vault.md +583 -583
- package/comandos/swl/fix.md +118 -118
- package/comandos/swl/gateway.md +158 -158
- package/comandos/swl/inbox.md +116 -116
- package/comandos/swl/instalar.md +220 -220
- package/comandos/swl/instintos.md +86 -86
- package/comandos/swl/mapear-codebase.md +312 -312
- package/comandos/swl/mcp-status.md +175 -175
- package/comandos/swl/modelo.md +100 -100
- package/comandos/swl/nemesis.md +433 -433
- package/comandos/swl/notificaciones.md +299 -299
- package/comandos/swl/nuevo-proyecto.md +251 -251
- package/comandos/swl/planear-fase.md +263 -263
- package/comandos/swl/plugins.md +256 -256
- package/comandos/swl/predecir.md +169 -169
- package/comandos/swl/reflect-skills.md +125 -125
- package/comandos/swl/release.md +450 -450
- package/comandos/swl/revisar-impacto.md +201 -201
- package/comandos/swl/revisar.md +330 -330
- package/comandos/swl/seguridad.md +189 -189
- package/comandos/swl/sesiones.md +200 -200
- package/comandos/swl/skill-search.md +113 -113
- package/comandos/swl/status.md +343 -343
- package/comandos/swl/verificar.md +817 -817
- package/comandos/swl/wiki.md +620 -620
- package/gateway/cron/jobs.example.json +12 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -276
- package/habilidades/autoresearch/SKILL.md +3 -2
- package/habilidades/benchmark-memoria/SKILL.md +7 -7
- package/habilidades/changelog-generator/SKILL.md +174 -174
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
- package/habilidades/context-builder/SKILL.md +4 -0
- package/habilidades/doubt-driven-review/SKILL.md +207 -191
- package/habilidades/drift-detection/SKILL.md +6 -1
- package/habilidades/ejecutar-fase/SKILL.md +6 -6
- package/habilidades/eval-framework/SKILL.md +8 -3
- package/habilidades/harness-claude-code/SKILL.md +312 -308
- package/habilidades/infra-github-actions/SKILL.md +4 -3
- package/habilidades/instalar-sistema/SKILL.md +227 -223
- package/habilidades/memoria-busqueda/SKILL.md +31 -39
- package/habilidades/planear-fase/SKILL.md +358 -350
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
- package/habilidades/swl-claudemd/SKILL.md +6 -7
- package/habilidades/swl-dashboard/SKILL.md +11 -43
- package/habilidades/tdd-workflow/SKILL.md +749 -744
- package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
- package/hooks/agente-lifecycle.js +2 -1
- package/hooks/aiisms-detector.js +13 -4
- package/hooks/audit-trail.js +2 -1
- package/hooks/auto-consolidacion.js +2 -1
- package/hooks/captura-acciones-post.js +2 -1
- package/hooks/captura-acciones-session.js +2 -1
- package/hooks/captura-feedback-usuario.js +3 -2
- package/hooks/claudemd-bloat-detector.js +12 -3
- package/hooks/claudemd-duplicacion-detector.js +13 -3
- package/hooks/contexto-iteracion.js +2 -1
- package/hooks/degradacion-instintos.js +2 -1
- package/hooks/extraccion-aprendizajes.js +109 -15
- package/hooks/grafo-contexto.js +2 -1
- package/hooks/guardrail-modelo.js +2 -1
- package/hooks/inbox-aviso.js +2 -1
- package/hooks/inyeccion-contexto.js +2 -1
- package/hooks/lib/agent-matcher.js +2 -1
- package/hooks/lib/agent-routing.js +2 -1
- package/hooks/lib/autonomia.js +5 -3
- package/hooks/lib/captura-acciones.js +2 -1
- package/hooks/lib/consolidation-lock.js +21 -10
- package/hooks/lib/etapa-auto-evolucion.js +10 -4
- package/hooks/lib/etapa-metricas.js +2 -1
- package/hooks/lib/etapa-perfil-usuario.js +20 -4
- package/hooks/lib/evolution-tracker.js +2 -1
- package/hooks/lib/gateway-notify.js +193 -179
- package/hooks/lib/loop-telemetry.js +5 -4
- package/hooks/lib/mcp-health.js +2 -1
- package/hooks/lib/memory-search.js +4 -0
- package/hooks/lib/merkle-audit.js +58 -6
- package/hooks/lib/nudge-tracker.js +2 -1
- package/hooks/lib/otlp-exporter.js +2 -1
- package/hooks/lib/propose-step.js +3 -2
- package/hooks/lib/raiz-proyecto.js +102 -0
- package/hooks/lib/run-log.js +2 -1
- package/hooks/lib/singleton-guard.js +218 -27
- package/hooks/lib/telegram-cliente.js +17 -8
- package/hooks/preservar-estado-pre-compact.js +2 -1
- package/hooks/proteccion-rutas.js +59 -3
- package/hooks/registro-turnos.js +2 -1
- package/hooks/resumen-sesion.js +2 -1
- package/hooks/risk-scoring.js +2 -1
- package/hooks/rotar-audit-auto.js +46 -20
- package/hooks/session-briefing.js +127 -1
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +6 -3
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/hooks/telemetria-agentes.js +2 -1
- package/hooks/telemetria-skill-routing.js +2 -1
- package/hooks/tracking-costos.js +4 -3
- package/hooks/validar-formato-post-subagente.js +2 -1
- package/hooks/validar-intent-spec.js +2 -1
- package/hooks/validar-memoria-hook.js +13 -3
- package/hooks/validar-planning-paths.js +2 -1
- package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
- package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
- package/instintos/perfil-usuario.yaml +506 -3
- package/instintos/proyecto.yaml +78 -0
- package/llms.txt +2 -2
- package/manifiestos/canonical-hashes.json +335 -3
- package/manifiestos/modulos.json +19 -14
- package/manifiestos/planning-paths.json +1 -0
- package/manifiestos/skills-lock.json +50 -50
- package/package.json +2 -3
- package/plugin.json +2 -2
- package/scripts/actualizar.js +3 -0
- package/scripts/auditar-clases-conocidas.js +32 -4
- package/scripts/benchmark-memoria.js +1 -0
- package/scripts/cli/autonomia.js +23 -0
- package/scripts/cli/benchmark-memoria.js +37 -0
- package/scripts/cli/ciclo-autonomo.js +73 -0
- package/scripts/cli/ciclo-fase-b.js +102 -0
- package/scripts/cli/guardrail-metrics.js +39 -0
- package/scripts/cli/loop-telemetry.js +4 -2
- package/scripts/cli/memoria-search.js +69 -0
- package/scripts/cli/nudge-accionar.js +39 -0
- package/scripts/cli/run-eval.js +38 -0
- package/scripts/cli/run-skill-evals.js +13 -2
- package/scripts/derivar-feature-list.js +15 -14
- package/scripts/desinstalar.js +11 -0
- package/scripts/doctor.js +24 -10
- package/scripts/instalador.js +85 -7
- package/scripts/lib/activar-hooks-proyecto.js +12 -0
- package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
- package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
- package/scripts/lib/ciclo-autonomo/config.js +165 -0
- package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
- package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
- package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
- package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
- package/scripts/lib/ciclo-autonomo/index.js +301 -0
- package/scripts/lib/ciclo-autonomo/lock.js +124 -0
- package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
- package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
- package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
- package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
- package/scripts/lib/estado.js +9 -0
- package/scripts/lib/gitignore-manifest.js +8 -1
- package/scripts/lib/hooks-settings.js +45 -0
- package/scripts/rotar-audit-logs.js +48 -2
- package/scripts/run-eval.js +1 -0
- package/scripts/run-skill-evals.js +287 -8
- package/scripts/smoke-test.js +16 -8
- package/scripts/tui/pantallas/install-wizard.js +403 -347
- 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
|
-
|
|
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
|
-
|
|
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
|
|
package/scripts/run-eval.js
CHANGED
|
@@ -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> --
|
|
19
|
-
*
|
|
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
|
|
32
|
-
const 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
|
-
|
|
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
|
-
|
|
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);
|
package/scripts/smoke-test.js
CHANGED
|
@@ -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
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
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
|
|
102
|
-
|
|
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
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
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
|
}
|