zugzbot-sdd 1.5.34 → 1.5.36

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.
@@ -44,7 +44,8 @@ export default tool({
44
44
  category: tool.schema.string().optional().describe("Categoría del aprendizaje para el cerebro"),
45
45
  tag: tool.schema.string().optional().describe("Tag corto del aprendizaje para el cerebro"),
46
46
  problem: tool.schema.string().optional().describe("Problema resuelto (máx 120 caracteres)"),
47
- solution: tool.schema.string().optional().describe("Solución aplicada (máx 300 caracteres)")
47
+ solution: tool.schema.string().optional().describe("Solución aplicada (máx 300 caracteres)"),
48
+ bypassPendingTasks: tool.schema.boolean().optional().default(false).describe("Ignorar/Bypassear la verificación de tareas pendientes en el lockfile si el usuario aprobó manualmente o es un flujo QA Manual")
48
49
  },
49
50
  async execute(args, context) {
50
51
  const projectRoot = context.worktree || context.directory;
@@ -57,11 +58,23 @@ export default tool({
57
58
  if (fs.existsSync(lockfilePath)) {
58
59
  try {
59
60
  const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"));
61
+ const isManualQa = lockfile.qa_manual === true || lockfile.manual_qa === true;
62
+ const shouldBypass = args.bypassPendingTasks === true || isManualQa;
60
63
  if (lockfile.tasks && Array.isArray(lockfile.tasks) && lockfile.tasks.length > 0) {
61
64
  const pendingTasks = lockfile.tasks.filter((t) => t.status === "pending");
62
65
  if (pendingTasks.length > 0) {
63
- const pendingList = pendingTasks.map((t) => ` ⚠️ [${t.id}] ${t.desc}`).join("\n");
64
- return `[SDD Archive Blocked] No se puede cerrar el ciclo. Hay ${pendingTasks.length} tarea(s) pendiente(s):\n${pendingList}\n\nPor favor, completa todas las tareas o fuerza el cierre con aprobación explícita del usuario.`;
66
+ if (shouldBypass) {
67
+ // Auto-completar tareas en caliente para dejar el lockfile limpio
68
+ lockfile.tasks.forEach((t) => {
69
+ if (t.status === "pending")
70
+ t.status = "completed";
71
+ });
72
+ fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2), "utf-8");
73
+ }
74
+ else {
75
+ const pendingList = pendingTasks.map((t) => ` ⚠️ [${t.id}] ${t.desc}`).join("\n");
76
+ return `[SDD Archive Blocked] No se puede cerrar el ciclo. Hay ${pendingTasks.length} tarea(s) pendiente(s):\n${pendingList}\n\nPor favor, completa todas las tareas o fuerza el cierre con aprobación explícita del usuario.`;
77
+ }
65
78
  }
66
79
  }
67
80
  }
@@ -161,15 +174,7 @@ export default tool({
161
174
  catch (e) {
162
175
  report.push(`⚠️ Sincronización automática de habilidades fallida o no disponible: ${e.message || e}`);
163
176
  }
164
- // 4. Escribir commit_message.txt en location TEMPORAL antes de archivar
165
- const tempCommitMsgPath = path.join(projectRoot, ".openspec", `commit_msg_${args.changeName}.txt`);
166
- try {
167
- fs.writeFileSync(tempCommitMsgPath, args.commitMessage + "\n", "utf-8");
168
- report.push(`✓ Archivo commit_message.txt generado en location temporal`);
169
- }
170
- catch (e) {
171
- report.push(`⚠️ Error escribiendo commit_message.txt: ${e.message}`);
172
- }
177
+ // 4. No temporary file is needed on disk, we feed it directly to git commit stdin in step 7.
173
178
  // 5. Resetear el lockfile a idle (ANTES del commit para incluirlo en el cierre)
174
179
  if (fs.existsSync(lockfilePath)) {
175
180
  try {
@@ -264,20 +269,13 @@ Este reporte detalla la telemetría de tokens y coste financiero en USD acumulad
264
269
  if (fs.existsSync(path.join(projectRoot, ".git"))) {
265
270
  try {
266
271
  execSync("git add .", { cwd: projectRoot, stdio: "ignore" });
267
- execSync(`git commit -F "${tempCommitMsgPath}"`, { cwd: projectRoot, stdio: "ignore" });
272
+ execSync("git commit -F -", { cwd: projectRoot, input: args.commitMessage + "\n", stdio: ["pipe", "ignore", "ignore"] });
268
273
  report.push(`✓ Commit de Git ejecutado usando el mensaje semántico (incluye archivos archivados)`);
269
274
  }
270
275
  catch (e) {
271
276
  report.push(`⚠️ Git Commit falló o no había cambios pendientes de código: ${e.message}`);
272
277
  }
273
278
  }
274
- // 8. Limpiar archivo temporal de commit message
275
- try {
276
- if (fs.existsSync(tempCommitMsgPath)) {
277
- fs.unlinkSync(tempCommitMsgPath);
278
- }
279
- }
280
- catch (e) { }
281
279
  report.push("━━━ finalizado con éxito absoluto ━━━");
282
280
  return report.join("\n");
283
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot-sdd",
3
- "version": "1.5.34",
3
+ "version": "1.5.36",
4
4
  "description": "Zugzbot SDD Swarm - Spec-Driven Development Harness for OpenCode",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -43,7 +43,8 @@ export default tool({
43
43
  category: tool.schema.string().optional().describe("Categoría del aprendizaje para el cerebro"),
44
44
  tag: tool.schema.string().optional().describe("Tag corto del aprendizaje para el cerebro"),
45
45
  problem: tool.schema.string().optional().describe("Problema resuelto (máx 120 caracteres)"),
46
- solution: tool.schema.string().optional().describe("Solución aplicada (máx 300 caracteres)")
46
+ solution: tool.schema.string().optional().describe("Solución aplicada (máx 300 caracteres)"),
47
+ bypassPendingTasks: tool.schema.boolean().optional().default(false).describe("Ignorar/Bypassear la verificación de tareas pendientes en el lockfile si el usuario aprobó manualmente o es un flujo QA Manual")
47
48
  },
48
49
  async execute(args, context) {
49
50
  const projectRoot = context.worktree || context.directory
@@ -58,11 +59,22 @@ export default tool({
58
59
  if (fs.existsSync(lockfilePath)) {
59
60
  try {
60
61
  const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"))
62
+ const isManualQa = lockfile.qa_manual === true || lockfile.manual_qa === true
63
+ const shouldBypass = args.bypassPendingTasks === true || isManualQa
64
+
61
65
  if (lockfile.tasks && Array.isArray(lockfile.tasks) && lockfile.tasks.length > 0) {
62
66
  const pendingTasks = lockfile.tasks.filter((t: any) => t.status === "pending")
63
67
  if (pendingTasks.length > 0) {
64
- const pendingList = pendingTasks.map((t: any) => ` ⚠️ [${t.id}] ${t.desc}`).join("\n")
65
- return `[SDD Archive Blocked] No se puede cerrar el ciclo. Hay ${pendingTasks.length} tarea(s) pendiente(s):\n${pendingList}\n\nPor favor, completa todas las tareas o fuerza el cierre con aprobación explícita del usuario.`
68
+ if (shouldBypass) {
69
+ // Auto-completar tareas en caliente para dejar el lockfile limpio
70
+ lockfile.tasks.forEach((t: any) => {
71
+ if (t.status === "pending") t.status = "completed"
72
+ })
73
+ fs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2), "utf-8")
74
+ } else {
75
+ const pendingList = pendingTasks.map((t: any) => ` ⚠️ [${t.id}] ${t.desc}`).join("\n")
76
+ return `[SDD Archive Blocked] No se puede cerrar el ciclo. Hay ${pendingTasks.length} tarea(s) pendiente(s):\n${pendingList}\n\nPor favor, completa todas las tareas o fuerza el cierre con aprobación explícita del usuario.`
77
+ }
66
78
  }
67
79
  }
68
80
  } catch (e: any) {}
@@ -160,14 +172,7 @@ export default tool({
160
172
  report.push(`⚠️ Sincronización automática de habilidades fallida o no disponible: ${e.message || e}`)
161
173
  }
162
174
 
163
- // 4. Escribir commit_message.txt en location TEMPORAL antes de archivar
164
- const tempCommitMsgPath = path.join(projectRoot, ".openspec", `commit_msg_${args.changeName}.txt`)
165
- try {
166
- fs.writeFileSync(tempCommitMsgPath, args.commitMessage + "\n", "utf-8")
167
- report.push(`✓ Archivo commit_message.txt generado en location temporal`)
168
- } catch (e: any) {
169
- report.push(`⚠️ Error escribiendo commit_message.txt: ${e.message}`)
170
- }
175
+ // 4. No temporary file is needed on disk, we feed it directly to git commit stdin in step 7.
171
176
 
172
177
  // 5. Resetear el lockfile a idle (ANTES del commit para incluirlo en el cierre)
173
178
  if (fs.existsSync(lockfilePath)) {
@@ -265,20 +270,13 @@ Este reporte detalla la telemetría de tokens y coste financiero en USD acumulad
265
270
  if (fs.existsSync(path.join(projectRoot, ".git"))) {
266
271
  try {
267
272
  execSync("git add .", { cwd: projectRoot, stdio: "ignore" })
268
- execSync(`git commit -F "${tempCommitMsgPath}"`, { cwd: projectRoot, stdio: "ignore" })
273
+ execSync("git commit -F -", { cwd: projectRoot, input: args.commitMessage + "\n", stdio: ["pipe", "ignore", "ignore"] })
269
274
  report.push(`✓ Commit de Git ejecutado usando el mensaje semántico (incluye archivos archivados)`)
270
275
  } catch (e: any) {
271
276
  report.push(`⚠️ Git Commit falló o no había cambios pendientes de código: ${e.message}`)
272
277
  }
273
278
  }
274
279
 
275
- // 8. Limpiar archivo temporal de commit message
276
- try {
277
- if (fs.existsSync(tempCommitMsgPath)) {
278
- fs.unlinkSync(tempCommitMsgPath)
279
- }
280
- } catch (e: any) {}
281
-
282
280
  report.push("━━━ finalizado con éxito absoluto ━━━")
283
281
  return report.join("\n")
284
282
  }