@saulwade/swl-ses 1.5.1 → 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.
- package/CLAUDE.md +225 -209
- package/README.md +561 -561
- package/agentes/arquitecto-swl.md +33 -1
- package/agentes/nemesis-auditor-swl.md +59 -19
- package/bin/swl-mcp-server.js +214 -214
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +230 -56
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- 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/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- 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/SKILL.md +207 -4
- 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/nemesis-evaluacion-json/SKILL.md +266 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
- package/habilidades/node-experto/SKILL.md +94 -4
- 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/protocolo-revision-swl/SKILL.md +350 -276
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/tdd-workflow/SKILL.md +121 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/check-update.js +31 -3
- package/hooks/claudemd-bloat-detector.js +161 -161
- 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-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- 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/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 +1324 -1321
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +2 -2
- 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 +353 -351
- 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/registro-componentes-nuevos.md +192 -0
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/actualizar.js +110 -1
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -489
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +27 -0
- 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/expandir-targets.js +71 -71
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/mcp_config.py +127 -0
- 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/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/codex.js +375 -375
- package/scripts/lib/transformadores/cursor.js +359 -359
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-orchestrator.py +8 -18
- package/scripts/mcp-pool-manager.py +12 -23
- package/scripts/mcp-server/README.md +170 -170
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/mcp-server/telemetry.js +78 -78
- 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-userland-vacio.js +110 -110
|
@@ -12,7 +12,12 @@ model: claude-opus-4-7
|
|
|
12
12
|
modeloAlterno: claude-sonnet-4-6
|
|
13
13
|
ventanaContexto: 200k
|
|
14
14
|
color: blue
|
|
15
|
-
version: 1.0.
|
|
15
|
+
version: 1.0.1
|
|
16
|
+
evolved: true
|
|
17
|
+
evolved-from: "1.0.0"
|
|
18
|
+
evolved-at: "2026-05-16"
|
|
19
|
+
evolved-by: "aprender"
|
|
20
|
+
evolved-note: "v1.0.1: documentar contrato 'arquitecto NO tiene Write tool — produce contenido textual, el padre lo materializa'. Origen: sesión 2026-05-16 ADR-0021."
|
|
16
21
|
nivelRiesgo: MEDIO
|
|
17
22
|
skillsInvocables: [api-rest-diseno, microservicios, event-driven, cloud-aws, postgresql-experto, extraccion-documentos, diagrama-arquitectura]
|
|
18
23
|
skillsRestringidos: []
|
|
@@ -111,6 +116,33 @@ Luego elige una y justifica la elección en función de las restricciones del pr
|
|
|
111
116
|
|
|
112
117
|
### Fase 4 — Crear el ADR
|
|
113
118
|
|
|
119
|
+
**Nota operativa importante sobre el contrato de este agente**: el agente
|
|
120
|
+
`arquitecto-swl` tiene `tools: [Read, Grep, Glob, WebSearch]` — **NO incluye
|
|
121
|
+
`Write` ni `Edit`**. Esto es decisión deliberada de privilegio mínimo: el
|
|
122
|
+
arquitecto **diseña** decisiones, no las **persiste** en disco.
|
|
123
|
+
|
|
124
|
+
Cuando el agente padre (orquestador, comando, usuario directo) solicita
|
|
125
|
+
"redacta el ADR-NNNN en .planning/adrs/NNNN-titulo.md", el agente:
|
|
126
|
+
|
|
127
|
+
1. Produce el **contenido completo** del ADR en su respuesta final (markdown
|
|
128
|
+
listo para copiar a disco).
|
|
129
|
+
2. Retorna la **ruta destino sugerida** (`.planning/adrs/NNNN-titulo.md`).
|
|
130
|
+
3. **NO escribe el archivo**. Eso lo hace el agente padre con `Write` o el
|
|
131
|
+
usuario con copy-paste.
|
|
132
|
+
|
|
133
|
+
Esta separación es coherente con la regla `seguridad-agentes.md § Privilegio
|
|
134
|
+
mínimo`: el arquitecto no necesita escritura para producir su valor. Si el
|
|
135
|
+
diseño se persiste, esa responsabilidad recae en el orquestador o
|
|
136
|
+
implementador que sí tiene Write.
|
|
137
|
+
|
|
138
|
+
Si el agente padre asume erróneamente que arquitecto-swl materializa el
|
|
139
|
+
ADR, el ADR se "produce" textualmente pero queda solo en el output del agent
|
|
140
|
+
call — invisible al filesystem. **Patrón observado** en sesión 2026-05-16
|
|
141
|
+
(swl-ses): el agente padre recibió el contenido del ADR-0021 y lo escribió
|
|
142
|
+
manualmente con `Write` siguiendo la indicación clara del arquitecto.
|
|
143
|
+
|
|
144
|
+
### Formato del ADR
|
|
145
|
+
|
|
114
146
|
Cada decisión arquitectónica significativa se documenta como ADR con este formato:
|
|
115
147
|
|
|
116
148
|
```
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
name: nemesis-auditor-swl
|
|
3
3
|
description: >
|
|
4
4
|
Auditor de doble paso iterativo (Feynman + State Inconsistency) que encuentra
|
|
5
|
-
bugs en la intersección que ningún paso individual detecta.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
bugs en la intersección que ningún paso individual detecta. Opera como
|
|
6
|
+
evaluator en el patrón evaluator-optimizer del comando /swl:nemesis cuando
|
|
7
|
+
se invoca con --remediar (ADR-0021). Emite evaluacion.json estructurado para
|
|
8
|
+
alimentar el loop de remediación. Language-agnostic (Python, TypeScript, Go,
|
|
9
|
+
Rust, Java, C#). Invocar tras revisor-codigo-swl y revisor-seguridad-swl
|
|
10
|
+
cuando la fase toca lógica de negocio compleja con estado acoplado.
|
|
9
11
|
tools: [Read, Grep, Glob, Bash, Write]
|
|
10
12
|
model: claude-sonnet-4-6
|
|
11
|
-
version: 1.
|
|
13
|
+
version: 1.1.0
|
|
12
14
|
nivelRiesgo: MEDIO
|
|
13
|
-
skillsInvocables: [feynman-auditor-swl, state-inconsistency-auditor-swl]
|
|
15
|
+
skillsInvocables: [feynman-auditor-swl, state-inconsistency-auditor-swl, memoria-busqueda, checklist-seguridad, nemesis-evaluacion-json]
|
|
14
16
|
permisosRed: false
|
|
15
17
|
permisosEscritura: true
|
|
16
18
|
permisosComandos: true
|
|
@@ -18,9 +20,10 @@ maxTurnos: 20
|
|
|
18
20
|
evolvable: true
|
|
19
21
|
exclusiones:
|
|
20
22
|
- "No invocar para pattern-matching de CVEs conocidos — usar revisor-seguridad-swl."
|
|
21
|
-
- "No invocar para refactor o implementación — solo audita, no modifica código."
|
|
23
|
+
- "No invocar para refactor o implementación — solo audita, no modifica código. Esta exclusión es invariante por diseño (ADR-0021): el evaluator NUNCA aplica fixes, eso es trabajo del generator (orquestador-swl) invocado por el comando."
|
|
22
24
|
- "No invocar como sustituto de tests — Nemesis complementa, no reemplaza testing."
|
|
23
25
|
- "No invocar para análisis de blockchain — el agente fue generalizado a Python/TS/Go/Rust/Java/C#."
|
|
26
|
+
- "No invocar para scopes > 1500 LOC o > 5 archivos en módulos distintos sin pasar primero por el comando /swl:nemesis (que cargará Skill('nemesis-redistribuir') automáticamente). Invocación directa con scope grande satura el context window — ver ADR-0021."
|
|
24
27
|
---
|
|
25
28
|
|
|
26
29
|
# Cuándo NO invocarme
|
|
@@ -29,6 +32,7 @@ exclusiones:
|
|
|
29
32
|
- Para refactor, limpiar deuda técnica o implementar funcionalidad nueva — Nemesis audita, no toca código.
|
|
30
33
|
- Para reemplazar un suite de tests — la cobertura de Nemesis es profundidad, no amplitud.
|
|
31
34
|
- Para código sin estado acoplado (scripts de utilería, transformaciones funcionales puras).
|
|
35
|
+
- Para scope grande (>1500 LOC o >5 archivos en módulos distintos) sin pasar por `/swl:nemesis` — el comando aplicará redistribución automática. Invocación directa con scope grande satura context.
|
|
32
36
|
|
|
33
37
|
---
|
|
34
38
|
|
|
@@ -54,14 +58,37 @@ PASADA 3+: Pasadas alternantes dirigidas hasta convergencia
|
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
57
|
-
## Proceso — Fase 0: Contexto
|
|
61
|
+
## Proceso — Fase 0: Contexto y consulta de memoria
|
|
58
62
|
|
|
59
63
|
Antes de la Pasada 1, establecer:
|
|
60
64
|
|
|
61
65
|
1. ¿Qué archivos o módulos están en scope? (sin scope → el auditor infiere desde el directorio de trabajo)
|
|
62
|
-
2. ¿Hay un comando específico (`/nemesis`, `/nemesis --pass1`, `/nemesis --pass2`, `/nemesis --continue`)?
|
|
63
|
-
3. ¿Hay un target de un solo módulo (`/nemesis --
|
|
64
|
-
4. ¿Existen hallazgos previos en `.audit/findings/`?
|
|
66
|
+
2. ¿Hay un comando específico (`/nemesis`, `/nemesis --pass1`, `/nemesis --pass2`, `/nemesis --continue`, `/nemesis --remediar`)?
|
|
67
|
+
3. ¿Hay un target de un solo módulo (`/nemesis --modulo <ruta>`)?
|
|
68
|
+
4. ¿Existen hallazgos previos en `.planning/audit/findings/`?
|
|
69
|
+
5. **Consulta de memoria histórica**: cargar `Skill("memoria-busqueda")` y buscar en `APRENDIZAJES.md` + `.planning/sessions/` si los archivos del scope ya tienen hallazgos cerrados o decisiones de ADR. Marcar esos sitios como "ya validados" para no re-auditarlos a menos que el caller fuerce `--reset-memoria`.
|
|
70
|
+
6. **Carga de catálogo de seguridad**: si el scope toca paths típicos de seguridad (auth, rbac, rls, crypto, session, token, password), cargar `Skill("checklist-seguridad")` para tener disponible la taxonomía OWASP+A11 al clasificar hallazgos.
|
|
71
|
+
|
|
72
|
+
### Iteración del loop evaluator-optimizer
|
|
73
|
+
|
|
74
|
+
Si la invocación viene desde `/swl:nemesis --remediar`, el agente recibe:
|
|
75
|
+
|
|
76
|
+
- `iter`: entero `∈ {1, 2, 3}` (one-indexed, coherente con los paths `iter-1/`, `iter-2/`, `iter-3/`).
|
|
77
|
+
- `sub_id` (opcional, solo en modo redistribuido): string identificador del sub-scope dentro del plan de redistribución (ej: `"sub-1-auth-backend"`). Determina el subdirectorio bajo `iter-N/` donde el agente DEBE escribir su output. Si `sub_id` viene `null` o `undefined`, el agente escribe directamente en `iter-N/` (modo monolítico).
|
|
78
|
+
- En iteraciones >= 2, también recibe la ruta del `evaluacion.json` previo.
|
|
79
|
+
|
|
80
|
+
**Path de output según el modo**:
|
|
81
|
+
|
|
82
|
+
| Modo | sub_id | Output |
|
|
83
|
+
|------|--------|--------|
|
|
84
|
+
| Monolítico | `null` / `undefined` | `.planning/audit/findings/iter-N/feynman-passK.md`, `state-passK.md`, `nemesis-verified.md`, `evaluacion.json` |
|
|
85
|
+
| Redistribuido | string (ej: `sub-1-auth-backend`) | `.planning/audit/findings/iter-N/<sub_id>/feynman-passK.md`, `state-passK.md`, `nemesis-verified.md`, `evaluacion.json` |
|
|
86
|
+
|
|
87
|
+
**Lectura del iter previo**: si `iter > 1`, leer `.planning/audit/findings/iter-<N-1>/evaluacion.json` para detectar hallazgos persistentes, cerrados, nuevos y regresiones. En modo redistribuido, también revisar el `evaluacion.json` consolidado de la iteración previa (el comando lo escribe directamente en `iter-N-1/evaluacion.json`, no bajo un sub_id), porque ese reporte tiene la vista cross-sub-scope.
|
|
88
|
+
|
|
89
|
+
En iter=1 no hay iteración previa — `comparacion_iteracion_previa.hallazgos_cerrados=[]` y todos los hallazgos son `hallazgos_nuevos` (alineado con el schema `nemesis-evaluacion-json`).
|
|
90
|
+
|
|
91
|
+
Esta información alimenta el campo `comparacion_iteracion_previa` del JSON de salida.
|
|
65
92
|
|
|
66
93
|
---
|
|
67
94
|
|
|
@@ -69,7 +96,7 @@ Antes de la Pasada 1, establecer:
|
|
|
69
96
|
|
|
70
97
|
Cargar `Skill("feynman-auditor-swl")` y ejecutar la auditoría completa.
|
|
71
98
|
|
|
72
|
-
- Salida cruda: `.audit/findings/feynman-pass1.md`
|
|
99
|
+
- Salida cruda: `.planning/audit/findings/iter-N/feynman-pass1.md`
|
|
73
100
|
- Registrar sospechosos de alta confianza para alimentar la Pasada 2.
|
|
74
101
|
|
|
75
102
|
---
|
|
@@ -102,9 +129,12 @@ Límite duro: 6 pasadas totales (3 Feynman + 3 State).
|
|
|
102
129
|
|
|
103
130
|
---
|
|
104
131
|
|
|
105
|
-
## Proceso — Fase 7: Reporte
|
|
132
|
+
## Proceso — Fase 7: Reporte final consolidado
|
|
133
|
+
|
|
134
|
+
Consolidar todos los hallazgos verificados en **dos** artefactos:
|
|
106
135
|
|
|
107
|
-
|
|
136
|
+
1. **Reporte legible** en `.planning/audit/findings/iter-N/nemesis-verified.md` (formato actual para consumo humano).
|
|
137
|
+
2. **Veredicto estructurado** en `.planning/audit/findings/iter-N/evaluacion.json` siguiendo el schema `nemesis-evaluacion-json` (v1.0.0). Este JSON es el que consume el comando `/swl:nemesis` para decidir convergencia, activar remediación o escalar a Recovery Catalog. Cargar `Skill("nemesis-evaluacion-json")` antes de emitirlo para validar contra el schema.
|
|
108
138
|
|
|
109
139
|
Cada hallazgo etiquetado con su ruta de descubrimiento:
|
|
110
140
|
|
|
@@ -121,17 +151,27 @@ Cada hallazgo etiquetado con su ruta de descubrimiento:
|
|
|
121
151
|
| MEDIO | Contabilidad degradada, griefing, errores en casos de borde frecuentes |
|
|
122
152
|
| BAJO | Problemas cosméticos, inaccuracy de eventos/logs, errores de casos de borde raros |
|
|
123
153
|
|
|
154
|
+
### Veredicto `status` (alimenta el loop del comando)
|
|
155
|
+
|
|
156
|
+
- **PASS**: 0 críticos + 0 altos. Pueden quedar medios/bajos/informativos.
|
|
157
|
+
- **NEEDS_IMPROVEMENT**: hay críticos o altos pero todos tienen `accion_sugerida` concreta y `agente_recomendado` válido. El comando puede remediar automáticamente.
|
|
158
|
+
- **FAIL**: hay hallazgos que requieren decisión humana (cambio arquitectural, decisión de producto, datos inválidos) o veto items. El comando escala a Recovery Catalog inmediatamente.
|
|
159
|
+
|
|
160
|
+
Si el agente detecta veto items según `reglas/gobernanza.md § Veto items`, status DEBE ser FAIL y `puede_remediar_automaticamente=false`.
|
|
161
|
+
|
|
124
162
|
---
|
|
125
163
|
|
|
126
164
|
## Comandos
|
|
127
165
|
|
|
128
166
|
| Comando | Acción |
|
|
129
167
|
|---------|--------|
|
|
130
|
-
| `/nemesis` | Auditoría completa iterativa |
|
|
131
|
-
| `/nemesis --
|
|
132
|
-
| `/nemesis --
|
|
133
|
-
| `/nemesis --
|
|
134
|
-
| `/nemesis --
|
|
168
|
+
| `/swl:nemesis` | Auditoría completa iterativa (solo audita, sin remediar) |
|
|
169
|
+
| `/swl:nemesis --remediar` | Loop evaluator-optimizer completo: audita → invoca orquestador-swl con hallazgos → re-audita. Max 3 iteraciones. Convergencia cuando status=PASS. |
|
|
170
|
+
| `/swl:nemesis --pass1` | Solo Pasada 1 — Feynman completo |
|
|
171
|
+
| `/swl:nemesis --pass2` | Solo Pasada 2 — State sobre output existente de Pasada 1 |
|
|
172
|
+
| `/swl:nemesis --continue` | Continuar desde la última pasada |
|
|
173
|
+
| `/swl:nemesis --modulo <ruta>` | Auditoría sobre un módulo específico |
|
|
174
|
+
| `/swl:nemesis --redistribuir` | Forzar modo redistribuido (comando carga `Skill("nemesis-redistribuir")` aunque scope sea menor al umbral) |
|
|
135
175
|
|
|
136
176
|
---
|
|
137
177
|
|
package/bin/swl-mcp-server.js
CHANGED
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* swl-mcp-server — Servidor MCP de solo lectura para exponer la memoria
|
|
6
|
-
* de swl-ses a clientes MCP externos (Cursor, Codex CLI, Gemini CLI, etc.).
|
|
7
|
-
*
|
|
8
|
-
* v1.0.0 (ADR-0019 Sub-fase 3) — promovido de stub experimental a versión
|
|
9
|
-
* estable con auth opt-in, caching mtime-based, telemetría JSONL y schema
|
|
10
|
-
* versioning. Mantiene compatibilidad total con clientes existentes que
|
|
11
|
-
* conectan sin auth (si `SWL_MCP_API_KEY` no está set, comportamiento idéntico
|
|
12
|
-
* al stub v0.1.x).
|
|
13
|
-
*
|
|
14
|
-
* Modo de transporte: stdio (JSON-RPC sobre stdin/stdout).
|
|
15
|
-
*
|
|
16
|
-
* Uso (cliente MCP):
|
|
17
|
-
* - Configurar el cliente para ejecutar `node /path/to/swl-ses/bin/swl-mcp-server.js`
|
|
18
|
-
* con stdio.
|
|
19
|
-
* - El cwd del proceso determina baseDir (override con `SWL_MCP_BASE_DIR`).
|
|
20
|
-
*
|
|
21
|
-
* Variables opt-in (env del server):
|
|
22
|
-
* - SWL_MCP_API_KEY — Si set, requiere params._auth en cada tools/call.
|
|
23
|
-
* - SWL_MCP_CACHE_TTL_MS — TTL del cache mtime-based (default 60000).
|
|
24
|
-
* - SWL_MCP_METRICS — Si "1" o "true", persiste metrics en
|
|
25
|
-
* .planning/evolucion/mcp-metrics.jsonl.
|
|
26
|
-
*
|
|
27
|
-
* Schema versioning:
|
|
28
|
-
* - Cada handler declara `schemaVersion` en su definición.
|
|
29
|
-
* - El cliente puede inspeccionarlo via `tools/list` (campo `_schemaVersion`).
|
|
30
|
-
*
|
|
31
|
-
* Protocolo MCP soportado (subset):
|
|
32
|
-
* - initialize / initialized
|
|
33
|
-
* - tools/list
|
|
34
|
-
* - tools/call
|
|
35
|
-
* - ping
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
const path = require('path');
|
|
39
|
-
|
|
40
|
-
const { HANDLERS } = require('../scripts/mcp-server/handlers');
|
|
41
|
-
const { construirValidador } = require('../scripts/mcp-server/auth');
|
|
42
|
-
const { construirTelemetria } = require('../scripts/mcp-server/telemetry');
|
|
43
|
-
|
|
44
|
-
const SERVER_NAME = 'swl-mcp-server';
|
|
45
|
-
const SERVER_VERSION = '1.0.0';
|
|
46
|
-
const PROTOCOL_VERSION = '2024-11-05';
|
|
47
|
-
|
|
48
|
-
const baseDir = process.env.SWL_MCP_BASE_DIR || process.cwd();
|
|
49
|
-
const authValidator = construirValidador();
|
|
50
|
-
const telemetry = construirTelemetria({ baseDir });
|
|
51
|
-
|
|
52
|
-
// ── logging ───────────────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
// Stderr para evitar contaminar stdout (que es JSON-RPC).
|
|
55
|
-
function log(level, msg, data) {
|
|
56
|
-
const linea = JSON.stringify({
|
|
57
|
-
timestamp: new Date().toISOString(),
|
|
58
|
-
level,
|
|
59
|
-
msg,
|
|
60
|
-
...(data ? { data } : {}),
|
|
61
|
-
});
|
|
62
|
-
process.stderr.write(linea + '\n');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ── JSON-RPC helpers ──────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
function respuesta(id, result) {
|
|
68
|
-
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function errorResp(id, code, message) {
|
|
72
|
-
return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── routing ───────────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
function manejarInitialize(request) {
|
|
78
|
-
return respuesta(request.id, {
|
|
79
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
80
|
-
capabilities: {
|
|
81
|
-
tools: { listChanged: false },
|
|
82
|
-
},
|
|
83
|
-
serverInfo: {
|
|
84
|
-
name: SERVER_NAME,
|
|
85
|
-
version: SERVER_VERSION,
|
|
86
|
-
authRequired: authValidator.requerida,
|
|
87
|
-
telemetryEnabled: telemetry.habilitada,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function manejarToolsList(request) {
|
|
93
|
-
const tools = Object.entries(HANDLERS).map(([name, def]) => ({
|
|
94
|
-
name,
|
|
95
|
-
description: def.description,
|
|
96
|
-
inputSchema: def.inputSchema,
|
|
97
|
-
_schemaVersion: def.schemaVersion || '1.0.0',
|
|
98
|
-
}));
|
|
99
|
-
return respuesta(request.id, { tools });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function manejarToolsCall(request) {
|
|
103
|
-
const { name, arguments: args } = request.params || {};
|
|
104
|
-
const def = HANDLERS[name];
|
|
105
|
-
if (!def) {
|
|
106
|
-
return errorResp(request.id, -32601, `Tool no encontrado: ${name}`);
|
|
107
|
-
}
|
|
108
|
-
const inicio = Date.now();
|
|
109
|
-
try {
|
|
110
|
-
const result = def.handler(baseDir, args || {});
|
|
111
|
-
const duracionMs = Date.now() - inicio;
|
|
112
|
-
telemetry.registrar({ tool: name, durationMs: duracionMs, ok: true, baseDir });
|
|
113
|
-
return respuesta(request.id, {
|
|
114
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
115
|
-
});
|
|
116
|
-
} catch (err) {
|
|
117
|
-
const duracionMs = Date.now() - inicio;
|
|
118
|
-
log('error', `Excepción en handler ${name}`, { error: err.message });
|
|
119
|
-
telemetry.registrar({ tool: name, durationMs: duracionMs, ok: false, error: err.message, baseDir });
|
|
120
|
-
return errorResp(request.id, -32603, `Error interno: ${err.message}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Punto de entrada del routing — público para tests.
|
|
126
|
-
*
|
|
127
|
-
* @param {object} request - Mensaje JSON-RPC parseado.
|
|
128
|
-
* @returns {string|null} - String JSON-RPC respuesta o null (notification).
|
|
129
|
-
*/
|
|
130
|
-
function rutear(request) {
|
|
131
|
-
// Auth gate: si SWL_MCP_API_KEY está set, validar antes de routing.
|
|
132
|
-
const auth = authValidator.validar(request);
|
|
133
|
-
if (!auth.ok) {
|
|
134
|
-
return errorResp(request.id, auth.code, auth.message);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
switch (request.method) {
|
|
138
|
-
case 'initialize':
|
|
139
|
-
return manejarInitialize(request);
|
|
140
|
-
case 'initialized':
|
|
141
|
-
case 'notifications/initialized':
|
|
142
|
-
return null; // notification — sin respuesta
|
|
143
|
-
case 'tools/list':
|
|
144
|
-
return manejarToolsList(request);
|
|
145
|
-
case 'tools/call':
|
|
146
|
-
return manejarToolsCall(request);
|
|
147
|
-
case 'ping':
|
|
148
|
-
return respuesta(request.id, {});
|
|
149
|
-
default:
|
|
150
|
-
return errorResp(request.id, -32601, `Método no soportado: ${request.method}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── loop principal ────────────────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
function arrancar() {
|
|
157
|
-
log('info', `${SERVER_NAME} v${SERVER_VERSION} iniciando`, {
|
|
158
|
-
baseDir,
|
|
159
|
-
authRequired: authValidator.requerida,
|
|
160
|
-
telemetryEnabled: telemetry.habilitada,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
let buffer = '';
|
|
164
|
-
|
|
165
|
-
process.stdin.setEncoding('utf8');
|
|
166
|
-
process.stdin.on('data', (chunk) => {
|
|
167
|
-
buffer += chunk;
|
|
168
|
-
|
|
169
|
-
// Cada mensaje JSON-RPC termina con \n
|
|
170
|
-
let nlIndex;
|
|
171
|
-
while ((nlIndex = buffer.indexOf('\n')) >= 0) {
|
|
172
|
-
const linea = buffer.slice(0, nlIndex).trim();
|
|
173
|
-
buffer = buffer.slice(nlIndex + 1);
|
|
174
|
-
|
|
175
|
-
if (!linea) continue;
|
|
176
|
-
|
|
177
|
-
let request;
|
|
178
|
-
try {
|
|
179
|
-
request = JSON.parse(linea);
|
|
180
|
-
} catch (err) {
|
|
181
|
-
log('error', 'JSON inválido recibido', { error: err.message, linea: linea.slice(0, 100) });
|
|
182
|
-
process.stdout.write(errorResp(null, -32700, 'Parse error') + '\n');
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const respuestaStr = rutear(request);
|
|
187
|
-
if (respuestaStr) {
|
|
188
|
-
process.stdout.write(respuestaStr + '\n');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
process.stdin.on('end', () => {
|
|
194
|
-
log('info', 'stdin cerrado, server termina');
|
|
195
|
-
process.exit(0);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Manejo de errores no capturados — nunca crashear silenciosamente
|
|
199
|
-
process.on('uncaughtException', (err) => {
|
|
200
|
-
log('error', 'uncaughtException', { error: err.message, stack: err.stack });
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (require.main === module) {
|
|
205
|
-
arrancar();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
module.exports = {
|
|
209
|
-
rutear,
|
|
210
|
-
arrancar,
|
|
211
|
-
SERVER_NAME,
|
|
212
|
-
SERVER_VERSION,
|
|
213
|
-
PROTOCOL_VERSION,
|
|
214
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swl-mcp-server — Servidor MCP de solo lectura para exponer la memoria
|
|
6
|
+
* de swl-ses a clientes MCP externos (Cursor, Codex CLI, Gemini CLI, etc.).
|
|
7
|
+
*
|
|
8
|
+
* v1.0.0 (ADR-0019 Sub-fase 3) — promovido de stub experimental a versión
|
|
9
|
+
* estable con auth opt-in, caching mtime-based, telemetría JSONL y schema
|
|
10
|
+
* versioning. Mantiene compatibilidad total con clientes existentes que
|
|
11
|
+
* conectan sin auth (si `SWL_MCP_API_KEY` no está set, comportamiento idéntico
|
|
12
|
+
* al stub v0.1.x).
|
|
13
|
+
*
|
|
14
|
+
* Modo de transporte: stdio (JSON-RPC sobre stdin/stdout).
|
|
15
|
+
*
|
|
16
|
+
* Uso (cliente MCP):
|
|
17
|
+
* - Configurar el cliente para ejecutar `node /path/to/swl-ses/bin/swl-mcp-server.js`
|
|
18
|
+
* con stdio.
|
|
19
|
+
* - El cwd del proceso determina baseDir (override con `SWL_MCP_BASE_DIR`).
|
|
20
|
+
*
|
|
21
|
+
* Variables opt-in (env del server):
|
|
22
|
+
* - SWL_MCP_API_KEY — Si set, requiere params._auth en cada tools/call.
|
|
23
|
+
* - SWL_MCP_CACHE_TTL_MS — TTL del cache mtime-based (default 60000).
|
|
24
|
+
* - SWL_MCP_METRICS — Si "1" o "true", persiste metrics en
|
|
25
|
+
* .planning/evolucion/mcp-metrics.jsonl.
|
|
26
|
+
*
|
|
27
|
+
* Schema versioning:
|
|
28
|
+
* - Cada handler declara `schemaVersion` en su definición.
|
|
29
|
+
* - El cliente puede inspeccionarlo via `tools/list` (campo `_schemaVersion`).
|
|
30
|
+
*
|
|
31
|
+
* Protocolo MCP soportado (subset):
|
|
32
|
+
* - initialize / initialized
|
|
33
|
+
* - tools/list
|
|
34
|
+
* - tools/call
|
|
35
|
+
* - ping
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const path = require('path');
|
|
39
|
+
|
|
40
|
+
const { HANDLERS } = require('../scripts/mcp-server/handlers');
|
|
41
|
+
const { construirValidador } = require('../scripts/mcp-server/auth');
|
|
42
|
+
const { construirTelemetria } = require('../scripts/mcp-server/telemetry');
|
|
43
|
+
|
|
44
|
+
const SERVER_NAME = 'swl-mcp-server';
|
|
45
|
+
const SERVER_VERSION = '1.0.0';
|
|
46
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
47
|
+
|
|
48
|
+
const baseDir = process.env.SWL_MCP_BASE_DIR || process.cwd();
|
|
49
|
+
const authValidator = construirValidador();
|
|
50
|
+
const telemetry = construirTelemetria({ baseDir });
|
|
51
|
+
|
|
52
|
+
// ── logging ───────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
// Stderr para evitar contaminar stdout (que es JSON-RPC).
|
|
55
|
+
function log(level, msg, data) {
|
|
56
|
+
const linea = JSON.stringify({
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
level,
|
|
59
|
+
msg,
|
|
60
|
+
...(data ? { data } : {}),
|
|
61
|
+
});
|
|
62
|
+
process.stderr.write(linea + '\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── JSON-RPC helpers ──────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function respuesta(id, result) {
|
|
68
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function errorResp(id, code, message) {
|
|
72
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── routing ───────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function manejarInitialize(request) {
|
|
78
|
+
return respuesta(request.id, {
|
|
79
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
80
|
+
capabilities: {
|
|
81
|
+
tools: { listChanged: false },
|
|
82
|
+
},
|
|
83
|
+
serverInfo: {
|
|
84
|
+
name: SERVER_NAME,
|
|
85
|
+
version: SERVER_VERSION,
|
|
86
|
+
authRequired: authValidator.requerida,
|
|
87
|
+
telemetryEnabled: telemetry.habilitada,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function manejarToolsList(request) {
|
|
93
|
+
const tools = Object.entries(HANDLERS).map(([name, def]) => ({
|
|
94
|
+
name,
|
|
95
|
+
description: def.description,
|
|
96
|
+
inputSchema: def.inputSchema,
|
|
97
|
+
_schemaVersion: def.schemaVersion || '1.0.0',
|
|
98
|
+
}));
|
|
99
|
+
return respuesta(request.id, { tools });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function manejarToolsCall(request) {
|
|
103
|
+
const { name, arguments: args } = request.params || {};
|
|
104
|
+
const def = HANDLERS[name];
|
|
105
|
+
if (!def) {
|
|
106
|
+
return errorResp(request.id, -32601, `Tool no encontrado: ${name}`);
|
|
107
|
+
}
|
|
108
|
+
const inicio = Date.now();
|
|
109
|
+
try {
|
|
110
|
+
const result = def.handler(baseDir, args || {});
|
|
111
|
+
const duracionMs = Date.now() - inicio;
|
|
112
|
+
telemetry.registrar({ tool: name, durationMs: duracionMs, ok: true, baseDir });
|
|
113
|
+
return respuesta(request.id, {
|
|
114
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const duracionMs = Date.now() - inicio;
|
|
118
|
+
log('error', `Excepción en handler ${name}`, { error: err.message });
|
|
119
|
+
telemetry.registrar({ tool: name, durationMs: duracionMs, ok: false, error: err.message, baseDir });
|
|
120
|
+
return errorResp(request.id, -32603, `Error interno: ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Punto de entrada del routing — público para tests.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} request - Mensaje JSON-RPC parseado.
|
|
128
|
+
* @returns {string|null} - String JSON-RPC respuesta o null (notification).
|
|
129
|
+
*/
|
|
130
|
+
function rutear(request) {
|
|
131
|
+
// Auth gate: si SWL_MCP_API_KEY está set, validar antes de routing.
|
|
132
|
+
const auth = authValidator.validar(request);
|
|
133
|
+
if (!auth.ok) {
|
|
134
|
+
return errorResp(request.id, auth.code, auth.message);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
switch (request.method) {
|
|
138
|
+
case 'initialize':
|
|
139
|
+
return manejarInitialize(request);
|
|
140
|
+
case 'initialized':
|
|
141
|
+
case 'notifications/initialized':
|
|
142
|
+
return null; // notification — sin respuesta
|
|
143
|
+
case 'tools/list':
|
|
144
|
+
return manejarToolsList(request);
|
|
145
|
+
case 'tools/call':
|
|
146
|
+
return manejarToolsCall(request);
|
|
147
|
+
case 'ping':
|
|
148
|
+
return respuesta(request.id, {});
|
|
149
|
+
default:
|
|
150
|
+
return errorResp(request.id, -32601, `Método no soportado: ${request.method}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── loop principal ────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
function arrancar() {
|
|
157
|
+
log('info', `${SERVER_NAME} v${SERVER_VERSION} iniciando`, {
|
|
158
|
+
baseDir,
|
|
159
|
+
authRequired: authValidator.requerida,
|
|
160
|
+
telemetryEnabled: telemetry.habilitada,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
let buffer = '';
|
|
164
|
+
|
|
165
|
+
process.stdin.setEncoding('utf8');
|
|
166
|
+
process.stdin.on('data', (chunk) => {
|
|
167
|
+
buffer += chunk;
|
|
168
|
+
|
|
169
|
+
// Cada mensaje JSON-RPC termina con \n
|
|
170
|
+
let nlIndex;
|
|
171
|
+
while ((nlIndex = buffer.indexOf('\n')) >= 0) {
|
|
172
|
+
const linea = buffer.slice(0, nlIndex).trim();
|
|
173
|
+
buffer = buffer.slice(nlIndex + 1);
|
|
174
|
+
|
|
175
|
+
if (!linea) continue;
|
|
176
|
+
|
|
177
|
+
let request;
|
|
178
|
+
try {
|
|
179
|
+
request = JSON.parse(linea);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
log('error', 'JSON inválido recibido', { error: err.message, linea: linea.slice(0, 100) });
|
|
182
|
+
process.stdout.write(errorResp(null, -32700, 'Parse error') + '\n');
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const respuestaStr = rutear(request);
|
|
187
|
+
if (respuestaStr) {
|
|
188
|
+
process.stdout.write(respuestaStr + '\n');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
process.stdin.on('end', () => {
|
|
194
|
+
log('info', 'stdin cerrado, server termina');
|
|
195
|
+
process.exit(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Manejo de errores no capturados — nunca crashear silenciosamente
|
|
199
|
+
process.on('uncaughtException', (err) => {
|
|
200
|
+
log('error', 'uncaughtException', { error: err.message, stack: err.stack });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (require.main === module) {
|
|
205
|
+
arrancar();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
rutear,
|
|
210
|
+
arrancar,
|
|
211
|
+
SERVER_NAME,
|
|
212
|
+
SERVER_VERSION,
|
|
213
|
+
PROTOCOL_VERSION,
|
|
214
|
+
};
|