@saulwade/swl-ses 1.3.8 → 1.4.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.
Files changed (148) hide show
  1. package/CLAUDE.md +15 -6
  2. package/README.md +15 -14
  3. package/agentes/nemesis-auditor-swl.md +161 -0
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/bin/swl-webhook-server.js +198 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/adoptar-proyecto.md +21 -1
  8. package/comandos/swl/claudemd.md +14 -1
  9. package/comandos/swl/contribuir.md +233 -233
  10. package/comandos/swl/exportar-vault.md +108 -0
  11. package/comandos/swl/nemesis.md +122 -0
  12. package/comandos/swl/nuevo-proyecto.md +24 -2
  13. package/comandos/swl/salud.md +34 -0
  14. package/comandos/swl/verificar.md +45 -0
  15. package/gateway/adapters/base.js +109 -0
  16. package/gateway/adapters/discord.js +167 -0
  17. package/gateway/adapters/email.js +221 -0
  18. package/gateway/adapters/slack.js +192 -0
  19. package/gateway/adapters/telegram.js +183 -0
  20. package/gateway/adapters/webhook.js +113 -0
  21. package/gateway/adapters/whatsapp.js +214 -0
  22. package/gateway/agent-executor.js +322 -0
  23. package/gateway/command-relay.js +271 -0
  24. package/gateway/cron/jobs.js +263 -0
  25. package/gateway/cron/scheduler.js +322 -0
  26. package/gateway/cron/store.js +335 -0
  27. package/gateway/index.js +320 -0
  28. package/gateway/lib/event-channel.js +191 -0
  29. package/gateway/session.js +131 -0
  30. package/gateway/webhook-server.js +324 -0
  31. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  32. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  33. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  34. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  35. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  36. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  37. package/habilidades/eval-framework/SKILL.md +212 -212
  38. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  39. package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
  40. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
  41. package/habilidades/harness-claude-code/SKILL.md +299 -299
  42. package/habilidades/infra-github-actions/SKILL.md +166 -166
  43. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  44. package/habilidades/manejo-errores/.evolved.json +8 -8
  45. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  46. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  47. package/habilidades/nextjs-testing/SKILL.md +89 -5
  48. package/habilidades/node-experto/SKILL.md +37 -1
  49. package/habilidades/patrones-python/SKILL.md +229 -229
  50. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  51. package/habilidades/planear-fase/SKILL.md +319 -319
  52. package/habilidades/react-experto/SKILL.md +45 -4
  53. package/habilidades/release-semver/.evolved.json +8 -8
  54. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
  55. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
  56. package/habilidades/tdd-workflow/SKILL.md +36 -4
  57. package/habilidades/testing-python/SKILL.md +340 -340
  58. package/habilidades/web-fetcher-routing/SKILL.md +75 -0
  59. package/hooks/claudemd-bloat-detector.js +161 -161
  60. package/hooks/inyeccion-contexto.js +8 -3
  61. package/hooks/lib/agent-routing.js +107 -107
  62. package/hooks/lib/auto-consolidator.js +335 -335
  63. package/hooks/lib/error-classifier.js +308 -308
  64. package/hooks/lib/merkle-audit.js +96 -96
  65. package/hooks/lib/provenance-tracker.js +191 -191
  66. package/hooks/lib/rate-limit-ip.js +177 -0
  67. package/hooks/lib/rate-limit-tracker.js +253 -253
  68. package/hooks/lib/resource-quota.js +122 -122
  69. package/hooks/lib/retry-jitter.js +165 -165
  70. package/hooks/lib/security-net.js +201 -0
  71. package/hooks/lib/skill-auditor.js +588 -588
  72. package/hooks/lib/sync-status.js +228 -228
  73. package/hooks/lib/taint-tracker.js +107 -107
  74. package/hooks/lib/text-similarity.js +241 -241
  75. package/hooks/lib/toon-compressor.js +245 -245
  76. package/hooks/lib/webhook-dedup.js +184 -0
  77. package/hooks/lib/webhook-verify.js +123 -0
  78. package/hooks/proteccion-rutas.js +120 -15
  79. package/hooks/registro-turnos.js +209 -209
  80. package/hooks/sugerir-regenerar-inventario.js +170 -170
  81. package/hooks/validar-formato-post-subagente.js +140 -140
  82. package/hooks/validar-memoria-hook.js +218 -218
  83. package/instintos/prompt-appendices.yaml +57 -57
  84. package/manifiestos/agent-output-schemas.json +57 -57
  85. package/manifiestos/modulos.json +31 -0
  86. package/manifiestos/skills-lock.json +1114 -1093
  87. package/package.json +6 -4
  88. package/plantillas/auditor-veto-template.md +105 -105
  89. package/plantillas/github-workflows/README.md +47 -47
  90. package/plantillas/github-workflows/release-please.yml +44 -44
  91. package/plantillas/github-workflows/swl-ci.yml +107 -107
  92. package/plantillas/github-workflows/swl-security.yml +51 -51
  93. package/plugin.json +2 -2
  94. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  95. package/reglas/arreglar-al-detectar.md +147 -147
  96. package/reglas/fragmentos-compartidos.md +152 -152
  97. package/reglas/harness-claude-code.md +213 -213
  98. package/reglas/usar-context7.md +226 -226
  99. package/reglas/usar-sistema-swl.md +251 -0
  100. package/schemas/diary-entry.schema.json +80 -80
  101. package/scripts/audit-tools/audit-history.js +330 -0
  102. package/scripts/audit-tools/bundle-tracker.js +290 -0
  103. package/scripts/audit-tools/canary-monitor.js +352 -0
  104. package/scripts/audit-tools/code-profiler.js +605 -0
  105. package/scripts/audit-tools/dep-doctor.js +320 -0
  106. package/scripts/audit-tools/env-validator.js +206 -0
  107. package/scripts/audit-tools/lib/fs-walk.js +48 -0
  108. package/scripts/audit-tools/lib/output.js +23 -0
  109. package/scripts/audit-tools/migration-checker.js +392 -0
  110. package/scripts/audit-tools/pentest-scanner.js +1436 -0
  111. package/scripts/benchmark-memoria.js +167 -167
  112. package/scripts/comandos/skills.js +251 -2
  113. package/scripts/configurar-branch-protection.js +418 -418
  114. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  115. package/scripts/field-report.js +199 -199
  116. package/scripts/generar-checklists-consolidados.js +273 -273
  117. package/scripts/generar-inventario.js +420 -420
  118. package/scripts/generar-matriz-lenguajes.js +271 -271
  119. package/scripts/lib/artefactos-python.js +43 -43
  120. package/scripts/lib/benchmark-metrics.js +160 -160
  121. package/scripts/lib/budget-enforcer.js +252 -252
  122. package/scripts/lib/configurar-ci.js +380 -380
  123. package/scripts/lib/contadores-inventario.js +217 -217
  124. package/scripts/lib/detectar-stack-detallado.js +307 -307
  125. package/scripts/lib/diary-entry.js +234 -234
  126. package/scripts/lib/eval-metrics-store.js +218 -218
  127. package/scripts/lib/eval-quality.js +171 -171
  128. package/scripts/lib/eval-schemas.js +144 -144
  129. package/scripts/lib/eval-self-correct.js +106 -106
  130. package/scripts/lib/eval-validator.js +185 -185
  131. package/scripts/lib/jaccard-similarity.js +98 -98
  132. package/scripts/lib/longmemeval-runner.js +125 -125
  133. package/scripts/lib/npm-version.js +261 -261
  134. package/scripts/lib/paquetes-conocidos.js +50 -50
  135. package/scripts/lib/prompt-builder.js +264 -264
  136. package/scripts/lib/rrf-fusion.js +175 -175
  137. package/scripts/lib/scoring-instintos.js +277 -277
  138. package/scripts/lib/semantic-search.js +252 -252
  139. package/scripts/limpiar-artefactos-python.js +131 -131
  140. package/scripts/mcp-server/README.md +128 -128
  141. package/scripts/mcp-server/handlers.js +206 -206
  142. package/scripts/migrar-csv-a-array.js +168 -168
  143. package/scripts/migrar-fase-dominio.js +201 -201
  144. package/scripts/publicar.js +511 -511
  145. package/scripts/run-eval.js +141 -141
  146. package/scripts/validar-manifest.js +195 -195
  147. package/scripts/validar-userland-vacio.js +110 -110
  148. package/scripts/verificar-release.js +110 -0
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * validar-userland-vacio.js — guard pre-publish.
6
- *
7
- * Verifica que `_userland/` solo contenga archivos `.gitkeep` antes de
8
- * empaquetar el tarball. Si algún contributor accidentalmente commiteó
9
- * agentes, skills o credenciales experimentales en `_userland/`, este
10
- * gate aborta el publish con exit 1 y lista los archivos extraños.
11
- *
12
- * Por qué existe (defensa en profundidad):
13
- * `_userland/` es el directorio donde el USUARIO instala su contenido
14
- * custom. En el repo del sistema debe permanecer vacío (solo
15
- * `.gitkeep` para preservar la estructura). El campo `files` de
16
- * package.json incluye `_userland/`, así que cualquier archivo real
17
- * que se cuele entrará al tarball publicado a npm — exposición
18
- * potencial de IP o secretos. Aunque `.gitignore` y `.npmignore` ya
19
- * cubren los vectores típicos (.env, credenciales), este guard
20
- * garantiza que la regla "_userland/ vacío en distribución" se cumpla
21
- * determinísticamente.
22
- *
23
- * Variables de entorno:
24
- * SWL_USERLAND_GUARD_ALLOW=archivo1,archivo2 — permite archivos extra
25
- * (uso excepcional documentado en commit que active la variable).
26
- *
27
- * Exit codes:
28
- * 0 — OK, _userland/ solo contiene .gitkeep
29
- * 1 — hay archivos extraños; lista impresa a stderr
30
- * 2 — error de invariante (cwd incorrecto, _userland/ no existe)
31
- */
32
-
33
- const fs = require('fs');
34
- const path = require('path');
35
-
36
- const USERLAND_DIR = '_userland';
37
- const PERMITIDOS_BASE = new Set(['.gitkeep', '.gitignore']);
38
-
39
- function log(msg) { process.stdout.write(`[userland-guard] ${msg}\n`); }
40
- function err(msg) { process.stderr.write(`[userland-guard] ERROR: ${msg}\n`); }
41
-
42
- /**
43
- * Normaliza una ruta relativa al separador POSIX (`/`) para que la
44
- * comparación contra la allowlist funcione idéntica en Windows y POSIX.
45
- */
46
- function normalizar(rel) {
47
- return rel.replace(/\\/g, '/');
48
- }
49
-
50
- function obtenerExtras() {
51
- const extras = process.env.SWL_USERLAND_GUARD_ALLOW;
52
- if (!extras) return new Set();
53
- return new Set(extras.split(',').map(s => normalizar(s.trim())).filter(Boolean));
54
- }
55
-
56
- function listarRecursivo(dir, baseDir = dir, acumulador = []) {
57
- const entries = fs.readdirSync(dir, { withFileTypes: true });
58
- for (const entry of entries) {
59
- const full = path.join(dir, entry.name);
60
- const rel = normalizar(path.relative(baseDir, full));
61
- if (entry.isDirectory()) {
62
- listarRecursivo(full, baseDir, acumulador);
63
- } else if (entry.isFile()) {
64
- acumulador.push(rel);
65
- } else if (entry.isSymbolicLink()) {
66
- // Symlinks dentro del paquete distribuido son anti-patrón.
67
- acumulador.push(`${rel} (symlink)`);
68
- }
69
- }
70
- return acumulador;
71
- }
72
-
73
- function main() {
74
- const cwd = process.cwd();
75
- const userland = path.join(cwd, USERLAND_DIR);
76
-
77
- if (!fs.existsSync(userland)) {
78
- err(`${USERLAND_DIR}/ no existe en ${cwd}. ¿cwd incorrecto?`);
79
- process.exit(2);
80
- }
81
-
82
- const extrasPermitidos = obtenerExtras();
83
- const archivos = listarRecursivo(userland);
84
-
85
- const noPermitidos = archivos.filter(rel => {
86
- const nombre = path.basename(rel);
87
- if (PERMITIDOS_BASE.has(nombre)) return false;
88
- if (extrasPermitidos.has(rel)) return false;
89
- return true;
90
- });
91
-
92
- if (noPermitidos.length > 0) {
93
- err(`${USERLAND_DIR}/ contiene ${noPermitidos.length} archivo(s) que no deberían distribuirse en el tarball npm:`);
94
- for (const rel of noPermitidos) {
95
- err(` - ${USERLAND_DIR}/${rel}`);
96
- }
97
- err('');
98
- err('Acciones posibles:');
99
- err(` 1. Mover el contenido fuera de ${USERLAND_DIR}/ si es del sistema base`);
100
- err(` 2. Borrarlo si era código experimental local`);
101
- err(` 3. Permitir explícitamente con SWL_USERLAND_GUARD_ALLOW="ruta1,ruta2"`);
102
- err(' (documentar la excepción en el mensaje de commit)');
103
- process.exit(1);
104
- }
105
-
106
- log(`OK: ${USERLAND_DIR}/ solo contiene ${archivos.length} archivo(s) permitido(s).`);
107
- process.exit(0);
108
- }
109
-
110
- main();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * validar-userland-vacio.js — guard pre-publish.
6
+ *
7
+ * Verifica que `_userland/` solo contenga archivos `.gitkeep` antes de
8
+ * empaquetar el tarball. Si algún contributor accidentalmente commiteó
9
+ * agentes, skills o credenciales experimentales en `_userland/`, este
10
+ * gate aborta el publish con exit 1 y lista los archivos extraños.
11
+ *
12
+ * Por qué existe (defensa en profundidad):
13
+ * `_userland/` es el directorio donde el USUARIO instala su contenido
14
+ * custom. En el repo del sistema debe permanecer vacío (solo
15
+ * `.gitkeep` para preservar la estructura). El campo `files` de
16
+ * package.json incluye `_userland/`, así que cualquier archivo real
17
+ * que se cuele entrará al tarball publicado a npm — exposición
18
+ * potencial de IP o secretos. Aunque `.gitignore` y `.npmignore` ya
19
+ * cubren los vectores típicos (.env, credenciales), este guard
20
+ * garantiza que la regla "_userland/ vacío en distribución" se cumpla
21
+ * determinísticamente.
22
+ *
23
+ * Variables de entorno:
24
+ * SWL_USERLAND_GUARD_ALLOW=archivo1,archivo2 — permite archivos extra
25
+ * (uso excepcional documentado en commit que active la variable).
26
+ *
27
+ * Exit codes:
28
+ * 0 — OK, _userland/ solo contiene .gitkeep
29
+ * 1 — hay archivos extraños; lista impresa a stderr
30
+ * 2 — error de invariante (cwd incorrecto, _userland/ no existe)
31
+ */
32
+
33
+ const fs = require('fs');
34
+ const path = require('path');
35
+
36
+ const USERLAND_DIR = '_userland';
37
+ const PERMITIDOS_BASE = new Set(['.gitkeep', '.gitignore']);
38
+
39
+ function log(msg) { process.stdout.write(`[userland-guard] ${msg}\n`); }
40
+ function err(msg) { process.stderr.write(`[userland-guard] ERROR: ${msg}\n`); }
41
+
42
+ /**
43
+ * Normaliza una ruta relativa al separador POSIX (`/`) para que la
44
+ * comparación contra la allowlist funcione idéntica en Windows y POSIX.
45
+ */
46
+ function normalizar(rel) {
47
+ return rel.replace(/\\/g, '/');
48
+ }
49
+
50
+ function obtenerExtras() {
51
+ const extras = process.env.SWL_USERLAND_GUARD_ALLOW;
52
+ if (!extras) return new Set();
53
+ return new Set(extras.split(',').map(s => normalizar(s.trim())).filter(Boolean));
54
+ }
55
+
56
+ function listarRecursivo(dir, baseDir = dir, acumulador = []) {
57
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ const full = path.join(dir, entry.name);
60
+ const rel = normalizar(path.relative(baseDir, full));
61
+ if (entry.isDirectory()) {
62
+ listarRecursivo(full, baseDir, acumulador);
63
+ } else if (entry.isFile()) {
64
+ acumulador.push(rel);
65
+ } else if (entry.isSymbolicLink()) {
66
+ // Symlinks dentro del paquete distribuido son anti-patrón.
67
+ acumulador.push(`${rel} (symlink)`);
68
+ }
69
+ }
70
+ return acumulador;
71
+ }
72
+
73
+ function main() {
74
+ const cwd = process.cwd();
75
+ const userland = path.join(cwd, USERLAND_DIR);
76
+
77
+ if (!fs.existsSync(userland)) {
78
+ err(`${USERLAND_DIR}/ no existe en ${cwd}. ¿cwd incorrecto?`);
79
+ process.exit(2);
80
+ }
81
+
82
+ const extrasPermitidos = obtenerExtras();
83
+ const archivos = listarRecursivo(userland);
84
+
85
+ const noPermitidos = archivos.filter(rel => {
86
+ const nombre = path.basename(rel);
87
+ if (PERMITIDOS_BASE.has(nombre)) return false;
88
+ if (extrasPermitidos.has(rel)) return false;
89
+ return true;
90
+ });
91
+
92
+ if (noPermitidos.length > 0) {
93
+ err(`${USERLAND_DIR}/ contiene ${noPermitidos.length} archivo(s) que no deberían distribuirse en el tarball npm:`);
94
+ for (const rel of noPermitidos) {
95
+ err(` - ${USERLAND_DIR}/${rel}`);
96
+ }
97
+ err('');
98
+ err('Acciones posibles:');
99
+ err(` 1. Mover el contenido fuera de ${USERLAND_DIR}/ si es del sistema base`);
100
+ err(` 2. Borrarlo si era código experimental local`);
101
+ err(` 3. Permitir explícitamente con SWL_USERLAND_GUARD_ALLOW="ruta1,ruta2"`);
102
+ err(' (documentar la excepción en el mensaje de commit)');
103
+ process.exit(1);
104
+ }
105
+
106
+ log(`OK: ${USERLAND_DIR}/ solo contiene ${archivos.length} archivo(s) permitido(s).`);
107
+ process.exit(0);
108
+ }
109
+
110
+ main();
@@ -262,6 +262,16 @@ function main() {
262
262
  fallasObligatorias++;
263
263
  }
264
264
 
265
+ // Gate de package.json#files: valida que TODOS los directorios referenciados
266
+ // por bin/*.js via require('./X/...') o require('../X/...') estén listados
267
+ // en package.json#files. Sin este gate, los binarios fallan con
268
+ // MODULE_NOT_FOUND tras instalación pública aunque la suite local pase.
269
+ // Origen: bug histórico de gateway/ ausente en files (v1.4.0).
270
+ const gateBinImports = ejecutarGateBinImports();
271
+ if (gateBinImports.disponible && gateBinImports.faltantes.length > 0) {
272
+ fallasObligatorias++;
273
+ }
274
+
265
275
  // Gate opt-in de AI-isms sobre CHANGELOG.md + RELEASE_NOTES.md
266
276
  // Se activa solo cuando SWL_AIISMS_GATE=1 y bloquea si encuentra P0.
267
277
  let aiismsGate = null;
@@ -280,6 +290,7 @@ function main() {
280
290
  fallas_obligatorias: fallasObligatorias,
281
291
  warnings_opcionales: warningsOpcionales,
282
292
  contadores_gate: gateContadores,
293
+ bin_imports_gate: gateBinImports,
283
294
  aiisms_gate: aiismsGate,
284
295
  resultados: resultados.map(({ entrada, resultado }) => ({
285
296
  archivo: entrada.archivo,
@@ -332,6 +343,27 @@ function main() {
332
343
  process.stdout.write('\n');
333
344
  process.stdout.write('Gate de contadores: no disponible — ' + gateContadores.error + '\n');
334
345
  }
346
+ if (gateBinImports.disponible) {
347
+ process.stdout.write('\n');
348
+ process.stdout.write('Gate de package.json#files vs bin/*.js imports:\n');
349
+ process.stdout.write(' Bins analizados: ' + gateBinImports.bins.length + '\n');
350
+ process.stdout.write(' Directorios requeridos: ' + gateBinImports.requeridos.join(', ') + '\n');
351
+ process.stdout.write(' En package.json#files: ' + gateBinImports.enFiles.sort().join(', ') + '\n');
352
+ if (gateBinImports.faltantes.length > 0) {
353
+ process.stdout.write(' [FALLA] Faltan en files: ' + gateBinImports.faltantes.join(', ') + '\n');
354
+ process.stdout.write(' Sin estos directorios en `files`, los bins fallaran con MODULE_NOT_FOUND tras instalacion publica.\n');
355
+ for (const detalle of gateBinImports.detalle) {
356
+ if (detalle.requeridos.some(d => gateBinImports.faltantes.includes(d))) {
357
+ process.stdout.write(' ' + detalle.bin + ' requiere: ' + detalle.requeridos.join(', ') + '\n');
358
+ }
359
+ }
360
+ } else {
361
+ process.stdout.write(' [OK] Todos los directorios requeridos por los bins estan en files\n');
362
+ }
363
+ } else if (gateBinImports.error) {
364
+ process.stdout.write('\n');
365
+ process.stdout.write('Gate de bin-imports: no disponible — ' + gateBinImports.error + '\n');
366
+ }
335
367
  if (aiismsGate) {
336
368
  process.stdout.write('\n');
337
369
  process.stdout.write('Gate AI-isms (SWL_AIISMS_GATE=1):\n');
@@ -433,6 +465,84 @@ function ejecutarGateContadores() {
433
465
  * Python 3.10+ debe estar disponible. Si falla la invocación, se reporta
434
466
  * como warning pero no bloquea (el gate de versión es independiente).
435
467
  */
468
+
469
+ /**
470
+ * Gate de package.json#files vs imports de bin/*.js: detecta directorios
471
+ * referenciados por los binarios via require('./X/...') o require('../X/...')
472
+ * que NO están listados en `package.json#files`. Sin este gate, los bins
473
+ * fallan con MODULE_NOT_FOUND tras instalación pública aunque la suite
474
+ * local pase.
475
+ *
476
+ * Solo top-level directories del repo se consideran (el primer segmento
477
+ * del path relativo). Módulos como `node:fs`, `crypto`, `path` se ignoran.
478
+ * Paths que apuntan a archivos individuales en root (`./package.json`)
479
+ * también se ignoran porque npm los incluye por convención.
480
+ *
481
+ * Origen: bug histórico v1.4.0 — gateway/ ausente en files rompía
482
+ * /swl:cron, /swl:gateway, /swl:inbox tras instalación pública.
483
+ */
484
+ function ejecutarGateBinImports() {
485
+ try {
486
+ const pkgPath = path.join(CWD, 'package.json');
487
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
488
+ const files = Array.isArray(pkg.files) ? pkg.files : [];
489
+ const enFiles = files.filter(f => !f.endsWith('.json') && !f.endsWith('.md'));
490
+
491
+ const binDir = path.join(CWD, 'bin');
492
+ if (!fs.existsSync(binDir)) {
493
+ return { disponible: false, error: 'bin/ no existe' };
494
+ }
495
+ const bins = fs.readdirSync(binDir).filter(f => f.endsWith('.js'));
496
+ if (bins.length === 0) {
497
+ return { disponible: false, error: 'bin/ vacío' };
498
+ }
499
+
500
+ // Regex captura: require('./X/...') o require('../X/...') o require('./X')
501
+ // No matchea require('node:fs'), require('crypto'), require('path'),
502
+ // ni require(variable). Solo strings literales con ruta relativa.
503
+ const regexImport = /require\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g;
504
+
505
+ const detalle = [];
506
+ const requeridos = new Set();
507
+
508
+ for (const bin of bins) {
509
+ const binPath = path.join(binDir, bin);
510
+ const contenido = fs.readFileSync(binPath, 'utf-8');
511
+ const dirs = new Set();
512
+ let m;
513
+ while ((m = regexImport.exec(contenido)) !== null) {
514
+ const rutaRel = m[1];
515
+ // Resolver hacia raíz del repo. bin/X.js requires ./Y -> bin/Y (no top-level).
516
+ // bin/X.js requires ../Y -> Y (top-level del repo).
517
+ // Por convención: solo ../ apunta a top-level desde bin/.
518
+ if (rutaRel.startsWith('../')) {
519
+ const sinPrefijo = rutaRel.slice(3);
520
+ const topDir = sinPrefijo.split('/')[0];
521
+ // Filtrar archivos individuales en root (sin slash o solo nombre.ext)
522
+ if (sinPrefijo.includes('/')) {
523
+ dirs.add(topDir);
524
+ requeridos.add(topDir);
525
+ }
526
+ }
527
+ }
528
+ detalle.push({ bin: 'bin/' + bin, requeridos: Array.from(dirs).sort() });
529
+ }
530
+
531
+ const faltantes = Array.from(requeridos).filter(d => !enFiles.includes(d)).sort();
532
+
533
+ return {
534
+ disponible: true,
535
+ bins,
536
+ requeridos: Array.from(requeridos).sort(),
537
+ enFiles,
538
+ faltantes,
539
+ detalle,
540
+ };
541
+ } catch (err) {
542
+ return { disponible: false, error: err.message };
543
+ }
544
+ }
545
+
436
546
  function ejecutarGateAiisms() {
437
547
  const { spawnSync } = require('child_process');
438
548
  const detector = path.join(CWD, 'habilidades', 'estilo-sin-ai-isms', 'scripts', 'detectar_aiisms.py');