@saulwade/swl-ses 1.5.0 → 1.5.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 (134) hide show
  1. package/CLAUDE.md +19 -2
  2. package/README.md +561 -561
  3. package/agentes/arquitecto-swl.md +33 -1
  4. package/agentes/nemesis-auditor-swl.md +59 -19
  5. package/bin/swl-mcp-server.js +214 -214
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/contribuir.md +233 -233
  8. package/comandos/swl/nemesis.md +230 -56
  9. package/gateway/lib/event-channel.js +191 -191
  10. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  11. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  12. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  13. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  14. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  15. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  16. package/habilidades/eval-framework/SKILL.md +212 -212
  17. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  18. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  19. package/habilidades/harness-claude-code/SKILL.md +299 -299
  20. package/habilidades/infra-github-actions/SKILL.md +166 -166
  21. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  22. package/habilidades/manejo-errores/.evolved.json +8 -8
  23. package/habilidades/meta-skills-estandar/SKILL.md +225 -1
  24. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  25. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  26. package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
  27. package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
  28. package/habilidades/node-experto/SKILL.md +105 -4
  29. package/habilidades/patrones-python/SKILL.md +229 -229
  30. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  31. package/habilidades/planear-fase/SKILL.md +319 -319
  32. package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
  33. package/habilidades/release-semver/.evolved.json +8 -8
  34. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  35. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  36. package/habilidades/tdd-workflow/SKILL.md +150 -4
  37. package/habilidades/testing-python/SKILL.md +340 -340
  38. package/habilidades/verificar-trabajo/SKILL.md +8 -3
  39. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  40. package/hooks/check-update.js +31 -3
  41. package/hooks/claudemd-bloat-detector.js +161 -161
  42. package/hooks/lib/agent-routing.js +107 -107
  43. package/hooks/lib/auto-consolidator.js +335 -335
  44. package/hooks/lib/error-classifier.js +308 -308
  45. package/hooks/lib/merkle-audit.js +96 -96
  46. package/hooks/lib/provenance-tracker.js +191 -191
  47. package/hooks/lib/rate-limit-tracker.js +253 -253
  48. package/hooks/lib/resource-quota.js +122 -122
  49. package/hooks/lib/retry-jitter.js +165 -165
  50. package/hooks/lib/security-net.js +201 -201
  51. package/hooks/lib/skill-auditor.js +588 -588
  52. package/hooks/lib/sync-status.js +228 -228
  53. package/hooks/lib/taint-tracker.js +107 -107
  54. package/hooks/lib/text-similarity.js +241 -241
  55. package/hooks/lib/toon-compressor.js +245 -245
  56. package/hooks/registro-turnos.js +209 -209
  57. package/hooks/sugerir-regenerar-inventario.js +170 -170
  58. package/hooks/validar-formato-post-subagente.js +140 -140
  59. package/hooks/validar-memoria-hook.js +218 -218
  60. package/instintos/prompt-appendices.yaml +57 -57
  61. package/manifiestos/agent-output-schemas.json +57 -57
  62. package/manifiestos/modulos.json +1324 -1321
  63. package/manifiestos/skills-lock.json +1114 -1114
  64. package/package.json +2 -2
  65. package/plantillas/auditor-veto-template.md +105 -105
  66. package/plantillas/github-workflows/README.md +47 -47
  67. package/plantillas/github-workflows/release-please.yml +44 -44
  68. package/plantillas/github-workflows/swl-ci.yml +107 -107
  69. package/plantillas/github-workflows/swl-security.yml +51 -51
  70. package/plugin.json +353 -351
  71. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  72. package/reglas/arreglar-al-detectar.md +147 -147
  73. package/reglas/fragmentos-compartidos.md +152 -152
  74. package/reglas/harness-claude-code.md +213 -213
  75. package/reglas/registro-componentes-nuevos.md +192 -0
  76. package/reglas/usar-context7.md +226 -226
  77. package/schemas/diary-entry.schema.json +80 -80
  78. package/scripts/actualizar.js +110 -1
  79. package/scripts/audit-tools/audit-history.js +330 -330
  80. package/scripts/audit-tools/bundle-tracker.js +290 -290
  81. package/scripts/audit-tools/canary-monitor.js +352 -352
  82. package/scripts/audit-tools/code-profiler.js +605 -605
  83. package/scripts/audit-tools/dep-doctor.js +320 -320
  84. package/scripts/audit-tools/env-validator.js +206 -206
  85. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  86. package/scripts/audit-tools/lib/output.js +23 -23
  87. package/scripts/audit-tools/migration-checker.js +392 -392
  88. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  89. package/scripts/benchmark-memoria.js +167 -167
  90. package/scripts/configurar-branch-protection.js +418 -418
  91. package/scripts/derivar-feature-list.js +489 -489
  92. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  93. package/scripts/doctor.js +58 -4
  94. package/scripts/field-report.js +199 -199
  95. package/scripts/generar-checklists-consolidados.js +273 -273
  96. package/scripts/generar-inventario.js +420 -420
  97. package/scripts/generar-matriz-lenguajes.js +271 -271
  98. package/scripts/lib/artefactos-python.js +43 -43
  99. package/scripts/lib/benchmark-metrics.js +160 -160
  100. package/scripts/lib/budget-enforcer.js +252 -252
  101. package/scripts/lib/configurar-ci.js +380 -380
  102. package/scripts/lib/contadores-inventario.js +217 -217
  103. package/scripts/lib/detectar-stack-detallado.js +307 -307
  104. package/scripts/lib/diary-entry.js +234 -234
  105. package/scripts/lib/eval-metrics-store.js +218 -218
  106. package/scripts/lib/eval-quality.js +171 -171
  107. package/scripts/lib/eval-schemas.js +144 -144
  108. package/scripts/lib/eval-self-correct.js +106 -106
  109. package/scripts/lib/eval-validator.js +185 -185
  110. package/scripts/lib/expandir-targets.js +71 -71
  111. package/scripts/lib/jaccard-similarity.js +98 -98
  112. package/scripts/lib/longmemeval-runner.js +125 -125
  113. package/scripts/lib/mcp_config.py +127 -0
  114. package/scripts/lib/npm-version.js +261 -261
  115. package/scripts/lib/paquetes-conocidos.js +50 -50
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/lib/toml-merge.js +204 -204
  121. package/scripts/lib/transformadores/codex.js +375 -375
  122. package/scripts/lib/transformadores/cursor.js +359 -359
  123. package/scripts/limpiar-artefactos-python.js +131 -131
  124. package/scripts/mcp-orchestrator.py +8 -18
  125. package/scripts/mcp-pool-manager.py +12 -23
  126. package/scripts/mcp-server/README.md +170 -170
  127. package/scripts/mcp-server/auth.js +105 -105
  128. package/scripts/mcp-server/cache.js +106 -106
  129. package/scripts/mcp-server/telemetry.js +78 -78
  130. package/scripts/migrar-csv-a-array.js +168 -168
  131. package/scripts/migrar-fase-dominio.js +201 -201
  132. package/scripts/publicar.js +511 -511
  133. package/scripts/run-eval.js +141 -141
  134. package/scripts/validar-userland-vacio.js +110 -110
@@ -1,141 +1,141 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * run-eval.js — CLI para ejecutar evaluaciones del eval framework.
6
- *
7
- * Uso:
8
- * node scripts/run-eval.js <ruta-eval.json>
9
- *
10
- * Formato del archivo JSON de eval:
11
- * {
12
- * "functionId": "memoria-busqueda::search",
13
- * "schemaName": "MEMORY_SEARCH_RESULT_SCHEMA", // opcional, valida output
14
- * "qualityScorer": "scoreObservacion", // opcional, calcula calidad
15
- * "input": { ... }, // datos de entrada
16
- * "expectedKeys": ["id", "tipo", "titulo"], // opcional, valida presencia
17
- * "output": { ... } // output a evaluar
18
- * }
19
- *
20
- * Exit codes:
21
- * 0 - eval pasa (valid: true)
22
- * 1 - eval falla (valid: false) o error de I/O
23
- * 2 - error de uso (argumentos inválidos)
24
- */
25
-
26
- const fs = require('fs');
27
- const path = require('path');
28
-
29
- const schemas = require('./lib/eval-schemas');
30
- const validator = require('./lib/eval-validator');
31
- const quality = require('./lib/eval-quality');
32
- const metricsStore = require('./lib/eval-metrics-store');
33
-
34
- function uso() {
35
- console.error('Uso: node scripts/run-eval.js <ruta-eval.json>');
36
- console.error(' node scripts/run-eval.js --rebuild-aggregate');
37
- process.exit(2);
38
- }
39
-
40
- function ejecutarEval(ruta) {
41
- if (!fs.existsSync(ruta)) {
42
- console.error(`Archivo no existe: ${ruta}`);
43
- return 1;
44
- }
45
-
46
- let definicion;
47
- try {
48
- definicion = JSON.parse(fs.readFileSync(ruta, 'utf8'));
49
- } catch (err) {
50
- console.error(`Error parseando ${ruta}: ${err.message}`);
51
- return 1;
52
- }
53
-
54
- const { functionId, schemaName, qualityScorer, output } = definicion;
55
- if (!functionId || typeof functionId !== 'string') {
56
- console.error('functionId requerido en el archivo de eval');
57
- return 1;
58
- }
59
-
60
- const inicio = Date.now();
61
- let valid = true;
62
- let errors = [];
63
- let qualityScore = null;
64
-
65
- // Validar contra schema si se especifica
66
- if (schemaName) {
67
- const schema = schemas[schemaName];
68
- if (!schema) {
69
- console.error(`Schema desconocido: ${schemaName}. Disponibles: ${Object.keys(schemas).filter(k => k.endsWith('_SCHEMA')).join(', ')}`);
70
- return 1;
71
- }
72
- const r = validator.validar(output, schema);
73
- valid = valid && r.valid;
74
- if (!r.valid) errors.push(...r.errors);
75
- }
76
-
77
- // Validar campos esperados si se especifican
78
- if (Array.isArray(definicion.expectedKeys)) {
79
- for (const key of definicion.expectedKeys) {
80
- if (!output || !(key in output)) {
81
- valid = false;
82
- errors.push(`expectedKey faltante: ${key}`);
83
- }
84
- }
85
- }
86
-
87
- // Calcular quality score si se especifica
88
- if (qualityScorer) {
89
- const scorer = quality[qualityScorer];
90
- if (typeof scorer !== 'function') {
91
- console.error(`Quality scorer desconocido: ${qualityScorer}. Disponibles: ${Object.keys(quality).join(', ')}`);
92
- return 1;
93
- }
94
- qualityScore = scorer(output);
95
- }
96
-
97
- const latencyMs = Date.now() - inicio;
98
- const exito = valid;
99
-
100
- // Persistir resultado
101
- const baseDir = process.cwd();
102
- metricsStore.registrar(baseDir, {
103
- functionId,
104
- latencyMs,
105
- success: exito,
106
- qualityScore,
107
- metadata: { schemaName, qualityScorer, evalFile: path.basename(ruta) },
108
- });
109
-
110
- // Reportar
111
- console.log(JSON.stringify({
112
- functionId,
113
- valid,
114
- errors: errors.length > 0 ? errors : undefined,
115
- qualityScore,
116
- latencyMs,
117
- }, null, 2));
118
-
119
- return valid ? 0 : 1;
120
- }
121
-
122
- function main() {
123
- const args = process.argv.slice(2);
124
- if (args.length === 0) uso();
125
-
126
- if (args[0] === '--rebuild-aggregate') {
127
- const r = metricsStore.reconstruirAgregado(process.cwd());
128
- console.log(`Agregado reconstruido: ${r.rebuilt} eventos, ${r.functions} funciones únicas.`);
129
- process.exit(0);
130
- }
131
-
132
- process.exit(ejecutarEval(args[0]));
133
- }
134
-
135
- if (require.main === module) {
136
- main();
137
- }
138
-
139
- module.exports = {
140
- ejecutarEval,
141
- };
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * run-eval.js — CLI para ejecutar evaluaciones del eval framework.
6
+ *
7
+ * Uso:
8
+ * node scripts/run-eval.js <ruta-eval.json>
9
+ *
10
+ * Formato del archivo JSON de eval:
11
+ * {
12
+ * "functionId": "memoria-busqueda::search",
13
+ * "schemaName": "MEMORY_SEARCH_RESULT_SCHEMA", // opcional, valida output
14
+ * "qualityScorer": "scoreObservacion", // opcional, calcula calidad
15
+ * "input": { ... }, // datos de entrada
16
+ * "expectedKeys": ["id", "tipo", "titulo"], // opcional, valida presencia
17
+ * "output": { ... } // output a evaluar
18
+ * }
19
+ *
20
+ * Exit codes:
21
+ * 0 - eval pasa (valid: true)
22
+ * 1 - eval falla (valid: false) o error de I/O
23
+ * 2 - error de uso (argumentos inválidos)
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+
29
+ const schemas = require('./lib/eval-schemas');
30
+ const validator = require('./lib/eval-validator');
31
+ const quality = require('./lib/eval-quality');
32
+ const metricsStore = require('./lib/eval-metrics-store');
33
+
34
+ function uso() {
35
+ console.error('Uso: node scripts/run-eval.js <ruta-eval.json>');
36
+ console.error(' node scripts/run-eval.js --rebuild-aggregate');
37
+ process.exit(2);
38
+ }
39
+
40
+ function ejecutarEval(ruta) {
41
+ if (!fs.existsSync(ruta)) {
42
+ console.error(`Archivo no existe: ${ruta}`);
43
+ return 1;
44
+ }
45
+
46
+ let definicion;
47
+ try {
48
+ definicion = JSON.parse(fs.readFileSync(ruta, 'utf8'));
49
+ } catch (err) {
50
+ console.error(`Error parseando ${ruta}: ${err.message}`);
51
+ return 1;
52
+ }
53
+
54
+ const { functionId, schemaName, qualityScorer, output } = definicion;
55
+ if (!functionId || typeof functionId !== 'string') {
56
+ console.error('functionId requerido en el archivo de eval');
57
+ return 1;
58
+ }
59
+
60
+ const inicio = Date.now();
61
+ let valid = true;
62
+ let errors = [];
63
+ let qualityScore = null;
64
+
65
+ // Validar contra schema si se especifica
66
+ if (schemaName) {
67
+ const schema = schemas[schemaName];
68
+ if (!schema) {
69
+ console.error(`Schema desconocido: ${schemaName}. Disponibles: ${Object.keys(schemas).filter(k => k.endsWith('_SCHEMA')).join(', ')}`);
70
+ return 1;
71
+ }
72
+ const r = validator.validar(output, schema);
73
+ valid = valid && r.valid;
74
+ if (!r.valid) errors.push(...r.errors);
75
+ }
76
+
77
+ // Validar campos esperados si se especifican
78
+ if (Array.isArray(definicion.expectedKeys)) {
79
+ for (const key of definicion.expectedKeys) {
80
+ if (!output || !(key in output)) {
81
+ valid = false;
82
+ errors.push(`expectedKey faltante: ${key}`);
83
+ }
84
+ }
85
+ }
86
+
87
+ // Calcular quality score si se especifica
88
+ if (qualityScorer) {
89
+ const scorer = quality[qualityScorer];
90
+ if (typeof scorer !== 'function') {
91
+ console.error(`Quality scorer desconocido: ${qualityScorer}. Disponibles: ${Object.keys(quality).join(', ')}`);
92
+ return 1;
93
+ }
94
+ qualityScore = scorer(output);
95
+ }
96
+
97
+ const latencyMs = Date.now() - inicio;
98
+ const exito = valid;
99
+
100
+ // Persistir resultado
101
+ const baseDir = process.cwd();
102
+ metricsStore.registrar(baseDir, {
103
+ functionId,
104
+ latencyMs,
105
+ success: exito,
106
+ qualityScore,
107
+ metadata: { schemaName, qualityScorer, evalFile: path.basename(ruta) },
108
+ });
109
+
110
+ // Reportar
111
+ console.log(JSON.stringify({
112
+ functionId,
113
+ valid,
114
+ errors: errors.length > 0 ? errors : undefined,
115
+ qualityScore,
116
+ latencyMs,
117
+ }, null, 2));
118
+
119
+ return valid ? 0 : 1;
120
+ }
121
+
122
+ function main() {
123
+ const args = process.argv.slice(2);
124
+ if (args.length === 0) uso();
125
+
126
+ if (args[0] === '--rebuild-aggregate') {
127
+ const r = metricsStore.reconstruirAgregado(process.cwd());
128
+ console.log(`Agregado reconstruido: ${r.rebuilt} eventos, ${r.functions} funciones únicas.`);
129
+ process.exit(0);
130
+ }
131
+
132
+ process.exit(ejecutarEval(args[0]));
133
+ }
134
+
135
+ if (require.main === module) {
136
+ main();
137
+ }
138
+
139
+ module.exports = {
140
+ ejecutarEval,
141
+ };
@@ -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();