@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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/bin/aad.js +2 -0
  4. package/package.json +78 -0
  5. package/src/__tests__/e2e/pipeline-e2e.test.ts +279 -0
  6. package/src/__tests__/e2e/resume-e2e.test.ts +200 -0
  7. package/src/__tests__/integration/cli-smoke.test.ts +175 -0
  8. package/src/__tests__/integration/pipeline.test.ts +346 -0
  9. package/src/bun-imports.d.ts +14 -0
  10. package/src/main.ts +52 -0
  11. package/src/modules/claude-provider/__tests__/claude-cli.adapter.test.ts +277 -0
  12. package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +127 -0
  13. package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +347 -0
  14. package/src/modules/claude-provider/__tests__/effort-strategy.test.ts +212 -0
  15. package/src/modules/claude-provider/__tests__/provider-registry.test.ts +251 -0
  16. package/src/modules/claude-provider/__tests__/retry.test.ts +201 -0
  17. package/src/modules/claude-provider/claude-cli.adapter.ts +156 -0
  18. package/src/modules/claude-provider/claude-provider.port.ts +35 -0
  19. package/src/modules/claude-provider/claude-sdk.adapter.ts +217 -0
  20. package/src/modules/claude-provider/effort-strategy.ts +94 -0
  21. package/src/modules/claude-provider/index.ts +32 -0
  22. package/src/modules/claude-provider/provider-registry.ts +92 -0
  23. package/src/modules/claude-provider/retry.ts +81 -0
  24. package/src/modules/cli/__tests__/app.test.ts +160 -0
  25. package/src/modules/cli/__tests__/cleanup.test.ts +111 -0
  26. package/src/modules/cli/__tests__/commands.test.ts +186 -0
  27. package/src/modules/cli/__tests__/output.test.ts +329 -0
  28. package/src/modules/cli/__tests__/resume.test.ts +324 -0
  29. package/src/modules/cli/__tests__/run.test.ts +168 -0
  30. package/src/modules/cli/__tests__/shutdown.test.ts +168 -0
  31. package/src/modules/cli/__tests__/status.test.ts +144 -0
  32. package/src/modules/cli/app.ts +241 -0
  33. package/src/modules/cli/commands/cleanup.ts +120 -0
  34. package/src/modules/cli/commands/resume.ts +156 -0
  35. package/src/modules/cli/commands/run.ts +322 -0
  36. package/src/modules/cli/commands/status.ts +101 -0
  37. package/src/modules/cli/index.ts +29 -0
  38. package/src/modules/cli/output.ts +256 -0
  39. package/src/modules/cli/shutdown.ts +122 -0
  40. package/src/modules/dashboard/__tests__/api-routes.test.ts +204 -0
  41. package/src/modules/dashboard/__tests__/file-watcher.test.ts +34 -0
  42. package/src/modules/dashboard/__tests__/server.test.ts +120 -0
  43. package/src/modules/dashboard/__tests__/sse-broadcaster.test.ts +163 -0
  44. package/src/modules/dashboard/__tests__/sse-routes.test.ts +58 -0
  45. package/src/modules/dashboard/__tests__/state-aggregator.test.ts +330 -0
  46. package/src/modules/dashboard/index.ts +8 -0
  47. package/src/modules/dashboard/routes/api.ts +84 -0
  48. package/src/modules/dashboard/routes/sse.ts +37 -0
  49. package/src/modules/dashboard/server.ts +111 -0
  50. package/src/modules/dashboard/services/file-watcher.ts +36 -0
  51. package/src/modules/dashboard/services/sse-broadcaster.ts +81 -0
  52. package/src/modules/dashboard/services/state-aggregator.ts +132 -0
  53. package/src/modules/dashboard/ui/dashboard.html +405 -0
  54. package/src/modules/git-workspace/__tests__/branch-manager.test.ts +335 -0
  55. package/src/modules/git-workspace/__tests__/git-exec.test.ts +91 -0
  56. package/src/modules/git-workspace/__tests__/memory-sync.test.ts +273 -0
  57. package/src/modules/git-workspace/__tests__/merge-service.test.ts +286 -0
  58. package/src/modules/git-workspace/__tests__/settings-merge.test.ts +163 -0
  59. package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +247 -0
  60. package/src/modules/git-workspace/branch-manager.ts +191 -0
  61. package/src/modules/git-workspace/git-exec.ts +124 -0
  62. package/src/modules/git-workspace/index.ts +17 -0
  63. package/src/modules/git-workspace/memory-sync.ts +89 -0
  64. package/src/modules/git-workspace/merge-service.ts +156 -0
  65. package/src/modules/git-workspace/settings-merge.ts +95 -0
  66. package/src/modules/git-workspace/worktree-manager.ts +199 -0
  67. package/src/modules/logging/__tests__/log-store.test.ts +242 -0
  68. package/src/modules/logging/__tests__/logger.test.ts +81 -0
  69. package/src/modules/logging/__tests__/sse-transport.test.ts +93 -0
  70. package/src/modules/logging/index.ts +7 -0
  71. package/src/modules/logging/log-store.ts +80 -0
  72. package/src/modules/logging/logger.ts +55 -0
  73. package/src/modules/logging/transports/sse-transport.ts +28 -0
  74. package/src/modules/multi-repo/__tests__/multi-repo-planner.test.ts +93 -0
  75. package/src/modules/multi-repo/__tests__/repo-context.test.ts +79 -0
  76. package/src/modules/multi-repo/index.ts +12 -0
  77. package/src/modules/multi-repo/multi-repo-planner.ts +112 -0
  78. package/src/modules/multi-repo/repo-context.ts +71 -0
  79. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +10 -0
  80. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +10 -0
  81. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +13 -0
  82. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +10 -0
  83. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +10 -0
  84. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +5 -0
  85. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +5 -0
  86. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +10 -0
  87. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +10 -0
  88. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +13 -0
  89. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +10 -0
  90. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +10 -0
  91. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +5 -0
  92. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +5 -0
  93. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +10 -0
  94. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +10 -0
  95. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +13 -0
  96. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +10 -0
  97. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +10 -0
  98. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +5 -0
  99. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +5 -0
  100. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +10 -0
  101. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +10 -0
  102. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +13 -0
  103. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +10 -0
  104. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +10 -0
  105. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +5 -0
  106. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +5 -0
  107. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +10 -0
  108. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +10 -0
  109. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +13 -0
  110. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +10 -0
  111. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +10 -0
  112. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +5 -0
  113. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +5 -0
  114. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +10 -0
  115. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +10 -0
  116. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +13 -0
  117. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +10 -0
  118. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +10 -0
  119. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +5 -0
  120. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +5 -0
  121. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +10 -0
  122. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +10 -0
  123. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +13 -0
  124. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +10 -0
  125. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +10 -0
  126. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +5 -0
  127. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +5 -0
  128. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +10 -0
  129. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +10 -0
  130. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +13 -0
  131. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +10 -0
  132. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +10 -0
  133. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +5 -0
  134. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +5 -0
  135. package/src/modules/persistence/__tests__/file-lock.test.ts +141 -0
  136. package/src/modules/persistence/__tests__/index.test.ts +38 -0
  137. package/src/modules/persistence/__tests__/stores.test.ts +594 -0
  138. package/src/modules/persistence/file-lock.ts +158 -0
  139. package/src/modules/persistence/fs-run-store.ts +73 -0
  140. package/src/modules/persistence/fs-task-store.ts +152 -0
  141. package/src/modules/persistence/fs-worker-store.ts +116 -0
  142. package/src/modules/persistence/in-memory-stores.ts +98 -0
  143. package/src/modules/persistence/index.ts +60 -0
  144. package/src/modules/persistence/stores.port.ts +60 -0
  145. package/src/modules/planning/__tests__/file-conflict-validator.test.ts +256 -0
  146. package/src/modules/planning/__tests__/planning-service.test.ts +366 -0
  147. package/src/modules/planning/__tests__/project-detection.test.ts +707 -0
  148. package/src/modules/planning/file-conflict-validator.ts +135 -0
  149. package/src/modules/planning/index.ts +40 -0
  150. package/src/modules/planning/planning.service.ts +262 -0
  151. package/src/modules/planning/project-detection.ts +525 -0
  152. package/src/modules/plugin/__tests__/plugin-loader.test.ts +83 -0
  153. package/src/modules/plugin/__tests__/plugin-manager.test.ts +187 -0
  154. package/src/modules/plugin/index.ts +3 -0
  155. package/src/modules/plugin/plugin-loader.ts +46 -0
  156. package/src/modules/plugin/plugin-manager.ts +90 -0
  157. package/src/modules/plugin/plugin.types.ts +37 -0
  158. package/src/modules/process-manager/__tests__/process-manager.test.ts +210 -0
  159. package/src/modules/process-manager/__tests__/worker.test.ts +89 -0
  160. package/src/modules/process-manager/index.ts +5 -0
  161. package/src/modules/process-manager/process-manager.ts +193 -0
  162. package/src/modules/process-manager/worker.ts +106 -0
  163. package/src/modules/task-execution/__tests__/default-spawner.test.ts +154 -0
  164. package/src/modules/task-execution/__tests__/executor.test.ts +760 -0
  165. package/src/modules/task-execution/__tests__/implementer-green.test.ts +286 -0
  166. package/src/modules/task-execution/__tests__/merge-phase.test.ts +368 -0
  167. package/src/modules/task-execution/__tests__/reviewer.test.ts +302 -0
  168. package/src/modules/task-execution/__tests__/tester-red.test.ts +281 -0
  169. package/src/modules/task-execution/__tests__/tester-verify.test.ts +313 -0
  170. package/src/modules/task-execution/executor.ts +303 -0
  171. package/src/modules/task-execution/index.ts +45 -0
  172. package/src/modules/task-execution/phases/default-spawner.ts +49 -0
  173. package/src/modules/task-execution/phases/implementer-green.ts +100 -0
  174. package/src/modules/task-execution/phases/merge.ts +122 -0
  175. package/src/modules/task-execution/phases/reviewer.ts +160 -0
  176. package/src/modules/task-execution/phases/tester-red.ts +100 -0
  177. package/src/modules/task-execution/phases/tester-verify.ts +120 -0
  178. package/src/modules/task-queue/__tests__/dependency-resolver.test.ts +456 -0
  179. package/src/modules/task-queue/__tests__/dispatcher.test.ts +824 -0
  180. package/src/modules/task-queue/__tests__/task-plan.test.ts +122 -0
  181. package/src/modules/task-queue/__tests__/task.test.ts +130 -0
  182. package/src/modules/task-queue/dependency-resolver.ts +171 -0
  183. package/src/modules/task-queue/dispatcher.ts +372 -0
  184. package/src/modules/task-queue/index.ts +16 -0
  185. package/src/modules/task-queue/task-plan.ts +40 -0
  186. package/src/modules/task-queue/task.ts +67 -0
  187. package/src/shared/__tests__/config.test.ts +204 -0
  188. package/src/shared/__tests__/errors.test.ts +285 -0
  189. package/src/shared/__tests__/events.test.ts +496 -0
  190. package/src/shared/__tests__/types.test.ts +360 -0
  191. package/src/shared/config.ts +133 -0
  192. package/src/shared/errors.ts +128 -0
  193. package/src/shared/events.ts +171 -0
  194. package/src/shared/types.ts +143 -0
  195. 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
+ });