@saulwade/swl-ses 1.4.1 → 1.5.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 +3 -3
- package/README.md +561 -560
- package/agentes/nemesis-auditor-swl.md +161 -161
- package/bin/swl-mcp-server.js +49 -22
- package/bin/swl-ses.js +74 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/ejecutar-fase.md +33 -4
- package/comandos/swl/metricas.md +72 -0
- package/comandos/swl/nemesis.md +122 -122
- 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/discutir-fase/SKILL.md +50 -2
- 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 -0
- 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/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- 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 +276 -0
- 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/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +49 -5
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- 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 +1321 -1262
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +3 -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 +351 -343
- 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/schemas/diary-entry.schema.json +80 -80
- 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 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +31 -4
- 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/instalador.js +56 -5
- 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-runtime.js +75 -9
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/estado.js +13 -1
- 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 -0
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +3 -0
- 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 -0
- package/scripts/lib/transformadores/base.js +43 -9
- package/scripts/lib/transformadores/codex.js +375 -115
- package/scripts/lib/transformadores/cursor.js +359 -0
- package/scripts/lib/transformadores/index.js +2 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +122 -80
- package/scripts/mcp-server/auth.js +105 -0
- package/scripts/mcp-server/cache.js +106 -0
- package/scripts/mcp-server/handlers.js +386 -206
- package/scripts/mcp-server/telemetry.js +78 -0
- 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 +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,206 +1,386 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* handlers.js — Handlers para los 3 endpoints MCP
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Los handlers leen el estado file-based de swl-ses (APRENDIZAJES.md,
|
|
10
|
-
* .planning/sessions/, instintos/proyecto.yaml) y devuelven datos
|
|
11
|
-
* estructurados al cliente MCP. NO escriben — solo lectura.
|
|
12
|
-
*
|
|
13
|
-
* @module scripts/mcp-server/handlers
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
|
|
19
|
-
const memorySearch = require('../../hooks/lib/memory-search');
|
|
20
|
-
const scoringInstintos = require('../lib/scoring-instintos');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (typeof args.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* handlers.js — Handlers para los 3 endpoints MCP del swl-mcp-server.
|
|
5
|
+
*
|
|
6
|
+
* v1.0.0 (ADR-0019 Sub-fase 3): integra caching mtime-based desde
|
|
7
|
+
* `scripts/mcp-server/cache.js`. Sigue siendo solo lectura.
|
|
8
|
+
*
|
|
9
|
+
* Los handlers leen el estado file-based de swl-ses (APRENDIZAJES.md,
|
|
10
|
+
* .planning/sessions/, instintos/proyecto.yaml) y devuelven datos
|
|
11
|
+
* estructurados al cliente MCP. NO escriben — solo lectura.
|
|
12
|
+
*
|
|
13
|
+
* @module scripts/mcp-server/handlers
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const memorySearch = require('../../hooks/lib/memory-search');
|
|
20
|
+
const scoringInstintos = require('../lib/scoring-instintos');
|
|
21
|
+
const { crearCache } = require('./cache');
|
|
22
|
+
|
|
23
|
+
// Cache singleton — compartido entre handlers para reducir IO en lecturas repetidas.
|
|
24
|
+
// mtime-based, invalidación automática cuando el archivo cambia. ADR-0019 Sub-fase 3.
|
|
25
|
+
const cache = crearCache();
|
|
26
|
+
|
|
27
|
+
// ── handler: swl_memory_search ────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Búsqueda hybrid sobre memoria SWL (aprendizajes + sesiones + instintos).
|
|
31
|
+
*
|
|
32
|
+
* @param {object} args - { query: string, limit?: number, tipo?: string }
|
|
33
|
+
* @returns {object} { results: Array, count: number }
|
|
34
|
+
*/
|
|
35
|
+
function swlMemorySearch(baseDir, args) {
|
|
36
|
+
if (!args || typeof args.query !== 'string' || !args.query.trim()) {
|
|
37
|
+
return { error: 'query (string) requerido', results: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const filtros = {};
|
|
41
|
+
if (typeof args.limit === 'number' && args.limit > 0) filtros.limit = Math.min(args.limit, 50);
|
|
42
|
+
if (typeof args.tipo === 'string') filtros.tipo = args.tipo;
|
|
43
|
+
|
|
44
|
+
const results = memorySearch.search(baseDir, args.query, filtros);
|
|
45
|
+
return {
|
|
46
|
+
results: results.map(r => ({
|
|
47
|
+
id: r.id,
|
|
48
|
+
tipo: r.tipo,
|
|
49
|
+
titulo: r.titulo,
|
|
50
|
+
fecha: r.fecha,
|
|
51
|
+
relevancia: r.relevancia,
|
|
52
|
+
combinedScore: r.combinedScore,
|
|
53
|
+
})),
|
|
54
|
+
count: results.length,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── handler: swl_aprendizajes_recientes ───────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Devuelve los N aprendizajes más recientes de APRENDIZAJES.md.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} args - { limit?: number (default 10) }
|
|
64
|
+
* @returns {object} { results, count }
|
|
65
|
+
*/
|
|
66
|
+
function swlAprendizajesRecientes(baseDir, args = {}) {
|
|
67
|
+
const limit = (typeof args.limit === 'number' && args.limit > 0)
|
|
68
|
+
? Math.min(args.limit, 50)
|
|
69
|
+
: 10;
|
|
70
|
+
|
|
71
|
+
const ruta = path.join(baseDir, '.planning', 'APRENDIZAJES.md');
|
|
72
|
+
let cached;
|
|
73
|
+
try {
|
|
74
|
+
cached = cache.get(ruta, (contenido) => {
|
|
75
|
+
const bloques = contenido.split(/^## /m).filter(b => b.trim().length > 0);
|
|
76
|
+
return { bloques };
|
|
77
|
+
});
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return { error: 'Error de lectura: ' + err.message, results: [] };
|
|
80
|
+
}
|
|
81
|
+
if (!cached) return { error: 'APRENDIZAJES.md no encontrado', results: [] };
|
|
82
|
+
|
|
83
|
+
const { bloques } = cached.data;
|
|
84
|
+
// Los más recientes están al FINAL del archivo (append-only por convención)
|
|
85
|
+
const recientes = bloques.slice(-limit).reverse();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
results: recientes.map((b, i) => {
|
|
89
|
+
const primeraLinea = b.split('\n')[0].trim();
|
|
90
|
+
const cuerpoTrim = b.split('\n').slice(1).join('\n').trim().slice(0, 500);
|
|
91
|
+
return {
|
|
92
|
+
index: bloques.length - i,
|
|
93
|
+
titulo: primeraLinea,
|
|
94
|
+
contenido: cuerpoTrim,
|
|
95
|
+
};
|
|
96
|
+
}),
|
|
97
|
+
count: recientes.length,
|
|
98
|
+
total: bloques.length,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── handler: swl_instintos_activos ────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Devuelve instintos con effective_confidence ≥ umbral.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} args - { minConfidence?: number (default 0.5), limit?: number }
|
|
108
|
+
* @returns {object} { results, count }
|
|
109
|
+
*/
|
|
110
|
+
function swlInstintosActivos(baseDir, args = {}) {
|
|
111
|
+
const minConfidence = (typeof args.minConfidence === 'number')
|
|
112
|
+
? args.minConfidence : 0.5;
|
|
113
|
+
const limit = (typeof args.limit === 'number' && args.limit > 0)
|
|
114
|
+
? Math.min(args.limit, 100) : 20;
|
|
115
|
+
|
|
116
|
+
const ruta = path.join(baseDir, 'instintos', 'proyecto.yaml');
|
|
117
|
+
if (!fs.existsSync(ruta)) {
|
|
118
|
+
return { error: 'instintos/proyecto.yaml no encontrado', results: [] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let contenido;
|
|
122
|
+
try {
|
|
123
|
+
contenido = fs.readFileSync(ruta, 'utf8');
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return { error: 'Error de lectura: ' + err.message, results: [] };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Parser simple sin dep YAML (mismo patrón que memory-search.js)
|
|
129
|
+
const instinto_re = /- id:\s*(\S+)[\s\S]*?pattern:\s*"([^"]+)"[\s\S]*?confidence:\s*([\d.]+)[\s\S]*?status:\s*(\w+)/g;
|
|
130
|
+
const results = [];
|
|
131
|
+
let match;
|
|
132
|
+
const ahora = new Date();
|
|
133
|
+
|
|
134
|
+
while ((match = instinto_re.exec(contenido)) !== null) {
|
|
135
|
+
const [, id, pattern, confidenceStr, status] = match;
|
|
136
|
+
const confidence = parseFloat(confidenceStr);
|
|
137
|
+
if (status !== 'active') continue;
|
|
138
|
+
|
|
139
|
+
// Construir objeto mínimo para scoring
|
|
140
|
+
const instinto = {
|
|
141
|
+
id,
|
|
142
|
+
pattern,
|
|
143
|
+
confidence,
|
|
144
|
+
status,
|
|
145
|
+
// Sin más metadata, effective_confidence ≈ confidence
|
|
146
|
+
};
|
|
147
|
+
const effective = scoringInstintos.effectiveConfidence(instinto, ahora);
|
|
148
|
+
if (effective < minConfidence) continue;
|
|
149
|
+
|
|
150
|
+
results.push({
|
|
151
|
+
id,
|
|
152
|
+
pattern,
|
|
153
|
+
confidence,
|
|
154
|
+
effective_confidence: Math.round(effective * 1000) / 1000,
|
|
155
|
+
status,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
results.sort((a, b) => b.effective_confidence - a.effective_confidence);
|
|
160
|
+
return {
|
|
161
|
+
results: results.slice(0, limit),
|
|
162
|
+
count: Math.min(results.length, limit),
|
|
163
|
+
total: results.length,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── handler: swl_list_skills ──────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Lista los skills disponibles (nombre + descripción del frontmatter).
|
|
171
|
+
*
|
|
172
|
+
* Sub-fase 9 (v1.5.0). Útil para clientes MCP que no cargan skills filesystem
|
|
173
|
+
* nativamente y necesitan descubrir qué skills existen antes de invocarlos
|
|
174
|
+
* con swl_invoke_skill.
|
|
175
|
+
*
|
|
176
|
+
* Resolución del directorio de skills:
|
|
177
|
+
* 1. Si baseDir tiene `habilidades/`, usar ese (repo SWL como project root).
|
|
178
|
+
* 2. Si baseDir tiene `.claude/skills/`, usar ese (proyecto consumidor con SWL instalado).
|
|
179
|
+
* 3. Si baseDir tiene `.cursor/skills/`, usar ese.
|
|
180
|
+
* 4. Si nada de eso, devolver error.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} baseDir
|
|
183
|
+
* @param {object} args - { dominio?: string, limit?: number }
|
|
184
|
+
*/
|
|
185
|
+
function swlListSkills(baseDir, args = {}) {
|
|
186
|
+
const dirSkills = resolverDirSkills(baseDir);
|
|
187
|
+
if (!dirSkills) {
|
|
188
|
+
return { error: 'No se encontró directorio de skills (habilidades/, .claude/skills/, .cursor/skills/)', results: [] };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const limit = (typeof args.limit === 'number' && args.limit > 0)
|
|
192
|
+
? Math.min(args.limit, 500) : 200;
|
|
193
|
+
const dominio = typeof args.dominio === 'string' ? args.dominio.toLowerCase() : null;
|
|
194
|
+
|
|
195
|
+
let nombres;
|
|
196
|
+
try {
|
|
197
|
+
nombres = fs.readdirSync(dirSkills, { withFileTypes: true })
|
|
198
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('.'))
|
|
199
|
+
.map(d => d.name);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
return { error: 'Error leyendo directorio: ' + err.message, results: [] };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const results = [];
|
|
205
|
+
for (const nombre of nombres) {
|
|
206
|
+
if (dominio && !nombre.toLowerCase().includes(dominio)) continue;
|
|
207
|
+
const skillMd = path.join(dirSkills, nombre, 'SKILL.md');
|
|
208
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
209
|
+
try {
|
|
210
|
+
const contenido = fs.readFileSync(skillMd, 'utf-8');
|
|
211
|
+
const descMatch = contenido.match(/description:\s*>?\s*\n?\s*(.+?)(?=\n\w+:|\n---)/s);
|
|
212
|
+
const descripcion = descMatch ? descMatch[1].replace(/\s+/g, ' ').trim() : '';
|
|
213
|
+
results.push({
|
|
214
|
+
nombre,
|
|
215
|
+
descripcion: descripcion.length > 300 ? descripcion.slice(0, 297) + '...' : descripcion,
|
|
216
|
+
});
|
|
217
|
+
} catch { /* skill ilegible — saltar */ }
|
|
218
|
+
if (results.length >= limit) break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { results, count: results.length, total: nombres.length, dir: dirSkills };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── handler: swl_invoke_skill ─────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Devuelve el contenido completo de un SKILL.md por nombre.
|
|
228
|
+
*
|
|
229
|
+
* Sub-fase 9 (v1.5.0). Útil para clientes MCP donde el cliente NO carga skills
|
|
230
|
+
* filesystem nativamente (Codex CLI en `--local`, Gemini CLI, otros) pero quiere
|
|
231
|
+
* acceder a conocimiento operacional SWL. El cliente recibe el cuerpo completo
|
|
232
|
+
* del SKILL.md y puede usarlo como contexto en su próxima llamada.
|
|
233
|
+
*
|
|
234
|
+
* Limitaciones aceptadas:
|
|
235
|
+
* - NO ejecuta scripts del skill — solo devuelve el SKILL.md como texto.
|
|
236
|
+
* - Si el skill referencia `recursos/X.md`, el cliente debe pedirlo aparte
|
|
237
|
+
* (no implementamos retrieval recursivo en v1.5.0).
|
|
238
|
+
* - El cuerpo se trunca a 100 KB para no saturar el contexto del cliente.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} baseDir
|
|
241
|
+
* @param {object} args - { nombre: string }
|
|
242
|
+
*/
|
|
243
|
+
function swlInvokeSkill(baseDir, args) {
|
|
244
|
+
if (!args || typeof args.nombre !== 'string' || !args.nombre.trim()) {
|
|
245
|
+
return { error: 'argumento "nombre" (string) requerido' };
|
|
246
|
+
}
|
|
247
|
+
// Sanitización: solo nombres en kebab-case (prevenir path traversal).
|
|
248
|
+
const nombre = args.nombre.trim();
|
|
249
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}$/i.test(nombre)) {
|
|
250
|
+
return { error: 'Nombre de skill inválido. Solo [a-zA-Z0-9-], 1-63 caracteres.' };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const dirSkills = resolverDirSkills(baseDir);
|
|
254
|
+
if (!dirSkills) {
|
|
255
|
+
return { error: 'No se encontró directorio de skills (habilidades/, .claude/skills/, .cursor/skills/)' };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const skillMd = path.join(dirSkills, nombre, 'SKILL.md');
|
|
259
|
+
if (!fs.existsSync(skillMd)) {
|
|
260
|
+
return { error: `Skill "${nombre}" no encontrado en ${dirSkills}` };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let contenido;
|
|
264
|
+
try {
|
|
265
|
+
contenido = fs.readFileSync(skillMd, 'utf-8');
|
|
266
|
+
} catch (err) {
|
|
267
|
+
return { error: 'Error leyendo SKILL.md: ' + err.message };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const MAX = 100 * 1024;
|
|
271
|
+
let truncado = false;
|
|
272
|
+
if (Buffer.byteLength(contenido, 'utf-8') > MAX) {
|
|
273
|
+
contenido = contenido.slice(0, MAX) + '\n\n> NOTA: contenido truncado a 100 KB.';
|
|
274
|
+
truncado = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Listar recursos disponibles (sin leerlos) para que el cliente sepa qué pedir.
|
|
278
|
+
const recursos = [];
|
|
279
|
+
const dirRecursos = path.join(dirSkills, nombre, 'recursos');
|
|
280
|
+
if (fs.existsSync(dirRecursos)) {
|
|
281
|
+
try {
|
|
282
|
+
const archivos = fs.readdirSync(dirRecursos)
|
|
283
|
+
.filter(f => /\.(md|json|yaml|yml|txt)$/i.test(f));
|
|
284
|
+
recursos.push(...archivos.map(f => `recursos/${f}`));
|
|
285
|
+
} catch { /* sin recursos */ }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return { nombre, contenido, truncado, recursos, fuente: skillMd };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Resuelve el directorio de skills en el orden de precedencia:
|
|
293
|
+
* 1. baseDir/habilidades/ (repo SWL como project root)
|
|
294
|
+
* 2. baseDir/.claude/skills/ (proyecto consumidor con SWL instalado en Claude)
|
|
295
|
+
* 3. baseDir/.cursor/skills/ (proyecto con SWL instalado en Cursor)
|
|
296
|
+
*
|
|
297
|
+
* Retorna null si ninguno existe.
|
|
298
|
+
*/
|
|
299
|
+
function resolverDirSkills(baseDir) {
|
|
300
|
+
const candidatos = [
|
|
301
|
+
path.join(baseDir, 'habilidades'),
|
|
302
|
+
path.join(baseDir, '.claude', 'skills'),
|
|
303
|
+
path.join(baseDir, '.cursor', 'skills'),
|
|
304
|
+
];
|
|
305
|
+
for (const dir of candidatos) {
|
|
306
|
+
if (fs.existsSync(dir)) return dir;
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── exports ───────────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
const HANDLERS = {
|
|
314
|
+
swl_memory_search: {
|
|
315
|
+
description: 'Búsqueda hybrid sobre memoria SWL (aprendizajes + sesiones + instintos) con RRF fusion.',
|
|
316
|
+
schemaVersion: '1.0.0',
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {
|
|
320
|
+
query: { type: 'string', description: 'Texto libre de búsqueda' },
|
|
321
|
+
limit: { type: 'number', description: 'Máximo de resultados (default 20, max 50)' },
|
|
322
|
+
tipo: { type: 'string', enum: ['aprendizaje', 'sesion', 'instinto'], description: 'Filtrar por tipo' },
|
|
323
|
+
},
|
|
324
|
+
required: ['query'],
|
|
325
|
+
},
|
|
326
|
+
handler: swlMemorySearch,
|
|
327
|
+
},
|
|
328
|
+
swl_aprendizajes_recientes: {
|
|
329
|
+
description: 'Últimos N aprendizajes de .planning/APRENDIZAJES.md (más recientes primero).',
|
|
330
|
+
schemaVersion: '1.0.0',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
limit: { type: 'number', description: 'Cuántos retornar (default 10, max 50)' },
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
handler: swlAprendizajesRecientes,
|
|
338
|
+
},
|
|
339
|
+
swl_instintos_activos: {
|
|
340
|
+
description: 'Instintos con effective_confidence ≥ umbral. Default 0.5.',
|
|
341
|
+
schemaVersion: '1.0.0',
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
minConfidence: { type: 'number', description: 'Umbral mínimo (default 0.5)' },
|
|
346
|
+
limit: { type: 'number', description: 'Máximo (default 20, max 100)' },
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
handler: swlInstintosActivos,
|
|
350
|
+
},
|
|
351
|
+
swl_list_skills: {
|
|
352
|
+
description: 'Lista skills SWL disponibles (nombre + descripción). Útil para descubrir conocimiento operacional antes de invocar swl_invoke_skill.',
|
|
353
|
+
schemaVersion: '1.0.0',
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
dominio: { type: 'string', description: 'Filtro substring por nombre (opcional)' },
|
|
358
|
+
limit: { type: 'number', description: 'Máximo (default 200, max 500)' },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
handler: swlListSkills,
|
|
362
|
+
},
|
|
363
|
+
swl_invoke_skill: {
|
|
364
|
+
description: 'Devuelve el SKILL.md completo de un skill SWL por nombre. Para clientes MCP que no cargan skills filesystem nativamente — el cliente recibe el cuerpo y lo usa como contexto.',
|
|
365
|
+
schemaVersion: '1.0.0',
|
|
366
|
+
inputSchema: {
|
|
367
|
+
type: 'object',
|
|
368
|
+
properties: {
|
|
369
|
+
nombre: { type: 'string', description: 'Nombre del skill (kebab-case, ej. fastapi-experto)' },
|
|
370
|
+
},
|
|
371
|
+
required: ['nombre'],
|
|
372
|
+
},
|
|
373
|
+
handler: swlInvokeSkill,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
module.exports = {
|
|
378
|
+
HANDLERS,
|
|
379
|
+
swlMemorySearch,
|
|
380
|
+
swlAprendizajesRecientes,
|
|
381
|
+
swlInstintosActivos,
|
|
382
|
+
swlListSkills,
|
|
383
|
+
swlInvokeSkill,
|
|
384
|
+
// Expuesto para tests — permite invalidar el cache singleton entre runs.
|
|
385
|
+
_cache: cache,
|
|
386
|
+
};
|