@saulwade/swl-ses 1.3.8 → 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 (128) 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 +108 -0
  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 +20 -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/tdd-workflow/SKILL.md +36 -4
  49. package/habilidades/testing-python/SKILL.md +340 -340
  50. package/hooks/claudemd-bloat-detector.js +161 -161
  51. package/hooks/inyeccion-contexto.js +8 -3
  52. package/hooks/lib/agent-routing.js +107 -107
  53. package/hooks/lib/auto-consolidator.js +335 -335
  54. package/hooks/lib/error-classifier.js +308 -308
  55. package/hooks/lib/merkle-audit.js +96 -96
  56. package/hooks/lib/provenance-tracker.js +191 -191
  57. package/hooks/lib/rate-limit-ip.js +177 -0
  58. package/hooks/lib/rate-limit-tracker.js +253 -253
  59. package/hooks/lib/resource-quota.js +122 -122
  60. package/hooks/lib/retry-jitter.js +165 -165
  61. package/hooks/lib/skill-auditor.js +588 -588
  62. package/hooks/lib/sync-status.js +228 -228
  63. package/hooks/lib/taint-tracker.js +107 -107
  64. package/hooks/lib/text-similarity.js +241 -241
  65. package/hooks/lib/toon-compressor.js +245 -245
  66. package/hooks/lib/webhook-dedup.js +184 -0
  67. package/hooks/lib/webhook-verify.js +123 -0
  68. package/hooks/proteccion-rutas.js +120 -15
  69. package/hooks/registro-turnos.js +209 -209
  70. package/hooks/sugerir-regenerar-inventario.js +170 -170
  71. package/hooks/validar-formato-post-subagente.js +140 -140
  72. package/hooks/validar-memoria-hook.js +218 -218
  73. package/instintos/prompt-appendices.yaml +57 -57
  74. package/manifiestos/agent-output-schemas.json +57 -57
  75. package/manifiestos/modulos.json +1 -0
  76. package/manifiestos/skills-lock.json +34 -34
  77. package/package.json +5 -3
  78. package/plantillas/auditor-veto-template.md +105 -105
  79. package/plantillas/github-workflows/README.md +47 -47
  80. package/plantillas/github-workflows/release-please.yml +44 -44
  81. package/plantillas/github-workflows/swl-ci.yml +107 -107
  82. package/plantillas/github-workflows/swl-security.yml +51 -51
  83. package/plugin.json +1 -1
  84. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  85. package/reglas/arreglar-al-detectar.md +147 -147
  86. package/reglas/fragmentos-compartidos.md +152 -152
  87. package/reglas/harness-claude-code.md +213 -213
  88. package/reglas/usar-context7.md +226 -226
  89. package/reglas/usar-sistema-swl.md +251 -0
  90. package/schemas/diary-entry.schema.json +80 -80
  91. package/scripts/benchmark-memoria.js +167 -167
  92. package/scripts/comandos/skills.js +251 -2
  93. package/scripts/configurar-branch-protection.js +418 -418
  94. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  95. package/scripts/field-report.js +199 -199
  96. package/scripts/generar-checklists-consolidados.js +273 -273
  97. package/scripts/generar-inventario.js +420 -420
  98. package/scripts/generar-matriz-lenguajes.js +271 -271
  99. package/scripts/lib/artefactos-python.js +43 -43
  100. package/scripts/lib/benchmark-metrics.js +160 -160
  101. package/scripts/lib/budget-enforcer.js +252 -252
  102. package/scripts/lib/configurar-ci.js +380 -380
  103. package/scripts/lib/contadores-inventario.js +217 -217
  104. package/scripts/lib/detectar-stack-detallado.js +307 -307
  105. package/scripts/lib/diary-entry.js +234 -234
  106. package/scripts/lib/eval-metrics-store.js +218 -218
  107. package/scripts/lib/eval-quality.js +171 -171
  108. package/scripts/lib/eval-schemas.js +144 -144
  109. package/scripts/lib/eval-self-correct.js +106 -106
  110. package/scripts/lib/eval-validator.js +185 -185
  111. package/scripts/lib/jaccard-similarity.js +98 -98
  112. package/scripts/lib/longmemeval-runner.js +125 -125
  113. package/scripts/lib/npm-version.js +261 -261
  114. package/scripts/lib/paquetes-conocidos.js +50 -50
  115. package/scripts/lib/prompt-builder.js +264 -264
  116. package/scripts/lib/rrf-fusion.js +175 -175
  117. package/scripts/lib/scoring-instintos.js +277 -277
  118. package/scripts/lib/semantic-search.js +252 -252
  119. package/scripts/limpiar-artefactos-python.js +131 -131
  120. package/scripts/mcp-server/README.md +128 -128
  121. package/scripts/mcp-server/handlers.js +206 -206
  122. package/scripts/migrar-csv-a-array.js +168 -168
  123. package/scripts/migrar-fase-dominio.js +201 -201
  124. package/scripts/publicar.js +511 -511
  125. package/scripts/run-eval.js +141 -141
  126. package/scripts/validar-manifest.js +195 -195
  127. package/scripts/validar-userland-vacio.js +110 -110
  128. 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
  // ---------------------------------------------------------------------------