zugzbot-sdd 1.5.30 → 1.5.32

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.
@@ -185,15 +185,77 @@ export default tool({
185
185
  report.push(`⚠️ No se pudo restablecer el lockfile: ${e.message}`);
186
186
  }
187
187
  }
188
+ // 5.5. Generar reporte dedicado de tokens y costes para este cambio
189
+ const historyPath = path.join(projectRoot, ".openspec/changes", args.changeName, "phase_history.jsonl");
190
+ if (fs.existsSync(historyPath)) {
191
+ try {
192
+ const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
193
+ const entries = lines.map(l => JSON.parse(l));
194
+ let totalCost = 0;
195
+ let totalInput = 0;
196
+ let totalOutput = 0;
197
+ const models = new Set();
198
+ entries.forEach((e) => {
199
+ if (e.analytics) {
200
+ totalCost = Math.max(totalCost, e.analytics.cumulative_cost_usd || 0);
201
+ totalInput = Math.max(totalInput, e.analytics.cumulative_tokens_input || 0);
202
+ totalOutput = Math.max(totalOutput, e.analytics.cumulative_tokens_output || 0);
203
+ if (Array.isArray(e.analytics.models_used)) {
204
+ e.analytics.models_used.forEach((m) => models.add(m));
205
+ }
206
+ }
207
+ });
208
+ const tokenUsageMarkdown = `# 💳 Reporte de Consumo del Cambio: ${args.changeName}
209
+
210
+ Este reporte detalla la telemetría de tokens y coste financiero en USD acumulado por el enjambre de agentes durante el desarrollo de esta tarea.
211
+
212
+ ## 📊 Métricas de Consumo
213
+ - **Coste Total de la Sesión:** $${totalCost.toFixed(4)} USD
214
+ - **Tokens de Entrada (Prompt):** ${totalInput.toLocaleString()} tokens
215
+ - **Tokens de Salida (Completion):** ${totalOutput.toLocaleString()} tokens
216
+ - **Modelos de IA Participantes:** ${Array.from(models).join(", ") || "Ninguno registrado"}
217
+
218
+ ---
219
+ *Reporte autogenerado de forma atómica al cierre de la tarea por sdd_archive_and_commit.*
220
+ `;
221
+ fs.writeFileSync(path.join(projectRoot, ".openspec/changes", args.changeName, "token_usage.md"), tokenUsageMarkdown, "utf-8");
222
+ report.push(`✓ Reporte de telemetría de tokens generado con éxito en token_usage.md`);
223
+ }
224
+ catch (e) {
225
+ report.push(`⚠️ No se pudo generar el reporte de telemetría de tokens: ${e.message}`);
226
+ }
227
+ }
188
228
  // 6. Archivar la carpeta físicamente (ANTES del commit para que quede registrado de forma atómica)
189
- const archiveDir = path.join(projectRoot, ".openspec/changes/archive", `${dateStr}-${args.changeName}`);
229
+ // Calcular el siguiente prefijo de secuencia cronológica (ej: 0001_2026-05-30_143015)
230
+ let seqPrefix = "0001";
231
+ const archiveRoot = path.join(projectRoot, ".openspec/changes/archive");
232
+ if (fs.existsSync(archiveRoot)) {
233
+ try {
234
+ const dirs = fs.readdirSync(archiveRoot).filter(f => fs.statSync(path.join(archiveRoot, f)).isDirectory());
235
+ let maxSeq = 0;
236
+ dirs.forEach(d => {
237
+ const match = d.match(/^(\d{4})_/);
238
+ if (match) {
239
+ const seq = parseInt(match[1], 10);
240
+ if (seq > maxSeq)
241
+ maxSeq = seq;
242
+ }
243
+ });
244
+ seqPrefix = String(maxSeq + 1).padStart(4, "0");
245
+ }
246
+ catch (e) { }
247
+ }
248
+ const now = new Date();
249
+ const timeStr = now.toTimeString().split(" ")[0].replace(/:/g, ""); // e.g. "143015"
250
+ const archiveFolderName = `${seqPrefix}_${dateStr}_${timeStr}-${args.changeName}`;
251
+ const archiveDir = path.join(archiveRoot, archiveFolderName);
190
252
  try {
191
253
  if (fs.existsSync(archiveDir)) {
192
254
  fs.rmSync(archiveDir, { recursive: true, force: true });
193
255
  }
194
256
  fs.mkdirSync(path.dirname(archiveDir), { recursive: true });
195
257
  moveRecursive(changeDir, archiveDir);
196
- report.push(`✓ Carpeta archivada en: .openspec/changes/archive/${dateStr}-${args.changeName}/`);
258
+ report.push(`✓ Carpeta archivada en: .openspec/changes/archive/${archiveFolderName}/`);
197
259
  }
198
260
  catch (e) {
199
261
  return `[SDD Archive Error] Error crítico archivando carpetas: ${e.message}`;
package/AGENTS.md CHANGED
@@ -15,6 +15,7 @@ Queda terminantemente prohibido para cualquier agente del swarm (incluyendo al O
15
15
  - **No Trabajo en Caliente**: Está prohibido proponer código fuente, diseños HTML/CSS o parches técnicos directamente al usuario en el chat principal sin antes haber completado la **Fase 1 (Planificación e Interrogación)** y obtenido su visto bueno explícito.
16
16
  - **Rol del Orquestador**: `@zugzbot` debe educar siempre al usuario sobre el flujo de SDD cuando se solicite una nueva característica o cambio. Debe generar un **Roadmap de las 6 Fases de SDD de una línea por fase** y delegar la Fase 1 de inmediato.
17
17
  - **Flujo de Trabajo Estricto**: Todo cambio lógico debe iniciarse a través de la delegación estructurada hacia `@sdd-planner`.
18
+ - **Slug Semántico del Cambio (kebab-case) [CRÍTICO]**: Al iniciar un ciclo (transición a Fase 1), el orquestador `@zugzbot` o `@sdd-planner` debe generar obligatoriamente un nombre de cambio (`changeName` o `change_name`) en kebab-case que describa de manera precisa el requerimiento del usuario (ej: `crear-modulo-auth` o `setup-clasp-mocker`). Queda terminantemente prohibido utilizar el valor genérico 'nuevo-cambio' o nombres sin relación semántica.
18
19
 
19
20
  ---
20
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot-sdd",
3
- "version": "1.5.30",
3
+ "version": "1.5.32",
4
4
  "description": "Zugzbot SDD Swarm - Spec-Driven Development Harness for OpenCode",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -184,15 +184,79 @@ export default tool({
184
184
  }
185
185
  }
186
186
 
187
+ // 5.5. Generar reporte dedicado de tokens y costes para este cambio
188
+ const historyPath = path.join(projectRoot, ".openspec/changes", args.changeName, "phase_history.jsonl");
189
+ if (fs.existsSync(historyPath)) {
190
+ try {
191
+ const lines = fs.readFileSync(historyPath, "utf-8").split("\n").filter(Boolean);
192
+ const entries = lines.map(l => JSON.parse(l));
193
+ let totalCost = 0;
194
+ let totalInput = 0;
195
+ let totalOutput = 0;
196
+ const models = new Set<string>();
197
+
198
+ entries.forEach((e: any) => {
199
+ if (e.analytics) {
200
+ totalCost = Math.max(totalCost, e.analytics.cumulative_cost_usd || 0);
201
+ totalInput = Math.max(totalInput, e.analytics.cumulative_tokens_input || 0);
202
+ totalOutput = Math.max(totalOutput, e.analytics.cumulative_tokens_output || 0);
203
+ if (Array.isArray(e.analytics.models_used)) {
204
+ e.analytics.models_used.forEach((m: string) => models.add(m));
205
+ }
206
+ }
207
+ });
208
+
209
+ const tokenUsageMarkdown = `# 💳 Reporte de Consumo del Cambio: ${args.changeName}
210
+
211
+ Este reporte detalla la telemetría de tokens y coste financiero en USD acumulado por el enjambre de agentes durante el desarrollo de esta tarea.
212
+
213
+ ## 📊 Métricas de Consumo
214
+ - **Coste Total de la Sesión:** $${totalCost.toFixed(4)} USD
215
+ - **Tokens de Entrada (Prompt):** ${totalInput.toLocaleString()} tokens
216
+ - **Tokens de Salida (Completion):** ${totalOutput.toLocaleString()} tokens
217
+ - **Modelos de IA Participantes:** ${Array.from(models).join(", ") || "Ninguno registrado"}
218
+
219
+ ---
220
+ *Reporte autogenerado de forma atómica al cierre de la tarea por sdd_archive_and_commit.*
221
+ `;
222
+ fs.writeFileSync(path.join(projectRoot, ".openspec/changes", args.changeName, "token_usage.md"), tokenUsageMarkdown, "utf-8");
223
+ report.push(`✓ Reporte de telemetría de tokens generado con éxito en token_usage.md`);
224
+ } catch (e: any) {
225
+ report.push(`⚠️ No se pudo generar el reporte de telemetría de tokens: ${e.message}`);
226
+ }
227
+ }
228
+
187
229
  // 6. Archivar la carpeta físicamente (ANTES del commit para que quede registrado de forma atómica)
188
- const archiveDir = path.join(projectRoot, ".openspec/changes/archive", `${dateStr}-${args.changeName}`)
230
+ // Calcular el siguiente prefijo de secuencia cronológica (ej: 0001_2026-05-30_143015)
231
+ let seqPrefix = "0001";
232
+ const archiveRoot = path.join(projectRoot, ".openspec/changes/archive");
233
+ if (fs.existsSync(archiveRoot)) {
234
+ try {
235
+ const dirs = fs.readdirSync(archiveRoot).filter(f => fs.statSync(path.join(archiveRoot, f)).isDirectory());
236
+ let maxSeq = 0;
237
+ dirs.forEach(d => {
238
+ const match = d.match(/^(\d{4})_/);
239
+ if (match) {
240
+ const seq = parseInt(match[1], 10);
241
+ if (seq > maxSeq) maxSeq = seq;
242
+ }
243
+ });
244
+ seqPrefix = String(maxSeq + 1).padStart(4, "0");
245
+ } catch (e) {}
246
+ }
247
+
248
+ const now = new Date();
249
+ const timeStr = now.toTimeString().split(" ")[0].replace(/:/g, ""); // e.g. "143015"
250
+ const archiveFolderName = `${seqPrefix}_${dateStr}_${timeStr}-${args.changeName}`;
251
+ const archiveDir = path.join(archiveRoot, archiveFolderName);
252
+
189
253
  try {
190
254
  if (fs.existsSync(archiveDir)) {
191
255
  fs.rmSync(archiveDir, { recursive: true, force: true })
192
256
  }
193
257
  fs.mkdirSync(path.dirname(archiveDir), { recursive: true })
194
258
  moveRecursive(changeDir, archiveDir)
195
- report.push(`✓ Carpeta archivada en: .openspec/changes/archive/${dateStr}-${args.changeName}/`)
259
+ report.push(`✓ Carpeta archivada en: .openspec/changes/archive/${archiveFolderName}/`)
196
260
  } catch (e: any) {
197
261
  return `[SDD Archive Error] Error crítico archivando carpetas: ${e.message}`
198
262
  }