@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/db/schema.test.ts CHANGED
@@ -1,32 +1,21 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { Database } from "bun:sqlite";
3
- import { mkdirSync, rmSync, existsSync } from "fs";
4
- import { join } from "path";
5
-
6
- // Setup: use a temporary directory for test databases
7
- const TEST_DIR = join(import.meta.dir, "..", ".test-db");
8
- const TEST_DB_PATH = join(TEST_DIR, "test.db");
9
-
10
- // We need to override the DB path before importing schema functions.
11
- // The connection module uses process.cwd() so we mock via direct DB manipulation.
2
+ import { createClient, type Client } from "@libsql/client";
3
+ import { setClient, resetClient, dbGet, dbAll, dbRun, dbExec } from "./connection";
12
4
 
13
5
  describe("Migration System", () => {
14
- let db: Database;
6
+ let client: Client;
15
7
 
16
8
  beforeEach(() => {
17
- // Create in-memory database with same schema setup
18
- db = new Database(":memory:");
19
- db.exec("PRAGMA journal_mode = WAL");
20
- db.exec("PRAGMA foreign_keys = ON");
9
+ client = createClient({ url: ":memory:" });
10
+ setClient(client);
21
11
  });
22
12
 
23
13
  afterEach(() => {
24
- db.close();
14
+ resetClient();
25
15
  });
26
16
 
27
- function createBaseTables(db: Database) {
28
- // Minimal table creation to test migrations against
29
- db.exec(`
17
+ async function createBaseTables() {
18
+ await dbExec(`
30
19
  CREATE TABLE IF NOT EXISTS specs (
31
20
  id TEXT PRIMARY KEY,
32
21
  name TEXT NOT NULL,
@@ -34,16 +23,18 @@ describe("Migration System", () => {
34
23
  approved_at TEXT,
35
24
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
36
25
  updated_at TEXT
37
- );
38
-
26
+ )
27
+ `);
28
+ await dbExec(`
39
29
  CREATE TABLE IF NOT EXISTS project (
40
30
  id TEXT PRIMARY KEY DEFAULT 'default',
41
31
  name TEXT,
42
32
  stack TEXT NOT NULL,
43
33
  discovered_at TEXT,
44
34
  updated_at TEXT
45
- );
46
-
35
+ )
36
+ `);
37
+ await dbExec(`
47
38
  CREATE TABLE IF NOT EXISTS tasks (
48
39
  id INTEGER PRIMARY KEY AUTOINCREMENT,
49
40
  spec_id TEXT NOT NULL REFERENCES specs(id),
@@ -57,8 +48,9 @@ describe("Migration System", () => {
57
48
  checkpoint TEXT,
58
49
  completed_at TEXT,
59
50
  UNIQUE(spec_id, number)
60
- );
61
-
51
+ )
52
+ `);
53
+ await dbExec(`
62
54
  CREATE TABLE IF NOT EXISTS decisions (
63
55
  id TEXT PRIMARY KEY,
64
56
  spec_id TEXT NOT NULL REFERENCES specs(id),
@@ -68,103 +60,101 @@ describe("Migration System", () => {
68
60
  rationale TEXT,
69
61
  status TEXT DEFAULT 'active',
70
62
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
71
- );
72
-
63
+ )
64
+ `);
65
+ await dbExec(`
73
66
  CREATE TABLE IF NOT EXISTS schema_migrations (
74
67
  version TEXT PRIMARY KEY,
75
68
  description TEXT NOT NULL,
76
69
  applied_at TEXT DEFAULT CURRENT_TIMESTAMP
77
- );
70
+ )
78
71
  `);
79
72
  }
80
73
 
81
- // ═══════════════════════════════════════════════════════════════
82
- // runMigrations() tests
83
- // ═══════════════════════════════════════════════════════════════
84
-
85
74
  describe("runMigrations()", () => {
86
- it("should apply all migrations on a fresh database", () => {
87
- createBaseTables(db);
75
+ it("should apply all migrations on a fresh database", async () => {
76
+ await createBaseTables();
88
77
 
89
- // Define test migrations
90
78
  const migrations = [
91
79
  {
92
80
  version: "8.4.0",
93
81
  description: "Add analysis_id to specs",
94
- up: (d: Database) => d.exec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
82
+ up: async () => await dbExec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
95
83
  },
96
84
  {
97
85
  version: "8.7.0",
98
86
  description: "Add cli_version to project",
99
- up: (d: Database) => d.exec("ALTER TABLE project ADD COLUMN cli_version TEXT"),
87
+ up: async () => await dbExec("ALTER TABLE project ADD COLUMN cli_version TEXT"),
100
88
  },
101
89
  ];
102
90
 
103
- // Run migrations manually (simulating runMigrations logic)
104
91
  for (const migration of migrations) {
105
- const existing = db.query("SELECT version FROM schema_migrations WHERE version = ?").get(migration.version);
92
+ const existing = await dbGet<{ version: string }>(
93
+ "SELECT version FROM schema_migrations WHERE version = ?",
94
+ [migration.version]
95
+ );
106
96
  if (existing) continue;
107
97
 
108
- migration.up(db);
109
- db.run(
98
+ await migration.up();
99
+ await dbRun(
110
100
  "INSERT INTO schema_migrations (version, description) VALUES (?, ?)",
111
101
  [migration.version, migration.description]
112
102
  );
113
103
  }
114
104
 
115
- // Verify all migrations were recorded
116
- const applied = db.query("SELECT * FROM schema_migrations ORDER BY version").all() as any[];
105
+ const applied = await dbAll<{ version: string }>(
106
+ "SELECT * FROM schema_migrations ORDER BY version"
107
+ );
117
108
  expect(applied.length).toBe(2);
118
109
  expect(applied[0].version).toBe("8.4.0");
119
110
  expect(applied[1].version).toBe("8.7.0");
120
111
 
121
- // Verify columns were added
122
- const columns = db.query("PRAGMA table_info(specs)").all() as any[];
123
- const colNames = columns.map((c: any) => c.name);
112
+ const columns = await dbAll<{ name: string }>("PRAGMA table_info(specs)");
113
+ const colNames = columns.map((c) => c.name);
124
114
  expect(colNames).toContain("analysis_id");
125
115
  });
126
116
 
127
- it("should be idempotent — calling twice applies each migration only once", () => {
128
- createBaseTables(db);
117
+ it("should be idempotent — calling twice applies each migration only once", async () => {
118
+ await createBaseTables();
129
119
 
130
120
  const migration = {
131
121
  version: "8.4.0",
132
122
  description: "Add analysis_id to specs",
133
- up: (d: Database) => d.exec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
123
+ up: async () => await dbExec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
134
124
  };
135
125
 
136
- // First run
137
- migration.up(db);
138
- db.run(
126
+ await migration.up();
127
+ await dbRun(
139
128
  "INSERT INTO schema_migrations (version, description) VALUES (?, ?)",
140
129
  [migration.version, migration.description]
141
130
  );
142
131
 
143
- // Second run should skip because already applied
144
- const existing = db.query("SELECT version FROM schema_migrations WHERE version = ?").get(migration.version);
132
+ const existing = await dbGet<{ version: string }>(
133
+ "SELECT version FROM schema_migrations WHERE version = ?",
134
+ [migration.version]
135
+ );
145
136
  expect(existing).not.toBeNull();
146
137
 
147
- // Verify only 1 record
148
- const applied = db.query("SELECT COUNT(*) as c FROM schema_migrations").get() as any;
149
- expect(applied.c).toBe(1);
138
+ const applied = await dbGet<{ c: number }>(
139
+ "SELECT COUNT(*) as c FROM schema_migrations"
140
+ );
141
+ expect(applied!.c).toBe(1);
150
142
  });
151
143
 
152
- it("should absorb 'duplicate column name' errors from pre-migration databases", () => {
153
- createBaseTables(db);
144
+ it("should absorb 'duplicate column name' errors from pre-migration databases", async () => {
145
+ await createBaseTables();
154
146
 
155
- // Manually add the column first (simulating pre-migration DB)
156
- db.exec("ALTER TABLE specs ADD COLUMN analysis_id TEXT");
147
+ await dbExec("ALTER TABLE specs ADD COLUMN analysis_id TEXT");
157
148
 
158
- // Now try to run the migration — should NOT throw
159
149
  const migration = {
160
150
  version: "8.4.0",
161
151
  description: "Add analysis_id to specs",
162
- up: (d: Database) => d.exec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
152
+ up: async () => await dbExec("ALTER TABLE specs ADD COLUMN analysis_id TEXT"),
163
153
  };
164
154
 
165
155
  let absorbed = false;
166
156
  try {
167
- migration.up(db);
157
+ await migration.up();
168
158
  } catch (e: any) {
169
159
  if (e.message.includes("duplicate column name")) {
170
160
  absorbed = true;
@@ -175,41 +165,36 @@ describe("Migration System", () => {
175
165
 
176
166
  expect(absorbed).toBe(true);
177
167
 
178
- // Record it anyway
179
- db.run(
168
+ await dbRun(
180
169
  "INSERT INTO schema_migrations (version, description) VALUES (?, ?)",
181
170
  [migration.version, migration.description]
182
171
  );
183
172
 
184
- const applied = db.query("SELECT COUNT(*) as c FROM schema_migrations").get() as any;
185
- expect(applied.c).toBe(1);
173
+ const applied = await dbGet<{ c: number }>(
174
+ "SELECT COUNT(*) as c FROM schema_migrations"
175
+ );
176
+ expect(applied!.c).toBe(1);
186
177
  });
187
178
 
188
- it("should propagate real errors (not duplicate column)", () => {
189
- createBaseTables(db);
179
+ it("should propagate real errors (not duplicate column)", async () => {
180
+ await createBaseTables();
190
181
 
191
182
  const badMigration = {
192
183
  version: "99.0.0",
193
184
  description: "Bad migration",
194
- up: (d: Database) => d.exec("ALTER TABLE nonexistent_table ADD COLUMN foo TEXT"),
185
+ up: async () => await dbExec("ALTER TABLE nonexistent_table ADD COLUMN foo TEXT"),
195
186
  };
196
187
 
197
- expect(() => {
198
- badMigration.up(db);
199
- }).toThrow();
188
+ expect(badMigration.up()).rejects.toThrow();
200
189
 
201
- // Migration should NOT be recorded
202
- const applied = db.query("SELECT COUNT(*) as c FROM schema_migrations").get() as any;
203
- expect(applied.c).toBe(0);
190
+ const applied = await dbGet<{ c: number }>(
191
+ "SELECT COUNT(*) as c FROM schema_migrations"
192
+ );
193
+ expect(applied!.c).toBe(0);
204
194
  });
205
195
  });
206
196
 
207
- // ═══════════════════════════════════════════════════════════════
208
- // getNextDecisionId() tests
209
- // ═══════════════════════════════════════════════════════════════
210
-
211
197
  describe("getNextDecisionId()", () => {
212
- // Reimplemented: uses timestamp+random instead of sequential count
213
198
  function getNextDecisionId(specId: string): string {
214
199
  const slug = specId.split("-").slice(1, 3).join("-");
215
200
  const ts = Date.now().toString(36);
@@ -234,7 +219,6 @@ describe("Migration System", () => {
234
219
  });
235
220
 
236
221
  it("should not require DB access (no race condition)", () => {
237
- // Generate 100 IDs rapidly — all should be unique
238
222
  const ids = new Set<string>();
239
223
  for (let i = 0; i < 100; i++) {
240
224
  ids.add(getNextDecisionId("SPEC-001"));
@@ -243,76 +227,69 @@ describe("Migration System", () => {
243
227
  });
244
228
  });
245
229
 
246
- // ═══════════════════════════════════════════════════════════════
247
- // claimTask() tests
248
- // ═══════════════════════════════════════════════════════════════
249
-
250
230
  describe("claimTask()", () => {
251
- function claimTask(taskId: number): boolean {
252
- const result = db.run(
231
+ async function claimTask(taskId: number): Promise<boolean> {
232
+ const result = await dbRun(
253
233
  "UPDATE tasks SET status = 'running' WHERE id = ? AND status = 'pending'",
254
234
  [taskId]
255
235
  );
256
236
  return result.changes > 0;
257
237
  }
258
238
 
259
- beforeEach(() => {
260
- createBaseTables(db);
261
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
262
- db.run(
239
+ beforeEach(async () => {
240
+ await createBaseTables();
241
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
242
+ await dbRun(
263
243
  "INSERT INTO tasks (spec_id, number, name, status) VALUES (?, ?, ?, ?)",
264
244
  ["SPEC-001", 1, "Test task", "pending"]
265
245
  );
266
246
  });
267
247
 
268
- it("should return true and set status to running for a pending task", () => {
269
- const taskId = (db.query("SELECT id FROM tasks WHERE number = 1").get() as any).id;
270
- expect(claimTask(taskId)).toBe(true);
248
+ it("should return true and set status to running for a pending task", async () => {
249
+ const row = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 1");
250
+ const taskId = row!.id;
251
+ expect(await claimTask(taskId)).toBe(true);
271
252
 
272
- const task = db.query("SELECT status FROM tasks WHERE id = ?").get(taskId) as any;
273
- expect(task.status).toBe("running");
253
+ const task = await dbGet<{ status: string }>("SELECT status FROM tasks WHERE id = ?", [taskId]);
254
+ expect(task!.status).toBe("running");
274
255
  });
275
256
 
276
- it("should return false for a task already in running status", () => {
277
- const taskId = (db.query("SELECT id FROM tasks WHERE number = 1").get() as any).id;
257
+ it("should return false for a task already in running status", async () => {
258
+ const row = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 1");
259
+ const taskId = row!.id;
278
260
 
279
- // First claim succeeds
280
- expect(claimTask(taskId)).toBe(true);
281
- // Second claim fails
282
- expect(claimTask(taskId)).toBe(false);
261
+ expect(await claimTask(taskId)).toBe(true);
262
+ expect(await claimTask(taskId)).toBe(false);
283
263
  });
284
264
 
285
- it("should return false for a task in done status", () => {
286
- const taskId = (db.query("SELECT id FROM tasks WHERE number = 1").get() as any).id;
287
- db.run("UPDATE tasks SET status = 'done' WHERE id = ?", [taskId]);
265
+ it("should return false for a task in done status", async () => {
266
+ const row = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 1");
267
+ const taskId = row!.id;
268
+ await dbRun("UPDATE tasks SET status = 'done' WHERE id = ?", [taskId]);
288
269
 
289
- expect(claimTask(taskId)).toBe(false);
270
+ expect(await claimTask(taskId)).toBe(false);
290
271
  });
291
272
 
292
- it("should return false for a non-existent task ID", () => {
293
- expect(claimTask(99999)).toBe(false);
273
+ it("should return false for a non-existent task ID", async () => {
274
+ expect(await claimTask(99999)).toBe(false);
294
275
  });
295
276
 
296
- it("should prevent double-claim (only first caller wins)", () => {
297
- const taskId = (db.query("SELECT id FROM tasks WHERE number = 1").get() as any).id;
277
+ it("should prevent double-claim (only first caller wins)", async () => {
278
+ const row = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 1");
279
+ const taskId = row!.id;
298
280
 
299
- // Simulate two concurrent claims
300
- const claim1 = claimTask(taskId);
301
- const claim2 = claimTask(taskId);
281
+ const claim1 = await claimTask(taskId);
282
+ const claim2 = await claimTask(taskId);
302
283
 
303
284
  expect(claim1).toBe(true);
304
285
  expect(claim2).toBe(false);
305
286
  });
306
287
  });
307
288
 
308
- // ═══════════════════════════════════════════════════════════════
309
- // v9.3: Agent Performance (P3.2)
310
- // ═══════════════════════════════════════════════════════════════
311
-
312
289
  describe("agent_performance", () => {
313
- function createPerformanceTables(db: Database) {
314
- createBaseTables(db);
315
- db.exec(`
290
+ async function createPerformanceTables() {
291
+ await createBaseTables();
292
+ await dbExec(`
316
293
  CREATE TABLE IF NOT EXISTS agent_performance (
317
294
  id INTEGER PRIMARY KEY AUTOINCREMENT,
318
295
  agent_type TEXT NOT NULL,
@@ -328,9 +305,8 @@ describe("Migration System", () => {
328
305
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
329
306
  )
330
307
  `);
331
- db.exec(`CREATE INDEX IF NOT EXISTS idx_agent_perf_type ON agent_performance(agent_type)`);
332
-
333
- db.exec(`
308
+ await dbExec("CREATE INDEX IF NOT EXISTS idx_agent_perf_type ON agent_performance(agent_type)");
309
+ await dbExec(`
334
310
  CREATE TABLE IF NOT EXISTS gate_bypasses (
335
311
  id INTEGER PRIMARY KEY AUTOINCREMENT,
336
312
  spec_id TEXT,
@@ -342,11 +318,11 @@ describe("Migration System", () => {
342
318
  `);
343
319
  }
344
320
 
345
- it("migration 9.3.0 should create agent_performance table", () => {
346
- createPerformanceTables(db);
321
+ it("migration 9.3.0 should create agent_performance table", async () => {
322
+ await createPerformanceTables();
347
323
 
348
- const columns = db.query("PRAGMA table_info(agent_performance)").all() as any[];
349
- const colNames = columns.map((c: any) => c.name);
324
+ const columns = await dbAll<{ name: string }>("PRAGMA table_info(agent_performance)");
325
+ const colNames = columns.map((c) => c.name);
350
326
  expect(colNames).toContain("agent_type");
351
327
  expect(colNames).toContain("spec_id");
352
328
  expect(colNames).toContain("task_id");
@@ -356,46 +332,49 @@ describe("Migration System", () => {
356
332
  expect(colNames).toContain("execution_duration_ms");
357
333
  });
358
334
 
359
- it("should insert and retrieve performance data", () => {
360
- createPerformanceTables(db);
361
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
362
- db.run(
335
+ it("should insert and retrieve performance data", async () => {
336
+ await createPerformanceTables();
337
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
338
+ await dbRun(
363
339
  "INSERT INTO tasks (spec_id, number, name, agent, status) VALUES (?, ?, ?, ?, ?)",
364
340
  ["SPEC-001", 1, "Test task", "frontend-next", "done"]
365
341
  );
366
342
 
367
343
  const now = new Date().toISOString();
368
- db.run(
344
+ await dbRun(
369
345
  `INSERT INTO agent_performance
370
346
  (agent_type, spec_id, task_id, gates_passed_first_try, gates_total, bypasses_used, files_created, files_modified, context_size_bytes, execution_duration_ms, created_at)
371
347
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
372
348
  ["frontend-next", "SPEC-001", 1, 7, 7, 0, 3, 1, 4096, 15000, now]
373
349
  );
374
350
 
375
- const rows = db.query("SELECT * FROM agent_performance WHERE agent_type = ?").all("frontend-next") as any[];
351
+ const rows = await dbAll<any>(
352
+ "SELECT * FROM agent_performance WHERE agent_type = ?",
353
+ ["frontend-next"]
354
+ );
376
355
  expect(rows).toHaveLength(1);
377
356
  expect(rows[0].gates_passed_first_try).toBe(7);
378
357
  expect(rows[0].bypasses_used).toBe(0);
379
358
  expect(rows[0].execution_duration_ms).toBe(15000);
380
359
  });
381
360
 
382
- it("should compute hints: no data returns empty", () => {
383
- createPerformanceTables(db);
361
+ it("should compute hints: no data returns empty", async () => {
362
+ await createPerformanceTables();
384
363
 
385
- const recent = db.query(
386
- "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5"
387
- ).all("nonexistent") as any[];
364
+ const recent = await dbAll<any>(
365
+ "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5",
366
+ ["nonexistent"]
367
+ );
388
368
  expect(recent).toHaveLength(0);
389
369
  });
390
370
 
391
- it("should detect high bypass rate from performance data", () => {
392
- createPerformanceTables(db);
393
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
371
+ it("should detect high bypass rate from performance data", async () => {
372
+ await createPerformanceTables();
373
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
394
374
 
395
375
  const now = new Date().toISOString();
396
- // Insert 3 records with bypasses
397
376
  for (let i = 1; i <= 3; i++) {
398
- db.run(
377
+ await dbRun(
399
378
  `INSERT INTO agent_performance
400
379
  (agent_type, spec_id, task_id, gates_passed_first_try, gates_total, bypasses_used, created_at)
401
380
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -403,9 +382,10 @@ describe("Migration System", () => {
403
382
  );
404
383
  }
405
384
 
406
- const recent = db.query(
407
- "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5"
408
- ).all("backend-go") as any[];
385
+ const recent = await dbAll<any>(
386
+ "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5",
387
+ ["backend-go"]
388
+ );
409
389
 
410
390
  const avgBypass = recent.reduce((sum: number, r: any) => sum + r.bypasses_used, 0) / recent.length;
411
391
  expect(avgBypass).toBe(2);
@@ -415,16 +395,16 @@ describe("Migration System", () => {
415
395
  return sum + (r.gates_total > 0 ? r.gates_passed_first_try / r.gates_total : 1);
416
396
  }, 0) / recent.length;
417
397
  expect(avgGateRate).toBeCloseTo(5 / 7, 2);
418
- expect(avgGateRate < 0.7).toBe(false); // 5/7 ≈ 0.71, just above threshold
398
+ expect(avgGateRate < 0.7).toBe(false);
419
399
  });
420
400
 
421
- it("should detect low gate pass rate", () => {
422
- createPerformanceTables(db);
423
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
401
+ it("should detect low gate pass rate", async () => {
402
+ await createPerformanceTables();
403
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
424
404
 
425
405
  const now = new Date().toISOString();
426
406
  for (let i = 1; i <= 3; i++) {
427
- db.run(
407
+ await dbRun(
428
408
  `INSERT INTO agent_performance
429
409
  (agent_type, spec_id, task_id, gates_passed_first_try, gates_total, bypasses_used, created_at)
430
410
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -432,45 +412,52 @@ describe("Migration System", () => {
432
412
  );
433
413
  }
434
414
 
435
- const recent = db.query(
436
- "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5"
437
- ).all("backend-csharp") as any[];
415
+ const recent = await dbAll<any>(
416
+ "SELECT * FROM agent_performance WHERE agent_type = ? ORDER BY created_at DESC LIMIT 5",
417
+ ["backend-csharp"]
418
+ );
438
419
 
439
420
  const avgGateRate = recent.reduce((sum: number, r: any) => {
440
421
  return sum + (r.gates_total > 0 ? r.gates_passed_first_try / r.gates_total : 1);
441
422
  }, 0) / recent.length;
442
423
  expect(avgGateRate).toBeCloseTo(3 / 7, 2);
443
- expect(avgGateRate < 0.7).toBe(true); // 3/7 ≈ 0.43 — should trigger hint
424
+ expect(avgGateRate < 0.7).toBe(true);
444
425
  });
445
426
 
446
- it("should track frequent gate bypass types", () => {
447
- createPerformanceTables(db);
448
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
449
- db.run(
427
+ it("should track frequent gate bypass types", async () => {
428
+ await createPerformanceTables();
429
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
430
+ await dbRun(
450
431
  "INSERT INTO tasks (spec_id, number, name, agent, status) VALUES (?, ?, ?, ?, ?)",
451
432
  ["SPEC-001", 1, "Task 1", "frontend-next", "done"]
452
433
  );
453
- db.run(
434
+ await dbRun(
454
435
  "INSERT INTO tasks (spec_id, number, name, agent, status) VALUES (?, ?, ?, ?, ?)",
455
436
  ["SPEC-001", 2, "Task 2", "frontend-next", "done"]
456
437
  );
457
438
 
458
- const taskId1 = (db.query("SELECT id FROM tasks WHERE number = 1").get() as any).id;
459
- const taskId2 = (db.query("SELECT id FROM tasks WHERE number = 2").get() as any).id;
439
+ const row1 = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 1");
440
+ const row2 = await dbGet<{ id: number }>("SELECT id FROM tasks WHERE number = 2");
441
+ const taskId1 = row1!.id;
442
+ const taskId2 = row2!.id;
460
443
 
461
- // Add bypasses for same gate
462
- db.run("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, ?, ?, ?)",
463
- ["SPEC-001", taskId1, "standards-follow", "test"]);
464
- db.run("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, ?, ?, ?)",
465
- ["SPEC-001", taskId2, "standards-follow", "test"]);
444
+ await dbRun(
445
+ "INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, ?, ?, ?)",
446
+ ["SPEC-001", taskId1, "standards-follow", "test"]
447
+ );
448
+ await dbRun(
449
+ "INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, ?, ?, ?)",
450
+ ["SPEC-001", taskId2, "standards-follow", "test"]
451
+ );
466
452
 
467
- const bypassTypes = db.query(
453
+ const bypassTypes = await dbAll<any>(
468
454
  `SELECT gb.gate_name, COUNT(*) as cnt FROM gate_bypasses gb
469
455
  JOIN tasks t ON gb.task_id = t.id
470
456
  WHERE t.agent = ?
471
457
  GROUP BY gb.gate_name
472
- ORDER BY cnt DESC LIMIT 3`
473
- ).all("frontend-next") as any[];
458
+ ORDER BY cnt DESC LIMIT 3`,
459
+ ["frontend-next"]
460
+ );
474
461
 
475
462
  expect(bypassTypes).toHaveLength(1);
476
463
  expect(bypassTypes[0].gate_name).toBe("standards-follow");
@@ -478,14 +465,10 @@ describe("Migration System", () => {
478
465
  });
479
466
  });
480
467
 
481
- // ═══════════════════════════════════════════════════════════════
482
- // v9.4: Knowledge Acknowledgments (P1-5)
483
- // ═══════════════════════════════════════════════════════════════
484
-
485
468
  describe("knowledge_acknowledgments", () => {
486
- function createKnowledgeTables(db: Database) {
487
- createBaseTables(db);
488
- db.exec(`
469
+ async function createKnowledgeTables() {
470
+ await createBaseTables();
471
+ await dbExec(`
489
472
  CREATE TABLE IF NOT EXISTS knowledge (
490
473
  id INTEGER PRIMARY KEY AUTOINCREMENT,
491
474
  spec_id TEXT NOT NULL,
@@ -497,7 +480,7 @@ describe("Migration System", () => {
497
480
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
498
481
  )
499
482
  `);
500
- db.exec(`
483
+ await dbExec(`
501
484
  CREATE TABLE IF NOT EXISTS knowledge_acknowledgments (
502
485
  knowledge_id INTEGER NOT NULL REFERENCES knowledge(id) ON DELETE CASCADE,
503
486
  task_id INTEGER NOT NULL,
@@ -505,72 +488,72 @@ describe("Migration System", () => {
505
488
  PRIMARY KEY (knowledge_id, task_id)
506
489
  )
507
490
  `);
508
- db.exec(`CREATE INDEX IF NOT EXISTS idx_ka_task ON knowledge_acknowledgments(task_id)`);
491
+ await dbExec("CREATE INDEX IF NOT EXISTS idx_ka_task ON knowledge_acknowledgments(task_id)");
509
492
  }
510
493
 
511
- it("migration 9.4.0 should create knowledge_acknowledgments table", () => {
512
- createKnowledgeTables(db);
494
+ it("migration 9.4.0 should create knowledge_acknowledgments table", async () => {
495
+ await createKnowledgeTables();
513
496
 
514
- const columns = db.query("PRAGMA table_info(knowledge_acknowledgments)").all() as any[];
515
- const colNames = columns.map((c: any) => c.name);
497
+ const columns = await dbAll<{ name: string }>("PRAGMA table_info(knowledge_acknowledgments)");
498
+ const colNames = columns.map((c) => c.name);
516
499
  expect(colNames).toContain("knowledge_id");
517
500
  expect(colNames).toContain("task_id");
518
501
  expect(colNames).toContain("acknowledged_at");
519
502
  });
520
503
 
521
- it("should insert and query acknowledgment", () => {
522
- createKnowledgeTables(db);
523
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
524
- db.run(
504
+ it("should insert and query acknowledgment", async () => {
505
+ await createKnowledgeTables();
506
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
507
+ await dbRun(
525
508
  "INSERT INTO knowledge (spec_id, task_origin, category, content, severity) VALUES (?, ?, ?, ?, ?)",
526
509
  ["SPEC-001", 1, "discovery", "Test knowledge", "critical"]
527
510
  );
528
511
 
529
- const kid = (db.query("SELECT id FROM knowledge LIMIT 1").get() as any).id;
512
+ const kRow = await dbGet<{ id: number }>("SELECT id FROM knowledge LIMIT 1");
513
+ const kid = kRow!.id;
530
514
 
531
- // Not acknowledged yet
532
- const before = db.query(
533
- "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ? AND task_id = ?"
534
- ).get(kid, 2);
515
+ const before = await dbGet(
516
+ "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ? AND task_id = ?",
517
+ [kid, 2]
518
+ );
535
519
  expect(before).toBeNull();
536
520
 
537
- // Acknowledge
538
- db.run(
521
+ await dbRun(
539
522
  "INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)",
540
523
  [kid, 2]
541
524
  );
542
525
 
543
- // Now acknowledged
544
- const after = db.query(
545
- "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ? AND task_id = ?"
546
- ).get(kid, 2);
526
+ const after = await dbGet(
527
+ "SELECT 1 FROM knowledge_acknowledgments WHERE knowledge_id = ? AND task_id = ?",
528
+ [kid, 2]
529
+ );
547
530
  expect(after).not.toBeNull();
548
531
  });
549
532
 
550
- it("should be idempotent (INSERT OR IGNORE)", () => {
551
- createKnowledgeTables(db);
552
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
553
- db.run(
533
+ it("should be idempotent (INSERT OR IGNORE)", async () => {
534
+ await createKnowledgeTables();
535
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
536
+ await dbRun(
554
537
  "INSERT INTO knowledge (spec_id, task_origin, category, content) VALUES (?, ?, ?, ?)",
555
538
  ["SPEC-001", 1, "discovery", "Test"]
556
539
  );
557
540
 
558
- const kid = (db.query("SELECT id FROM knowledge LIMIT 1").get() as any).id;
541
+ const kRow = await dbGet<{ id: number }>("SELECT id FROM knowledge LIMIT 1");
542
+ const kid = kRow!.id;
559
543
 
560
- // Insert twice no error
561
- db.run("INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kid, 2]);
562
- db.run("INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kid, 2]);
544
+ await dbRun("INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kid, 2]);
545
+ await dbRun("INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kid, 2]);
563
546
 
564
- const count = (db.query(
565
- "SELECT COUNT(*) as c FROM knowledge_acknowledgments WHERE knowledge_id = ?"
566
- ).get(kid) as any).c;
567
- expect(count).toBe(1);
547
+ const count = await dbGet<{ c: number }>(
548
+ "SELECT COUNT(*) as c FROM knowledge_acknowledgments WHERE knowledge_id = ?",
549
+ [kid]
550
+ );
551
+ expect(count!.c).toBe(1);
568
552
  });
569
553
 
570
- it("should migrate data from JSON acknowledged_by", () => {
571
- // Recreate old schema WITH acknowledged_by column to test migration from legacy format
572
- createBaseTables(db);
573
- db.exec(`
554
+ it("should migrate data from JSON acknowledged_by", async () => {
555
+ await createBaseTables();
556
+ await dbExec(`
574
557
  CREATE TABLE IF NOT EXISTS knowledge (
575
558
  id INTEGER PRIMARY KEY AUTOINCREMENT,
576
559
  spec_id TEXT NOT NULL,
@@ -581,62 +564,65 @@ describe("Migration System", () => {
581
564
  broadcast_to TEXT DEFAULT 'all',
582
565
  acknowledged_by TEXT,
583
566
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
584
- );
567
+ )
568
+ `);
569
+ await dbExec(`
585
570
  CREATE TABLE IF NOT EXISTS knowledge_acknowledgments (
586
571
  knowledge_id INTEGER NOT NULL REFERENCES knowledge(id) ON DELETE CASCADE,
587
572
  task_id INTEGER NOT NULL,
588
573
  acknowledged_at TEXT DEFAULT CURRENT_TIMESTAMP,
589
574
  PRIMARY KEY (knowledge_id, task_id)
590
- );
575
+ )
591
576
  `);
592
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
577
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
593
578
 
594
- // Insert with old JSON format
595
- db.run(
579
+ await dbRun(
596
580
  "INSERT INTO knowledge (spec_id, task_origin, category, content, acknowledged_by) VALUES (?, ?, ?, ?, ?)",
597
581
  ["SPEC-001", 1, "discovery", "Test", JSON.stringify([2, 3, 5])]
598
582
  );
599
583
 
600
- const kid = (db.query("SELECT id FROM knowledge LIMIT 1").get() as any).id;
584
+ const kRow = await dbGet<{ id: number }>("SELECT id FROM knowledge LIMIT 1");
585
+ const kid = kRow!.id;
601
586
 
602
- // Simulate migration
603
- const rows = db.query("SELECT id, acknowledged_by FROM knowledge WHERE acknowledged_by IS NOT NULL").all() as any[];
604
- const insert = db.prepare("INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)");
587
+ const rows = await dbAll<{ id: number; acknowledged_by: string }>(
588
+ "SELECT id, acknowledged_by FROM knowledge WHERE acknowledged_by IS NOT NULL"
589
+ );
605
590
  for (const row of rows) {
606
591
  const taskIds = JSON.parse(row.acknowledged_by) as number[];
607
592
  for (const taskId of taskIds) {
608
- insert.run(row.id, taskId);
593
+ await dbRun(
594
+ "INSERT OR IGNORE INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)",
595
+ [row.id, taskId]
596
+ );
609
597
  }
610
598
  }
611
599
 
612
- // Verify migrated data
613
- const acks = db.query(
614
- "SELECT task_id FROM knowledge_acknowledgments WHERE knowledge_id = ? ORDER BY task_id"
615
- ).all(kid) as any[];
616
- expect(acks.map((a: any) => a.task_id)).toEqual([2, 3, 5]);
600
+ const acks = await dbAll<{ task_id: number }>(
601
+ "SELECT task_id FROM knowledge_acknowledgments WHERE knowledge_id = ? ORDER BY task_id",
602
+ [kid]
603
+ );
604
+ expect(acks.map((a) => a.task_id)).toEqual([2, 3, 5]);
617
605
  });
618
606
 
619
- it("should find unacknowledged critical knowledge via NOT EXISTS", () => {
620
- createKnowledgeTables(db);
621
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
607
+ it("should find unacknowledged critical knowledge via NOT EXISTS", async () => {
608
+ await createKnowledgeTables();
609
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'implementing')");
622
610
 
623
- // Insert critical knowledge from task 1
624
- db.run(
611
+ await dbRun(
625
612
  "INSERT INTO knowledge (spec_id, task_origin, category, content, severity) VALUES (?, ?, ?, ?, ?)",
626
613
  ["SPEC-001", 1, "discovery", "Critical A", "critical"]
627
614
  );
628
- db.run(
615
+ await dbRun(
629
616
  "INSERT INTO knowledge (spec_id, task_origin, category, content, severity) VALUES (?, ?, ?, ?, ?)",
630
617
  ["SPEC-001", 1, "discovery", "Critical B", "critical"]
631
618
  );
632
619
 
633
- const kids = (db.query("SELECT id FROM knowledge ORDER BY id").all() as any[]).map((r: any) => r.id);
620
+ const allK = await dbAll<{ id: number }>("SELECT id FROM knowledge ORDER BY id");
621
+ const kids = allK.map((r) => r.id);
634
622
 
635
- // Acknowledge only the first one by task 2
636
- db.run("INSERT INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kids[0], 2]);
623
+ await dbRun("INSERT INTO knowledge_acknowledgments (knowledge_id, task_id) VALUES (?, ?)", [kids[0], 2]);
637
624
 
638
- // Query unacknowledged for task 2
639
- const unacked = db.query(`
625
+ const unacked = await dbAll<any>(`
640
626
  SELECT k.* FROM knowledge k
641
627
  WHERE k.spec_id = ?
642
628
  AND k.severity = 'critical'
@@ -645,21 +631,17 @@ describe("Migration System", () => {
645
631
  SELECT 1 FROM knowledge_acknowledgments ka
646
632
  WHERE ka.knowledge_id = k.id AND ka.task_id = ?
647
633
  )
648
- `).all("SPEC-001", 2, 2) as any[];
634
+ `, ["SPEC-001", 2, 2]);
649
635
 
650
636
  expect(unacked).toHaveLength(1);
651
637
  expect(unacked[0].content).toBe("Critical B");
652
638
  });
653
639
  });
654
640
 
655
- // ═══════════════════════════════════════════════════════════════
656
- // P1-2: Review Score
657
- // ═══════════════════════════════════════════════════════════════
658
-
659
641
  describe("calculateReviewScore()", () => {
660
- function createReviewTables(db: Database) {
661
- createBaseTables(db);
662
- db.exec(`
642
+ async function createReviewTables() {
643
+ await createBaseTables();
644
+ await dbExec(`
663
645
  CREATE TABLE IF NOT EXISTS artifacts (
664
646
  id INTEGER PRIMARY KEY AUTOINCREMENT,
665
647
  spec_id TEXT NOT NULL,
@@ -668,7 +650,9 @@ describe("Migration System", () => {
668
650
  action TEXT NOT NULL,
669
651
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
670
652
  UNIQUE(spec_id, path)
671
- );
653
+ )
654
+ `);
655
+ await dbExec(`
672
656
  CREATE TABLE IF NOT EXISTS gate_bypasses (
673
657
  id INTEGER PRIMARY KEY AUTOINCREMENT,
674
658
  spec_id TEXT NOT NULL,
@@ -676,7 +660,9 @@ describe("Migration System", () => {
676
660
  gate_name TEXT NOT NULL,
677
661
  reason TEXT,
678
662
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
679
- );
663
+ )
664
+ `);
665
+ await dbExec(`
680
666
  CREATE TABLE IF NOT EXISTS review (
681
667
  id INTEGER PRIMARY KEY AUTOINCREMENT,
682
668
  spec_id TEXT NOT NULL,
@@ -684,45 +670,50 @@ describe("Migration System", () => {
684
670
  deviations TEXT,
685
671
  status TEXT DEFAULT 'pending',
686
672
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
687
- );
673
+ )
688
674
  `);
689
675
  }
690
676
 
691
- function calcScore(db: Database, specId: string) {
692
- const totalTasks = (db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?").get(specId) as any).c;
693
- const completedTasks = (db.query("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'").get(specId) as any).c;
677
+ async function calcScore(specId: string) {
678
+ const totalRow = await dbGet<{ c: number }>("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ?", [specId]);
679
+ const totalTasks = totalRow!.c;
680
+ const completedRow = await dbGet<{ c: number }>("SELECT COUNT(*) as c FROM tasks WHERE spec_id = ? AND status = 'done'", [specId]);
681
+ const completedTasks = completedRow!.c;
694
682
  const tasksCompleted = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 25) : 25;
695
683
 
696
684
  const totalGateEvents = totalTasks * 7;
697
- const bypassCount = (db.query("SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ?").get(specId) as any).c;
685
+ const bypassRow = await dbGet<{ c: number }>("SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ?", [specId]);
686
+ const bypassCount = bypassRow!.c;
698
687
  const cleanGateEvents = Math.max(0, totalGateEvents - bypassCount);
699
688
  const gatesPassedClean = totalGateEvents > 0 ? Math.round((cleanGateEvents / totalGateEvents) * 25) : 25;
700
689
 
701
- const plannedFiles = db.query("SELECT files FROM tasks WHERE spec_id = ? AND files IS NOT NULL").all(specId) as any[];
690
+ const plannedFiles = await dbAll<{ files: string }>("SELECT files FROM tasks WHERE spec_id = ? AND files IS NOT NULL", [specId]);
702
691
  const allPlannedFiles = new Set<string>();
703
692
  for (const t of plannedFiles) {
704
693
  try { for (const f of JSON.parse(t.files) as string[]) allPlannedFiles.add(f); } catch {}
705
694
  }
706
- const deliveredFiles = new Set((db.query("SELECT DISTINCT path FROM artifacts WHERE spec_id = ?").all(specId) as any[]).map(a => a.path));
695
+ const deliveredRows = await dbAll<{ path: string }>("SELECT DISTINCT path FROM artifacts WHERE spec_id = ?", [specId]);
696
+ const deliveredFiles = new Set(deliveredRows.map(a => a.path));
707
697
  let filesDelivered: number;
708
698
  if (allPlannedFiles.size === 0) { filesDelivered = deliveredFiles.size > 0 ? 25 : 0; }
709
699
  else { let m = 0; for (const f of allPlannedFiles) { if (deliveredFiles.has(f)) m++; } filesDelivered = Math.round((m / allPlannedFiles.size) * 25); }
710
700
 
711
- const standardsBypasses = (db.query("SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ? AND gate_name = 'standards-follow'").get(specId) as any).c;
701
+ const standardsRow = await dbGet<{ c: number }>("SELECT COUNT(*) as c FROM gate_bypasses WHERE spec_id = ? AND gate_name = 'standards-follow'", [specId]);
702
+ const standardsBypasses = standardsRow!.c;
712
703
  const standardsFollowed = totalTasks > 0 ? Math.round(((totalTasks - standardsBypasses) / totalTasks) * 25) : 25;
713
704
 
714
705
  return { total: tasksCompleted + gatesPassedClean + filesDelivered + standardsFollowed, breakdown: { tasksCompleted, gatesPassedClean, filesDelivered, standardsFollowed } };
715
706
  }
716
707
 
717
- it("should return perfect score for clean implementation", () => {
718
- createReviewTables(db);
719
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
720
- db.run("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 1, 'Task 1', 'done', ?)", ["SPEC-001", '["src/a.ts"]']);
721
- db.run("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 2, 'Task 2', 'done', ?)", ["SPEC-001", '["src/b.ts"]']);
722
- db.run("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 1, 'src/a.ts', 'created')", ["SPEC-001"]);
723
- db.run("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 2, 'src/b.ts', 'created')", ["SPEC-001"]);
708
+ it("should return perfect score for clean implementation", async () => {
709
+ await createReviewTables();
710
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
711
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 1, 'Task 1', 'done', ?)", ["SPEC-001", '["src/a.ts"]']);
712
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 2, 'Task 2', 'done', ?)", ["SPEC-001", '["src/b.ts"]']);
713
+ await dbRun("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 1, 'src/a.ts', 'created')", ["SPEC-001"]);
714
+ await dbRun("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 2, 'src/b.ts', 'created')", ["SPEC-001"]);
724
715
 
725
- const score = calcScore(db, "SPEC-001");
716
+ const score = await calcScore("SPEC-001");
726
717
  expect(score.total).toBe(100);
727
718
  expect(score.breakdown.tasksCompleted).toBe(25);
728
719
  expect(score.breakdown.gatesPassedClean).toBe(25);
@@ -730,48 +721,46 @@ describe("Migration System", () => {
730
721
  expect(score.breakdown.standardsFollowed).toBe(25);
731
722
  });
732
723
 
733
- it("should penalize incomplete tasks", () => {
734
- createReviewTables(db);
735
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
736
- db.run("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
737
- db.run("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 2, 'Task 2', 'pending')", ["SPEC-001"]);
724
+ it("should penalize incomplete tasks", async () => {
725
+ await createReviewTables();
726
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
727
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
728
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 2, 'Task 2', 'pending')", ["SPEC-001"]);
738
729
 
739
- const score = calcScore(db, "SPEC-001");
740
- expect(score.breakdown.tasksCompleted).toBe(13); // round(1/2 * 25) = 13
730
+ const score = await calcScore("SPEC-001");
731
+ expect(score.breakdown.tasksCompleted).toBe(13);
741
732
  });
742
733
 
743
- it("should penalize gate bypasses", () => {
744
- createReviewTables(db);
745
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
746
- db.run("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
747
- db.run("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'standards-follow', 'test')", ["SPEC-001"]);
748
- db.run("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'dry-check', 'test')", ["SPEC-001"]);
734
+ it("should penalize gate bypasses", async () => {
735
+ await createReviewTables();
736
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
737
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
738
+ await dbRun("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'standards-follow', 'test')", ["SPEC-001"]);
739
+ await dbRun("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'dry-check', 'test')", ["SPEC-001"]);
749
740
 
750
- const score = calcScore(db, "SPEC-001");
751
- // 1 task * 7 gates = 7 events, 2 bypasses = 5/7 clean
741
+ const score = await calcScore("SPEC-001");
752
742
  expect(score.breakdown.gatesPassedClean).toBe(Math.round(5 / 7 * 25));
753
743
  });
754
744
 
755
- it("should penalize missing files", () => {
756
- createReviewTables(db);
757
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
758
- db.run("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 1, 'Task 1', 'done', ?)", ["SPEC-001", '["src/a.ts","src/b.ts"]']);
759
- db.run("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 1, 'src/a.ts', 'created')", ["SPEC-001"]);
760
- // src/b.ts missing
745
+ it("should penalize missing files", async () => {
746
+ await createReviewTables();
747
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
748
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status, files) VALUES (?, 1, 'Task 1', 'done', ?)", ["SPEC-001", '["src/a.ts","src/b.ts"]']);
749
+ await dbRun("INSERT INTO artifacts (spec_id, task_ref, path, action) VALUES (?, 1, 'src/a.ts', 'created')", ["SPEC-001"]);
761
750
 
762
- const score = calcScore(db, "SPEC-001");
763
- expect(score.breakdown.filesDelivered).toBe(13); // round(1/2 * 25) = 13
751
+ const score = await calcScore("SPEC-001");
752
+ expect(score.breakdown.filesDelivered).toBe(13);
764
753
  });
765
754
 
766
- it("should penalize standards bypasses specifically", () => {
767
- createReviewTables(db);
768
- db.run("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
769
- db.run("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
770
- db.run("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 2, 'Task 2', 'done')", ["SPEC-001"]);
771
- db.run("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'standards-follow', 'test')", ["SPEC-001"]);
755
+ it("should penalize standards bypasses specifically", async () => {
756
+ await createReviewTables();
757
+ await dbRun("INSERT INTO specs (id, name, phase) VALUES ('SPEC-001', 'test', 'reviewing')");
758
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 1, 'Task 1', 'done')", ["SPEC-001"]);
759
+ await dbRun("INSERT INTO tasks (spec_id, number, name, status) VALUES (?, 2, 'Task 2', 'done')", ["SPEC-001"]);
760
+ await dbRun("INSERT INTO gate_bypasses (spec_id, task_id, gate_name, reason) VALUES (?, 1, 'standards-follow', 'test')", ["SPEC-001"]);
772
761
 
773
- const score = calcScore(db, "SPEC-001");
774
- expect(score.breakdown.standardsFollowed).toBe(13); // round(1/2 * 25) = 13
762
+ const score = await calcScore("SPEC-001");
763
+ expect(score.breakdown.standardsFollowed).toBe(13);
775
764
  });
776
765
  });
777
766
  });