@saulwade/swl-ses 1.0.1 → 1.1.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 +8 -5
- package/README.md +3 -3
- package/agentes/accesibilidad-wcag-swl.md +5 -7
- package/agentes/arquitecto-swl.md +5 -3
- package/agentes/auto-evolucion-swl.md +42 -12
- package/agentes/backend-api-swl.md +5 -3
- package/agentes/backend-csharp-swl.md +5 -3
- package/agentes/backend-go-swl.md +5 -3
- package/agentes/backend-java-swl.md +5 -3
- package/agentes/backend-node-swl.md +5 -3
- package/agentes/backend-python-swl.md +5 -3
- package/agentes/backend-rust-swl.md +5 -3
- package/agentes/backend-workers-swl.md +5 -3
- package/agentes/cloud-infra-swl.md +5 -6
- package/agentes/consolidador-swl.md +5 -3
- package/agentes/datos-swl.md +5 -7
- package/agentes/depurador-swl.md +6 -3
- package/agentes/devops-ci-swl.md +5 -3
- package/agentes/disenador-ui-swl.md +5 -7
- package/agentes/documentador-swl.md +5 -3
- package/agentes/frontend-angular-swl.md +5 -11
- package/agentes/frontend-css-swl.md +5 -9
- package/agentes/frontend-react-swl.md +5 -9
- package/agentes/frontend-swl.md +5 -9
- package/agentes/frontend-tailwind-swl.md +5 -9
- package/agentes/implementador-swl.md +6 -3
- package/agentes/investigador-swl.md +5 -3
- package/agentes/investigador-ux-swl.md +5 -9
- package/agentes/llm-apps-swl.md +5 -3
- package/agentes/migrador-swl.md +6 -3
- package/agentes/mobile-android-swl.md +5 -3
- package/agentes/mobile-cross-swl.md +5 -3
- package/agentes/mobile-ios-swl.md +5 -3
- package/agentes/mobile-testing-swl.md +5 -3
- package/agentes/notificador-swl.md +5 -3
- package/agentes/observabilidad-swl.md +5 -3
- package/agentes/orquestador-swl.md +29 -8
- package/agentes/pagos-swl.md +5 -3
- package/agentes/perfilador-usuario-swl.md +4 -2
- package/agentes/planificador-swl.md +5 -3
- package/agentes/producto-prd-swl.md +5 -3
- package/agentes/red-team-swl.md +4 -2
- package/agentes/release-manager-swl.md +6 -8
- package/agentes/rendimiento-swl.md +5 -6
- package/agentes/resolutor-build-swl.md +5 -3
- package/agentes/revisor-angular-swl.md +5 -3
- package/agentes/revisor-codigo-swl.md +90 -4
- package/agentes/revisor-csharp-swl.md +5 -3
- package/agentes/revisor-go-swl.md +5 -3
- package/agentes/revisor-java-swl.md +5 -3
- package/agentes/revisor-kotlin-swl.md +5 -3
- package/agentes/revisor-nextjs-swl.md +5 -3
- package/agentes/revisor-php-swl.md +5 -3
- package/agentes/revisor-react-swl.md +5 -3
- package/agentes/revisor-rust-swl.md +5 -3
- package/agentes/revisor-seguridad-swl.md +5 -3
- package/agentes/revisor-swift-swl.md +5 -3
- package/agentes/revisor-typescript-swl.md +5 -3
- package/agentes/sre-swl.md +5 -3
- package/agentes/tdd-qa-swl.md +5 -3
- package/agentes/ux-disenador-swl.md +5 -9
- package/comandos/swl/evaluar-skill.md +18 -0
- package/comandos/swl/evolucion-estado.md +49 -0
- package/comandos/swl/release.md +77 -1
- package/comandos/swl/salud.md +23 -0
- package/habilidades/checklist-seguridad/SKILL.md +57 -1
- package/habilidades/extractor-de-aprendizajes/SKILL.md +15 -5
- package/habilidades/fastapi-experto/SKILL.md +10 -1
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/manejo-errores/SKILL.md +63 -4
- package/habilidades/patrones-python/SKILL.md +5 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/release-semver/SKILL.md +85 -1
- package/hooks/auto-evolucion.js +35 -1
- package/hooks/clasificador-mensajes.js +50 -3
- package/hooks/lib/agent-routing.js +107 -0
- package/hooks/lib/delegation-tracker.js +162 -44
- package/hooks/lib/evolution-tracker.js +12 -3
- package/hooks/lib/memory-search.js +59 -1
- package/hooks/lib/nudge-tracker.js +10 -1
- package/hooks/lib/provenance-tracker.js +11 -3
- package/hooks/lib/text-similarity.js +241 -0
- package/hooks/metricas-evolucion.js +168 -1
- package/hooks/monitor-contexto.js +54 -6
- package/hooks/preservar-estado-pre-compact.js +11 -1
- package/hooks/risk-scoring.js +10 -1
- package/hooks/tracking-costos.js +10 -1
- package/hooks/validar-formato-post-subagente.js +140 -0
- package/hooks/validar-memoria-hook.js +218 -0
- package/manifiestos/agent-output-schemas.json +57 -0
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +3 -0
- package/manifiestos/skills-lock.json +1065 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/reglas/arquitectura.md +20 -0
- package/reglas/fragmentos-compartidos.md +152 -0
- package/reglas/gobernanza.md +10 -1
- package/reglas/seguridad-agentes.md +12 -0
- package/reglas/skills-estandar.md +19 -0
- package/schemas/agent-frontmatter.schema.json +18 -0
- package/scripts/auditar-agentes-gaps.js +9 -1
- package/scripts/auditar-cobertura-frameworks.js +9 -1
- package/scripts/auditar-skills-gaps.js +9 -1
- package/scripts/bootstrap-instintos.js +11 -1
- package/scripts/generar-inventario.js +112 -9
- package/scripts/generar-matriz-lenguajes.js +271 -0
- package/scripts/generar-skills-lock.js +190 -0
- package/scripts/lib/estado.js +12 -2
- package/scripts/lib/gitignore-manifest.js +32 -2
- package/scripts/migrar-csv-a-array.js +168 -0
- package/scripts/migrar-fase-dominio.js +201 -0
- package/scripts/publicar.js +88 -18
|
@@ -0,0 +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 };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Migración Sprint 2B — añade campos `fase` y `dominio` al frontmatter de los
|
|
4
|
+
* 59 agentes de SWL. Skip si ya los tiene. Inserta antes de `exclusiones:` o
|
|
5
|
+
* al final del frontmatter.
|
|
6
|
+
*
|
|
7
|
+
* Idempotente: corre dos veces sin efectos.
|
|
8
|
+
*
|
|
9
|
+
* Uso:
|
|
10
|
+
* node scripts/migrar-fase-dominio.js [--dry-run]
|
|
11
|
+
*
|
|
12
|
+
* Origen: análisis de temp/ + propuesta consolidada (mayo 2026).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const AGENTES_DIR = path.join(__dirname, '..', 'agentes');
|
|
21
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
22
|
+
|
|
23
|
+
// Mapeo agente → (fase, dominio). Vocabulario controlado del schema.
|
|
24
|
+
const MAPEO = {
|
|
25
|
+
'accesibilidad-wcag-swl': ['verify', 'ux'],
|
|
26
|
+
'arquitecto-swl': ['plan', 'general'],
|
|
27
|
+
'auto-evolucion-swl': ['learn', 'meta'],
|
|
28
|
+
'backend-api-swl': ['implement', 'backend'],
|
|
29
|
+
'backend-csharp-swl': ['implement', 'backend'],
|
|
30
|
+
'backend-go-swl': ['implement', 'backend'],
|
|
31
|
+
'backend-java-swl': ['implement', 'backend'],
|
|
32
|
+
'backend-node-swl': ['implement', 'backend'],
|
|
33
|
+
'backend-python-swl': ['implement', 'backend'],
|
|
34
|
+
'backend-rust-swl': ['implement', 'backend'],
|
|
35
|
+
'backend-workers-swl': ['implement', 'backend'],
|
|
36
|
+
'cloud-infra-swl': ['release', 'infra'],
|
|
37
|
+
'consolidador-swl': ['learn', 'meta'],
|
|
38
|
+
'datos-swl': ['implement', 'data'],
|
|
39
|
+
'depurador-swl': ['implement', 'general'],
|
|
40
|
+
'devops-ci-swl': ['release', 'infra'],
|
|
41
|
+
'disenador-ui-swl': ['plan', 'ux'],
|
|
42
|
+
'documentador-swl': ['meta', 'docs'],
|
|
43
|
+
'frontend-angular-swl': ['implement', 'frontend'],
|
|
44
|
+
'frontend-css-swl': ['implement', 'frontend'],
|
|
45
|
+
'frontend-react-swl': ['implement', 'frontend'],
|
|
46
|
+
'frontend-swl': ['implement', 'frontend'],
|
|
47
|
+
'frontend-tailwind-swl': ['implement', 'frontend'],
|
|
48
|
+
'implementador-swl': ['implement', 'general'],
|
|
49
|
+
'investigador-swl': ['discover', 'general'],
|
|
50
|
+
'investigador-ux-swl': ['discover', 'ux'],
|
|
51
|
+
'llm-apps-swl': ['implement', 'backend'],
|
|
52
|
+
'migrador-swl': ['implement', 'data'],
|
|
53
|
+
'mobile-android-swl': ['implement', 'mobile'],
|
|
54
|
+
'mobile-cross-swl': ['implement', 'mobile'],
|
|
55
|
+
'mobile-ios-swl': ['implement', 'mobile'],
|
|
56
|
+
'mobile-testing-swl': ['verify', 'quality'],
|
|
57
|
+
'notificador-swl': ['meta', 'meta'],
|
|
58
|
+
'observabilidad-swl': ['release', 'infra'],
|
|
59
|
+
'orquestador-swl': ['meta', 'process'],
|
|
60
|
+
'pagos-swl': ['implement', 'backend'],
|
|
61
|
+
'perfilador-usuario-swl': ['meta', 'meta'],
|
|
62
|
+
'planificador-swl': ['plan', 'process'],
|
|
63
|
+
'producto-prd-swl': ['discover', 'process'],
|
|
64
|
+
'red-team-swl': ['verify', 'security'],
|
|
65
|
+
'release-manager-swl': ['release', 'process'],
|
|
66
|
+
'rendimiento-swl': ['verify', 'quality'],
|
|
67
|
+
'resolutor-build-swl': ['implement', 'general'],
|
|
68
|
+
'revisor-angular-swl': ['verify', 'quality'],
|
|
69
|
+
'revisor-codigo-swl': ['verify', 'quality'],
|
|
70
|
+
'revisor-csharp-swl': ['verify', 'quality'],
|
|
71
|
+
'revisor-go-swl': ['verify', 'quality'],
|
|
72
|
+
'revisor-java-swl': ['verify', 'quality'],
|
|
73
|
+
'revisor-kotlin-swl': ['verify', 'quality'],
|
|
74
|
+
'revisor-nextjs-swl': ['verify', 'quality'],
|
|
75
|
+
'revisor-php-swl': ['verify', 'quality'],
|
|
76
|
+
'revisor-react-swl': ['verify', 'quality'],
|
|
77
|
+
'revisor-rust-swl': ['verify', 'quality'],
|
|
78
|
+
'revisor-seguridad-swl': ['verify', 'security'],
|
|
79
|
+
'revisor-swift-swl': ['verify', 'quality'],
|
|
80
|
+
'revisor-typescript-swl': ['verify', 'quality'],
|
|
81
|
+
'sre-swl': ['release', 'infra'],
|
|
82
|
+
'tdd-qa-swl': ['verify', 'quality'],
|
|
83
|
+
'ux-disenador-swl': ['plan', 'ux'],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function migrarAgente(nombre) {
|
|
87
|
+
const archivo = path.join(AGENTES_DIR, `${nombre}.md`);
|
|
88
|
+
if (!fs.existsSync(archivo)) {
|
|
89
|
+
return { nombre, status: 'no-existe' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const par = MAPEO[nombre];
|
|
93
|
+
if (!par) {
|
|
94
|
+
return { nombre, status: 'sin-mapeo' };
|
|
95
|
+
}
|
|
96
|
+
const [fase, dominio] = par;
|
|
97
|
+
|
|
98
|
+
const contenido = fs.readFileSync(archivo, 'utf-8');
|
|
99
|
+
|
|
100
|
+
// Detectar el frontmatter delimitado por `---` (acepta CRLF y LF)
|
|
101
|
+
const match = contenido.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return { nombre, status: 'sin-frontmatter' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const frontmatter = match[1];
|
|
107
|
+
|
|
108
|
+
// Idempotencia: skip si ya tiene fase y dominio
|
|
109
|
+
const tieneFase = /^fase:\s*\S+/m.test(frontmatter);
|
|
110
|
+
const tieneDominio = /^dominio:\s*\S+/m.test(frontmatter);
|
|
111
|
+
if (tieneFase && tieneDominio) {
|
|
112
|
+
return { nombre, status: 'ya-migrado', fase, dominio };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Insertar antes de `exclusiones:` o al final del frontmatter
|
|
116
|
+
let nuevoFrontmatter;
|
|
117
|
+
if (/^exclusiones:/m.test(frontmatter)) {
|
|
118
|
+
nuevoFrontmatter = frontmatter.replace(
|
|
119
|
+
/^(exclusiones:)/m,
|
|
120
|
+
`fase: ${fase}\ndominio: ${dominio}\n$1`
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
// Sin exclusiones: agregar al final del frontmatter
|
|
124
|
+
nuevoFrontmatter = frontmatter.trimEnd() + `\nfase: ${fase}\ndominio: ${dominio}\n`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Preservar el line ending detectado
|
|
128
|
+
const eol = match[0].includes('\r\n') ? '\r\n' : '\n';
|
|
129
|
+
const fm = nuevoFrontmatter.replace(/\r\n/g, '\n').replace(/\n/g, eol);
|
|
130
|
+
const nuevoContenido = contenido.replace(match[0], `---${eol}${fm}${eol}---${eol}`);
|
|
131
|
+
|
|
132
|
+
if (DRY_RUN) {
|
|
133
|
+
return { nombre, status: 'dry-run-ok', fase, dominio };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fs.writeFileSync(archivo, nuevoContenido, 'utf-8');
|
|
137
|
+
return { nombre, status: 'migrado', fase, dominio };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function main() {
|
|
141
|
+
console.log(`Migrando fase/dominio en agentes${DRY_RUN ? ' (dry-run)' : ''}...\n`);
|
|
142
|
+
|
|
143
|
+
const archivosEnDisco = fs
|
|
144
|
+
.readdirSync(AGENTES_DIR)
|
|
145
|
+
.filter((f) => f.endsWith('.md') && !f.startsWith('_'))
|
|
146
|
+
.map((f) => f.replace(/\.md$/, ''));
|
|
147
|
+
|
|
148
|
+
const resultados = archivosEnDisco.map(migrarAgente);
|
|
149
|
+
|
|
150
|
+
const stats = {
|
|
151
|
+
migrado: 0,
|
|
152
|
+
'ya-migrado': 0,
|
|
153
|
+
'dry-run-ok': 0,
|
|
154
|
+
'sin-mapeo': [],
|
|
155
|
+
'sin-frontmatter': [],
|
|
156
|
+
'no-existe': [],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
for (const r of resultados) {
|
|
160
|
+
if (typeof stats[r.status] === 'number') {
|
|
161
|
+
stats[r.status]++;
|
|
162
|
+
} else if (Array.isArray(stats[r.status])) {
|
|
163
|
+
stats[r.status].push(r.nombre);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`Migrados: ${stats.migrado}`);
|
|
168
|
+
console.log(`Ya migrados (skip): ${stats['ya-migrado']}`);
|
|
169
|
+
console.log(`Dry-run OK: ${stats['dry-run-ok']}`);
|
|
170
|
+
if (stats['sin-mapeo'].length) {
|
|
171
|
+
console.log(`\n⚠️ Sin mapeo en MAPEO (${stats['sin-mapeo'].length}):`);
|
|
172
|
+
stats['sin-mapeo'].forEach((n) => console.log(` - ${n}`));
|
|
173
|
+
}
|
|
174
|
+
if (stats['sin-frontmatter'].length) {
|
|
175
|
+
console.log(`\n❌ Sin frontmatter detectable:`);
|
|
176
|
+
stats['sin-frontmatter'].forEach((n) => console.log(` - ${n}`));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Validación: el mapeo debe cubrir todos los agentes en disco
|
|
180
|
+
const enMapeo = new Set(Object.keys(MAPEO));
|
|
181
|
+
const enDisco = new Set(archivosEnDisco);
|
|
182
|
+
const faltanEnMapeo = [...enDisco].filter((n) => !enMapeo.has(n));
|
|
183
|
+
const sobranEnMapeo = [...enMapeo].filter((n) => !enDisco.has(n));
|
|
184
|
+
|
|
185
|
+
if (faltanEnMapeo.length) {
|
|
186
|
+
console.log(`\n⚠️ Agentes en disco sin entrada en MAPEO:`);
|
|
187
|
+
faltanEnMapeo.forEach((n) => console.log(` - ${n}`));
|
|
188
|
+
}
|
|
189
|
+
if (sobranEnMapeo.length) {
|
|
190
|
+
console.log(`\nℹ️ Entradas en MAPEO sin archivo en disco (probablemente renombrados):`);
|
|
191
|
+
sobranEnMapeo.forEach((n) => console.log(` - ${n}`));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
process.exit(stats['sin-frontmatter'].length || faltanEnMapeo.length ? 1 : 0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (require.main === module) {
|
|
198
|
+
main();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { MAPEO, migrarAgente };
|
package/scripts/publicar.js
CHANGED
|
@@ -62,6 +62,26 @@ function leerPkg() {
|
|
|
62
62
|
return JSON.parse(fs.readFileSync(PKG_PATH, 'utf-8'));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Clasifica el error de `npm whoami` capturado en stderr para distinguir
|
|
67
|
+
* causas raíz comunes y permitir mensajes accionables al usuario.
|
|
68
|
+
*
|
|
69
|
+
* Tipos retornados:
|
|
70
|
+
* - 'no-token' : no hay _authToken configurado para ese registry.
|
|
71
|
+
* - 'token-401' : token presente pero rechazado (expirado/revocado).
|
|
72
|
+
* - 'token-403' : token válido pero sin permiso (cuenta sin acceso al scope).
|
|
73
|
+
* - 'registry-404' : el registry no responde el endpoint whoami (URL incorrecta).
|
|
74
|
+
* - 'desconocido' : error de red, npm no en PATH, timeout, etc.
|
|
75
|
+
*/
|
|
76
|
+
function clasificarErrorAuth(stderr, mensaje) {
|
|
77
|
+
const blob = (stderr || '') + '\n' + (mensaje || '');
|
|
78
|
+
if (/\b401\b/.test(blob)) return 'token-401';
|
|
79
|
+
if (/\b403\b/.test(blob)) return 'token-403';
|
|
80
|
+
if (/\b404\b/.test(blob)) return 'registry-404';
|
|
81
|
+
if (/ENEEDAUTH|need to authorize/i.test(blob)) return 'no-token';
|
|
82
|
+
return 'desconocido';
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
function verificarLogin(registry) {
|
|
66
86
|
try {
|
|
67
87
|
const result = npmExec(['whoami', `--registry=${registry}`], {
|
|
@@ -69,16 +89,66 @@ function verificarLogin(registry) {
|
|
|
69
89
|
encoding: 'utf-8',
|
|
70
90
|
timeout: 15_000,
|
|
71
91
|
});
|
|
72
|
-
return String(result).trim();
|
|
92
|
+
return { ok: true, usuario: String(result).trim() };
|
|
73
93
|
} catch (err) {
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
process.stderr.write(`[verificarLogin ${registry}] ${motivo}\n`);
|
|
80
|
-
return
|
|
94
|
+
// Capturar stderr del proceso para clasificar la causa raíz.
|
|
95
|
+
// execFileSync expone stderr en err.stderr cuando stdio fue 'pipe'.
|
|
96
|
+
const stderrBuf = err.stderr ? String(err.stderr) : '';
|
|
97
|
+
const motivo = (stderrBuf || String(err.message || err)).split('\n')[0].slice(0, 200);
|
|
98
|
+
const tipo = clasificarErrorAuth(stderrBuf, err.message);
|
|
99
|
+
process.stderr.write(`[verificarLogin ${registry}] ${tipo}: ${motivo}\n`);
|
|
100
|
+
return { ok: false, tipo, motivo };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Imprime guía accionable según el tipo de fallo de auth y el registry.
|
|
106
|
+
* Se llama desde publicarNpmjs/publicarGitHub cuando verificarLogin falla.
|
|
107
|
+
*/
|
|
108
|
+
function imprimirGuiaAuth(registry, pkgName, tipo) {
|
|
109
|
+
const esGithub = /npm\.pkg\.github\.com/.test(registry);
|
|
110
|
+
console.error('');
|
|
111
|
+
switch (tipo) {
|
|
112
|
+
case 'token-401':
|
|
113
|
+
console.error('CAUSA: token de autenticación rechazado (HTTP 401 — expirado o revocado).');
|
|
114
|
+
if (esGithub) {
|
|
115
|
+
console.error('FIX: GitHub Packages NO acepta `npm login`. Genera un nuevo PAT en');
|
|
116
|
+
console.error(' Settings → Developer settings → Personal access tokens (classic)');
|
|
117
|
+
console.error(' con scopes `read:packages` y `write:packages`, y reemplaza la línea');
|
|
118
|
+
console.error(' en ~/.npmrc:');
|
|
119
|
+
console.error(' //npm.pkg.github.com/:_authToken=<NUEVO_TOKEN>');
|
|
120
|
+
} else {
|
|
121
|
+
console.error(`FIX: npm login --registry=${registry}`);
|
|
122
|
+
console.error(' (abrirá el navegador para autenticar y reescribirá ~/.npmrc).');
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case 'token-403':
|
|
126
|
+
console.error('CAUSA: token válido pero la cuenta no tiene permiso al scope del paquete.');
|
|
127
|
+
console.error(`FIX: Verifica el dueño del scope con:`);
|
|
128
|
+
console.error(` npm owner ls ${pkgName} --registry=${registry}`);
|
|
129
|
+
console.error(' Si la cuenta autenticada no aparece como owner, autenticate con la');
|
|
130
|
+
console.error(' cuenta dueña del scope o pide ser agregado como maintainer.');
|
|
131
|
+
break;
|
|
132
|
+
case 'registry-404':
|
|
133
|
+
console.error('CAUSA: el registry no responde el endpoint whoami (URL probablemente incorrecta).');
|
|
134
|
+
console.error(`FIX: Verifica que la URL sea exactamente '${registry}' (incluyendo https:// y trailing slash si aplica).`);
|
|
135
|
+
break;
|
|
136
|
+
case 'no-token':
|
|
137
|
+
console.error('CAUSA: no hay token configurado para este registry en ~/.npmrc.');
|
|
138
|
+
if (esGithub) {
|
|
139
|
+
console.error('FIX: GitHub Packages requiere PAT manual. Agrega a ~/.npmrc:');
|
|
140
|
+
console.error(' //npm.pkg.github.com/:_authToken=<TU_PAT>');
|
|
141
|
+
console.error(' (NO uses `npm login` con GitHub Packages — devuelve 404/403.)');
|
|
142
|
+
} else {
|
|
143
|
+
console.error(`FIX: npm login --registry=${registry}`);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
console.error('CAUSA: error desconocido al consultar whoami.');
|
|
148
|
+
console.error(' Verifica conectividad de red y que `npm` esté en PATH.');
|
|
149
|
+
console.error(` Para diagnóstico manual: npm whoami --registry=${registry}`);
|
|
81
150
|
}
|
|
151
|
+
console.error('');
|
|
82
152
|
}
|
|
83
153
|
|
|
84
154
|
function copiarDir(src, dest) {
|
|
@@ -165,13 +235,13 @@ function publicarNpmjs(pkg, dryRun) {
|
|
|
165
235
|
console.log(`Paquete: ${pkg.name}@${pkg.version}`);
|
|
166
236
|
console.log(`Registry: ${NPMJS_REGISTRY}`);
|
|
167
237
|
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
console.error(
|
|
171
|
-
|
|
238
|
+
const auth = verificarLogin(NPMJS_REGISTRY);
|
|
239
|
+
if (!auth.ok) {
|
|
240
|
+
console.error(`ERROR: No autenticado en npmjs (${auth.tipo}).`);
|
|
241
|
+
imprimirGuiaAuth(NPMJS_REGISTRY, pkg.name, auth.tipo);
|
|
172
242
|
return false;
|
|
173
243
|
}
|
|
174
|
-
console.log(`Autenticado como: ${usuario}`);
|
|
244
|
+
console.log(`Autenticado como: ${auth.usuario}`);
|
|
175
245
|
|
|
176
246
|
const args = ['publish', `--registry=${NPMJS_REGISTRY}`, '--access', 'public'];
|
|
177
247
|
if (dryRun) args.push('--dry-run');
|
|
@@ -190,13 +260,13 @@ function publicarGitHub(pkg, dryRun) {
|
|
|
190
260
|
console.log(`Paquete: ${GITHUB_NAME}@${pkg.version}`);
|
|
191
261
|
console.log(`Registry: ${GITHUB_REGISTRY}`);
|
|
192
262
|
|
|
193
|
-
const
|
|
194
|
-
if (!
|
|
195
|
-
console.error(
|
|
196
|
-
|
|
263
|
+
const auth = verificarLogin(GITHUB_REGISTRY);
|
|
264
|
+
if (!auth.ok) {
|
|
265
|
+
console.error(`ERROR: No autenticado en GitHub Packages (${auth.tipo}).`);
|
|
266
|
+
imprimirGuiaAuth(GITHUB_REGISTRY, GITHUB_NAME, auth.tipo);
|
|
197
267
|
return false;
|
|
198
268
|
}
|
|
199
|
-
console.log(`Autenticado como: ${usuario}`);
|
|
269
|
+
console.log(`Autenticado como: ${auth.usuario}`);
|
|
200
270
|
|
|
201
271
|
const tmpDir = prepararDirectorioTemporal(pkg, GITHUB_NAME, GITHUB_REGISTRY);
|
|
202
272
|
|