@saulwade/swl-ses 1.4.2 → 1.5.1
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 +209 -208
- package/README.md +561 -560
- package/bin/swl-mcp-server.js +214 -187
- package/bin/swl-ses.js +74 -0
- package/comandos/swl/ejecutar-fase.md +33 -4
- package/comandos/swl/metricas.md +72 -0
- package/habilidades/discutir-fase/SKILL.md +50 -2
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -0
- package/habilidades/meta-skills-estandar/SKILL.md +22 -1
- package/habilidades/node-experto/SKILL.md +13 -2
- package/habilidades/protocolo-revision-swl/SKILL.md +276 -0
- package/habilidades/tdd-workflow/SKILL.md +33 -4
- package/habilidades/verificar-trabajo/SKILL.md +54 -5
- package/manifiestos/modulos.json +1321 -1267
- package/package.json +3 -3
- package/plugin.json +351 -351
- package/scripts/derivar-feature-list.js +489 -0
- package/scripts/doctor.js +62 -8
- package/scripts/instalador.js +56 -5
- package/scripts/lib/detectar-runtime.js +75 -9
- package/scripts/lib/estado.js +13 -1
- package/scripts/lib/expandir-targets.js +71 -0
- package/scripts/lib/parsear-opciones.js +3 -0
- 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/mcp-server/README.md +170 -128
- package/scripts/mcp-server/auth.js +105 -0
- package/scripts/mcp-server/cache.js +106 -0
- package/scripts/mcp-server/handlers.js +190 -10
- package/scripts/mcp-server/telemetry.js +78 -0
package/bin/swl-mcp-server.js
CHANGED
|
@@ -1,187 +1,214 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* swl-mcp-server — Servidor MCP
|
|
6
|
-
* de swl-ses a clientes MCP externos (Cursor,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* -
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
const path = require('path');
|
|
39
|
-
|
|
40
|
-
const { HANDLERS } = require('../scripts/mcp-server/handlers');
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
function
|
|
68
|
-
return JSON.stringify({ jsonrpc: '2.0', id,
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
process.stdin.on('
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
};
|
package/bin/swl-ses.js
CHANGED
|
@@ -336,6 +336,35 @@ function main() {
|
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
// ADR-0019 Sub-fase 2.5: Multi-target install/update/uninstall
|
|
340
|
+
//
|
|
341
|
+
// Si `--target=a,b,c` (CSV) o `--all-runtimes` está activo, iterar el comando
|
|
342
|
+
// por cada target. Solo aplica a comandos que aceptan `--target`. Para el resto,
|
|
343
|
+
// se pasa el comando tal cual.
|
|
344
|
+
const COMANDOS_MULTI_TARGET = ['install', 'update', 'uninstall'];
|
|
345
|
+
if (COMANDOS_MULTI_TARGET.includes(comando)) {
|
|
346
|
+
const { expandirTargets: expandir } = require('../scripts/lib/expandir-targets');
|
|
347
|
+
const expansion = expandir(opciones);
|
|
348
|
+
if (expansion.errores.length > 0) {
|
|
349
|
+
for (const e of expansion.errores) console.error(`[swl-ses] ${e}`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
const targets = expansion.targets;
|
|
353
|
+
if (targets.length > 1) {
|
|
354
|
+
ejecutarMultiTarget(comando, opciones, targets).catch(err => {
|
|
355
|
+
console.error(`Error en multi-target ${comando}: ${err.message}`);
|
|
356
|
+
if (opciones.verbose) console.error(err.stack);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Si targets.length === 1, ya viene normalizado y continuamos al flujo normal.
|
|
362
|
+
if (targets.length === 1) {
|
|
363
|
+
opciones.target = targets[0];
|
|
364
|
+
opciones.objetivo = targets[0];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
339
368
|
try {
|
|
340
369
|
const modulo = require(COMANDOS[comando]);
|
|
341
370
|
const resultado = modulo(opciones);
|
|
@@ -357,4 +386,49 @@ function main() {
|
|
|
357
386
|
}
|
|
358
387
|
}
|
|
359
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Ejecuta un comando (install/update/uninstall) sobre múltiples targets en serie.
|
|
391
|
+
*
|
|
392
|
+
* Atomicidad por target (ADR-0019 punto 9): si un target falla, los anteriores
|
|
393
|
+
* quedan instalados y se reporta el error sin rollback. Los targets siguientes
|
|
394
|
+
* igual se intentan — el usuario decide qué hacer después.
|
|
395
|
+
*
|
|
396
|
+
* @param {string} comando - install | update | uninstall
|
|
397
|
+
* @param {object} opcionesBase - Opciones parseadas (se clonan por target)
|
|
398
|
+
* @param {string[]} targets - Lista de target IDs ≥1
|
|
399
|
+
*/
|
|
400
|
+
async function ejecutarMultiTarget(comando, opcionesBase, targets) {
|
|
401
|
+
console.log(`\n[swl-ses] Multi-target ${comando}: ${targets.join(', ')}`);
|
|
402
|
+
console.log('='.repeat(60));
|
|
403
|
+
|
|
404
|
+
const resultados = [];
|
|
405
|
+
for (const t of targets) {
|
|
406
|
+
console.log(`\n[swl-ses] ▶ Target: ${t}`);
|
|
407
|
+
console.log('-'.repeat(60));
|
|
408
|
+
const opciones = { ...opcionesBase, target: t, objetivo: t };
|
|
409
|
+
try {
|
|
410
|
+
const modulo = require(COMANDOS[comando]);
|
|
411
|
+
const r = modulo(opciones);
|
|
412
|
+
if (r && typeof r.then === 'function') await r;
|
|
413
|
+
resultados.push({ target: t, ok: true });
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.error(`[swl-ses] ✘ Falló ${comando} para target "${t}": ${err.message}`);
|
|
416
|
+
if (opciones.verbose) console.error(err.stack);
|
|
417
|
+
resultados.push({ target: t, ok: false, error: err.message });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log('\n' + '='.repeat(60));
|
|
422
|
+
console.log(`[swl-ses] Resumen multi-target ${comando}:`);
|
|
423
|
+
for (const r of resultados) {
|
|
424
|
+
const marca = r.ok ? '✓' : '✘';
|
|
425
|
+
const detalle = r.error ? ` — ${r.error}` : '';
|
|
426
|
+
console.log(` ${marca} ${r.target}${detalle}`);
|
|
427
|
+
}
|
|
428
|
+
const fallidos = resultados.filter(r => !r.ok);
|
|
429
|
+
if (fallidos.length > 0) {
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
360
434
|
main();
|
|
@@ -4,20 +4,41 @@ description: Recibe el número de una fase y la implementa siguiendo el PLAN.md.
|
|
|
4
4
|
allowed_tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /swl:ejecutar-fase <n> — Ejecutar implementación de una fase
|
|
7
|
+
# /swl:ejecutar-fase <n> [--iterative] — Ejecutar implementación de una fase
|
|
8
8
|
|
|
9
9
|
Eres el coordinador de ejecución SWL. Orquestas la implementación real del código para una fase, delegando al agente implementador-swl, verificando cada slice y manteniendo el estado del proyecto actualizado.
|
|
10
10
|
|
|
11
11
|
## Uso
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
/swl:ejecutar-fase 1
|
|
15
|
-
/swl:ejecutar-fase 2
|
|
14
|
+
/swl:ejecutar-fase 1 # modo default — oleadas paralelas
|
|
15
|
+
/swl:ejecutar-fase 2 --iterative # modo iterativo — 1 tarea, review per-task, auto-debug
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
### Cuándo usar `--iterative` (modo opt-in)
|
|
19
|
+
|
|
20
|
+
Activar el flag `--iterative` cuando:
|
|
21
|
+
|
|
22
|
+
- La fase tiene **dependencias secuenciales fuertes** entre tareas.
|
|
23
|
+
- El módulo es **crítico** (dinero, permisos, datos irreversibles) y cada tarea merece revisión adversarial inmediata.
|
|
24
|
+
- La fase tiene **>12 tareas** y el modo paralelo arriesga acumular deuda silenciosa.
|
|
25
|
+
- El usuario pide explícitamente "tarea por tarea con revisión".
|
|
26
|
+
|
|
27
|
+
NO usar `--iterative` para fases pequeñas (<5 tareas) o cuando las tareas son
|
|
28
|
+
independientes y de bajo riesgo — el overhead per-task supera el beneficio.
|
|
29
|
+
|
|
18
30
|
## Paso 0 — Carga de habilidades
|
|
19
31
|
|
|
20
|
-
|
|
32
|
+
Si `--iterative` está presente, carga:
|
|
33
|
+
```
|
|
34
|
+
Skill("ejecutar-task-iterativo")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
El skill iterativo redirige el flujo: 1 tarea por iteración, contexto fresco
|
|
38
|
+
por implementer, review task-local con `protocolo-revision-swl`, auto-debug
|
|
39
|
+
en BLOCKED con `depurador-swl`, y commits atómicos estrictos por tarea.
|
|
40
|
+
|
|
41
|
+
Si NO hay `--iterative`, carga el flujo default:
|
|
21
42
|
```
|
|
22
43
|
Skill("ejecutar-fase")
|
|
23
44
|
```
|
|
@@ -31,6 +52,14 @@ Luego carga habilidades específicas del stack detectado en PLAN.md o PROYECTO.m
|
|
|
31
52
|
- Autenticación: `Skill("auth-patrones")`
|
|
32
53
|
- Siempre: `Skill("manejo-errores")`
|
|
33
54
|
|
|
55
|
+
### Si `--iterative` está activo
|
|
56
|
+
|
|
57
|
+
Tras cargar `ejecutar-task-iterativo`, sigue el protocolo definido ahí
|
|
58
|
+
(loop per-task con Pasos 1-8 internos del skill). El resto de pasos de
|
|
59
|
+
este comando se delegan al skill iterativo. Saltar los Pasos 2-7 de este
|
|
60
|
+
comando. El Paso 8 (Reporte final) y Reglas de comportamiento siguen
|
|
61
|
+
aplicando en ambos modos.
|
|
62
|
+
|
|
34
63
|
## Paso 1 — Verificación de prerrequisitos
|
|
35
64
|
|
|
36
65
|
Verifica en orden:
|
package/comandos/swl/metricas.md
CHANGED
|
@@ -21,6 +21,7 @@ costo estimado, herramientas más usadas y estado del presupuesto configurado.
|
|
|
21
21
|
```
|
|
22
22
|
/swl:metricas — Resumen de métricas de la sesión
|
|
23
23
|
/swl:metricas detalle — Desglose completo por herramienta y timeline
|
|
24
|
+
/swl:metricas fases — Progreso de fases del proyecto desde feature-list.json
|
|
24
25
|
/swl:metricas historico — Abre el dashboard histórico multi-sesión (alias de /swl:dashboard)
|
|
25
26
|
```
|
|
26
27
|
|
|
@@ -246,6 +247,77 @@ EOF
|
|
|
246
247
|
|
|
247
248
|
---
|
|
248
249
|
|
|
250
|
+
## Subcomando: `fases` — Progreso de fases del proyecto
|
|
251
|
+
|
|
252
|
+
Si el usuario invoca `/swl:metricas fases`, muestra el estado actual de las
|
|
253
|
+
fases del proyecto leyendo el JSON derivado de `HOJA-RUTA.md`.
|
|
254
|
+
|
|
255
|
+
### Generación y lectura del estado
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Regenerar el JSON desde HOJA-RUTA.md (fuente de verdad markdown)
|
|
259
|
+
node scripts/derivar-feature-list.js
|
|
260
|
+
|
|
261
|
+
# Leer el JSON generado en .planning/feature-list.json
|
|
262
|
+
cat .planning/feature-list.json | python3 -c "
|
|
263
|
+
import json, sys
|
|
264
|
+
data = json.load(sys.stdin)
|
|
265
|
+
t = data['totales']
|
|
266
|
+
total = t['fases'] or 1
|
|
267
|
+
pct = (t['completadas'] / total) * 100
|
|
268
|
+
|
|
269
|
+
print(f\"\\nSWL — Progreso de fases del proyecto\")
|
|
270
|
+
print('─' * 60)
|
|
271
|
+
print(f\" Total de fases: {t['fases']}\")
|
|
272
|
+
print(f\" Completadas: {t['completadas']} ({pct:.0f}%)\")
|
|
273
|
+
print(f\" En progreso: {t['en_progreso']}\")
|
|
274
|
+
print(f\" Pendientes: {t['pendientes']}\")
|
|
275
|
+
print(f\" Bloqueadas: {t['bloqueadas']}\")
|
|
276
|
+
print(f\" Spec lista: {t['spec_listas']}\")
|
|
277
|
+
print('─' * 60)
|
|
278
|
+
print()
|
|
279
|
+
for f in data['fases']:
|
|
280
|
+
estado = f['estado']
|
|
281
|
+
marca = {'completado': '✓', 'en_progreso': '◐', 'pendiente': '○',
|
|
282
|
+
'bloqueado': '✗', 'spec_listo': '◔'}.get(estado, '?')
|
|
283
|
+
nombre = f['nombre'][:48]
|
|
284
|
+
art = f.get('artefactos') or {}
|
|
285
|
+
plan = ' [PLAN]' if art.get('plan_md') else ''
|
|
286
|
+
resumen = ' [RESUMEN]' if art.get('resumen_md') else ''
|
|
287
|
+
print(f\" {marca} Fase {f['numero']:>2}: {nombre:<50}{plan}{resumen}\")
|
|
288
|
+
print()
|
|
289
|
+
print(f\" Fuente: {data['fuente']} · Generado: {data['generado_en']}\")
|
|
290
|
+
"
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Lo que se reporta
|
|
294
|
+
|
|
295
|
+
- **Totales**: cuántas fases hay y cuántas en cada estado canónico (`completado`,
|
|
296
|
+
`en_progreso`, `pendiente`, `bloqueado`, `spec_listo`).
|
|
297
|
+
- **Lista de fases** con marca visual de estado y artefactos presentes
|
|
298
|
+
(`[PLAN]` si existe `0N-PLAN.md`, `[RESUMEN]` si existe `0N-RESUMEN.md`).
|
|
299
|
+
- **Timestamp** de generación para detectar staleness.
|
|
300
|
+
|
|
301
|
+
### Cuándo usar
|
|
302
|
+
|
|
303
|
+
- Antes de retomar trabajo en una fase, para ver el estado actualizado.
|
|
304
|
+
- Al cerrar una sesión productiva, para verificar progreso vs HOJA-RUTA.md.
|
|
305
|
+
- Para auditoría programática (consumir el JSON desde un hook o dashboard externo).
|
|
306
|
+
|
|
307
|
+
### Detección de drift
|
|
308
|
+
|
|
309
|
+
Si `HOJA-RUTA.md` se modifica pero `feature-list.json` no se regenera, los datos
|
|
310
|
+
quedan desactualizados. Para verificar:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
node scripts/derivar-feature-list.js --check
|
|
314
|
+
# exit 0 = sincronizado, exit 2 = drift detectado
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`/swl:metricas fases` siempre regenera primero, así que ve estado fresco.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
249
321
|
## Subcomando: `historico`
|
|
250
322
|
|
|
251
323
|
Si el usuario invoca `/swl:metricas historico`, redirigir inmediatamente a:
|