@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.
@@ -5,66 +5,59 @@
5
5
  * between phases, knowledge propagates between tasks, and gates
6
6
  * enforce constraints as expected.
7
7
  */
8
- import { describe, it, expect, beforeEach } from "bun:test";
9
- import { getDb } from "../db/connection";
8
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
9
+ import { createClient } from "@libsql/client";
10
+ import { setClient, resetClient, dbGet, dbAll, dbRun } from "../db/connection";
10
11
  import { initSchema } from "../db/schema";
11
12
  import { cleanDb } from "../db/test-helpers";
12
- import { planStart, planTaskAdd } from "./plan";
13
- import { checkRequest, checkApprove } from "./check";
14
- import { taskStart, taskDone, taskNext } from "./task";
15
- import { reviewStart, reviewApprove, calculateReviewScore } from "./review";
16
13
  import { processSubagentReturn } from "../protocol/process-return";
17
14
  import { validateGate } from "../gates/validator";
15
+ import { calculateReviewScore } from "./review";
18
16
 
19
- // Helper: criar spec + tasks diretamente no DB (bypass console.log)
20
- function setupSpec(name: string, phase: string = "planning"): string {
21
- const db = getDb();
17
+ async function setupSpec(name: string, phase: string = "planning"): Promise<string> {
22
18
  const id = `test-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
23
19
  const now = new Date().toISOString();
24
- db.run(
20
+ await dbRun(
25
21
  "INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
26
22
  [id, name, phase, now, now]
27
23
  );
28
- db.run(
24
+ await dbRun(
29
25
  "INSERT INTO context (spec_id, objective, updated_at) VALUES (?, ?, ?)",
30
26
  [id, name, now]
31
27
  );
32
28
  return id;
33
29
  }
34
30
 
35
- function addTask(specId: string, number: number, name: string, agent?: string, dependsOn?: number[]): number {
36
- const db = getDb();
31
+ async function addTask(specId: string, number: number, name: string, agent?: string, dependsOn?: number[]): Promise<number> {
37
32
  const deps = dependsOn && dependsOn.length > 0 ? JSON.stringify(dependsOn) : null;
38
- db.run(
33
+ await dbRun(
39
34
  `INSERT INTO tasks (spec_id, number, name, agent, depends_on, can_parallel, status)
40
35
  VALUES (?, ?, ?, ?, ?, 1, 'pending')`,
41
36
  [specId, number, name, agent || null, deps]
42
37
  );
43
- const task = db.query(
44
- "SELECT id FROM tasks WHERE spec_id = ? AND number = ?"
45
- ).get(specId, number) as any;
38
+ const task = await dbGet<any>(
39
+ "SELECT id FROM tasks WHERE spec_id = ? AND number = ?",
40
+ [specId, number]
41
+ );
46
42
  return task.id;
47
43
  }
48
44
 
49
- function approveSpec(specId: string) {
50
- const db = getDb();
45
+ async function approveSpec(specId: string) {
51
46
  const now = new Date().toISOString();
52
- db.run(
47
+ await dbRun(
53
48
  "UPDATE specs SET phase = 'implementing', approved_at = ?, updated_at = ? WHERE id = ?",
54
49
  [now, now, specId]
55
50
  );
56
51
  }
57
52
 
58
- function startTask(taskId: number) {
59
- const db = getDb();
53
+ async function startTask(taskId: number) {
60
54
  const now = new Date().toISOString();
61
- db.run("UPDATE tasks SET status = 'running', started_at = ? WHERE id = ?", [now, taskId]);
55
+ await dbRun("UPDATE tasks SET status = 'running', started_at = ? WHERE id = ?", [now, taskId]);
62
56
  }
63
57
 
64
- function completeTask(taskId: number, checkpoint: string) {
65
- const db = getDb();
58
+ async function completeTask(taskId: number, checkpoint: string) {
66
59
  const now = new Date().toISOString();
67
- db.run(
60
+ await dbRun(
68
61
  "UPDATE tasks SET status = 'done', checkpoint = ?, completed_at = ? WHERE id = ?",
69
62
  [checkpoint, now, taskId]
70
63
  );
@@ -74,48 +67,48 @@ function completeTask(taskId: number, checkpoint: string) {
74
67
  // Tests
75
68
  // ============================================================
76
69
 
77
- beforeEach(() => {
78
- initSchema();
79
- cleanDb();
70
+ beforeEach(async () => {
71
+ const client = createClient({ url: ":memory:" });
72
+ setClient(client);
73
+ await initSchema();
74
+ await cleanDb();
80
75
  });
81
76
 
82
- describe("PLAN → CHECK → IMP → REV lifecycle", () => {
83
- it("should complete full lifecycle with knowledge propagation", () => {
84
- const db = getDb();
77
+ afterEach(() => {
78
+ resetClient();
79
+ });
85
80
 
81
+ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
82
+ it("should complete full lifecycle with knowledge propagation", async () => {
86
83
  // ──── PLAN ────
87
- const specId = setupSpec("E2E Test Feature");
88
- const task1Id = addTask(specId, 1, "Setup database schema", "backend-javascript");
89
- const task2Id = addTask(specId, 2, "Create API endpoints", "backend-javascript", [1]);
84
+ const specId = await setupSpec("E2E Test Feature");
85
+ const task1Id = await addTask(specId, 1, "Setup database schema", "backend-javascript");
86
+ const task2Id = await addTask(specId, 2, "Create API endpoints", "backend-javascript", [1]);
90
87
 
91
- // Update task count in context
92
- db.run("UPDATE context SET total_tasks = 2, updated_at = ? WHERE spec_id = ?", [
88
+ await dbRun("UPDATE context SET total_tasks = 2, updated_at = ? WHERE spec_id = ?", [
93
89
  new Date().toISOString(), specId,
94
90
  ]);
95
91
 
96
- // Verify plan exists
97
- const spec = db.query("SELECT * FROM specs WHERE id = ?").get(specId) as any;
92
+ const spec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [specId]);
98
93
  expect(spec.phase).toBe("planning");
99
94
 
100
95
  // ──── CHECK ────
101
- // Move to checking phase, then approve
102
- db.run("UPDATE specs SET phase = 'checking', updated_at = ? WHERE id = ?", [
96
+ await dbRun("UPDATE specs SET phase = 'checking', updated_at = ? WHERE id = ?", [
103
97
  new Date().toISOString(), specId,
104
98
  ]);
105
- approveSpec(specId);
99
+ await approveSpec(specId);
106
100
 
107
- const approvedSpec = db.query("SELECT * FROM specs WHERE id = ?").get(specId) as any;
101
+ const approvedSpec = await dbGet<any>("SELECT * FROM specs WHERE id = ?", [specId]);
108
102
  expect(approvedSpec.phase).toBe("implementing");
109
103
  expect(approvedSpec.approved_at).toBeTruthy();
110
104
 
111
105
  // ──── IMP Task 1 ────
112
- startTask(task1Id);
106
+ await startTask(task1Id);
113
107
 
114
- const runningTask = db.query("SELECT * FROM tasks WHERE id = ?").get(task1Id) as any;
108
+ const runningTask = await dbGet<any>("SELECT * FROM tasks WHERE id = ?", [task1Id]);
115
109
  expect(runningTask.status).toBe("running");
116
110
  expect(runningTask.started_at).toBeTruthy();
117
111
 
118
- // Simulate subagent return for task 1
119
112
  const subagentReturn1 = {
120
113
  status: "completed" as const,
121
114
  summary: "Created database schema with users and sessions tables",
@@ -148,20 +141,19 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
148
141
  ],
149
142
  };
150
143
 
151
- const processResult1 = processSubagentReturn(specId, task1Id, 1, subagentReturn1);
144
+ const processResult1 = await processSubagentReturn(specId, task1Id, 1, subagentReturn1);
152
145
  expect(processResult1.success).toBe(true);
153
146
  expect(processResult1.knowledgeAdded).toBe(2);
154
147
  expect(processResult1.decisionsAdded).toBe(1);
155
148
  expect(processResult1.artifactsAdded).toBe(2);
156
- expect(processResult1.reasoningAdded).toBeGreaterThanOrEqual(3); // approach + challenge + alternative + recommendation
149
+ expect(processResult1.reasoningAdded).toBeGreaterThanOrEqual(3);
157
150
 
158
- // Complete task 1
159
- completeTask(task1Id, "Created database schema with users and sessions tables");
151
+ await completeTask(task1Id, "Created database schema with users and sessions tables");
160
152
 
161
- // Verify knowledge was stored
162
- const knowledge = db.query(
163
- "SELECT * FROM knowledge WHERE spec_id = ? ORDER BY created_at"
164
- ).all(specId) as any[];
153
+ const knowledge = await dbAll<any>(
154
+ "SELECT * FROM knowledge WHERE spec_id = ? ORDER BY created_at",
155
+ [specId]
156
+ );
165
157
  expect(knowledge.length).toBeGreaterThanOrEqual(2);
166
158
 
167
159
  const warningKnowledge = knowledge.find(
@@ -169,39 +161,37 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
169
161
  );
170
162
  expect(warningKnowledge).toBeTruthy();
171
163
 
172
- // Verify decisions were stored
173
- const decisions = db.query(
174
- "SELECT * FROM decisions WHERE spec_id = ?"
175
- ).all(specId) as any[];
164
+ const decisions = await dbAll<any>(
165
+ "SELECT * FROM decisions WHERE spec_id = ?",
166
+ [specId]
167
+ );
176
168
  expect(decisions.length).toBe(1);
177
169
  expect(decisions[0].title).toBe("Database ORM choice");
178
170
 
179
- // Verify artifacts were stored
180
- const artifacts = db.query(
181
- "SELECT * FROM artifacts WHERE spec_id = ?"
182
- ).all(specId) as any[];
171
+ const artifacts = await dbAll<any>(
172
+ "SELECT * FROM artifacts WHERE spec_id = ?",
173
+ [specId]
174
+ );
183
175
  expect(artifacts.length).toBe(2);
184
176
 
185
- // Verify reasoning was logged
186
- const reasoning = db.query(
187
- "SELECT * FROM reasoning_log WHERE spec_id = ? AND task_id = ?"
188
- ).all(specId, task1Id) as any[];
177
+ const reasoning = await dbAll<any>(
178
+ "SELECT * FROM reasoning_log WHERE spec_id = ? AND task_id = ?",
179
+ [specId, task1Id]
180
+ );
189
181
  expect(reasoning.length).toBeGreaterThanOrEqual(3);
190
182
 
191
- // Verify knowledge graph relations
192
- const relations = db.query(
193
- "SELECT * FROM knowledge_graph WHERE spec_id = ?"
194
- ).all(specId) as any[];
183
+ const relations = await dbAll<any>(
184
+ "SELECT * FROM knowledge_graph WHERE spec_id = ?",
185
+ [specId]
186
+ );
195
187
  expect(relations.length).toBeGreaterThan(0);
196
188
 
197
189
  // ──── IMP Task 2 ────
198
- // Task 2 depends on task 1 verify dependencies gate passes
199
- const task1Done = db.query("SELECT status FROM tasks WHERE id = ?").get(task1Id) as any;
190
+ const task1Done = await dbGet<any>("SELECT status FROM tasks WHERE id = ?", [task1Id]);
200
191
  expect(task1Done.status).toBe("done");
201
192
 
202
- startTask(task2Id);
193
+ await startTask(task2Id);
203
194
 
204
- // Simulate subagent return for task 2
205
195
  const subagentReturn2 = {
206
196
  status: "completed" as const,
207
197
  summary: "Created REST API endpoints for users CRUD with authentication",
@@ -219,48 +209,43 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
219
209
  ],
220
210
  };
221
211
 
222
- const processResult2 = processSubagentReturn(specId, task2Id, 2, subagentReturn2);
212
+ const processResult2 = await processSubagentReturn(specId, task2Id, 2, subagentReturn2);
223
213
  expect(processResult2.success).toBe(true);
224
- expect(processResult2.artifactsAdded).toBe(3); // 2 created + 1 modified (OR REPLACE)
214
+ expect(processResult2.artifactsAdded).toBe(3);
225
215
 
226
- completeTask(task2Id, "Created REST API endpoints for users CRUD with authentication");
216
+ await completeTask(task2Id, "Created REST API endpoints for users CRUD with authentication");
227
217
 
228
- // Verify total knowledge accumulated from both tasks
229
- const allKnowledge = db.query(
230
- "SELECT * FROM knowledge WHERE spec_id = ?"
231
- ).all(specId) as any[];
232
- expect(allKnowledge.length).toBeGreaterThanOrEqual(3); // 2 from task 1 + 1 from task 2
218
+ const allKnowledge = await dbAll<any>(
219
+ "SELECT * FROM knowledge WHERE spec_id = ?",
220
+ [specId]
221
+ );
222
+ expect(allKnowledge.length).toBeGreaterThanOrEqual(3);
233
223
 
234
224
  // ──── REV ────
235
- // Move to reviewing phase
236
- db.run("UPDATE specs SET phase = 'reviewing', updated_at = ? WHERE id = ?", [
225
+ await dbRun("UPDATE specs SET phase = 'reviewing', updated_at = ? WHERE id = ?", [
237
226
  new Date().toISOString(), specId,
238
227
  ]);
239
228
 
240
- // Calculate review score
241
- const score = calculateReviewScore(specId);
229
+ const score = await calculateReviewScore(specId);
242
230
  expect(score.total).toBeGreaterThanOrEqual(50);
243
- expect(score.breakdown.tasksCompleted).toBe(25); // All tasks done = full marks
231
+ expect(score.breakdown.tasksCompleted).toBe(25);
244
232
 
245
- // Verify all tasks are done
246
- const pendingTasks = db.query(
247
- "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status != 'done'"
248
- ).get(specId) as any;
233
+ const pendingTasks = await dbGet<any>(
234
+ "SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status != 'done'",
235
+ [specId]
236
+ );
249
237
  expect(pendingTasks.c).toBe(0);
250
238
  });
251
239
 
252
- it("should propagate knowledge from task 1 to task 2 context", () => {
253
- const db = getDb();
240
+ it("should propagate knowledge from task 1 to task 2 context", async () => {
241
+ const specId = await setupSpec("Knowledge Propagation Test");
242
+ const task1Id = await addTask(specId, 1, "First task", "backend-javascript");
243
+ const task2Id = await addTask(specId, 2, "Second task", "backend-javascript", [1]);
254
244
 
255
- const specId = setupSpec("Knowledge Propagation Test");
256
- const task1Id = addTask(specId, 1, "First task", "backend-javascript");
257
- const task2Id = addTask(specId, 2, "Second task", "backend-javascript", [1]);
245
+ await approveSpec(specId);
246
+ await startTask(task1Id);
258
247
 
259
- approveSpec(specId);
260
- startTask(task1Id);
261
-
262
- // Task 1 broadcasts critical knowledge
263
- processSubagentReturn(specId, task1Id, 1, {
248
+ await processSubagentReturn(specId, task1Id, 1, {
264
249
  status: "completed",
265
250
  summary: "Completed first task with important discovery",
266
251
  files_created: ["src/first.ts"],
@@ -274,34 +259,31 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
274
259
  },
275
260
  ],
276
261
  });
277
- completeTask(task1Id, "Done with first task");
262
+ await completeTask(task1Id, "Done with first task");
278
263
 
279
- // Verify knowledge exists
280
- const criticalKnowledge = db.query(
281
- "SELECT * FROM knowledge WHERE spec_id = ? AND severity = 'critical'"
282
- ).all(specId) as any[];
264
+ const criticalKnowledge = await dbAll<any>(
265
+ "SELECT * FROM knowledge WHERE spec_id = ? AND severity = 'critical'",
266
+ [specId]
267
+ );
283
268
  expect(criticalKnowledge.length).toBeGreaterThanOrEqual(1);
284
269
  expect(criticalKnowledge[0].content).toContain("X-Auth header");
285
270
 
286
- // Knowledge should be visible for task 2
287
- const task2Knowledge = db.query(
288
- `SELECT * FROM knowledge WHERE spec_id = ? AND task_origin = ?`
289
- ).all(specId, task1Id) as any[];
271
+ const task2Knowledge = await dbAll<any>(
272
+ "SELECT * FROM knowledge WHERE spec_id = ? AND task_origin = ?",
273
+ [specId, task1Id]
274
+ );
290
275
  expect(task2Knowledge.length).toBeGreaterThanOrEqual(1);
291
276
  });
292
277
 
293
- it("should deduplicate identical knowledge entries", () => {
294
- const db = getDb();
295
-
296
- const specId = setupSpec("Dedup Test");
297
- const task1Id = addTask(specId, 1, "Task 1", "backend-javascript");
298
- const task2Id = addTask(specId, 2, "Task 2", "backend-javascript");
278
+ it("should deduplicate identical knowledge entries", async () => {
279
+ const specId = await setupSpec("Dedup Test");
280
+ const task1Id = await addTask(specId, 1, "Task 1", "backend-javascript");
281
+ const task2Id = await addTask(specId, 2, "Task 2", "backend-javascript");
299
282
 
300
- approveSpec(specId);
283
+ await approveSpec(specId);
301
284
 
302
- // Both tasks broadcast the same knowledge
303
- startTask(task1Id);
304
- processSubagentReturn(specId, task1Id, 1, {
285
+ await startTask(task1Id);
286
+ await processSubagentReturn(specId, task1Id, 1, {
305
287
  status: "completed",
306
288
  summary: "First task done",
307
289
  files_created: ["src/a.ts"],
@@ -311,10 +293,10 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
311
293
  { category: "discovery", content: "Uses UTF-8 encoding everywhere", severity: "info" },
312
294
  ],
313
295
  });
314
- completeTask(task1Id, "Done");
296
+ await completeTask(task1Id, "Done");
315
297
 
316
- startTask(task2Id);
317
- processSubagentReturn(specId, task2Id, 2, {
298
+ await startTask(task2Id);
299
+ await processSubagentReturn(specId, task2Id, 2, {
318
300
  status: "completed",
319
301
  summary: "Second task done",
320
302
  files_created: ["src/b.ts"],
@@ -324,27 +306,24 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
324
306
  { category: "discovery", content: "Uses UTF-8 encoding everywhere", severity: "info" },
325
307
  ],
326
308
  });
327
- completeTask(task2Id, "Done");
309
+ await completeTask(task2Id, "Done");
328
310
 
329
- // Should only have 1 entry (deduplicated)
330
- const entries = db.query(
331
- "SELECT * FROM knowledge WHERE spec_id = ? AND content = 'Uses UTF-8 encoding everywhere'"
332
- ).all(specId) as any[];
311
+ const entries = await dbAll<any>(
312
+ "SELECT * FROM knowledge WHERE spec_id = ? AND content = 'Uses UTF-8 encoding everywhere'",
313
+ [specId]
314
+ );
333
315
  expect(entries.length).toBe(1);
334
316
  });
335
317
 
336
- it("should register decisions and detect conflicts", () => {
337
- const db = getDb();
338
-
339
- const specId = setupSpec("Decision Conflict Test");
340
- const task1Id = addTask(specId, 1, "Task 1", "backend-javascript");
341
- const task2Id = addTask(specId, 2, "Task 2", "backend-javascript");
318
+ it("should register decisions and detect conflicts", async () => {
319
+ const specId = await setupSpec("Decision Conflict Test");
320
+ const task1Id = await addTask(specId, 1, "Task 1", "backend-javascript");
321
+ const task2Id = await addTask(specId, 2, "Task 2", "backend-javascript");
342
322
 
343
- approveSpec(specId);
323
+ await approveSpec(specId);
344
324
 
345
- // Task 1 decides to use Prisma
346
- startTask(task1Id);
347
- processSubagentReturn(specId, task1Id, 1, {
325
+ await startTask(task1Id);
326
+ await processSubagentReturn(specId, task1Id, 1, {
348
327
  status: "completed",
349
328
  summary: "Setup database with Prisma ORM for type-safe database access",
350
329
  files_created: ["prisma/schema.prisma"],
@@ -354,45 +333,40 @@ describe("PLAN → CHECK → IMP → REV lifecycle", () => {
354
333
  { title: "ORM Selection", decision: "Use Prisma ORM", rationale: "Best TypeScript integration" },
355
334
  ],
356
335
  });
357
- completeTask(task1Id, "Done with Prisma setup");
336
+ await completeTask(task1Id, "Done with Prisma setup");
358
337
 
359
- // Verify decision saved
360
- const decisions = db.query(
361
- "SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'"
362
- ).all(specId) as any[];
338
+ const decisions = await dbAll<any>(
339
+ "SELECT * FROM decisions WHERE spec_id = ? AND status = 'active'",
340
+ [specId]
341
+ );
363
342
  expect(decisions.length).toBe(1);
364
343
  expect(decisions[0].decision).toContain("Prisma");
365
344
  });
366
345
  });
367
346
 
368
347
  describe("Gate enforcement", () => {
369
- it("should block task-done without checkpoint", () => {
370
- const db = getDb();
371
-
372
- const specId = setupSpec("Gate Test");
373
- const taskId = addTask(specId, 1, "Test task");
374
- approveSpec(specId);
375
- startTask(taskId);
348
+ it("should block task-done without checkpoint", async () => {
349
+ const specId = await setupSpec("Gate Test");
350
+ const taskId = await addTask(specId, 1, "Test task");
351
+ await approveSpec(specId);
352
+ await startTask(taskId);
376
353
 
377
- const result = validateGate("task-done", {
354
+ const result = await validateGate("task-done", {
378
355
  taskId,
379
- checkpoint: "short", // < 10 chars
356
+ checkpoint: "short",
380
357
  files: [],
381
358
  });
382
359
  expect(result.passed).toBe(false);
383
360
  expect(result.reason).toContain("Checkpoint");
384
361
  });
385
362
 
386
- it("should block task-start when dependencies are not done", () => {
387
- const db = getDb();
363
+ it("should block task-start when dependencies are not done", async () => {
364
+ const specId = await setupSpec("Dependency Gate Test");
365
+ const task1Id = await addTask(specId, 1, "First task");
366
+ const task2Id = await addTask(specId, 2, "Second task", undefined, [1]);
367
+ await approveSpec(specId);
388
368
 
389
- const specId = setupSpec("Dependency Gate Test");
390
- const task1Id = addTask(specId, 1, "First task");
391
- const task2Id = addTask(specId, 2, "Second task", undefined, [1]);
392
- approveSpec(specId);
393
-
394
- // Task 1 is still pending
395
- const result = validateGate("task-start", {
369
+ const result = await validateGate("task-start", {
396
370
  taskId: task2Id,
397
371
  specId,
398
372
  });
@@ -400,53 +374,50 @@ describe("Gate enforcement", () => {
400
374
  expect(result.reason).toContain("Dependencias");
401
375
  });
402
376
 
403
- it("should pass task-start when dependencies are done", () => {
404
- const db = getDb();
405
-
406
- const specId = setupSpec("Dependency Pass Test");
407
- const task1Id = addTask(specId, 1, "First task");
408
- const task2Id = addTask(specId, 2, "Second task", undefined, [1]);
409
- approveSpec(specId);
377
+ it("should pass task-start when dependencies are done", async () => {
378
+ const specId = await setupSpec("Dependency Pass Test");
379
+ const task1Id = await addTask(specId, 1, "First task");
380
+ const task2Id = await addTask(specId, 2, "Second task", undefined, [1]);
381
+ await approveSpec(specId);
410
382
 
411
- // Complete task 1
412
- startTask(task1Id);
413
- completeTask(task1Id, "First task completed successfully");
383
+ await startTask(task1Id);
384
+ await completeTask(task1Id, "First task completed successfully");
414
385
 
415
- const result = validateGate("task-start", {
386
+ const result = await validateGate("task-start", {
416
387
  taskId: task2Id,
417
388
  specId,
418
389
  });
419
390
  expect(result.passed).toBe(true);
420
391
  });
421
392
 
422
- it("should block review-start when tasks are pending", () => {
423
- const specId = setupSpec("Review Gate Test");
424
- addTask(specId, 1, "Incomplete task");
425
- approveSpec(specId);
393
+ it("should block review-start when tasks are pending", async () => {
394
+ const specId = await setupSpec("Review Gate Test");
395
+ await addTask(specId, 1, "Incomplete task");
396
+ await approveSpec(specId);
426
397
 
427
- const result = validateGate("review-start", { specId });
398
+ const result = await validateGate("review-start", { specId });
428
399
  expect(result.passed).toBe(false);
429
400
  expect(result.reason).toContain("Tasks pendentes");
430
401
  });
431
402
 
432
- it("should pass review-start when all tasks are done", () => {
433
- const specId = setupSpec("Review Pass Test");
434
- const taskId = addTask(specId, 1, "Only task");
435
- approveSpec(specId);
436
- startTask(taskId);
437
- completeTask(taskId, "Completed the only task successfully");
403
+ it("should pass review-start when all tasks are done", async () => {
404
+ const specId = await setupSpec("Review Pass Test");
405
+ const taskId = await addTask(specId, 1, "Only task");
406
+ await approveSpec(specId);
407
+ await startTask(taskId);
408
+ await completeTask(taskId, "Completed the only task successfully");
438
409
 
439
- const result = validateGate("review-start", { specId });
410
+ const result = await validateGate("review-start", { specId });
440
411
  expect(result.passed).toBe(true);
441
412
  });
442
413
 
443
- it("should require reasoning for completed subagent returns", () => {
444
- const specId = setupSpec("Reasoning Gate Test");
445
- const taskId = addTask(specId, 1, "Reasoning task");
446
- approveSpec(specId);
447
- startTask(taskId);
414
+ it("should require reasoning for completed subagent returns", async () => {
415
+ const specId = await setupSpec("Reasoning Gate Test");
416
+ const taskId = await addTask(specId, 1, "Reasoning task");
417
+ await approveSpec(specId);
418
+ await startTask(taskId);
448
419
 
449
- const result = validateGate("task-done", {
420
+ const result = await validateGate("task-done", {
450
421
  taskId,
451
422
  checkpoint: "This is a valid checkpoint text",
452
423
  files: [],
@@ -455,7 +426,7 @@ describe("Gate enforcement", () => {
455
426
  summary: "Done",
456
427
  files_created: [],
457
428
  files_modified: [],
458
- reasoning: { approach: "short" }, // < 20 chars
429
+ reasoning: { approach: "short" },
459
430
  },
460
431
  });
461
432
  expect(result.passed).toBe(false);
@@ -464,13 +435,13 @@ describe("Gate enforcement", () => {
464
435
  });
465
436
 
466
437
  describe("files-exist gate with sandbox", () => {
467
- it("should pass when subagent reports files that dont exist on disk", () => {
468
- const specId = setupSpec("Sandbox Files Test");
469
- const taskId = addTask(specId, 1, "Sandbox task");
470
- approveSpec(specId);
471
- startTask(taskId);
438
+ it("should pass when subagent reports files that dont exist on disk", async () => {
439
+ const specId = await setupSpec("Sandbox Files Test");
440
+ const taskId = await addTask(specId, 1, "Sandbox task");
441
+ await approveSpec(specId);
442
+ await startTask(taskId);
472
443
 
473
- const result = validateGate("task-done", {
444
+ const result = await validateGate("task-done", {
474
445
  taskId,
475
446
  checkpoint: "Completed sandbox task with new files created",
476
447
  files: ["src/sandbox/nonexistent-file.ts"],
@@ -485,36 +456,32 @@ describe("files-exist gate with sandbox", () => {
485
456
  expect(result.passed).toBe(true);
486
457
  });
487
458
 
488
- it("should fail when file not on disk and not from subagent", () => {
489
- const specId = setupSpec("Missing File Test");
490
- const taskId = addTask(specId, 1, "Missing file task");
491
- approveSpec(specId);
492
- startTask(taskId);
459
+ it("should fail when file not on disk and not from subagent", async () => {
460
+ const specId = await setupSpec("Missing File Test");
461
+ const taskId = await addTask(specId, 1, "Missing file task");
462
+ await approveSpec(specId);
463
+ await startTask(taskId);
493
464
 
494
- const result = validateGate("task-done", {
465
+ const result = await validateGate("task-done", {
495
466
  taskId,
496
467
  checkpoint: "Completed missing file task properly",
497
468
  files: ["src/this-file-definitely-does-not-exist-xyz.ts"],
498
- // No subagentData — file should exist on disk
499
469
  });
500
470
  expect(result.passed).toBe(false);
501
471
  expect(result.reason).toContain("nao encontrado");
502
472
  });
503
473
 
504
- it("should pass when subagent reports mix of existing and sandbox files", () => {
505
- const db = getDb();
506
- const specId = setupSpec("Mixed Files Test");
507
- const taskId = addTask(specId, 1, "Mixed files task");
508
- approveSpec(specId);
474
+ it("should pass when subagent reports mix of existing and sandbox files", async () => {
475
+ const specId = await setupSpec("Mixed Files Test");
476
+ const taskId = await addTask(specId, 1, "Mixed files task");
477
+ await approveSpec(specId);
509
478
 
510
- // Set started_at far in the past so mtime check passes for existing file
511
479
  const pastTime = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString();
512
- db.run("UPDATE tasks SET status = 'running', started_at = ? WHERE id = ?", [pastTime, taskId]);
480
+ await dbRun("UPDATE tasks SET status = 'running', started_at = ? WHERE id = ?", [pastTime, taskId]);
513
481
 
514
- // Use a file that actually exists (this test file itself)
515
482
  const existingFile = import.meta.path;
516
483
 
517
- const result = validateGate("task-done", {
484
+ const result = await validateGate("task-done", {
518
485
  taskId,
519
486
  checkpoint: "Completed mixed files task with both types",
520
487
  files: [existingFile, "src/sandbox/new-file.ts"],
@@ -526,79 +493,69 @@ describe("files-exist gate with sandbox", () => {
526
493
  reasoning: { approach: "Modified existing file and created new file for component" },
527
494
  },
528
495
  });
529
- // The existing file passes validation, the sandbox file is trusted via subagent
530
496
  expect(result.passed).toBe(true);
531
497
  });
532
498
  });
533
499
 
534
500
  describe("Review scoring", () => {
535
- it("should calculate perfect score when all tasks done and no bypasses", () => {
536
- const db = getDb();
537
-
538
- const specId = setupSpec("Perfect Score Test");
539
- const task1Id = addTask(specId, 1, "Task 1");
540
- const task2Id = addTask(specId, 2, "Task 2");
541
- approveSpec(specId);
542
-
543
- // Complete both tasks
544
- startTask(task1Id);
545
- completeTask(task1Id, "Task 1 done perfectly");
546
- startTask(task2Id);
547
- completeTask(task2Id, "Task 2 done perfectly");
548
-
549
- // Register some artifacts
550
- db.run(
501
+ it("should calculate perfect score when all tasks done and no bypasses", async () => {
502
+ const specId = await setupSpec("Perfect Score Test");
503
+ const task1Id = await addTask(specId, 1, "Task 1");
504
+ const task2Id = await addTask(specId, 2, "Task 2");
505
+ await approveSpec(specId);
506
+
507
+ await startTask(task1Id);
508
+ await completeTask(task1Id, "Task 1 done perfectly");
509
+ await startTask(task2Id);
510
+ await completeTask(task2Id, "Task 2 done perfectly");
511
+
512
+ await dbRun(
551
513
  "INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 1, 'src/a.ts', 'created')",
552
514
  [specId]
553
515
  );
554
- db.run(
516
+ await dbRun(
555
517
  "INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 2, 'src/b.ts', 'created')",
556
518
  [specId]
557
519
  );
558
520
 
559
- const score = calculateReviewScore(specId);
521
+ const score = await calculateReviewScore(specId);
560
522
  expect(score.breakdown.tasksCompleted).toBe(25);
561
523
  expect(score.breakdown.gatesPassedClean).toBe(25);
562
524
  expect(score.breakdown.standardsFollowed).toBe(25);
563
- expect(score.total).toBeGreaterThanOrEqual(75); // files_delivered depends on planned vs created
525
+ expect(score.total).toBeGreaterThanOrEqual(75);
564
526
  expect(score.autoApproveEligible).toBe(true);
565
527
  expect(score.mustReviewItems.length).toBe(0);
566
528
  });
567
529
 
568
- it("should reduce score when gates are bypassed", () => {
569
- const db = getDb();
570
-
571
- const specId = setupSpec("Bypass Score Test");
572
- const taskId = addTask(specId, 1, "Bypassed task");
573
- approveSpec(specId);
530
+ it("should reduce score when gates are bypassed", async () => {
531
+ const specId = await setupSpec("Bypass Score Test");
532
+ const taskId = await addTask(specId, 1, "Bypassed task");
533
+ await approveSpec(specId);
574
534
 
575
- startTask(taskId);
576
- completeTask(taskId, "Done with bypasses");
535
+ await startTask(taskId);
536
+ await completeTask(taskId, "Done with bypasses");
577
537
 
578
- // Register a critical bypass
579
- db.run(
538
+ await dbRun(
580
539
  "INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, ?, 'standards-follow', 'Test bypass')",
581
540
  [specId, taskId]
582
541
  );
583
542
 
584
- const score = calculateReviewScore(specId);
543
+ const score = await calculateReviewScore(specId);
585
544
  expect(score.breakdown.gatesPassedClean).toBeLessThan(25);
586
545
  expect(score.breakdown.standardsFollowed).toBeLessThan(25);
587
- expect(score.autoApproveEligible).toBe(false); // Critical bypass
546
+ expect(score.autoApproveEligible).toBe(false);
588
547
  expect(score.mustReviewItems.length).toBeGreaterThan(0);
589
548
  });
590
549
  });
591
550
 
592
551
  describe("processSubagentReturn", () => {
593
- it("should handle blocked status with blockers as critical knowledge", () => {
594
- const db = getDb();
552
+ it("should handle blocked status with blockers as critical knowledge", async () => {
553
+ const specId = await setupSpec("Blocked Task Test");
554
+ const taskId = await addTask(specId, 1, "Blocked task");
555
+ await approveSpec(specId);
556
+ await startTask(taskId);
595
557
 
596
- const specId = setupSpec("Blocked Task Test");
597
- const taskId = addTask(specId, 1, "Blocked task");
598
- approveSpec(specId);
599
- startTask(taskId);
600
-
601
- const result = processSubagentReturn(specId, taskId, 1, {
558
+ const result = await processSubagentReturn(specId, taskId, 1, {
602
559
  status: "blocked",
603
560
  summary: "Blocked by missing API key configuration",
604
561
  files_created: [],
@@ -612,22 +569,20 @@ describe("processSubagentReturn", () => {
612
569
  expect(result.knowledgeAdded).toBe(2);
613
570
  expect(result.artifactsAdded).toBe(0);
614
571
 
615
- // Verify blockers stored as critical knowledge
616
- const blockers = db.query(
617
- "SELECT * FROM knowledge WHERE spec_id = ? AND category = 'blocker' AND severity = 'critical'"
618
- ).all(specId) as any[];
572
+ const blockers = await dbAll<any>(
573
+ "SELECT * FROM knowledge WHERE spec_id = ? AND category = 'blocker' AND severity = 'critical'",
574
+ [specId]
575
+ );
619
576
  expect(blockers.length).toBe(2);
620
577
  });
621
578
 
622
- it("should store all artifacts from files_created and files_modified", () => {
623
- const db = getDb();
579
+ it("should store all artifacts from files_created and files_modified", async () => {
580
+ const specId = await setupSpec("Artifacts Test");
581
+ const taskId = await addTask(specId, 1, "Artifact task");
582
+ await approveSpec(specId);
583
+ await startTask(taskId);
624
584
 
625
- const specId = setupSpec("Artifacts Test");
626
- const taskId = addTask(specId, 1, "Artifact task");
627
- approveSpec(specId);
628
- startTask(taskId);
629
-
630
- const result = processSubagentReturn(specId, taskId, 1, {
585
+ const result = await processSubagentReturn(specId, taskId, 1, {
631
586
  status: "completed",
632
587
  summary: "Created and modified multiple files for the feature",
633
588
  files_created: ["src/new1.ts", "src/new2.ts"],
@@ -637,9 +592,10 @@ describe("processSubagentReturn", () => {
637
592
 
638
593
  expect(result.artifactsAdded).toBe(3);
639
594
 
640
- const artifacts = db.query(
641
- "SELECT * FROM artifacts WHERE spec_id = ? ORDER BY path"
642
- ).all(specId) as any[];
595
+ const artifacts = await dbAll<any>(
596
+ "SELECT * FROM artifacts WHERE spec_id = ? ORDER BY path",
597
+ [specId]
598
+ );
643
599
  expect(artifacts.length).toBe(3);
644
600
 
645
601
  const created = artifacts.filter((a: any) => a.action === "created");
@@ -648,15 +604,13 @@ describe("processSubagentReturn", () => {
648
604
  expect(modified.length).toBe(1);
649
605
  });
650
606
 
651
- it("should build knowledge graph relations", () => {
652
- const db = getDb();
607
+ it("should build knowledge graph relations", async () => {
608
+ const specId = await setupSpec("Graph Test");
609
+ const taskId = await addTask(specId, 1, "Graph task");
610
+ await approveSpec(specId);
611
+ await startTask(taskId);
653
612
 
654
- const specId = setupSpec("Graph Test");
655
- const taskId = addTask(specId, 1, "Graph task");
656
- approveSpec(specId);
657
- startTask(taskId);
658
-
659
- processSubagentReturn(specId, taskId, 1, {
613
+ await processSubagentReturn(specId, taskId, 1, {
660
614
  status: "completed",
661
615
  summary: "Created files with patterns for the component",
662
616
  files_created: ["src/api.ts"],
@@ -665,12 +619,11 @@ describe("processSubagentReturn", () => {
665
619
  patterns_discovered: ["REST endpoint pattern"],
666
620
  });
667
621
 
668
- const relations = db.query(
669
- "SELECT * FROM knowledge_graph WHERE spec_id = ?"
670
- ).all(specId) as any[];
622
+ const relations = await dbAll<any>(
623
+ "SELECT * FROM knowledge_graph WHERE spec_id = ?",
624
+ [specId]
625
+ );
671
626
 
672
- // Should have: task->api.ts(creates), task->config.ts(modifies),
673
- // pattern->api.ts(extracted_from)
674
627
  const creates = relations.filter((r: any) => r.relation === "creates");
675
628
  const modifies = relations.filter((r: any) => r.relation === "modifies");
676
629
  const extracted = relations.filter((r: any) => r.relation === "extracted_from");
@@ -682,42 +635,36 @@ describe("processSubagentReturn", () => {
682
635
  });
683
636
 
684
637
  describe("Multi-spec parallel", () => {
685
- it("should allow multiple specs to coexist", () => {
686
- const db = getDb();
638
+ it("should allow multiple specs to coexist", async () => {
639
+ const specA = await setupSpec("Feature A");
640
+ const specB = await setupSpec("Feature B");
687
641
 
688
- const specA = setupSpec("Feature A");
689
- const specB = setupSpec("Feature B");
642
+ await addTask(specA, 1, "Task A1");
643
+ await addTask(specB, 1, "Task B1");
690
644
 
691
- addTask(specA, 1, "Task A1");
692
- addTask(specB, 1, "Task B1");
693
-
694
- // Both should exist
695
- const specs = db.query(
645
+ const specs = await dbAll<any>(
696
646
  "SELECT * FROM specs WHERE phase NOT IN ('completed', 'cancelled')"
697
- ).all() as any[];
647
+ );
698
648
  expect(specs.length).toBe(2);
699
649
 
700
- // Tasks should be isolated per spec
701
- const tasksA = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(specA) as any[];
702
- const tasksB = db.query("SELECT * FROM tasks WHERE spec_id = ?").all(specB) as any[];
650
+ const tasksA = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ?", [specA]);
651
+ const tasksB = await dbAll<any>("SELECT * FROM tasks WHERE spec_id = ?", [specB]);
703
652
  expect(tasksA.length).toBe(1);
704
653
  expect(tasksB.length).toBe(1);
705
654
  });
706
655
 
707
- it("should isolate knowledge per spec", () => {
708
- const db = getDb();
709
-
710
- const specA = setupSpec("Feature A");
711
- const specB = setupSpec("Feature B");
712
- const taskA = addTask(specA, 1, "Task A1");
713
- const taskB = addTask(specB, 1, "Task B1");
656
+ it("should isolate knowledge per spec", async () => {
657
+ const specA = await setupSpec("Feature A");
658
+ const specB = await setupSpec("Feature B");
659
+ const taskA = await addTask(specA, 1, "Task A1");
660
+ const taskB = await addTask(specB, 1, "Task B1");
714
661
 
715
- approveSpec(specA);
716
- approveSpec(specB);
717
- startTask(taskA);
718
- startTask(taskB);
662
+ await approveSpec(specA);
663
+ await approveSpec(specB);
664
+ await startTask(taskA);
665
+ await startTask(taskB);
719
666
 
720
- processSubagentReturn(specA, taskA, 1, {
667
+ await processSubagentReturn(specA, taskA, 1, {
721
668
  status: "completed",
722
669
  summary: "Done with Feature A task and found important info",
723
670
  files_created: ["src/a.ts"],
@@ -728,7 +675,7 @@ describe("Multi-spec parallel", () => {
728
675
  ],
729
676
  });
730
677
 
731
- processSubagentReturn(specB, taskB, 1, {
678
+ await processSubagentReturn(specB, taskB, 1, {
732
679
  status: "completed",
733
680
  summary: "Done with Feature B task and found other info",
734
681
  files_created: ["src/b.ts"],
@@ -739,12 +686,14 @@ describe("Multi-spec parallel", () => {
739
686
  ],
740
687
  });
741
688
 
742
- const knowledgeA = db.query(
743
- "SELECT * FROM knowledge WHERE spec_id = ?"
744
- ).all(specA) as any[];
745
- const knowledgeB = db.query(
746
- "SELECT * FROM knowledge WHERE spec_id = ?"
747
- ).all(specB) as any[];
689
+ const knowledgeA = await dbAll<any>(
690
+ "SELECT * FROM knowledge WHERE spec_id = ?",
691
+ [specA]
692
+ );
693
+ const knowledgeB = await dbAll<any>(
694
+ "SELECT * FROM knowledge WHERE spec_id = ?",
695
+ [specB]
696
+ );
748
697
 
749
698
  expect(knowledgeA.length).toBeGreaterThanOrEqual(1);
750
699
  expect(knowledgeB.length).toBeGreaterThanOrEqual(1);