@saulwade/swl-ses 2.2.0 → 2.2.3

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 (81) hide show
  1. package/CLAUDE.md +199 -196
  2. package/README.md +597 -579
  3. package/agentes/arquitecto-swl.md +0 -5
  4. package/agentes/backend-python-swl.md +0 -5
  5. package/agentes/implementador-swl.md +0 -5
  6. package/agentes/nemesis-auditor-swl.md +0 -5
  7. package/agentes/orquestador-swl.md +0 -5
  8. package/agentes/planificador-swl.md +0 -5
  9. package/agentes/revisor-codigo-swl.md +0 -5
  10. package/bin/swl-mcp-server.js +1 -1
  11. package/comandos/swl/adoptar-proyecto.md +253 -258
  12. package/comandos/swl/aprender.md +823 -828
  13. package/comandos/swl/claudemd.md +234 -239
  14. package/comandos/swl/ejecutar-fase.md +0 -5
  15. package/comandos/swl/nuevo-proyecto.md +200 -205
  16. package/comandos/swl/release.md +19 -5
  17. package/comandos/swl/revisar-impacto.md +0 -5
  18. package/habilidades/agent-browser/SKILL.md +0 -5
  19. package/habilidades/angular-moderno/SKILL.md +0 -5
  20. package/habilidades/api-rest-diseno/SKILL.md +0 -5
  21. package/habilidades/aprendizaje-continuo/SKILL.md +0 -5
  22. package/habilidades/auth-patrones/SKILL.md +0 -5
  23. package/habilidades/build-errors-nextjs/SKILL.md +0 -5
  24. package/habilidades/changelog-generator/SKILL.md +174 -179
  25. package/habilidades/checklist-seguridad/SKILL.md +0 -5
  26. package/habilidades/contenedores-docker/SKILL.md +0 -5
  27. package/habilidades/datos-etl/SKILL.md +0 -5
  28. package/habilidades/doc-sync/SKILL.md +0 -5
  29. package/habilidades/extractor-de-aprendizajes/SKILL.md +0 -5
  30. package/habilidades/fastapi-experto/SKILL.md +0 -5
  31. package/habilidades/frontend-avanzado/SKILL.md +0 -5
  32. package/habilidades/iam-secretos/SKILL.md +0 -5
  33. package/habilidades/manejo-errores/SKILL.md +0 -5
  34. package/habilidades/mapear-codebase/SKILL.md +0 -5
  35. package/habilidades/meta-skills-estandar/SKILL.md +0 -5
  36. package/habilidades/monitoring-alertas/SKILL.md +0 -5
  37. package/habilidades/nextjs-experto/SKILL.md +0 -5
  38. package/habilidades/nextjs-testing/SKILL.md +0 -5
  39. package/habilidades/node-experto/SKILL.md +0 -5
  40. package/habilidades/orquestacion-async/SKILL.md +0 -5
  41. package/habilidades/patrones-python/SKILL.md +227 -232
  42. package/habilidades/planear-fase/SKILL.md +336 -341
  43. package/habilidades/postgresql-experto/SKILL.md +0 -5
  44. package/habilidades/prevencion-sobreingenieria/SKILL.md +0 -5
  45. package/habilidades/protocolo-revision-swl/SKILL.md +0 -5
  46. package/habilidades/react-experto/SKILL.md +0 -5
  47. package/habilidades/release-semver/SKILL.md +0 -5
  48. package/habilidades/swl-claudemd/SKILL.md +0 -5
  49. package/habilidades/tdd-workflow/SKILL.md +710 -715
  50. package/habilidades/testing-python/SKILL.md +335 -340
  51. package/habilidades/verificar-trabajo/SKILL.md +0 -5
  52. package/hooks/lib/etapa-perfil-usuario.js +1 -1
  53. package/hooks/lib/evolution-tracker.js +191 -35
  54. package/hooks/resumen-sesion.js +4 -4
  55. package/llms.txt +1 -1
  56. package/manifiestos/canonical-hashes.json +1310 -0
  57. package/manifiestos/modulos.json +3 -0
  58. package/manifiestos/skills-lock.json +70 -70
  59. package/package.json +1 -1
  60. package/plugin.json +1 -1
  61. package/scripts/doctor.js +13 -0
  62. package/scripts/generar-canonical-hashes.js +147 -0
  63. package/scripts/instalador.js +140 -54
  64. package/scripts/lib/audit-evolved.js +76 -0
  65. package/scripts/lib/canonical-hash.js +94 -0
  66. package/scripts/lib/evolved-fuente.js +138 -0
  67. package/scripts/lib/manifiestos.js +1 -1
  68. package/scripts/publicar.js +42 -5
  69. package/scripts/remediar-evolved-instaladas.js +242 -0
  70. package/scripts/validar.js +14 -0
  71. package/scripts/vendor/claude-usage/__pycache__/scanner.cpython-314.pyc +0 -0
  72. package/scripts/verificar-evolucion.js +36 -0
  73. package/scripts/verificar-release.js +33 -0
  74. package/agentes/.evolved.json +0 -9
  75. package/comandos/swl/.evolved.json +0 -23
  76. package/habilidades/auth-patrones/.evolved.json +0 -9
  77. package/habilidades/extractor-de-aprendizajes/.evolved.json +0 -9
  78. package/habilidades/instalar-sistema/.evolved.json +0 -9
  79. package/habilidades/manejo-errores/.evolved.json +0 -9
  80. package/habilidades/node-experto/.evolved.json +0 -9
  81. package/habilidades/release-semver/.evolved.json +0 -9
@@ -32,6 +32,32 @@ const {
32
32
  const ROOT = path.resolve(__dirname, '..');
33
33
  const PKG_PATH = path.join(ROOT, 'package.json');
34
34
 
35
+ // --- Log de la corrida (.planning/logs/publish-<version>-<ts>.log) -----------
36
+ // Captura stdout+stderr del publish para diagnóstico: npm v10 NO escribe el
37
+ // stdout de un script fallido (prepublishOnly/test:release) en su debug log,
38
+ // así que sin esto el test que rompe el publish queda invisible. Tee: eco a
39
+ // consola + archivo persistente.
40
+ let LOG_PATH = null;
41
+ function iniciarLog(version) {
42
+ try {
43
+ const dir = path.join(ROOT, '.planning', 'logs');
44
+ fs.mkdirSync(dir, { recursive: true });
45
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
46
+ LOG_PATH = path.join(dir, `publish-${version}-${ts}.log`);
47
+ // Escritura SÍNCRONA (writeFileSync/appendFileSync): un createWriteStream
48
+ // async + process.exit() inmediato perdía el buffer sin volcarlo a disco
49
+ // (dejaba el log vacío). appendFileSync vacía en cada llamada.
50
+ fs.writeFileSync(LOG_PATH, `# publish ${version} — ${new Date().toISOString()}\n# argv: ${process.argv.slice(2).join(' ')}\n\n`);
51
+ } catch (err) {
52
+ process.stderr.write(`[publicar] no se pudo abrir log: ${String(err.message).slice(0, 120)}\n`);
53
+ LOG_PATH = null;
54
+ }
55
+ }
56
+ function logWrite(text) {
57
+ if (LOG_PATH && text) { try { fs.appendFileSync(LOG_PATH, text); } catch { /* best-effort */ } }
58
+ }
59
+ function cerrarLog() { /* no-op: appendFileSync ya volcó todo a disco */ }
60
+
35
61
  /**
36
62
  * Resuelve el binario de npm a invocar. Usa node + npm-cli.js cuando es
37
63
  * posible para evitar shell:true en Windows (mismo enfoque que la lib
@@ -60,15 +86,19 @@ function npmExec(args, opts = {}) {
60
86
  }
61
87
 
62
88
  /**
63
- * Variante de npmExec que captura stderr para detección posterior de errores
64
- * estructurados (ej: EOTP). stdout sigue heredado (live-streaming visible al
65
- * usuario) y stderr se pipea a un buffer + se ecoa a process.stderr al final.
89
+ * Variante de npmExec que captura stdout+stderr para (a) detección de errores
90
+ * estructurados (ej: EOTP) y (b) persistencia en el log de la corrida
91
+ * (`iniciarLog`). Ambos streams se pipean, se ecoan a consola al terminar y se
92
+ * vuelcan al log — así el stdout del script que rompe el publish (test:release)
93
+ * queda registrado. Trade-off: la salida del prepublish aparece al finalizar el
94
+ * spawn (no en vivo), por eso se imprime un aviso antes.
66
95
  *
67
96
  * Retorna { status, stderr } sin lanzar — el caller inspecciona el status.
68
97
  */
69
98
  function npmSpawnCaptureStderr(args, opts = {}) {
70
99
  const defaults = { cwd: ROOT, timeout: 300_000, env: process.env };
71
- const merged = { ...defaults, ...opts, stdio: ['inherit', 'inherit', 'pipe'] };
100
+ const merged = { ...defaults, ...opts, stdio: ['inherit', 'pipe', 'pipe'] };
101
+ process.stderr.write('[publicar] ejecutando npm (incluye prepublish/test:release; salida completa al finalizar y en el log)...\n');
72
102
  let res;
73
103
  if (NPM_CLI_JS) {
74
104
  res = spawnSync(process.execPath, [NPM_CLI_JS, ...args], merged);
@@ -76,9 +106,13 @@ function npmSpawnCaptureStderr(args, opts = {}) {
76
106
  const bin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
77
107
  res = spawnSync(bin, args, merged);
78
108
  }
109
+ const stdout = res.stdout ? String(res.stdout) : '';
79
110
  const stderr = res.stderr ? String(res.stderr) : '';
80
- // Ecoar stderr al usuario para que vea el diagnóstico de npm aunque se capturó.
111
+ // Eco a consola + volcado al log (stdout primero, luego stderr).
112
+ if (stdout) process.stdout.write(stdout);
81
113
  if (stderr) process.stderr.write(stderr);
114
+ logWrite(stdout);
115
+ logWrite(stderr);
82
116
  return { status: res.status, signal: res.signal, stderr, error: res.error };
83
117
  }
84
118
 
@@ -482,6 +516,7 @@ function reportarResultado(pkg, opts, npmjsOk, githubOk) {
482
516
  function main() {
483
517
  const opts = parsearArgs(process.argv.slice(2));
484
518
  const pkg = leerPkg();
519
+ iniciarLog(pkg.version);
485
520
  imprimirEncabezado(pkg.version, opts.dryRun);
486
521
  avisarSiHayCambiosSinCommit(opts.dryRun);
487
522
 
@@ -489,6 +524,8 @@ function main() {
489
524
  const githubOk = opts.soloNpmjs ? true : publicarGitHub(pkg, opts.dryRun);
490
525
 
491
526
  reportarResultado(pkg, opts, npmjsOk, githubOk);
527
+ if (LOG_PATH) process.stdout.write(`\nLog de la corrida: ${path.relative(ROOT, LOG_PATH).replace(/\\/g, '/')}\n`);
528
+ cerrarLog();
492
529
  process.exit(npmjsOk && githubOk ? 0 : 1);
493
530
  }
494
531
 
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * remediar-evolved-instaladas — Descongela copias instaladas con marcadores
6
+ * evolved espurios (Fase 16, T-25, REQ-16-14).
7
+ *
8
+ * Las instalaciones previas a Fase 16 quedaron con componentes `evolved:true`
9
+ * shippeados de fábrica que el instalador preservaba para siempre (bug). Este
10
+ * sub-comando los reclasifica en la máquina del usuario:
11
+ *
12
+ * - B (shipped intacto): el cuerpo canónico instalado coincide con el baseline
13
+ * de su `evolved-from` (manifiesto actual o `git show v<from>:<ruta>`).
14
+ * → el usuario NO lo tocó → actualizar al canónico actual (con backup + auditoría).
15
+ * - A (evolución del usuario): el cuerpo difiere del baseline, o no hay baseline
16
+ * verificable. → PRESERVAR (jamás overwrite). Invariante.
17
+ *
18
+ * SEGURO por defecto: `--dry-run` (solo reporta). Requiere `--apply` explícito.
19
+ * Ante CUALQUIER duda (sin baseline, sin fuente, error) clasifica A (preserva).
20
+ *
21
+ * Uso:
22
+ * node scripts/remediar-evolved-instaladas.js --target=~/.claude (dry-run)
23
+ * node scripts/remediar-evolved-instaladas.js --target=~/.claude --apply (aplica)
24
+ *
25
+ * Zero-deps salvo git (para baseline histórico). Layout runtime: claude.
26
+ *
27
+ * @module scripts/remediar-evolved-instaladas
28
+ */
29
+
30
+ const fs = require('fs');
31
+ const os = require('os');
32
+ const path = require('path');
33
+ const { spawnSync } = require('child_process');
34
+
35
+ const { canonicalHash } = require('./lib/canonical-hash');
36
+ const { auditarEscritura } = require('./lib/audit-evolved');
37
+ const { mergeEvolved } = require('../hooks/lib/evolution-tracker');
38
+
39
+ const RAIZ_PKG = path.resolve(__dirname, '..');
40
+
41
+ // Mapeo dir-runtime (claude) → dir-fuente (clave del manifiesto / git).
42
+ const MAP_DIRS = [
43
+ { runtime: 'agents', fuente: 'agentes', skill: false },
44
+ { runtime: 'commands/swl', fuente: 'comandos/swl', skill: false },
45
+ { runtime: 'rules', fuente: 'reglas', skill: false },
46
+ { runtime: 'skills', fuente: 'habilidades', skill: true },
47
+ ];
48
+
49
+ function expandirTarget(t) {
50
+ if (!t) return path.join(os.homedir(), '.claude');
51
+ if (t === '~' || t.startsWith('~/') || t.startsWith('~\\')) return path.join(os.homedir(), t.slice(1).replace(/^[/\\]/, ''));
52
+ return path.resolve(t);
53
+ }
54
+
55
+ function leerEvolved(content) {
56
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
57
+ if (!m) return { evolved: false };
58
+ const fm = m[1];
59
+ if (!/^evolved:\s*(true|yes)\b/mi.test(fm)) return { evolved: false };
60
+ const from = (/^evolved-from:\s*["']?([^"'\n\r]+)/mi.exec(fm) || [])[1];
61
+ // CRÍTICO (nemesis): extraer evolved-origin — sin esto, la defensa
62
+ // 'evolved-origin: user fuerza A' era letra muerta aquí (ev.origin undefined).
63
+ const origin = (/^evolved-origin:\s*["']?([^"'\n\r]+)/mi.exec(fm) || [])[1];
64
+ return { evolved: true, from: from ? from.trim() : '', origin: origin ? origin.trim() : '' };
65
+ }
66
+
67
+ /** Baseline canónico de un componente en una versión: manifiesto actual o git. */
68
+ function baselineCanonico(manifiesto, from, sourceRel) {
69
+ if (manifiesto && manifiesto[from] && manifiesto[from][sourceRel]) return manifiesto[from][sourceRel];
70
+ if (!from) return null;
71
+ const r = spawnSync('git', ['show', `v${from}:${sourceRel}`], { cwd: RAIZ_PKG, encoding: 'utf8' });
72
+ if (r.status !== 0 || !r.stdout) return null;
73
+ return canonicalHash(r.stdout);
74
+ }
75
+
76
+ /**
77
+ * Conjunto de hashes canónicos que el archivo `sourceRel` tuvo a lo largo de
78
+ * TODA la historia del repo madre (siguiendo renames). Si el cuerpo instalado
79
+ * coincide con cualquiera de ellos, el componente es contenido shipped del repo
80
+ * (ya incorporado en algún momento → B), no una evolución inventada por el
81
+ * usuario (Punto 1: "analizar si ya lo incorporas al proyecto madre").
82
+ * @param {string} sourceRel
83
+ * @param {number} [maxCommits=120]
84
+ * @returns {Set<string>}
85
+ */
86
+ function hashesHistoricos(sourceRel, maxCommits = 120) {
87
+ const set = new Set();
88
+ const GIT_OPTS = { cwd: RAIZ_PKG, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 };
89
+ const log = spawnSync('git', ['log', '--all', '--follow', '--pretty=%H', '--', sourceRel], GIT_OPTS);
90
+ if (log.status !== 0 || !log.stdout) return set;
91
+ const commits = log.stdout.trim().split('\n').filter(Boolean).slice(0, maxCommits);
92
+ for (const c of commits) {
93
+ // status !== 0 es esperado en commits pre-rename (la ruta nueva no existía):
94
+ // se saltan, no son error. Solo se agregan blobs realmente presentes.
95
+ const show = spawnSync('git', ['show', `${c}:${sourceRel}`], GIT_OPTS);
96
+ if (show.status === 0 && show.stdout) set.add(canonicalHash(show.stdout));
97
+ }
98
+ return set;
99
+ }
100
+
101
+ function cargarManifiesto() {
102
+ try { return JSON.parse(fs.readFileSync(path.join(RAIZ_PKG, 'manifiestos', 'canonical-hashes.json'), 'utf8')); }
103
+ catch { return {}; }
104
+ }
105
+
106
+ /** Lista componentes evolved instalados en el target, con su mapeo a fuente. */
107
+ function escanear(target) {
108
+ const items = [];
109
+ for (const { runtime, fuente, skill } of MAP_DIRS) {
110
+ const dir = path.join(target, runtime);
111
+ if (!fs.existsSync(dir)) continue;
112
+ if (skill) {
113
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
114
+ if (!e.isDirectory()) continue;
115
+ const skillMd = path.join(dir, e.name, 'SKILL.md');
116
+ if (fs.existsSync(skillMd)) items.push({ archivo: skillMd, sourceRel: `${fuente}/${e.name}/SKILL.md`, esDir: true, dir: path.join(dir, e.name) });
117
+ }
118
+ } else {
119
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
120
+ if (e.isFile() && e.name.endsWith('.md')) items.push({ archivo: path.join(dir, e.name), sourceRel: `${fuente}/${e.name}`, esDir: false });
121
+ }
122
+ }
123
+ }
124
+ return items;
125
+ }
126
+
127
+ function backupSimple(target, archivo, ts) {
128
+ const rel = path.relative(target, archivo);
129
+ const dest = path.join(target, '.swl-evolved-backups', ts, rel);
130
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
131
+ fs.copyFileSync(archivo, dest);
132
+ return dest;
133
+ }
134
+
135
+ function remediar({ target, apply = false } = {}) {
136
+ target = expandirTarget(target);
137
+ const manifiesto = cargarManifiesto();
138
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
139
+ // El target es un dir de config runtime (~/.claude, ~/.codex), NO un proyecto:
140
+ // los artefactos de gobernanza evolved van bajo `.swl-evolved-backups/` (donde
141
+ // ya viven los backups), sin crear un `.planning/` de proyecto ahí (C-3).
142
+ const reconcileDir = path.join(target, '.swl-evolved-backups', 'reconcile');
143
+ const resumen = { target, A: [], B: [], errores: [], reconcileDir };
144
+
145
+ for (const item of escanear(target)) {
146
+ let content;
147
+ try { content = fs.readFileSync(item.archivo, 'utf8'); } catch (e) { resumen.errores.push({ archivo: item.sourceRel, error: e.message }); continue; }
148
+ const ev = leerEvolved(content);
149
+ if (!ev.evolved) continue;
150
+
151
+ const fuenteAbs = path.join(RAIZ_PKG, item.sourceRel);
152
+ const instaladoHash = canonicalHash(content);
153
+
154
+ // Clasificación B (shipped → ya incorporado en el repo madre):
155
+ // (1) marca explícita evolved-origin: user → A definitivo (defensa en profundidad).
156
+ // (2) coincide con baseline del manifiesto / git show v<from>.
157
+ // (3) coincide con CUALQUIER estado histórico del archivo en el repo (Punto 1).
158
+ let esB = false;
159
+ let evidencia = '';
160
+ if ((ev.origin || '').toLowerCase() === 'user') {
161
+ evidencia = 'evolved-origin: user (marca explícita)';
162
+ } else {
163
+ const baseline = baselineCanonico(manifiesto, ev.from, item.sourceRel);
164
+ if (baseline && instaladoHash === baseline) { esB = true; evidencia = `baseline v${ev.from} coincide`; }
165
+ else if (hashesHistoricos(item.sourceRel).has(instaladoHash)) { esB = true; evidencia = 'coincide con un estado histórico del repo madre'; }
166
+ else evidencia = baseline ? 'cuerpo difiere de todo estado del repo (edición del usuario)' : 'sin baseline ni coincidencia histórica';
167
+ }
168
+
169
+ if (!esB) {
170
+ // A — evolución del usuario. PRESERVAR + diff centralizado para revisión upstream.
171
+ let diffRel = null;
172
+ if (apply && fs.existsSync(fuenteAbs)) {
173
+ const m = mergeEvolved(item.archivo, fuenteAbs, 'actual', { diffDir: reconcileDir });
174
+ if (m.merged && m.diffPath) diffRel = path.relative(target, m.diffPath).replace(/\\/g, '/');
175
+ }
176
+ resumen.A.push({ archivo: item.sourceRel, from: ev.from, razon: evidencia, diff: diffRel });
177
+ continue;
178
+ }
179
+
180
+ // B — shipped intacto. Actualizar al canónico actual del paquete.
181
+ if (!fs.existsSync(fuenteAbs)) { resumen.errores.push({ archivo: item.sourceRel, error: 'fuente actual no existe' }); continue; }
182
+ if (apply) {
183
+ try {
184
+ const hashFuente = canonicalHash(fs.readFileSync(fuenteAbs, 'utf8')); // antes del copy
185
+ const backupPath = backupSimple(target, item.archivo, ts); // backup PRECEDE al overwrite
186
+ fs.copyFileSync(fuenteAbs, item.archivo); // fuente limpio (sin evolved-*) → descongelado
187
+ auditarEscritura({ archivo: item.sourceRel, clasificacion: 'B', accion: 'overwrite', hashAntes: instaladoHash, hashDespues: hashFuente, backupPath, evidencia: `remediación: ${evidencia}`, cwd: target, subdir: '.swl-evolved-backups' });
188
+ } catch (e) { resumen.errores.push({ archivo: item.sourceRel, error: e.message }); continue; }
189
+ }
190
+ resumen.B.push({ archivo: item.sourceRel, from: ev.from, razon: evidencia });
191
+ }
192
+
193
+ if (apply) escribirIndiceReconcile(resumen);
194
+ return resumen;
195
+ }
196
+
197
+ /** Escribe un índice consolidado de reconciliación (superficie de revisión upstream). */
198
+ function escribirIndiceReconcile(resumen) {
199
+ if (resumen.A.length === 0) return;
200
+ try {
201
+ fs.mkdirSync(resumen.reconcileDir, { recursive: true });
202
+ const lineas = [
203
+ '# Reconciliación de evoluciones del usuario (revisión upstream)',
204
+ '',
205
+ 'Estos componentes difieren de todo estado conocido del repo madre — son',
206
+ 'ediciones locales genuinas. Revisa cada diff y decide si incorporarlo al',
207
+ 'repo con `/swl:aprender` o `/swl:autoresearch`, o descartarlo.',
208
+ '',
209
+ '| Componente | evolved-from | Diff | Motivo |',
210
+ '|---|---|---|---|',
211
+ ...resumen.A.map(a => `| ${a.archivo} | v${a.from || '?'} | ${a.diff || '—'} | ${a.razon} |`),
212
+ '',
213
+ ];
214
+ fs.writeFileSync(path.join(resumen.reconcileDir, 'INDEX.md'), lineas.join('\n'), 'utf8');
215
+ } catch { /* best-effort */ }
216
+ }
217
+
218
+ function imprimir(r, apply) {
219
+ process.stdout.write(`\n=== Remediación evolved instaladas — target: ${r.target} ===\n`);
220
+ process.stdout.write(`Modo: ${apply ? 'APPLY (escribe)' : 'DRY-RUN (solo reporta)'}\n\n`);
221
+ process.stdout.write(`B (shipped intacto → ${apply ? 'actualizado' : 'se actualizaría'}): ${r.B.length}\n`);
222
+ for (const b of r.B) process.stdout.write(` ⇪ ${b.archivo} (from v${b.from})\n`);
223
+ process.stdout.write(`\nA (evolución del usuario → PRESERVADO): ${r.A.length}\n`);
224
+ for (const a of r.A) process.stdout.write(` ★ ${a.archivo} (from v${a.from || '?'}) — ${a.razon}\n`);
225
+ if (r.errores.length) {
226
+ process.stdout.write(`\nErrores: ${r.errores.length}\n`);
227
+ for (const e of r.errores) process.stdout.write(` ! ${e.archivo}: ${e.error}\n`);
228
+ }
229
+ if (apply && r.A.length) process.stdout.write(`\nDiffs de reconciliación (revisión upstream): ${path.relative(r.target, r.reconcileDir).replace(/\\/g, '/')}/INDEX.md\n`);
230
+ if (!apply && r.B.length) process.stdout.write(`\n→ Para aplicar: agregar --apply (backups, diffs y AUDITORIA centralizados en <target>/.swl-evolved-backups/).\n`);
231
+ }
232
+
233
+ if (require.main === module) {
234
+ const args = process.argv.slice(2);
235
+ const targetArg = (args.find(a => a.startsWith('--target=')) || '').slice('--target='.length) || undefined;
236
+ const apply = args.includes('--apply');
237
+ const r = remediar({ target: targetArg, apply });
238
+ imprimir(r, apply);
239
+ process.exit(0);
240
+ }
241
+
242
+ module.exports = { remediar, escanear, leerEvolved, baselineCanonico, expandirTarget };
@@ -101,6 +101,20 @@ 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
+ // 7c. Gate inverso de evolución (Fase 16, REQ-16-03): el FUENTE no debe portar
105
+ // marcadores evolved (frontmatter evolved:true ni sidecar .evolved.json). Un
106
+ // marcador en el repo madre es shipped-evolved espurio que congela el componente
107
+ // downstream. Ver scripts/lib/evolved-fuente.js y verificar-evolucion.js --gate-inverso.
108
+ const { listarOfensores } = require('./lib/evolved-fuente');
109
+ const ofensoresEvolved = listarOfensores(RAIZ);
110
+ const totalOfensores = ofensoresEvolved.frontmatter.length + ofensoresEvolved.sidecars.length;
111
+ verificar(
112
+ totalOfensores === 0,
113
+ totalOfensores === 0
114
+ ? 'Gate inverso evolved: fuente sin marcadores evolved espurios'
115
+ : `Gate inverso evolved: ${totalOfensores} componente(s) del fuente con evolved (corregir: node scripts/verificar-evolucion.js --gate-inverso --fix)`
116
+ );
117
+
104
118
  // 8. Reglas
105
119
  const reglas = fs.readdirSync(path.join(RAIZ, 'reglas')).filter(f => f.endsWith('.md'));
106
120
  verificar(reglas.length >= 7, `Reglas: ${reglas.length} (mínimo 7)`);
@@ -32,6 +32,7 @@
32
32
  const fs = require('fs');
33
33
  const path = require('path');
34
34
  const { execSync } = require('child_process');
35
+ const { listarOfensores, limpiar } = require('./lib/evolved-fuente');
35
36
 
36
37
  /**
37
38
  * Detecta si `dir` es la raíz del paquete swl-ses (repo madre).
@@ -291,9 +292,43 @@ function imprimirResultado(r) {
291
292
  process.stdout.write('[' + marca + '] ' + r.archivo + ': ' + mensaje + '\n');
292
293
  }
293
294
 
295
+ /**
296
+ * Gate inverso (Fase 16, REQ-16-03): ningún componente del FUENTE (repo madre)
297
+ * puede portar marcador evolved (frontmatter `evolved:true` o sidecar
298
+ * `.evolved.json`). Un marcador en el fuente es shipped-evolved espurio que
299
+ * congela el componente downstream. Corre SIEMPRE en el repo madre — es el
300
+ * complemento exacto del bypass `if (raizPaquete)` de verificarArchivo (que
301
+ * omite los checks evolved-de-usuario; este gate verifica lo contrario).
302
+ *
303
+ * @param {{ fix?: boolean, raiz?: string }} [opts]
304
+ * @returns {number} 0 si limpio, 1 si hay ofensores (sin --fix)
305
+ */
306
+ function gateInverso({ fix = false, raiz = process.cwd() } = {}) {
307
+ if (fix) {
308
+ const acciones = limpiar(raiz);
309
+ for (const a of acciones) process.stdout.write(` [fix] ${a.tipo}: ${a.archivo}\n`);
310
+ process.stdout.write(`[gate-inverso] limpieza aplicada: ${acciones.length} acción(es)\n`);
311
+ }
312
+ const { frontmatter, sidecars } = listarOfensores(raiz);
313
+ const total = frontmatter.length + sidecars.length;
314
+ if (total === 0) {
315
+ process.stdout.write('[gate-inverso] OK — ningún componente del fuente porta marcador evolved.\n');
316
+ return 0;
317
+ }
318
+ process.stdout.write(`[gate-inverso] FALLA — ${total} componente(s) del fuente con marcador evolved (deben limpiarse):\n`);
319
+ for (const f of frontmatter) process.stdout.write(` - frontmatter evolved: ${f}\n`);
320
+ for (const s of sidecars) process.stdout.write(` - sidecar .evolved.json: ${s}\n`);
321
+ process.stdout.write(' → corregir: node scripts/verificar-evolucion.js --gate-inverso --fix\n');
322
+ return 1;
323
+ }
324
+
294
325
  function main() {
295
326
  const args = process.argv.slice(2);
296
327
 
328
+ if (args.includes('--gate-inverso')) {
329
+ process.exit(gateInverso({ fix: args.includes('--fix') }));
330
+ }
331
+
297
332
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
298
333
  process.stdout.write(
299
334
  'Uso:\n' +
@@ -351,5 +386,6 @@ module.exports = {
351
386
  leerCampo,
352
387
  obtenerVersionEnHEAD,
353
388
  esRaizDelPaquete,
389
+ gateInverso,
354
390
  main,
355
391
  };
@@ -317,6 +317,18 @@ function main() {
317
317
  // calibración + ADR posterior (mismo patrón que G0/G2/G3).
318
318
  const gateLicencias = ejecutarGateLicencias();
319
319
 
320
+ // Gate de evolución (Fase 16, REQ-16-13): el manifiesto canonical-hashes debe
321
+ // estar al día y el fuente NO debe portar marcadores evolved espurios. Ambos
322
+ // son blocking — un release con manifiesto stale o fuente evolved congelaría
323
+ // componentes downstream.
324
+ const gateEvolved = ejecutarGateEvolved();
325
+ if (!gateEvolved.ok) {
326
+ fallasObligatorias++;
327
+ }
328
+ console.log(gateEvolved.ok
329
+ ? ' [OK] Gate evolución: manifiesto al día + fuente sin evolved espurio'
330
+ : ` [FALLA] Gate evolución: ${gateEvolved.problemas.join('; ')}`);
331
+
320
332
  if (jsonOut) {
321
333
  process.stdout.write(JSON.stringify({
322
334
  version,
@@ -330,6 +342,7 @@ function main() {
330
342
  description_gate: gateDescription,
331
343
  aiisms_gate: aiismsGate,
332
344
  gate_licencias: gateLicencias,
345
+ gate_evolved: gateEvolved,
333
346
  resultados: resultados.map(({ entrada, resultado }) => ({
334
347
  archivo: entrada.archivo,
335
348
  obligatorio: entrada.obligatorio,
@@ -887,6 +900,25 @@ function ejecutarGateDescription(contadoresReales) {
887
900
  * para tests; en producción usa el CWD del proceso).
888
901
  * @returns {{disponible: boolean, hallazgos?: Array, resumen?: object, error?: string}}
889
902
  */
903
+ /**
904
+ * Gate de evolución (Fase 16): manifiesto canonical-hashes al día + fuente sin
905
+ * marcadores evolved espurios. Reusa los scripts existentes vía subproceso.
906
+ * @returns {{ ok: boolean, problemas: string[] }}
907
+ */
908
+ function ejecutarGateEvolved() {
909
+ const { spawnSync } = require('child_process');
910
+ const problemas = [];
911
+ const correr = (args) => spawnSync(process.execPath, args, { cwd: CWD, encoding: 'utf8' }).status;
912
+
913
+ if (correr([path.join(CWD, 'scripts', 'generar-canonical-hashes.js'), '--check']) !== 0) {
914
+ problemas.push('manifiesto canonical-hashes desactualizado (node scripts/generar-canonical-hashes.js)');
915
+ }
916
+ if (correr([path.join(CWD, 'scripts', 'verificar-evolucion.js'), '--gate-inverso']) !== 0) {
917
+ problemas.push('fuente con marcadores evolved espurios (node scripts/verificar-evolucion.js --gate-inverso --fix)');
918
+ }
919
+ return { ok: problemas.length === 0, problemas };
920
+ }
921
+
890
922
  function ejecutarGateLicencias(baseDir = CWD) {
891
923
  try {
892
924
  const { hallazgos, resumen } = evaluarLicencias(baseDir);
@@ -905,4 +937,5 @@ module.exports = {
905
937
  extraerCifrasDescription,
906
938
  ejecutarGateDescription,
907
939
  ejecutarGateLicencias,
940
+ ejecutarGateEvolved,
908
941
  };
@@ -1,9 +0,0 @@
1
- {
2
- "release-manager-swl.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.10.4",
5
- "evolvedAt": "2026-04-20",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "gap recurrente en bumps: package-lock nested + .planning/"
8
- }
9
- }
@@ -1,23 +0,0 @@
1
- {
2
- "release.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.4.0",
5
- "evolvedAt": "2026-04-11",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "mejora de metodología: checklist obligatoria de archivos de versión en paso 6"
8
- },
9
- "aprender.md": {
10
- "evolved": true,
11
- "evolvedFrom": "5.12.3",
12
- "evolvedAt": "2026-04-25",
13
- "evolvedBy": "aprender",
14
- "evolvedNote": "Paso 2 — filtro crítico obligatorio sobre reportes de sub-agentes Explore para evitar sobre-ingeniería al analizar papers académicos"
15
- },
16
- "verificar.md": {
17
- "evolved": true,
18
- "evolvedFrom": "5.12.3",
19
- "evolvedAt": "2026-04-26",
20
- "evolvedBy": "evolucionar",
21
- "evolvedNote": "flag --until-converge para iterar verificar→corregir→re-verificar hasta 0 hallazgos CRÍTICO+ALTO+MAYOR (max-iter=5, --no-prompt CI, detección adversarial ≥5 hallazgos nuevos)"
22
- }
23
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.11.1",
5
- "evolvedAt": "2026-04-23",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "sección nueva: Multitenancy con get_readable_statement (patrón de polar)"
8
- }
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.11.0",
5
- "evolvedAt": "2026-04-22",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "Gotchas nuevos: registro dual manifiestos para hooks + inventario estimado a mano"
8
- }
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.10.4",
5
- "evolvedAt": "2026-04-20",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "state file busqueda en 7 ubicaciones"
8
- }
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "1.1.0",
5
- "evolvedAt": "2026-05-02",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "Gotcha nueva: try/catch require para módulos opcionales (15 archivos migrados)"
8
- }
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "5.3.3",
5
- "evolvedAt": "2026-04-09",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "anti-patrón JSDoc glob, JSONL append-only, webhook fire-and-forget, dual-use scripts"
8
- }
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "SKILL.md": {
3
- "evolved": true,
4
- "evolvedFrom": "1.0.1",
5
- "evolvedAt": "2026-05-02",
6
- "evolvedBy": "aprender",
7
- "evolvedNote": "Sección nueva: publish a múltiples registries (republish-only + auth GitHub Packages)"
8
- }
9
- }