@codexa/cli 9.0.31 → 9.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/architect.ts +52 -87
- package/commands/check.ts +22 -23
- package/commands/clear.ts +42 -48
- package/commands/decide.ts +46 -44
- package/commands/discover.ts +81 -94
- package/commands/integration.test.ts +262 -313
- package/commands/knowledge.test.ts +56 -61
- package/commands/knowledge.ts +126 -131
- package/commands/patterns.ts +28 -43
- package/commands/plan.ts +50 -48
- package/commands/product.ts +57 -59
- package/commands/research.ts +64 -77
- package/commands/review.ts +100 -86
- package/commands/simplify.ts +24 -35
- package/commands/spec-resolver.test.ts +52 -48
- package/commands/spec-resolver.ts +21 -23
- package/commands/standards.ts +20 -27
- package/commands/sync.ts +2 -8
- package/commands/task.ts +106 -97
- package/commands/team.test.ts +22 -83
- package/commands/team.ts +62 -50
- package/commands/utils.ts +83 -81
- package/context/assembly.ts +0 -1
- package/context/generator.ts +66 -79
- package/context/sections.ts +8 -14
- package/db/connection.ts +195 -19
- package/db/schema.test.ts +288 -299
- package/db/schema.ts +297 -394
- package/db/test-helpers.ts +18 -29
- package/gates/standards-validator.test.ts +83 -86
- package/gates/standards-validator.ts +9 -41
- package/gates/validator.test.ts +13 -22
- package/gates/validator.ts +69 -107
- package/package.json +2 -1
- package/protocol/process-return.ts +41 -57
- package/simplify/prompt-builder.test.ts +44 -42
- package/simplify/prompt-builder.ts +12 -14
- package/workflow.ts +159 -174
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
44
|
-
"SELECT id FROM tasks WHERE spec_id = ? AND number = ?"
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
70
|
+
beforeEach(async () => {
|
|
71
|
+
const client = createClient({ url: ":memory:" });
|
|
72
|
+
setClient(client);
|
|
73
|
+
await initSchema();
|
|
74
|
+
await cleanDb();
|
|
80
75
|
});
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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);
|
|
149
|
+
expect(processResult1.reasoningAdded).toBeGreaterThanOrEqual(3);
|
|
157
150
|
|
|
158
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
)
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
expect(allKnowledge.length).toBeGreaterThanOrEqual(3);
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
231
|
+
expect(score.breakdown.tasksCompleted).toBe(25);
|
|
244
232
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
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
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
const task2Id = addTask(specId, 2, "Second task", "backend-javascript", [1]);
|
|
245
|
+
await approveSpec(specId);
|
|
246
|
+
await startTask(task1Id);
|
|
258
247
|
|
|
259
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
)
|
|
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
|
|
295
|
-
|
|
296
|
-
const
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
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
|
|
338
|
-
|
|
339
|
-
const
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
)
|
|
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
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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",
|
|
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
|
|
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
|
|
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
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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" },
|
|
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
|
|
506
|
-
const
|
|
507
|
-
|
|
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
|
-
|
|
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
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
|
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
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
)
|
|
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
|
|
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
|
|
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 =
|
|
641
|
-
"SELECT * FROM artifacts WHERE spec_id = ? ORDER BY path"
|
|
642
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
669
|
-
"SELECT * FROM knowledge_graph WHERE spec_id = ?"
|
|
670
|
-
|
|
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
|
|
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
|
-
|
|
689
|
-
|
|
642
|
+
await addTask(specA, 1, "Task A1");
|
|
643
|
+
await addTask(specB, 1, "Task B1");
|
|
690
644
|
|
|
691
|
-
|
|
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
|
-
)
|
|
647
|
+
);
|
|
698
648
|
expect(specs.length).toBe(2);
|
|
699
649
|
|
|
700
|
-
|
|
701
|
-
const
|
|
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
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
const
|
|
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 =
|
|
743
|
-
"SELECT * FROM knowledge WHERE spec_id = ?"
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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);
|