@codexa/cli 9.0.31 → 9.0.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.
package/commands/task.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getDb } from "../db/connection";
1
+ import { dbGet, dbAll, dbRun } from "../db/connection";
2
2
  import { initSchema, getPatternsForFiles, getPatternsByScope, getRecentReasoning, claimTask, recordAgentPerformance, detectStuckTasks } from "../db/schema";
3
3
  import { enforceGate } from "../gates/validator";
4
4
  import { parseSubagentReturn, formatValidationErrors } from "../protocol/subagent-protocol";
@@ -16,10 +16,9 @@ import { getContextBudget, formatContextWarning, estimateTokens } from "../conte
16
16
  import { isAutoSimplifyEnabled } from "./simplify";
17
17
  import { buildSimplifyPrompt, writeSimplifyContext, type SimplifyFile } from "../simplify/prompt-builder";
18
18
 
19
- export function taskNext(json: boolean = false, specId?: string): void {
20
- initSchema();
19
+ export async function taskNext(json: boolean = false, specId?: string): Promise<void> {
20
+ await initSchema();
21
21
 
22
- const db = getDb();
23
22
  const spec = resolveSpecOrNull(specId, ["implementing"]);
24
23
 
25
24
  if (!spec) {
@@ -31,14 +30,12 @@ export function taskNext(json: boolean = false, specId?: string): void {
31
30
  }
32
31
 
33
32
  // v9.9: Get current phase info
34
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
33
+ const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
35
34
  const currentPhase = context?.current_phase || 1;
36
35
  const totalPhases = context?.total_phases || 1;
37
36
 
38
37
  // Buscar tasks pendentes cujas dependencias estao todas concluidas
39
- const allTasks = db
40
- .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
41
- .all(spec.id) as any[];
38
+ const allTasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number", [spec.id]);
42
39
 
43
40
  const available: any[] = [];
44
41
 
@@ -86,7 +83,7 @@ export function taskNext(json: boolean = false, specId?: string): void {
86
83
  } else {
87
84
  console.log("\nNenhuma task disponivel no momento.");
88
85
  console.log(`Tasks em execucao ou bloqueadas: ${pending.length}`);
89
- const stuck = detectStuckTasks(spec.id);
86
+ const stuck = await detectStuckTasks(spec.id);
90
87
  if (stuck.length > 0) {
91
88
  showStuckWarning(stuck);
92
89
  } else {
@@ -150,11 +147,10 @@ function showStuckWarning(stuck: any[]): void {
150
147
  console.log(` Use: task done <id> --force --force-reason "timeout" para liberar\n`);
151
148
  }
152
149
 
153
- export function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): void {
154
- initSchema();
150
+ export async function taskStart(ids: string, json: boolean = false, minimalContext: boolean = false, specId?: string): Promise<void> {
151
+ await initSchema();
155
152
  enforceGate("task-start");
156
153
 
157
- const db = getDb();
158
154
  const now = new Date().toISOString();
159
155
 
160
156
  const spec = resolveSpec(specId, ["implementing"]);
@@ -163,7 +159,7 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
163
159
  const startedTasks: any[] = [];
164
160
 
165
161
  for (const taskId of taskIds) {
166
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
162
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
167
163
 
168
164
  if (!task) {
169
165
  throw new TaskStateError(`Task #${taskId} nao encontrada.`);
@@ -173,8 +169,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
173
169
  enforceGate("task-start", { taskId });
174
170
 
175
171
  // Claim atomico: UPDATE ... WHERE status = 'pending'
176
- if (!claimTask(taskId)) {
177
- const current = db.query("SELECT status FROM tasks WHERE id = ?").get(taskId) as any;
172
+ if (!(await claimTask(taskId))) {
173
+ const current = await dbGet<any>("SELECT status FROM tasks WHERE id = ?", [taskId]);
178
174
  throw new TaskStateError(
179
175
  `Task #${task.number} nao pode ser iniciada (status atual: ${current?.status || "desconhecido"}).`
180
176
  );
@@ -184,19 +180,20 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
184
180
  }
185
181
 
186
182
  // Atualizar contexto
187
- db.run("UPDATE context SET current_task = ?, updated_at = ? WHERE spec_id = ?", [
183
+ await dbRun("UPDATE context SET current_task = ?, updated_at = ? WHERE spec_id = ?", [
188
184
  startedTasks[0].number,
189
185
  now,
190
186
  spec.id,
191
187
  ]);
192
188
 
193
189
  // v9.0: Mostrar resumo de knowledge relevante antes de iniciar
194
- const allKnowledge = db.query(
190
+ const allKnowledge = await dbAll<any>(
195
191
  `SELECT * FROM knowledge
196
192
  WHERE spec_id = ? AND severity IN ('critical', 'warning')
197
193
  ORDER BY severity DESC, created_at DESC
198
- LIMIT 10`
199
- ).all(spec.id) as any[];
194
+ LIMIT 10`,
195
+ [spec.id]
196
+ );
200
197
 
201
198
  if (allKnowledge.length > 0 && !json) {
202
199
  console.log(`\n[i] Knowledge ativo (${allKnowledge.length}):`);
@@ -211,7 +208,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
211
208
  }
212
209
 
213
210
  if (json) {
214
- const contexts = startedTasks.map((task) => {
211
+ const contexts: any[] = [];
212
+ for (const task of startedTasks) {
215
213
  const taskFiles = task.files ? JSON.parse(task.files) : [];
216
214
  const agentDomain = getAgentDomain(task.agent);
217
215
  const agentScope = domainToScope(agentDomain);
@@ -233,16 +231,15 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
233
231
 
234
232
  // v11.0: Inline context delivery — full context in prompt, no files
235
233
  const inlineContext = minimalContext
236
- ? getMinimalContextForSubagent(task.id)
237
- : getContextForSubagent(task.id);
234
+ ? await getMinimalContextForSubagent(task.id)
235
+ : await getContextForSubagent(task.id);
238
236
 
239
237
  // Build pending knowledge section
240
- const pendingKnowledgeForPrompt = (() => {
241
- const unread = getUnreadKnowledgeForTask(spec.id, task.id);
242
- const critical = unread.filter((k: any) => k.severity === 'critical' && k.task_origin !== task.id);
243
- if (critical.length === 0) return "";
244
- return `## KNOWLEDGE PENDENTE (OBRIGATORIO)\n\nVoce DEVE reconhecer estes items antes de completar a task:\n${critical.map((k: any) => `- [ID ${k.id}] ${k.content}`).join("\n")}\n\nUse: \`codexa knowledge ack <id>\` para cada item.`;
245
- })();
238
+ const unreadForPrompt = await getUnreadKnowledgeForTask(spec.id, task.id);
239
+ const criticalForPrompt = unreadForPrompt.filter((k: any) => k.severity === 'critical' && k.task_origin !== task.id);
240
+ const pendingKnowledgeForPrompt = criticalForPrompt.length === 0
241
+ ? ""
242
+ : `## KNOWLEDGE PENDENTE (OBRIGATORIO)\n\nVoce DEVE reconhecer estes items antes de completar a task:\n${criticalForPrompt.map((k: any) => `- [ID ${k.id}] ${k.content}`).join("\n")}\n\nUse: \`codexa knowledge ack <id>\` para cada item.`;
246
243
 
247
244
  // Pre-built prompt for subagent (orchestrator just passes it)
248
245
  const subagentPrompt = loadTemplate("subagent-prompt-lean", {
@@ -258,9 +255,11 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
258
255
  });
259
256
 
260
257
  // Context budget estimation
261
- const completedTaskCount = (db.query(
262
- "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'"
263
- ).get(spec.id) as any)?.c || 0;
258
+ const completedTaskRow = await dbGet<any>(
259
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'",
260
+ [spec.id]
261
+ );
262
+ const completedTaskCount = completedTaskRow?.c || 0;
264
263
 
265
264
  const agentProfile = getProfileForDomain(agentDomain);
266
265
  const contextBudget = getContextBudget(subagentPrompt, completedTaskCount, agentProfile);
@@ -286,7 +285,7 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
286
285
  };
287
286
 
288
287
  // Unread knowledge for orchestrator awareness
289
- const unreadKnowledge = getUnreadKnowledgeForTask(spec.id, task.id);
288
+ const unreadKnowledge = await getUnreadKnowledgeForTask(spec.id, task.id);
290
289
  if (unreadKnowledge.length > 0) {
291
290
  output.unreadKnowledge = unreadKnowledge.map((k: any) => ({
292
291
  id: k.id, category: k.category, content: k.content,
@@ -294,8 +293,8 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
294
293
  }));
295
294
  }
296
295
 
297
- return output;
298
- });
296
+ contexts.push(output);
297
+ }
299
298
  console.log(JSON.stringify({ started: contexts }));
300
299
  return;
301
300
  }
@@ -307,18 +306,17 @@ export function taskStart(ids: string, json: boolean = false, minimalContext: bo
307
306
  console.log(`\nAo concluir, use: task done <id> --checkpoint "resumo"\n`);
308
307
  }
309
308
 
310
- export function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string; simplify?: boolean }): void {
311
- initSchema();
309
+ export async function taskDone(id: string, options: { checkpoint: string; files?: string; force?: boolean; forceReason?: string; output?: string; simplify?: boolean }): Promise<void> {
310
+ await initSchema();
312
311
 
313
312
  const taskId = parseInt(id);
314
- const db = getDb();
315
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(taskId) as any;
313
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [taskId]);
316
314
 
317
315
  if (!task) {
318
316
  throw new TaskStateError(`Task #${taskId} nao encontrada.`);
319
317
  }
320
318
 
321
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(task.spec_id) as any;
319
+ const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [task.spec_id]);
322
320
  const now = new Date().toISOString();
323
321
 
324
322
  // ============================================================
@@ -397,7 +395,8 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
397
395
  console.log(formatProcessResult(processResult));
398
396
 
399
397
  // v8.3: BLOCKING check para knowledge critico nao reconhecido (substitui warning v8.2)
400
- const unackedCritical = getUnreadKnowledgeForTask(spec.id, taskId)
398
+ const allUnackedKnowledge = await getUnreadKnowledgeForTask(spec.id, taskId);
399
+ const unackedCritical = allUnackedKnowledge
401
400
  .filter((k: any) => k.severity === 'critical' && k.task_origin !== taskId);
402
401
  if (unackedCritical.length > 0) {
403
402
  if (!options.force) {
@@ -479,7 +478,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
479
478
  }
480
479
 
481
480
  // Marcar como done
482
- db.run(
481
+ await dbRun(
483
482
  "UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
484
483
  [checkpoint, now, taskId]
485
484
  );
@@ -489,12 +488,14 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
489
488
  const agentType = task.agent || "general-purpose";
490
489
  const startedAt = task.started_at ? new Date(task.started_at).getTime() : Date.now();
491
490
  const duration = Date.now() - startedAt;
492
- const bypassCount = (db.query(
493
- "SELECT COUNT(*) as c FROM gate_bypasses WHERE task_id = ?"
494
- ).get(taskId) as any)?.c || 0;
491
+ const bypassRow = await dbGet<any>(
492
+ "SELECT COUNT(*) as c FROM gate_bypasses WHERE task_id = ?",
493
+ [taskId]
494
+ );
495
+ const bypassCount = bypassRow?.c || 0;
495
496
  const totalGates = 8;
496
497
 
497
- recordAgentPerformance({
498
+ await recordAgentPerformance({
498
499
  agentType,
499
500
  specId: spec.id,
500
501
  taskId,
@@ -503,7 +504,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
503
504
  bypassesUsed: bypassCount,
504
505
  filesCreated: subagentData?.files_created?.length || 0,
505
506
  filesModified: subagentData?.files_modified?.length || 0,
506
- contextSizeBytes: estimateTokens(getContextForSubagent(task.id)) * 4,
507
+ contextSizeBytes: estimateTokens(await getContextForSubagent(task.id)) * 4,
507
508
  executionDurationMs: duration,
508
509
  });
509
510
  } catch { /* nao-critico: nao falhar task done por tracking de performance */ }
@@ -512,7 +513,7 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
512
513
  if (!subagentData && options.files) {
513
514
  const files = options.files.split(",").map((s) => s.trim());
514
515
  for (const file of files) {
515
- db.run(
516
+ await dbRun(
516
517
  `INSERT OR REPLACE INTO artifacts (spec_id, task_ref, path, action, created_at)
517
518
  VALUES (?, ?, ?, 'created', ?)`,
518
519
  [spec.id, task.number, file, now]
@@ -521,52 +522,58 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
521
522
  }
522
523
 
523
524
  // Atualizar contexto
524
- const doneCount = db
525
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'")
526
- .get(spec.id) as any;
527
- const totalCount = db
528
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?")
529
- .get(spec.id) as any;
530
-
531
- db.run(
525
+ const doneCount = await dbGet<any>(
526
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'",
527
+ [spec.id]
528
+ );
529
+ const totalCount = await dbGet<any>(
530
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?",
531
+ [spec.id]
532
+ );
533
+
534
+ await dbRun(
532
535
  "UPDATE context SET last_checkpoint = ?, updated_at = ? WHERE spec_id = ?",
533
536
  [checkpoint, now, spec.id]
534
537
  );
535
538
 
536
539
  // v9.9: Phase-aware progress
537
- const ctx = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
540
+ const ctx = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
538
541
  const currentPhase = ctx?.current_phase || 1;
539
542
  const totalPhases = ctx?.total_phases || 1;
540
543
 
541
544
  console.log(`\nTask #${task.number} concluida!`);
542
545
  console.log(`Checkpoint: ${options.checkpoint}`);
543
- console.log(`Progresso: ${doneCount.c}/${totalCount.c} tasks`);
546
+ console.log(`Progresso: ${doneCount?.c || 0}/${totalCount?.c || 0} tasks`);
544
547
 
545
548
  if (totalPhases > 1) {
546
- const phaseTasks = db
547
- .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ?")
548
- .all(spec.id, currentPhase) as any[];
549
+ const phaseTasks = await dbAll<any>(
550
+ "SELECT * FROM tasks WHERE spec_id = ? AND phase = ?",
551
+ [spec.id, currentPhase]
552
+ );
549
553
  const phaseDone = phaseTasks.filter((t: any) => t.status === "done").length;
550
554
  console.log(`Fase ${currentPhase}/${totalPhases}: ${phaseDone}/${phaseTasks.length} tasks`);
551
555
 
552
556
  if (phaseDone === phaseTasks.length && currentPhase < totalPhases) {
553
557
  // Phase complete — show phase summary
554
- const phaseDecisions = db
555
- .query("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?")
556
- .get(spec.id) as any;
557
- const phaseKnowledge = db
558
- .query("SELECT COUNT(*) as c FROM knowledge WHERE spec_id = ? AND severity != 'archived'")
559
- .get(spec.id) as any;
558
+ const phaseDecisions = await dbGet<any>(
559
+ "SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?",
560
+ [spec.id]
561
+ );
562
+ const phaseKnowledge = await dbGet<any>(
563
+ "SELECT COUNT(*) as c FROM knowledge WHERE spec_id = ? AND severity != 'archived'",
564
+ [spec.id]
565
+ );
560
566
 
561
- const nextPhaseTasks = db
562
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND phase = ?")
563
- .get(spec.id, currentPhase + 1) as any;
567
+ const nextPhaseTasks = await dbGet<any>(
568
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND phase = ?",
569
+ [spec.id, currentPhase + 1]
570
+ );
564
571
 
565
572
  console.log(`\n${"=".repeat(55)}`);
566
573
  console.log(`FASE ${currentPhase} CONCLUIDA (${phaseDone}/${phaseTasks.length} tasks)`);
567
574
  console.log(`${"=".repeat(55)}`);
568
- console.log(` Decisions acumuladas: ${phaseDecisions.c}`);
569
- console.log(` Knowledge acumulado: ${phaseKnowledge.c}`);
575
+ console.log(` Decisions acumuladas: ${phaseDecisions?.c || 0}`);
576
+ console.log(` Knowledge acumulado: ${phaseKnowledge?.c || 0}`);
570
577
  console.log(`\n Proxima fase: ${currentPhase + 1}/${totalPhases} (${nextPhaseTasks?.c || 0} tasks)`);
571
578
  console.log(`\n Recomendacao: Compactar contexto antes de continuar`);
572
579
  console.log(` -> codexa task phase-advance [--no-compact] [--spec ${spec.id}]`);
@@ -575,12 +582,12 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
575
582
  }
576
583
  }
577
584
 
578
- if (doneCount.c === totalCount.c) {
585
+ if (doneCount?.c === totalCount?.c) {
579
586
  // Mostrar resumo completo da implementacao
580
- const allTasks = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(spec.id) as any[];
581
- const allDecisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
582
- const allArtifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
583
- showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
587
+ const allTasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ?", [spec.id]);
588
+ const allDecisions = await dbAll<any>("SELECT * FROM decisions WHERE spec_id = ?", [spec.id]);
589
+ const allArtifacts = await dbAll<any>("SELECT * FROM artifacts WHERE spec_id = ?", [spec.id]);
590
+ await showImplementationSummary(spec.id, allTasks, allArtifacts, allDecisions);
584
591
  } else {
585
592
  console.log(`\nProximas tasks: task next\n`);
586
593
  }
@@ -590,15 +597,14 @@ export function taskDone(id: string, options: { checkpoint: string; files?: stri
590
597
  * Mostra resumo detalhado da implementacao apos todas as tasks concluidas
591
598
  * Permite ao usuario decidir se quer fazer review ou nao
592
599
  */
593
- function showImplementationSummary(
600
+ async function showImplementationSummary(
594
601
  specId: number,
595
602
  tasks: any[],
596
603
  artifacts: any[],
597
604
  decisions: any[]
598
- ): void {
599
- const db = getDb();
600
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(specId) as any;
601
- const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(specId) as any[];
605
+ ): Promise<void> {
606
+ const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [specId]);
607
+ const knowledge = await dbAll<any>("SELECT * FROM knowledge WHERE spec_id = ?", [specId]);
602
608
 
603
609
  console.log(`\n${"=".repeat(60)}`);
604
610
  console.log(`IMPLEMENTACAO CONCLUIDA: ${spec.name}`);
@@ -694,12 +700,11 @@ function showImplementationSummary(
694
700
  }
695
701
 
696
702
  // v9.9: Phase advance — compact context and move to next phase
697
- export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean; spec?: string } = {}): void {
698
- initSchema();
699
- const db = getDb();
703
+ export async function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean; spec?: string } = {}): Promise<void> {
704
+ await initSchema();
700
705
 
701
706
  const spec = resolveSpec(options.spec, ["implementing"]);
702
- const ctx = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
707
+ const ctx = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
703
708
  const currentPhase = ctx?.current_phase || 1;
704
709
  const totalPhases = ctx?.total_phases || 1;
705
710
 
@@ -715,9 +720,10 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
715
720
  }
716
721
 
717
722
  // Verify current phase is fully complete
718
- const phaseTasks = db
719
- .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ?")
720
- .all(spec.id, currentPhase) as any[];
723
+ const phaseTasks = await dbAll<any>(
724
+ "SELECT * FROM tasks WHERE spec_id = ? AND phase = ?",
725
+ [spec.id, currentPhase]
726
+ );
721
727
  const pendingInPhase = phaseTasks.filter((t: any) => t.status !== "done" && t.status !== "cancelled");
722
728
 
723
729
  if (pendingInPhase.length > 0) {
@@ -732,8 +738,8 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
732
738
  // Compact knowledge, reasoning, and clean up stale files (unless --no-compact)
733
739
  if (!options.noCompact) {
734
740
  console.log(`\nCompactando contexto da fase ${currentPhase}...`);
735
- compactKnowledge({ specId: spec.id });
736
- const reasoningResult = compactReasoning({ specId: spec.id });
741
+ await compactKnowledge({ specId: spec.id });
742
+ const reasoningResult = await compactReasoning({ specId: spec.id });
737
743
  if (reasoningResult.archived > 0) {
738
744
  console.log(` Reasoning compactado: ${reasoningResult.archived} entradas removidas, ${reasoningResult.kept} mantidas`);
739
745
  }
@@ -741,12 +747,14 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
741
747
 
742
748
  // Create phase summary as critical knowledge
743
749
  const phaseTaskNames = phaseTasks.map((t: any) => t.name).join(", ");
744
- const decisionCount = (db.query("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?").get(spec.id) as any).c;
745
- const artifactCount = (db.query("SELECT COUNT(*) as c FROM artifacts WHERE spec_id = ?").get(spec.id) as any).c;
750
+ const decisionCountRow = await dbGet<any>("SELECT COUNT(*) as c FROM decisions WHERE spec_id = ?", [spec.id]);
751
+ const artifactCountRow = await dbGet<any>("SELECT COUNT(*) as c FROM artifacts WHERE spec_id = ?", [spec.id]);
752
+ const decisionCount = decisionCountRow?.c || 0;
753
+ const artifactCount = artifactCountRow?.c || 0;
746
754
 
747
755
  const summaryContent = `Resumo Fase ${currentPhase}: ${phaseTasks.length} tasks concluidas (${phaseTaskNames.substring(0, 200)}). ${decisionCount} decisions, ${artifactCount} artefatos acumulados.`;
748
756
 
749
- db.run(
757
+ await dbRun(
750
758
  `INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
751
759
  VALUES (?, 0, 'phase_summary', ?, 'critical', 'all', ?)`,
752
760
  [spec.id, summaryContent, now]
@@ -754,15 +762,16 @@ export function taskPhaseAdvance(options: { noCompact?: boolean; json?: boolean;
754
762
 
755
763
  // Advance to next phase
756
764
  const nextPhase = currentPhase + 1;
757
- db.run(
765
+ await dbRun(
758
766
  "UPDATE context SET current_phase = ?, updated_at = ? WHERE spec_id = ?",
759
767
  [nextPhase, now, spec.id]
760
768
  );
761
769
 
762
770
  // Show next phase info
763
- const nextPhaseTasks = db
764
- .query("SELECT * FROM tasks WHERE spec_id = ? AND phase = ? ORDER BY number")
765
- .all(spec.id, nextPhase) as any[];
771
+ const nextPhaseTasks = await dbAll<any>(
772
+ "SELECT * FROM tasks WHERE spec_id = ? AND phase = ? ORDER BY number",
773
+ [spec.id, nextPhase]
774
+ );
766
775
 
767
776
  if (options.json) {
768
777
  console.log(JSON.stringify({
@@ -1,5 +1,7 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { Database } from "bun:sqlite";
2
+ import { createClient } from "@libsql/client";
3
+ import { setClient, resetClient, dbRun } from "../db/connection";
4
+ import { initSchema } from "../db/schema";
3
5
  import {
4
6
  detectPhase,
5
7
  buildLeadSpawnInstruction,
@@ -10,103 +12,40 @@ import {
10
12
  type TeammateConfig,
11
13
  } from "./team";
12
14
 
13
- // ─────────────────────────────────────────────────────────────────
14
- // Helpers
15
- // ─────────────────────────────────────────────────────────────────
16
-
17
- function createTestDb(): Database {
18
- const db = new Database(":memory:");
19
-
20
- db.run(`CREATE TABLE specs (
21
- id TEXT PRIMARY KEY, name TEXT, phase TEXT, approved_at TEXT,
22
- analysis_id TEXT, created_at TEXT, updated_at TEXT
23
- )`);
24
-
25
- db.run(`CREATE TABLE tasks (
26
- id INTEGER PRIMARY KEY AUTOINCREMENT, spec_id TEXT, number INTEGER,
27
- name TEXT, depends_on TEXT, can_parallel INTEGER DEFAULT 1,
28
- agent TEXT, files TEXT, status TEXT DEFAULT 'pending',
29
- checkpoint TEXT, started_at TEXT, completed_at TEXT
30
- )`);
31
-
32
- db.run(`CREATE TABLE artifacts (
33
- id INTEGER PRIMARY KEY AUTOINCREMENT, spec_id TEXT,
34
- task_ref INTEGER, path TEXT, action TEXT, created_at TEXT
35
- )`);
36
-
37
- db.run(`CREATE TABLE architectural_analyses (
38
- id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, status TEXT DEFAULT 'pending'
39
- )`);
40
-
41
- db.run(`CREATE TABLE project (
42
- id TEXT PRIMARY KEY DEFAULT 'default', stack TEXT,
43
- cli_version TEXT, typecheck_command TEXT, grepai_workspace TEXT
44
- )`);
45
-
46
- db.run("INSERT INTO project (id) VALUES ('default')");
47
-
48
- return db;
49
- }
50
-
51
- function insertSpec(db: Database, id: string, name: string, phase: string): void {
52
- db.run("INSERT INTO specs (id, name, phase, created_at) VALUES (?, ?, ?, datetime('now'))", [id, name, phase]);
53
- }
54
-
55
- function insertTask(
56
- db: Database,
57
- specId: string,
58
- number: number,
59
- name: string,
60
- options: { agent?: string; canParallel?: boolean; dependsOn?: number[]; status?: string } = {}
61
- ): number {
62
- const result = db.run(
63
- `INSERT INTO tasks (spec_id, number, name, agent, can_parallel, depends_on, status)
64
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
65
- [
66
- specId,
67
- number,
68
- name,
69
- options.agent || "general",
70
- options.canParallel !== false ? 1 : 0,
71
- options.dependsOn ? JSON.stringify(options.dependsOn) : null,
72
- options.status || "pending",
73
- ]
74
- );
75
- return Number(result.lastInsertRowid);
76
- }
77
-
78
- function insertArtifact(db: Database, specId: string, taskRef: number, path: string): void {
79
- db.run("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, ?, ?, 'created')", [specId, taskRef, path]);
80
- }
81
-
82
15
  // ─────────────────────────────────────────────────────────────────
83
16
  // Tests
84
17
  // ─────────────────────────────────────────────────────────────────
85
18
 
86
19
  describe("detectPhase", () => {
87
- it("detecta imp quando spec.phase = implementing", () => {
88
- const db = createTestDb();
20
+ beforeEach(async () => {
21
+ const client = createClient({ url: ":memory:" });
22
+ setClient(client);
23
+ await initSchema();
24
+ });
25
+
26
+ afterEach(() => {
27
+ resetClient();
28
+ });
29
+
30
+ it("detecta imp quando spec.phase = implementing", async () => {
89
31
  const spec = { phase: "implementing" };
90
- expect(detectPhase(db, spec)).toBe("imp");
32
+ expect(await detectPhase(spec)).toBe("imp");
91
33
  });
92
34
 
93
- it("detecta rev quando spec.phase = reviewing", () => {
94
- const db = createTestDb();
35
+ it("detecta rev quando spec.phase = reviewing", async () => {
95
36
  const spec = { phase: "reviewing" };
96
- expect(detectPhase(db, spec)).toBe("rev");
37
+ expect(await detectPhase(spec)).toBe("rev");
97
38
  });
98
39
 
99
- it("detecta architect quando ha analise pendente", () => {
100
- const db = createTestDb();
101
- db.run("INSERT INTO architectural_analyses (name, status) VALUES ('test', 'pending')");
40
+ it("detecta architect quando ha analise pendente", async () => {
41
+ await dbRun("INSERT INTO architectural_analyses (id, name, description, status) VALUES ('test-id', 'test', 'test description', 'pending')");
102
42
  const spec = { phase: "planning" };
103
- expect(detectPhase(db, spec)).toBe("architect");
43
+ expect(await detectPhase(spec)).toBe("architect");
104
44
  });
105
45
 
106
- it("retorna phase original se nao ha analise pendente", () => {
107
- const db = createTestDb();
46
+ it("retorna phase original se nao ha analise pendente", async () => {
108
47
  const spec = { phase: "planning" };
109
- expect(detectPhase(db, spec)).toBe("planning");
48
+ expect(await detectPhase(spec)).toBe("planning");
110
49
  });
111
50
  });
112
51