@codexa/cli 9.0.30 → 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.
@@ -1,4 +1,4 @@
1
- import { getDb } from "../db/connection";
1
+ import { dbGet, dbAll, dbRun } from "../db/connection";
2
2
  import { initSchema, getArchitecturalAnalysisForSpec } from "../db/schema";
3
3
  import { enforceGate } from "../gates/validator";
4
4
  import { resolveSpec } from "./spec-resolver";
@@ -22,18 +22,22 @@ export interface ReviewScore {
22
22
  mustReviewItems: string[]; // Items que exigem atencao humana
23
23
  }
24
24
 
25
- export function calculateReviewScore(specId: string): ReviewScore {
26
- const db = getDb();
27
-
25
+ export async function calculateReviewScore(specId: string): Promise<ReviewScore> {
28
26
  const mustReviewItems: string[] = [];
29
27
 
30
28
  // 1. Tasks: (completed / total) * 25
31
- const totalTasks = (db.query(
32
- "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?"
33
- ).get(specId) as any).c;
34
- const completedTasks = (db.query(
35
- "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'"
36
- ).get(specId) as any).c;
29
+ const totalTasksRow = await dbGet<any>(
30
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?",
31
+ [specId]
32
+ );
33
+ const totalTasks = totalTasksRow?.c ?? 0;
34
+
35
+ const completedTasksRow = await dbGet<any>(
36
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'",
37
+ [specId]
38
+ );
39
+ const completedTasks = completedTasksRow?.c ?? 0;
40
+
37
41
  const tasksCompleted = totalTasks > 0
38
42
  ? Math.round((completedTasks / totalTasks) * 25)
39
43
  : 25;
@@ -45,26 +49,30 @@ export function calculateReviewScore(specId: string): ReviewScore {
45
49
  // 2. Gates: (clean passes / total gate events) * 25
46
50
  // Total gate events = tasks * 7 gates per task
47
51
  const totalGateEvents = totalTasks * 7;
48
- const bypassCount = (db.query(
49
- "SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ?"
50
- ).get(specId) as any).c;
52
+ const bypassCountRow = await dbGet<any>(
53
+ "SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ?",
54
+ [specId]
55
+ );
56
+ const bypassCount = bypassCountRow?.c ?? 0;
51
57
  const cleanGateEvents = Math.max(0, totalGateEvents - bypassCount);
52
58
  const gatesPassedClean = totalGateEvents > 0
53
59
  ? Math.round((cleanGateEvents / totalGateEvents) * 25)
54
60
  : 25;
55
61
 
56
62
  // Check for critical bypasses
57
- const criticalBypasses = db.query(
58
- "SELECT * FROM gate_bypasses WHERE spec_id = ? AND gate_name IN ('standards-follow', 'dry-check', 'typecheck-pass')"
59
- ).all(specId) as any[];
63
+ const criticalBypasses = await dbAll<any>(
64
+ "SELECT * FROM gate_bypasses WHERE spec_id = ? AND gate_name IN ('standards-follow', 'dry-check', 'typecheck-pass')",
65
+ [specId]
66
+ );
60
67
  if (criticalBypasses.length > 0) {
61
68
  mustReviewItems.push(`${criticalBypasses.length} bypass(es) de gates criticos`);
62
69
  }
63
70
 
64
71
  // 3. Files: (delivered / planned) * 25
65
- const plannedFiles = db.query(
66
- "SELECT files FROM tasks WHERE spec_id = ? AND files IS NOT NULL"
67
- ).all(specId) as any[];
72
+ const plannedFiles = await dbAll<any>(
73
+ "SELECT files FROM tasks WHERE spec_id = ? AND files IS NOT NULL",
74
+ [specId]
75
+ );
68
76
  const allPlannedFiles = new Set<string>();
69
77
  for (const t of plannedFiles) {
70
78
  try {
@@ -73,11 +81,11 @@ export function calculateReviewScore(specId: string): ReviewScore {
73
81
  } catch { /* ignore */ }
74
82
  }
75
83
 
76
- const deliveredFiles = new Set(
77
- (db.query(
78
- "SELECT DISTINCT path FROM artifacts WHERE spec_id = ?"
79
- ).all(specId) as any[]).map(a => a.path)
84
+ const deliveredArtifacts = await dbAll<any>(
85
+ "SELECT DISTINCT path FROM artifacts WHERE spec_id = ?",
86
+ [specId]
80
87
  );
88
+ const deliveredFiles = new Set(deliveredArtifacts.map(a => a.path));
81
89
 
82
90
  let filesDelivered: number;
83
91
  if (allPlannedFiles.size === 0) {
@@ -97,9 +105,11 @@ export function calculateReviewScore(specId: string): ReviewScore {
97
105
 
98
106
  // 4. Standards: (followed / total) * 25
99
107
  // Inverse of standards-follow bypasses relative to total tasks
100
- const standardsBypasses = (db.query(
101
- "SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ? AND gate_name = 'standards-follow'"
102
- ).get(specId) as any).c;
108
+ const standardsBypassesRow = await dbGet<any>(
109
+ "SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ? AND gate_name = 'standards-follow'",
110
+ [specId]
111
+ );
112
+ const standardsBypasses = standardsBypassesRow?.c ?? 0;
103
113
  const standardsFollowed = totalTasks > 0
104
114
  ? Math.round(((totalTasks - standardsBypasses) / totalTasks) * 25)
105
115
  : 25;
@@ -124,32 +134,35 @@ export function calculateReviewScore(specId: string): ReviewScore {
124
134
  };
125
135
  }
126
136
 
127
- export function reviewStart(json: boolean = false, specId?: string): void {
128
- initSchema();
137
+ export async function reviewStart(json: boolean = false, specId?: string): Promise<void> {
138
+ await initSchema();
129
139
  enforceGate("review-start");
130
140
 
131
- const db = getDb();
132
141
  const now = new Date().toISOString();
133
142
 
134
- const spec = resolveSpec(specId, ["implementing"]);
143
+ const spec = await resolveSpec(specId, ["implementing"]);
135
144
 
136
- const tasks = db
137
- .query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number")
138
- .all(spec.id) as any[];
145
+ const tasks = await dbAll<any>(
146
+ "SELECT * FROM tasks WHERE spec_id = ? ORDER BY number",
147
+ [spec.id]
148
+ );
139
149
 
140
- const artifacts = db
141
- .query("SELECT * FROM artifacts WHERE spec_id = ?")
142
- .all(spec.id) as any[];
150
+ const artifacts = await dbAll<any>(
151
+ "SELECT * FROM artifacts WHERE spec_id = ?",
152
+ [spec.id]
153
+ );
143
154
 
144
- const decisions = db
145
- .query("SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'")
146
- .all(spec.id) as any[];
155
+ const decisions = await dbAll<any>(
156
+ "SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'",
157
+ [spec.id]
158
+ );
147
159
 
148
160
  // v8.4: Buscar analise arquitetural (link explicito via analysis_id ou nome)
149
- const archAnalysis = getArchitecturalAnalysisForSpec(spec.name, spec.id);
150
- const gateBypasses = db.query(
151
- "SELECT * FROM gate_bypasses WHERE spec_id = ? ORDER BY created_at"
152
- ).all(spec.id) as any[];
161
+ const archAnalysis = await getArchitecturalAnalysisForSpec(spec.name, spec.id);
162
+ const gateBypasses = await dbAll<any>(
163
+ "SELECT * FROM gate_bypasses WHERE spec_id = ? ORDER BY created_at",
164
+ [spec.id]
165
+ );
153
166
 
154
167
  // Analisar plano vs implementado
155
168
  const plannedFiles = tasks.flatMap((t) => (t.files ? JSON.parse(t.files) : []));
@@ -166,6 +179,15 @@ export function reviewStart(json: boolean = false, specId?: string): void {
166
179
  deviations.push(`Arquivos extras criados: ${extraFiles.join(", ")}`);
167
180
  }
168
181
 
182
+ // v8.5: Utilities criadas nesta feature (DRY audit)
183
+ let utilitiesCreated: any[] = [];
184
+ try {
185
+ utilitiesCreated = await dbAll<any>(
186
+ "SELECT * FROM project_utilities WHERE spec_id = ? ORDER BY file_path",
187
+ [spec.id]
188
+ );
189
+ } catch { utilitiesCreated = []; }
190
+
169
191
  // Criar registro de review
170
192
  const reviewData = {
171
193
  tasks_completed: tasks.length,
@@ -183,17 +205,10 @@ export function reviewStart(json: boolean = false, specId?: string): void {
183
205
  decisions: archAnalysis.decisions ? JSON.parse(archAnalysis.decisions) : [],
184
206
  } : null,
185
207
  gate_bypasses: gateBypasses,
186
- // v8.5: Utilities criadas nesta feature (DRY audit)
187
- utilities_created: (() => {
188
- try {
189
- return db.query(
190
- "SELECT * FROM project_utilities WHERE spec_id = ? ORDER BY file_path"
191
- ).all(spec.id) as any[];
192
- } catch { return []; }
193
- })(),
208
+ utilities_created: utilitiesCreated,
194
209
  };
195
210
 
196
- db.run(
211
+ await dbRun(
197
212
  `INSERT INTO review (spec_id, planned_vs_done, deviations, status, created_at)
198
213
  VALUES (?, ?, ?, 'pending', ?)`,
199
214
  [
@@ -205,10 +220,10 @@ export function reviewStart(json: boolean = false, specId?: string): void {
205
220
  );
206
221
 
207
222
  // Atualizar fase
208
- db.run("UPDATE specs SET phase = 'reviewing', updated_at = ? WHERE id = ?", [now, spec.id]);
223
+ await dbRun("UPDATE specs SET phase = 'reviewing', updated_at = ? WHERE id = ?", [now, spec.id]);
209
224
 
210
225
  // P1-2: Calcular score automatico
211
- const score = calculateReviewScore(spec.id);
226
+ const score = await calculateReviewScore(spec.id);
212
227
 
213
228
  if (json) {
214
229
  console.log(JSON.stringify({ spec, reviewData, deviations, score }));
@@ -284,11 +299,10 @@ export function reviewStart(json: boolean = false, specId?: string): void {
284
299
  console.log(`Para ver status: status\n`);
285
300
  }
286
301
 
287
- export function reviewApprove(options?: { specId?: string; force?: boolean; forceReason?: string } | string): void {
288
- initSchema();
302
+ export async function reviewApprove(options?: { specId?: string; force?: boolean; forceReason?: string } | string): Promise<void> {
303
+ await initSchema();
289
304
  enforceGate("review-approve");
290
305
 
291
- const db = getDb();
292
306
  const now = new Date().toISOString();
293
307
 
294
308
  // Backward compat: accept string (old API) or options object
@@ -296,10 +310,10 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
296
310
  ? { specId: options }
297
311
  : (options || {});
298
312
 
299
- const spec = resolveSpec(opts.specId, ["reviewing"]);
313
+ const spec = await resolveSpec(opts.specId, ["reviewing"]);
300
314
 
301
315
  // P1-2: Enforce minimum score
302
- const score = calculateReviewScore(spec.id);
316
+ const score = await calculateReviewScore(spec.id);
303
317
  if (score.total < 50 && !opts.force) {
304
318
  throw new GateError(
305
319
  `Review score muito baixo: ${score.total}/100.\n` +
@@ -312,7 +326,7 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
312
326
 
313
327
  if (score.total < 50 && opts.force) {
314
328
  // Log the forced approval
315
- db.run(
329
+ await dbRun(
316
330
  `INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason, created_at)
317
331
  VALUES (?, 0, 'review-low-score', ?, ?)`,
318
332
  [spec.id, opts.forceReason || `Score ${score.total}/100 - forced approval`, now]
@@ -321,20 +335,20 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
321
335
  }
322
336
 
323
337
  // Atualizar review
324
- db.run("UPDATE review SET status = 'passed' WHERE spec_id = ?", [spec.id]);
338
+ await dbRun("UPDATE review SET status = 'passed' WHERE spec_id = ?", [spec.id]);
325
339
 
326
340
  // Atualizar spec para completed
327
- db.run("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
341
+ await dbRun("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
328
342
 
329
343
 
330
344
 
331
345
  // Buscar todos os dados para snapshot e relatorio
332
- const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number").all(spec.id) as any[];
333
- const decisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
334
- const artifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
335
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
336
- const review = db.query("SELECT * FROM review WHERE spec_id = ?").get(spec.id) as any;
337
- const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(spec.id) as any[];
346
+ const tasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number", [spec.id]);
347
+ const decisions = await dbAll<any>("SELECT * FROM decisions WHERE spec_id = ?", [spec.id]);
348
+ const artifacts = await dbAll<any>("SELECT * FROM artifacts WHERE spec_id = ?", [spec.id]);
349
+ const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
350
+ const review = await dbGet<any>("SELECT * FROM review WHERE spec_id = ?", [spec.id]);
351
+ const knowledge = await dbAll<any>("SELECT * FROM knowledge WHERE spec_id = ?", [spec.id]);
338
352
 
339
353
  // Criar snapshot final
340
354
  const allData = {
@@ -347,7 +361,7 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
347
361
  knowledge,
348
362
  };
349
363
 
350
- db.run("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'final', ?)", [
364
+ await dbRun("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'final', ?)", [
351
365
  spec.id,
352
366
  JSON.stringify(allData),
353
367
  now,
@@ -360,44 +374,44 @@ export function reviewApprove(options?: { specId?: string; force?: boolean; forc
360
374
  /**
361
375
  * Pula o review e finaliza a feature diretamente
362
376
  */
363
- export function reviewSkip(specId?: string): void {
364
- initSchema();
377
+ export async function reviewSkip(specId?: string): Promise<void> {
378
+ await initSchema();
365
379
 
366
- const db = getDb();
367
380
  const now = new Date().toISOString();
368
381
 
369
- const spec = resolveSpec(specId, ["implementing"]);
382
+ const spec = await resolveSpec(specId, ["implementing"]);
370
383
 
371
384
  // Verificar se todas tasks estao done
372
- const pending = db
373
- .query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status != 'done'")
374
- .get(spec.id) as any;
385
+ const pending = await dbGet<any>(
386
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status != 'done'",
387
+ [spec.id]
388
+ );
375
389
 
376
- if (pending.c > 0) {
390
+ if (pending && pending.c > 0) {
377
391
  throw new CodexaError(`Ainda existem ${pending.c} tasks pendentes.\nComplete todas as tasks antes de pular o review.`);
378
392
  }
379
393
 
380
394
  // Buscar dados
381
- const tasks = db.query("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number").all(spec.id) as any[];
382
- const decisions = db.query("SELECT * FROM decisions WHERE spec_id = ?").all(spec.id) as any[];
383
- const artifacts = db.query("SELECT * FROM artifacts WHERE spec_id = ?").all(spec.id) as any[];
384
- const context = db.query("SELECT * FROM context WHERE spec_id = ?").get(spec.id) as any;
385
- const knowledge = db.query("SELECT * FROM knowledge WHERE spec_id = ?").all(spec.id) as any[];
395
+ const tasks = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ? ORDER BY number", [spec.id]);
396
+ const decisions = await dbAll<any>("SELECT * FROM decisions WHERE spec_id = ?", [spec.id]);
397
+ const artifacts = await dbAll<any>("SELECT * FROM artifacts WHERE spec_id = ?", [spec.id]);
398
+ const context = await dbGet<any>("SELECT * FROM context WHERE spec_id = ?", [spec.id]);
399
+ const knowledge = await dbAll<any>("SELECT * FROM knowledge WHERE spec_id = ?", [spec.id]);
386
400
 
387
401
  // Criar registro de review como skipped
388
- db.run(
402
+ await dbRun(
389
403
  `INSERT INTO review (spec_id, planned_vs_done, deviations, status, created_at)
390
404
  VALUES (?, ?, NULL, 'skipped', ?)`,
391
405
  [spec.id, JSON.stringify({ skipped: true, reason: "User chose to skip review" }), now]
392
406
  );
393
407
 
394
408
  // Atualizar spec para completed
395
- db.run("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
409
+ await dbRun("UPDATE specs SET phase = 'completed', updated_at = ? WHERE id = ?", [now, spec.id]);
396
410
 
397
411
 
398
412
 
399
413
  // Criar snapshot final
400
- const review = db.query("SELECT * FROM review WHERE spec_id = ?").get(spec.id) as any;
414
+ const review = await dbGet<any>("SELECT * FROM review WHERE spec_id = ?", [spec.id]);
401
415
  const allData = {
402
416
  spec,
403
417
  context,
@@ -408,7 +422,7 @@ export function reviewSkip(specId?: string): void {
408
422
  knowledge,
409
423
  };
410
424
 
411
- db.run("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'final', ?)", [
425
+ await dbRun("INSERT INTO snapshots (spec_id, data, trigger, created_at) VALUES (?, ?, 'final', ?)", [
412
426
  spec.id,
413
427
  JSON.stringify(allData),
414
428
  now,
@@ -4,7 +4,7 @@
4
4
  // Used post-task (Option A) and pre-review (Option C).
5
5
  // ═══════════════════════════════════════════════════════════════
6
6
 
7
- import { getDb } from "../db/connection";
7
+ import { dbGet, dbAll, dbRun } from "../db/connection";
8
8
  import { initSchema } from "../db/schema";
9
9
  import { resolveSpec } from "./spec-resolver";
10
10
  import { getAgentDomain, domainToScope } from "../context/domains";
@@ -20,15 +20,14 @@ import {
20
20
  // SIMPLIFY FOR TASK (Option A)
21
21
  // ═══════════════════════════════════════════════════════════════
22
22
 
23
- export function simplifyTask(
23
+ export async function simplifyTask(
24
24
  taskId: string,
25
25
  options: { audit?: boolean; specId?: string } = {}
26
- ): void {
27
- initSchema();
26
+ ): Promise<void> {
27
+ await initSchema();
28
28
 
29
- const db = getDb();
30
29
  const id = parseInt(taskId);
31
- const task = db.query("SELECT * FROM tasks WHERE id = ?").get(id) as any;
30
+ const task = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [id]);
32
31
 
33
32
  if (!task) {
34
33
  throw new CodexaError(`Task #${id} nao encontrada.`);
@@ -40,12 +39,10 @@ export function simplifyTask(
40
39
  );
41
40
  }
42
41
 
43
- // Get files from artifacts
44
- const artifacts = db
45
- .query(
46
- "SELECT path, action FROM artifacts WHERE spec_id = ? AND task_ref = ?"
47
- )
48
- .all(task.spec_id, task.number) as any[];
42
+ const artifacts = await dbAll<any>(
43
+ "SELECT path, action FROM artifacts WHERE spec_id = ? AND task_ref = ?",
44
+ [task.spec_id, task.number]
45
+ );
49
46
 
50
47
  if (artifacts.length === 0) {
51
48
  console.log(`\nTask #${task.number} nao tem arquivos registrados. Nada para simplificar.\n`);
@@ -57,18 +54,14 @@ export function simplifyTask(
57
54
  action: a.action === "created" ? "created" as const : "modified" as const,
58
55
  }));
59
56
 
60
- // Determine agent domain for scope filtering
61
57
  const domain = getAgentDomain(task.agent);
62
58
  const scope = domainToScope(domain);
63
59
  const mode: SimplifyMode = options.audit ? "audit" : "refactor";
64
60
 
65
- // Build prompt with standards
66
61
  const prompt = buildSimplifyPrompt(files, scope, mode);
67
62
 
68
- // Write context file
69
63
  const contextPath = writeSimplifyContext(`task-${task.number}`, prompt);
70
64
 
71
- // Output
72
65
  const modeLabel = mode === "audit" ? "AUDITORIA" : "REFATORACAO";
73
66
  console.log(`\n${"=".repeat(55)}`);
74
67
  console.log(`SIMPLIFY (${modeLabel}) — Task #${task.number}`);
@@ -86,16 +79,15 @@ export function simplifyTask(
86
79
  // SIMPLIFY AUDIT FOR REVIEW (Option C)
87
80
  // ═══════════════════════════════════════════════════════════════
88
81
 
89
- export function simplifyAudit(specId?: string): string | null {
90
- initSchema();
82
+ export async function simplifyAudit(specId?: string): Promise<string | null> {
83
+ await initSchema();
91
84
 
92
- const db = getDb();
93
- const spec = resolveSpec(specId, ["implementing", "reviewing"]);
85
+ const spec = await resolveSpec(specId, ["implementing", "reviewing"]);
94
86
 
95
- // Get all feature artifacts
96
- const artifacts = db
97
- .query("SELECT DISTINCT path, action FROM artifacts WHERE spec_id = ?")
98
- .all(spec.id) as any[];
87
+ const artifacts = await dbAll<any>(
88
+ "SELECT DISTINCT path, action FROM artifacts WHERE spec_id = ?",
89
+ [spec.id]
90
+ );
99
91
 
100
92
  if (artifacts.length === 0) {
101
93
  return null;
@@ -106,10 +98,8 @@ export function simplifyAudit(specId?: string): string | null {
106
98
  action: a.action === "created" ? "created" as const : "modified" as const,
107
99
  }));
108
100
 
109
- // Use "all" scope for review (covers all domains)
110
101
  const prompt = buildSimplifyPrompt(files, "all", "audit");
111
102
 
112
- // Write context file
113
103
  const contextPath = writeSimplifyContext(`audit-${spec.id}`, prompt);
114
104
 
115
105
  return contextPath;
@@ -119,22 +109,21 @@ export function simplifyAudit(specId?: string): string | null {
119
109
  // AUTO-SIMPLIFY CHECK
120
110
  // ═══════════════════════════════════════════════════════════════
121
111
 
122
- export function isAutoSimplifyEnabled(): boolean {
112
+ export async function isAutoSimplifyEnabled(): Promise<boolean> {
123
113
  try {
124
- const db = getDb();
125
- const project = db
126
- .query("SELECT auto_simplify FROM project WHERE id = 'default'")
127
- .get() as any;
114
+ const project = await dbGet<any>(
115
+ "SELECT auto_simplify FROM project WHERE id = 'default'",
116
+ []
117
+ );
128
118
  return project?.auto_simplify === 1;
129
119
  } catch {
130
120
  return false;
131
121
  }
132
122
  }
133
123
 
134
- export function setAutoSimplify(enabled: boolean): void {
135
- initSchema();
136
- const db = getDb();
137
- db.run(
124
+ export async function setAutoSimplify(enabled: boolean): Promise<void> {
125
+ await initSchema();
126
+ await dbRun(
138
127
  "UPDATE project SET auto_simplify = ?, updated_at = ? WHERE id = 'default'",
139
128
  [enabled ? 1 : 0, new Date().toISOString()]
140
129
  );
@@ -1,106 +1,110 @@
1
- import { describe, it, expect, beforeEach } from "bun:test";
1
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
2
  import { resolveSpec, getAllActiveSpecs } from "./spec-resolver";
3
- import { getDb } from "../db/connection";
3
+ import { createClient } from "@libsql/client";
4
+ import { setClient, resetClient, dbRun } from "../db/connection";
4
5
  import { initSchema } from "../db/schema";
5
6
  import { cleanDb } from "../db/test-helpers";
6
7
 
7
- beforeEach(() => {
8
- initSchema();
9
- cleanDb();
8
+ beforeEach(async () => {
9
+ const client = createClient({ url: ":memory:" });
10
+ setClient(client);
11
+ await initSchema();
12
+ await cleanDb();
10
13
  });
11
14
 
12
- function createSpec(id: string, name: string, phase: string, createdAt?: string) {
13
- const db = getDb();
15
+ afterEach(() => {
16
+ resetClient();
17
+ });
18
+
19
+ async function createSpec(id: string, name: string, phase: string, createdAt?: string) {
14
20
  const now = createdAt || new Date().toISOString();
15
- db.run(
21
+ await dbRun(
16
22
  "INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
17
23
  [id, name, phase, now, now]
18
24
  );
19
25
  }
20
26
 
21
27
  describe("resolveSpec", () => {
22
- it("returns spec by ID when specId provided", () => {
23
- createSpec("spec-a", "Feature A", "planning");
24
- createSpec("spec-b", "Feature B", "implementing");
28
+ it("returns spec by ID when specId provided", async () => {
29
+ await createSpec("spec-a", "Feature A", "planning");
30
+ await createSpec("spec-b", "Feature B", "implementing");
25
31
 
26
- const result = resolveSpec("spec-a");
32
+ const result = await resolveSpec("spec-a");
27
33
  expect(result.id).toBe("spec-a");
28
34
  expect(result.name).toBe("Feature A");
29
35
  });
30
36
 
31
- it("returns most recent active spec when no specId", () => {
32
- createSpec("spec-old", "Old Feature", "planning", "2026-01-01T00:00:00Z");
33
- createSpec("spec-new", "New Feature", "implementing", "2026-02-01T00:00:00Z");
37
+ it("returns most recent active spec when no specId", async () => {
38
+ await createSpec("spec-old", "Old Feature", "planning", "2026-01-01T00:00:00Z");
39
+ await createSpec("spec-new", "New Feature", "implementing", "2026-02-01T00:00:00Z");
34
40
 
35
- const result = resolveSpec();
41
+ const result = await resolveSpec();
36
42
  expect(result.id).toBe("spec-new");
37
43
  });
38
44
 
39
- it("validates requiredPhases", () => {
40
- createSpec("spec-a", "Feature A", "planning");
45
+ it("validates requiredPhases", async () => {
46
+ await createSpec("spec-a", "Feature A", "planning");
41
47
 
42
- // Should work with matching phase
43
- const result = resolveSpec("spec-a", ["planning"]);
48
+ const result = await resolveSpec("spec-a", ["planning"]);
44
49
  expect(result.phase).toBe("planning");
45
50
  });
46
51
 
47
- it("accepts multiple requiredPhases", () => {
48
- createSpec("spec-a", "Feature A", "checking");
49
- const result = resolveSpec("spec-a", ["planning", "checking"]);
52
+ it("accepts multiple requiredPhases", async () => {
53
+ await createSpec("spec-a", "Feature A", "checking");
54
+ const result = await resolveSpec("spec-a", ["planning", "checking"]);
50
55
  expect(result.phase).toBe("checking");
51
56
  });
52
57
 
53
- it("skips completed specs when no specId", () => {
54
- createSpec("spec-done", "Done Feature", "completed", "2026-02-01T00:00:00Z");
55
- createSpec("spec-active", "Active Feature", "planning", "2026-01-01T00:00:00Z");
58
+ it("skips completed specs when no specId", async () => {
59
+ await createSpec("spec-done", "Done Feature", "completed", "2026-02-01T00:00:00Z");
60
+ await createSpec("spec-active", "Active Feature", "planning", "2026-01-01T00:00:00Z");
56
61
 
57
- const result = resolveSpec();
62
+ const result = await resolveSpec();
58
63
  expect(result.id).toBe("spec-active");
59
64
  });
60
65
 
61
- it("skips cancelled specs when no specId", () => {
62
- createSpec("spec-cancelled", "Cancelled Feature", "cancelled", "2026-02-01T00:00:00Z");
63
- createSpec("spec-active", "Active Feature", "implementing", "2026-01-01T00:00:00Z");
66
+ it("skips cancelled specs when no specId", async () => {
67
+ await createSpec("spec-cancelled", "Cancelled Feature", "cancelled", "2026-02-01T00:00:00Z");
68
+ await createSpec("spec-active", "Active Feature", "implementing", "2026-01-01T00:00:00Z");
64
69
 
65
- const result = resolveSpec();
70
+ const result = await resolveSpec();
66
71
  expect(result.id).toBe("spec-active");
67
72
  });
68
73
  });
69
74
 
70
75
  describe("getAllActiveSpecs", () => {
71
- it("returns empty array when no active specs", () => {
72
- const result = getAllActiveSpecs();
76
+ it("returns empty array when no active specs", async () => {
77
+ const result = await getAllActiveSpecs();
73
78
  expect(result).toEqual([]);
74
79
  });
75
80
 
76
- it("returns all active specs ordered by created_at DESC", () => {
77
- createSpec("spec-old", "Old Feature", "planning", "2026-01-01T00:00:00Z");
78
- createSpec("spec-new", "New Feature", "implementing", "2026-02-01T00:00:00Z");
81
+ it("returns all active specs ordered by created_at DESC", async () => {
82
+ await createSpec("spec-old", "Old Feature", "planning", "2026-01-01T00:00:00Z");
83
+ await createSpec("spec-new", "New Feature", "implementing", "2026-02-01T00:00:00Z");
79
84
 
80
- const result = getAllActiveSpecs();
85
+ const result = await getAllActiveSpecs();
81
86
  expect(result.length).toBe(2);
82
87
  expect(result[0].id).toBe("spec-new");
83
88
  expect(result[1].id).toBe("spec-old");
84
89
  });
85
90
 
86
- it("excludes completed and cancelled specs", () => {
87
- createSpec("spec-active", "Active", "planning");
88
- createSpec("spec-done", "Done", "completed");
89
- createSpec("spec-cancelled", "Cancelled", "cancelled");
91
+ it("excludes completed and cancelled specs", async () => {
92
+ await createSpec("spec-active", "Active", "planning");
93
+ await createSpec("spec-done", "Done", "completed");
94
+ await createSpec("spec-cancelled", "Cancelled", "cancelled");
90
95
 
91
- const result = getAllActiveSpecs();
96
+ const result = await getAllActiveSpecs();
92
97
  expect(result.length).toBe(1);
93
98
  expect(result[0].id).toBe("spec-active");
94
99
  });
95
100
 
96
- it("returns multiple specs in different phases", () => {
97
- createSpec("spec-plan", "Planning Feature", "planning", "2026-01-01T00:00:00Z");
98
- createSpec("spec-impl", "Implementing Feature", "implementing", "2026-01-02T00:00:00Z");
99
- createSpec("spec-rev", "Reviewing Feature", "reviewing", "2026-01-03T00:00:00Z");
101
+ it("returns multiple specs in different phases", async () => {
102
+ await createSpec("spec-plan", "Planning Feature", "planning", "2026-01-01T00:00:00Z");
103
+ await createSpec("spec-impl", "Implementing Feature", "implementing", "2026-01-02T00:00:00Z");
104
+ await createSpec("spec-rev", "Reviewing Feature", "reviewing", "2026-01-03T00:00:00Z");
100
105
 
101
- const result = getAllActiveSpecs();
106
+ const result = await getAllActiveSpecs();
102
107
  expect(result.length).toBe(3);
103
- // Ordered by created_at DESC
104
108
  expect(result[0].id).toBe("spec-rev");
105
109
  expect(result[1].id).toBe("spec-impl");
106
110
  expect(result[2].id).toBe("spec-plan");