@saulwade/swl-ses 1.1.4 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CLAUDE.md +13 -2
  2. package/README.md +3 -3
  3. package/agentes/revisor-codigo-swl.md +88 -36
  4. package/bin/swl-mcp-server.js +187 -0
  5. package/habilidades/benchmark-memoria/SKILL.md +186 -0
  6. package/habilidades/contenedores-docker/SKILL.md +8 -1
  7. package/habilidades/datos-etl/SKILL.md +18 -1
  8. package/habilidades/doubt-driven-review/SKILL.md +171 -0
  9. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -0
  10. package/habilidades/eval-framework/SKILL.md +212 -0
  11. package/habilidades/memoria-busqueda/SKILL.md +24 -1
  12. package/habilidades/meta-skills-estandar/SKILL.md +4 -0
  13. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -0
  14. package/habilidades/planear-fase/SKILL.md +299 -269
  15. package/habilidades/postgresql-experto/SKILL.md +24 -1
  16. package/habilidades/verificar-trabajo/SKILL.md +7 -1
  17. package/hooks/lib/evolution-tracker.js +65 -11
  18. package/hooks/lib/memory-search.js +44 -13
  19. package/hooks/sugerir-contribuir.js +226 -0
  20. package/manifiestos/hooks-config.json +9 -0
  21. package/manifiestos/modulos.json +35 -2
  22. package/manifiestos/perfiles.json +2 -1
  23. package/package.json +6 -3
  24. package/plugin.json +343 -343
  25. package/reglas/skills-estandar.md +3 -0
  26. package/scripts/benchmark-memoria.js +167 -0
  27. package/scripts/detectar-aprendizajes-duplicados.js +151 -0
  28. package/scripts/generar-checklists-consolidados.js +273 -0
  29. package/scripts/lib/benchmark-metrics.js +160 -0
  30. package/scripts/lib/eval-metrics-store.js +218 -0
  31. package/scripts/lib/eval-quality.js +171 -0
  32. package/scripts/lib/eval-schemas.js +144 -0
  33. package/scripts/lib/eval-self-correct.js +106 -0
  34. package/scripts/lib/eval-validator.js +185 -0
  35. package/scripts/lib/jaccard-similarity.js +98 -0
  36. package/scripts/lib/longmemeval-runner.js +125 -0
  37. package/scripts/lib/rrf-fusion.js +175 -0
  38. package/scripts/lib/scoring-instintos.js +40 -3
  39. package/scripts/mcp-server/README.md +128 -0
  40. package/scripts/mcp-server/handlers.js +206 -0
  41. package/scripts/run-eval.js +141 -0
@@ -1,7 +1,12 @@
1
1
  ---
2
2
  name: verificar-trabajo
3
3
  description: Verificación goal-backward del trabajo ejecutado en 4 niveles progresivos — EXISTE, SUSTANTIVO, CONECTADO, DATOS_FLUYEN. 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
- version: "1.1.0"
4
+ version: "1.1.1"
5
+ evolved: true
6
+ evolved-from: "1.1.0"
7
+ evolved-at: "2026-05-05"
8
+ evolved-by: "aprender"
9
+ evolved-note: "Gotcha de la sesión SIGM 2026-05-05 (L7): tests + linter no detectan schema-seed drift; cuando el alcance toca BD, Nivel 4 obligatorio con docker compose down -v && up fresco"
5
10
  herramientasPermitidas: [Read, Write, Edit, Bash, Glob, Grep]
6
11
  exclusiones:
7
12
  - "No cargar durante la implementación activa de una tarea; la verificación es posterior a la implementación, no concurrente."
@@ -290,6 +295,7 @@ estructurado JSON, ver [recursos/plantilla-verificacion.md](recursos/plantilla-v
290
295
  - **Score del veredicto calculado solo sobre artefactos verificados, no sobre todos los prometidos**: si el plan prometía 8 entregables pero solo se verificaron 5, el score 1.0 sobre los 5 no es evidencia de que los 8 están correctos. Causa: el agente omite entregables del plan en la lista de artefactos. Solución: antes de verificar, listar todos los artefactos del PLAN.md y verificar cada uno explícitamente — un artefacto no verificado es un Fail implícito.
291
296
  - **Loop de reparación sin re-verificación completa**: tras el fix, el agente re-verifica solo el item que falló en lugar del veredicto completo. Causa: optimización incorrecta para ahorrar tiempo. Solución: el paso 3 del loop de reparación dice explícitamente "re-ejecutar la verificación COMPLETA" — un fix puede resolver un fallo pero introducir otro.
292
297
  - **Hollow Component no detectado en Nivel 2**: el componente Angular tiene template pero no usa ninguna señal del componente. El agente verifica que el archivo existe (Nivel 1) y que tiene contenido (Nivel 2), pero no detecta que el contenido es decorativo. Causa: la definición de "stub" en Nivel 2 no se aplicó al caso de templates sin binding. Solución: verificar explícitamente que el template usa al menos una variable/señal del componente.
298
+ - **Verificación con tests + linter pasa pero al levantar BD fresca todo se rompe**: tests Python con mocks y `ruff check` retornan verde, pero `docker compose down -v && up` falla porque hay drift entre `database/schemas/`, seeds, funciones SQL y vistas. Caso real (SIGM 2026-05-04): primera pasada de `/swl:verificar` reportó "APROBADO 21/22" sin detectar que ~10 seeds tenían columnas obsoletas, UUIDs inválidos y un script `02-crear-buckets-minio.sh` que abortaba initdb del contenedor postgres. Causa: la verificación corre solo contra el código (que mockea BD); nunca aplica el SQL real. Solución: cuando el alcance toca cualquier archivo en `database/schemas/`, `database/seeds/`, `database/functions/` o `database/views/`, ejecutar como Nivel 4 (DATOS_FLUYEN) obligatorio: `docker compose down -v && docker compose up -d db && wait_for_health && docker logs <db_container> | grep -E "ERROR|skipped"`. Cualquier ERROR en logs de init invalida el veredicto Pass.
293
299
 
294
300
  ## Regla de oro del verificador
295
301
 
@@ -72,8 +72,8 @@ function readEvolutionMeta(filePath) {
72
72
  try {
73
73
  const content = fs.readFileSync(filePath, 'utf8');
74
74
 
75
- // Detectar frontmatter YAML (entre --- y ---)
76
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
75
+ // Detectar frontmatter YAML (entre --- y ---). Soporta LF y CRLF.
76
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
77
77
  if (!fmMatch) {
78
78
  // Sin frontmatter — verificar sidecar
79
79
  return _readSidecar(filePath);
@@ -120,6 +120,38 @@ function _readSidecar(filePath) {
120
120
  return { evolved: false, metadata: {} };
121
121
  }
122
122
 
123
+ // ---------------------------------------------------------------------------
124
+ // Detección de CWD == package root (commits del mantenedor)
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Determina si el CWD actual es la raíz del paquete swl-ses.
129
+ *
130
+ * Cuando el desarrollador del paquete edita componentes en su propio repo, no
131
+ * son evoluciones de usuario — son commits normales del mantenedor. Marcar
132
+ * `evolved: true` en esos casos contamina el frontmatter, ensucia el flujo de
133
+ * preserve/conflict en upgrades de usuario, y rompe el invariante de que
134
+ * `evolved: true` significa "este archivo fue modificado por un usuario tras
135
+ * recibirlo del paquete".
136
+ *
137
+ * Heurística: leer `package.json` del cwd; si su campo `name` es
138
+ * `"@saulwade/swl-ses"` (o `"swl-ses"` legacy), estamos en el repo fuente.
139
+ *
140
+ * @param {string} [cwd=process.cwd()]
141
+ * @returns {boolean}
142
+ */
143
+ function isPackageRoot(cwd) {
144
+ const dir = cwd || process.cwd();
145
+ try {
146
+ const pkgPath = path.join(dir, 'package.json');
147
+ if (!fs.existsSync(pkgPath)) return false;
148
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
149
+ return pkg.name === '@saulwade/swl-ses' || pkg.name === 'swl-ses';
150
+ } catch {
151
+ return false;
152
+ }
153
+ }
154
+
123
155
  // ---------------------------------------------------------------------------
124
156
  // Escritura de metadata de evolución
125
157
  // ---------------------------------------------------------------------------
@@ -127,6 +159,11 @@ function _readSidecar(filePath) {
127
159
  /**
128
160
  * Marca un archivo como evolucionado inyectando campos en su frontmatter.
129
161
  *
162
+ * Cuando el CWD es la raíz del paquete swl-ses (commit del mantenedor), NO se
163
+ * inyecta `evolved: true` ni los campos `evolved-*`. El sistema asume que los
164
+ * cambios en el repo fuente son parte del desarrollo normal, no evoluciones
165
+ * de usuario.
166
+ *
130
167
  * @param {string} filePath - Ruta del archivo a marcar.
131
168
  * @param {object} meta
132
169
  * @param {string} meta.from - Versión base (ej: "5.1.0").
@@ -134,16 +171,26 @@ function _readSidecar(filePath) {
134
171
  * @param {number} [meta.rounds] - Rondas de autoresearch.
135
172
  * @param {string} [meta.score] - Score "baseline% → final%".
136
173
  * @param {string} [meta.note] - Descripción del cambio.
137
- * @returns {{ marked: boolean, error?: string }}
174
+ * @param {boolean} [meta.force] - Forzar marcado aunque CWD == package root (ej: tests).
175
+ * @returns {{ marked: boolean, skipped?: boolean, reason?: string, error?: string }}
138
176
  */
139
177
  function markAsEvolved(filePath, meta) {
140
178
  if (!fs.existsSync(filePath)) {
141
179
  return { marked: false, error: 'Archivo no existe' };
142
180
  }
143
181
 
182
+ if (!meta?.force && isPackageRoot()) {
183
+ return {
184
+ marked: false,
185
+ skipped: true,
186
+ reason: 'CWD es la raíz del paquete swl-ses — los cambios del mantenedor no se marcan como evolved',
187
+ };
188
+ }
189
+
144
190
  try {
145
191
  const content = fs.readFileSync(filePath, 'utf8');
146
- const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
192
+ // Soportar LF y CRLF en line endings.
193
+ const fmMatch = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---)/);
147
194
 
148
195
  if (!fmMatch) {
149
196
  // Sin frontmatter — usar sidecar
@@ -155,11 +202,14 @@ function markAsEvolved(filePath, meta) {
155
202
  const suffix = fmMatch[3];
156
203
  const rest = content.slice(fmMatch[0].length);
157
204
 
205
+ // Detectar EOL del archivo para preservarlo en la escritura.
206
+ const eol = prefix.includes('\r\n') ? '\r\n' : '\n';
207
+
158
208
  // Remover campos evolved previos
159
209
  frontmatter = frontmatter
160
- .split('\n')
210
+ .split(/\r?\n/)
161
211
  .filter(line => !line.match(/^evolved[-\w]*:/))
162
- .join('\n');
212
+ .join(eol);
163
213
 
164
214
  // Agregar campos nuevos
165
215
  const date = new Date().toISOString().split('T')[0];
@@ -173,7 +223,7 @@ function markAsEvolved(filePath, meta) {
173
223
  if (meta.score) newFields.push(`evolved-score: "${meta.score}"`);
174
224
  if (meta.note) newFields.push(`evolved-note: "${meta.note}"`);
175
225
 
176
- frontmatter = frontmatter.trimEnd() + '\n' + newFields.join('\n');
226
+ frontmatter = frontmatter.trimEnd() + eol + newFields.join(eol);
177
227
 
178
228
  const newContent = prefix + frontmatter + suffix + rest;
179
229
  atomicWriteSync(filePath, newContent, 'utf8');
@@ -284,11 +334,13 @@ function decideUpdateStrategy(destino, origen, versionNueva) {
284
334
 
285
335
  /**
286
336
  * Remueve campos de evolución del frontmatter para comparación limpia.
337
+ * Normaliza CRLF a LF para que el contenido se pueda comparar
338
+ * independientemente del sistema operativo en que se generó.
287
339
  * @private
288
340
  */
289
341
  function _stripEvolutionFields(content) {
290
342
  return content
291
- .split('\n')
343
+ .split(/\r?\n/)
292
344
  .filter(line => !line.match(/^evolved[-\w]*:/))
293
345
  .join('\n');
294
346
  }
@@ -323,10 +375,11 @@ function mergeEvolved(destino, origen, versionNueva) {
323
375
  const destinoContent = fs.readFileSync(destino, 'utf8');
324
376
  const origenContent = fs.readFileSync(origen, 'utf8');
325
377
 
326
- // Extraer las líneas que son diferentes entre destino (sin evolved fields) y origen
378
+ // Extraer las líneas que son diferentes entre destino (sin evolved fields) y origen.
379
+ // Normalizar CRLF a LF para comparar independientemente del SO de origen.
327
380
  const destinoSinEvo = _stripEvolutionFields(destinoContent);
328
- const origenLines = origenContent.split('\n');
329
- const destinoLines = destinoSinEvo.split('\n');
381
+ const origenLines = origenContent.split(/\r?\n/);
382
+ const destinoLines = destinoSinEvo.split(/\r?\n/);
330
383
 
331
384
  const diffs = [];
332
385
  const maxLen = Math.max(origenLines.length, destinoLines.length);
@@ -445,6 +498,7 @@ module.exports = {
445
498
  decideUpdateStrategy,
446
499
  mergeEvolved,
447
500
  scanEvolved,
501
+ isPackageRoot,
448
502
  EVOLUTION_FIELDS,
449
503
  EVOLUTION_SOURCES,
450
504
  SIDECAR_FILENAME,
@@ -25,6 +25,16 @@ const path = require('path');
25
25
 
26
26
  const sessionFts = require('./session-fts');
27
27
 
28
+ // RRF fusion para combinar streams heterogéneos. Carga defensiva: si la lib no
29
+ // está disponible (ej. instalación parcial), `search()` cae al merge por
30
+ // relevancia simple — backward compatible.
31
+ let rrfFusion = null;
32
+ try {
33
+ ({ rrfFusion } = require('../../scripts/lib/rrf-fusion'));
34
+ } catch (_) {
35
+ rrfFusion = null;
36
+ }
37
+
28
38
  // ---------------------------------------------------------------------------
29
39
  // Constantes
30
40
  // ---------------------------------------------------------------------------
@@ -321,16 +331,39 @@ function search(baseDir, query, filtros = {}) {
321
331
  ? filtros.limit
322
332
  : 20;
323
333
 
324
- let resultados = [];
325
-
326
- if (!filtros.tipo || filtros.tipo === 'aprendizaje') {
327
- resultados.push(...buscarEnAprendizajes(baseDir, terminos));
328
- }
329
- if (!filtros.tipo || filtros.tipo === 'sesion') {
330
- resultados.push(...buscarEnSesiones(baseDir, terminos));
331
- }
332
- if (!filtros.tipo || filtros.tipo === 'instinto') {
333
- resultados.push(...buscarEnInstintos(baseDir, terminos));
334
+ // Cada fuente devuelve sus resultados ordenados por relevancia interna.
335
+ // Conservamos los streams separados para que RRF los combine usando rank,
336
+ // no la magnitud de la relevancia (que no es comparable entre fuentes).
337
+ const stream_aprendizajes = (!filtros.tipo || filtros.tipo === 'aprendizaje')
338
+ ? buscarEnAprendizajes(baseDir, terminos).sort((a, b) => b.relevancia - a.relevancia)
339
+ : [];
340
+ const stream_sesiones = (!filtros.tipo || filtros.tipo === 'sesion')
341
+ ? buscarEnSesiones(baseDir, terminos).sort((a, b) => b.relevancia - a.relevancia)
342
+ : [];
343
+ const stream_instintos = (!filtros.tipo || filtros.tipo === 'instinto')
344
+ ? buscarEnInstintos(baseDir, terminos).sort((a, b) => b.relevancia - a.relevancia)
345
+ : [];
346
+
347
+ let resultados;
348
+
349
+ if (rrfFusion && (stream_aprendizajes.length + stream_sesiones.length + stream_instintos.length) > 0) {
350
+ // RRF fusion: combina streams heterogéneos usando posición (rank), no
351
+ // magnitud de relevancia. Los pesos reflejan la utilidad relativa
352
+ // observada empíricamente:
353
+ // - Aprendizajes (0.4): conocimiento curado, mayor señal por entrada.
354
+ // - Sesiones (0.4): contexto operativo, alta densidad pero ruidoso.
355
+ // - Instintos (0.2): patrones consolidados, pocos pero específicos.
356
+ resultados = rrfFusion(
357
+ [stream_aprendizajes, stream_sesiones, stream_instintos],
358
+ { weights: [0.4, 0.4, 0.2] },
359
+ );
360
+ } else {
361
+ // Fallback sin RRF: merge por relevancia simple (comportamiento legado).
362
+ resultados = [
363
+ ...stream_aprendizajes,
364
+ ...stream_sesiones,
365
+ ...stream_instintos,
366
+ ].sort((a, b) => b.relevancia - a.relevancia);
334
367
  }
335
368
 
336
369
  // Filtros de fecha (solo sobre resultados con fecha real)
@@ -345,9 +378,7 @@ function search(baseDir, query, filtros = {}) {
345
378
  );
346
379
  }
347
380
 
348
- return resultados
349
- .sort((a, b) => b.relevancia - a.relevancia)
350
- .slice(0, limit);
381
+ return resultados.slice(0, limit);
351
382
  }
352
383
 
353
384
  // ---------------------------------------------------------------------------
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: sugerir-contribuir.js
6
+ * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
7
+ *
8
+ * Cuando el usuario edita un componente con frontmatter `evolved: true` en su
9
+ * instalación de SWL (no en el repo fuente), sugiere — sin bloquear — abrir
10
+ * `/swl:contribuir <skill>` para subir la mejora al core via PR.
11
+ *
12
+ * Origen: durante v1.2.0 detectamos que las evoluciones locales quedaban
13
+ * atrapadas en una sola máquina porque `markAsEvolved` marca pero no notifica.
14
+ * Sin canal de "subida", una mejora de un usuario nunca llega al paquete y se
15
+ * pierde para todos los demás equipos. Este nudge cierra ese ciclo.
16
+ *
17
+ * Reglas de activación:
18
+ * - El archivo está en agentes/, habilidades/, comandos/swl/ o reglas/
19
+ * - El archivo tiene `evolved: true` en frontmatter (o sidecar .evolved.json)
20
+ * - El CWD NO es la raíz del paquete swl-ses (un mantenedor editando el
21
+ * repo fuente NO debe recibir el nudge — sus cambios van por PR normal)
22
+ * - No estamos dentro del cooldown de 24h para este archivo
23
+ *
24
+ * Cooldown: 24h por archivo. Estado en
25
+ * `.planning/comms/sugerir-contribuir-cooldown.json`.
26
+ *
27
+ * Resultado:
28
+ * - Cumple condiciones → sugerencia en stdout (exit 0)
29
+ * - No cumple → sin output (exit 0)
30
+ * - Error interno → sin output (nunca bloquear)
31
+ *
32
+ * Activo por defecto. Para silenciar: SWL_SUGERIR_CONTRIBUIR=0.
33
+ */
34
+
35
+ const fs = require('fs');
36
+ const path = require('path');
37
+
38
+ let isPackageRoot, readEvolutionMeta;
39
+ try {
40
+ ({ isPackageRoot, readEvolutionMeta } = require('./lib/evolution-tracker'));
41
+ } catch {
42
+ // Fallback si el módulo no carga: nunca bloquear, no emitir
43
+ isPackageRoot = () => false;
44
+ readEvolutionMeta = () => ({ evolved: false, metadata: {} });
45
+ }
46
+
47
+ const CARPETAS_EVOLUCIONABLES = [
48
+ 'agentes/',
49
+ 'habilidades/',
50
+ 'comandos/swl/',
51
+ 'reglas/',
52
+ ];
53
+
54
+ const STATE_FILE = path.join(
55
+ process.cwd(),
56
+ '.planning',
57
+ 'comms',
58
+ 'sugerir-contribuir-cooldown.json'
59
+ );
60
+ const COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24h
61
+
62
+ function normalizarSeparadores(p) {
63
+ return String(p).replace(/\\/g, '/');
64
+ }
65
+
66
+ function rutaRelativaAlProyecto(rutaRaw) {
67
+ if (!rutaRaw) return null;
68
+ let normalizada = normalizarSeparadores(rutaRaw);
69
+
70
+ if (path.isAbsolute(normalizada)) {
71
+ const cwd = normalizarSeparadores(process.cwd());
72
+ if (normalizada.toLowerCase().startsWith(cwd.toLowerCase())) {
73
+ normalizada = normalizada.slice(cwd.length).replace(/^\/+/, '');
74
+ } else {
75
+ return null;
76
+ }
77
+ } else {
78
+ normalizada = normalizada.replace(/^\.\//, '');
79
+ }
80
+
81
+ return normalizada;
82
+ }
83
+
84
+ function esEvolucionable(rutaRelativa) {
85
+ if (!rutaRelativa) return false;
86
+ if (!rutaRelativa.endsWith('.md')) return false;
87
+ return CARPETAS_EVOLUCIONABLES.some(c => rutaRelativa.startsWith(c));
88
+ }
89
+
90
+ function nombreSkillDesdeRuta(rutaRelativa) {
91
+ // habilidades/postgresql-experto/SKILL.md → postgresql-experto
92
+ // agentes/orquestador-swl.md → orquestador-swl
93
+ // comandos/swl/release.md → /swl:release
94
+ // reglas/seguridad.md → reglas/seguridad
95
+ if (rutaRelativa.startsWith('habilidades/')) {
96
+ const rest = rutaRelativa.slice('habilidades/'.length);
97
+ return rest.split('/')[0];
98
+ }
99
+ if (rutaRelativa.startsWith('agentes/')) {
100
+ return path.basename(rutaRelativa, '.md');
101
+ }
102
+ if (rutaRelativa.startsWith('comandos/swl/')) {
103
+ return '/swl:' + path.basename(rutaRelativa, '.md');
104
+ }
105
+ if (rutaRelativa.startsWith('reglas/')) {
106
+ return rutaRelativa.replace(/\.md$/, '');
107
+ }
108
+ return path.basename(rutaRelativa, '.md');
109
+ }
110
+
111
+ function leerEstado() {
112
+ try {
113
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
114
+ } catch {
115
+ return {};
116
+ }
117
+ }
118
+
119
+ function escribirEstado(estado) {
120
+ try {
121
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
122
+ const tmp = STATE_FILE + '.tmp';
123
+ fs.writeFileSync(tmp, JSON.stringify(estado, null, 2), 'utf8');
124
+ fs.renameSync(tmp, STATE_FILE);
125
+ } catch {
126
+ // Falla silenciosa — cooldown no es crítico
127
+ }
128
+ }
129
+
130
+ function dentroDeCooldown(estado, ruta) {
131
+ const ultima = estado[ruta];
132
+ if (!ultima) return false;
133
+ return Date.now() - ultima < COOLDOWN_MS;
134
+ }
135
+
136
+ function marcarAviso(estado, ruta) {
137
+ estado[ruta] = Date.now();
138
+ escribirEstado(estado);
139
+ }
140
+
141
+ function construirMensaje(rutaRelativa, metadata) {
142
+ const skill = nombreSkillDesdeRuta(rutaRelativa);
143
+ const desde = metadata.evolvedFrom || '?';
144
+ const por = metadata.evolvedBy || 'auto-evolución';
145
+ const nota = metadata.evolvedNote ? `\n Nota: ${metadata.evolvedNote}` : '';
146
+
147
+ return (
148
+ '\n[swl-ses] Detecté una evolución local en `' + rutaRelativa + '` ' +
149
+ '(desde v' + desde + ' por ' + por + ').' + nota + '\n' +
150
+ ' Si la mejora aplica a ingeniería de software general, considera contribuirla:\n' +
151
+ ' /swl:contribuir ' + skill + '\n' +
152
+ ' Eso valida con PluginEval (≥80) y abre PR al core para que otros equipos\n' +
153
+ ' reciban el cambio en el siguiente release.\n' +
154
+ ' (silenciar con SWL_SUGERIR_CONTRIBUIR=0; cooldown 24h por archivo)\n'
155
+ );
156
+ }
157
+
158
+ function extraerRuta(toolInput) {
159
+ if (!toolInput || typeof toolInput !== 'object') return null;
160
+ return (
161
+ toolInput.file_path ||
162
+ toolInput.path ||
163
+ toolInput.notebook_path ||
164
+ null
165
+ );
166
+ }
167
+
168
+ function evaluar(toolName, toolInput) {
169
+ if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) return null;
170
+ if (isPackageRoot()) return null; // Mantenedor editando el repo: nunca sugerir
171
+
172
+ const rutaRaw = extraerRuta(toolInput);
173
+ const ruta = rutaRelativaAlProyecto(rutaRaw);
174
+ if (!ruta) return null;
175
+ if (!esEvolucionable(ruta)) return null;
176
+
177
+ const rutaAbsoluta = path.isAbsolute(rutaRaw) ? rutaRaw : path.join(process.cwd(), ruta);
178
+ if (!fs.existsSync(rutaAbsoluta)) return null;
179
+
180
+ const evo = readEvolutionMeta(rutaAbsoluta);
181
+ if (!evo.evolved) return null;
182
+
183
+ return { ruta, metadata: evo.metadata };
184
+ }
185
+
186
+ function ejecutarComoHook() {
187
+ let inputRaw = '';
188
+
189
+ process.stdin.on('data', chunk => { inputRaw += chunk; });
190
+
191
+ process.stdin.on('end', () => {
192
+ try {
193
+ if (process.env.SWL_SUGERIR_CONTRIBUIR === '0') return;
194
+
195
+ const data = JSON.parse(inputRaw || '{}');
196
+ const toolName = String(data.tool_name || data.tool?.name || '');
197
+ const toolInput = data.tool_input || data.tool?.input || {};
198
+
199
+ const r = evaluar(toolName, toolInput);
200
+ if (!r) return;
201
+
202
+ const estado = leerEstado();
203
+ if (dentroDeCooldown(estado, r.ruta)) return;
204
+
205
+ process.stdout.write(construirMensaje(r.ruta, r.metadata));
206
+ marcarAviso(estado, r.ruta);
207
+ } catch {
208
+ // Errores internos nunca bloquean
209
+ }
210
+ });
211
+ }
212
+
213
+ if (require.main === module) {
214
+ ejecutarComoHook();
215
+ }
216
+
217
+ module.exports = {
218
+ CARPETAS_EVOLUCIONABLES,
219
+ COOLDOWN_MS,
220
+ rutaRelativaAlProyecto,
221
+ esEvolucionable,
222
+ nombreSkillDesdeRuta,
223
+ dentroDeCooldown,
224
+ construirMensaje,
225
+ evaluar,
226
+ };
@@ -99,6 +99,15 @@
99
99
  "maxConsecutiveFailures": 5,
100
100
  "degradeOnFailure": "skip"
101
101
  },
102
+ "sugerir-contribuir.js": {
103
+ "event": "PostToolUse",
104
+ "matcher": "Write|Edit|MultiEdit",
105
+ "description": "Cuando el usuario edita un componente con frontmatter `evolved: true` en su instalación (no en el repo fuente), sugiere abrir /swl:contribuir para subir la mejora al core. Cooldown 24h por archivo. Activo por defecto; silenciar con SWL_SUGERIR_CONTRIBUIR=0.",
106
+ "blocking": false,
107
+ "async": true,
108
+ "maxConsecutiveFailures": 5,
109
+ "degradeOnFailure": "skip"
110
+ },
102
111
  "degradacion-instintos.js": {
103
112
  "event": "PostToolUse",
104
113
  "matcher": "Bash|Write|Edit",
@@ -727,10 +727,13 @@
727
727
  "habilidades/brainstorming",
728
728
  "habilidades/context-builder",
729
729
  "habilidades/memoria-busqueda",
730
+ "habilidades/eval-framework",
731
+ "habilidades/benchmark-memoria",
730
732
  "habilidades/prevencion-racionalizacion",
731
733
  "habilidades/prevencion-sobreingenieria",
732
734
  "habilidades/privacy-memoria",
733
- "habilidades/verificacion-evidencia"
735
+ "habilidades/verificacion-evidencia",
736
+ "habilidades/doubt-driven-review"
734
737
  ],
735
738
  "targets": [
736
739
  "claude",
@@ -954,6 +957,7 @@
954
957
  "hooks/inbox-aviso.js",
955
958
  "hooks/aiisms-detector.js",
956
959
  "hooks/sugerir-regenerar-inventario.js",
960
+ "hooks/sugerir-contribuir.js",
957
961
  "hooks/_run-hook.sh",
958
962
  "hooks/lib/fingerprint-id.js",
959
963
  "hooks/lib/token-budget.js",
@@ -1001,7 +1005,19 @@
1001
1005
  "scripts/lib/scoring-instintos.js",
1002
1006
  "scripts/lib/budget-enforcer.js",
1003
1007
  "scripts/lib/semantic-search.js",
1004
- "scripts/lib/diary-entry.js"
1008
+ "scripts/lib/diary-entry.js",
1009
+ "scripts/lib/rrf-fusion.js",
1010
+ "scripts/lib/jaccard-similarity.js",
1011
+ "scripts/detectar-aprendizajes-duplicados.js",
1012
+ "scripts/lib/eval-schemas.js",
1013
+ "scripts/lib/eval-validator.js",
1014
+ "scripts/lib/eval-quality.js",
1015
+ "scripts/lib/eval-self-correct.js",
1016
+ "scripts/lib/eval-metrics-store.js",
1017
+ "scripts/run-eval.js",
1018
+ "scripts/lib/benchmark-metrics.js",
1019
+ "scripts/lib/longmemeval-runner.js",
1020
+ "scripts/benchmark-memoria.js"
1005
1021
  ],
1006
1022
  "targets": [
1007
1023
  "claude",
@@ -1120,6 +1136,23 @@
1120
1136
  "gemini"
1121
1137
  ]
1122
1138
  },
1139
+ "mcp-server-swl": {
1140
+ "descripcion": "MCP server stub experimental que expone memoria SWL (aprendizajes, sesiones, instintos) a clientes MCP externos (Cursor, Gemini CLI, OpenCode, Cline, Claude Desktop). Modo stdio. 3 endpoints: swl_memory_search, swl_aprendizajes_recientes, swl_instintos_activos. SIN auth, SIN rate limiting, SIN HTTP transport, SIN tests integración. NO USAR EN PRODUCCIÓN. Trigger para hardening: uso real ≥2 runtimes diferentes consistentemente por ≥1 mes. El binario `swl-mcp-server` se instala automáticamente vía npm install -g (declarado en package.json bin). NO se propaga al runtime SWL — vive en el paquete npm como herramienta opt-in. Ver scripts/mcp-server/README.md para 11 limitaciones explícitas y diseño futuro.",
1141
+ "tipo": "scripts",
1142
+ "archivos": [
1143
+ "bin/swl-mcp-server.js",
1144
+ "scripts/mcp-server/handlers.js",
1145
+ "scripts/mcp-server/README.md"
1146
+ ],
1147
+ "targets": [
1148
+ "claude",
1149
+ "openclaude",
1150
+ "copilot",
1151
+ "opencode",
1152
+ "codex",
1153
+ "gemini"
1154
+ ]
1155
+ },
1123
1156
  "rotacion-audit": {
1124
1157
  "descripcion": "Rotación de logs de auditoría (.planning/audit.jsonl y audit-merkle.jsonl) a archivos .gz mensuales en .planning/archivo/audit/. Política por defecto: entradas con timestamp mayor a 30 días se archivan. Zero-deps (fs/zlib/path), CRLF-safe, preserva cadena Merkle. Ejecución automática vía hook Stop 'rotar-audit-auto.js' cuando algún audit*.jsonl supera 5 MB (cooldown 6h). Ejecución manual vía 'node scripts/rotar-audit-logs.js' con --dry-run/--dias=N opcionales. Desactivable con SWL_AUDIT_ROTATE_OFF=1.",
1125
1158
  "tipo": "scripts",
@@ -397,7 +397,8 @@
397
397
  "hooks-observabilidad",
398
398
  "hooks-multica",
399
399
  "graphify-swl",
400
- "mcp-swl"
400
+ "mcp-swl",
401
+ "mcp-server-swl"
401
402
  ]
402
403
  }
403
404
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@saulwade/swl-ses",
3
- "version": "1.1.4",
4
- "description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes, 151 habilidades, 42 comandos, 64 reglas y 39 hooks. Soporta 11 lenguajes y 5 runtimes: Claude Code, Copilot, OpenCode, Codex y Gemini CLI. 100% en espanol (Mexico). Incluye gateway bidireccional con relay Telegram a Claude Code.",
3
+ "version": "1.2.1",
4
+ "description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes, 151 habilidades, 42 comandos, 64 reglas y 40 hooks. Soporta 11 lenguajes y 5 runtimes: Claude Code, Copilot, OpenCode, Codex y Gemini CLI. 100% en espanol (Mexico). Incluye gateway bidireccional con relay Telegram a Claude Code.",
5
5
  "bin": {
6
6
  "swl-ses": "bin/swl-ses.js",
7
- "swl-telegram-bot": "bin/swl-telegram-bot.js"
7
+ "swl-telegram-bot": "bin/swl-telegram-bot.js",
8
+ "swl-mcp-server": "bin/swl-mcp-server.js"
8
9
  },
9
10
  "files": [
10
11
  "bin",
@@ -41,6 +42,8 @@
41
42
  "publish:npmjs": "node scripts/publicar.js --solo-npmjs",
42
43
  "publish:dry": "node scripts/publicar.js --dry-run",
43
44
  "generate:docs": "node scripts/generar-inventario.js",
45
+ "gen-checklists": "node scripts/generar-checklists-consolidados.js",
46
+ "gen-checklists:check": "node scripts/generar-checklists-consolidados.js --check",
44
47
  "field-report": "node scripts/field-report.js",
45
48
  "configure:branch-protection": "node scripts/configurar-branch-protection.js"
46
49
  },