@ronkovic/aad 0.3.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/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/aad.js +2 -0
- package/package.json +78 -0
- package/src/__tests__/e2e/pipeline-e2e.test.ts +279 -0
- package/src/__tests__/e2e/resume-e2e.test.ts +200 -0
- package/src/__tests__/integration/cli-smoke.test.ts +175 -0
- package/src/__tests__/integration/pipeline.test.ts +346 -0
- package/src/bun-imports.d.ts +14 -0
- package/src/main.ts +52 -0
- package/src/modules/claude-provider/__tests__/claude-cli.adapter.test.ts +277 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +127 -0
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +347 -0
- package/src/modules/claude-provider/__tests__/effort-strategy.test.ts +212 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +251 -0
- package/src/modules/claude-provider/__tests__/retry.test.ts +201 -0
- package/src/modules/claude-provider/claude-cli.adapter.ts +156 -0
- package/src/modules/claude-provider/claude-provider.port.ts +35 -0
- package/src/modules/claude-provider/claude-sdk.adapter.ts +217 -0
- package/src/modules/claude-provider/effort-strategy.ts +94 -0
- package/src/modules/claude-provider/index.ts +32 -0
- package/src/modules/claude-provider/provider-registry.ts +92 -0
- package/src/modules/claude-provider/retry.ts +81 -0
- package/src/modules/cli/__tests__/app.test.ts +160 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +111 -0
- package/src/modules/cli/__tests__/commands.test.ts +186 -0
- package/src/modules/cli/__tests__/output.test.ts +329 -0
- package/src/modules/cli/__tests__/resume.test.ts +324 -0
- package/src/modules/cli/__tests__/run.test.ts +168 -0
- package/src/modules/cli/__tests__/shutdown.test.ts +168 -0
- package/src/modules/cli/__tests__/status.test.ts +144 -0
- package/src/modules/cli/app.ts +241 -0
- package/src/modules/cli/commands/cleanup.ts +120 -0
- package/src/modules/cli/commands/resume.ts +156 -0
- package/src/modules/cli/commands/run.ts +322 -0
- package/src/modules/cli/commands/status.ts +101 -0
- package/src/modules/cli/index.ts +29 -0
- package/src/modules/cli/output.ts +256 -0
- package/src/modules/cli/shutdown.ts +122 -0
- package/src/modules/dashboard/__tests__/api-routes.test.ts +204 -0
- package/src/modules/dashboard/__tests__/file-watcher.test.ts +34 -0
- package/src/modules/dashboard/__tests__/server.test.ts +120 -0
- package/src/modules/dashboard/__tests__/sse-broadcaster.test.ts +163 -0
- package/src/modules/dashboard/__tests__/sse-routes.test.ts +58 -0
- package/src/modules/dashboard/__tests__/state-aggregator.test.ts +330 -0
- package/src/modules/dashboard/index.ts +8 -0
- package/src/modules/dashboard/routes/api.ts +84 -0
- package/src/modules/dashboard/routes/sse.ts +37 -0
- package/src/modules/dashboard/server.ts +111 -0
- package/src/modules/dashboard/services/file-watcher.ts +36 -0
- package/src/modules/dashboard/services/sse-broadcaster.ts +81 -0
- package/src/modules/dashboard/services/state-aggregator.ts +132 -0
- package/src/modules/dashboard/ui/dashboard.html +405 -0
- package/src/modules/git-workspace/__tests__/branch-manager.test.ts +335 -0
- package/src/modules/git-workspace/__tests__/git-exec.test.ts +91 -0
- package/src/modules/git-workspace/__tests__/memory-sync.test.ts +273 -0
- package/src/modules/git-workspace/__tests__/merge-service.test.ts +286 -0
- package/src/modules/git-workspace/__tests__/settings-merge.test.ts +163 -0
- package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +247 -0
- package/src/modules/git-workspace/branch-manager.ts +191 -0
- package/src/modules/git-workspace/git-exec.ts +124 -0
- package/src/modules/git-workspace/index.ts +17 -0
- package/src/modules/git-workspace/memory-sync.ts +89 -0
- package/src/modules/git-workspace/merge-service.ts +156 -0
- package/src/modules/git-workspace/settings-merge.ts +95 -0
- package/src/modules/git-workspace/worktree-manager.ts +199 -0
- package/src/modules/logging/__tests__/log-store.test.ts +242 -0
- package/src/modules/logging/__tests__/logger.test.ts +81 -0
- package/src/modules/logging/__tests__/sse-transport.test.ts +93 -0
- package/src/modules/logging/index.ts +7 -0
- package/src/modules/logging/log-store.ts +80 -0
- package/src/modules/logging/logger.ts +55 -0
- package/src/modules/logging/transports/sse-transport.ts +28 -0
- package/src/modules/multi-repo/__tests__/multi-repo-planner.test.ts +93 -0
- package/src/modules/multi-repo/__tests__/repo-context.test.ts +79 -0
- package/src/modules/multi-repo/index.ts +12 -0
- package/src/modules/multi-repo/multi-repo-planner.ts +112 -0
- package/src/modules/multi-repo/repo-context.ts +71 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/file-lock.test.ts +141 -0
- package/src/modules/persistence/__tests__/index.test.ts +38 -0
- package/src/modules/persistence/__tests__/stores.test.ts +594 -0
- package/src/modules/persistence/file-lock.ts +158 -0
- package/src/modules/persistence/fs-run-store.ts +73 -0
- package/src/modules/persistence/fs-task-store.ts +152 -0
- package/src/modules/persistence/fs-worker-store.ts +116 -0
- package/src/modules/persistence/in-memory-stores.ts +98 -0
- package/src/modules/persistence/index.ts +60 -0
- package/src/modules/persistence/stores.port.ts +60 -0
- package/src/modules/planning/__tests__/file-conflict-validator.test.ts +256 -0
- package/src/modules/planning/__tests__/planning-service.test.ts +366 -0
- package/src/modules/planning/__tests__/project-detection.test.ts +707 -0
- package/src/modules/planning/file-conflict-validator.ts +135 -0
- package/src/modules/planning/index.ts +40 -0
- package/src/modules/planning/planning.service.ts +262 -0
- package/src/modules/planning/project-detection.ts +525 -0
- package/src/modules/plugin/__tests__/plugin-loader.test.ts +83 -0
- package/src/modules/plugin/__tests__/plugin-manager.test.ts +187 -0
- package/src/modules/plugin/index.ts +3 -0
- package/src/modules/plugin/plugin-loader.ts +46 -0
- package/src/modules/plugin/plugin-manager.ts +90 -0
- package/src/modules/plugin/plugin.types.ts +37 -0
- package/src/modules/process-manager/__tests__/process-manager.test.ts +210 -0
- package/src/modules/process-manager/__tests__/worker.test.ts +89 -0
- package/src/modules/process-manager/index.ts +5 -0
- package/src/modules/process-manager/process-manager.ts +193 -0
- package/src/modules/process-manager/worker.ts +106 -0
- package/src/modules/task-execution/__tests__/default-spawner.test.ts +154 -0
- package/src/modules/task-execution/__tests__/executor.test.ts +760 -0
- package/src/modules/task-execution/__tests__/implementer-green.test.ts +286 -0
- package/src/modules/task-execution/__tests__/merge-phase.test.ts +368 -0
- package/src/modules/task-execution/__tests__/reviewer.test.ts +302 -0
- package/src/modules/task-execution/__tests__/tester-red.test.ts +281 -0
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +313 -0
- package/src/modules/task-execution/executor.ts +303 -0
- package/src/modules/task-execution/index.ts +45 -0
- package/src/modules/task-execution/phases/default-spawner.ts +49 -0
- package/src/modules/task-execution/phases/implementer-green.ts +100 -0
- package/src/modules/task-execution/phases/merge.ts +122 -0
- package/src/modules/task-execution/phases/reviewer.ts +160 -0
- package/src/modules/task-execution/phases/tester-red.ts +100 -0
- package/src/modules/task-execution/phases/tester-verify.ts +120 -0
- package/src/modules/task-queue/__tests__/dependency-resolver.test.ts +456 -0
- package/src/modules/task-queue/__tests__/dispatcher.test.ts +824 -0
- package/src/modules/task-queue/__tests__/task-plan.test.ts +122 -0
- package/src/modules/task-queue/__tests__/task.test.ts +130 -0
- package/src/modules/task-queue/dependency-resolver.ts +171 -0
- package/src/modules/task-queue/dispatcher.ts +372 -0
- package/src/modules/task-queue/index.ts +16 -0
- package/src/modules/task-queue/task-plan.ts +40 -0
- package/src/modules/task-queue/task.ts +67 -0
- package/src/shared/__tests__/config.test.ts +204 -0
- package/src/shared/__tests__/errors.test.ts +285 -0
- package/src/shared/__tests__/events.test.ts +496 -0
- package/src/shared/__tests__/types.test.ts +360 -0
- package/src/shared/config.ts +133 -0
- package/src/shared/errors.ts +128 -0
- package/src/shared/events.ts +171 -0
- package/src/shared/types.ts +143 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Test: Cross-Module Pipeline
|
|
3
|
+
* Tests the integration between planning, task-queue, task-execution, and git-workspace modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
7
|
+
import { PlanningService } from "@aad/planning";
|
|
8
|
+
import { Dispatcher, getReadyTasks, validateTaskPlan } from "@aad/task-queue";
|
|
9
|
+
import { BranchManager, WorktreeManager, MergeService } from "@aad/git-workspace";
|
|
10
|
+
import { EventBus } from "../../shared/events";
|
|
11
|
+
import { createRunId, createTaskId, createWorkerId, type Task } from "../../shared/types";
|
|
12
|
+
import { CircularDependencyError } from "../../shared/errors";
|
|
13
|
+
import { createStores, type Stores } from "@aad/persistence";
|
|
14
|
+
import type { Config } from "../../shared/config";
|
|
15
|
+
import { pino } from "pino";
|
|
16
|
+
|
|
17
|
+
describe("Cross-Module Integration: Pipeline", () => {
|
|
18
|
+
let eventBus: EventBus;
|
|
19
|
+
let config: Config;
|
|
20
|
+
let stores: Stores;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
eventBus = new EventBus();
|
|
24
|
+
config = {
|
|
25
|
+
workers: { num: 2, max: 8 },
|
|
26
|
+
models: {},
|
|
27
|
+
timeouts: { claude: 1200, test: 600, staleTask: 5400 },
|
|
28
|
+
retry: { maxRetries: 2 },
|
|
29
|
+
debug: false,
|
|
30
|
+
adaptiveEffort: false,
|
|
31
|
+
teams: { splitter: false, reviewer: false },
|
|
32
|
+
memorySync: false,
|
|
33
|
+
dashboard: { enabled: false, port: 7333, host: "localhost" },
|
|
34
|
+
};
|
|
35
|
+
stores = createStores("memory");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("Module Interface Compatibility", () => {
|
|
39
|
+
test("planning produces task plan compatible with task-queue", async () => {
|
|
40
|
+
const logger = pino({ level: "silent" });
|
|
41
|
+
const mockClaudeProvider = {
|
|
42
|
+
async call() {
|
|
43
|
+
return {
|
|
44
|
+
result: JSON.stringify({
|
|
45
|
+
run_id: "run-001",
|
|
46
|
+
parent_branch: "main",
|
|
47
|
+
tasks: [
|
|
48
|
+
{
|
|
49
|
+
task_id: "task-1",
|
|
50
|
+
title: "Test task",
|
|
51
|
+
description: "Test description",
|
|
52
|
+
files_to_modify: ["test.ts"],
|
|
53
|
+
depends_on: [],
|
|
54
|
+
priority: 1,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
exitCode: 0,
|
|
59
|
+
model: "claude-sonnet-4-5",
|
|
60
|
+
effortLevel: "medium",
|
|
61
|
+
duration: 1000,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
} as any;
|
|
65
|
+
|
|
66
|
+
// Mock FileChecker for project detection
|
|
67
|
+
const mockFileChecker = {
|
|
68
|
+
async exists() { return false; },
|
|
69
|
+
async readText() { return ""; },
|
|
70
|
+
async glob() { return []; },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const planningService = new PlanningService(
|
|
74
|
+
mockClaudeProvider,
|
|
75
|
+
eventBus,
|
|
76
|
+
config,
|
|
77
|
+
logger,
|
|
78
|
+
{ fileChecker: mockFileChecker }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const taskPlan = await planningService.planTasks({
|
|
82
|
+
runId: createRunId("run-001"),
|
|
83
|
+
requirementsPath: "/tmp/requirements.md",
|
|
84
|
+
parentBranch: "main",
|
|
85
|
+
projectRoot: "/test/repo",
|
|
86
|
+
targetDocsDir: "/test/repo/.aad/docs/run-001",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Validate task plan structure is compatible with task-queue
|
|
90
|
+
expect(taskPlan.runId).toBeDefined();
|
|
91
|
+
expect(taskPlan.parentBranch).toBe("main");
|
|
92
|
+
expect(taskPlan.tasks).toBeInstanceOf(Array);
|
|
93
|
+
expect(taskPlan.tasks.length).toBeGreaterThan(0);
|
|
94
|
+
|
|
95
|
+
// Verify task-queue can validate the plan (throws on error)
|
|
96
|
+
expect(() => validateTaskPlan(taskPlan.tasks)).not.toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("task-queue dispatcher works with EventBus", async () => {
|
|
100
|
+
const task: Task = {
|
|
101
|
+
taskId: createTaskId("task-001"),
|
|
102
|
+
title: "Test task",
|
|
103
|
+
description: "Test",
|
|
104
|
+
filesToModify: ["test.ts"],
|
|
105
|
+
dependsOn: [],
|
|
106
|
+
priority: 1,
|
|
107
|
+
status: "pending",
|
|
108
|
+
retryCount: 0,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
await stores.taskStore.save(task);
|
|
112
|
+
|
|
113
|
+
const dispatcher = new Dispatcher({
|
|
114
|
+
eventBus,
|
|
115
|
+
taskStore: stores.taskStore,
|
|
116
|
+
workerStore: stores.workerStore,
|
|
117
|
+
runStore: stores.runStore,
|
|
118
|
+
config: {
|
|
119
|
+
maxRetries: config.retry.maxRetries,
|
|
120
|
+
staleTaskThreshold: config.timeouts.staleTask,
|
|
121
|
+
},
|
|
122
|
+
logger: pino({ level: "silent" }),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const events: string[] = [];
|
|
126
|
+
eventBus.on("queue:initialized", (event) => {
|
|
127
|
+
if (event.type === "queue:initialized") {
|
|
128
|
+
events.push(event.type);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await dispatcher.initialize({
|
|
133
|
+
runId: createRunId("run-001"),
|
|
134
|
+
parentBranch: "main",
|
|
135
|
+
tasks: [task],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Verify dispatcher emits queue:initialized event
|
|
139
|
+
expect(events).toContain("queue:initialized");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("git-workspace modules integrate correctly", async () => {
|
|
143
|
+
const repoRoot = "/test/repo";
|
|
144
|
+
const worktreeBase = "/test/worktrees";
|
|
145
|
+
|
|
146
|
+
const branchManager = new BranchManager({
|
|
147
|
+
repoRoot,
|
|
148
|
+
logger: pino({ level: "silent" }),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const worktreeManager = new WorktreeManager({
|
|
152
|
+
repoRoot,
|
|
153
|
+
worktreeBase,
|
|
154
|
+
logger: pino({ level: "silent" }),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const mergeService = new MergeService({
|
|
158
|
+
repoRoot,
|
|
159
|
+
logger: pino({ level: "silent" }),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Verify all git-workspace modules share compatible interfaces
|
|
163
|
+
expect(branchManager).toHaveProperty("createTaskBranch");
|
|
164
|
+
expect(worktreeManager).toHaveProperty("createTaskWorktree");
|
|
165
|
+
expect(mergeService).toHaveProperty("mergeToParent");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("Data Flow Between Modules", () => {
|
|
170
|
+
test("task flows from planning to task-queue to task-execution", async () => {
|
|
171
|
+
// Step 1: Planning produces tasks
|
|
172
|
+
const tasks: Task[] = [
|
|
173
|
+
{
|
|
174
|
+
taskId: createTaskId("task-001"),
|
|
175
|
+
title: "Add feature",
|
|
176
|
+
description: "New feature",
|
|
177
|
+
filesToModify: ["src/feature.ts"],
|
|
178
|
+
dependsOn: [],
|
|
179
|
+
priority: 1,
|
|
180
|
+
status: "pending",
|
|
181
|
+
retryCount: 0,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
// Step 2: Task-queue validates and manages tasks
|
|
186
|
+
expect(() => validateTaskPlan(tasks)).not.toThrow();
|
|
187
|
+
|
|
188
|
+
const taskMap = new Map(tasks.map((t) => [t.taskId, t]));
|
|
189
|
+
const readyTasks = getReadyTasks(taskMap);
|
|
190
|
+
expect(readyTasks.length).toBeGreaterThan(0);
|
|
191
|
+
expect(readyTasks[0]!.taskId).toBe(tasks[0]!.taskId);
|
|
192
|
+
|
|
193
|
+
// Step 3: Task-execution can process the task structure
|
|
194
|
+
// (We don't actually execute, just verify the interface compatibility)
|
|
195
|
+
const task = tasks[0]!;
|
|
196
|
+
expect(task.taskId).toBeDefined();
|
|
197
|
+
expect(task.filesToModify).toBeInstanceOf(Array);
|
|
198
|
+
expect(task.dependsOn).toBeInstanceOf(Array);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("EventBus enables cross-module communication", async () => {
|
|
202
|
+
const events: Array<{ type: string; source: string }> = [];
|
|
203
|
+
|
|
204
|
+
// Simulate planning emitting event
|
|
205
|
+
eventBus.on("planning:completed", (event) => {
|
|
206
|
+
if (event.type === "planning:completed") {
|
|
207
|
+
events.push({ type: event.type, source: "planning" });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Simulate task-queue emitting event
|
|
212
|
+
eventBus.on("task:dispatched", (event) => {
|
|
213
|
+
if (event.type === "task:dispatched") {
|
|
214
|
+
events.push({ type: event.type, source: "task-queue" });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Emit events
|
|
219
|
+
eventBus.emit({
|
|
220
|
+
type: "planning:completed",
|
|
221
|
+
taskCount: 5,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
eventBus.emit({
|
|
225
|
+
type: "task:dispatched",
|
|
226
|
+
taskId: createTaskId("task-001"),
|
|
227
|
+
workerId: createWorkerId("worker-1"),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(events).toHaveLength(2);
|
|
231
|
+
expect(events[0]?.type).toBe("planning:completed");
|
|
232
|
+
expect(events[1]?.type).toBe("task:dispatched");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("Type Compatibility", () => {
|
|
237
|
+
test("shared types are compatible across modules", () => {
|
|
238
|
+
const testRunId = createRunId("run-001");
|
|
239
|
+
const testTaskId = createTaskId("task-001");
|
|
240
|
+
|
|
241
|
+
// Verify branded types work across modules
|
|
242
|
+
const testTask: Task = {
|
|
243
|
+
taskId: testTaskId,
|
|
244
|
+
title: "Test",
|
|
245
|
+
description: "Test",
|
|
246
|
+
filesToModify: [],
|
|
247
|
+
dependsOn: [],
|
|
248
|
+
priority: 1,
|
|
249
|
+
status: "pending",
|
|
250
|
+
retryCount: 0,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
expect(testRunId).toBeDefined();
|
|
254
|
+
expect(testTask.taskId).toBe(testTaskId);
|
|
255
|
+
|
|
256
|
+
// Verify Config type is consistent
|
|
257
|
+
expect(config.workers.num).toBeGreaterThan(0);
|
|
258
|
+
expect(config.timeouts.claude).toBeGreaterThan(0);
|
|
259
|
+
expect(config.dashboard.port).toBeGreaterThan(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("stores interface is compatible with dispatcher", async () => {
|
|
263
|
+
const task: Task = {
|
|
264
|
+
taskId: createTaskId("task-001"),
|
|
265
|
+
title: "Test",
|
|
266
|
+
description: "Test",
|
|
267
|
+
filesToModify: [],
|
|
268
|
+
dependsOn: [],
|
|
269
|
+
priority: 1,
|
|
270
|
+
status: "pending",
|
|
271
|
+
retryCount: 0,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Verify stores implement the expected interface
|
|
275
|
+
await stores.taskStore.save(task);
|
|
276
|
+
const retrieved = await stores.taskStore.get(task.taskId);
|
|
277
|
+
expect(retrieved?.taskId).toBe(task.taskId);
|
|
278
|
+
|
|
279
|
+
// Verify Dispatcher can use these stores
|
|
280
|
+
const dispatcher = new Dispatcher({
|
|
281
|
+
eventBus,
|
|
282
|
+
taskStore: stores.taskStore,
|
|
283
|
+
workerStore: stores.workerStore,
|
|
284
|
+
runStore: stores.runStore,
|
|
285
|
+
config: {
|
|
286
|
+
maxRetries: config.retry.maxRetries,
|
|
287
|
+
staleTaskThreshold: config.timeouts.staleTask,
|
|
288
|
+
},
|
|
289
|
+
logger: pino({ level: "silent" }),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(dispatcher).toBeDefined();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("Error Propagation", () => {
|
|
297
|
+
test("errors from planning propagate correctly", async () => {
|
|
298
|
+
const logger = pino({ level: "silent" });
|
|
299
|
+
const mockClaudeProvider = {
|
|
300
|
+
async call() {
|
|
301
|
+
return {
|
|
302
|
+
result: "Invalid JSON",
|
|
303
|
+
exitCode: 1,
|
|
304
|
+
model: "claude-sonnet-4-5",
|
|
305
|
+
effortLevel: "medium",
|
|
306
|
+
duration: 1000,
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
} as any;
|
|
310
|
+
|
|
311
|
+
const planningService = new PlanningService(
|
|
312
|
+
mockClaudeProvider,
|
|
313
|
+
eventBus,
|
|
314
|
+
config,
|
|
315
|
+
logger
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
await expect(
|
|
319
|
+
planningService.planTasks({
|
|
320
|
+
runId: createRunId("run-001"),
|
|
321
|
+
requirementsPath: "/tmp/requirements.md",
|
|
322
|
+
parentBranch: "main",
|
|
323
|
+
projectRoot: "/test/repo",
|
|
324
|
+
targetDocsDir: "/test/repo/.aad/docs/run-001",
|
|
325
|
+
})
|
|
326
|
+
).rejects.toThrow();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("task validation errors are caught by task-queue", () => {
|
|
330
|
+
const invalidTasks: Task[] = [
|
|
331
|
+
{
|
|
332
|
+
taskId: createTaskId("task-001"),
|
|
333
|
+
title: "Task 1",
|
|
334
|
+
description: "Depends on itself",
|
|
335
|
+
filesToModify: [],
|
|
336
|
+
dependsOn: [createTaskId("task-001")], // Circular dependency
|
|
337
|
+
priority: 1,
|
|
338
|
+
status: "pending",
|
|
339
|
+
retryCount: 0,
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
expect(() => validateTaskPlan(invalidTasks)).toThrow(CircularDependencyError);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun-specific import attributes type declarations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
declare module "*.html" {
|
|
6
|
+
const content: string;
|
|
7
|
+
export default content;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module "*.json" {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
const value: any;
|
|
13
|
+
export default value;
|
|
14
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* AAD - Autonomous Agent Development Orchestrator
|
|
4
|
+
* CLI Entry Point
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import {
|
|
9
|
+
createApp,
|
|
10
|
+
createRunCommand,
|
|
11
|
+
createResumeCommand,
|
|
12
|
+
createStatusCommand,
|
|
13
|
+
createCleanupCommand,
|
|
14
|
+
type App,
|
|
15
|
+
type AppOptions,
|
|
16
|
+
} from "./modules/cli";
|
|
17
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
18
|
+
|
|
19
|
+
const program = new Command()
|
|
20
|
+
.name("aad")
|
|
21
|
+
.description("Autonomous Agent Development Orchestrator - Multi-agent TDD pipeline")
|
|
22
|
+
.version(packageJson.version);
|
|
23
|
+
|
|
24
|
+
// Global options
|
|
25
|
+
program
|
|
26
|
+
.option("-w, --workers <number>", "Number of workers", parseInt)
|
|
27
|
+
.option("--persist <mode>", "Persistence mode: memory or fs", "memory")
|
|
28
|
+
.option("--debug", "Enable debug logging", false)
|
|
29
|
+
.option("--no-dashboard", "Disable dashboard server")
|
|
30
|
+
.option("--provider <type>", "Default provider: cli or sdk", "cli");
|
|
31
|
+
|
|
32
|
+
// App factory with global options
|
|
33
|
+
const getApp = (): App => {
|
|
34
|
+
const opts = program.opts();
|
|
35
|
+
const appOptions: AppOptions = {
|
|
36
|
+
workers: opts.workers,
|
|
37
|
+
persist: opts.persist,
|
|
38
|
+
debug: opts.debug,
|
|
39
|
+
dashboard: opts.dashboard,
|
|
40
|
+
providerDefault: opts.provider,
|
|
41
|
+
};
|
|
42
|
+
return createApp(appOptions);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Commands
|
|
46
|
+
program.addCommand(createRunCommand(getApp));
|
|
47
|
+
program.addCommand(createResumeCommand(getApp));
|
|
48
|
+
program.addCommand(createStatusCommand(getApp));
|
|
49
|
+
program.addCommand(createCleanupCommand(getApp));
|
|
50
|
+
|
|
51
|
+
// Parse CLI args
|
|
52
|
+
program.parse();
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { describe, test, expect, mock } from "bun:test";
|
|
2
|
+
import { ClaudeCliAdapter } from "../claude-cli.adapter";
|
|
3
|
+
import type { Config } from "../../../shared/config";
|
|
4
|
+
import type pino from "pino";
|
|
5
|
+
|
|
6
|
+
describe("ClaudeCliAdapter", () => {
|
|
7
|
+
const mockConfig: Config = {
|
|
8
|
+
timeouts: {
|
|
9
|
+
claude: 1200,
|
|
10
|
+
test: 600,
|
|
11
|
+
staleTask: 5400,
|
|
12
|
+
},
|
|
13
|
+
retry: {
|
|
14
|
+
maxRetries: 2,
|
|
15
|
+
},
|
|
16
|
+
models: {
|
|
17
|
+
default: "sonnet",
|
|
18
|
+
},
|
|
19
|
+
} as Config;
|
|
20
|
+
|
|
21
|
+
const mockLogger = {
|
|
22
|
+
info: mock(() => {}),
|
|
23
|
+
error: mock(() => {}),
|
|
24
|
+
warn: mock(() => {}),
|
|
25
|
+
debug: mock(() => {}),
|
|
26
|
+
} as unknown as pino.Logger;
|
|
27
|
+
|
|
28
|
+
test("constructs CLI arguments correctly", async () => {
|
|
29
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
30
|
+
|
|
31
|
+
// Bun.spawnをモック
|
|
32
|
+
const originalSpawn = Bun.spawn;
|
|
33
|
+
let capturedArgs: string[] = [];
|
|
34
|
+
|
|
35
|
+
(Bun as any).spawn = (args: string[]) => {
|
|
36
|
+
capturedArgs = args;
|
|
37
|
+
return {
|
|
38
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
39
|
+
stdout: {
|
|
40
|
+
text: async () => "test output",
|
|
41
|
+
},
|
|
42
|
+
stderr: {
|
|
43
|
+
text: async () => "",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await adapter.call({
|
|
50
|
+
prompt: "test prompt",
|
|
51
|
+
model: "opus",
|
|
52
|
+
effortLevel: "high",
|
|
53
|
+
timeout: 30,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(capturedArgs).toContain("claude");
|
|
57
|
+
expect(capturedArgs).toContain("-p");
|
|
58
|
+
expect(capturedArgs).toContain("--model");
|
|
59
|
+
expect(capturedArgs).toContain("opus");
|
|
60
|
+
expect(capturedArgs).toContain("test prompt");
|
|
61
|
+
} finally {
|
|
62
|
+
(Bun as any).spawn = originalSpawn;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("includes system prompt when provided", async () => {
|
|
67
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
68
|
+
|
|
69
|
+
const originalSpawn = Bun.spawn;
|
|
70
|
+
let capturedArgs: string[] = [];
|
|
71
|
+
|
|
72
|
+
(Bun as any).spawn = (args: string[]) => {
|
|
73
|
+
capturedArgs = args;
|
|
74
|
+
return {
|
|
75
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
76
|
+
stdout: {
|
|
77
|
+
text: async () => "test output",
|
|
78
|
+
},
|
|
79
|
+
stderr: {
|
|
80
|
+
text: async () => "",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await adapter.call({
|
|
87
|
+
prompt: "test prompt",
|
|
88
|
+
systemPrompt: "You are a test assistant",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(capturedArgs).toContain("--append-system-prompt");
|
|
92
|
+
expect(capturedArgs).toContain("You are a test assistant");
|
|
93
|
+
} finally {
|
|
94
|
+
(Bun as any).spawn = originalSpawn;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("includes allowed tools when provided", async () => {
|
|
99
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
100
|
+
|
|
101
|
+
const originalSpawn = Bun.spawn;
|
|
102
|
+
let capturedArgs: string[] = [];
|
|
103
|
+
|
|
104
|
+
(Bun as any).spawn = (args: string[]) => {
|
|
105
|
+
capturedArgs = args;
|
|
106
|
+
return {
|
|
107
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
108
|
+
stdout: {
|
|
109
|
+
text: async () => "test output",
|
|
110
|
+
},
|
|
111
|
+
stderr: {
|
|
112
|
+
text: async () => "",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await adapter.call({
|
|
119
|
+
prompt: "test prompt",
|
|
120
|
+
allowedTools: ["read", "write"],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(capturedArgs).toContain("--allowed-tools");
|
|
124
|
+
expect(capturedArgs).toContain("read,write");
|
|
125
|
+
} finally {
|
|
126
|
+
(Bun as any).spawn = originalSpawn;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("injects subagents into prompt as fallback", async () => {
|
|
131
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
132
|
+
|
|
133
|
+
const originalSpawn = Bun.spawn;
|
|
134
|
+
let capturedArgs: string[] = [];
|
|
135
|
+
|
|
136
|
+
(Bun as any).spawn = (args: string[]) => {
|
|
137
|
+
capturedArgs = args;
|
|
138
|
+
return {
|
|
139
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
140
|
+
stdout: {
|
|
141
|
+
text: async () => "test output",
|
|
142
|
+
},
|
|
143
|
+
stderr: {
|
|
144
|
+
text: async () => "",
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await adapter.call({
|
|
151
|
+
prompt: "test prompt",
|
|
152
|
+
subagents: [
|
|
153
|
+
{ name: "analyzer", prompt: "Analyze the code" },
|
|
154
|
+
{ name: "reviewer", prompt: "Review the code" },
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const promptArg = capturedArgs[capturedArgs.length - 1];
|
|
159
|
+
expect(promptArg).toContain("test prompt");
|
|
160
|
+
expect(promptArg).toContain("Available Subagents");
|
|
161
|
+
expect(promptArg).toContain("analyzer: Analyze the code");
|
|
162
|
+
expect(promptArg).toContain("reviewer: Review the code");
|
|
163
|
+
} finally {
|
|
164
|
+
(Bun as any).spawn = originalSpawn;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("does not inject subagents when empty", async () => {
|
|
169
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
170
|
+
|
|
171
|
+
const originalSpawn = Bun.spawn;
|
|
172
|
+
let capturedArgs: string[] = [];
|
|
173
|
+
|
|
174
|
+
(Bun as any).spawn = (args: string[]) => {
|
|
175
|
+
capturedArgs = args;
|
|
176
|
+
return {
|
|
177
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
178
|
+
stdout: {
|
|
179
|
+
text: async () => "test output",
|
|
180
|
+
},
|
|
181
|
+
stderr: {
|
|
182
|
+
text: async () => "",
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
await adapter.call({
|
|
189
|
+
prompt: "test prompt",
|
|
190
|
+
subagents: [],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const promptArg = capturedArgs[capturedArgs.length - 1];
|
|
194
|
+
expect(promptArg).toBe("test prompt");
|
|
195
|
+
expect(promptArg).not.toContain("Available Subagents");
|
|
196
|
+
} finally {
|
|
197
|
+
(Bun as any).spawn = originalSpawn;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("throws ClaudeProviderError on non-zero exit code", async () => {
|
|
202
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger);
|
|
203
|
+
|
|
204
|
+
const originalSpawn = Bun.spawn;
|
|
205
|
+
|
|
206
|
+
(Bun as any).spawn = () => {
|
|
207
|
+
return {
|
|
208
|
+
exited: Promise.resolve({ exitCode: 1 }),
|
|
209
|
+
stdout: {
|
|
210
|
+
text: async () => "",
|
|
211
|
+
},
|
|
212
|
+
stderr: {
|
|
213
|
+
text: async () => "Claude error",
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await expect(
|
|
220
|
+
adapter.call({
|
|
221
|
+
prompt: "test prompt",
|
|
222
|
+
maxRetries: 0, // リトライなし
|
|
223
|
+
})
|
|
224
|
+
).rejects.toThrow("Claude CLI failed");
|
|
225
|
+
} finally {
|
|
226
|
+
(Bun as any).spawn = originalSpawn;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("applies retry logic with effort escalation", async () => {
|
|
231
|
+
const adapter = new ClaudeCliAdapter(mockConfig, mockLogger, 10); // 10ms backoff for testing
|
|
232
|
+
|
|
233
|
+
const originalSpawn = Bun.spawn;
|
|
234
|
+
let attemptCount = 0;
|
|
235
|
+
|
|
236
|
+
(Bun as any).spawn = (_args: string[]) => {
|
|
237
|
+
attemptCount++;
|
|
238
|
+
|
|
239
|
+
// 2回目の試行で成功
|
|
240
|
+
if (attemptCount >= 2) {
|
|
241
|
+
return {
|
|
242
|
+
exited: Promise.resolve({ exitCode: 0 }),
|
|
243
|
+
stdout: {
|
|
244
|
+
text: async () => "success",
|
|
245
|
+
},
|
|
246
|
+
stderr: {
|
|
247
|
+
text: async () => "",
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 1回目は失敗
|
|
253
|
+
return {
|
|
254
|
+
exited: Promise.resolve({ exitCode: 1 }),
|
|
255
|
+
stdout: {
|
|
256
|
+
text: async () => "",
|
|
257
|
+
},
|
|
258
|
+
stderr: {
|
|
259
|
+
text: async () => "temporary error",
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const response = await adapter.call({
|
|
266
|
+
prompt: "test prompt",
|
|
267
|
+
effortLevel: "low",
|
|
268
|
+
maxRetries: 2,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(attemptCount).toBe(2);
|
|
272
|
+
expect(response.effortLevel).toBe("medium"); // escalated from low
|
|
273
|
+
} finally {
|
|
274
|
+
(Bun as any).spawn = originalSpawn;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|