@saulwade/swl-ses 1.4.1 → 1.5.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 +3 -3
- package/README.md +561 -560
- package/agentes/nemesis-auditor-swl.md +161 -161
- package/bin/swl-mcp-server.js +49 -22
- package/bin/swl-ses.js +74 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/ejecutar-fase.md +33 -4
- package/comandos/swl/metricas.md +72 -0
- package/comandos/swl/nemesis.md +122 -122
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/discutir-fase/SKILL.md +50 -2
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -0
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/protocolo-revision-swl/SKILL.md +276 -0
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +49 -5
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1321 -1262
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +3 -3
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +351 -343
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +31 -4
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/instalador.js +56 -5
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-runtime.js +75 -9
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/estado.js +13 -1
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/expandir-targets.js +71 -0
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +3 -0
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -0
- package/scripts/lib/transformadores/base.js +43 -9
- package/scripts/lib/transformadores/codex.js +375 -115
- package/scripts/lib/transformadores/cursor.js +359 -0
- package/scripts/lib/transformadores/index.js +2 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +122 -80
- package/scripts/mcp-server/auth.js +105 -0
- package/scripts/mcp-server/cache.js +106 -0
- package/scripts/mcp-server/handlers.js +386 -206
- package/scripts/mcp-server/telemetry.js +78 -0
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Telemetría opt-in del swl-mcp-server v1.0.0 (ADR-0019 Sub-fase 3).
|
|
5
|
+
*
|
|
6
|
+
* Cuando `SWL_MCP_METRICS=1` está activo, cada `tools/call` se registra en
|
|
7
|
+
* `.planning/evolucion/mcp-metrics.jsonl` con:
|
|
8
|
+
* { ts, tool, durationMs, ok, error?, baseDir }
|
|
9
|
+
*
|
|
10
|
+
* Defaults:
|
|
11
|
+
* - SWL_MCP_METRICS no set → no se persiste nada (zero-cost).
|
|
12
|
+
* - Se usa append-only JSONL (alta frecuencia → atomicWriteJSON sería ineficiente).
|
|
13
|
+
* - Si el filesystem no permite escritura (p.ej. baseDir read-only), el error
|
|
14
|
+
* se traga silenciosamente — la telemetría NUNCA debe romper el server.
|
|
15
|
+
*
|
|
16
|
+
* Zero-deps.
|
|
17
|
+
*
|
|
18
|
+
* @module scripts/mcp-server/telemetry
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Construye un grabador de telemetría desde el entorno actual.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} [opciones]
|
|
28
|
+
* @param {string} opciones.baseDir - Directorio raíz del proyecto SWL (donde está .planning/).
|
|
29
|
+
* @param {object} [opciones.env] - Sustituible para tests.
|
|
30
|
+
* @returns {{ habilitada: boolean, registrar: Function, ruta: string|null }}
|
|
31
|
+
*/
|
|
32
|
+
function construirTelemetria(opciones = {}) {
|
|
33
|
+
const env = opciones.env || process.env;
|
|
34
|
+
const habilitada = env.SWL_MCP_METRICS === '1' || env.SWL_MCP_METRICS === 'true';
|
|
35
|
+
const baseDir = opciones.baseDir;
|
|
36
|
+
|
|
37
|
+
if (!habilitada || !baseDir) {
|
|
38
|
+
return {
|
|
39
|
+
habilitada: false,
|
|
40
|
+
registrar: () => {}, // noop
|
|
41
|
+
ruta: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const dirEvolucion = path.join(baseDir, '.planning', 'evolucion');
|
|
46
|
+
const ruta = path.join(dirEvolucion, 'mcp-metrics.jsonl');
|
|
47
|
+
|
|
48
|
+
// Asegurar que el directorio existe — si falla, deshabilitamos silenciosamente.
|
|
49
|
+
let escribible = true;
|
|
50
|
+
try {
|
|
51
|
+
fs.mkdirSync(dirEvolucion, { recursive: true });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
escribible = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
habilitada: escribible,
|
|
58
|
+
registrar: (evento) => {
|
|
59
|
+
if (!escribible) return;
|
|
60
|
+
try {
|
|
61
|
+
const linea = JSON.stringify({
|
|
62
|
+
ts: new Date().toISOString(),
|
|
63
|
+
...evento,
|
|
64
|
+
}) + '\n';
|
|
65
|
+
fs.appendFileSync(ruta, linea, { encoding: 'utf-8' });
|
|
66
|
+
} catch {
|
|
67
|
+
// Telemetría nunca debe romper el server — error silencioso.
|
|
68
|
+
// Tras 5 errores consecutivos podríamos deshabilitar, pero no
|
|
69
|
+
// implementamos eso hasta que aparezca como problema real.
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
ruta,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
construirTelemetria,
|
|
78
|
+
};
|
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Migración 3A — convierte campos CSV a array YAML inline en frontmatter de
|
|
4
|
-
* agentes. Afecta `tools`, `skillsInvocables`, `skillsRestringidos`.
|
|
5
|
-
*
|
|
6
|
-
* Antes: tools: Read, Write, Edit, Bash
|
|
7
|
-
* Después: tools: [Read, Write, Edit, Bash]
|
|
8
|
-
*
|
|
9
|
-
* Razón: el schema declara `type: array` pero el uso real es CSV string. YAML
|
|
10
|
-
* estándar NO convierte CSV a array — el parser produce una sola string. Esto
|
|
11
|
-
* causó bugs históricos documentados en CLAUDE.md ("regla obligatoria CSV
|
|
12
|
-
* estricto"). Migrar a array inline elimina la ambigüedad.
|
|
13
|
-
*
|
|
14
|
-
* Idempotente: skip si ya es array.
|
|
15
|
-
*
|
|
16
|
-
* Casos especiales detectados en agentes:
|
|
17
|
-
* - skillsInvocables: <ninguno> → mantener literal
|
|
18
|
-
* - skillsRestringidos: ninguno → convertir a [] (array vacío)
|
|
19
|
-
* - skillsRestringidos: \n - x\n → ya es array YAML multilinea, mantener
|
|
20
|
-
*
|
|
21
|
-
* Uso:
|
|
22
|
-
* node scripts/migrar-csv-a-array.js [--dry-run]
|
|
23
|
-
*
|
|
24
|
-
* Zero-dependencies. Compatible Windows (CRLF-safe), Node 18+.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
'use strict';
|
|
28
|
-
|
|
29
|
-
const fs = require('fs');
|
|
30
|
-
const path = require('path');
|
|
31
|
-
|
|
32
|
-
const AGENTES_DIR = path.join(__dirname, '..', 'agentes');
|
|
33
|
-
const DRY_RUN = process.argv.includes('--dry-run');
|
|
34
|
-
|
|
35
|
-
// Campos que migran de CSV a array
|
|
36
|
-
const CAMPOS_CSV = ['tools', 'skillsInvocables', 'skillsRestringidos'];
|
|
37
|
-
|
|
38
|
-
// Valores especiales que NO son listas y deben preservarse
|
|
39
|
-
const VALORES_LITERAL = new Set(['ninguno', 'ninguna', '<ninguno>', '<ninguna>', 'none', 'null']);
|
|
40
|
-
|
|
41
|
-
function parsearValorCSV(valorStr) {
|
|
42
|
-
// Detectar si ya es array inline `[a, b, c]`
|
|
43
|
-
if (/^\s*\[/.test(valorStr)) return null;
|
|
44
|
-
|
|
45
|
-
// Limpiar
|
|
46
|
-
const valor = valorStr.trim();
|
|
47
|
-
if (!valor) return [];
|
|
48
|
-
|
|
49
|
-
// Valor especial literal
|
|
50
|
-
if (VALORES_LITERAL.has(valor.toLowerCase())) return [];
|
|
51
|
-
|
|
52
|
-
// CSV split
|
|
53
|
-
return valor
|
|
54
|
-
.split(',')
|
|
55
|
-
.map((s) => s.trim())
|
|
56
|
-
.filter(Boolean);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function migrarFrontmatter(contenido) {
|
|
60
|
-
const match = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
61
|
-
if (!match) return { contenido, cambios: 0 };
|
|
62
|
-
|
|
63
|
-
const eol = match[0].includes('\r\n') ? '\r\n' : '\n';
|
|
64
|
-
const fmCompleto = match[0];
|
|
65
|
-
|
|
66
|
-
let nuevoFm = fmCompleto;
|
|
67
|
-
let cambios = 0;
|
|
68
|
-
|
|
69
|
-
for (const campo of CAMPOS_CSV) {
|
|
70
|
-
// Patrón 2 PRIMERO: array YAML multilínea
|
|
71
|
-
// skillsRestringidos:
|
|
72
|
-
// - angular-moderno
|
|
73
|
-
// - typescript-avanzado
|
|
74
|
-
// El campo está solo en su línea (sin valor) seguido de líneas con ` - X`
|
|
75
|
-
const re2 = new RegExp(`^(${campo}):[ \\t]*\\r?\\n((?:[ \\t]+-[ \\t]+[^\\r\\n]+\\r?\\n)+)`, 'm');
|
|
76
|
-
const m2 = nuevoFm.match(re2);
|
|
77
|
-
if (m2) {
|
|
78
|
-
const bloque = m2[2];
|
|
79
|
-
const items = bloque
|
|
80
|
-
.split(/\r?\n/)
|
|
81
|
-
.map((l) => l.trim())
|
|
82
|
-
.filter((l) => l.startsWith('- '))
|
|
83
|
-
.map((l) => l.slice(2).trim().replace(/^["']|["']$/g, ''));
|
|
84
|
-
if (items.length > 0) {
|
|
85
|
-
const arrayInline = `[${items.join(', ')}]`;
|
|
86
|
-
const reemplazo = `${campo}: ${arrayInline}${eol}`;
|
|
87
|
-
nuevoFm = nuevoFm.replace(re2, reemplazo);
|
|
88
|
-
cambios++;
|
|
89
|
-
}
|
|
90
|
-
continue; // procesado, no aplicar regex 1
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Patrón 1: campo en una sola línea con valor (CSV o array inline)
|
|
94
|
-
// tools: Read, Write
|
|
95
|
-
// skillsRestringidos: ninguno
|
|
96
|
-
// tools: [Read, Write] ← skip, ya es array
|
|
97
|
-
// CRÍTICO: usar [ \\t]* (NO \\s*) para no consumir newlines
|
|
98
|
-
const re1 = new RegExp(`^(${campo}):[ \\t]*([^\\r\\n]+)$`, 'm');
|
|
99
|
-
const m1 = nuevoFm.match(re1);
|
|
100
|
-
if (m1) {
|
|
101
|
-
const valorStr = m1[2];
|
|
102
|
-
const items = parsearValorCSV(valorStr);
|
|
103
|
-
if (items === null) continue; // ya es array, skip
|
|
104
|
-
const arrayInline = `[${items.join(', ')}]`;
|
|
105
|
-
const reemplazo = `${campo}: ${arrayInline}`;
|
|
106
|
-
if (m1[0] !== reemplazo) {
|
|
107
|
-
nuevoFm = nuevoFm.replace(re1, reemplazo);
|
|
108
|
-
cambios++;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (cambios === 0) return { contenido, cambios: 0 };
|
|
114
|
-
|
|
115
|
-
return { contenido: contenido.replace(fmCompleto, nuevoFm), cambios };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function migrarAgente(archivo) {
|
|
119
|
-
const contenido = fs.readFileSync(archivo, 'utf-8');
|
|
120
|
-
const { contenido: nuevo, cambios } = migrarFrontmatter(contenido);
|
|
121
|
-
|
|
122
|
-
if (cambios === 0) {
|
|
123
|
-
return { archivo: path.basename(archivo), status: 'sin-cambios' };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (DRY_RUN) {
|
|
127
|
-
return { archivo: path.basename(archivo), status: 'dry-run', cambios };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
fs.writeFileSync(archivo, nuevo, 'utf-8');
|
|
131
|
-
return { archivo: path.basename(archivo), status: 'migrado', cambios };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function main() {
|
|
135
|
-
console.log(`Migrando CSV → array YAML${DRY_RUN ? ' (dry-run)' : ''}...\n`);
|
|
136
|
-
|
|
137
|
-
const archivos = fs
|
|
138
|
-
.readdirSync(AGENTES_DIR)
|
|
139
|
-
.filter((f) => f.endsWith('.md') && !f.startsWith('_'))
|
|
140
|
-
.map((f) => path.join(AGENTES_DIR, f));
|
|
141
|
-
|
|
142
|
-
const resultados = archivos.map(migrarAgente);
|
|
143
|
-
|
|
144
|
-
const stats = {
|
|
145
|
-
'sin-cambios': 0,
|
|
146
|
-
migrado: 0,
|
|
147
|
-
'dry-run': 0,
|
|
148
|
-
totalCambios: 0,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
for (const r of resultados) {
|
|
152
|
-
stats[r.status] = (stats[r.status] || 0) + 1;
|
|
153
|
-
if (r.cambios) stats.totalCambios += r.cambios;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log(`Sin cambios: ${stats['sin-cambios']}`);
|
|
157
|
-
console.log(`Migrados: ${stats.migrado}`);
|
|
158
|
-
console.log(`Dry-run: ${stats['dry-run']}`);
|
|
159
|
-
console.log(`Total ediciones: ${stats.totalCambios}`);
|
|
160
|
-
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (require.main === module) {
|
|
165
|
-
main();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
module.exports = { parsearValorCSV, migrarFrontmatter };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Migración 3A — convierte campos CSV a array YAML inline en frontmatter de
|
|
4
|
+
* agentes. Afecta `tools`, `skillsInvocables`, `skillsRestringidos`.
|
|
5
|
+
*
|
|
6
|
+
* Antes: tools: Read, Write, Edit, Bash
|
|
7
|
+
* Después: tools: [Read, Write, Edit, Bash]
|
|
8
|
+
*
|
|
9
|
+
* Razón: el schema declara `type: array` pero el uso real es CSV string. YAML
|
|
10
|
+
* estándar NO convierte CSV a array — el parser produce una sola string. Esto
|
|
11
|
+
* causó bugs históricos documentados en CLAUDE.md ("regla obligatoria CSV
|
|
12
|
+
* estricto"). Migrar a array inline elimina la ambigüedad.
|
|
13
|
+
*
|
|
14
|
+
* Idempotente: skip si ya es array.
|
|
15
|
+
*
|
|
16
|
+
* Casos especiales detectados en agentes:
|
|
17
|
+
* - skillsInvocables: <ninguno> → mantener literal
|
|
18
|
+
* - skillsRestringidos: ninguno → convertir a [] (array vacío)
|
|
19
|
+
* - skillsRestringidos: \n - x\n → ya es array YAML multilinea, mantener
|
|
20
|
+
*
|
|
21
|
+
* Uso:
|
|
22
|
+
* node scripts/migrar-csv-a-array.js [--dry-run]
|
|
23
|
+
*
|
|
24
|
+
* Zero-dependencies. Compatible Windows (CRLF-safe), Node 18+.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
const AGENTES_DIR = path.join(__dirname, '..', 'agentes');
|
|
33
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
34
|
+
|
|
35
|
+
// Campos que migran de CSV a array
|
|
36
|
+
const CAMPOS_CSV = ['tools', 'skillsInvocables', 'skillsRestringidos'];
|
|
37
|
+
|
|
38
|
+
// Valores especiales que NO son listas y deben preservarse
|
|
39
|
+
const VALORES_LITERAL = new Set(['ninguno', 'ninguna', '<ninguno>', '<ninguna>', 'none', 'null']);
|
|
40
|
+
|
|
41
|
+
function parsearValorCSV(valorStr) {
|
|
42
|
+
// Detectar si ya es array inline `[a, b, c]`
|
|
43
|
+
if (/^\s*\[/.test(valorStr)) return null;
|
|
44
|
+
|
|
45
|
+
// Limpiar
|
|
46
|
+
const valor = valorStr.trim();
|
|
47
|
+
if (!valor) return [];
|
|
48
|
+
|
|
49
|
+
// Valor especial literal
|
|
50
|
+
if (VALORES_LITERAL.has(valor.toLowerCase())) return [];
|
|
51
|
+
|
|
52
|
+
// CSV split
|
|
53
|
+
return valor
|
|
54
|
+
.split(',')
|
|
55
|
+
.map((s) => s.trim())
|
|
56
|
+
.filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function migrarFrontmatter(contenido) {
|
|
60
|
+
const match = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
61
|
+
if (!match) return { contenido, cambios: 0 };
|
|
62
|
+
|
|
63
|
+
const eol = match[0].includes('\r\n') ? '\r\n' : '\n';
|
|
64
|
+
const fmCompleto = match[0];
|
|
65
|
+
|
|
66
|
+
let nuevoFm = fmCompleto;
|
|
67
|
+
let cambios = 0;
|
|
68
|
+
|
|
69
|
+
for (const campo of CAMPOS_CSV) {
|
|
70
|
+
// Patrón 2 PRIMERO: array YAML multilínea
|
|
71
|
+
// skillsRestringidos:
|
|
72
|
+
// - angular-moderno
|
|
73
|
+
// - typescript-avanzado
|
|
74
|
+
// El campo está solo en su línea (sin valor) seguido de líneas con ` - X`
|
|
75
|
+
const re2 = new RegExp(`^(${campo}):[ \\t]*\\r?\\n((?:[ \\t]+-[ \\t]+[^\\r\\n]+\\r?\\n)+)`, 'm');
|
|
76
|
+
const m2 = nuevoFm.match(re2);
|
|
77
|
+
if (m2) {
|
|
78
|
+
const bloque = m2[2];
|
|
79
|
+
const items = bloque
|
|
80
|
+
.split(/\r?\n/)
|
|
81
|
+
.map((l) => l.trim())
|
|
82
|
+
.filter((l) => l.startsWith('- '))
|
|
83
|
+
.map((l) => l.slice(2).trim().replace(/^["']|["']$/g, ''));
|
|
84
|
+
if (items.length > 0) {
|
|
85
|
+
const arrayInline = `[${items.join(', ')}]`;
|
|
86
|
+
const reemplazo = `${campo}: ${arrayInline}${eol}`;
|
|
87
|
+
nuevoFm = nuevoFm.replace(re2, reemplazo);
|
|
88
|
+
cambios++;
|
|
89
|
+
}
|
|
90
|
+
continue; // procesado, no aplicar regex 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Patrón 1: campo en una sola línea con valor (CSV o array inline)
|
|
94
|
+
// tools: Read, Write
|
|
95
|
+
// skillsRestringidos: ninguno
|
|
96
|
+
// tools: [Read, Write] ← skip, ya es array
|
|
97
|
+
// CRÍTICO: usar [ \\t]* (NO \\s*) para no consumir newlines
|
|
98
|
+
const re1 = new RegExp(`^(${campo}):[ \\t]*([^\\r\\n]+)$`, 'm');
|
|
99
|
+
const m1 = nuevoFm.match(re1);
|
|
100
|
+
if (m1) {
|
|
101
|
+
const valorStr = m1[2];
|
|
102
|
+
const items = parsearValorCSV(valorStr);
|
|
103
|
+
if (items === null) continue; // ya es array, skip
|
|
104
|
+
const arrayInline = `[${items.join(', ')}]`;
|
|
105
|
+
const reemplazo = `${campo}: ${arrayInline}`;
|
|
106
|
+
if (m1[0] !== reemplazo) {
|
|
107
|
+
nuevoFm = nuevoFm.replace(re1, reemplazo);
|
|
108
|
+
cambios++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (cambios === 0) return { contenido, cambios: 0 };
|
|
114
|
+
|
|
115
|
+
return { contenido: contenido.replace(fmCompleto, nuevoFm), cambios };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function migrarAgente(archivo) {
|
|
119
|
+
const contenido = fs.readFileSync(archivo, 'utf-8');
|
|
120
|
+
const { contenido: nuevo, cambios } = migrarFrontmatter(contenido);
|
|
121
|
+
|
|
122
|
+
if (cambios === 0) {
|
|
123
|
+
return { archivo: path.basename(archivo), status: 'sin-cambios' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (DRY_RUN) {
|
|
127
|
+
return { archivo: path.basename(archivo), status: 'dry-run', cambios };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(archivo, nuevo, 'utf-8');
|
|
131
|
+
return { archivo: path.basename(archivo), status: 'migrado', cambios };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function main() {
|
|
135
|
+
console.log(`Migrando CSV → array YAML${DRY_RUN ? ' (dry-run)' : ''}...\n`);
|
|
136
|
+
|
|
137
|
+
const archivos = fs
|
|
138
|
+
.readdirSync(AGENTES_DIR)
|
|
139
|
+
.filter((f) => f.endsWith('.md') && !f.startsWith('_'))
|
|
140
|
+
.map((f) => path.join(AGENTES_DIR, f));
|
|
141
|
+
|
|
142
|
+
const resultados = archivos.map(migrarAgente);
|
|
143
|
+
|
|
144
|
+
const stats = {
|
|
145
|
+
'sin-cambios': 0,
|
|
146
|
+
migrado: 0,
|
|
147
|
+
'dry-run': 0,
|
|
148
|
+
totalCambios: 0,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
for (const r of resultados) {
|
|
152
|
+
stats[r.status] = (stats[r.status] || 0) + 1;
|
|
153
|
+
if (r.cambios) stats.totalCambios += r.cambios;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`Sin cambios: ${stats['sin-cambios']}`);
|
|
157
|
+
console.log(`Migrados: ${stats.migrado}`);
|
|
158
|
+
console.log(`Dry-run: ${stats['dry-run']}`);
|
|
159
|
+
console.log(`Total ediciones: ${stats.totalCambios}`);
|
|
160
|
+
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (require.main === module) {
|
|
165
|
+
main();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { parsearValorCSV, migrarFrontmatter };
|