@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.
- package/CLAUDE.md +13 -2
- package/README.md +3 -3
- package/agentes/revisor-codigo-swl.md +88 -36
- package/bin/swl-mcp-server.js +187 -0
- package/habilidades/benchmark-memoria/SKILL.md +186 -0
- package/habilidades/contenedores-docker/SKILL.md +8 -1
- package/habilidades/datos-etl/SKILL.md +18 -1
- package/habilidades/doubt-driven-review/SKILL.md +171 -0
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -0
- package/habilidades/eval-framework/SKILL.md +212 -0
- package/habilidades/memoria-busqueda/SKILL.md +24 -1
- package/habilidades/meta-skills-estandar/SKILL.md +4 -0
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -0
- package/habilidades/planear-fase/SKILL.md +299 -269
- package/habilidades/postgresql-experto/SKILL.md +24 -1
- package/habilidades/verificar-trabajo/SKILL.md +7 -1
- package/hooks/lib/evolution-tracker.js +65 -11
- package/hooks/lib/memory-search.js +44 -13
- package/hooks/sugerir-contribuir.js +226 -0
- package/manifiestos/hooks-config.json +9 -0
- package/manifiestos/modulos.json +35 -2
- package/manifiestos/perfiles.json +2 -1
- package/package.json +6 -3
- package/plugin.json +343 -343
- package/reglas/skills-estandar.md +3 -0
- package/scripts/benchmark-memoria.js +167 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -0
- package/scripts/generar-checklists-consolidados.js +273 -0
- package/scripts/lib/benchmark-metrics.js +160 -0
- package/scripts/lib/eval-metrics-store.js +218 -0
- package/scripts/lib/eval-quality.js +171 -0
- package/scripts/lib/eval-schemas.js +144 -0
- package/scripts/lib/eval-self-correct.js +106 -0
- package/scripts/lib/eval-validator.js +185 -0
- package/scripts/lib/jaccard-similarity.js +98 -0
- package/scripts/lib/longmemeval-runner.js +125 -0
- package/scripts/lib/rrf-fusion.js +175 -0
- package/scripts/lib/scoring-instintos.js +40 -3
- package/scripts/mcp-server/README.md +128 -0
- package/scripts/mcp-server/handlers.js +206 -0
- 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.
|
|
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
|
-
* @
|
|
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
|
-
|
|
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(
|
|
210
|
+
.split(/\r?\n/)
|
|
161
211
|
.filter(line => !line.match(/^evolved[-\w]*:/))
|
|
162
|
-
.join(
|
|
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() +
|
|
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(
|
|
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(
|
|
329
|
-
const destinoLines = destinoSinEvo.split(
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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",
|
package/manifiestos/modulos.json
CHANGED
|
@@ -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",
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulwade/swl-ses",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes, 151 habilidades, 42 comandos, 64 reglas y
|
|
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
|
},
|