@saulwade/swl-ses 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +3 -3
- 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/eval-framework/SKILL.md +212 -0
- package/habilidades/memoria-busqueda/SKILL.md +24 -1
- 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 +33 -1
- package/manifiestos/perfiles.json +2 -1
- package/package.json +4 -3
- package/plugin.json +343 -343
- package/scripts/benchmark-memoria.js +167 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -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: postgresql-experto
|
|
3
3
|
description: PostgreSQL avanzado. JSONB, arrays, tipos personalizados, búsqueda de texto completo, window functions, CTEs recursivos, Row Level Security y funciones almacenadas.
|
|
4
|
-
version: "1.
|
|
4
|
+
version: "1.1.0"
|
|
5
|
+
evolved: true
|
|
6
|
+
evolved-from: "1.0.0"
|
|
7
|
+
evolved-at: "2026-05-05"
|
|
8
|
+
evolved-by: "aprender"
|
|
9
|
+
evolved-note: "3 gotchas nuevos de la sesión SIGM 2026-05-05: RLS bypass por superusuarios, UUIDs hex en seeds, consultar enum_range antes de seedear (L2/L4/L8)"
|
|
5
10
|
herramientasPermitidas: [Read, Grep]
|
|
6
11
|
exclusiones:
|
|
7
12
|
- "No cargar para optimización de queries SQL (EXPLAIN ANALYZE, índices, partitioning) — para optimización cargar `sql-optimizacion`."
|
|
@@ -144,6 +149,24 @@ ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
|
|
|
144
149
|
|
|
145
150
|
**`SET LOCAL app.empresa_id` para RLS no persiste fuera de una transacción**: `SET LOCAL` establece la variable solo para la transacción actual — si se ejecuta fuera de `BEGIN/COMMIT`, tiene el mismo efecto que `SET` (sesión completa). En aplicaciones con pooling (PgBouncer en transaction mode), la sesión se reutiliza entre conexiones y la variable puede persistir de una petición anterior. Causa: el pool de conexiones reutiliza sessions sin limpiar el estado. Fix: usar SIEMPRE `SET LOCAL` dentro de una transacción explícita (`async with session.begin()`) y verificar que el pool esté en transaction mode.
|
|
146
151
|
|
|
152
|
+
**RLS NO se aplica a superusuarios — verificar siempre con rol app-only**: `FORCE ROW LEVEL SECURITY` hace que las policies apliquen al OWNER de la tabla, pero los superusuarios SIEMPRE bypassean RLS. En imágenes oficiales (`postgis/postgis`, `postgres`), el rol que se crea por defecto (con `POSTGRES_USER`) es superusuario. Caso real: `SET app.tenant_id = COAT` mostraba 20 predios del tenant MINA porque la query corría como `sigm` superuser. Fix: para verificar aislamiento RLS o exponer al backend de producción, crear un rol app-only sin BYPASSRLS:
|
|
153
|
+
```sql
|
|
154
|
+
CREATE ROLE sigm_app NOLOGIN;
|
|
155
|
+
GRANT USAGE ON SCHEMA <s> TO sigm_app;
|
|
156
|
+
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA <s> TO sigm_app;
|
|
157
|
+
SET ROLE sigm_app; -- ahora RLS aplica
|
|
158
|
+
```
|
|
159
|
+
NUNCA usar el rol propietario del schema (suele ser superuser) como rol de aplicación.
|
|
160
|
+
|
|
161
|
+
**UUIDs deterministas en seeds DEBEN usar SOLO chars hex (0-9, a-f)**: la convención común de "prefijo legible" (`vut00000-...` para valores unitarios terreno, `z0000000-...` para zonas, `tv000000-...` para tipos vialidad) produce strings inválidos como UUID. PostgreSQL rechaza con `invalid input syntax for type uuid: "vut00000-..."`. Detectado en sesión 2026-05-04 con 30+ UUIDs inválidos cascadeando errores en seeds dependientes. Fix: usar dígitos hex como prefijo discriminador (`30000000-` zonas, `40000000-` manzanas, `b0000000-` colonias) o usar `gen_random_uuid()` y resolver vínculos via subqueries (`(SELECT id FROM ... WHERE clave = 'X')`).
|
|
162
|
+
|
|
163
|
+
**Enum types: nunca presumir variantes — consultar `enum_range` antes de seedear**: PostgreSQL crea tipos enum con variantes específicas que pueden diferir del nombre de dominio. Ejemplos reales: `tipo_suelo` no tiene `'rústico'` (con acento) sino `'rural'`; `tipo_estatus_medidor` no tiene `'funcionando'` sino `'funcional'`. Fix: antes de incluir un valor literal en seed o INSERT, ejecutar:
|
|
164
|
+
```sql
|
|
165
|
+
SELECT enum_range(NULL::<schema>.<tipo>);
|
|
166
|
+
-- {funcional,descompuesto,sin_medidor,retirado}
|
|
167
|
+
```
|
|
168
|
+
Si el valor que necesitas no existe, agregar la variante con `ALTER TYPE ... ADD VALUE` en una migración separada — no improvisar en seeds.
|
|
169
|
+
|
|
147
170
|
**GIN index en columna JSONB no se usa con el operador `->>` en una cláusula WHERE**: `WHERE metadata ->> 'marca' = 'Dell'` extrae texto y compara — este operador no usa el índice GIN. Solo los operadores `@>`, `?`, `?|`, `?&` usan el índice GIN. Causa: `->>` convierte JSONB a texto y la comparación es una operación de texto sin índice JSONB. Fix: para búsquedas de igualdad con índice, usar `WHERE metadata @> '{"marca": "Dell"}'` que sí usa el índice GIN, o crear un índice de expresión B-Tree sobre `(metadata ->> 'marca')`.
|
|
148
171
|
|
|
149
172
|
**`FORCE ROW LEVEL SECURITY` no protege al usuario dueño de la tabla (superuser/owner)**: el propietario de la tabla y los superusuarios de PostgreSQL bypasean RLS por defecto aunque esté `FORCE` habilitado. Causa: `FORCE` aplica a todos los usuarios excepto al dueño de la tabla y a superusuarios. Fix: si la aplicación conecta como el dueño de la tabla, cambiar el rol de conexión a un rol de aplicación sin privilegios de dueño (`GRANT CONNECT ON DATABASE ... TO app_user`), no usar el usuario `postgres` para conexiones de aplicación.
|
|
@@ -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,6 +727,8 @@
|
|
|
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",
|
|
@@ -954,6 +956,7 @@
|
|
|
954
956
|
"hooks/inbox-aviso.js",
|
|
955
957
|
"hooks/aiisms-detector.js",
|
|
956
958
|
"hooks/sugerir-regenerar-inventario.js",
|
|
959
|
+
"hooks/sugerir-contribuir.js",
|
|
957
960
|
"hooks/_run-hook.sh",
|
|
958
961
|
"hooks/lib/fingerprint-id.js",
|
|
959
962
|
"hooks/lib/token-budget.js",
|
|
@@ -1001,7 +1004,19 @@
|
|
|
1001
1004
|
"scripts/lib/scoring-instintos.js",
|
|
1002
1005
|
"scripts/lib/budget-enforcer.js",
|
|
1003
1006
|
"scripts/lib/semantic-search.js",
|
|
1004
|
-
"scripts/lib/diary-entry.js"
|
|
1007
|
+
"scripts/lib/diary-entry.js",
|
|
1008
|
+
"scripts/lib/rrf-fusion.js",
|
|
1009
|
+
"scripts/lib/jaccard-similarity.js",
|
|
1010
|
+
"scripts/detectar-aprendizajes-duplicados.js",
|
|
1011
|
+
"scripts/lib/eval-schemas.js",
|
|
1012
|
+
"scripts/lib/eval-validator.js",
|
|
1013
|
+
"scripts/lib/eval-quality.js",
|
|
1014
|
+
"scripts/lib/eval-self-correct.js",
|
|
1015
|
+
"scripts/lib/eval-metrics-store.js",
|
|
1016
|
+
"scripts/run-eval.js",
|
|
1017
|
+
"scripts/lib/benchmark-metrics.js",
|
|
1018
|
+
"scripts/lib/longmemeval-runner.js",
|
|
1019
|
+
"scripts/benchmark-memoria.js"
|
|
1005
1020
|
],
|
|
1006
1021
|
"targets": [
|
|
1007
1022
|
"claude",
|
|
@@ -1120,6 +1135,23 @@
|
|
|
1120
1135
|
"gemini"
|
|
1121
1136
|
]
|
|
1122
1137
|
},
|
|
1138
|
+
"mcp-server-swl": {
|
|
1139
|
+
"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.",
|
|
1140
|
+
"tipo": "scripts",
|
|
1141
|
+
"archivos": [
|
|
1142
|
+
"bin/swl-mcp-server.js",
|
|
1143
|
+
"scripts/mcp-server/handlers.js",
|
|
1144
|
+
"scripts/mcp-server/README.md"
|
|
1145
|
+
],
|
|
1146
|
+
"targets": [
|
|
1147
|
+
"claude",
|
|
1148
|
+
"openclaude",
|
|
1149
|
+
"copilot",
|
|
1150
|
+
"opencode",
|
|
1151
|
+
"codex",
|
|
1152
|
+
"gemini"
|
|
1153
|
+
]
|
|
1154
|
+
},
|
|
1123
1155
|
"rotacion-audit": {
|
|
1124
1156
|
"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
1157
|
"tipo": "scripts",
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulwade/swl-ses",
|
|
3
|
-
"version": "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.0",
|
|
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",
|