@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,200 @@
1
+ /**
2
+ * E2E Resume Test — FS-based stores with real EventBus
3
+ */
4
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
5
+ import { EventBus } from "@aad/shared/events";
6
+ import { createStores, type Stores } from "@aad/persistence/index";
7
+ import { resumeRun } from "@aad/cli/commands/resume";
8
+ import {
9
+ createRunId,
10
+ createTaskId,
11
+ type Task,
12
+ type RunState,
13
+ } from "@aad/shared/types";
14
+ import type { App } from "@aad/cli/app";
15
+ import { mkdtemp, rm } from "node:fs/promises";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { mock } from "bun:test";
19
+
20
+ function createMockLogger() {
21
+ const noop = () => {};
22
+ return {
23
+ info: noop, warn: noop, error: noop, debug: noop, trace: noop, fatal: noop,
24
+ child: () => createMockLogger(),
25
+ } as any;
26
+ }
27
+
28
+ describe("E2E Resume", () => {
29
+ let tmpDir: string;
30
+ let stores: Stores;
31
+ let eventBus: EventBus;
32
+
33
+ beforeEach(async () => {
34
+ tmpDir = await mkdtemp(join(tmpdir(), "aad-resume-e2e-"));
35
+ stores = createStores("fs", { basePath: tmpDir, logger: createMockLogger() });
36
+ eventBus = new EventBus();
37
+ });
38
+
39
+ afterEach(async () => {
40
+ await rm(tmpDir, { recursive: true, force: true });
41
+ });
42
+
43
+ function createApp(overrides?: Partial<App>): App {
44
+ return {
45
+ config: {
46
+ workers: { num: 2, max: 4 },
47
+ models: {},
48
+ timeouts: { claude: 1200, test: 600, staleTask: 10 }, // 10s timeout for tests
49
+ retry: { maxRetries: 2 },
50
+ debug: false,
51
+ adaptiveEffort: false,
52
+ teams: { splitter: false, reviewer: false },
53
+ memorySync: false,
54
+ dashboard: { enabled: false, port: 7333, host: "localhost" },
55
+ },
56
+ eventBus,
57
+ logger: createMockLogger(),
58
+ stores,
59
+ dispatcher: {
60
+ initialize: mock(async () => {}),
61
+ start: mock(() => {}),
62
+ } as any,
63
+ processManager: {
64
+ initializePool: mock(async () => {}),
65
+ } as any,
66
+ planningService: {} as any,
67
+ providerRegistry: {} as any,
68
+ worktreeManager: {} as any,
69
+ branchManager: {} as any,
70
+ mergeService: {} as any,
71
+ shutdown: mock(async () => {}),
72
+ ...overrides,
73
+ } as App;
74
+ }
75
+
76
+ test("resumes with FS-persisted tasks, resets running to pending, completes on event", async () => {
77
+ const runId = createRunId("e2e-run-1");
78
+
79
+ // Write run state
80
+ const runState: RunState = {
81
+ runId,
82
+ parentBranch: "main",
83
+ totalTasks: 2,
84
+ pending: 0,
85
+ running: 1,
86
+ completed: 1,
87
+ failed: 0,
88
+ startTime: new Date().toISOString(),
89
+ };
90
+ await stores.runStore.save(runState);
91
+
92
+ // Write tasks
93
+ const tasks: Task[] = [
94
+ {
95
+ taskId: createTaskId("task-e2e-run-1-1"),
96
+ title: "Completed task",
97
+ description: "Already done",
98
+ filesToModify: [],
99
+ dependsOn: [],
100
+ priority: 1,
101
+ status: "completed",
102
+ retryCount: 0,
103
+ },
104
+ {
105
+ taskId: createTaskId("task-e2e-run-1-2"),
106
+ title: "Running task",
107
+ description: "Was interrupted",
108
+ filesToModify: ["src/foo.ts"],
109
+ dependsOn: [],
110
+ priority: 2,
111
+ status: "running",
112
+ retryCount: 0,
113
+ },
114
+ ];
115
+ for (const t of tasks) {
116
+ await stores.taskStore.save(t);
117
+ }
118
+
119
+ const app = createApp();
120
+
121
+ // Emit completion after short delay
122
+ setTimeout(() => {
123
+ eventBus.emit({ type: "run:completed", runId });
124
+ }, 2000);
125
+
126
+ await resumeRun(app, "e2e-run-1");
127
+
128
+ // Verify running task was reset
129
+ const saved = await stores.taskStore.get(createTaskId("task-e2e-run-1-2"));
130
+ expect(saved?.status).toBe("pending");
131
+ expect(saved?.workerId).toBeUndefined();
132
+
133
+ expect(app.dispatcher.initialize).toHaveBeenCalled();
134
+ expect(app.dispatcher.start).toHaveBeenCalled();
135
+ expect(app.shutdown).toHaveBeenCalled();
136
+ });
137
+
138
+ test("early returns when all tasks already completed (FS store)", async () => {
139
+ const runId = createRunId("e2e-run-2");
140
+
141
+ await stores.runStore.save({
142
+ runId,
143
+ parentBranch: "main",
144
+ totalTasks: 1,
145
+ pending: 0,
146
+ running: 0,
147
+ completed: 1,
148
+ failed: 0,
149
+ startTime: new Date().toISOString(),
150
+ });
151
+
152
+ await stores.taskStore.save({
153
+ taskId: createTaskId("task-e2e-run-2-1"),
154
+ title: "Done task",
155
+ description: "Already completed",
156
+ filesToModify: [],
157
+ dependsOn: [],
158
+ priority: 1,
159
+ status: "completed",
160
+ retryCount: 0,
161
+ });
162
+
163
+ const app = createApp();
164
+ await resumeRun(app, "e2e-run-2");
165
+
166
+ expect(app.dispatcher.start).not.toHaveBeenCalled();
167
+ expect(app.shutdown).toHaveBeenCalled();
168
+ });
169
+
170
+ test("times out when no completion event arrives (FS store)", async () => {
171
+ const runId = createRunId("e2e-run-3");
172
+
173
+ await stores.runStore.save({
174
+ runId,
175
+ parentBranch: "main",
176
+ totalTasks: 1,
177
+ pending: 1,
178
+ running: 0,
179
+ completed: 0,
180
+ failed: 0,
181
+ startTime: new Date().toISOString(),
182
+ });
183
+
184
+ await stores.taskStore.save({
185
+ taskId: createTaskId("task-e2e-run-3-1"),
186
+ title: "Pending task",
187
+ description: "Waiting",
188
+ filesToModify: [],
189
+ dependsOn: [],
190
+ priority: 1,
191
+ status: "pending",
192
+ retryCount: 0,
193
+ });
194
+
195
+ const app = createApp();
196
+ app.config.timeouts.staleTask = 1; // 1s for this test
197
+
198
+ await expect(resumeRun(app, "e2e-run-3")).rejects.toThrow("Resume timed out");
199
+ });
200
+ });
@@ -0,0 +1,175 @@
1
+ /**
2
+ * CLI Smoke Tests
3
+ * 全CLIコマンド(run, resume, status, cleanup)の基本動作を検証
4
+ */
5
+
6
+ import { describe, test, expect, beforeAll, afterAll } from "bun:test";
7
+ import { rm, mkdir } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+
10
+ const TEST_DIR = join(import.meta.dir, ".tmp-cli-smoke-test");
11
+ const AAD_CLI = join(import.meta.dir, "../../main.ts");
12
+
13
+ /** Helper: spawn CLI and collect output with timeout */
14
+ async function runCli(
15
+ args: string[],
16
+ opts?: { cwd?: string; timeoutMs?: number }
17
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
18
+ const cwd = opts?.cwd ?? TEST_DIR;
19
+ const timeoutMs = opts?.timeoutMs ?? 20_000;
20
+
21
+ const proc = Bun.spawn(["bun", AAD_CLI, ...args], {
22
+ cwd,
23
+ stdout: "pipe",
24
+ stderr: "pipe",
25
+ });
26
+
27
+ const timer = setTimeout(() => proc.kill(), timeoutMs);
28
+
29
+ // Read stdout/stderr in parallel with proc.exited to avoid deadlock
30
+ const [stdout, stderr, exitCode] = await Promise.all([
31
+ new Response(proc.stdout).text(),
32
+ new Response(proc.stderr).text(),
33
+ proc.exited,
34
+ ]);
35
+
36
+ clearTimeout(timer);
37
+ return { stdout, stderr, exitCode };
38
+ }
39
+
40
+ describe("CLI Smoke Tests", () => {
41
+ beforeAll(async () => {
42
+ await rm(TEST_DIR, { recursive: true, force: true });
43
+ await mkdir(TEST_DIR, { recursive: true });
44
+ });
45
+
46
+ afterAll(async () => {
47
+ await rm(TEST_DIR, { recursive: true, force: true });
48
+ });
49
+
50
+ describe("aad --help", () => {
51
+ test("displays help message", async () => {
52
+ const { stdout } = await runCli(["--help"]);
53
+
54
+ expect(stdout).toContain("Autonomous Agent Development Orchestrator");
55
+ expect(stdout).toContain("run");
56
+ expect(stdout).toContain("resume");
57
+ expect(stdout).toContain("status");
58
+ expect(stdout).toContain("cleanup");
59
+ }, 30_000);
60
+ });
61
+
62
+ describe("aad --version", () => {
63
+ test("displays version number", async () => {
64
+ const { stdout } = await runCli(["--version"]);
65
+
66
+ expect(stdout).toMatch(/\d+\.\d+\.\d+/); // Semantic version format
67
+ }, 30_000);
68
+ });
69
+
70
+ describe("aad run", () => {
71
+ test("shows error when requirements file is missing", async () => {
72
+ const { exitCode, stdout, stderr } = await runCli(["run", "nonexistent.md"]);
73
+
74
+ expect(exitCode).not.toBe(0);
75
+ // Error may appear in stdout or stderr depending on Commander.js version
76
+ const combined = stdout + stderr;
77
+ expect(combined.length).toBeGreaterThan(0);
78
+ }, 30_000);
79
+
80
+ test("accepts --workers option", async () => {
81
+ // Create minimal requirements file
82
+ const reqFile = join(TEST_DIR, "requirements.md");
83
+ await Bun.write(reqFile, "# Test Requirements\n\nBasic test.\n");
84
+
85
+ const { exitCode } = await runCli(
86
+ ["run", reqFile, "--workers", "2", "--debug"],
87
+ { timeoutMs: 3000 }
88
+ );
89
+
90
+ // Expect either success or graceful shutdown (killed by timeout)
91
+ expect([0, 1, 143, 137]).toContain(exitCode); // 143=SIGTERM, 137=SIGKILL
92
+ }, 30_000);
93
+ });
94
+
95
+ describe("aad status", () => {
96
+ test("shows error when no run ID provided and no runs exist", async () => {
97
+ const { exitCode, stdout, stderr } = await runCli(["status", "--no-dashboard"]);
98
+ const combined = stdout + stderr;
99
+
100
+ expect(exitCode).not.toBe(0);
101
+ expect(combined).toContain("No runs found");
102
+ }, 30_000);
103
+
104
+ test("shows error for non-existent run ID", async () => {
105
+ const { exitCode, stdout, stderr } = await runCli(["status", "fake-run-id", "--no-dashboard"]);
106
+ const combined = stdout + stderr;
107
+
108
+ expect(exitCode).not.toBe(0);
109
+ expect(combined).toContain("Run not found");
110
+ }, 30_000);
111
+ });
112
+
113
+ describe("aad resume", () => {
114
+ test("shows error when run ID is missing", async () => {
115
+ const { exitCode, stdout, stderr } = await runCli(["resume", "--no-dashboard"]);
116
+ const combined = stdout + stderr;
117
+
118
+ expect(exitCode).not.toBe(0);
119
+ expect(combined).toContain("required");
120
+ }, 30_000);
121
+
122
+ test("shows error for non-existent run ID", async () => {
123
+ const { exitCode, stdout, stderr } = await runCli(["resume", "fake-run-id", "--no-dashboard"]);
124
+ const combined = stdout + stderr;
125
+
126
+ expect(exitCode).not.toBe(0);
127
+ expect(combined).toContain("Run not found");
128
+ }, 30_000);
129
+ });
130
+
131
+ describe("aad cleanup", () => {
132
+ test("runs without errors when no worktrees exist", async () => {
133
+ const { exitCode, stdout } = await runCli(["cleanup", "--no-dashboard"]);
134
+
135
+ expect(exitCode).toBe(0);
136
+ expect(stdout).toContain("No AAD worktrees found");
137
+ }, 30_000);
138
+
139
+ test("accepts --force option", async () => {
140
+ const { exitCode, stdout } = await runCli(["cleanup", "--force", "--no-dashboard"]);
141
+
142
+ expect(exitCode).toBe(0);
143
+ expect(stdout).toContain("Cleanup complete");
144
+ }, 30_000);
145
+
146
+ test("accepts run ID argument", async () => {
147
+ const { exitCode, stdout } = await runCli(["cleanup", "test-run-id", "--no-dashboard"]);
148
+
149
+ expect(exitCode).toBe(0);
150
+ expect(stdout).toContain("Cleanup complete");
151
+ }, 30_000);
152
+ });
153
+
154
+ describe("aad global options", () => {
155
+ test("accepts --debug flag", async () => {
156
+ const { exitCode } = await runCli(["cleanup", "--debug", "--no-dashboard"]);
157
+ expect(exitCode).toBe(0);
158
+ }, 30_000);
159
+
160
+ test("accepts --persist option", async () => {
161
+ const { exitCode } = await runCli(["cleanup", "--persist", "memory", "--no-dashboard"]);
162
+ expect(exitCode).toBe(0);
163
+ }, 30_000);
164
+
165
+ test("accepts --no-dashboard flag", async () => {
166
+ const { exitCode } = await runCli(["cleanup", "--no-dashboard"]);
167
+ expect(exitCode).toBe(0);
168
+ }, 30_000);
169
+
170
+ test("accepts --provider option", async () => {
171
+ const { exitCode } = await runCli(["cleanup", "--provider", "cli", "--no-dashboard"]);
172
+ expect(exitCode).toBe(0);
173
+ }, 30_000);
174
+ });
175
+ });