@saulwade/swl-ses 1.4.1 → 1.4.2
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 +1 -1
- package/README.md +1 -1
- package/agentes/nemesis-auditor-swl.md +161 -161
- package/bin/swl-mcp-server.js +187 -187
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- 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/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- 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/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/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 +11 -6
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +1114 -1114
- 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 +9 -1
- 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/detectar-aprendizajes-duplicados.js +151 -151
- 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/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-stack-detallado.js +307 -307
- 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/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/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/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- 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
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* contadores-inventario.js
|
|
5
|
-
*
|
|
6
|
-
* Funciones puras zero-deps para validar consistencia entre los contadores
|
|
7
|
-
* de inventario (agentes, skills, comandos, hooks, reglas) declarados en
|
|
8
|
-
* `INVENTARIO.md` (fuente de verdad regenerada por
|
|
9
|
-
* `scripts/generar-inventario.js`) y los contadores citados en los demás
|
|
10
|
-
* documentos canónicos del repositorio.
|
|
11
|
-
*
|
|
12
|
-
* Motivado por el drift recurrente detectado el 2026-04-29: MAPEO_SKILLS_AGENTES.md
|
|
13
|
-
* decía "153 skills" cuando la realidad eran 150; COMPACTACION.md reportaba
|
|
14
|
-
* "147 skills, 40 comandos, 31 hooks". El hook `verificar-release.js` valida
|
|
15
|
-
* versión pero no contadores. Este módulo cierra ese hueco.
|
|
16
|
-
*
|
|
17
|
-
* Cobertura intencional:
|
|
18
|
-
* - Patrón resumen "X agentes, Y skills, Z comandos, W reglas, V hooks"
|
|
19
|
-
* (separadores `,` o `+` o `y`) — valida los 5 números a la vez.
|
|
20
|
-
* - Patrón "X agentes + Y habilidades + Z comandos + W reglas + V hooks"
|
|
21
|
-
* común en tablas de perfiles `completo`.
|
|
22
|
-
* - Patrón "Total de agentes: X" / "Total de skills: X" en MAPEO.
|
|
23
|
-
*
|
|
24
|
-
* Cobertura intencionalmente excluida (falsos positivos clásicos):
|
|
25
|
-
* - "los 17 skills de Anthropic" (skills externos)
|
|
26
|
-
* - "8 lenguajes × 5 reglas" (multiplicación)
|
|
27
|
-
* - perfiles parciales "2 hooks", "6 hooks" (no son totales)
|
|
28
|
-
* - notas históricas "estimé 28 hooks visualmente y propagué el error"
|
|
29
|
-
* - referencias a versiones pasadas en MAPEO ("136 skills tras release X")
|
|
30
|
-
* - filas de tabla histórica con SemVer + fecha ("| 5.4.0 | 2026-04-10 | 57 agentes...")
|
|
31
|
-
*
|
|
32
|
-
* Estas se ignoran porque el patrón resumen es el único que tiene los 5
|
|
33
|
-
* componentes alineados; las menciones aisladas de un solo número se dejan
|
|
34
|
-
* para una auditoría aparte (no bloqueante).
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
const COMPONENTES = ['agentes', 'skills', 'comandos', 'hooks', 'reglas'];
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Parsea la sección "## Resumen" de INVENTARIO.md y retorna un mapa con
|
|
41
|
-
* los contadores reales del sistema.
|
|
42
|
-
*
|
|
43
|
-
* Espera tabla del tipo:
|
|
44
|
-
* ## Resumen
|
|
45
|
-
* | Componente | Cantidad |
|
|
46
|
-
* |-----------|----------|
|
|
47
|
-
* | Agentes SWL | 59 |
|
|
48
|
-
* | Habilidades | 150 |
|
|
49
|
-
* | Comandos | 41 |
|
|
50
|
-
* | Reglas base | 20 |
|
|
51
|
-
* | Reglas por lenguaje | 40 |
|
|
52
|
-
* | Hooks | 36 |
|
|
53
|
-
*
|
|
54
|
-
* Retorna `{ agentes, skills, comandos, hooks, reglas }`.
|
|
55
|
-
* `skills` y `habilidades` se mapean al mismo número.
|
|
56
|
-
* `reglas` = base + por_lenguaje.
|
|
57
|
-
*/
|
|
58
|
-
function parsearInventario(rawMd) {
|
|
59
|
-
const lineas = rawMd.split(/\r?\n/);
|
|
60
|
-
const resumen = {};
|
|
61
|
-
let enResumen = false;
|
|
62
|
-
for (const linea of lineas) {
|
|
63
|
-
if (/^##\s+Resumen\s*$/.test(linea)) { enResumen = true; continue; }
|
|
64
|
-
if (enResumen && /^##\s+/.test(linea)) break;
|
|
65
|
-
if (!enResumen) continue;
|
|
66
|
-
const m = linea.match(/^\|\s*([^|]+?)\s*\|\s*(\d+)\s*\|/);
|
|
67
|
-
if (m) resumen[m[1].trim()] = parseInt(m[2], 10);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const reglasBase = resumen['Reglas base'] || 0;
|
|
71
|
-
const reglasLeng = resumen['Reglas por lenguaje'] || 0;
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
agentes: resumen['Agentes SWL'] || 0,
|
|
75
|
-
skills: resumen['Habilidades'] || 0,
|
|
76
|
-
comandos: resumen['Comandos'] || 0,
|
|
77
|
-
hooks: resumen['Hooks'] || 0,
|
|
78
|
-
reglas: reglasBase + reglasLeng,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Detecta el patrón resumen en una línea y retorna los números encontrados.
|
|
84
|
-
* Soporta separadores `,`, `+` y `y`. Tolera el orden canónico
|
|
85
|
-
* agentes → skills/habilidades → comandos → reglas → hooks.
|
|
86
|
-
*
|
|
87
|
-
* Ejemplos que matchean:
|
|
88
|
-
* "59 agentes, 150 skills, 41 comandos, 60 reglas, 36 hooks"
|
|
89
|
-
* "59 agentes + 150 habilidades + 41 comandos + 60 reglas + 36 hooks"
|
|
90
|
-
* "59 agentes, 150 skills, 41 comandos, 60 reglas y 36 hooks"
|
|
91
|
-
* "Frontmatter: 59 agentes + 150 skills + 41 comandos verificados"
|
|
92
|
-
* (los 3 primeros se validan; los 2 últimos faltan → no full match,
|
|
93
|
-
* se devuelve match parcial con flags)
|
|
94
|
-
*
|
|
95
|
-
* Retorna `null` si no encuentra al menos `agentes` y un segundo componente.
|
|
96
|
-
*/
|
|
97
|
-
function extraerResumenDeLinea(linea) {
|
|
98
|
-
// Patrón principal: 5 componentes en orden
|
|
99
|
-
const re5 = /(\d{1,4})\s+agentes?[\s,+y]+\s*(\d{1,4})\s+(?:skills|habilidades)[\s,+y]+\s*(\d{1,4})\s+comandos[\s,+y]+\s*(\d{1,4})\s+reglas[\s,+y]+\s*(\d{1,4})\s+hooks/i;
|
|
100
|
-
const m5 = linea.match(re5);
|
|
101
|
-
if (m5) {
|
|
102
|
-
return {
|
|
103
|
-
tipo: 'resumen5',
|
|
104
|
-
texto: m5[0],
|
|
105
|
-
agentes: parseInt(m5[1], 10),
|
|
106
|
-
skills: parseInt(m5[2], 10),
|
|
107
|
-
comandos: parseInt(m5[3], 10),
|
|
108
|
-
reglas: parseInt(m5[4], 10),
|
|
109
|
-
hooks: parseInt(m5[5], 10),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Patrón parcial: 3 componentes (agentes, skills/habilidades, comandos)
|
|
114
|
-
const re3 = /(\d{1,4})\s+agentes?[\s,+y]+\s*(\d{1,4})\s+(?:skills|habilidades)[\s,+y]+\s*(\d{1,4})\s+comandos/i;
|
|
115
|
-
const m3 = linea.match(re3);
|
|
116
|
-
if (m3) {
|
|
117
|
-
return {
|
|
118
|
-
tipo: 'resumen3',
|
|
119
|
-
texto: m3[0],
|
|
120
|
-
agentes: parseInt(m3[1], 10),
|
|
121
|
-
skills: parseInt(m3[2], 10),
|
|
122
|
-
comandos: parseInt(m3[3], 10),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Decide si una línea debe ignorarse por ser obviamente histórica
|
|
131
|
-
* (fila de changelog/timeline con versión SemVer + fecha en formato tabla).
|
|
132
|
-
*/
|
|
133
|
-
function esLineaHistorica(linea) {
|
|
134
|
-
// Tabla con versión SemVer + fecha ISO: "| 5.4.0 | 2026-04-10 | ..."
|
|
135
|
-
if (/^\s*\|\s*\d+\.\d+\.\d+\s*\|\s*\d{4}-\d{2}-\d{2}/.test(linea)) return true;
|
|
136
|
-
// "Baseline:" suele ser nota histórica
|
|
137
|
-
if (/\bBaseline\s*:/i.test(linea)) return true;
|
|
138
|
-
// "antes de v|previa a|tras release N" sugiere historia
|
|
139
|
-
if (/\b(?:antes de v|previa a|tras release|en v\d+\.\d+)\b/i.test(linea)) return true;
|
|
140
|
-
// Línea entre comillas dobles (cita textual de un patrón documentado): "22 hooks"
|
|
141
|
-
if (/"\s*\d+\s+(?:agentes|skills|habilidades|comandos|hooks|reglas)\s*"/i.test(linea)) return true;
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Valida un archivo contra los contadores reales.
|
|
147
|
-
* Solo flagea las líneas que contienen patrón resumen identificable.
|
|
148
|
-
*
|
|
149
|
-
* Retorna { ok, lineas_revisadas, discrepancias: [{linea, encontrado, esperado, tipo, contexto}] }
|
|
150
|
-
*/
|
|
151
|
-
function validarArchivo(rawMd, contadoresReales) {
|
|
152
|
-
const lineas = rawMd.split(/\r?\n/);
|
|
153
|
-
const discrepancias = [];
|
|
154
|
-
let lineasRevisadas = 0;
|
|
155
|
-
|
|
156
|
-
for (let i = 0; i < lineas.length; i++) {
|
|
157
|
-
if (esLineaHistorica(lineas[i])) continue;
|
|
158
|
-
const r = extraerResumenDeLinea(lineas[i]);
|
|
159
|
-
if (!r) continue;
|
|
160
|
-
lineasRevisadas++;
|
|
161
|
-
|
|
162
|
-
const campos = r.tipo === 'resumen5'
|
|
163
|
-
? COMPONENTES
|
|
164
|
-
: ['agentes', 'skills', 'comandos'];
|
|
165
|
-
|
|
166
|
-
for (const campo of campos) {
|
|
167
|
-
if (r[campo] !== contadoresReales[campo]) {
|
|
168
|
-
discrepancias.push({
|
|
169
|
-
linea: i + 1,
|
|
170
|
-
campo,
|
|
171
|
-
encontrado: r[campo],
|
|
172
|
-
esperado: contadoresReales[campo],
|
|
173
|
-
tipo: r.tipo,
|
|
174
|
-
contexto: lineas[i].trim().slice(0, 110),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Patrón nominal: "**Total de agentes**: N", "**Total de skills**: N"
|
|
181
|
-
const reTotal = /\*?\*?Total\s+de\s+(agentes|skills|habilidades|comandos|hooks|reglas)\*?\*?\s*:\s*(\d{1,4})/gi;
|
|
182
|
-
for (let i = 0; i < lineas.length; i++) {
|
|
183
|
-
if (esLineaHistorica(lineas[i])) continue;
|
|
184
|
-
let m;
|
|
185
|
-
reTotal.lastIndex = 0;
|
|
186
|
-
while ((m = reTotal.exec(lineas[i])) !== null) {
|
|
187
|
-
const palabra = m[1].toLowerCase();
|
|
188
|
-
const campo = palabra === 'habilidades' ? 'skills' : palabra;
|
|
189
|
-
const num = parseInt(m[2], 10);
|
|
190
|
-
lineasRevisadas++;
|
|
191
|
-
if (num !== contadoresReales[campo]) {
|
|
192
|
-
discrepancias.push({
|
|
193
|
-
linea: i + 1,
|
|
194
|
-
campo,
|
|
195
|
-
encontrado: num,
|
|
196
|
-
esperado: contadoresReales[campo],
|
|
197
|
-
tipo: 'total_nominal',
|
|
198
|
-
contexto: lineas[i].trim().slice(0, 110),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
ok: discrepancias.length === 0,
|
|
206
|
-
lineas_revisadas: lineasRevisadas,
|
|
207
|
-
discrepancias,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
module.exports = {
|
|
212
|
-
COMPONENTES,
|
|
213
|
-
parsearInventario,
|
|
214
|
-
extraerResumenDeLinea,
|
|
215
|
-
esLineaHistorica,
|
|
216
|
-
validarArchivo,
|
|
217
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* contadores-inventario.js
|
|
5
|
+
*
|
|
6
|
+
* Funciones puras zero-deps para validar consistencia entre los contadores
|
|
7
|
+
* de inventario (agentes, skills, comandos, hooks, reglas) declarados en
|
|
8
|
+
* `INVENTARIO.md` (fuente de verdad regenerada por
|
|
9
|
+
* `scripts/generar-inventario.js`) y los contadores citados en los demás
|
|
10
|
+
* documentos canónicos del repositorio.
|
|
11
|
+
*
|
|
12
|
+
* Motivado por el drift recurrente detectado el 2026-04-29: MAPEO_SKILLS_AGENTES.md
|
|
13
|
+
* decía "153 skills" cuando la realidad eran 150; COMPACTACION.md reportaba
|
|
14
|
+
* "147 skills, 40 comandos, 31 hooks". El hook `verificar-release.js` valida
|
|
15
|
+
* versión pero no contadores. Este módulo cierra ese hueco.
|
|
16
|
+
*
|
|
17
|
+
* Cobertura intencional:
|
|
18
|
+
* - Patrón resumen "X agentes, Y skills, Z comandos, W reglas, V hooks"
|
|
19
|
+
* (separadores `,` o `+` o `y`) — valida los 5 números a la vez.
|
|
20
|
+
* - Patrón "X agentes + Y habilidades + Z comandos + W reglas + V hooks"
|
|
21
|
+
* común en tablas de perfiles `completo`.
|
|
22
|
+
* - Patrón "Total de agentes: X" / "Total de skills: X" en MAPEO.
|
|
23
|
+
*
|
|
24
|
+
* Cobertura intencionalmente excluida (falsos positivos clásicos):
|
|
25
|
+
* - "los 17 skills de Anthropic" (skills externos)
|
|
26
|
+
* - "8 lenguajes × 5 reglas" (multiplicación)
|
|
27
|
+
* - perfiles parciales "2 hooks", "6 hooks" (no son totales)
|
|
28
|
+
* - notas históricas "estimé 28 hooks visualmente y propagué el error"
|
|
29
|
+
* - referencias a versiones pasadas en MAPEO ("136 skills tras release X")
|
|
30
|
+
* - filas de tabla histórica con SemVer + fecha ("| 5.4.0 | 2026-04-10 | 57 agentes...")
|
|
31
|
+
*
|
|
32
|
+
* Estas se ignoran porque el patrón resumen es el único que tiene los 5
|
|
33
|
+
* componentes alineados; las menciones aisladas de un solo número se dejan
|
|
34
|
+
* para una auditoría aparte (no bloqueante).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const COMPONENTES = ['agentes', 'skills', 'comandos', 'hooks', 'reglas'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parsea la sección "## Resumen" de INVENTARIO.md y retorna un mapa con
|
|
41
|
+
* los contadores reales del sistema.
|
|
42
|
+
*
|
|
43
|
+
* Espera tabla del tipo:
|
|
44
|
+
* ## Resumen
|
|
45
|
+
* | Componente | Cantidad |
|
|
46
|
+
* |-----------|----------|
|
|
47
|
+
* | Agentes SWL | 59 |
|
|
48
|
+
* | Habilidades | 150 |
|
|
49
|
+
* | Comandos | 41 |
|
|
50
|
+
* | Reglas base | 20 |
|
|
51
|
+
* | Reglas por lenguaje | 40 |
|
|
52
|
+
* | Hooks | 36 |
|
|
53
|
+
*
|
|
54
|
+
* Retorna `{ agentes, skills, comandos, hooks, reglas }`.
|
|
55
|
+
* `skills` y `habilidades` se mapean al mismo número.
|
|
56
|
+
* `reglas` = base + por_lenguaje.
|
|
57
|
+
*/
|
|
58
|
+
function parsearInventario(rawMd) {
|
|
59
|
+
const lineas = rawMd.split(/\r?\n/);
|
|
60
|
+
const resumen = {};
|
|
61
|
+
let enResumen = false;
|
|
62
|
+
for (const linea of lineas) {
|
|
63
|
+
if (/^##\s+Resumen\s*$/.test(linea)) { enResumen = true; continue; }
|
|
64
|
+
if (enResumen && /^##\s+/.test(linea)) break;
|
|
65
|
+
if (!enResumen) continue;
|
|
66
|
+
const m = linea.match(/^\|\s*([^|]+?)\s*\|\s*(\d+)\s*\|/);
|
|
67
|
+
if (m) resumen[m[1].trim()] = parseInt(m[2], 10);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const reglasBase = resumen['Reglas base'] || 0;
|
|
71
|
+
const reglasLeng = resumen['Reglas por lenguaje'] || 0;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
agentes: resumen['Agentes SWL'] || 0,
|
|
75
|
+
skills: resumen['Habilidades'] || 0,
|
|
76
|
+
comandos: resumen['Comandos'] || 0,
|
|
77
|
+
hooks: resumen['Hooks'] || 0,
|
|
78
|
+
reglas: reglasBase + reglasLeng,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detecta el patrón resumen en una línea y retorna los números encontrados.
|
|
84
|
+
* Soporta separadores `,`, `+` y `y`. Tolera el orden canónico
|
|
85
|
+
* agentes → skills/habilidades → comandos → reglas → hooks.
|
|
86
|
+
*
|
|
87
|
+
* Ejemplos que matchean:
|
|
88
|
+
* "59 agentes, 150 skills, 41 comandos, 60 reglas, 36 hooks"
|
|
89
|
+
* "59 agentes + 150 habilidades + 41 comandos + 60 reglas + 36 hooks"
|
|
90
|
+
* "59 agentes, 150 skills, 41 comandos, 60 reglas y 36 hooks"
|
|
91
|
+
* "Frontmatter: 59 agentes + 150 skills + 41 comandos verificados"
|
|
92
|
+
* (los 3 primeros se validan; los 2 últimos faltan → no full match,
|
|
93
|
+
* se devuelve match parcial con flags)
|
|
94
|
+
*
|
|
95
|
+
* Retorna `null` si no encuentra al menos `agentes` y un segundo componente.
|
|
96
|
+
*/
|
|
97
|
+
function extraerResumenDeLinea(linea) {
|
|
98
|
+
// Patrón principal: 5 componentes en orden
|
|
99
|
+
const re5 = /(\d{1,4})\s+agentes?[\s,+y]+\s*(\d{1,4})\s+(?:skills|habilidades)[\s,+y]+\s*(\d{1,4})\s+comandos[\s,+y]+\s*(\d{1,4})\s+reglas[\s,+y]+\s*(\d{1,4})\s+hooks/i;
|
|
100
|
+
const m5 = linea.match(re5);
|
|
101
|
+
if (m5) {
|
|
102
|
+
return {
|
|
103
|
+
tipo: 'resumen5',
|
|
104
|
+
texto: m5[0],
|
|
105
|
+
agentes: parseInt(m5[1], 10),
|
|
106
|
+
skills: parseInt(m5[2], 10),
|
|
107
|
+
comandos: parseInt(m5[3], 10),
|
|
108
|
+
reglas: parseInt(m5[4], 10),
|
|
109
|
+
hooks: parseInt(m5[5], 10),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Patrón parcial: 3 componentes (agentes, skills/habilidades, comandos)
|
|
114
|
+
const re3 = /(\d{1,4})\s+agentes?[\s,+y]+\s*(\d{1,4})\s+(?:skills|habilidades)[\s,+y]+\s*(\d{1,4})\s+comandos/i;
|
|
115
|
+
const m3 = linea.match(re3);
|
|
116
|
+
if (m3) {
|
|
117
|
+
return {
|
|
118
|
+
tipo: 'resumen3',
|
|
119
|
+
texto: m3[0],
|
|
120
|
+
agentes: parseInt(m3[1], 10),
|
|
121
|
+
skills: parseInt(m3[2], 10),
|
|
122
|
+
comandos: parseInt(m3[3], 10),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Decide si una línea debe ignorarse por ser obviamente histórica
|
|
131
|
+
* (fila de changelog/timeline con versión SemVer + fecha en formato tabla).
|
|
132
|
+
*/
|
|
133
|
+
function esLineaHistorica(linea) {
|
|
134
|
+
// Tabla con versión SemVer + fecha ISO: "| 5.4.0 | 2026-04-10 | ..."
|
|
135
|
+
if (/^\s*\|\s*\d+\.\d+\.\d+\s*\|\s*\d{4}-\d{2}-\d{2}/.test(linea)) return true;
|
|
136
|
+
// "Baseline:" suele ser nota histórica
|
|
137
|
+
if (/\bBaseline\s*:/i.test(linea)) return true;
|
|
138
|
+
// "antes de v|previa a|tras release N" sugiere historia
|
|
139
|
+
if (/\b(?:antes de v|previa a|tras release|en v\d+\.\d+)\b/i.test(linea)) return true;
|
|
140
|
+
// Línea entre comillas dobles (cita textual de un patrón documentado): "22 hooks"
|
|
141
|
+
if (/"\s*\d+\s+(?:agentes|skills|habilidades|comandos|hooks|reglas)\s*"/i.test(linea)) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Valida un archivo contra los contadores reales.
|
|
147
|
+
* Solo flagea las líneas que contienen patrón resumen identificable.
|
|
148
|
+
*
|
|
149
|
+
* Retorna { ok, lineas_revisadas, discrepancias: [{linea, encontrado, esperado, tipo, contexto}] }
|
|
150
|
+
*/
|
|
151
|
+
function validarArchivo(rawMd, contadoresReales) {
|
|
152
|
+
const lineas = rawMd.split(/\r?\n/);
|
|
153
|
+
const discrepancias = [];
|
|
154
|
+
let lineasRevisadas = 0;
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < lineas.length; i++) {
|
|
157
|
+
if (esLineaHistorica(lineas[i])) continue;
|
|
158
|
+
const r = extraerResumenDeLinea(lineas[i]);
|
|
159
|
+
if (!r) continue;
|
|
160
|
+
lineasRevisadas++;
|
|
161
|
+
|
|
162
|
+
const campos = r.tipo === 'resumen5'
|
|
163
|
+
? COMPONENTES
|
|
164
|
+
: ['agentes', 'skills', 'comandos'];
|
|
165
|
+
|
|
166
|
+
for (const campo of campos) {
|
|
167
|
+
if (r[campo] !== contadoresReales[campo]) {
|
|
168
|
+
discrepancias.push({
|
|
169
|
+
linea: i + 1,
|
|
170
|
+
campo,
|
|
171
|
+
encontrado: r[campo],
|
|
172
|
+
esperado: contadoresReales[campo],
|
|
173
|
+
tipo: r.tipo,
|
|
174
|
+
contexto: lineas[i].trim().slice(0, 110),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Patrón nominal: "**Total de agentes**: N", "**Total de skills**: N"
|
|
181
|
+
const reTotal = /\*?\*?Total\s+de\s+(agentes|skills|habilidades|comandos|hooks|reglas)\*?\*?\s*:\s*(\d{1,4})/gi;
|
|
182
|
+
for (let i = 0; i < lineas.length; i++) {
|
|
183
|
+
if (esLineaHistorica(lineas[i])) continue;
|
|
184
|
+
let m;
|
|
185
|
+
reTotal.lastIndex = 0;
|
|
186
|
+
while ((m = reTotal.exec(lineas[i])) !== null) {
|
|
187
|
+
const palabra = m[1].toLowerCase();
|
|
188
|
+
const campo = palabra === 'habilidades' ? 'skills' : palabra;
|
|
189
|
+
const num = parseInt(m[2], 10);
|
|
190
|
+
lineasRevisadas++;
|
|
191
|
+
if (num !== contadoresReales[campo]) {
|
|
192
|
+
discrepancias.push({
|
|
193
|
+
linea: i + 1,
|
|
194
|
+
campo,
|
|
195
|
+
encontrado: num,
|
|
196
|
+
esperado: contadoresReales[campo],
|
|
197
|
+
tipo: 'total_nominal',
|
|
198
|
+
contexto: lineas[i].trim().slice(0, 110),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
ok: discrepancias.length === 0,
|
|
206
|
+
lineas_revisadas: lineasRevisadas,
|
|
207
|
+
discrepancias,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
COMPONENTES,
|
|
213
|
+
parsearInventario,
|
|
214
|
+
extraerResumenDeLinea,
|
|
215
|
+
esLineaHistorica,
|
|
216
|
+
validarArchivo,
|
|
217
|
+
};
|