@saulwade/swl-ses 2.3.0 → 2.3.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 +47 -6
- package/README.md +1 -1
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +472 -472
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +479 -479
- package/agentes/backend-python-swl.md +605 -605
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +605 -605
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +2 -2
- package/agentes/implementador-swl.md +328 -328
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +278 -278
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +918 -918
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/orquestador-swl.md +1026 -1026
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +1 -1
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +245 -245
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +505 -505
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +291 -291
- package/agentes/tdd-qa-swl.md +393 -393
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/predecir.md +139 -139
- package/comandos/swl/sesiones.md +1 -1
- package/gateway/agent-executor.js +1 -1
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/agent-deep-links/SKILL.md +148 -148
- package/habilidades/agentes-como-servicio/SKILL.md +2 -2
- package/habilidades/angular-moderno/SKILL.md +20 -1
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
- package/habilidades/backend-async-postgres-testing/SKILL.md +215 -215
- package/habilidades/backend-error-design/SKILL.md +221 -221
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/calidad-anti-patrones-universales/SKILL.md +368 -368
- package/habilidades/calidad-contract-testing/SKILL.md +165 -165
- package/habilidades/calidad-mutation-testing/SKILL.md +170 -170
- package/habilidades/changelog-generator/scripts/parse-commits.js +367 -367
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/drift-detection/SKILL.md +179 -179
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +1 -1
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +252 -252
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/perfil-usuario/SKILL.md +200 -200
- package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -255
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -105
- package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
- package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -138
- package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
- package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
- package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
- package/habilidades/proceso-modular-split/SKILL.md +256 -256
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +2 -2
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/structured-outputs/SKILL.md +2 -2
- package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
- package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
- package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
- package/habilidades/swl-dashboard/SKILL.md +2 -2
- package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +1 -1
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +1 -1
- package/hooks/ciclo-evolucion-subagente.js +26 -26
- package/hooks/ciclo-evolucion.js +26 -26
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/claudemd-duplicacion-detector.js +170 -170
- package/hooks/contexto-iteracion.js +144 -144
- package/hooks/guardrail-modelo.js +1 -1
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/autonomia.js +208 -208
- package/hooks/lib/ciclo-evolucion.js +47 -47
- package/hooks/lib/deep-links.js +185 -185
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/etapa-auto-evolucion.js +701 -701
- package/hooks/lib/etapa-metricas.js +388 -388
- package/hooks/lib/etapa-perfil-usuario.js +376 -376
- package/hooks/lib/loop-telemetry.js +321 -321
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/model-router.js +2 -2
- package/hooks/lib/propose-step.js +358 -358
- 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/token-estimator.js +1 -0
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/usage-model.js +1 -1
- package/hooks/linea-estado.js +2 -0
- package/hooks/registro-turnos.js +209 -209
- package/hooks/session-briefing.js +98 -98
- package/hooks/spec-gate.js +211 -211
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/tdd-gate.js +241 -241
- package/hooks/telemetria-skill-routing.js +100 -100
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-intent-spec.js +242 -242
- package/hooks/validar-memoria-hook.js +218 -218
- package/hooks/validar-planning-paths.js +134 -134
- package/instintos/autonomia.yaml +27 -27
- package/instintos/prompt-appendices.yaml +57 -57
- package/llms.txt +29 -29
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +2291 -1964
- package/manifiestos/planning-paths.json +44 -44
- package/manifiestos/skills-lock.json +1282 -1282
- package/package.json +1 -1
- 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 +1 -1
- package/reglas/accesibilidad.md +10 -10
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/api-diseno.md +9 -9
- package/reglas/arreglar-al-detectar.md +240 -240
- package/reglas/auditorias-documentales-estructurales.md +7 -7
- package/reglas/cloud-infra.md +8 -8
- package/reglas/consultar-vault-primero.md +195 -195
- package/reglas/debatir-antes-de-aceptar.md +158 -158
- package/reglas/fragmentos-compartidos.md +183 -183
- package/reglas/hooks.md +6 -6
- package/reglas/intent-engineering.md +218 -218
- package/reglas/markitdown.md +8 -8
- package/reglas/monitor-ci.md +309 -309
- package/reglas/patrones.md +6 -6
- package/reglas/sesiones-paralelas.md +180 -180
- package/reglas/sin-duplicacion-reglas-globales.md +182 -182
- package/reglas/skills-estandar.md +6 -6
- package/reglas/testing.md +7 -7
- package/reglas/tests-cleanup.md +224 -224
- package/reglas/usar-code-review-graph.md +155 -155
- package/reglas/usar-context7.md +226 -226
- package/reglas/verificar-citas-normativas.md +548 -548
- package/schemas/agent-frontmatter.schema.json +3 -3
- package/schemas/agent-message.schema.json +73 -73
- package/schemas/agent-output-implementacion.schema.json +114 -114
- package/schemas/agent-output-planificacion.schema.json +150 -150
- package/schemas/agent-output-review.schema.json +98 -98
- package/schemas/diary-entry.schema.json +112 -112
- package/schemas/hook-profiles.schema.json +54 -54
- package/schemas/hooks-config.schema.json +89 -89
- package/schemas/modulos.schema.json +38 -38
- package/schemas/perfiles.schema.json +36 -36
- package/schemas/plugin.schema.json +77 -77
- package/schemas/skill-evals.schema.json +119 -119
- package/schemas/skill-frontmatter.schema.json +245 -245
- 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/cli/aprobar-plan.js +73 -73
- package/scripts/cli/briefing.js +23 -23
- package/scripts/cli/ciclo-evolucion.js +26 -26
- package/scripts/cli/configurar-ci.js +40 -40
- package/scripts/cli/derivar-feature-list.js +25 -25
- package/scripts/cli/detectar-host.js +27 -27
- package/scripts/cli/diary-entry.js +69 -69
- package/scripts/cli/execution-state.js +18 -18
- package/scripts/cli/gateway-notify.js +41 -41
- package/scripts/cli/liberar-fase.js +42 -42
- package/scripts/cli/loop-telemetry.js +125 -125
- package/scripts/cli/mark-evolved.js +56 -56
- package/scripts/cli/metricas-dora.js +26 -26
- package/scripts/cli/near-duplicate.js +55 -55
- package/scripts/cli/notificaciones.js +123 -123
- package/scripts/cli/propose-step.js +29 -29
- package/scripts/cli/schedule-parse.js +19 -19
- package/scripts/cli/sugerir-modelo.js +20 -20
- package/scripts/cli/verificar-plan.js +36 -36
- package/scripts/cli/verificar-trazabilidad.js +35 -35
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/auditar-invocaciones-comandos.js +104 -104
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/ci-reader.js +193 -193
- package/scripts/lib/claude-sessions.js +1 -0
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-host-swl.js +175 -175
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
- package/scripts/lib/detector-reglas-duplicadas.js +220 -220
- package/scripts/lib/diary-entry.js +234 -234
- 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/evidencia-release.js +322 -322
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/gate-hooks-requires.js +249 -249
- package/scripts/lib/gate-licencias.js +212 -212
- package/scripts/lib/git-metricas.js +257 -257
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/metricas-dora.js +204 -204
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/plan-lock.js +275 -275
- package/scripts/lib/pr-analyzer.js +399 -399
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/reglas-globales-conocidas.json +180 -180
- package/scripts/lib/resolver-plan-fase.js +37 -37
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/schema-version.js +164 -164
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/tool-cost-analyzer.js +1 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +200 -200
- package/scripts/publicar.js +497 -497
- package/scripts/run-eval.js +141 -141
- package/scripts/tui/componentes/selector-multi.js +189 -189
- package/scripts/tui/componentes/selector-unico.js +158 -158
- package/scripts/tui/ejecutores.js +375 -375
- package/scripts/tui/index.js +162 -162
- package/scripts/tui/lib/colores.js +129 -129
- package/scripts/tui/lib/render.js +264 -264
- package/scripts/tui/lib/teclas.js +113 -113
- package/scripts/tui/pantallas/inspect.js +173 -173
- package/scripts/tui/pantallas/install-wizard.js +334 -334
- package/scripts/tui/pantallas/menu-principal.js +52 -52
- package/scripts/tui/pantallas/progreso.js +274 -274
- package/scripts/tui/pantallas/resumen.js +132 -132
- package/scripts/tui/pantallas/uninstall-wizard.js +208 -208
- package/scripts/tui/pantallas/update-wizard.js +232 -232
- package/scripts/tui/pantallas/welcome.js +187 -187
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cache mtime-based para handlers del swl-mcp-server v1.0.0 (ADR-0019 Sub-fase 3).
|
|
5
|
-
*
|
|
6
|
-
* El stub experimental leía disco en cada call (latencia medida ~10ms con
|
|
7
|
-
* APRENDIZAJES.md de 1172 líneas). Para datasets más grandes (>10k líneas) o
|
|
8
|
-
* múltiples clientes concurrentes, el caching reduce sustancialmente la carga.
|
|
9
|
-
*
|
|
10
|
-
* Patrón:
|
|
11
|
-
* - Key = path absoluto del archivo.
|
|
12
|
-
* - Value = { mtime, contenido, parsed }.
|
|
13
|
-
* - Invalidación: si fs.stat().mtime > value.mtime → recargar y reparsear.
|
|
14
|
-
* - TTL opcional vía `SWL_MCP_CACHE_TTL_MS` (default 60000 ms): aunque mtime no
|
|
15
|
-
* haya cambiado, recargar tras TTL para defensa contra clock skew o ediciones
|
|
16
|
-
* que no actualizan mtime (raras pero posibles en filesystems exóticos).
|
|
17
|
-
*
|
|
18
|
-
* Zero-deps. NO usar para escritura — sigue read-only.
|
|
19
|
-
*
|
|
20
|
-
* @module scripts/mcp-server/cache
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
|
|
25
|
-
const DEFAULT_TTL_MS = 60 * 1000;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Crea una instancia de cache mtime-based.
|
|
29
|
-
*
|
|
30
|
-
* @param {object} [opciones]
|
|
31
|
-
* @param {number} [opciones.ttlMs] - TTL en milisegundos. Default 60000.
|
|
32
|
-
* @param {object} [opciones.env] - Para leer SWL_MCP_CACHE_TTL_MS en tests.
|
|
33
|
-
* @returns {{ get: Function, invalidate: Function, stats: Function, _store: Map }}
|
|
34
|
-
*/
|
|
35
|
-
function crearCache(opciones = {}) {
|
|
36
|
-
const env = opciones.env || process.env;
|
|
37
|
-
const ttlMs = opciones.ttlMs !== undefined
|
|
38
|
-
? opciones.ttlMs
|
|
39
|
-
: (parseInt(env.SWL_MCP_CACHE_TTL_MS, 10) || DEFAULT_TTL_MS);
|
|
40
|
-
|
|
41
|
-
const store = new Map();
|
|
42
|
-
const stats = { hits: 0, misses: 0, invalidations: 0 };
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Obtiene el valor cacheado para `path`. Si el archivo cambió (mtime) o el TTL
|
|
46
|
-
* expiró, vuelve a leer y parsear con `parser(contenido, path)`.
|
|
47
|
-
*
|
|
48
|
-
* @param {string} ruta - Path absoluto del archivo a cachear.
|
|
49
|
-
* @param {(contenido: string, ruta: string) => any} parser - Función que transforma
|
|
50
|
-
* el contenido del archivo en el objeto a cachear. DEBE ser pura.
|
|
51
|
-
* @returns {{ data: any, hit: boolean } | null} null si el archivo no existe.
|
|
52
|
-
*/
|
|
53
|
-
function get(ruta, parser) {
|
|
54
|
-
let stat;
|
|
55
|
-
try {
|
|
56
|
-
stat = fs.statSync(ruta);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
if (err.code === 'ENOENT') return null;
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const mtimeMs = stat.mtimeMs;
|
|
63
|
-
const ahora = Date.now();
|
|
64
|
-
const cached = store.get(ruta);
|
|
65
|
-
|
|
66
|
-
if (cached && cached.mtimeMs === mtimeMs && (ahora - cached.cargadoEn) < ttlMs) {
|
|
67
|
-
stats.hits++;
|
|
68
|
-
return { data: cached.data, hit: true };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (cached) stats.invalidations++;
|
|
72
|
-
else stats.misses++;
|
|
73
|
-
|
|
74
|
-
const contenido = fs.readFileSync(ruta, 'utf-8');
|
|
75
|
-
const data = parser(contenido, ruta);
|
|
76
|
-
store.set(ruta, { mtimeMs, cargadoEn: ahora, data });
|
|
77
|
-
return { data, hit: false };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Invalida una entrada o todo el cache.
|
|
82
|
-
* @param {string} [ruta] - Si se omite, vacía todo.
|
|
83
|
-
*/
|
|
84
|
-
function invalidate(ruta) {
|
|
85
|
-
if (ruta === undefined) {
|
|
86
|
-
const n = store.size;
|
|
87
|
-
store.clear();
|
|
88
|
-
stats.invalidations += n;
|
|
89
|
-
return n;
|
|
90
|
-
}
|
|
91
|
-
if (store.delete(ruta)) stats.invalidations++;
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
get,
|
|
97
|
-
invalidate,
|
|
98
|
-
stats: () => ({ ...stats, size: store.size, ttlMs }),
|
|
99
|
-
_store: store, // exposed para tests
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = {
|
|
104
|
-
crearCache,
|
|
105
|
-
DEFAULT_TTL_MS,
|
|
106
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache mtime-based para handlers del swl-mcp-server v1.0.0 (ADR-0019 Sub-fase 3).
|
|
5
|
+
*
|
|
6
|
+
* El stub experimental leía disco en cada call (latencia medida ~10ms con
|
|
7
|
+
* APRENDIZAJES.md de 1172 líneas). Para datasets más grandes (>10k líneas) o
|
|
8
|
+
* múltiples clientes concurrentes, el caching reduce sustancialmente la carga.
|
|
9
|
+
*
|
|
10
|
+
* Patrón:
|
|
11
|
+
* - Key = path absoluto del archivo.
|
|
12
|
+
* - Value = { mtime, contenido, parsed }.
|
|
13
|
+
* - Invalidación: si fs.stat().mtime > value.mtime → recargar y reparsear.
|
|
14
|
+
* - TTL opcional vía `SWL_MCP_CACHE_TTL_MS` (default 60000 ms): aunque mtime no
|
|
15
|
+
* haya cambiado, recargar tras TTL para defensa contra clock skew o ediciones
|
|
16
|
+
* que no actualizan mtime (raras pero posibles en filesystems exóticos).
|
|
17
|
+
*
|
|
18
|
+
* Zero-deps. NO usar para escritura — sigue read-only.
|
|
19
|
+
*
|
|
20
|
+
* @module scripts/mcp-server/cache
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TTL_MS = 60 * 1000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Crea una instancia de cache mtime-based.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} [opciones]
|
|
31
|
+
* @param {number} [opciones.ttlMs] - TTL en milisegundos. Default 60000.
|
|
32
|
+
* @param {object} [opciones.env] - Para leer SWL_MCP_CACHE_TTL_MS en tests.
|
|
33
|
+
* @returns {{ get: Function, invalidate: Function, stats: Function, _store: Map }}
|
|
34
|
+
*/
|
|
35
|
+
function crearCache(opciones = {}) {
|
|
36
|
+
const env = opciones.env || process.env;
|
|
37
|
+
const ttlMs = opciones.ttlMs !== undefined
|
|
38
|
+
? opciones.ttlMs
|
|
39
|
+
: (parseInt(env.SWL_MCP_CACHE_TTL_MS, 10) || DEFAULT_TTL_MS);
|
|
40
|
+
|
|
41
|
+
const store = new Map();
|
|
42
|
+
const stats = { hits: 0, misses: 0, invalidations: 0 };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Obtiene el valor cacheado para `path`. Si el archivo cambió (mtime) o el TTL
|
|
46
|
+
* expiró, vuelve a leer y parsear con `parser(contenido, path)`.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} ruta - Path absoluto del archivo a cachear.
|
|
49
|
+
* @param {(contenido: string, ruta: string) => any} parser - Función que transforma
|
|
50
|
+
* el contenido del archivo en el objeto a cachear. DEBE ser pura.
|
|
51
|
+
* @returns {{ data: any, hit: boolean } | null} null si el archivo no existe.
|
|
52
|
+
*/
|
|
53
|
+
function get(ruta, parser) {
|
|
54
|
+
let stat;
|
|
55
|
+
try {
|
|
56
|
+
stat = fs.statSync(ruta);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err.code === 'ENOENT') return null;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mtimeMs = stat.mtimeMs;
|
|
63
|
+
const ahora = Date.now();
|
|
64
|
+
const cached = store.get(ruta);
|
|
65
|
+
|
|
66
|
+
if (cached && cached.mtimeMs === mtimeMs && (ahora - cached.cargadoEn) < ttlMs) {
|
|
67
|
+
stats.hits++;
|
|
68
|
+
return { data: cached.data, hit: true };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (cached) stats.invalidations++;
|
|
72
|
+
else stats.misses++;
|
|
73
|
+
|
|
74
|
+
const contenido = fs.readFileSync(ruta, 'utf-8');
|
|
75
|
+
const data = parser(contenido, ruta);
|
|
76
|
+
store.set(ruta, { mtimeMs, cargadoEn: ahora, data });
|
|
77
|
+
return { data, hit: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Invalida una entrada o todo el cache.
|
|
82
|
+
* @param {string} [ruta] - Si se omite, vacía todo.
|
|
83
|
+
*/
|
|
84
|
+
function invalidate(ruta) {
|
|
85
|
+
if (ruta === undefined) {
|
|
86
|
+
const n = store.size;
|
|
87
|
+
store.clear();
|
|
88
|
+
stats.invalidations += n;
|
|
89
|
+
return n;
|
|
90
|
+
}
|
|
91
|
+
if (store.delete(ruta)) stats.invalidations++;
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
get,
|
|
97
|
+
invalidate,
|
|
98
|
+
stats: () => ({ ...stats, size: store.size, ttlMs }),
|
|
99
|
+
_store: store, // exposed para tests
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
crearCache,
|
|
105
|
+
DEFAULT_TTL_MS,
|
|
106
|
+
};
|
|
@@ -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 };
|