@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
@@ -2,11 +2,6 @@
2
2
  name: verificar-trabajo
3
3
  description: Verificación goal-backward del trabajo ejecutado en 4 niveles progresivos — EXISTE, SUSTANTIVO, CONECTADO, DATOS_FLUYEN. Clasifica claims en 4 tipos (TASK, FIX, TEST_OR_BUILD, FEATURE_GO) con evidencia proporcional. Detecta stubs, componentes huérfanos, integraciones rotas y flujos incompletos. Produce veredictos estructurados JSON con clasificación de riesgo (Low/Medium/High) y evidencia verificable. Soporta loop de reparación cuando el veredicto es Fail.
4
4
  version: "1.2.1"
5
- evolved: true
6
- evolved-from: "1.2.0"
7
- evolved-at: "2026-05-15"
8
- evolved-by: "aprender"
9
- evolved-note: "Gotchas v1.5.1 acumulados en este ciclo: (1) health-check post-hoc que recalcula contexto del install desde cwd actual → falso positivo (caso doctor 2026-05-15); (2) smoke E2E obligatorio para features cross-cutting (Sub-fase 11.5: transformarAgente nunca invocado pese a suite 100% verde); (3) auto-reparación debe verificar que el chequeo subsecuente ya no detecta el problema, si no → loop infinito (Sub-fase 12: doctor)."
10
5
  herramientasPermitidas: [Read, Write, Edit, Bash, Glob, Grep]
11
6
  exclusiones:
12
7
  - "No cargar durante la implementación activa de una tarea; la verificación es posterior a la implementación, no concurrente."
@@ -251,7 +251,7 @@ function ejecutar(inputRaw) {
251
251
  // cada sesión.
252
252
  const señalesKw = detectarKeywords(textoUsuario)
253
253
  .map(s => {
254
- const r = scanInjection(s.snippet, 'perfil-usuario/dirty.json');
254
+ const r = scanInjection(s.snippet, 'user-profile/dirty.json');
255
255
  return { ...s, _scan: r };
256
256
  })
257
257
  .filter(s => {
@@ -28,6 +28,75 @@ try {
28
28
  atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
29
29
  }
30
30
 
31
+ // Comparación SemVer real (Fase 16 — el bug original comparaba versiones como
32
+ // string). Fallback defensivo a comparación numérica por segmento.
33
+ let compararSemver;
34
+ try {
35
+ ({ compararSemver } = require('../../scripts/lib/npm-version'));
36
+ } catch {
37
+ compararSemver = (a, b) => {
38
+ const pa = /^(\d+)\.(\d+)\.(\d+)/.exec(String(a));
39
+ const pb = /^(\d+)\.(\d+)\.(\d+)/.exec(String(b));
40
+ if (!pa || !pb) return null;
41
+ for (let i = 1; i <= 3; i++) {
42
+ const x = Number(pa[i]), y = Number(pb[i]);
43
+ if (x !== y) return x > y ? 1 : -1;
44
+ }
45
+ return 0;
46
+ };
47
+ }
48
+
49
+ // Discriminador A/B por hash del cuerpo canónico (Fase 16).
50
+ let canonicalHash, hashCoincide;
51
+ try {
52
+ ({ canonicalHash, hashCoincide } = require('../../scripts/lib/canonical-hash'));
53
+ } catch (e) {
54
+ // H3 (nemesis O5): no-fallback-silencioso. Si canonical-hash no carga, el
55
+ // discriminador A/B queda inhabilitado (todo evolved → conflict/merge, jamás
56
+ // overwrite — protector pero bloquea actualizaciones de shipped). Avisar.
57
+ try { process.stderr.write(`[evolution-tracker] canonical-hash no disponible (${e.code || e.message}); discriminador A/B deshabilitado → todo evolved se trata como evolución del usuario.\n`); } catch { /* noop */ }
58
+ canonicalHash = null;
59
+ hashCoincide = () => false;
60
+ }
61
+
62
+ /** Ruta al manifiesto de hashes canónicos (baseline del discriminador A/B). */
63
+ const CANONICAL_HASHES_PATH = path.resolve(__dirname, '../../manifiestos/canonical-hashes.json');
64
+
65
+ /**
66
+ * Carga el manifiesto de hashes canónicos. Tolera ausencia (→ {}), de modo que
67
+ * la ausencia de baseline degrada a estrategia protectora (conflict), nunca a
68
+ * overwrite. Permite override de ruta para tests.
69
+ * @param {string} [rutaManifiesto]
70
+ * @returns {object}
71
+ */
72
+ function _cargarCanonicalHashes(rutaManifiesto) {
73
+ const ruta = rutaManifiesto || CANONICAL_HASHES_PATH;
74
+ try {
75
+ return JSON.parse(fs.readFileSync(ruta, 'utf8'));
76
+ } catch {
77
+ return {};
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Deriva la ruta relativa del componente (clave del manifiesto) a partir de la
83
+ * ruta del archivo ORIGEN (del paquete). El manifiesto usa claves tipo
84
+ * `agentes/X-swl.md`, `comandos/swl/foo.md`, `habilidades/Y/SKILL.md`,
85
+ * `reglas/z.md`. Devuelve null si no se reconoce un dominio versionable.
86
+ * @param {string} origenAbs
87
+ * @returns {string|null}
88
+ */
89
+ function _rutaRelComponente(origenAbs) {
90
+ const partes = String(origenAbs).split(/[\\/]+/);
91
+ const dominios = new Set(['agentes', 'habilidades', 'comandos', 'reglas']);
92
+ for (let i = partes.length - 1; i >= 0; i--) {
93
+ if (dominios.has(partes[i])) {
94
+ return partes.slice(i).join('/');
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
31
100
  // ---------------------------------------------------------------------------
32
101
  // Constantes
33
102
  // ---------------------------------------------------------------------------
@@ -41,6 +110,7 @@ const EVOLUTION_FIELDS = {
41
110
  evolvedRounds: 'evolved-rounds', // number: rondas de autoresearch (si aplica)
42
111
  evolvedScore: 'evolved-score', // string: "baseline% → final%"
43
112
  evolvedNote: 'evolved-note', // string: descripción breve del cambio
113
+ evolvedOrigin: 'evolved-origin', // string: "user" | "shipped" (defensa en profundidad)
44
114
  };
45
115
 
46
116
  /** Nombre del archivo sidecar para componentes sin frontmatter (reglas). */
@@ -123,7 +193,11 @@ function readEvolutionMeta(filePath) {
123
193
 
124
194
  return { evolved: true, metadata };
125
195
  } catch (_) {
126
- return empty;
196
+ // Fail-cerrado (Fase 16, REQ-16-10): un error de lectura/parse NO significa
197
+ // "no evolucionado". Señalamos `_readError` para que decideUpdateStrategy
198
+ // proteja el archivo (conflict) en lugar de sobreescribirlo. Mantenemos
199
+ // `evolved: false` para no contaminar el conteo de scanEvolved.
200
+ return { evolved: false, metadata: {}, _readError: true };
127
201
  }
128
202
  }
129
203
 
@@ -145,7 +219,10 @@ function _readSidecar(filePath) {
145
219
  if (entry && entry.evolved) {
146
220
  return { evolved: true, metadata: entry };
147
221
  }
148
- } catch (_) {}
222
+ } catch (_) {
223
+ // Fail-cerrado (REQ-16-10): sidecar corrupto/ilegible → proteger, no overwrite.
224
+ return { evolved: false, metadata: {}, _readError: true };
225
+ }
149
226
 
150
227
  return { evolved: false, metadata: {} };
151
228
  }
@@ -235,10 +312,18 @@ function markAsEvolved(filePath, meta) {
235
312
  // Detectar EOL del archivo para preservarlo en la escritura.
236
313
  const eol = prefix.includes('\r\n') ? '\r\n' : '\n';
237
314
 
238
- // Remover campos evolved previos
315
+ // Preservar el origen existente si el caller no lo sobreescribe (REQ-16-06):
316
+ // un `evolved-origin: shipped` no debe degradarse a `user` por defecto.
317
+ const existingOrigin = (frontmatter.match(/^evolved-origin:\s*["']?(\w+)["']?/mi) || [])[1];
318
+ const origin = meta.origin || existingOrigin || 'user';
319
+
320
+ // Remover campos evolved previos. Flag `i`: simétrico con la extracción
321
+ // case-insensitive de existingOrigin (arriba) y con readEvolutionMeta — sin
322
+ // él, `Evolved-Origin:` se detectaría pero no se removería, dejando campos
323
+ // duplicados en el frontmatter (Bug 1).
239
324
  frontmatter = frontmatter
240
325
  .split(/\r?\n/)
241
- .filter(line => !line.match(/^evolved[-\w]*:/))
326
+ .filter(line => !line.match(/^evolved[-\w]*:/i))
242
327
  .join(eol);
243
328
 
244
329
  // Agregar campos nuevos
@@ -248,6 +333,7 @@ function markAsEvolved(filePath, meta) {
248
333
  `evolved-from: "${meta.from}"`,
249
334
  `evolved-at: "${date}"`,
250
335
  `evolved-by: "${meta.by}"`,
336
+ `evolved-origin: "${origin}"`,
251
337
  ];
252
338
  if (meta.rounds) newFields.push(`evolved-rounds: ${meta.rounds}`);
253
339
  if (meta.score) newFields.push(`evolved-score: "${meta.score}"`);
@@ -279,11 +365,13 @@ function _writeSidecar(filePath, meta) {
279
365
  }
280
366
  } catch (_) {}
281
367
 
368
+ const existingOrigin = data[basename] && data[basename].evolvedOrigin;
282
369
  data[basename] = {
283
370
  evolved: true,
284
371
  evolvedFrom: meta.from,
285
372
  evolvedAt: new Date().toISOString().split('T')[0],
286
373
  evolvedBy: meta.by,
374
+ evolvedOrigin: meta.origin || existingOrigin || 'user',
287
375
  ...(meta.rounds && { evolvedRounds: meta.rounds }),
288
376
  ...(meta.score && { evolvedScore: meta.score }),
289
377
  ...(meta.note && { evolvedNote: meta.note }),
@@ -305,9 +393,18 @@ function _writeSidecar(filePath, meta) {
305
393
  * @param {string} versionNueva - Versión del paquete nuevo (ej: "5.2.0").
306
394
  * @returns {{ strategy: 'preserve'|'overwrite'|'conflict', reason: string, metadata?: object }}
307
395
  */
308
- function decideUpdateStrategy(destino, origen, versionNueva) {
396
+ function decideUpdateStrategy(destino, origen, versionNueva, opts = {}) {
309
397
  const evo = readEvolutionMeta(destino);
310
398
 
399
+ // Fail-cerrado (REQ-16-10): metadata ilegible/corrupta → proteger, NUNCA overwrite.
400
+ if (evo._readError) {
401
+ return {
402
+ strategy: 'conflict',
403
+ reason: 'No se pudo leer/parsear la metadata de evolución del destino — se protege el archivo (no overwrite)',
404
+ metadata: {},
405
+ };
406
+ }
407
+
311
408
  if (!evo.evolved) {
312
409
  return { strategy: 'overwrite', reason: 'No evolucionado — actualización normal' };
313
410
  }
@@ -316,7 +413,7 @@ function decideUpdateStrategy(destino, origen, versionNueva) {
316
413
  const evolvedBy = evo.metadata.evolvedBy || evo.metadata.evolved_by || 'auto-evolución';
317
414
 
318
415
  // Caso 1: evolved: true y no hay archivo origen disponible → preservar siempre
319
- // (no podemos comparar, pero el usuario evolucionó deliberadamente)
416
+ // (no podemos comparar, pero el archivo está marcado como evolucionado).
320
417
  if (!origen || !fs.existsSync(origen)) {
321
418
  return {
322
419
  strategy: 'preserve',
@@ -325,39 +422,75 @@ function decideUpdateStrategy(destino, origen, versionNueva) {
325
422
  };
326
423
  }
327
424
 
328
- // Caso 2: Si el contenido del origen (sin evolved fields) es igual al destino (sin evolved fields)
329
- // → preservar (la versión nueva no cambió el contenido base de este archivo)
425
+ let origenContent, destinoContent;
330
426
  try {
331
- const origenContent = fs.readFileSync(origen, 'utf8');
332
- const destinoContent = fs.readFileSync(destino, 'utf8');
333
- const origenSinEvo = _stripEvolutionFields(origenContent);
334
- const destinoSinEvo = _stripEvolutionFields(destinoContent);
335
-
336
- if (origenSinEvo.trim() === destinoSinEvo.trim()) {
337
- return {
338
- strategy: 'preserve',
339
- reason: 'Contenido base idéntico — solo tiene campos evolved adicionales',
340
- metadata: evo.metadata,
341
- };
342
- }
343
- } catch (_) {}
427
+ origenContent = fs.readFileSync(origen, 'utf8');
428
+ destinoContent = fs.readFileSync(destino, 'utf8');
429
+ } catch (_) {
430
+ // No se pudo leer alguno de los dos → proteger (fail-cerrado).
431
+ return {
432
+ strategy: 'conflict',
433
+ reason: 'Error de lectura al comparar origen/destino — se protege el archivo (no overwrite)',
434
+ metadata: evo.metadata,
435
+ };
436
+ }
344
437
 
345
- // Caso 3: La versión base de la evolución es la misma o anterior a la nueva
346
- // Y el contenido del destino tiene mutaciones reales preservar
347
- // (las mutaciones del usuario son más valiosas que la actualización base)
348
- if (evolvedFrom && evolvedFrom <= versionNueva) {
438
+ // Caso 2: cuerpo canónico idéntico (ignorando campos evolved, CRLF-safe)
439
+ // preservar: la versión nueva no cambió el contenido base de este archivo.
440
+ const mismoBody = canonicalHash
441
+ ? canonicalHash(origenContent) === canonicalHash(destinoContent)
442
+ : _stripEvolutionFields(origenContent).trim() === _stripEvolutionFields(destinoContent).trim();
443
+ if (mismoBody) {
349
444
  return {
350
445
  strategy: 'preserve',
351
- reason: `Evolucionado desde ${evolvedFrom} por ${evolvedBy} mutaciones locales preservadas sobre v${versionNueva}`,
446
+ reason: 'Contenido base idénticosolo difieren los campos evolved',
447
+ metadata: evo.metadata,
448
+ };
449
+ }
450
+
451
+ // El cuerpo difiere entre origen (canónico nuevo) y destino (instalado evolved).
452
+ // Discriminador A/B (REQ-16-05, REQ-16-06):
453
+ // B — shipped-evolved intacto: el cuerpo del destino coincide con el baseline
454
+ // de la versión `evolvedFrom` → el usuario NO lo tocó → actualización
455
+ // segura (overwrite con backup, lo aplica el caller).
456
+ // A — evolución del usuario: el cuerpo difiere del baseline (o no hay
457
+ // baseline para esa versión) → MERGE, jamás overwrite (invariante).
458
+
459
+ // Defensa en profundidad (REQ-16-06): una marca explícita `evolved-origin: user`
460
+ // gana sobre el hash. Si el componente declara que fue evolucionado por el
461
+ // usuario, es A definitivamente — ni siquiera una coincidencia de hash con el
462
+ // baseline puede degradarlo a overwrite. (Las evoluciones de usuario las marca
463
+ // markAsEvolved con origin 'user' por defecto.) Inversamente, `shipped` no
464
+ // fuerza B: aún exige que el hash baseline coincida (el hash manda para B).
465
+ const origin = (evo.metadata.evolvedOrigin || '').toLowerCase();
466
+ if (origin === 'user') {
467
+ return {
468
+ strategy: 'conflict',
469
+ reason: 'evolved-origin: user (marca explícita del usuario) — merge, no overwrite',
352
470
  metadata: evo.metadata,
353
471
  };
354
472
  }
355
473
 
356
- // Caso 4: Versión base diferente + contenido diferente + versión futura (edge case)
357
- // conflicto (requiere revisión manual)
474
+ const rutaRel = _rutaRelComponente(origen);
475
+ const manifiesto = _cargarCanonicalHashes(opts.manifestPath);
476
+ if (rutaRel && hashCoincide(manifiesto, evolvedFrom, rutaRel, destinoContent)) {
477
+ return {
478
+ strategy: 'overwrite',
479
+ reason: `shipped-evolved (hash baseline de v${evolvedFrom} coincide) — actualización segura a v${versionNueva}`,
480
+ origin: 'shipped',
481
+ metadata: evo.metadata,
482
+ };
483
+ }
484
+
485
+ // Caso 3 (REGLA NUEVA): evolución del usuario o sin baseline verificable.
486
+ // Antes esto hacía `preserve` mudo por comparación string; ahora enruta al
487
+ // merge definido (el caller invoca mergeEvolved → preserve + .evolved-diff.txt).
488
+ const semverInfo = compararSemver(evolvedFrom, versionNueva);
358
489
  return {
359
490
  strategy: 'conflict',
360
- reason: `Evolucionado desde ${evolvedFrom} pero la versión nueva (${versionNueva}) tiene cambios significativos. Requiere revisión.`,
491
+ reason: rutaRel
492
+ ? `Evolución del usuario (cuerpo difiere del baseline de v${evolvedFrom}) — merge, no overwrite`
493
+ : `Evolucionado desde v${evolvedFrom} (semver ${semverInfo}) sin baseline verificable — merge, no overwrite`,
361
494
  metadata: evo.metadata,
362
495
  };
363
496
  }
@@ -371,7 +504,7 @@ function decideUpdateStrategy(destino, origen, versionNueva) {
371
504
  function _stripEvolutionFields(content) {
372
505
  return content
373
506
  .split(/\r?\n/)
374
- .filter(line => !line.match(/^evolved[-\w]*:/))
507
+ .filter(line => !line.match(/^evolved[-\w]*:/i))
375
508
  .join('\n');
376
509
  }
377
510
 
@@ -441,7 +574,7 @@ const DIFF_NOISY_THRESHOLD = 50;
441
574
  * error?: string
442
575
  * }}
443
576
  */
444
- function mergeEvolved(destino, origen, versionNueva) {
577
+ function mergeEvolved(destino, origen, versionNueva, opts = {}) {
445
578
  try {
446
579
  if (!fs.existsSync(destino) || !fs.existsSync(origen)) {
447
580
  return { merged: false, error: 'Archivo destino u origen no existe' };
@@ -478,11 +611,31 @@ function mergeEvolved(destino, origen, versionNueva) {
478
611
  }
479
612
  }
480
613
 
481
- const diffPath = destino.replace(/\.md$/, '.evolved-diff.txt');
482
- // Legacy: versiones previas escribían el diff como `.evolved-diff.md`, que
483
- // el harness indexaba como slash-command. Se limpia siempre que se toca el
484
- // componente, exista o no divergencia nueva.
614
+ // Diff centralizado (Fase 16, Punto 2): si opts.diffDir está presente, el
615
+ // diff se escribe en un directorio único de reconciliación (p. ej.
616
+ // `.planning/evolution/reconcile/`) con nombre plano, en vez de adyacente al
617
+ // componente. Esto evita ruido en `commands/`/`skills/` (donde scanners y el
618
+ // harness podrían detectarlo) y da una superficie única de revisión para
619
+ // decidir incorporación upstream. Si no, fallback adyacente (legacy).
620
+ const adyacenteTxt = destino.replace(/\.md$/, '.evolved-diff.txt');
485
621
  const diffPathLegacy = destino.replace(/\.md$/, '.evolved-diff.md');
622
+ let diffPath = adyacenteTxt;
623
+ if (opts.diffDir) {
624
+ try { fs.mkdirSync(opts.diffDir, { recursive: true }); } catch { /* best-effort */ }
625
+ // Nombre plano con el directorio padre como prefijo (layout-agnóstico:
626
+ // sirve para runtime `agents/skills/commands/rules` y fuente
627
+ // `agentes/habilidades/comandos/reglas`). Evita colisión entre dominios
628
+ // (p. ej. agents/foo.md vs commands/foo.md → 'agents__foo' vs 'commands__foo').
629
+ const segs = destino.replace(/\\/g, '/').split('/').filter(Boolean);
630
+ const esSkill = segs[segs.length - 1] === 'SKILL.md';
631
+ const baseNombre = esSkill
632
+ ? `${segs[segs.length - 3] || 'x'}__${segs[segs.length - 2]}__SKILL`
633
+ : `${segs[segs.length - 2] || 'x'}__${path.basename(segs[segs.length - 1], '.md')}`;
634
+ const nombrePlano = baseNombre.replace(/[^A-Za-z0-9_.-]/g, '_');
635
+ diffPath = path.join(opts.diffDir, nombrePlano + '.evolved-diff.txt');
636
+ // Limpiar cualquier diff adyacente previo (de versiones que no centralizaban).
637
+ if (fs.existsSync(adyacenteTxt)) { try { fs.unlinkSync(adyacenteTxt); } catch { /* best-effort */ } }
638
+ }
486
639
  const limpiarLegacy = () => {
487
640
  if (fs.existsSync(diffPathLegacy)) {
488
641
  try { fs.unlinkSync(diffPathLegacy); return true; } catch { /* best-effort */ }
@@ -515,6 +668,9 @@ function mergeEvolved(destino, origen, versionNueva) {
515
668
  rounds: evo.metadata.evolvedRounds ? parseInt(evo.metadata.evolvedRounds, 10) : undefined,
516
669
  score: evo.metadata.evolvedScore,
517
670
  note: `Re-aplicado desde v${evo.metadata.evolvedFrom || '?'} tras actualización a v${versionNueva}`,
671
+ // mergeEvolved solo se invoca para evolución del usuario (población A):
672
+ // decideUpdateStrategy enruta shipped (B) a 'overwrite', nunca aquí.
673
+ origin: evo.metadata.evolvedOrigin || 'user',
518
674
  force: true,
519
675
  });
520
676
  return { merged: marked.marked, cleanedDiff };
@@ -85,10 +85,10 @@ function hooksDisparados(sessionId) {
85
85
  try {
86
86
  const planningDir = path.join(process.cwd(), '.planning');
87
87
  const jsonlCandidatos = [
88
- ['evolucion/nudges.jsonl', 'nudge-tracker'],
89
- ['auto-evolucion/agentes.jsonl', 'auto-evolucion'],
90
- ['perfil-usuario/dirty.json', 'actualizar-perfil-usuario'],
91
- ['evolucion/metricas.json', 'metricas-evolucion'],
88
+ ['evolution/nudges.jsonl', 'nudge-tracker'],
89
+ ['auto-evolution/agentes.jsonl', 'auto-evolucion'],
90
+ ['user-profile/dirty.json', 'actualizar-perfil-usuario'],
91
+ ['evolution/metricas.json', 'metricas-evolucion'],
92
92
  ];
93
93
  for (const [rel, nombre] of jsonlCandidatos) {
94
94
  const p = path.join(planningDir, rel);
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # swl-ses (@saulwade/swl-ses)
2
2
 
3
- > Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. 60 agentes, 182 habilidades, 44 comandos, 37 reglas base y 48 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 2.2.0.
3
+ > Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. 60 agentes, 182 habilidades, 44 comandos, 37 reglas base y 48 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 2.2.3.
4
4
 
5
5
  Archivo generado por `node scripts/generar-inventario.js` — no editar a mano. Las cifras se sincronizan con INVENTARIO.md en cada regeneración.
6
6