@saulwade/swl-ses 1.3.7 → 1.4.0

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 (129) hide show
  1. package/CLAUDE.md +12 -4
  2. package/README.md +1 -1
  3. package/bin/swl-mcp-server.js +187 -187
  4. package/bin/swl-webhook-server.js +198 -0
  5. package/comandos/swl/.evolved.json +22 -22
  6. package/comandos/swl/adoptar-proyecto.md +21 -1
  7. package/comandos/swl/claudemd.md +14 -1
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/exportar-vault.md +207 -7
  10. package/comandos/swl/nuevo-proyecto.md +24 -2
  11. package/gateway/adapters/base.js +109 -0
  12. package/gateway/adapters/discord.js +167 -0
  13. package/gateway/adapters/email.js +221 -0
  14. package/gateway/adapters/slack.js +192 -0
  15. package/gateway/adapters/telegram.js +183 -0
  16. package/gateway/adapters/webhook.js +113 -0
  17. package/gateway/adapters/whatsapp.js +214 -0
  18. package/gateway/agent-executor.js +322 -0
  19. package/gateway/command-relay.js +271 -0
  20. package/gateway/cron/jobs.js +263 -0
  21. package/gateway/cron/scheduler.js +322 -0
  22. package/gateway/cron/store.js +335 -0
  23. package/gateway/index.js +320 -0
  24. package/gateway/lib/event-channel.js +191 -0
  25. package/gateway/session.js +131 -0
  26. package/gateway/webhook-server.js +324 -0
  27. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  28. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  29. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  30. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  31. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  32. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  33. package/habilidades/eval-framework/SKILL.md +212 -212
  34. package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
  35. package/habilidades/harness-claude-code/SKILL.md +299 -299
  36. package/habilidades/infra-github-actions/SKILL.md +166 -166
  37. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  38. package/habilidades/manejo-errores/.evolved.json +8 -8
  39. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  40. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  41. package/habilidades/nextjs-testing/SKILL.md +89 -5
  42. package/habilidades/node-experto/SKILL.md +37 -1
  43. package/habilidades/patrones-python/SKILL.md +229 -229
  44. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  45. package/habilidades/planear-fase/SKILL.md +319 -319
  46. package/habilidades/react-experto/SKILL.md +45 -4
  47. package/habilidades/release-semver/.evolved.json +8 -8
  48. package/habilidades/swl-claudemd/SKILL.md +15 -1
  49. package/habilidades/tdd-workflow/SKILL.md +36 -4
  50. package/habilidades/testing-python/SKILL.md +340 -340
  51. package/hooks/claudemd-bloat-detector.js +161 -161
  52. package/hooks/inyeccion-contexto.js +8 -3
  53. package/hooks/lib/agent-routing.js +107 -107
  54. package/hooks/lib/auto-consolidator.js +335 -335
  55. package/hooks/lib/error-classifier.js +308 -308
  56. package/hooks/lib/merkle-audit.js +96 -96
  57. package/hooks/lib/provenance-tracker.js +191 -191
  58. package/hooks/lib/rate-limit-ip.js +177 -0
  59. package/hooks/lib/rate-limit-tracker.js +253 -253
  60. package/hooks/lib/resource-quota.js +122 -122
  61. package/hooks/lib/retry-jitter.js +165 -165
  62. package/hooks/lib/skill-auditor.js +588 -588
  63. package/hooks/lib/sync-status.js +228 -228
  64. package/hooks/lib/taint-tracker.js +107 -107
  65. package/hooks/lib/text-similarity.js +241 -241
  66. package/hooks/lib/toon-compressor.js +245 -245
  67. package/hooks/lib/webhook-dedup.js +184 -0
  68. package/hooks/lib/webhook-verify.js +123 -0
  69. package/hooks/proteccion-rutas.js +120 -15
  70. package/hooks/registro-turnos.js +209 -209
  71. package/hooks/sugerir-regenerar-inventario.js +170 -170
  72. package/hooks/validar-formato-post-subagente.js +140 -140
  73. package/hooks/validar-memoria-hook.js +218 -218
  74. package/instintos/prompt-appendices.yaml +57 -57
  75. package/manifiestos/agent-output-schemas.json +57 -57
  76. package/manifiestos/modulos.json +1 -0
  77. package/manifiestos/skills-lock.json +37 -37
  78. package/package.json +5 -3
  79. package/plantillas/auditor-veto-template.md +105 -105
  80. package/plantillas/github-workflows/README.md +47 -47
  81. package/plantillas/github-workflows/release-please.yml +44 -44
  82. package/plantillas/github-workflows/swl-ci.yml +107 -107
  83. package/plantillas/github-workflows/swl-security.yml +51 -51
  84. package/plugin.json +1 -1
  85. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  86. package/reglas/arreglar-al-detectar.md +147 -147
  87. package/reglas/fragmentos-compartidos.md +152 -152
  88. package/reglas/harness-claude-code.md +213 -213
  89. package/reglas/usar-context7.md +226 -226
  90. package/reglas/usar-sistema-swl.md +251 -0
  91. package/schemas/diary-entry.schema.json +80 -80
  92. package/scripts/benchmark-memoria.js +167 -167
  93. package/scripts/comandos/skills.js +251 -2
  94. package/scripts/configurar-branch-protection.js +418 -418
  95. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  96. package/scripts/field-report.js +199 -199
  97. package/scripts/generar-checklists-consolidados.js +273 -273
  98. package/scripts/generar-inventario.js +420 -420
  99. package/scripts/generar-matriz-lenguajes.js +271 -271
  100. package/scripts/lib/artefactos-python.js +43 -43
  101. package/scripts/lib/benchmark-metrics.js +160 -160
  102. package/scripts/lib/budget-enforcer.js +252 -252
  103. package/scripts/lib/configurar-ci.js +380 -380
  104. package/scripts/lib/contadores-inventario.js +217 -217
  105. package/scripts/lib/detectar-stack-detallado.js +307 -307
  106. package/scripts/lib/diary-entry.js +234 -234
  107. package/scripts/lib/eval-metrics-store.js +218 -218
  108. package/scripts/lib/eval-quality.js +171 -171
  109. package/scripts/lib/eval-schemas.js +144 -144
  110. package/scripts/lib/eval-self-correct.js +106 -106
  111. package/scripts/lib/eval-validator.js +185 -185
  112. package/scripts/lib/jaccard-similarity.js +98 -98
  113. package/scripts/lib/longmemeval-runner.js +125 -125
  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/limpiar-artefactos-python.js +131 -131
  121. package/scripts/mcp-server/README.md +128 -128
  122. package/scripts/mcp-server/handlers.js +206 -206
  123. package/scripts/migrar-csv-a-array.js +168 -168
  124. package/scripts/migrar-fase-dominio.js +201 -201
  125. package/scripts/publicar.js +511 -511
  126. package/scripts/run-eval.js +141 -141
  127. package/scripts/validar-manifest.js +195 -195
  128. package/scripts/validar-userland-vacio.js +110 -110
  129. package/scripts/verificar-release.js +110 -0
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * webhook-verify.js — Verificación de firmas HMAC en webhooks entrantes.
5
+ *
6
+ * Provee dos funciones independientes:
7
+ *
8
+ * - verifyGithubSignature(payloadBody, signatureHeader, secret)
9
+ * GitHub envía la firma como header `X-Hub-Signature-256: sha256=<hex>`.
10
+ * Calcula HMAC SHA-256 sobre el body crudo y compara timing-safe.
11
+ *
12
+ * - verifyBearer(authorizationHeader, secret)
13
+ * Genérico: header `Authorization: Bearer <token>` comparado timing-safe
14
+ * contra el secreto compartido.
15
+ *
16
+ * Ambas funciones:
17
+ * - Aceptan body como Buffer o string. Buffer es lo correcto para HMAC
18
+ * porque GitHub firma los bytes literales del request, no la cadena
19
+ * decodificada.
20
+ * - Retornan boolean. Nunca lanzan excepciones — falla = false.
21
+ * - Usan `crypto.timingSafeEqual` para evitar ataques de timing.
22
+ * - Zero-deps (solo `crypto` nativo).
23
+ *
24
+ * Origen: port de `temp/claude-code-telegram-main/src/api/auth.py` (Python, 61 LOC).
25
+ *
26
+ * @module hooks/lib/webhook-verify
27
+ */
28
+
29
+ const crypto = require('crypto');
30
+
31
+ /**
32
+ * Verifica firma HMAC SHA-256 al estilo GitHub.
33
+ *
34
+ * @param {Buffer|string} payloadBody Body crudo del request.
35
+ * @param {string|undefined|null} signatureHeader Valor del header X-Hub-Signature-256.
36
+ * @param {string} secret Secreto compartido configurado en GitHub.
37
+ * @returns {boolean} true si la firma es válida; false en cualquier otro caso.
38
+ */
39
+ function verifyGithubSignature(payloadBody, signatureHeader, secret) {
40
+ if (!signatureHeader || typeof signatureHeader !== 'string') {
41
+ return false;
42
+ }
43
+ if (!signatureHeader.startsWith('sha256=')) {
44
+ return false;
45
+ }
46
+ if (!secret || typeof secret !== 'string') {
47
+ return false;
48
+ }
49
+ if (payloadBody === undefined || payloadBody === null) {
50
+ return false;
51
+ }
52
+
53
+ const bodyBuffer = Buffer.isBuffer(payloadBody)
54
+ ? payloadBody
55
+ : Buffer.from(String(payloadBody), 'utf8');
56
+
57
+ const expectedDigest = crypto
58
+ .createHmac('sha256', secret)
59
+ .update(bodyBuffer)
60
+ .digest('hex');
61
+ const expectedHeader = 'sha256=' + expectedDigest;
62
+
63
+ return constantTimeEqual(expectedHeader, signatureHeader);
64
+ }
65
+
66
+ /**
67
+ * Verifica un Bearer token simple en el header Authorization.
68
+ *
69
+ * @param {string|undefined|null} authorizationHeader Valor del header Authorization.
70
+ * @param {string} secret Token esperado (sin el prefijo "Bearer ").
71
+ * @returns {boolean} true si el token coincide; false en cualquier otro caso.
72
+ */
73
+ function verifyBearer(authorizationHeader, secret) {
74
+ if (!authorizationHeader || typeof authorizationHeader !== 'string') {
75
+ return false;
76
+ }
77
+ if (!authorizationHeader.startsWith('Bearer ')) {
78
+ return false;
79
+ }
80
+ if (!secret || typeof secret !== 'string') {
81
+ return false;
82
+ }
83
+
84
+ const token = authorizationHeader.slice('Bearer '.length);
85
+ return constantTimeEqual(token, secret);
86
+ }
87
+
88
+ /**
89
+ * Comparación timing-safe de dos strings.
90
+ *
91
+ * `crypto.timingSafeEqual` requiere buffers de la misma longitud. Si las
92
+ * longitudes difieren, el comparador termina temprano y eso filtra
93
+ * información de longitud por timing. Para evitarlo:
94
+ *
95
+ * 1. Si longitudes difieren, retornar false sin invocar timingSafeEqual.
96
+ * 2. Si coinciden, comparar.
97
+ *
98
+ * El filtrado de longitud es aceptable aquí porque la longitud de la firma
99
+ * HMAC SHA-256 hex es fija (71 caracteres con prefijo "sha256=") y la
100
+ * longitud del Bearer es controlada por el secreto del servidor — ninguna
101
+ * es información sensible derivable del input del atacante.
102
+ *
103
+ * @param {string} a
104
+ * @param {string} b
105
+ * @returns {boolean}
106
+ */
107
+ function constantTimeEqual(a, b) {
108
+ if (typeof a !== 'string' || typeof b !== 'string') {
109
+ return false;
110
+ }
111
+ if (a.length !== b.length) {
112
+ return false;
113
+ }
114
+ const bufA = Buffer.from(a, 'utf8');
115
+ const bufB = Buffer.from(b, 'utf8');
116
+ return crypto.timingSafeEqual(bufA, bufB);
117
+ }
118
+
119
+ module.exports = {
120
+ verifyGithubSignature,
121
+ verifyBearer,
122
+ constantTimeEqual,
123
+ };
@@ -208,8 +208,10 @@ function verificarDentroDeCwd(rutaRaw, cwdActual) {
208
208
 
209
209
  /**
210
210
  * Rutas permitidas fuera del CWD.
211
- * Estas rutas son legítimas para el funcionamiento de Claude Code
212
- * (memoria persistente, configuración de proyecto, etc.).
211
+ * Estas rutas son legítimas para el funcionamiento de Claude Code y SWL
212
+ * (memoria persistente, configuración global, skills/agentes/comandos/reglas
213
+ * del usuario que SWL instala globalmente y que se modifican durante backports
214
+ * entre proyectos).
213
215
  *
214
216
  * Se comparan con startsWith case-insensitive en Windows.
215
217
  */
@@ -218,21 +220,100 @@ const RUTAS_PERMITIDAS_EXTERNAS = [
218
220
  path.join(os.homedir(), '.claude', 'projects'),
219
221
  // Configuración global de Claude Code
220
222
  path.join(os.homedir(), '.claude', 'settings.json'),
223
+ // Skills globales del usuario (instaladas por SWL, modificables durante backport)
224
+ path.join(os.homedir(), '.claude', 'skills'),
225
+ // Agentes globales del usuario
226
+ path.join(os.homedir(), '.claude', 'agents'),
227
+ // Comandos globales del usuario
228
+ path.join(os.homedir(), '.claude', 'commands'),
229
+ // Reglas globales del usuario (modificables durante consolidación cross-proyecto)
230
+ path.join(os.homedir(), '.claude', 'rules'),
221
231
  ];
222
232
 
223
233
  /**
224
- * Verifica si una ruta resuelta está en la lista de rutas externas permitidas.
234
+ * Parsea la variable de entorno SWL_PROTECCION_RUTAS_PERMITIR (CSV de rutas
235
+ * absolutas) y devuelve un array de paths normalizados.
236
+ *
237
+ * Patrón de uso: el usuario define la variable ANTES de iniciar Claude Code
238
+ * cuando necesita permitir writes legítimos a destinos fuera del CWD que no
239
+ * están cubiertos por el whitelist por defecto. Ej:
240
+ *
241
+ * SWL_PROTECCION_RUTAS_PERMITIR="D:\Python\sigaf,F:\vault\notas"
242
+ *
243
+ * Cada ruta se resuelve con path.resolve para normalizar separadores y
244
+ * eliminar referencias relativas. Rutas vacías o solo whitespace se descartan.
245
+ *
246
+ * @returns {string[]} array de paths absolutos resueltos (puede estar vacío)
247
+ */
248
+ function parsearRutasPermitidasUsuario() {
249
+ const raw = process.env.SWL_PROTECCION_RUTAS_PERMITIR;
250
+ if (!raw || typeof raw !== 'string') return [];
251
+ return raw
252
+ .split(',')
253
+ .map(r => r.trim())
254
+ .filter(r => r.length > 0)
255
+ .map(r => path.resolve(r));
256
+ }
257
+
258
+ /**
259
+ * Cache módulo-level del parseo de SWL_PROTECCION_RUTAS_PERMITIR.
260
+ *
261
+ * `process.env` no cambia durante la vida del proceso (los hooks se ejecutan
262
+ * como subproceso de Claude Code, un proceso nuevo por cada Write/Edit), por
263
+ * lo que el parseo solo necesita hacerse una vez al cargar el módulo. Esto
264
+ * evita re-parsear el CSV y volver a llamar `path.resolve` en cada call a
265
+ * `esRutaExternaPermitida()`, que se ejecuta por cada operación de escritura.
266
+ *
267
+ * Para tests que necesitan re-parsear (cambian la variable después de cargar
268
+ * el módulo), exportar la función `_resetCacheRutasPermitidas()` permitiría
269
+ * forzar el refresh — los tests existentes usan spawnSync, por lo que cada
270
+ * subproceso carga el módulo fresco con su propio entorno.
271
+ */
272
+ const RUTAS_PERMITIDAS_USUARIO = parsearRutasPermitidasUsuario();
273
+
274
+ /**
275
+ * Normaliza separadores y elimina trailing slash para comparación de prefijo
276
+ * con boundary check. Garantiza que la comparación sea sobre componentes de
277
+ * path completos, no sobre substrings.
278
+ *
279
+ * Ejemplo: ambas variantes de `~/.claude/skills/` y `~/.claude/skills` se
280
+ * normalizan a `.../.claude/skills` (sin trailing).
281
+ *
282
+ * @param {string} p
283
+ * @returns {string}
284
+ */
285
+ function normalizarParaComparacion(p) {
286
+ return normalizarSeparadores(p).replace(/\/+$/, '');
287
+ }
288
+
289
+ /**
290
+ * Verifica si una ruta resuelta está en la lista de rutas externas permitidas
291
+ * (whitelist por defecto + rutas opt-in del usuario vía variable de entorno).
292
+ *
293
+ * **Boundary check obligatorio**: usa match exacto O prefijo seguido de '/'
294
+ * para evitar que paths siblings con el mismo prefijo textual pasen la
295
+ * verificación. Ejemplos de hostiles que DEBEN ser bloqueados:
296
+ * - whitelist `~/.claude/skills` → `~/.claude/skills-evil/x.md` (no es subdir)
297
+ * - whitelist `~/.claude/settings.json` → `~/.claude/settings.json.bak`
298
+ * - whitelist `D:/Proyecto` → `D:/Proyecto-otro/file.md`
299
+ *
225
300
  * @param {string} rutaResuelta
226
301
  * @returns {boolean}
227
302
  */
228
303
  function esRutaExternaPermitida(rutaResuelta) {
229
- const rutaNorm = normalizarSeparadores(rutaResuelta);
230
- for (const permitida of RUTAS_PERMITIDAS_EXTERNAS) {
231
- const permitidaNorm = normalizarSeparadores(permitida);
232
- const coincide = process.platform === 'win32'
233
- ? rutaNorm.toLowerCase().startsWith(permitidaNorm.toLowerCase())
234
- : rutaNorm.startsWith(permitidaNorm);
235
- if (coincide) return true;
304
+ const rutaNorm = normalizarParaComparacion(rutaResuelta);
305
+ const rutaCmp = process.platform === 'win32' ? rutaNorm.toLowerCase() : rutaNorm;
306
+
307
+ const combinadas = RUTAS_PERMITIDAS_EXTERNAS.concat(RUTAS_PERMITIDAS_USUARIO);
308
+ for (const permitida of combinadas) {
309
+ const permitidaNorm = normalizarParaComparacion(permitida);
310
+ const permitidaCmp = process.platform === 'win32' ? permitidaNorm.toLowerCase() : permitidaNorm;
311
+
312
+ // Match exacto (cubre archivos como settings.json) O prefijo seguido de '/'
313
+ // (cubre subdirectorios como skills/foo dentro del directorio skills).
314
+ if (rutaCmp === permitidaCmp || rutaCmp.startsWith(permitidaCmp + '/')) {
315
+ return true;
316
+ }
236
317
  }
237
318
  return false;
238
319
  }
@@ -270,7 +351,8 @@ function extraerRuta(toolInput) {
270
351
  * @returns {string}
271
352
  */
272
353
  function construirMensajeBloqueo(tipoViolacion, rutaRaw, rutaResuelta, cwdActual) {
273
- return [
354
+ const esTraversal = /traversal/i.test(tipoViolacion);
355
+ const lineas = [
274
356
  `Escritura bloqueada por política de seguridad de rutas.`,
275
357
  ``,
276
358
  `Violación: ${tipoViolacion}`,
@@ -282,10 +364,33 @@ function construirMensajeBloqueo(tipoViolacion, rutaRaw, rutaResuelta, cwdActual
282
364
  ` - La ruta de destino debe estar dentro del directorio de trabajo actual.`,
283
365
  ` - No se permiten secuencias de path traversal (../, ~/, %2e%2e, etc.).`,
284
366
  ` - No se permiten rutas absolutas fuera del proyecto.`,
285
- ``,
286
- `Si necesitas escribir fuera del CWD, ajusta el directorio de trabajo`,
287
- `o utiliza una ruta relativa dentro del proyecto.`,
288
- ].join('\n');
367
+ ];
368
+
369
+ if (!esTraversal) {
370
+ lineas.push(
371
+ ``,
372
+ `Rutas externas permitidas por defecto:`,
373
+ ` - ~/.claude/projects, settings.json, skills, agents, commands, rules`,
374
+ ``,
375
+ `Para destinos legítimos no cubiertos (otros proyectos del usuario, vault,`,
376
+ `backports cross-proyecto), define la variable de entorno ANTES de iniciar`,
377
+ `Claude Code con un CSV de rutas absolutas:`,
378
+ ``,
379
+ ` PowerShell: $env:SWL_PROTECCION_RUTAS_PERMITIR = "D:\\Python\\sigaf,F:\\vault"`,
380
+ ` Bash: export SWL_PROTECCION_RUTAS_PERMITIR="/d/Python/sigaf,/f/vault"`,
381
+ ``,
382
+ `Alternativa: usar Bash con cp/heredoc (no pasa por este hook) cuando el`,
383
+ `destino es seguro y el cambio es puntual.`,
384
+ );
385
+ } else {
386
+ lineas.push(
387
+ ``,
388
+ `Path traversal NO se levanta con SWL_PROTECCION_RUTAS_PERMITIR — la variable`,
389
+ `solo afecta el check de "dentro del CWD", no las secuencias ../, ~/, %2e%2e.`,
390
+ );
391
+ }
392
+
393
+ return lineas.join('\n');
289
394
  }
290
395
 
291
396
  // ---------------------------------------------------------------------------