@ronkovic/aad 0.3.9 → 0.5.0
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/README.md +332 -14
- package/package.json +6 -1
- package/src/__tests__/e2e/cleanup-e2e.test.ts +186 -0
- package/src/__tests__/e2e/dashboard-api-e2e.test.ts +87 -0
- package/src/__tests__/e2e/pipeline-e2e.test.ts +10 -68
- package/src/__tests__/e2e/resume-e2e.test.ts +9 -11
- package/src/__tests__/e2e/retry-e2e.test.ts +285 -0
- package/src/__tests__/e2e/status-e2e.test.ts +227 -0
- package/src/__tests__/e2e/tdd-pipeline-e2e.test.ts +360 -0
- package/src/__tests__/helpers/index.ts +6 -0
- package/src/__tests__/helpers/mock-claude-provider.ts +53 -0
- package/src/__tests__/helpers/mock-logger.ts +36 -0
- package/src/__tests__/helpers/wait-helpers.ts +34 -0
- package/src/__tests__/integration/pipeline.test.ts +3 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +1 -1
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +6 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +3 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +73 -0
- package/src/modules/cli/__tests__/resume.test.ts +4 -0
- package/src/modules/cli/__tests__/run.test.ts +37 -0
- package/src/modules/cli/__tests__/status.test.ts +1 -0
- package/src/modules/cli/app.ts +2 -0
- package/src/modules/cli/commands/__tests__/task-dispatch-handler.test.ts +145 -0
- package/src/modules/cli/commands/cleanup.ts +26 -11
- package/src/modules/cli/commands/resume.ts +14 -8
- package/src/modules/cli/commands/run.ts +70 -8
- package/src/modules/cli/commands/task-dispatch-handler.ts +73 -3
- package/src/modules/dashboard/__tests__/api-graph.test.ts +332 -0
- package/src/modules/dashboard/__tests__/api-timeline.test.ts +461 -0
- package/src/modules/dashboard/routes/sse.ts +3 -2
- package/src/modules/dashboard/server.ts +1 -0
- package/src/modules/dashboard/services/sse-broadcaster.ts +29 -0
- package/src/modules/dashboard/ui/dashboard.html +640 -349
- package/src/modules/git-workspace/__tests__/branch-manager.test.ts +52 -0
- package/src/modules/git-workspace/__tests__/dependency-installer.test.ts +77 -0
- package/src/modules/git-workspace/__tests__/git-exec.test.ts +26 -0
- package/src/modules/git-workspace/__tests__/merge-service.test.ts +19 -0
- package/src/modules/git-workspace/__tests__/pr-manager.test.ts +80 -0
- package/src/modules/git-workspace/__tests__/template-copy.test.ts +189 -0
- package/src/modules/git-workspace/__tests__/worktree-cleanup.test.ts +29 -2
- package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +64 -4
- package/src/modules/git-workspace/branch-manager.ts +24 -3
- package/src/modules/git-workspace/dependency-installer.ts +113 -0
- package/src/modules/git-workspace/git-exec.ts +3 -2
- package/src/modules/git-workspace/index.ts +10 -1
- package/src/modules/git-workspace/merge-service.ts +36 -2
- package/src/modules/git-workspace/pr-manager.ts +278 -0
- package/src/modules/git-workspace/template-copy.ts +302 -0
- package/src/modules/git-workspace/worktree-manager.ts +37 -11
- package/src/modules/planning/__tests__/planning-service.test.ts +3 -0
- package/src/modules/planning/__tests__/planning.service.test.ts +149 -0
- package/src/modules/planning/__tests__/project-detection.test.ts +7 -1
- package/src/modules/planning/planning.service.ts +16 -2
- package/src/modules/planning/project-detection.ts +4 -1
- package/src/modules/process-manager/__tests__/process-manager.test.ts +3 -0
- package/src/modules/process-manager/process-manager.ts +2 -1
- package/src/modules/task-execution/__tests__/executor.test.ts +496 -0
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +4 -3
- package/src/modules/task-execution/executor.ts +163 -4
- package/src/modules/task-execution/phases/implementer-green.ts +22 -5
- package/src/modules/task-execution/phases/merge.ts +44 -2
- package/src/modules/task-execution/phases/tester-red.ts +22 -5
- package/src/modules/task-execution/phases/tester-verify.ts +22 -6
- package/src/modules/task-queue/dispatcher.ts +96 -3
- package/src/shared/__tests__/config.test.ts +30 -0
- package/src/shared/__tests__/events.test.ts +42 -16
- package/src/shared/__tests__/prerequisites.test.ts +176 -0
- package/src/shared/__tests__/shutdown-handler.test.ts +96 -0
- package/src/shared/config.ts +10 -0
- package/src/shared/events.ts +5 -0
- package/src/shared/memory-check.ts +2 -2
- package/src/shared/prerequisites.ts +190 -0
- package/src/shared/shutdown-handler.ts +12 -5
- package/src/shared/types.ts +25 -0
- package/templates/CLAUDE.md +122 -0
- package/templates/settings.json +117 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +0 -5
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Test: Status command (displayStatus with stores)
|
|
3
|
+
*/
|
|
4
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
5
|
+
import { createStores, type Stores } from "@aad/persistence";
|
|
6
|
+
import { EventBus } from "@aad/shared/events";
|
|
7
|
+
import { displayStatus, type App } from "@aad/cli";
|
|
8
|
+
import {
|
|
9
|
+
createRunId,
|
|
10
|
+
createTaskId,
|
|
11
|
+
type RunState,
|
|
12
|
+
type Task,
|
|
13
|
+
} from "@aad/shared/types";
|
|
14
|
+
import { createMockLogger } from "../helpers";
|
|
15
|
+
|
|
16
|
+
describe("E2E Status Command", () => {
|
|
17
|
+
let stores: Stores;
|
|
18
|
+
let eventBus: EventBus;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Use in-memory stores for status tests (FSRunStore has single-file limitation)
|
|
22
|
+
stores = createStores("memory");
|
|
23
|
+
eventBus = new EventBus();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function createApp(): App {
|
|
27
|
+
return {
|
|
28
|
+
stores,
|
|
29
|
+
logger: createMockLogger(),
|
|
30
|
+
eventBus,
|
|
31
|
+
} as App;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test("displayStatus reads from FS store and displays progress", async () => {
|
|
35
|
+
const runId = createRunId("status-run-1");
|
|
36
|
+
|
|
37
|
+
// Create run state
|
|
38
|
+
const runState: RunState = {
|
|
39
|
+
runId,
|
|
40
|
+
parentBranch: "main",
|
|
41
|
+
totalTasks: 3,
|
|
42
|
+
pending: 1,
|
|
43
|
+
running: 1,
|
|
44
|
+
completed: 1,
|
|
45
|
+
failed: 0,
|
|
46
|
+
startTime: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
await stores.runStore.save(runState);
|
|
49
|
+
|
|
50
|
+
// Create tasks
|
|
51
|
+
const tasks: Task[] = [
|
|
52
|
+
{
|
|
53
|
+
taskId: createTaskId("task-1"),
|
|
54
|
+
title: "Task 1",
|
|
55
|
+
description: "First task",
|
|
56
|
+
filesToModify: ["a.ts"],
|
|
57
|
+
dependsOn: [],
|
|
58
|
+
priority: 1,
|
|
59
|
+
status: "completed",
|
|
60
|
+
retryCount: 0,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
taskId: createTaskId("task-2"),
|
|
64
|
+
title: "Task 2",
|
|
65
|
+
description: "Second task",
|
|
66
|
+
filesToModify: ["b.ts"],
|
|
67
|
+
dependsOn: [],
|
|
68
|
+
priority: 2,
|
|
69
|
+
status: "running",
|
|
70
|
+
retryCount: 0,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
taskId: createTaskId("task-3"),
|
|
74
|
+
title: "Task 3",
|
|
75
|
+
description: "Third task",
|
|
76
|
+
filesToModify: ["c.ts"],
|
|
77
|
+
dependsOn: [],
|
|
78
|
+
priority: 3,
|
|
79
|
+
status: "pending",
|
|
80
|
+
retryCount: 0,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
for (const t of tasks) {
|
|
84
|
+
await stores.taskStore.save(t);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const app = createApp();
|
|
88
|
+
|
|
89
|
+
// Should not throw
|
|
90
|
+
await expect(displayStatus(app, "status-run-1")).resolves.toBeUndefined();
|
|
91
|
+
|
|
92
|
+
// Verify stores were read
|
|
93
|
+
const savedRun = await stores.runStore.get(runId);
|
|
94
|
+
expect(savedRun).toBeDefined();
|
|
95
|
+
expect(savedRun?.totalTasks).toBe(3);
|
|
96
|
+
expect(savedRun?.completed).toBe(1);
|
|
97
|
+
}, 15_000);
|
|
98
|
+
|
|
99
|
+
test("displayStatus handles multiple runs (latest vs specific runId)", async () => {
|
|
100
|
+
// Create multiple runs with tasks
|
|
101
|
+
await stores.runStore.save({
|
|
102
|
+
runId: createRunId("old-run"),
|
|
103
|
+
parentBranch: "main",
|
|
104
|
+
totalTasks: 1,
|
|
105
|
+
pending: 0,
|
|
106
|
+
running: 0,
|
|
107
|
+
completed: 1,
|
|
108
|
+
failed: 0,
|
|
109
|
+
startTime: new Date(Date.now() - 10000).toISOString(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await stores.taskStore.save({
|
|
113
|
+
taskId: createTaskId("old-task-1"),
|
|
114
|
+
title: "Old Task",
|
|
115
|
+
description: "From old run",
|
|
116
|
+
filesToModify: [],
|
|
117
|
+
dependsOn: [],
|
|
118
|
+
priority: 1,
|
|
119
|
+
status: "completed",
|
|
120
|
+
retryCount: 0,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await stores.runStore.save({
|
|
124
|
+
runId: createRunId("latest-run"),
|
|
125
|
+
parentBranch: "main",
|
|
126
|
+
totalTasks: 2,
|
|
127
|
+
pending: 1,
|
|
128
|
+
running: 1,
|
|
129
|
+
completed: 0,
|
|
130
|
+
failed: 0,
|
|
131
|
+
startTime: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await stores.taskStore.save({
|
|
135
|
+
taskId: createTaskId("latest-task-1"),
|
|
136
|
+
title: "Latest Task 1",
|
|
137
|
+
description: "From latest run",
|
|
138
|
+
filesToModify: [],
|
|
139
|
+
dependsOn: [],
|
|
140
|
+
priority: 1,
|
|
141
|
+
status: "running",
|
|
142
|
+
retryCount: 0,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await stores.taskStore.save({
|
|
146
|
+
taskId: createTaskId("latest-task-2"),
|
|
147
|
+
title: "Latest Task 2",
|
|
148
|
+
description: "From latest run",
|
|
149
|
+
filesToModify: [],
|
|
150
|
+
dependsOn: [],
|
|
151
|
+
priority: 2,
|
|
152
|
+
status: "pending",
|
|
153
|
+
retryCount: 0,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const app = createApp();
|
|
157
|
+
|
|
158
|
+
// Without runId arg, should use latest
|
|
159
|
+
await displayStatus(app);
|
|
160
|
+
|
|
161
|
+
// With specific runId, should use that one
|
|
162
|
+
await displayStatus(app, "old-run");
|
|
163
|
+
|
|
164
|
+
// Verify correct run was retrieved
|
|
165
|
+
const oldRun = await stores.runStore.get(createRunId("old-run"));
|
|
166
|
+
expect(oldRun).toBeDefined();
|
|
167
|
+
expect(oldRun?.totalTasks).toBe(1);
|
|
168
|
+
}, 15_000);
|
|
169
|
+
|
|
170
|
+
test("displayStatus shows per-task details when requested", async () => {
|
|
171
|
+
const runId = createRunId("task-detail-run");
|
|
172
|
+
|
|
173
|
+
await stores.runStore.save({
|
|
174
|
+
runId,
|
|
175
|
+
parentBranch: "main",
|
|
176
|
+
totalTasks: 2,
|
|
177
|
+
pending: 0,
|
|
178
|
+
running: 0,
|
|
179
|
+
completed: 2,
|
|
180
|
+
failed: 0,
|
|
181
|
+
startTime: new Date().toISOString(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const tasks: Task[] = [
|
|
185
|
+
{
|
|
186
|
+
taskId: createTaskId("detail-task-1"),
|
|
187
|
+
title: "Detailed Task 1",
|
|
188
|
+
description: "First detailed",
|
|
189
|
+
filesToModify: ["x.ts"],
|
|
190
|
+
dependsOn: [],
|
|
191
|
+
priority: 1,
|
|
192
|
+
status: "completed",
|
|
193
|
+
retryCount: 0,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
taskId: createTaskId("detail-task-2"),
|
|
197
|
+
title: "Detailed Task 2",
|
|
198
|
+
description: "Second detailed",
|
|
199
|
+
filesToModify: ["y.ts"],
|
|
200
|
+
dependsOn: [createTaskId("detail-task-1")],
|
|
201
|
+
priority: 2,
|
|
202
|
+
status: "completed",
|
|
203
|
+
retryCount: 1,
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
for (const t of tasks) {
|
|
207
|
+
await stores.taskStore.save(t);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const app = createApp();
|
|
211
|
+
await expect(displayStatus(app, "task-detail-run")).resolves.toBeUndefined();
|
|
212
|
+
|
|
213
|
+
// Verify tasks can be retrieved
|
|
214
|
+
const allTasks = await stores.taskStore.getAll();
|
|
215
|
+
expect(allTasks).toHaveLength(2);
|
|
216
|
+
expect(allTasks[1]?.retryCount).toBe(1);
|
|
217
|
+
}, 15_000);
|
|
218
|
+
|
|
219
|
+
test("displayStatus returns error when runId not found", async () => {
|
|
220
|
+
const app = createApp();
|
|
221
|
+
|
|
222
|
+
// No runs exist
|
|
223
|
+
await expect(displayStatus(app, "nonexistent-run")).rejects.toThrow(
|
|
224
|
+
"Run not found: nonexistent-run"
|
|
225
|
+
);
|
|
226
|
+
}, 15_000);
|
|
227
|
+
});
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Test: TDD Pipeline with all 5 phases (Red → Green → Verify → Review → Merge)
|
|
3
|
+
*/
|
|
4
|
+
import { describe, test, expect } from "bun:test";
|
|
5
|
+
import { executeTddPipeline } from "../../modules/task-execution/executor";
|
|
6
|
+
import { createMockConfig } from "../helpers/mock-logger";
|
|
7
|
+
import { createMockProvider } from "../helpers/mock-claude-provider";
|
|
8
|
+
import type { Task, WorkspaceInfo } from "../../shared/types";
|
|
9
|
+
import { createTaskId, createRunId } from "../../shared/types";
|
|
10
|
+
import type { EventBus } from "../../shared/events";
|
|
11
|
+
import type { MergeService } from "../../modules/git-workspace";
|
|
12
|
+
import type { ProcessSpawner } from "../../modules/task-execution/phases/tester-verify";
|
|
13
|
+
import type { ClaudeProvider } from "../../modules/claude-provider";
|
|
14
|
+
|
|
15
|
+
describe("E2E TDD Pipeline", () => {
|
|
16
|
+
test("executeTddPipeline completes all 5 phases successfully", async () => {
|
|
17
|
+
const task: Task = {
|
|
18
|
+
taskId: createTaskId("e2e-task-1"),
|
|
19
|
+
title: "Full pipeline test",
|
|
20
|
+
description: "Test all 5 phases",
|
|
21
|
+
filesToModify: ["src/feature.ts"],
|
|
22
|
+
dependsOn: [],
|
|
23
|
+
priority: 1,
|
|
24
|
+
status: "running",
|
|
25
|
+
retryCount: 0,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const workspace: WorkspaceInfo = {
|
|
29
|
+
path: "/workspace",
|
|
30
|
+
language: "typescript",
|
|
31
|
+
packageManager: "bun",
|
|
32
|
+
framework: "hono",
|
|
33
|
+
testFramework: "bun-test",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const config = createMockConfig({ strictTdd: true });
|
|
37
|
+
const mockProvider = createMockProvider();
|
|
38
|
+
|
|
39
|
+
const mockMergeService = {
|
|
40
|
+
async mergeToParent() {
|
|
41
|
+
return { success: true, message: "Merged" };
|
|
42
|
+
},
|
|
43
|
+
} as unknown as MergeService;
|
|
44
|
+
|
|
45
|
+
const events: string[] = [];
|
|
46
|
+
const mockEventBus = {
|
|
47
|
+
on() {},
|
|
48
|
+
off() {},
|
|
49
|
+
emit(event: any) {
|
|
50
|
+
events.push(event.type);
|
|
51
|
+
},
|
|
52
|
+
} as unknown as EventBus;
|
|
53
|
+
|
|
54
|
+
const mockSpawner: ProcessSpawner = {
|
|
55
|
+
async spawn() {
|
|
56
|
+
return { exitCode: 0, stdout: "All tests passed", stderr: "" };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = await executeTddPipeline(
|
|
61
|
+
task,
|
|
62
|
+
workspace,
|
|
63
|
+
"aad/task-1",
|
|
64
|
+
"main",
|
|
65
|
+
"/parent",
|
|
66
|
+
createRunId("e2e-run-1"),
|
|
67
|
+
config,
|
|
68
|
+
mockProvider,
|
|
69
|
+
mockMergeService,
|
|
70
|
+
mockEventBus,
|
|
71
|
+
mockSpawner
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(result.status).toBe("completed");
|
|
75
|
+
expect(events).toContain("execution:phase:started");
|
|
76
|
+
expect(events).toContain("execution:phase:completed");
|
|
77
|
+
}, 15_000);
|
|
78
|
+
|
|
79
|
+
test("executeTddPipeline stops at Red phase when test fails", async () => {
|
|
80
|
+
const task: Task = {
|
|
81
|
+
taskId: createTaskId("e2e-task-2"),
|
|
82
|
+
title: "Red phase failure",
|
|
83
|
+
description: "Test Red phase stop",
|
|
84
|
+
filesToModify: [],
|
|
85
|
+
dependsOn: [],
|
|
86
|
+
priority: 1,
|
|
87
|
+
status: "running",
|
|
88
|
+
retryCount: 0,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const workspace: WorkspaceInfo = {
|
|
92
|
+
path: "/workspace",
|
|
93
|
+
language: "typescript",
|
|
94
|
+
packageManager: "bun",
|
|
95
|
+
framework: "hono",
|
|
96
|
+
testFramework: "bun-test",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const config = createMockConfig({ strictTdd: true });
|
|
100
|
+
|
|
101
|
+
let callCount = 0;
|
|
102
|
+
const mockProvider: ClaudeProvider = {
|
|
103
|
+
async call() {
|
|
104
|
+
callCount++;
|
|
105
|
+
if (callCount === 1) {
|
|
106
|
+
// Red phase: return no test file
|
|
107
|
+
return {
|
|
108
|
+
result: "No test file created",
|
|
109
|
+
exitCode: 0,
|
|
110
|
+
model: "claude-sonnet-4-5",
|
|
111
|
+
effortLevel: "medium" as const,
|
|
112
|
+
duration: 1000,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
throw new Error("Should not reach Green phase");
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const mockMergeService = {
|
|
120
|
+
async mergeToParent() {
|
|
121
|
+
throw new Error("Should not reach merge");
|
|
122
|
+
},
|
|
123
|
+
} as unknown as MergeService;
|
|
124
|
+
|
|
125
|
+
const mockEventBus = {
|
|
126
|
+
on() {},
|
|
127
|
+
off() {},
|
|
128
|
+
emit() {},
|
|
129
|
+
} as unknown as EventBus;
|
|
130
|
+
|
|
131
|
+
const mockSpawner: ProcessSpawner = {
|
|
132
|
+
async spawn() {
|
|
133
|
+
return { exitCode: 0, stdout: "Tests passed (but no test file)", stderr: "" };
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = await executeTddPipeline(
|
|
138
|
+
task,
|
|
139
|
+
workspace,
|
|
140
|
+
"aad/task-2",
|
|
141
|
+
"main",
|
|
142
|
+
"/parent",
|
|
143
|
+
createRunId("e2e-run-2"),
|
|
144
|
+
config,
|
|
145
|
+
mockProvider,
|
|
146
|
+
mockMergeService,
|
|
147
|
+
mockEventBus,
|
|
148
|
+
mockSpawner
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Should complete even if Red phase has issues
|
|
152
|
+
expect(result.status).toMatch(/completed|failed/);
|
|
153
|
+
}, 15_000);
|
|
154
|
+
|
|
155
|
+
test("executeTddPipeline handles merge conflicts in Merge phase", async () => {
|
|
156
|
+
const task: Task = {
|
|
157
|
+
taskId: createTaskId("e2e-task-3"),
|
|
158
|
+
title: "Merge conflict test",
|
|
159
|
+
description: "Test merge conflict handling",
|
|
160
|
+
filesToModify: [],
|
|
161
|
+
dependsOn: [],
|
|
162
|
+
priority: 1,
|
|
163
|
+
status: "running",
|
|
164
|
+
retryCount: 0,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const workspace: WorkspaceInfo = {
|
|
168
|
+
path: "/workspace",
|
|
169
|
+
language: "typescript",
|
|
170
|
+
packageManager: "bun",
|
|
171
|
+
framework: "hono",
|
|
172
|
+
testFramework: "bun-test",
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const config = createMockConfig({ strictTdd: true });
|
|
176
|
+
const mockProvider = createMockProvider();
|
|
177
|
+
|
|
178
|
+
const mockMergeService = {
|
|
179
|
+
async mergeToParent() {
|
|
180
|
+
// Return success: false with no conflicts to simulate simple merge failure
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
message: "Merge failed due to unexpected error",
|
|
184
|
+
conflicts: [], // Empty conflicts array - simple failure
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
} as unknown as MergeService;
|
|
188
|
+
|
|
189
|
+
const events: Array<{ type: string; conflicts?: string[] }> = [];
|
|
190
|
+
const mockEventBus = {
|
|
191
|
+
on() {},
|
|
192
|
+
off() {},
|
|
193
|
+
emit(event: any) {
|
|
194
|
+
events.push(event);
|
|
195
|
+
},
|
|
196
|
+
} as unknown as EventBus;
|
|
197
|
+
|
|
198
|
+
const mockSpawner: ProcessSpawner = {
|
|
199
|
+
async spawn() {
|
|
200
|
+
return { exitCode: 0, stdout: "Tests passed", stderr: "" };
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = await executeTddPipeline(
|
|
205
|
+
task,
|
|
206
|
+
workspace,
|
|
207
|
+
"aad/task-3",
|
|
208
|
+
"main",
|
|
209
|
+
"/parent",
|
|
210
|
+
createRunId("e2e-run-3"),
|
|
211
|
+
config,
|
|
212
|
+
mockProvider,
|
|
213
|
+
mockMergeService,
|
|
214
|
+
mockEventBus,
|
|
215
|
+
mockSpawner
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Merge failure should result in failed status
|
|
219
|
+
expect(result.status).toBe("failed");
|
|
220
|
+
|
|
221
|
+
// Verify phase failure event was emitted
|
|
222
|
+
const phaseFailedEvent = events.find((e) => e.type === "execution:phase:failed");
|
|
223
|
+
expect(phaseFailedEvent).toBeDefined();
|
|
224
|
+
}, 15_000);
|
|
225
|
+
|
|
226
|
+
test("executeTddPipeline respects retryContext on retry attempt", async () => {
|
|
227
|
+
const task: Task = {
|
|
228
|
+
taskId: createTaskId("e2e-task-4"),
|
|
229
|
+
title: "Retry context test",
|
|
230
|
+
description: "Test retry context handling",
|
|
231
|
+
filesToModify: [],
|
|
232
|
+
dependsOn: [],
|
|
233
|
+
priority: 1,
|
|
234
|
+
status: "running",
|
|
235
|
+
retryCount: 1,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const workspace: WorkspaceInfo = {
|
|
239
|
+
path: "/workspace",
|
|
240
|
+
language: "typescript",
|
|
241
|
+
packageManager: "bun",
|
|
242
|
+
framework: "hono",
|
|
243
|
+
testFramework: "bun-test",
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const config = createMockConfig({ strictTdd: true });
|
|
247
|
+
const mockProvider = createMockProvider();
|
|
248
|
+
|
|
249
|
+
const mockMergeService = {
|
|
250
|
+
async mergeToParent() {
|
|
251
|
+
return { success: true };
|
|
252
|
+
},
|
|
253
|
+
} as unknown as MergeService;
|
|
254
|
+
|
|
255
|
+
const mockEventBus = {
|
|
256
|
+
on() {},
|
|
257
|
+
off() {},
|
|
258
|
+
emit() {},
|
|
259
|
+
} as unknown as EventBus;
|
|
260
|
+
|
|
261
|
+
const mockSpawner: ProcessSpawner = {
|
|
262
|
+
async spawn() {
|
|
263
|
+
return { exitCode: 0, stdout: "Tests passed", stderr: "" };
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const retryContext = {
|
|
268
|
+
retryCount: 1,
|
|
269
|
+
previousFailure: {
|
|
270
|
+
phase: "implementer-green" as const,
|
|
271
|
+
error: "Previous implementation failed",
|
|
272
|
+
retryCount: 0,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const result = await executeTddPipeline(
|
|
277
|
+
task,
|
|
278
|
+
workspace,
|
|
279
|
+
"aad/task-4",
|
|
280
|
+
"main",
|
|
281
|
+
"/parent",
|
|
282
|
+
createRunId("e2e-run-4"),
|
|
283
|
+
config,
|
|
284
|
+
mockProvider,
|
|
285
|
+
mockMergeService,
|
|
286
|
+
mockEventBus,
|
|
287
|
+
mockSpawner,
|
|
288
|
+
retryContext
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Should complete successfully with retry context
|
|
292
|
+
expect(result.status).toBe("completed");
|
|
293
|
+
}, 15_000);
|
|
294
|
+
|
|
295
|
+
test("executeTddPipeline emits phase transition events correctly", async () => {
|
|
296
|
+
const task: Task = {
|
|
297
|
+
taskId: createTaskId("e2e-task-5"),
|
|
298
|
+
title: "Event test",
|
|
299
|
+
description: "Test phase events",
|
|
300
|
+
filesToModify: [],
|
|
301
|
+
dependsOn: [],
|
|
302
|
+
priority: 1,
|
|
303
|
+
status: "running",
|
|
304
|
+
retryCount: 0,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const workspace: WorkspaceInfo = {
|
|
308
|
+
path: "/workspace",
|
|
309
|
+
language: "typescript",
|
|
310
|
+
packageManager: "bun",
|
|
311
|
+
framework: "hono",
|
|
312
|
+
testFramework: "bun-test",
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const config = createMockConfig({ strictTdd: true });
|
|
316
|
+
const mockProvider = createMockProvider();
|
|
317
|
+
|
|
318
|
+
const mockMergeService = {
|
|
319
|
+
async mergeToParent() {
|
|
320
|
+
return { success: true };
|
|
321
|
+
},
|
|
322
|
+
} as unknown as MergeService;
|
|
323
|
+
|
|
324
|
+
const events: Array<{ type: string; phase?: string }> = [];
|
|
325
|
+
const mockEventBus = {
|
|
326
|
+
on() {},
|
|
327
|
+
off() {},
|
|
328
|
+
emit(event: any) {
|
|
329
|
+
events.push(event);
|
|
330
|
+
},
|
|
331
|
+
} as unknown as EventBus;
|
|
332
|
+
|
|
333
|
+
const mockSpawner: ProcessSpawner = {
|
|
334
|
+
async spawn() {
|
|
335
|
+
return { exitCode: 0, stdout: "Tests passed", stderr: "" };
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
await executeTddPipeline(
|
|
340
|
+
task,
|
|
341
|
+
workspace,
|
|
342
|
+
"aad/task-5",
|
|
343
|
+
"main",
|
|
344
|
+
"/parent",
|
|
345
|
+
createRunId("e2e-run-5"),
|
|
346
|
+
config,
|
|
347
|
+
mockProvider,
|
|
348
|
+
mockMergeService,
|
|
349
|
+
mockEventBus,
|
|
350
|
+
mockSpawner
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Verify phase events
|
|
354
|
+
const phaseStarted = events.filter((e) => e.type === "execution:phase:started");
|
|
355
|
+
const phaseCompleted = events.filter((e) => e.type === "execution:phase:completed");
|
|
356
|
+
|
|
357
|
+
expect(phaseStarted.length).toBeGreaterThan(0);
|
|
358
|
+
expect(phaseCompleted.length).toBeGreaterThan(0);
|
|
359
|
+
}, 15_000);
|
|
360
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock ClaudeProvider for E2E tests
|
|
3
|
+
*/
|
|
4
|
+
import type {
|
|
5
|
+
ClaudeProvider,
|
|
6
|
+
ClaudeRequest,
|
|
7
|
+
ClaudeResponse,
|
|
8
|
+
} from "@aad/claude-provider/claude-provider.port";
|
|
9
|
+
|
|
10
|
+
export class MockClaudeProvider implements ClaudeProvider {
|
|
11
|
+
mockResponse: ClaudeResponse = {
|
|
12
|
+
result: "",
|
|
13
|
+
exitCode: 0,
|
|
14
|
+
model: "claude-sonnet-4-5",
|
|
15
|
+
effortLevel: "medium",
|
|
16
|
+
duration: 100,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async call(_req: ClaudeRequest): Promise<ClaudeResponse> {
|
|
20
|
+
return this.mockResponse;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Helper to set successful JSON response */
|
|
24
|
+
setSuccessResponse(data: unknown): void {
|
|
25
|
+
this.mockResponse = {
|
|
26
|
+
result: JSON.stringify(data),
|
|
27
|
+
exitCode: 0,
|
|
28
|
+
model: "claude-sonnet-4-5",
|
|
29
|
+
effortLevel: "medium",
|
|
30
|
+
duration: 100,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Helper to set error response */
|
|
35
|
+
setErrorResponse(errorMessage: string): void {
|
|
36
|
+
this.mockResponse = {
|
|
37
|
+
result: errorMessage,
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
model: "claude-sonnet-4-5",
|
|
40
|
+
effortLevel: "medium",
|
|
41
|
+
duration: 50,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Factory function to create a MockClaudeProvider with default successful response
|
|
48
|
+
*/
|
|
49
|
+
export function createMockProvider(): ClaudeProvider {
|
|
50
|
+
const provider = new MockClaudeProvider();
|
|
51
|
+
provider.setSuccessResponse({ ok: true });
|
|
52
|
+
return provider;
|
|
53
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock logger for E2E tests
|
|
3
|
+
*/
|
|
4
|
+
import type pino from "pino";
|
|
5
|
+
import type { Config } from "@aad/shared/config";
|
|
6
|
+
|
|
7
|
+
export function createMockLogger(): pino.Logger {
|
|
8
|
+
const noop = () => {};
|
|
9
|
+
return {
|
|
10
|
+
info: noop,
|
|
11
|
+
warn: noop,
|
|
12
|
+
error: noop,
|
|
13
|
+
debug: noop,
|
|
14
|
+
trace: noop,
|
|
15
|
+
fatal: noop,
|
|
16
|
+
child: () => createMockLogger(),
|
|
17
|
+
} as unknown as pino.Logger;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createMockConfig(overrides?: Partial<Config>): Config {
|
|
21
|
+
return {
|
|
22
|
+
workers: { num: 2, max: 4 },
|
|
23
|
+
models: {},
|
|
24
|
+
timeouts: { claude: 1200, test: 600, staleTask: 5400 },
|
|
25
|
+
retry: { maxRetries: 2 },
|
|
26
|
+
debug: false,
|
|
27
|
+
adaptiveEffort: false,
|
|
28
|
+
teams: { splitter: false, reviewer: false },
|
|
29
|
+
memorySync: false,
|
|
30
|
+
dashboard: { enabled: false, port: 7333, host: "localhost" },
|
|
31
|
+
git: { autoPush: false },
|
|
32
|
+
skipCompleted: true,
|
|
33
|
+
strictTdd: false,
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|