@saulwade/swl-ses 2.3.1 → 2.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.
Files changed (123) hide show
  1. package/CLAUDE.md +242 -240
  2. package/README.md +597 -597
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +908 -908
  8. package/agentes/backend-api-swl.md +472 -472
  9. package/agentes/backend-csharp-swl.md +420 -420
  10. package/agentes/backend-go-swl.md +390 -390
  11. package/agentes/backend-java-swl.md +281 -281
  12. package/agentes/backend-node-swl.md +479 -479
  13. package/agentes/backend-python-swl.md +605 -605
  14. package/agentes/backend-rust-swl.md +364 -364
  15. package/agentes/backend-workers-swl.md +482 -482
  16. package/agentes/cloud-infra-swl.md +509 -509
  17. package/agentes/consolidador-swl.md +541 -541
  18. package/agentes/datos-swl.md +605 -605
  19. package/agentes/depurador-swl.md +352 -352
  20. package/agentes/devops-ci-swl.md +400 -400
  21. package/agentes/disenador-ui-swl.md +569 -569
  22. package/agentes/documentador-swl.md +345 -345
  23. package/agentes/frontend-angular-swl.md +621 -621
  24. package/agentes/frontend-css-swl.md +716 -716
  25. package/agentes/frontend-react-swl.md +692 -692
  26. package/agentes/frontend-swl.md +496 -496
  27. package/agentes/frontend-tailwind-swl.md +826 -826
  28. package/agentes/gh-fix-ci-swl.md +275 -275
  29. package/agentes/implementador-swl.md +328 -328
  30. package/agentes/investigador-swl.md +432 -432
  31. package/agentes/investigador-ux-swl.md +505 -505
  32. package/agentes/llm-apps-swl.md +278 -278
  33. package/agentes/migrador-swl.md +442 -442
  34. package/agentes/mobile-android-swl.md +511 -511
  35. package/agentes/mobile-cross-swl.md +541 -541
  36. package/agentes/mobile-ios-swl.md +502 -502
  37. package/agentes/mobile-testing-swl.md +302 -302
  38. package/agentes/nemesis-auditor-swl.md +285 -285
  39. package/agentes/notificador-swl.md +918 -918
  40. package/agentes/observabilidad-swl.md +438 -438
  41. package/agentes/orquestador-swl.md +1053 -1026
  42. package/agentes/pagos-swl.md +310 -310
  43. package/agentes/perfilador-usuario-swl.md +321 -321
  44. package/agentes/planificador-swl.md +399 -399
  45. package/agentes/producto-prd-swl.md +589 -589
  46. package/agentes/red-team-swl.md +218 -218
  47. package/agentes/release-manager-swl.md +590 -590
  48. package/agentes/rendimiento-swl.md +713 -713
  49. package/agentes/resolutor-build-swl.md +245 -245
  50. package/agentes/revisor-angular-swl.md +278 -278
  51. package/agentes/revisor-codigo-swl.md +538 -505
  52. package/agentes/revisor-csharp-swl.md +264 -264
  53. package/agentes/revisor-go-swl.md +259 -259
  54. package/agentes/revisor-java-swl.md +257 -257
  55. package/agentes/revisor-kotlin-swl.md +273 -273
  56. package/agentes/revisor-nextjs-swl.md +281 -281
  57. package/agentes/revisor-php-swl.md +271 -271
  58. package/agentes/revisor-react-swl.md +278 -278
  59. package/agentes/revisor-rust-swl.md +346 -346
  60. package/agentes/revisor-seguridad-swl.md +399 -399
  61. package/agentes/revisor-swift-swl.md +268 -268
  62. package/agentes/revisor-typescript-swl.md +346 -346
  63. package/agentes/sre-swl.md +291 -291
  64. package/agentes/tdd-qa-swl.md +393 -393
  65. package/comandos/swl/contexto.md +0 -2
  66. package/comandos/swl/deuda-codigo.md +97 -0
  67. package/habilidades/angular-moderno/SKILL.md +5 -1
  68. package/habilidades/contenedores-docker/SKILL.md +3 -1
  69. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  70. package/habilidades/harness-claude-code/SKILL.md +5 -4
  71. package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +6 -5
  72. package/habilidades/legacy-code-rescue/SKILL.md +2 -1
  73. package/habilidades/meta-skills-estandar/SKILL.md +1 -1
  74. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
  75. package/habilidades/postgresql-experto/SKILL.md +3 -1
  76. package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
  77. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
  78. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
  79. package/habilidades/tdd-workflow/SKILL.md +3 -1
  80. package/hooks/audit-trail.js +4 -4
  81. package/hooks/calidad-pre-commit.js +15 -11
  82. package/hooks/captura-acciones-post.js +3 -2
  83. package/hooks/captura-acciones-session.js +3 -2
  84. package/hooks/contexto-iteracion.js +2 -1
  85. package/hooks/contexto-subagente.js +68 -0
  86. package/hooks/guardrail-modelo.js +13 -3
  87. package/hooks/inyeccion-contexto.js +12 -12
  88. package/hooks/registro-turnos.js +5 -4
  89. package/hooks/spec-gate.js +2 -1
  90. package/hooks/sugerir-contribuir.js +2 -1
  91. package/hooks/sugerir-regenerar-inventario.js +3 -2
  92. package/hooks/tdd-gate.js +2 -1
  93. package/llms.txt +3 -3
  94. package/manifiestos/canonical-hashes.json +995 -10
  95. package/manifiestos/hook-profiles.json +0 -2
  96. package/manifiestos/hooks-config.json +469 -477
  97. package/manifiestos/invariantes-criticos.json +30 -0
  98. package/manifiestos/modulos.json +1390 -1390
  99. package/manifiestos/skills-lock.json +24 -24
  100. package/package.json +93 -93
  101. package/plugin.json +369 -371
  102. package/reglas/arreglar-al-detectar.md +16 -0
  103. package/reglas/pruebas.md +7 -3
  104. package/schemas/agent-frontmatter.schema.json +3 -1
  105. package/scripts/actualizar.js +253 -254
  106. package/scripts/doctor.js +25 -17
  107. package/scripts/instalador.js +4 -1
  108. package/scripts/lib/detectar-runtime.js +58 -0
  109. package/scripts/lib/expandir-targets.js +71 -71
  110. package/scripts/lib/hooks-settings.js +40 -3
  111. package/scripts/lib/preservar-usuario.js +39 -5
  112. package/scripts/lib/toml-merge.js +204 -204
  113. package/scripts/mcp-server/auth.js +105 -105
  114. package/scripts/mcp-server/cache.js +106 -106
  115. package/scripts/tui/pantallas/inspect.js +175 -173
  116. package/scripts/tui/pantallas/uninstall-wizard.js +210 -208
  117. package/scripts/tui/pantallas/update-wizard.js +234 -232
  118. package/scripts/tui/pantallas/welcome.js +189 -187
  119. package/scripts/validar.js +1 -1
  120. package/scripts/verificar-release.js +51 -0
  121. package/hooks/lib/context-compressor.js +0 -657
  122. package/hooks/linea-estado.js +0 -328
  123. package/hooks/monitor-contexto.js +0 -373
@@ -1,187 +1,189 @@
1
- 'use strict';
2
-
3
- /**
4
- * Pantalla Welcome de la TUI de swl-ses.
5
- *
6
- * Diseño:
7
- * - Logo ASCII centrado en la parte superior
8
- * - Tabla de runtimes detectados (nombre, scope, versión instalada, estado)
9
- * - Pie con atajos: Enter continuar / Esc salir
10
- *
11
- * No-TTY: imprime versión texto plano y retorna { continuar: true } inmediatamente.
12
- */
13
-
14
- const path = require('path');
15
-
16
- const render = require('../lib/render');
17
- const teclas = require('../lib/teclas');
18
- const { colores, semantico, iconos } = require('../lib/colores');
19
-
20
- // Logo en caracteres ASCII art compactos (5 líneas)
21
- const LOGO = [
22
- ' ███████ ██ ██ ██ ███████ ███████ ███████ ',
23
- ' ██ ██ ██ ██ ██ ██ ██ ',
24
- ' ███████ ██ █ ██ ██ ███████ █████ ███████ ',
25
- ' ██ ██ ███ ██ ██ ██ ██ ██ ',
26
- ' ███████ ███ ███ ███████ ███████ ███████ ███████ ',
27
- ];
28
-
29
- const SUBTITULO = 'Sistema de ingeniería de software auto-evolutivo multi-runtime';
30
-
31
- /**
32
- * Detecta runtimes instalados leyendo desde scripts/lib/detectar-runtime.
33
- * No falla si la lib no está disponible — devuelve array vacío.
34
- */
35
- function detectarInstalaciones() {
36
- let detectarRuntimes;
37
- let cargarEstado;
38
- try {
39
- ({ detectarRuntimes } = require('../../lib/detectar-runtime'));
40
- ({ cargarEstado } = require('../../lib/estado'));
41
- } catch (_) {
42
- return [];
43
- }
44
-
45
- const runtimes = detectarRuntimes();
46
- const filas = [];
47
- for (const runtime of runtimes) {
48
- const dirs = [
49
- { ruta: runtime.global, scope: 'global' },
50
- { ruta: path.resolve(runtime.local), scope: 'proyecto' },
51
- ];
52
- for (const { ruta, scope } of dirs) {
53
- try {
54
- const estado = cargarEstado(ruta);
55
- if (!estado) continue;
56
- filas.push({
57
- runtime: runtime.nombre,
58
- scope,
59
- version: estado.versionSistema || 'desconocida',
60
- perfil: estado.perfil || '-',
61
- componentes: estado.componentesInstalados?.length || 0,
62
- });
63
- } catch (_) {
64
- // ignorar errores de lectura
65
- }
66
- }
67
- }
68
- return filas;
69
- }
70
-
71
- /**
72
- * Pinta el contenido completo de la pantalla. Idempotente — se puede invocar
73
- * múltiples veces (resize, re-render).
74
- */
75
- function _renderizar(filas, version) {
76
- render.limpiarPantalla();
77
- const { cols, rows } = render.obtenerDimensiones();
78
-
79
- // Logo centrado
80
- const filaLogo = 2;
81
- for (let i = 0; i < LOGO.length; i++) {
82
- const linea = LOGO[i];
83
- const colLogo = Math.max(1, Math.floor((cols - linea.length) / 2));
84
- render.escribirEn(filaLogo + i, colLogo, semantico.titulo(linea));
85
- }
86
-
87
- // Subtítulo + versión
88
- const filaSub = filaLogo + LOGO.length + 1;
89
- const subFinal = `${SUBTITULO} ${colores.dim('v' + version)}`;
90
- const colSub = Math.max(1, Math.floor((cols - render.anchoVisual(subFinal)) / 2));
91
- render.escribirEn(filaSub, colSub, subFinal);
92
-
93
- // Tabla de runtimes detectados
94
- const filaTabla = filaSub + 3;
95
- if (filas.length === 0) {
96
- const mensaje = colores.dim('No se detectaron instalaciones SWL en este equipo.');
97
- const colMsg = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
98
- render.escribirEn(filaTabla, colMsg, mensaje);
99
- } else {
100
- render.escribirEn(filaTabla - 1, 4, semantico.enfasis('Instalaciones detectadas:'));
101
- const cabecera =
102
- render.rellenarDer(colores.dim('Runtime'), 14) +
103
- render.rellenarDer(colores.dim('Scope'), 10) +
104
- render.rellenarDer(colores.dim('Versión'), 12) +
105
- render.rellenarDer(colores.dim('Perfil'), 18) +
106
- colores.dim('Componentes');
107
- render.escribirEn(filaTabla, 4, cabecera);
108
-
109
- filas.slice(0, rows - filaTabla - 4).forEach((f, i) => {
110
- const estado = f.version === version
111
- ? semantico.exito(iconos.check)
112
- : semantico.warn(iconos.warn);
113
- const linea =
114
- render.rellenarDer(`${estado} ${f.runtime}`, 14) +
115
- render.rellenarDer(f.scope, 10) +
116
- render.rellenarDer('v' + f.version, 12) +
117
- render.rellenarDer(f.perfil, 18) +
118
- colores.cyan(String(f.componentes));
119
- render.escribirEn(filaTabla + 1 + i, 4, linea);
120
- });
121
- }
122
-
123
- // Mensaje central de continuación
124
- const filaCont = rows - 4;
125
- const mensaje = `Presiona ${semantico.cursor('Enter')} para continuar al menú principal o ${semantico.cursor('Esc')} para salir.`;
126
- const colCont = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
127
- render.escribirEn(filaCont, colCont, mensaje);
128
-
129
- // Pie con atajos
130
- render.dibujarPiePagina([
131
- ['Enter', 'continuar'],
132
- ['Esc', 'salir'],
133
- ]);
134
- }
135
-
136
- /**
137
- * Muestra la pantalla Welcome y espera input del usuario.
138
- *
139
- * @returns {Promise<{ continuar: boolean, instalaciones: Array }>}
140
- */
141
- function mostrarWelcome() {
142
- const version = require('../../../package.json').version;
143
- const filas = detectarInstalaciones();
144
-
145
- return new Promise((resolve) => {
146
- // Modo no-TTY: salida texto plano sin esperar input
147
- if (!render.ES_TTY || !process.stdin.isTTY) {
148
- console.log('');
149
- console.log(` swl-ses v${version}`);
150
- console.log(` ${SUBTITULO}`);
151
- console.log('');
152
- if (filas.length > 0) {
153
- console.log(' Instalaciones detectadas:');
154
- for (const f of filas) {
155
- console.log(` ${f.runtime} (${f.scope}): v${f.version}, perfil ${f.perfil}`);
156
- }
157
- } else {
158
- console.log(' No se detectaron instalaciones SWL.');
159
- }
160
- console.log('');
161
- resolve({ continuar: true, instalaciones: filas });
162
- return;
163
- }
164
-
165
- render.iniciarModoTui();
166
- _renderizar(filas, version);
167
-
168
- // Re-renderizar en resize
169
- const onResize = () => _renderizar(filas, version);
170
- process.stdout.on('resize', onResize);
171
-
172
- const teclado = teclas.crearTeclado();
173
-
174
- function finalizar(continuar) {
175
- teclado.desactivar();
176
- process.stdout.removeListener('resize', onResize);
177
- render.salirModoTui();
178
- resolve({ continuar, instalaciones: filas });
179
- }
180
-
181
- teclado.on('return', () => finalizar(true));
182
- teclado.on('escape', () => finalizar(false));
183
- teclado.activar();
184
- });
185
- }
186
-
187
- module.exports = { mostrarWelcome, detectarInstalaciones, LOGO, SUBTITULO };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pantalla Welcome de la TUI de swl-ses.
5
+ *
6
+ * Diseño:
7
+ * - Logo ASCII centrado en la parte superior
8
+ * - Tabla de runtimes detectados (nombre, scope, versión instalada, estado)
9
+ * - Pie con atajos: Enter continuar / Esc salir
10
+ *
11
+ * No-TTY: imprime versión texto plano y retorna { continuar: true } inmediatamente.
12
+ */
13
+
14
+ const path = require('path');
15
+
16
+ const render = require('../lib/render');
17
+ const teclas = require('../lib/teclas');
18
+ const { colores, semantico, iconos } = require('../lib/colores');
19
+
20
+ // Logo en caracteres ASCII art compactos (5 líneas)
21
+ const LOGO = [
22
+ ' ███████ ██ ██ ██ ███████ ███████ ███████ ',
23
+ ' ██ ██ ██ ██ ██ ██ ██ ',
24
+ ' ███████ ██ █ ██ ██ ███████ █████ ███████ ',
25
+ ' ██ ██ ███ ██ ██ ██ ██ ██ ',
26
+ ' ███████ ███ ███ ███████ ███████ ███████ ███████ ',
27
+ ];
28
+
29
+ const SUBTITULO = 'Sistema de ingeniería de software auto-evolutivo multi-runtime';
30
+
31
+ /**
32
+ * Detecta runtimes instalados leyendo desde scripts/lib/detectar-runtime.
33
+ * No falla si la lib no está disponible — devuelve array vacío.
34
+ */
35
+ function detectarInstalaciones() {
36
+ let detectarRuntimes;
37
+ let cargarEstado;
38
+ try {
39
+ ({ detectarRuntimes, scopesReales } = require('../../lib/detectar-runtime'));
40
+ ({ cargarEstado } = require('../../lib/estado'));
41
+ } catch (_) {
42
+ return [];
43
+ }
44
+
45
+ const runtimes = detectarRuntimes();
46
+ const filas = [];
47
+ for (const runtime of runtimes) {
48
+ // Dedup + guard anti-fantasma: desde HOME, el local relativo resuelve al
49
+ // global propio (duplicado) o al de OTRO runtime (p.ej. .claude de
50
+ // OpenClaude = global de Claude Code) ver scopesReales().
51
+ const dirs = scopesReales(runtime).map(({ dir, esGlobal }) => (
52
+ { ruta: dir, scope: esGlobal ? 'global' : 'proyecto' }
53
+ ));
54
+ for (const { ruta, scope } of dirs) {
55
+ try {
56
+ const estado = cargarEstado(ruta);
57
+ if (!estado) continue;
58
+ filas.push({
59
+ runtime: runtime.nombre,
60
+ scope,
61
+ version: estado.versionSistema || 'desconocida',
62
+ perfil: estado.perfil || '-',
63
+ componentes: estado.componentesInstalados?.length || 0,
64
+ });
65
+ } catch (_) {
66
+ // ignorar errores de lectura
67
+ }
68
+ }
69
+ }
70
+ return filas;
71
+ }
72
+
73
+ /**
74
+ * Pinta el contenido completo de la pantalla. Idempotente — se puede invocar
75
+ * múltiples veces (resize, re-render).
76
+ */
77
+ function _renderizar(filas, version) {
78
+ render.limpiarPantalla();
79
+ const { cols, rows } = render.obtenerDimensiones();
80
+
81
+ // Logo centrado
82
+ const filaLogo = 2;
83
+ for (let i = 0; i < LOGO.length; i++) {
84
+ const linea = LOGO[i];
85
+ const colLogo = Math.max(1, Math.floor((cols - linea.length) / 2));
86
+ render.escribirEn(filaLogo + i, colLogo, semantico.titulo(linea));
87
+ }
88
+
89
+ // Subtítulo + versión
90
+ const filaSub = filaLogo + LOGO.length + 1;
91
+ const subFinal = `${SUBTITULO} ${colores.dim('v' + version)}`;
92
+ const colSub = Math.max(1, Math.floor((cols - render.anchoVisual(subFinal)) / 2));
93
+ render.escribirEn(filaSub, colSub, subFinal);
94
+
95
+ // Tabla de runtimes detectados
96
+ const filaTabla = filaSub + 3;
97
+ if (filas.length === 0) {
98
+ const mensaje = colores.dim('No se detectaron instalaciones SWL en este equipo.');
99
+ const colMsg = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
100
+ render.escribirEn(filaTabla, colMsg, mensaje);
101
+ } else {
102
+ render.escribirEn(filaTabla - 1, 4, semantico.enfasis('Instalaciones detectadas:'));
103
+ const cabecera =
104
+ render.rellenarDer(colores.dim('Runtime'), 14) +
105
+ render.rellenarDer(colores.dim('Scope'), 10) +
106
+ render.rellenarDer(colores.dim('Versión'), 12) +
107
+ render.rellenarDer(colores.dim('Perfil'), 18) +
108
+ colores.dim('Componentes');
109
+ render.escribirEn(filaTabla, 4, cabecera);
110
+
111
+ filas.slice(0, rows - filaTabla - 4).forEach((f, i) => {
112
+ const estado = f.version === version
113
+ ? semantico.exito(iconos.check)
114
+ : semantico.warn(iconos.warn);
115
+ const linea =
116
+ render.rellenarDer(`${estado} ${f.runtime}`, 14) +
117
+ render.rellenarDer(f.scope, 10) +
118
+ render.rellenarDer('v' + f.version, 12) +
119
+ render.rellenarDer(f.perfil, 18) +
120
+ colores.cyan(String(f.componentes));
121
+ render.escribirEn(filaTabla + 1 + i, 4, linea);
122
+ });
123
+ }
124
+
125
+ // Mensaje central de continuación
126
+ const filaCont = rows - 4;
127
+ const mensaje = `Presiona ${semantico.cursor('Enter')} para continuar al menú principal o ${semantico.cursor('Esc')} para salir.`;
128
+ const colCont = Math.max(1, Math.floor((cols - render.anchoVisual(mensaje)) / 2));
129
+ render.escribirEn(filaCont, colCont, mensaje);
130
+
131
+ // Pie con atajos
132
+ render.dibujarPiePagina([
133
+ ['Enter', 'continuar'],
134
+ ['Esc', 'salir'],
135
+ ]);
136
+ }
137
+
138
+ /**
139
+ * Muestra la pantalla Welcome y espera input del usuario.
140
+ *
141
+ * @returns {Promise<{ continuar: boolean, instalaciones: Array }>}
142
+ */
143
+ function mostrarWelcome() {
144
+ const version = require('../../../package.json').version;
145
+ const filas = detectarInstalaciones();
146
+
147
+ return new Promise((resolve) => {
148
+ // Modo no-TTY: salida texto plano sin esperar input
149
+ if (!render.ES_TTY || !process.stdin.isTTY) {
150
+ console.log('');
151
+ console.log(` swl-ses v${version}`);
152
+ console.log(` ${SUBTITULO}`);
153
+ console.log('');
154
+ if (filas.length > 0) {
155
+ console.log(' Instalaciones detectadas:');
156
+ for (const f of filas) {
157
+ console.log(` ${f.runtime} (${f.scope}): v${f.version}, perfil ${f.perfil}`);
158
+ }
159
+ } else {
160
+ console.log(' No se detectaron instalaciones SWL.');
161
+ }
162
+ console.log('');
163
+ resolve({ continuar: true, instalaciones: filas });
164
+ return;
165
+ }
166
+
167
+ render.iniciarModoTui();
168
+ _renderizar(filas, version);
169
+
170
+ // Re-renderizar en resize
171
+ const onResize = () => _renderizar(filas, version);
172
+ process.stdout.on('resize', onResize);
173
+
174
+ const teclado = teclas.crearTeclado();
175
+
176
+ function finalizar(continuar) {
177
+ teclado.desactivar();
178
+ process.stdout.removeListener('resize', onResize);
179
+ render.salirModoTui();
180
+ resolve({ continuar, instalaciones: filas });
181
+ }
182
+
183
+ teclado.on('return', () => finalizar(true));
184
+ teclado.on('escape', () => finalizar(false));
185
+ teclado.activar();
186
+ });
187
+ }
188
+
189
+ module.exports = { mostrarWelcome, detectarInstalaciones, LOGO, SUBTITULO };
@@ -55,7 +55,7 @@ if (fs.existsSync(hooksConfigPath)) {
55
55
  verificar(hooksEnConfig.length >= 4, `hooks-config.json: ${hooksEnConfig.length} hooks definidos (mínimo 4)`);
56
56
  for (const hookName of hooksEnConfig) {
57
57
  const hc = hooksConfig.hooks[hookName];
58
- const eventosValidos = ['PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SubagentStop', 'UserPromptSubmit', 'PreCompact', 'SessionStart'];
58
+ const eventosValidos = ['PreToolUse', 'PostToolUse', 'Stop', 'Notification', 'SubagentStart', 'SubagentStop', 'UserPromptSubmit', 'PreCompact', 'SessionStart'];
59
59
  verificar(
60
60
  eventosValidos.includes(hc.event),
61
61
  `hooks-config.json: ${hookName} tiene event válido (${hc.event})`
@@ -329,6 +329,17 @@ function main() {
329
329
  ? ' [OK] Gate evolución: manifiesto al día + fuente sin evolved espurio'
330
330
  : ` [FALLA] Gate evolución: ${gateEvolved.problemas.join('; ')}`);
331
331
 
332
+ // Gate de invariantes: frases load-bearing de manifiestos/invariantes-criticos.json
333
+ // presentes verbatim en sus componentes. Blocking — perder una frase en un
334
+ // reword rompe el contrato cross-componente en silencio.
335
+ const gateInvariantes = ejecutarGateInvariantes();
336
+ if (!gateInvariantes.ok) {
337
+ fallasObligatorias++;
338
+ }
339
+ console.log(gateInvariantes.ok
340
+ ? ` [OK] Gate invariantes: ${gateInvariantes.frasesVerificadas} frase(s) load-bearing presentes`
341
+ : ` [FALLA] Gate invariantes: ${gateInvariantes.problemas.join('; ')}`);
342
+
332
343
  if (jsonOut) {
333
344
  process.stdout.write(JSON.stringify({
334
345
  version,
@@ -343,6 +354,7 @@ function main() {
343
354
  aiisms_gate: aiismsGate,
344
355
  gate_licencias: gateLicencias,
345
356
  gate_evolved: gateEvolved,
357
+ gate_invariantes: gateInvariantes,
346
358
  resultados: resultados.map(({ entrada, resultado }) => ({
347
359
  archivo: entrada.archivo,
348
360
  obligatorio: entrada.obligatorio,
@@ -928,6 +940,44 @@ function ejecutarGateLicencias(baseDir = CWD) {
928
940
  }
929
941
  }
930
942
 
943
+ /**
944
+ * Gate de invariantes: frases load-bearing declaradas en
945
+ * manifiestos/invariantes-criticos.json deben existir verbatim en sus
946
+ * archivos. Complementa a canonical-hashes (que detecta QUE un archivo
947
+ * cambió) detectando si un reword perdió UNA regla puntual del contrato
948
+ * cross-componente. Blocking: la fricción de actualizar el manifiesto en
949
+ * un reword intencional es el recordatorio de propagar el cambio.
950
+ *
951
+ * @param {string} [baseDir=CWD] - Raíz del repo (inyectable para tests).
952
+ * @returns {{ ok: boolean, disponible: boolean, problemas: string[], frasesVerificadas: number }}
953
+ */
954
+ function ejecutarGateInvariantes(baseDir = CWD) {
955
+ const manifiestoPath = path.join(baseDir, 'manifiestos', 'invariantes-criticos.json');
956
+ let manifiesto;
957
+ try {
958
+ manifiesto = JSON.parse(fs.readFileSync(manifiestoPath, 'utf8'));
959
+ } catch (err) {
960
+ return { ok: false, disponible: false, problemas: [`manifiesto ilegible: ${err.message}`], frasesVerificadas: 0 };
961
+ }
962
+
963
+ const problemas = [];
964
+ let frasesVerificadas = 0;
965
+ for (const [archivo, frases] of Object.entries(manifiesto.invariantes || {})) {
966
+ const contenido = leerArchivoSeguro(path.join(baseDir, archivo));
967
+ if (contenido === null) {
968
+ problemas.push(`${archivo}: archivo no existe`);
969
+ continue;
970
+ }
971
+ for (const frase of frases) {
972
+ frasesVerificadas++;
973
+ if (!contenido.includes(frase)) {
974
+ problemas.push(`${archivo}: perdió la frase invariante "${frase}"`);
975
+ }
976
+ }
977
+ }
978
+ return { ok: problemas.length === 0, disponible: true, problemas, frasesVerificadas };
979
+ }
980
+
931
981
  if (require.main === module) main();
932
982
 
933
983
  module.exports = {
@@ -938,4 +988,5 @@ module.exports = {
938
988
  ejecutarGateDescription,
939
989
  ejecutarGateLicencias,
940
990
  ejecutarGateEvolved,
991
+ ejecutarGateInvariantes,
941
992
  };