@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.
- package/CLAUDE.md +12 -4
- package/README.md +1 -1
- package/bin/swl-mcp-server.js +187 -187
- package/bin/swl-webhook-server.js +198 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/adoptar-proyecto.md +21 -1
- package/comandos/swl/claudemd.md +14 -1
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/exportar-vault.md +207 -7
- package/comandos/swl/nuevo-proyecto.md +24 -2
- package/gateway/adapters/base.js +109 -0
- package/gateway/adapters/discord.js +167 -0
- package/gateway/adapters/email.js +221 -0
- package/gateway/adapters/slack.js +192 -0
- package/gateway/adapters/telegram.js +183 -0
- package/gateway/adapters/webhook.js +113 -0
- package/gateway/adapters/whatsapp.js +214 -0
- package/gateway/agent-executor.js +322 -0
- package/gateway/command-relay.js +271 -0
- package/gateway/cron/jobs.js +263 -0
- package/gateway/cron/scheduler.js +322 -0
- package/gateway/cron/store.js +335 -0
- package/gateway/index.js +320 -0
- package/gateway/lib/event-channel.js +191 -0
- package/gateway/session.js +131 -0
- package/gateway/webhook-server.js +324 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/build-errors-nextjs/SKILL.md +55 -1
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nextjs-testing/SKILL.md +89 -5
- package/habilidades/node-experto/SKILL.md +37 -1
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/react-experto/SKILL.md +45 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/swl-claudemd/SKILL.md +15 -1
- package/habilidades/tdd-workflow/SKILL.md +36 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/inyeccion-contexto.js +8 -3
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-ip.js +177 -0
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/webhook-dedup.js +184 -0
- package/hooks/lib/webhook-verify.js +123 -0
- package/hooks/proteccion-rutas.js +120 -15
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1 -0
- package/manifiestos/skills-lock.json +37 -37
- package/package.json +5 -3
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +1 -1
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/reglas/usar-sistema-swl.md +251 -0
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/comandos/skills.js +251 -2
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +195 -195
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/verificar-release.js +110 -0
|
@@ -17,9 +17,12 @@ Este comando es **complementario a `/swl:compactar`**, no lo reemplaza. Compacta
|
|
|
17
17
|
- Saul lo pide explícitamente.
|
|
18
18
|
- La sesión produjo una decisión arquitectural que afecta a otros proyectos del ecosistema.
|
|
19
19
|
|
|
20
|
-
## Paso 0 — Validación del destino
|
|
20
|
+
## Paso 0 — Validación del destino y canal de escritura
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Hay **dos canales** posibles para escribir al vault. El canal preferido es el
|
|
23
|
+
MCP de Obsidian; el filesystem directo es fallback documentado.
|
|
24
|
+
|
|
25
|
+
### 0a — Validar el vault en filesystem (para lectura y detección)
|
|
23
26
|
|
|
24
27
|
```bash
|
|
25
28
|
test -d "F:\Google Drive\Developer\Obsidian\Vault\SWL\00-Inbox" \
|
|
@@ -29,6 +32,89 @@ test -d "F:\Google Drive\Developer\Obsidian\Vault\SWL\00-Inbox" \
|
|
|
29
32
|
|
|
30
33
|
Si la ruta no es accesible (por ejemplo, Google Drive no sincronizado o letra de unidad distinta), **abortar con mensaje claro**. No intentes rutas alternativas sin permiso explícito.
|
|
31
34
|
|
|
35
|
+
### 0b — Detectar disponibilidad del MCP de Obsidian
|
|
36
|
+
|
|
37
|
+
Verifica si los tools `mcp__obsidian__obsidian_append_content` o
|
|
38
|
+
`mcp__obsidian__obsidian_patch_content` están cargados o deferidos:
|
|
39
|
+
|
|
40
|
+
- Si aparecen como **deferred** en `<system-reminder>`, cargar el schema con
|
|
41
|
+
`ToolSearch(query="select:mcp__obsidian__obsidian_append_content", ...)`.
|
|
42
|
+
- Si tras `ToolSearch` siguen sin estar disponibles, el MCP server no está
|
|
43
|
+
corriendo (Obsidian cerrado o plugin Local REST API desactivado). En ese
|
|
44
|
+
caso usar canal de fallback (filesystem directo) — ver Paso 4.
|
|
45
|
+
|
|
46
|
+
**Por qué MCP-first**: el hook `proteccion-rutas.js` bloquea la herramienta
|
|
47
|
+
`Write` con destino fuera del CWD del proyecto. La ruta del vault
|
|
48
|
+
(`F:\Google Drive\...`) siempre cae fuera del CWD si trabajas en
|
|
49
|
+
`D:\Python\<proyecto>\`. El MCP de Obsidian opera vía HTTPS al puerto 27124
|
|
50
|
+
del plugin Local REST API — **no pasa por el hook de filesystem**, así que
|
|
51
|
+
nunca dispara el bloqueo.
|
|
52
|
+
|
|
53
|
+
Defaultear a filesystem cuando el MCP está disponible es un anti-patrón
|
|
54
|
+
documentado en `reglas/consultar-vault-primero.md § Workflow forzoso para
|
|
55
|
+
escritura al vault`. El bloqueo por `proteccion-rutas.js` no es una falla a
|
|
56
|
+
esquivar — es una señal de que el canal correcto es el MCP.
|
|
57
|
+
|
|
58
|
+
### 0c — Verificar que el vault activo de Obsidian apunta a `Vault\SWL\` (CRÍTICO)
|
|
59
|
+
|
|
60
|
+
> Origen del paso: sesión 2026-05-13 v1.4.0. El MCP de Obsidian resuelve paths
|
|
61
|
+
> relativos contra el **vault que Obsidian tiene abierto**, no contra una
|
|
62
|
+
> ruta hardcodeada en el plugin. Si Obsidian abrió un vault distinto del
|
|
63
|
+
> esperado (ej. `F:\Google Drive\Developer\Obsidian\Vault\` raíz en lugar
|
|
64
|
+
> de `Vault\SWL\`), todas las escrituras del MCP irán al vault equivocado.
|
|
65
|
+
> El plugin **crea carpetas inexistentes** sobre la marcha, así que ni
|
|
66
|
+
> siquiera obtendrás error — los archivos terminan en una ubicación huérfana.
|
|
67
|
+
|
|
68
|
+
Verifica antes de cualquier llamada MCP:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Path canónico de la config global de Obsidian en Windows
|
|
72
|
+
APP="$APPDATA/obsidian/obsidian.json"
|
|
73
|
+
node -e "
|
|
74
|
+
const fs = require('fs');
|
|
75
|
+
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
76
|
+
const abierto = Object.values(data.vaults).find(v => v.open === true);
|
|
77
|
+
console.log('Vault abierto:', abierto ? abierto.path : 'NINGUNO');
|
|
78
|
+
console.log('Esperado: F:\\\\Google Drive\\\\Developer\\\\Obsidian\\\\Vault\\\\SWL');
|
|
79
|
+
console.log('OK:', abierto && abierto.path.endsWith('SWL') ? 'sí' : 'NO');
|
|
80
|
+
" "$APP"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Resultado esperado:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Vault abierto: F:\Google Drive\Developer\Obsidian\Vault\SWL
|
|
87
|
+
Esperado: F:\Google Drive\Developer\Obsidian\Vault\SWL
|
|
88
|
+
OK: sí
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Si `OK: NO` o `Vault abierto: NINGUNO`, **NO usar MCP** — caer al filesystem
|
|
92
|
+
para esta sesión y reportar al usuario que debe abrir el vault `SWL` en
|
|
93
|
+
Obsidian (File → Open vault → seleccionar `F:\Google Drive\Developer\Obsidian\Vault\SWL`).
|
|
94
|
+
|
|
95
|
+
### 0d — Verificar que la apiKey del MCP coincide con la del plugin activo
|
|
96
|
+
|
|
97
|
+
> Origen: cuando el vault activo cambia, la apiKey del plugin Local REST API
|
|
98
|
+
> cambia (cada vault tiene su propio `.obsidian/plugins/obsidian-local-rest-api/data.json`
|
|
99
|
+
> con apiKey, cert y privateKey distintos). El MCP server registrado en
|
|
100
|
+
> Claude.ai tiene una apiKey hardcodeada — si no se actualiza tras cambiar de
|
|
101
|
+
> vault, todas las llamadas retornan `40101 Authorization required`.
|
|
102
|
+
|
|
103
|
+
Si tras `ToolSearch` el MCP carga sus tools pero la primera llamada (ej.
|
|
104
|
+
`obsidian_list_files_in_dir`) retorna `Error 40101: Authorization required`,
|
|
105
|
+
el síntoma es claro: apiKey desincronizada.
|
|
106
|
+
|
|
107
|
+
Acción:
|
|
108
|
+
1. Leer la apiKey actual del plugin con
|
|
109
|
+
`grep '"apiKey"' "F:/Google Drive/Developer/Obsidian/Vault/SWL/.obsidian/plugins/obsidian-local-rest-api/data.json"`
|
|
110
|
+
2. Reportar al usuario que actualice en **claude.ai → Settings → Connectors
|
|
111
|
+
→ Obsidian → API Key**. Disconnect + Reconnect tras pegar el valor.
|
|
112
|
+
3. Mientras tanto, usar filesystem como fallback para escribir al Inbox.
|
|
113
|
+
|
|
114
|
+
Si hay procesos zombie de Obsidian o `mcp-obsidian` cliente con apiKey
|
|
115
|
+
cacheada, requieren terminación + reinicio de Claude Code para tomar la
|
|
116
|
+
nueva apiKey.
|
|
117
|
+
|
|
32
118
|
## Paso 1 — Identificación del proyecto actual
|
|
33
119
|
|
|
34
120
|
Detecta qué proyecto eres leyendo:
|
|
@@ -205,23 +291,104 @@ Ejecutar `/sync-projects {proyecto-slug}` en el vault para integrar los cambios
|
|
|
205
291
|
|
|
206
292
|
## Paso 4 — Escritura en el vault
|
|
207
293
|
|
|
208
|
-
|
|
294
|
+
El archivo destino es:
|
|
209
295
|
|
|
210
296
|
```
|
|
211
297
|
F:\Google Drive\Developer\Obsidian\Vault\SWL\00-Inbox\YYYY-MM-DD_HHMM_export-{proyecto-slug}.md
|
|
212
298
|
```
|
|
213
299
|
|
|
214
|
-
|
|
300
|
+
Con UTF-8 sin BOM. Usar **uno de dos canales** según disponibilidad:
|
|
301
|
+
|
|
302
|
+
### Canal A (preferido) — MCP de Obsidian
|
|
303
|
+
|
|
304
|
+
Si el Paso 0b confirmó que `mcp__obsidian__obsidian_append_content` está
|
|
305
|
+
disponible (cargado directamente o tras `ToolSearch`), usar este canal:
|
|
306
|
+
|
|
307
|
+
```jsonc
|
|
308
|
+
// La ruta es RELATIVA al vault root (no incluye F:\...\SWL\)
|
|
309
|
+
mcp__obsidian__obsidian_append_content({
|
|
310
|
+
filepath: "00-Inbox/YYYY-MM-DD_HHMM_export-{proyecto-slug}.md",
|
|
311
|
+
content: "<contenido completo del export>"
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Ventajas frente al filesystem:
|
|
316
|
+
- **No pasa por `proteccion-rutas.js`** — el MCP opera vía HTTPS al puerto
|
|
317
|
+
27124, fuera del flujo de Bash/Write/Edit.
|
|
318
|
+
- **Sin staging intermedio**: escribe directamente al archivo final del vault.
|
|
319
|
+
- **Auditable**: el plugin Local REST API de Obsidian registra el acceso.
|
|
320
|
+
- **Cross-OS**: el wrapper funciona idéntico en Windows/macOS/Linux sin
|
|
321
|
+
manejar separators de path.
|
|
322
|
+
|
|
323
|
+
Notas operativas:
|
|
324
|
+
- Si el archivo ya existe con ese timestamp (raro), agregar sufijo `_b`
|
|
325
|
+
al nombre antes de invocar `append_content`. El MCP de obsidian no
|
|
326
|
+
sobreescribe si el archivo existe — `append` agrega al final.
|
|
327
|
+
- Si se necesita escribir secciones específicas en lugar de un archivo
|
|
328
|
+
completo, usar `mcp__obsidian__obsidian_patch_content` con
|
|
329
|
+
`target_type: "heading"`.
|
|
330
|
+
|
|
331
|
+
### Canal B (fallback) — Filesystem directo
|
|
332
|
+
|
|
333
|
+
Solo cuando el MCP no responde tras `ToolSearch` (Obsidian cerrado, plugin
|
|
334
|
+
desactivado, sin red local). **Esto va a chocar con `proteccion-rutas.js`**
|
|
335
|
+
si el CWD es el directorio del proyecto, así que requiere ejecución vía
|
|
336
|
+
Bash con redirección o vía un comando del CLI nativo del SO:
|
|
215
337
|
|
|
216
338
|
```powershell
|
|
339
|
+
# PowerShell — UTF-8 sin BOM
|
|
217
340
|
$encoding = New-Object System.Text.UTF8Encoding($false)
|
|
218
341
|
[System.IO.File]::WriteAllText($path, $content, $encoding)
|
|
219
342
|
```
|
|
220
343
|
|
|
221
|
-
|
|
344
|
+
```bash
|
|
345
|
+
# Bash + heredoc — UTF-8 sin BOM por default en Node 18+
|
|
346
|
+
cat > "$path" <<'EOF'
|
|
347
|
+
<contenido del export>
|
|
348
|
+
EOF
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**No usar `Write` directo** desde la herramienta del agente — `proteccion-rutas.js`
|
|
352
|
+
lo bloquea. Si por algún motivo el agente necesita usar `Write`, primero
|
|
353
|
+
escribe a `_userland/staging/<timestamp>.md` dentro del CWD, luego mueve con
|
|
354
|
+
`Bash` (`mv` o PowerShell `Move-Item`).
|
|
355
|
+
|
|
356
|
+
Reportar al usuario qué canal se usó:
|
|
357
|
+
- Canal A → `[OK] Vía: MCP Obsidian (puerto 27124)`
|
|
358
|
+
- Canal B → `[OK] Vía: filesystem directo (MCP no disponible)`
|
|
359
|
+
- Canal Híbrido → `[OK] Vía: filesystem para Inbox, MCP para promociones`
|
|
360
|
+
|
|
361
|
+
### Modo HÍBRIDO (validado en sesión 2026-05-13)
|
|
362
|
+
|
|
363
|
+
Cuando la sesión incluye **autorización ampliada para promover documentos**
|
|
364
|
+
a `02-Projects/`, `07-Decisions/` y `04-Resources/`, el flujo óptimo combina
|
|
365
|
+
ambos canales:
|
|
366
|
+
|
|
367
|
+
1. **Inbox** (`00-Inbox/YYYY-MM-DD_HHMM_export-{slug}.md`): usar siempre
|
|
368
|
+
**filesystem directo via Bash heredoc con ruta absoluta** (canal B). Razón:
|
|
369
|
+
el Bash heredoc bypassa cualquier desalineación del vault activo de
|
|
370
|
+
Obsidian — escribe directamente al disco en la ruta exacta.
|
|
371
|
+
|
|
372
|
+
2. **Promociones** (a `02-Projects/`, `07-Decisions/`, `04-Resources/`):
|
|
373
|
+
usar **MCP append_content** (canal A) tras validar Pasos 0c y 0d. Razón:
|
|
374
|
+
las promociones requieren enlaces bidireccionales con `[[...]]` que el
|
|
375
|
+
plugin de Obsidian indexa al detectar la escritura. El filesystem directo
|
|
376
|
+
crea el archivo pero no dispara la reindexación hasta que Obsidian
|
|
377
|
+
detecte el cambio.
|
|
378
|
+
|
|
379
|
+
3. **Aprobación de revisiones** (cambiar `status: pending-review` → `reviewed`
|
|
380
|
+
en frontmatter): si el archivo está en `00-Inbox/`, usar `sed` via Bash;
|
|
381
|
+
si está en `02-Projects/` etc., usar `mcp__obsidian__obsidian_patch_content`
|
|
382
|
+
con `target_type: "frontmatter"`. Validado que `patch_content` puede
|
|
383
|
+
responder 404 en algunos archivos pre-existentes — caer al filesystem si
|
|
384
|
+
falla.
|
|
385
|
+
|
|
386
|
+
Reportar al usuario el desglose final del modo híbrido:
|
|
222
387
|
|
|
223
|
-
```
|
|
224
|
-
|
|
388
|
+
```
|
|
389
|
+
[OK] Inbox creado via filesystem: F:\...\00-Inbox\...md
|
|
390
|
+
[OK] 3 promociones via MCP: 02-Projects, 07-Decisions, 04-Resources
|
|
391
|
+
[OK] Revisión aprobada (reviewed: true) en el Inbox
|
|
225
392
|
```
|
|
226
393
|
|
|
227
394
|
## Paso 5 — Confirmación
|
|
@@ -267,6 +434,12 @@ Próximo paso: al abrir el vault, ejecuta:
|
|
|
267
434
|
- Duplicar con el `COMPACTACION.md` del proyecto (copiar pega tal cual). El export es una **síntesis para vault**, no un espejo.
|
|
268
435
|
- Intentar escribir directamente en `02-Projects/` del vault. Eso es zona ⚠️ en el vault — solo Saul decide si promoverlo.
|
|
269
436
|
- Usar rutas con backslash no escapadas en código generado.
|
|
437
|
+
- **Defaultear a filesystem cuando el MCP de Obsidian está disponible**.
|
|
438
|
+
El bloqueo por `proteccion-rutas.js` no es una falla a esquivar con Bash
|
|
439
|
+
staging — es señal de que el canal correcto es el MCP. Ver Paso 0b.
|
|
440
|
+
- **Recurrir al workaround del filesystem antes de cargar el schema del
|
|
441
|
+
MCP con `ToolSearch`**. Tools deferred ≠ tools ausentes — el MCP server
|
|
442
|
+
está corriendo, solo falta cargar el schema. Cargar antes de defaultear.
|
|
270
443
|
|
|
271
444
|
## Relación con otros comandos SWL
|
|
272
445
|
|
|
@@ -285,7 +458,34 @@ Produce:
|
|
|
285
458
|
|
|
286
459
|
```
|
|
287
460
|
[OK] Export creado: F:\Google Drive\Developer\Obsidian\Vault\SWL\00-Inbox\2026-04-16_2130_export-sigaf.md (487 palabras)
|
|
461
|
+
[OK] Vía: MCP Obsidian (puerto 27124)
|
|
462
|
+
[OK] Enlace canónico: [[DEV - SIGAF]]
|
|
288
463
|
|
|
289
464
|
Próximo paso: al abrir el vault, ejecuta:
|
|
290
465
|
/sync-projects sigaf
|
|
291
466
|
```
|
|
467
|
+
|
|
468
|
+
## Historial de cambios del comando
|
|
469
|
+
|
|
470
|
+
- **v1.4.0** (2026-05-13) — Agregados Pasos 0c y 0d con verificación previa
|
|
471
|
+
del vault activo de Obsidian y de la apiKey del MCP. Origen: sesión
|
|
472
|
+
2026-05-13 con autorización ampliada del usuario para promover docs;
|
|
473
|
+
el MCP escribió `02-Projects/DEV - swl-ses.md` en `Vault\` raíz en lugar
|
|
474
|
+
de `Vault\SWL\` porque Obsidian tenía abierto el vault un nivel arriba
|
|
475
|
+
del esperado. Hallazgo crítico: el plugin Local REST API resuelve paths
|
|
476
|
+
relativos contra el vault que Obsidian tiene abierto en `obsidian.json`
|
|
477
|
+
global, no contra una ruta hardcodeada. Si el vault activo está mal,
|
|
478
|
+
el MCP escribe a la ubicación equivocada sin error (el plugin crea
|
|
479
|
+
carpetas inexistentes). Agregado también Paso 0d para apiKey
|
|
480
|
+
desincronizada (síntoma `40101 Authorization required`). Y nuevo
|
|
481
|
+
modo HÍBRIDO en Paso 4: filesystem directo para Inbox + MCP para
|
|
482
|
+
promociones, validado en la misma sesión.
|
|
483
|
+
|
|
484
|
+
- **v1.3.8** (2026-05-11) — Agregado flujo MCP-first en Paso 4 con detección
|
|
485
|
+
en Paso 0b. Origen: en la sesión v1.3.4 → v1.3.8 el comando defaultó a
|
|
486
|
+
`Write` directo al filesystem que fue bloqueado por `proteccion-rutas.js`.
|
|
487
|
+
El fallback al MCP funcionó pero quedó manual. Esta versión documenta
|
|
488
|
+
el orden de preferencia (MCP primero, filesystem como fallback explícito)
|
|
489
|
+
y agrega la nota de "tools deferred ≠ tools ausentes" en anti-patrones.
|
|
490
|
+
Aplicación de la regla `consultar-vault-primero.md § Workflow forzoso
|
|
491
|
+
para escritura al vault`.
|
|
@@ -135,11 +135,33 @@ Estructura:
|
|
|
135
135
|
- Si el usuario no definió fases claras, propón una división lógica basada en las respuestas del Bloque C
|
|
136
136
|
- Estado inicial de todas las fases: "Pendiente"
|
|
137
137
|
|
|
138
|
-
## Paso 6 —
|
|
138
|
+
## Paso 6 — Generar CLAUDE.md inicial del proyecto
|
|
139
|
+
|
|
140
|
+
Si NO existe `CLAUDE.md` en la raíz del proyecto, generarlo con la estructura
|
|
141
|
+
mínima definida en `/swl:claudemd init-project`. **OBLIGATORIO** incluir como
|
|
142
|
+
primera sección bajo el título:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
## Reglas obligatorias
|
|
146
|
+
|
|
147
|
+
@reglas/usar-sistema-swl.md
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Esta referencia carga la matriz operacional del sistema SWL al inicio de cada
|
|
151
|
+
sesión del proyecto y previene que el agente haga trabajo directo cuando
|
|
152
|
+
existe un componente especializado. Sin ella, el proyecto pierde el contrato
|
|
153
|
+
de uso del sistema SWL.
|
|
154
|
+
|
|
155
|
+
Si ya existe `CLAUDE.md` (verificado en Paso 1), revisar que incluya
|
|
156
|
+
`@reglas/usar-sistema-swl.md` en la sección de reglas obligatorias. Si NO
|
|
157
|
+
lo incluye, agregarlo en este paso preservando el resto del contenido.
|
|
158
|
+
|
|
159
|
+
## Paso 7 — Reporte al usuario
|
|
139
160
|
|
|
140
161
|
Al terminar, reporta:
|
|
141
162
|
|
|
142
|
-
1. Lista de archivos creados con sus rutas absolutas
|
|
163
|
+
1. Lista de archivos creados con sus rutas absolutas (incluyendo CLAUDE.md
|
|
164
|
+
si se generó o se modificó)
|
|
143
165
|
2. Resumen de la investigación del agente (cuando esté disponible)
|
|
144
166
|
3. Próximo paso recomendado: "Para comenzar a trabajar en la Fase 1, usa `/swl:discutir-fase 1`"
|
|
145
167
|
4. Si encontraste riesgos o ambigüedades durante la entrevista, listarlos aquí como "Puntos a resolver"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base Adapter — Clase base abstracta para adaptadores de plataforma.
|
|
5
|
+
*
|
|
6
|
+
* Cada plataforma (Telegram, Discord, Webhook) extiende esta clase
|
|
7
|
+
* e implementa los métodos abstractos. El gateway/index.js orquesta
|
|
8
|
+
* múltiples adaptadores de forma uniforme.
|
|
9
|
+
*
|
|
10
|
+
* Patrón adoptado de Hermes Agent (gateway/platforms/base.py).
|
|
11
|
+
*
|
|
12
|
+
* @module gateway/adapters/base
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @abstract
|
|
17
|
+
*/
|
|
18
|
+
class BaseAdapter {
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} name - Nombre de la plataforma (ej: 'telegram').
|
|
21
|
+
* @param {object} config - Configuración específica de la plataforma.
|
|
22
|
+
*/
|
|
23
|
+
constructor(name, config) {
|
|
24
|
+
if (new.target === BaseAdapter) {
|
|
25
|
+
throw new Error('BaseAdapter es abstracto — usar un adaptador concreto.');
|
|
26
|
+
}
|
|
27
|
+
this.name = name;
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.running = false;
|
|
30
|
+
this._messageHandler = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Inicia la conexión con la plataforma.
|
|
35
|
+
* @abstract
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
async start() {
|
|
39
|
+
throw new Error(`${this.name}: start() no implementado`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detiene la conexión con la plataforma.
|
|
44
|
+
* @abstract
|
|
45
|
+
* @returns {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
async stop() {
|
|
48
|
+
this.running = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Envía un mensaje formateado a la plataforma.
|
|
53
|
+
* @abstract
|
|
54
|
+
* @param {object} message
|
|
55
|
+
* @param {string} message.text - Texto del mensaje (Markdown).
|
|
56
|
+
* @param {string} [message.chatId] - Destino específico.
|
|
57
|
+
* @param {string} [message.type] - Tipo: 'notification', 'alert', 'response'.
|
|
58
|
+
* @returns {Promise<boolean>} true si se envió correctamente.
|
|
59
|
+
*/
|
|
60
|
+
async send(message) {
|
|
61
|
+
throw new Error(`${this.name}: send() no implementado`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Registra un handler para mensajes entrantes de la plataforma.
|
|
66
|
+
* @param {function} handler - Callback (message) => void.
|
|
67
|
+
*/
|
|
68
|
+
onMessage(handler) {
|
|
69
|
+
this._messageHandler = handler;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Emite un mensaje entrante al handler registrado.
|
|
74
|
+
* @protected
|
|
75
|
+
* @param {object} message
|
|
76
|
+
*/
|
|
77
|
+
_emitMessage(message) {
|
|
78
|
+
if (typeof this._messageHandler === 'function') {
|
|
79
|
+
this._messageHandler({
|
|
80
|
+
platform: this.name,
|
|
81
|
+
...message,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Formatea un mensaje SWL para la plataforma específica.
|
|
88
|
+
* Override en cada adaptador para formato nativo (embeds, markdown, etc.).
|
|
89
|
+
*
|
|
90
|
+
* @param {object} swlMessage - Mensaje del sistema SWL.
|
|
91
|
+
* @returns {string} Texto formateado para la plataforma.
|
|
92
|
+
*/
|
|
93
|
+
formatMessage(swlMessage) {
|
|
94
|
+
const { jobName, status, output, type } = swlMessage.payload || swlMessage;
|
|
95
|
+
const lines = [];
|
|
96
|
+
|
|
97
|
+
if (type === 'gateway_notification' || jobName) {
|
|
98
|
+
lines.push(`*${jobName || 'SWL Notificación'}*`);
|
|
99
|
+
if (status) lines.push(`Estado: ${status}`);
|
|
100
|
+
if (output) lines.push(`\`\`\`\n${output.substring(0, 500)}\n\`\`\``);
|
|
101
|
+
} else {
|
|
102
|
+
lines.push(swlMessage.text || JSON.stringify(swlMessage));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = BaseAdapter;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Discord Adapter — Adaptador de plataforma para Discord.
|
|
5
|
+
*
|
|
6
|
+
* Usa discord.js para conectar un bot. Soporta:
|
|
7
|
+
* - Recepción de mensajes en canal dedicado
|
|
8
|
+
* - Envío con embeds para notificaciones estructuradas
|
|
9
|
+
* - Comandos slash nativos
|
|
10
|
+
* - Filtro por guildId y channelId
|
|
11
|
+
*
|
|
12
|
+
* Dependencia: discord.js (npm install discord.js)
|
|
13
|
+
* Si no está instalada, el adaptador se desactiva silenciosamente.
|
|
14
|
+
*
|
|
15
|
+
* Inspirado en Hermes Agent (gateway/platforms/discord.py).
|
|
16
|
+
*
|
|
17
|
+
* @module gateway/adapters/discord
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const BaseAdapter = require('./base');
|
|
21
|
+
|
|
22
|
+
/** Límite de caracteres por mensaje de Discord. */
|
|
23
|
+
const MAX_MSG_LENGTH = 2000;
|
|
24
|
+
|
|
25
|
+
class DiscordAdapter extends BaseAdapter {
|
|
26
|
+
constructor(config) {
|
|
27
|
+
super('discord', config);
|
|
28
|
+
this._client = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start() {
|
|
32
|
+
let Discord;
|
|
33
|
+
try {
|
|
34
|
+
Discord = require('discord.js');
|
|
35
|
+
} catch (_) {
|
|
36
|
+
console.log('[discord] discord.js no instalado. Ejecutar: npm install discord.js');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const token = this.config.token || process.env.DISCORD_BOT_TOKEN;
|
|
41
|
+
if (!token) {
|
|
42
|
+
console.log('[discord] Token no configurado. Establecer DISCORD_BOT_TOKEN.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { Client, GatewayIntentBits } = Discord;
|
|
47
|
+
|
|
48
|
+
this._client = new Client({
|
|
49
|
+
intents: [
|
|
50
|
+
GatewayIntentBits.Guilds,
|
|
51
|
+
GatewayIntentBits.GuildMessages,
|
|
52
|
+
GatewayIntentBits.MessageContent,
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this._client.on('ready', () => {
|
|
57
|
+
this.running = true;
|
|
58
|
+
console.log(`[discord] Bot conectado como ${this._client.user.tag}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this._client.on('messageCreate', (msg) => {
|
|
62
|
+
// Ignorar mensajes del bot
|
|
63
|
+
if (msg.author.bot) return;
|
|
64
|
+
|
|
65
|
+
// Filtrar por guild y canal si están configurados
|
|
66
|
+
if (this.config.guildId && msg.guildId !== this.config.guildId) return;
|
|
67
|
+
if (this.config.channelId && msg.channelId !== this.config.channelId) return;
|
|
68
|
+
|
|
69
|
+
const text = msg.content || '';
|
|
70
|
+
const parts = text.split(/\s+/);
|
|
71
|
+
const command = parts[0].startsWith('/') ? parts[0] : null;
|
|
72
|
+
const args = command ? parts.slice(1).join(' ') : '';
|
|
73
|
+
|
|
74
|
+
this._emitMessage({
|
|
75
|
+
chatId: msg.channelId,
|
|
76
|
+
userId: msg.author.id,
|
|
77
|
+
userName: msg.author.username,
|
|
78
|
+
text: text,
|
|
79
|
+
command: command,
|
|
80
|
+
args: args,
|
|
81
|
+
guildId: msg.guildId,
|
|
82
|
+
raw: msg,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this._client.on('error', (err) => {
|
|
87
|
+
console.error(`[discord] Error: ${err.message}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await this._client.login(token);
|
|
91
|
+
console.log('[discord] Adaptador iniciado.');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async stop() {
|
|
95
|
+
if (this._client) {
|
|
96
|
+
try { await this._client.destroy(); } catch (_) {}
|
|
97
|
+
this._client = null;
|
|
98
|
+
}
|
|
99
|
+
this.running = false;
|
|
100
|
+
console.log('[discord] Adaptador detenido.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async send(message) {
|
|
104
|
+
if (!this._client) return false;
|
|
105
|
+
|
|
106
|
+
const channelId = message.chatId || this.config.channelId;
|
|
107
|
+
if (!channelId) return false;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const channel = await this._client.channels.fetch(channelId);
|
|
111
|
+
if (!channel || !channel.isTextBased()) return false;
|
|
112
|
+
|
|
113
|
+
const embed = this._buildEmbed(message);
|
|
114
|
+
if (embed) {
|
|
115
|
+
await channel.send({ embeds: [embed] });
|
|
116
|
+
} else {
|
|
117
|
+
const text = this.formatMessage(message);
|
|
118
|
+
// Split si excede límite
|
|
119
|
+
if (text.length > MAX_MSG_LENGTH) {
|
|
120
|
+
const chunks = [];
|
|
121
|
+
for (let i = 0; i < text.length; i += MAX_MSG_LENGTH) {
|
|
122
|
+
chunks.push(text.substring(i, i + MAX_MSG_LENGTH));
|
|
123
|
+
}
|
|
124
|
+
for (const chunk of chunks) await channel.send(chunk);
|
|
125
|
+
} else {
|
|
126
|
+
await channel.send(text);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error(`[discord] Error enviando: ${err.message}`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Construye un embed de Discord para notificaciones estructuradas.
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_buildEmbed(message) {
|
|
141
|
+
const payload = message.payload || message;
|
|
142
|
+
if (!payload.jobName && !payload.title) return null;
|
|
143
|
+
|
|
144
|
+
let Discord;
|
|
145
|
+
try { Discord = require('discord.js'); } catch (_) { return null; }
|
|
146
|
+
|
|
147
|
+
const { EmbedBuilder } = Discord;
|
|
148
|
+
const isError = payload.status === 'error';
|
|
149
|
+
|
|
150
|
+
const embed = new EmbedBuilder()
|
|
151
|
+
.setTitle(payload.jobName || payload.title || 'SWL Notificación')
|
|
152
|
+
.setColor(isError ? 0xFF0000 : 0x00CC66)
|
|
153
|
+
.setTimestamp();
|
|
154
|
+
|
|
155
|
+
if (payload.status) {
|
|
156
|
+
embed.addFields({ name: 'Estado', value: `\`${payload.status}\``, inline: true });
|
|
157
|
+
}
|
|
158
|
+
if (payload.output) {
|
|
159
|
+
const output = payload.output.substring(0, 1000);
|
|
160
|
+
embed.addFields({ name: 'Output', value: `\`\`\`\n${output}\n\`\`\`` });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return embed;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = DiscordAdapter;
|