@saulwade/swl-ses 2.4.2 → 2.5.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 +194 -241
- package/README.md +600 -597
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/abogado-diablo-swl.md +145 -0
- 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 +1 -1
- 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 +1 -1
- package/agentes/backend-python-swl.md +1 -1
- 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 +1 -1
- 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 +6 -1
- package/agentes/implementador-swl.md +1 -1
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +1 -1
- 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 +1 -1
- package/agentes/observabilidad-swl.md +438 -438
- 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 +218 -218
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +10 -1
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +1 -1
- 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 +1 -1
- package/agentes/tdd-qa-swl.md +393 -393
- package/bin/lib/bot-comandos.js +1 -1
- package/bin/swl-ses.js +6 -0
- package/comandos/swl/adoptar-proyecto.md +14 -2
- package/comandos/swl/configurar-ci.md +8 -1
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/discutir-fase.md +22 -118
- package/comandos/swl/fix.md +118 -0
- package/comandos/swl/nuevo-proyecto.md +54 -3
- package/comandos/swl/predecir.md +32 -2
- package/comandos/swl/seguridad.md +189 -0
- package/comandos/swl/status.md +5 -3
- package/habilidades/aprendizaje-continuo/SKILL.md +3 -1
- package/habilidades/discutir-fase/SKILL.md +84 -81
- package/habilidades/discutir-fase/recursos/plantilla-contexto.md +136 -0
- package/habilidades/doc-sync/SKILL.md +3 -1
- package/habilidades/doubt-driven-review/SKILL.md +15 -1
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/estructura-proyecto-claude/SKILL.md +11 -2
- package/habilidades/harness-claude-code/SKILL.md +3 -1
- package/habilidades/instalar-sistema/SKILL.md +3 -1
- package/habilidades/meta-reglas-extendido/SKILL.md +92 -0
- package/habilidades/meta-reglas-extendido/recursos/analisis-previo-tareas-grandes.md +186 -0
- package/habilidades/meta-reglas-extendido/recursos/analizar-directorios-antes-de-escribir.md +235 -0
- package/habilidades/meta-reglas-extendido/recursos/api-diseno.md +413 -0
- package/habilidades/meta-reglas-extendido/recursos/arquitectura.md +491 -0
- package/habilidades/meta-reglas-extendido/recursos/arreglar-al-detectar.md +264 -0
- package/habilidades/meta-reglas-extendido/recursos/debatir-antes-de-aceptar.md +152 -0
- package/habilidades/meta-reglas-extendido/recursos/git-workflow.md +259 -0
- package/habilidades/meta-reglas-extendido/recursos/gobernanza.md +291 -0
- package/habilidades/meta-reglas-extendido/recursos/memoria-consolidada.md +263 -0
- package/habilidades/meta-reglas-extendido/recursos/seguridad-agentes.md +443 -0
- package/habilidades/meta-reglas-extendido/recursos/sesiones-paralelas.md +190 -0
- package/habilidades/meta-reglas-extendido/recursos/sin-duplicacion-reglas-globales.md +179 -0
- package/habilidades/meta-reglas-extendido/recursos/skills-estandar.md +394 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-code-review-graph.md +156 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-context7.md +236 -0
- package/habilidades/meta-reglas-extendido/recursos/usar-sistema-swl.md +253 -0
- package/habilidades/meta-reglas-extendido/recursos/verificar-citas-normativas.md +527 -0
- package/habilidades/meta-skills-estandar/SKILL.md +3 -1
- package/habilidades/nuevo-proyecto/SKILL.md +20 -3
- package/habilidades/php-experto/SKILL.md +10 -3
- package/habilidades/{filament-admin/SKILL.md → php-experto/recursos/filament-admin.md} +23 -39
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +5 -4
- package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -0
- package/hooks/check-update.js +19 -10
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/degradacion-instintos.js +1 -1
- package/hooks/extraccion-aprendizajes.js +2 -2
- package/hooks/lib/briefing.js +3 -3
- package/hooks/lib/nudge-tracker.js +1 -1
- package/hooks/lib/otlp-exporter.js +1 -1
- package/hooks/lib/webhook-dedup.js +1 -1
- package/hooks/session-briefing.js +1 -1
- package/llms.txt +6 -6
- package/manifiestos/canonical-hashes.json +989 -0
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/modulos.json +168 -135
- package/manifiestos/perfiles.json +0 -2
- package/manifiestos/skills-lock.json +52 -59
- package/package.json +7 -5
- package/plantillas/github-workflows/README.md +15 -1
- package/plantillas/github-workflows/swl-devsecops.yml +70 -0
- package/plugin.json +5 -5
- package/reglas/analisis-previo-tareas-grandes.md +30 -156
- package/reglas/analizar-directorios-antes-de-escribir.md +30 -211
- package/reglas/api-diseno.md +28 -398
- package/reglas/arquitectura.md +35 -456
- package/reglas/arreglar-al-detectar.md +30 -230
- package/reglas/debatir-antes-de-aceptar.md +30 -143
- package/reglas/docs.md +7 -0
- package/reglas/estilo-codigo.md +9 -0
- package/reglas/fragmentos-compartidos.md +6 -0
- package/reglas/git-workflow.md +44 -240
- package/reglas/gobernanza.md +23 -262
- package/reglas/memoria-consolidada.md +34 -228
- package/reglas/performance.md +8 -0
- package/reglas/pruebas.md +12 -0
- package/reglas/seguridad-agentes.md +37 -418
- package/reglas/seguridad.md +12 -0
- package/reglas/sesiones-paralelas.md +29 -162
- package/reglas/sin-duplicacion-reglas-globales.md +25 -166
- package/reglas/skills-estandar.md +23 -373
- package/reglas/usar-code-review-graph.md +31 -140
- package/reglas/usar-context7.md +30 -208
- package/reglas/usar-sistema-swl.md +47 -242
- package/reglas/verificar-citas-normativas.md +47 -537
- package/scripts/actualizar.js +253 -253
- package/scripts/audit-tools/auditar-relleno-inventario.js +145 -0
- package/scripts/auditar-clases-conocidas.js +106 -0
- package/scripts/bootstrap-instintos.js +2 -2
- package/scripts/canario-hooks.js +166 -0
- package/scripts/cli/configurar-ci.js +2 -1
- package/scripts/evidencia-valor.js +93 -0
- package/scripts/field-report.js +1 -1
- package/scripts/generar-comandos.js +143 -0
- package/scripts/generar-inventario.js +236 -23
- package/scripts/generar-matriz-lenguajes.js +1 -1
- package/scripts/instalador.js +15 -1
- package/scripts/lib/configurar-ci.js +10 -3
- package/scripts/lib/detectar-runtime.js +12 -3
- package/scripts/lib/diary-entry.js +3 -1
- package/scripts/lib/drift-detector.js +1 -1
- package/scripts/lib/evidencia-valor.js +189 -0
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/frontmatter-md.js +63 -0
- package/scripts/lib/parsear-opciones.js +2 -0
- package/scripts/lib/prune-componentes.js +180 -0
- package/scripts/lib/reglas-globales-conocidas.json +16 -2
- package/scripts/lib/scoring-instintos.js +2 -2
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/claude.js +1 -1
- package/scripts/lib/transformadores/codex.js +1 -1
- package/scripts/lib/transformadores/copilot.js +1 -1
- package/scripts/lib/transformadores/cursor.js +1 -1
- package/scripts/lib/transformadores/gemini.js +22 -2
- package/scripts/lib/transformadores/opencode.js +1 -1
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/prune.js +102 -0
- package/scripts/publicar.js +18 -2
- package/scripts/tui/pantallas/inspect.js +175 -175
- package/scripts/tui/pantallas/uninstall-wizard.js +210 -210
- package/scripts/tui/pantallas/update-wizard.js +234 -234
- package/scripts/tui/pantallas/welcome.js +189 -189
- package/habilidades/paid-media-tracking/SKILL.md +0 -269
- package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +0 -220
- package/habilidades/paid-media-tracking/recursos/google-ads-api.md +0 -215
- package/habilidades/tracking-measurement/SKILL.md +0 -239
- package/habilidades/tracking-measurement/recursos/consent-mode.md +0 -231
- package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +0 -216
- package/habilidades/tracking-measurement/recursos/meta-capi.md +0 -262
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* auditar-clases-conocidas.js — Gate de clases de bug ya documentadas.
|
|
6
|
+
*
|
|
7
|
+
* "El conocimiento documentado no previene — solo los gates previenen."
|
|
8
|
+
* Cada clase de este gate mordió al proyecto en producción pese a estar
|
|
9
|
+
* escrita como regla/aprendizaje. El gate la vuelve imposible de reintroducir:
|
|
10
|
+
*
|
|
11
|
+
* C1 — Fecha UTC truncada: toISOString + slice(0,10) fecha artefactos
|
|
12
|
+
* "mañana" desde México (23 instancias corregidas 2026-07-08/09).
|
|
13
|
+
* Correcto: `toLocaleDateString('sv')`. Escape: `// fecha-utc-ok`.
|
|
14
|
+
* (El patrón no se escribe literal aquí: el gate escanea este archivo
|
|
15
|
+
* también — se auto-detectó en el publish v2.5.1.)
|
|
16
|
+
* C2 — Comparación byte a byte en modos --check: comparar texto leído de
|
|
17
|
+
* disco sin normalizar EOL revienta con CRLF tras checkout/reset de
|
|
18
|
+
* git (tumbó el publish v2.5.0). Un script con modo --check que lee
|
|
19
|
+
* texto debe normalizar `\r\n` o comparar estructuras (JSON.parse).
|
|
20
|
+
* Escape: `// eol-ok: <razón>`.
|
|
21
|
+
* C3 — Bloqueo ciego en hooks: exit(2) sin console.error deja al modelo
|
|
22
|
+
* sin la razón del bloqueo (el canal de exit 2 es stderr).
|
|
23
|
+
*
|
|
24
|
+
* Alcance: archivos .js versionados en scripts/, hooks/, bin/, gateway/
|
|
25
|
+
* (tests/ excluido: los tests DEBEN poder citar el patrón para validarlo).
|
|
26
|
+
* Exit: 0 limpio · 1 con hallazgos.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const { execFileSync } = require('child_process');
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const RAIZ = path.resolve(__dirname, '..');
|
|
34
|
+
|
|
35
|
+
const RX_UTC = /toISOString\(\)\s*\.\s*(?:slice|substring)\s*\(\s*0\s*,\s*10\s*\)/;
|
|
36
|
+
const RX_ESCAPE_UTC = /fecha-utc-ok/;
|
|
37
|
+
const RX_ESCAPE_EOL = /eol-ok:/;
|
|
38
|
+
|
|
39
|
+
function archivosObjetivo() {
|
|
40
|
+
const salida = execFileSync('git', [
|
|
41
|
+
'ls-files', '--',
|
|
42
|
+
'scripts/*.js', 'scripts/**/*.js',
|
|
43
|
+
'hooks/*.js', 'hooks/**/*.js',
|
|
44
|
+
'bin/*.js',
|
|
45
|
+
'gateway/*.js', 'gateway/**/*.js',
|
|
46
|
+
], { cwd: RAIZ, encoding: 'utf8' });
|
|
47
|
+
return salida.split(/\r?\n/).filter(f => f && !f.includes('vendor/'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function auditarUtc(ruta, lineas, hallazgos) {
|
|
51
|
+
lineas.forEach((linea, i) => {
|
|
52
|
+
if (RX_UTC.test(linea) && !RX_ESCAPE_UTC.test(linea)) {
|
|
53
|
+
hallazgos.push(`[C1 fecha-UTC] ${ruta}:${i + 1} — toISOString().slice(0,10) fecha "mañana" desde UTC-6; usar toLocaleDateString('sv') o marcar \`// fecha-utc-ok\` si la semántica UTC es deliberada`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function auditarEolCheck(ruta, contenido, hallazgos) {
|
|
59
|
+
// Aplica solo a GENERADORES con modo --check (escriben el artefacto y en
|
|
60
|
+
// check comparan lo regenerado contra disco). Un --check selector de
|
|
61
|
+
// sub-checks (verificar-docs) no compara texto por igualdad — no aplica.
|
|
62
|
+
if (!contenido.includes("'--check'") && !contenido.includes('"--check"')) return;
|
|
63
|
+
if (!/atomicWrite|writeFileSync/.test(contenido)) return;
|
|
64
|
+
if (!/readFileSync\([^)]*utf-?8/i.test(contenido)) return;
|
|
65
|
+
const normaliza = contenido.includes('\\r\\n') || contenido.includes('\\r?\\n');
|
|
66
|
+
const comparaEstructura = contenido.includes('JSON.parse');
|
|
67
|
+
const escape = RX_ESCAPE_EOL.test(contenido);
|
|
68
|
+
if (!normaliza && !comparaEstructura && !escape) {
|
|
69
|
+
hallazgos.push(`[C2 EOL] ${ruta} — modo --check que lee texto sin normalizar \\r\\n ni comparar estructuras: reventará con CRLF tras checkout/reset de git (caso publish v2.5.0). Normalizar EOL antes de comparar o marcar \`// eol-ok: <razón>\``);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function auditarBloqueoCiego(ruta, contenido, hallazgos) {
|
|
74
|
+
if (!ruta.startsWith('hooks/') || ruta.startsWith('hooks/lib/')) return;
|
|
75
|
+
const usaExit2 = /exit\(2\)|exitCode\s*=\s*2/.test(contenido);
|
|
76
|
+
const escribeStderr = contenido.includes('console.error') || contenido.includes('process.stderr.write');
|
|
77
|
+
if (usaExit2 && !escribeStderr) {
|
|
78
|
+
hallazgos.push(`[C3 bloqueo-ciego] ${ruta} — usa exit 2 sin escribir a stderr (console.error/process.stderr.write): el modelo recibe el bloqueo sin la razón`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function main() {
|
|
83
|
+
const hallazgos = [];
|
|
84
|
+
const archivos = archivosObjetivo();
|
|
85
|
+
for (const ruta of archivos) {
|
|
86
|
+
let contenido;
|
|
87
|
+
try {
|
|
88
|
+
contenido = fs.readFileSync(path.join(RAIZ, ruta), 'utf8');
|
|
89
|
+
} catch { continue; }
|
|
90
|
+
const lineas = contenido.split(/\r?\n/);
|
|
91
|
+
auditarUtc(ruta, lineas, hallazgos);
|
|
92
|
+
auditarEolCheck(ruta, contenido, hallazgos);
|
|
93
|
+
auditarBloqueoCiego(ruta, contenido, hallazgos);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (hallazgos.length === 0) {
|
|
97
|
+
console.log(`[clases-conocidas] OK — ${archivos.length} archivos, 0 reincidencias de clases documentadas (C1 UTC, C2 EOL, C3 bloqueo ciego)`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.error(`[clases-conocidas] ${hallazgos.length} reincidencia(s) de clases de bug documentadas:`);
|
|
101
|
+
for (const h of hallazgos) console.error(' ' + h);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { auditarUtc, auditarEolCheck, auditarBloqueoCiego };
|
|
106
|
+
if (require.main === module) main();
|
|
@@ -159,7 +159,7 @@ function truncate(s, n) {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function generarInstintos(entradas) {
|
|
162
|
-
const hoy = new Date().
|
|
162
|
+
const hoy = new Date().toLocaleDateString('sv');
|
|
163
163
|
return entradas.map((e, idx) => {
|
|
164
164
|
const { confidence, tags } = detectarConfidence(e.seccion);
|
|
165
165
|
const pattern = truncate(e.titulo, 200);
|
|
@@ -190,7 +190,7 @@ function generarInstintos(entradas) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
function serializarYaml(instintos) {
|
|
193
|
-
const fechaActual = new Date().
|
|
193
|
+
const fechaActual = new Date().toLocaleDateString('sv');
|
|
194
194
|
const header = `# Instintos del Proyecto — SWL-SES
|
|
195
195
|
# Generado automáticamente por scripts/bootstrap-instintos.js desde .planning/APRENDIZAJES.md
|
|
196
196
|
# Formato estandarizado v1.0
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* canario-hooks.js — Canario de hooks: prueba que cada hook es viable y visible.
|
|
6
|
+
*
|
|
7
|
+
* Clase que cierra: "hook que funciona perfecto y nadie ve". Dos muertes
|
|
8
|
+
* silenciosas documentadas: (a) check-update requería libs de scripts/lib/ no
|
|
9
|
+
* distribuidas — MODULE_NOT_FOUND tragado por el wrapper en TODA instalación
|
|
10
|
+
* destino desde su creación; (b) avisos emitidos por stderr con exit 0 (canal
|
|
11
|
+
* invisible). La (b) la cubre auditar-clases-conocidas C3; este canario cubre
|
|
12
|
+
* la (a) y la ejecución real.
|
|
13
|
+
*
|
|
14
|
+
* Modo default (rápido — test:all):
|
|
15
|
+
* Por cada hook ejecutable de hooks/*.js, extrae su grafo de requires
|
|
16
|
+
* relativos (transitivo) y verifica que todo archivo que EXISTE en el repo
|
|
17
|
+
* esté registrado en manifiestos/modulos.json — si no, el instalador no lo
|
|
18
|
+
* distribuye y el hook muere en silencio en el destino. Paths de fallback
|
|
19
|
+
* que no existen en el repo (patrón de require tolerante) se omiten.
|
|
20
|
+
*
|
|
21
|
+
* Modo --smoke (test:release):
|
|
22
|
+
* Además EJECUTA cada hook en un sandbox temporal (cwd aislado — las
|
|
23
|
+
* escrituras de telemetría van al sandbox) con un evento JSON mínimo por
|
|
24
|
+
* stdin. FALLA si el hook crashea (exit ≠ 0 y ≠ 2). Timeout (5s) es WARN
|
|
25
|
+
* informativo, no fallo: un hook lento no rompe la sesión (el harness lo
|
|
26
|
+
* corta), pero un hook que crashea al cargar es la clase (a) viva.
|
|
27
|
+
* Reporta tabla de visibilidad (bytes stdout/stderr) para auditoría humana.
|
|
28
|
+
*
|
|
29
|
+
* Exit: 0 limpio · 1 con fallas.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const os = require('os');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
const { spawnSync } = require('child_process');
|
|
36
|
+
|
|
37
|
+
const RAIZ = path.resolve(__dirname, '..');
|
|
38
|
+
const SMOKE = process.argv.includes('--smoke');
|
|
39
|
+
const RX_REQUIRE = /require\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
40
|
+
|
|
41
|
+
function hooksEjecutables() {
|
|
42
|
+
// Forward slashes SIEMPRE: se comparan contra modulos.json (formato repo).
|
|
43
|
+
return fs.readdirSync(path.join(RAIZ, 'hooks'))
|
|
44
|
+
.filter(f => f.endsWith('.js'))
|
|
45
|
+
.map(f => `hooks/${f}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function archivosRegistrados() {
|
|
49
|
+
const m = JSON.parse(fs.readFileSync(path.join(RAIZ, 'manifiestos', 'modulos.json'), 'utf8'));
|
|
50
|
+
const set = new Set();
|
|
51
|
+
for (const mod of Object.values(m.modulos || {})) {
|
|
52
|
+
for (const a of mod.archivos || []) set.add(a.replace(/\\/g, '/'));
|
|
53
|
+
}
|
|
54
|
+
return set;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resuelve un require relativo a .js real (agrega .js / index.js). */
|
|
58
|
+
function resolverRequire(desdeArchivo, spec) {
|
|
59
|
+
const base = path.resolve(RAIZ, path.dirname(desdeArchivo), spec);
|
|
60
|
+
for (const cand of [base, `${base}.js`, path.join(base, 'index.js'), `${base}.json`]) {
|
|
61
|
+
if (fs.existsSync(cand) && fs.statSync(cand).isFile()) {
|
|
62
|
+
return path.relative(RAIZ, cand).replace(/\\/g, '/');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null; // fallback de require tolerante que solo existe en el destino
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Grafo transitivo de requires relativos que existen en el repo. */
|
|
69
|
+
function grafoRequires(archivoInicial) {
|
|
70
|
+
const vistos = new Set();
|
|
71
|
+
const pila = [archivoInicial];
|
|
72
|
+
while (pila.length > 0) {
|
|
73
|
+
const actual = pila.pop();
|
|
74
|
+
if (vistos.has(actual)) continue;
|
|
75
|
+
vistos.add(actual);
|
|
76
|
+
let contenido;
|
|
77
|
+
try { contenido = fs.readFileSync(path.join(RAIZ, actual), 'utf8'); } catch { continue; }
|
|
78
|
+
for (const m of contenido.matchAll(RX_REQUIRE)) {
|
|
79
|
+
const resuelto = resolverRequire(actual, m[1]);
|
|
80
|
+
if (resuelto && !vistos.has(resuelto)) pila.push(resuelto);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
vistos.delete(archivoInicial);
|
|
84
|
+
return [...vistos];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function verificarDistribucion(hooks, registrados, fallas) {
|
|
88
|
+
for (const hook of hooks) {
|
|
89
|
+
if (!registrados.has(hook)) {
|
|
90
|
+
fallas.push(`${hook} — el hook mismo no está registrado en modulos.json (no se distribuye)`);
|
|
91
|
+
}
|
|
92
|
+
for (const dep of grafoRequires(hook)) {
|
|
93
|
+
if (dep.endsWith('.json')) continue; // datos empaquetados aparte
|
|
94
|
+
if (!registrados.has(dep)) {
|
|
95
|
+
fallas.push(`${hook} — requiere ${dep}, que existe en el repo pero NO está en modulos.json: en el destino el require lanza MODULE_NOT_FOUND y el wrapper lo traga (clase check-update)`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const EVENTO_MINIMO = JSON.stringify({
|
|
102
|
+
session_id: 'canario-smoke',
|
|
103
|
+
hook_event_name: 'PostToolUse',
|
|
104
|
+
tool_name: 'Write',
|
|
105
|
+
tool_input: { file_path: 'canario.md', content: 'canario' },
|
|
106
|
+
tool_response: { success: true },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function smokeEjecutar(hooks, fallas) {
|
|
110
|
+
const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'swl-canario-'));
|
|
111
|
+
const tabla = [];
|
|
112
|
+
try {
|
|
113
|
+
for (const hook of hooks) {
|
|
114
|
+
const r = spawnSync(process.execPath, [path.join(RAIZ, hook)], {
|
|
115
|
+
cwd: sandbox,
|
|
116
|
+
input: EVENTO_MINIMO,
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
timeout: 5000,
|
|
119
|
+
env: { ...process.env, SWL_CANARIO_SMOKE: '1' },
|
|
120
|
+
});
|
|
121
|
+
const nombre = path.basename(hook);
|
|
122
|
+
if (r.error && r.error.code === 'ETIMEDOUT') {
|
|
123
|
+
tabla.push(` [WARN timeout] ${nombre} — >5s con evento mínimo (no rompe la sesión, pero revisar)`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const exit = r.status;
|
|
127
|
+
if (exit !== 0 && exit !== 2) {
|
|
128
|
+
const stderr = (r.stderr || '').split(/\r?\n/).find(l => l.trim()) || 'sin stderr';
|
|
129
|
+
fallas.push(`${hook} — crasheó con exit ${exit} ante evento mínimo: ${stderr.slice(0, 140)}`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const out = (r.stdout || '').length;
|
|
133
|
+
const err = (r.stderr || '').length;
|
|
134
|
+
tabla.push(` [ok exit ${exit}] ${nombre} — stdout:${out}B stderr:${err}B${exit === 0 && out === 0 && err > 0 ? ' ← solo stderr con exit 0: si esto es un aviso al usuario, es invisible (C3-runtime)' : ''}`);
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
try { fs.rmSync(sandbox, { recursive: true, force: true }); } catch { /* mejor esfuerzo */ }
|
|
138
|
+
}
|
|
139
|
+
return tabla;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function main() {
|
|
143
|
+
const hooks = hooksEjecutables();
|
|
144
|
+
const registrados = archivosRegistrados();
|
|
145
|
+
const fallas = [];
|
|
146
|
+
|
|
147
|
+
verificarDistribucion(hooks, registrados, fallas);
|
|
148
|
+
|
|
149
|
+
let tabla = [];
|
|
150
|
+
if (SMOKE) tabla = smokeEjecutar(hooks, fallas);
|
|
151
|
+
|
|
152
|
+
if (tabla.length > 0) {
|
|
153
|
+
console.log('[canario-hooks] Visibilidad por hook (smoke en sandbox):');
|
|
154
|
+
for (const l of tabla) console.log(l);
|
|
155
|
+
}
|
|
156
|
+
if (fallas.length === 0) {
|
|
157
|
+
console.log(`[canario-hooks] OK — ${hooks.length} hooks: grafo de requires distribuido completo${SMOKE ? ' + smoke sin crashes' : ''}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.error(`[canario-hooks] ${fallas.length} falla(s):`);
|
|
161
|
+
for (const f of fallas) console.error(' - ' + f);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { grafoRequires, resolverRequire };
|
|
166
|
+
if (require.main === module) main();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* relativos al proyecto (rotos downstream; ver docs/invocacion-cli-cross-scope.md).
|
|
7
7
|
*
|
|
8
8
|
* Ops (todas imprimen el resultado como JSON para que el comando lo inspeccione):
|
|
9
|
-
* init [--no-security] [--no-ci] [--with-release-please] [--dry-run] [--force]
|
|
9
|
+
* init [--no-security] [--no-ci] [--with-release-please] [--with-devsecops] [--dry-run] [--force]
|
|
10
10
|
* status (default)
|
|
11
11
|
* uninstall [--confirmar]
|
|
12
12
|
*/
|
|
@@ -21,6 +21,7 @@ function configurarCi(opciones = {}) {
|
|
|
21
21
|
withSecurity: !opciones['no-security'],
|
|
22
22
|
withCi: !opciones['no-ci'],
|
|
23
23
|
withReleasePlease: !!opciones['with-release-please'],
|
|
24
|
+
withDevsecops: !!opciones['with-devsecops'],
|
|
24
25
|
dryRun: !!opciones['dry-run'],
|
|
25
26
|
force: !!opciones.force,
|
|
26
27
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* evidencia-valor.js — CLI `swl-ses valor`: panel de evidencia de valor del
|
|
6
|
+
* harness en el proyecto downstream actual.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* swl-ses valor # panel legible (ventana 30 días)
|
|
10
|
+
* swl-ses valor --dias 90 # ventana distinta
|
|
11
|
+
* swl-ses valor --json # salida JSON para tooling
|
|
12
|
+
*
|
|
13
|
+
* Reglas duras del panel (docs/evidencia-valor.md):
|
|
14
|
+
* - Cada métrica declara su PROCEDENCIA (git / GitHub / hook-event) y su
|
|
15
|
+
* CARÁCTER (evidencia | proxy | contexto). Nunca scores autodeclarados.
|
|
16
|
+
* - Todo se lee LOCAL. El repo madre no lee estos datos.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { panelEvidencia } = require('./lib/evidencia-valor.js');
|
|
20
|
+
|
|
21
|
+
function main() {
|
|
22
|
+
const argv = process.argv.slice(2);
|
|
23
|
+
const json = argv.includes('--json');
|
|
24
|
+
const idx = argv.indexOf('--dias');
|
|
25
|
+
const ventanaDias = idx !== -1 ? parseInt(argv[idx + 1], 10) || 30 : 30;
|
|
26
|
+
|
|
27
|
+
const p = panelEvidencia(process.cwd(), { ventanaDias });
|
|
28
|
+
|
|
29
|
+
if (json) {
|
|
30
|
+
console.log(JSON.stringify(p, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`Evidencia de valor — ventana ${p.ventanaDias} días (${p.generado})`);
|
|
35
|
+
console.log(` ${p.frontera}`);
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
const i = p.intercepciones;
|
|
39
|
+
console.log('1. Intercepciones de gates [evidencia · hook-event]');
|
|
40
|
+
if (!i.disponible) {
|
|
41
|
+
console.log(` no disponible — ${i.razon}`);
|
|
42
|
+
} else {
|
|
43
|
+
console.log(` intervenciones: ${i.intervenciones} de ${i.totalEventos} eventos`);
|
|
44
|
+
const tipos = Object.entries(i.porTipo).sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
45
|
+
for (const [t, n] of tipos) console.log(` - ${t}: ${n}`);
|
|
46
|
+
}
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
const d = p.defectos;
|
|
50
|
+
console.log('2. Presión de defectos [proxy · git log]');
|
|
51
|
+
if (!d.disponible) {
|
|
52
|
+
console.log(` no disponible — ${d.razon}`);
|
|
53
|
+
} else {
|
|
54
|
+
console.log(` commits: ${d.commits} | fix: ${d.fixes} | feat: ${d.feats} | ratio fix/feat: ${d.ratioFixFeat ?? 'n/a'}`);
|
|
55
|
+
if (d.escapes.disponible) {
|
|
56
|
+
console.log(` escapes: ${d.escapes.fixesTrasUltimaVerificacion} fix tras la última verificación (${d.escapes.ultimaVerificacion}) — ${d.escapes.nota}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(` escapes: no disponible — ${d.escapes.razon}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
const dc = p.doraCi;
|
|
64
|
+
console.log('3. DORA-lite + CI [evidencia de tendencia · git + GitHub]');
|
|
65
|
+
if (!dc.disponible) {
|
|
66
|
+
console.log(` no disponible — ${dc.razon}`);
|
|
67
|
+
} else {
|
|
68
|
+
const dora = dc.dora;
|
|
69
|
+
const nd = m => `n/d (${(m && m.razon) || 'sin datos'})`;
|
|
70
|
+
const df = dora.deploymentFrequency;
|
|
71
|
+
console.log(` deployment frequency: ${df && df.disponible !== false && df.valor != null ? `${df.valor} deploys/semana [${df.clasificacion}]` : nd(df)}`);
|
|
72
|
+
const lt = dora.leadTime;
|
|
73
|
+
console.log(` lead time: ${lt && lt.disponible !== false && lt.p50Horas != null ? `p50 ${(+lt.p50Horas).toFixed(1)}h / p95 ${(+lt.p95Horas).toFixed(1)}h [${lt.clasificacion}]` : nd(lt)}`);
|
|
74
|
+
const cfr = dora.changeFailureRate;
|
|
75
|
+
console.log(` change failure rate: ${cfr && cfr.disponible !== false && cfr.valor != null ? `${cfr.valor}% [${cfr.clasificacion || ''}]` : nd(cfr)}`);
|
|
76
|
+
const mt = dora.mttr;
|
|
77
|
+
console.log(` mttr: ${mt && mt.disponible !== false && mt.mttrHoras != null ? `${mt.mttrHoras}h` : nd(mt)}`);
|
|
78
|
+
console.log(' (correlación con la adopción del flujo — no causalidad)');
|
|
79
|
+
}
|
|
80
|
+
console.log('');
|
|
81
|
+
|
|
82
|
+
const u = p.uso;
|
|
83
|
+
console.log('4. Uso del sistema [contexto · telemetría]');
|
|
84
|
+
if (!u.disponible) {
|
|
85
|
+
console.log(` no disponible — ${u.razon}`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` invocaciones de skills: ${u.invocaciones} | top: ${u.top5.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log('Compartir agregados con el autor (opt-in): swl-ses field-report — ver docs/evidencia-valor.md');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main();
|
package/scripts/field-report.js
CHANGED
|
@@ -163,7 +163,7 @@ function generarReporte() {
|
|
|
163
163
|
const sorted = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1]);
|
|
164
164
|
|
|
165
165
|
console.log('# Reporte de Validación en Campo — swl-ses');
|
|
166
|
-
console.log(`\n**Fecha**: ${new Date().
|
|
166
|
+
console.log(`\n**Fecha**: ${new Date().toLocaleDateString('sv')}`);
|
|
167
167
|
console.log(`**Sesiones analizadas**: ${totales.sesiones}`);
|
|
168
168
|
console.log(`**Proyectos**: ${totales.proyectos.size}`);
|
|
169
169
|
console.log(`**Tool calls totales**: ${totales.toolCalls}`);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* generar-comandos.js — Regenera los bloques de catálogo de comandos /swl:*
|
|
6
|
+
* en COMANDOS.md y AGENTS.md desde el frontmatter de comandos/swl/*.md.
|
|
7
|
+
*
|
|
8
|
+
* La fuente de verdad es el frontmatter (name, description, argument-hint);
|
|
9
|
+
* mantener las tablas a mano acumuló drift recurrente (auditoría 2026-07-08:
|
|
10
|
+
* 6 comandos ausentes en AGENTS.md, flags desactualizados en COMANDOS.md).
|
|
11
|
+
* Solo se reescribe el contenido entre los marcadores:
|
|
12
|
+
*
|
|
13
|
+
* <!-- BEGIN:CATALOGO-COMANDOS ... -->
|
|
14
|
+
* <!-- END:CATALOGO-COMANDOS -->
|
|
15
|
+
*
|
|
16
|
+
* La prosa de decisión (matriz situación→comando, gates) queda fuera de los
|
|
17
|
+
* marcadores y se mantiene a mano.
|
|
18
|
+
*
|
|
19
|
+
* Uso:
|
|
20
|
+
* node scripts/generar-comandos.js # regenera ambos archivos
|
|
21
|
+
* node scripts/generar-comandos.js --check # exit 2 si hay drift (uso CI)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { leerFrontmatter, resumir } = require('./lib/frontmatter-md.js');
|
|
27
|
+
|
|
28
|
+
const RAIZ = path.resolve(__dirname, '..');
|
|
29
|
+
|
|
30
|
+
let atomicWriteSync;
|
|
31
|
+
try {
|
|
32
|
+
({ atomicWriteSync } = require(path.join(RAIZ, 'hooks', 'lib', 'atomic-write.js')));
|
|
33
|
+
} catch {
|
|
34
|
+
atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CHECK = process.argv.includes('--check');
|
|
38
|
+
const MARCA_INICIO = /<!-- BEGIN:CATALOGO-COMANDOS[^>]*-->/;
|
|
39
|
+
const MARCA_FIN = '<!-- END:CATALOGO-COMANDOS -->';
|
|
40
|
+
|
|
41
|
+
function recolectarComandos() {
|
|
42
|
+
const dir = path.join(RAIZ, 'comandos', 'swl');
|
|
43
|
+
return fs.readdirSync(dir)
|
|
44
|
+
.filter(f => f.endsWith('.md'))
|
|
45
|
+
.map(f => {
|
|
46
|
+
const fm = leerFrontmatter(path.join(dir, f));
|
|
47
|
+
return {
|
|
48
|
+
archivo: f,
|
|
49
|
+
name: fm.name || `swl:${f.replace('.md', '')}`,
|
|
50
|
+
description: fm.description || '',
|
|
51
|
+
argumentHint: fm['argument-hint'] || fm.argument_hint || '—',
|
|
52
|
+
};
|
|
53
|
+
})
|
|
54
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Tabla completa para COMANDOS.md: comando + argumentos/flags + descripción. */
|
|
58
|
+
function bloqueComandosMd(comandos) {
|
|
59
|
+
const L = [];
|
|
60
|
+
L.push(`Catálogo de los ${comandos.length} comandos, generado desde el frontmatter`);
|
|
61
|
+
L.push('de `comandos/swl/*.md` (fuente de verdad de flags y semántica).');
|
|
62
|
+
L.push('');
|
|
63
|
+
L.push('| Comando | Argumentos y flags | Descripción |');
|
|
64
|
+
L.push('|---------|--------------------|-------------|');
|
|
65
|
+
for (const c of comandos) {
|
|
66
|
+
L.push(`| \`/${c.name}\` | \`${resumir(c.argumentHint, 120)}\` | ${resumir(c.description, 260)} |`);
|
|
67
|
+
}
|
|
68
|
+
return L.join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Tabla compacta para AGENTS.md: comando + propósito. */
|
|
72
|
+
function bloqueAgentsMd(comandos) {
|
|
73
|
+
const L = [];
|
|
74
|
+
L.push(`Los ${comandos.length} comandos del sistema, generados desde el frontmatter`);
|
|
75
|
+
L.push('de `comandos/swl/*.md`. Detalle de flags: `COMANDOS.md`; operación: `MANUAL_USO.md`.');
|
|
76
|
+
L.push('');
|
|
77
|
+
L.push('| Comando | Propósito |');
|
|
78
|
+
L.push('|---------|-----------|');
|
|
79
|
+
for (const c of comandos) {
|
|
80
|
+
L.push(`| \`/${c.name}\` | ${resumir(c.description, 200)} |`);
|
|
81
|
+
}
|
|
82
|
+
return L.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Reemplaza el contenido entre marcadores. Devuelve null si faltan marcadores. */
|
|
86
|
+
function inyectar(contenido, bloque) {
|
|
87
|
+
const ini = contenido.search(MARCA_INICIO);
|
|
88
|
+
const fin = contenido.indexOf(MARCA_FIN);
|
|
89
|
+
if (ini === -1 || fin === -1 || fin < ini) return null;
|
|
90
|
+
const finMarcaIni = contenido.indexOf('-->', ini) + 3;
|
|
91
|
+
return contenido.slice(0, finMarcaIni) + '\n\n' + bloque + '\n\n' + contenido.slice(fin);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Aplica el bloque al contenido crudo del archivo, tolerante a EOL.
|
|
96
|
+
*
|
|
97
|
+
* La comparación se hace SIEMPRE en LF: con `core.autocrlf=true` git puede
|
|
98
|
+
* reescribir el working tree con CRLF sin que el contenido cambie (checkout,
|
|
99
|
+
* reset, stash) — un compare byte a byte reporta DRIFT falso con catálogo
|
|
100
|
+
* idéntico (caso real: reventó el `prepublishOnly` del publish v2.5.0). La
|
|
101
|
+
* salida preserva el EOL dominante del archivo para no producir mixtos.
|
|
102
|
+
*
|
|
103
|
+
* @returns {{estado: 'sin-marcadores'|'sin-cambios'|'drift', salida?: string}}
|
|
104
|
+
*/
|
|
105
|
+
function aplicarBloque(crudo, bloque) {
|
|
106
|
+
const eol = crudo.includes('\r\n') ? '\r\n' : '\n';
|
|
107
|
+
const actual = crudo.replace(/\r\n/g, '\n');
|
|
108
|
+
const nuevo = inyectar(actual, bloque);
|
|
109
|
+
if (nuevo === null) return { estado: 'sin-marcadores' };
|
|
110
|
+
if (nuevo === actual) return { estado: 'sin-cambios' };
|
|
111
|
+
return { estado: 'drift', salida: eol === '\r\n' ? nuevo.replace(/\n/g, '\r\n') : nuevo };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function procesar(archivo, bloque) {
|
|
115
|
+
const ruta = path.join(RAIZ, archivo);
|
|
116
|
+
const crudo = fs.readFileSync(ruta, 'utf8');
|
|
117
|
+
const r = aplicarBloque(crudo, bloque);
|
|
118
|
+
if (r.estado === 'sin-marcadores') {
|
|
119
|
+
console.error(`[generar-comandos] ${archivo}: faltan los marcadores BEGIN/END:CATALOGO-COMANDOS`);
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (r.estado === 'sin-cambios') {
|
|
124
|
+
console.log(`[generar-comandos] ${archivo}: sin cambios`);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (CHECK) {
|
|
128
|
+
console.error(`[generar-comandos] DRIFT en ${archivo}: el catálogo no coincide con el frontmatter. Regenerar con \`node scripts/generar-comandos.js\`.`);
|
|
129
|
+
process.exitCode = 2;
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
atomicWriteSync(ruta, r.salida, 'utf8');
|
|
133
|
+
console.log(`[generar-comandos] ${archivo}: catálogo regenerado (${bloque.split('\n').length} líneas)`);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { inyectar, aplicarBloque };
|
|
138
|
+
|
|
139
|
+
if (require.main === module) {
|
|
140
|
+
const comandos = recolectarComandos();
|
|
141
|
+
procesar('COMANDOS.md', bloqueComandosMd(comandos));
|
|
142
|
+
procesar('AGENTS.md', bloqueAgentsMd(comandos));
|
|
143
|
+
}
|