@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,360 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ createTaskId,
4
+ createWorkerId,
5
+ createRunId,
6
+ createRepoName,
7
+ type TaskId,
8
+ type WorkerId,
9
+ type RunId,
10
+ type TaskStatus,
11
+ type EffortLevel,
12
+ type PhaseName,
13
+ type ProgressState,
14
+ type TaskExecutionResult,
15
+ type Task,
16
+ type Worker,
17
+ type WorkerStatus,
18
+ type RunState,
19
+ type TaskPlan,
20
+ } from "../types";
21
+
22
+ describe("Branded Types", () => {
23
+ test("createTaskId creates valid TaskId", () => {
24
+ const id = createTaskId("task-001");
25
+ expect(id as string).toBe("task-001");
26
+ });
27
+
28
+ test("createTaskId rejects empty string", () => {
29
+ expect(() => createTaskId("")).toThrow("cannot be empty");
30
+ });
31
+
32
+ test("createWorkerId creates valid WorkerId", () => {
33
+ const id = createWorkerId("worker-1");
34
+ expect(id as string).toBe("worker-1");
35
+ });
36
+
37
+ test("createWorkerId rejects empty string", () => {
38
+ expect(() => createWorkerId("")).toThrow("cannot be empty");
39
+ });
40
+
41
+ test("createRunId creates valid RunId", () => {
42
+ const id = createRunId("run-123");
43
+ expect(id as string).toBe("run-123");
44
+ });
45
+
46
+ test("createRunId rejects empty string", () => {
47
+ expect(() => createRunId("")).toThrow("cannot be empty");
48
+ });
49
+
50
+ test("createRepoName creates valid RepoName", () => {
51
+ const name = createRepoName("my-repo");
52
+ expect(name as string).toBe("my-repo");
53
+ });
54
+
55
+ test("createRepoName rejects empty string", () => {
56
+ expect(() => createRepoName("")).toThrow("cannot be empty");
57
+ });
58
+
59
+ test("createRepoName rejects invalid characters", () => {
60
+ expect(() => createRepoName("my repo")).toThrow("alphanumeric");
61
+ expect(() => createRepoName("my/repo")).toThrow("alphanumeric");
62
+ });
63
+
64
+ test("branded types are string-compatible", () => {
65
+ const taskId: TaskId = createTaskId("t1");
66
+ const workerId: WorkerId = createWorkerId("w1");
67
+ const runId: RunId = createRunId("r1");
68
+
69
+ expect(typeof taskId).toBe("string");
70
+ expect(typeof workerId).toBe("string");
71
+ expect(typeof runId).toBe("string");
72
+ });
73
+ });
74
+
75
+ describe("TaskStatus", () => {
76
+ test("accepts valid status values", () => {
77
+ const statuses: TaskStatus[] = ["pending", "running", "completed", "failed"];
78
+ statuses.forEach((status) => {
79
+ expect(["pending", "running", "completed", "failed"]).toContain(status);
80
+ });
81
+ });
82
+ });
83
+
84
+ describe("EffortLevel", () => {
85
+ test("accepts valid effort levels", () => {
86
+ const levels: EffortLevel[] = ["low", "medium", "high"];
87
+ levels.forEach((level) => {
88
+ expect(["low", "medium", "high"]).toContain(level);
89
+ });
90
+ });
91
+ });
92
+
93
+ describe("PhaseName", () => {
94
+ test("accepts valid phase names", () => {
95
+ const phases: PhaseName[] = [
96
+ "splitter",
97
+ "tester",
98
+ "implementer",
99
+ "reviewer",
100
+ "merge-resolver",
101
+ ];
102
+ phases.forEach((phase) => {
103
+ expect([
104
+ "splitter",
105
+ "tester",
106
+ "implementer",
107
+ "reviewer",
108
+ "merge-resolver",
109
+ ]).toContain(phase);
110
+ });
111
+ });
112
+ });
113
+
114
+ describe("ProgressState", () => {
115
+ test("creates valid ProgressState", () => {
116
+ const state: ProgressState = {
117
+ total: 10,
118
+ pending: 5,
119
+ running: 3,
120
+ completed: 2,
121
+ failed: 0,
122
+ };
123
+
124
+ expect(state.total).toBe(10);
125
+ expect(state.pending).toBe(5);
126
+ expect(state.running).toBe(3);
127
+ expect(state.completed).toBe(2);
128
+ expect(state.failed).toBe(0);
129
+ });
130
+ });
131
+
132
+ describe("TaskExecutionResult", () => {
133
+ test("creates successful result", () => {
134
+ const result: TaskExecutionResult = {
135
+ taskId: createTaskId("t1"),
136
+ status: "completed",
137
+ duration: 1500,
138
+ output: "Task completed successfully",
139
+ };
140
+
141
+ expect(result.taskId as string).toBe("t1");
142
+ expect(result.status).toBe("completed");
143
+ expect(result.duration).toBe(1500);
144
+ expect(result.output).toBe("Task completed successfully");
145
+ expect(result.error).toBeUndefined();
146
+ });
147
+
148
+ test("creates failed result with error", () => {
149
+ const result: TaskExecutionResult = {
150
+ taskId: createTaskId("t2"),
151
+ status: "failed",
152
+ duration: 500,
153
+ error: "Timeout exceeded",
154
+ };
155
+
156
+ expect(result.taskId as string).toBe("t2");
157
+ expect(result.status).toBe("failed");
158
+ expect(result.error).toBe("Timeout exceeded");
159
+ });
160
+ });
161
+
162
+ describe("Task", () => {
163
+ test("creates valid task with all required fields", () => {
164
+ const task: Task = {
165
+ taskId: createTaskId("task-1"),
166
+ title: "Implement feature X",
167
+ description: "Add new feature X to the system",
168
+ filesToModify: ["src/feature-x.ts", "src/types.ts"],
169
+ dependsOn: [],
170
+ priority: 1,
171
+ status: "pending",
172
+ retryCount: 0,
173
+ };
174
+
175
+ expect(task.taskId as string).toBe("task-1");
176
+ expect(task.title).toBe("Implement feature X");
177
+ expect(task.filesToModify).toHaveLength(2);
178
+ expect(task.dependsOn).toHaveLength(0);
179
+ expect(task.status).toBe("pending");
180
+ expect(task.retryCount).toBe(0);
181
+ });
182
+
183
+ test("creates task with dependencies", () => {
184
+ const task: Task = {
185
+ taskId: createTaskId("task-2"),
186
+ title: "Task 2",
187
+ description: "Depends on task-1",
188
+ filesToModify: [],
189
+ dependsOn: [createTaskId("task-1")],
190
+ priority: 2,
191
+ status: "pending",
192
+ retryCount: 0,
193
+ };
194
+
195
+ expect(task.dependsOn).toHaveLength(1);
196
+ expect(task.dependsOn[0] as string).toBe("task-1");
197
+ });
198
+
199
+ test("creates task with optional fields", () => {
200
+ const now = new Date().toISOString();
201
+ const task: Task = {
202
+ taskId: createTaskId("task-3"),
203
+ title: "Task 3",
204
+ description: "Running task",
205
+ filesToModify: ["file.ts"],
206
+ dependsOn: [],
207
+ priority: 1,
208
+ status: "running",
209
+ workerId: createWorkerId("worker-1"),
210
+ startTime: now,
211
+ retryCount: 1,
212
+ failureReason: "Previous timeout",
213
+ };
214
+
215
+ expect(task.workerId as string).toBe("worker-1");
216
+ expect(task.startTime).toBe(now);
217
+ expect(task.retryCount).toBe(1);
218
+ expect(task.failureReason).toBe("Previous timeout");
219
+ });
220
+ });
221
+
222
+ describe("WorkerStatus", () => {
223
+ test("accepts valid worker status values", () => {
224
+ const statuses: WorkerStatus[] = ["idle", "busy", "stopped"];
225
+ statuses.forEach((status) => {
226
+ expect(["idle", "busy", "stopped"]).toContain(status);
227
+ });
228
+ });
229
+ });
230
+
231
+ describe("Worker", () => {
232
+ test("creates idle worker", () => {
233
+ const worker: Worker = {
234
+ workerId: createWorkerId("worker-1"),
235
+ status: "idle",
236
+ currentTask: null,
237
+ };
238
+
239
+ expect(worker.workerId as string).toBe("worker-1");
240
+ expect(worker.status).toBe("idle");
241
+ expect(worker.currentTask).toBeNull();
242
+ });
243
+
244
+ test("creates busy worker with task", () => {
245
+ const worker: Worker = {
246
+ workerId: createWorkerId("worker-2"),
247
+ status: "busy",
248
+ currentTask: createTaskId("task-1"),
249
+ pid: 12345,
250
+ };
251
+
252
+ expect(worker.status).toBe("busy");
253
+ expect(worker.currentTask as string).toBe("task-1");
254
+ expect(worker.pid).toBe(12345);
255
+ });
256
+
257
+ test("creates stopped worker", () => {
258
+ const worker: Worker = {
259
+ workerId: createWorkerId("worker-3"),
260
+ status: "stopped",
261
+ currentTask: null,
262
+ };
263
+
264
+ expect(worker.status).toBe("stopped");
265
+ });
266
+ });
267
+
268
+ describe("RunState", () => {
269
+ test("creates valid run state", () => {
270
+ const now = new Date().toISOString();
271
+ const runState: RunState = {
272
+ runId: createRunId("run-123"),
273
+ parentBranch: "main",
274
+ totalTasks: 10,
275
+ pending: 5,
276
+ running: 3,
277
+ completed: 2,
278
+ failed: 0,
279
+ startTime: now,
280
+ };
281
+
282
+ expect(runState.runId as string).toBe("run-123");
283
+ expect(runState.parentBranch).toBe("main");
284
+ expect(runState.totalTasks).toBe(10);
285
+ expect(runState.pending + runState.running + runState.completed + runState.failed).toBe(10);
286
+ expect(runState.startTime).toBe(now);
287
+ expect(runState.endTime).toBeUndefined();
288
+ });
289
+
290
+ test("creates completed run state with end time", () => {
291
+ const start = new Date().toISOString();
292
+ const end = new Date(Date.now() + 3600000).toISOString();
293
+ const runState: RunState = {
294
+ runId: createRunId("run-124"),
295
+ parentBranch: "feat/new-feature",
296
+ totalTasks: 5,
297
+ pending: 0,
298
+ running: 0,
299
+ completed: 4,
300
+ failed: 1,
301
+ startTime: start,
302
+ endTime: end,
303
+ };
304
+
305
+ expect(runState.completed).toBe(4);
306
+ expect(runState.failed).toBe(1);
307
+ expect(runState.endTime).toBe(end);
308
+ });
309
+ });
310
+
311
+ describe("TaskPlan", () => {
312
+ test("creates valid task plan", () => {
313
+ const taskPlan: TaskPlan = {
314
+ runId: createRunId("run-125"),
315
+ parentBranch: "main",
316
+ title: "Add authentication",
317
+ description: "Implement JWT-based authentication",
318
+ tasks: [
319
+ {
320
+ taskId: createTaskId("task-1"),
321
+ title: "Setup JWT library",
322
+ description: "Install and configure JWT library",
323
+ filesToModify: ["package.json", "src/auth/jwt.ts"],
324
+ dependsOn: [],
325
+ priority: 1,
326
+ status: "pending",
327
+ retryCount: 0,
328
+ },
329
+ {
330
+ taskId: createTaskId("task-2"),
331
+ title: "Add login endpoint",
332
+ description: "Create POST /auth/login endpoint",
333
+ filesToModify: ["src/routes/auth.ts"],
334
+ dependsOn: [createTaskId("task-1")],
335
+ priority: 2,
336
+ status: "pending",
337
+ retryCount: 0,
338
+ },
339
+ ],
340
+ };
341
+
342
+ expect(taskPlan.runId as string).toBe("run-125");
343
+ expect(taskPlan.title).toBe("Add authentication");
344
+ expect(taskPlan.tasks).toHaveLength(2);
345
+ expect(taskPlan.tasks[0]?.taskId as string).toBe("task-1");
346
+ expect(taskPlan.tasks[1]?.dependsOn).toHaveLength(1);
347
+ });
348
+
349
+ test("creates minimal task plan without title/description", () => {
350
+ const taskPlan: TaskPlan = {
351
+ runId: createRunId("run-126"),
352
+ parentBranch: "develop",
353
+ tasks: [],
354
+ };
355
+
356
+ expect(taskPlan.tasks).toHaveLength(0);
357
+ expect(taskPlan.title).toBeUndefined();
358
+ expect(taskPlan.description).toBeUndefined();
359
+ });
360
+ });
@@ -0,0 +1,133 @@
1
+ import { z } from "zod";
2
+ import { ConfigError } from "./errors";
3
+
4
+ const configSchema = z.object({
5
+ workers: z.object({
6
+ num: z.number().int().min(1),
7
+ max: z.number().int().min(1),
8
+ }),
9
+ models: z.object({
10
+ default: z.string().optional(),
11
+ splitter: z.string().optional(),
12
+ tester: z.string().optional(),
13
+ implementer: z.string().optional(),
14
+ reviewer: z.string().optional(),
15
+ mergeResolver: z.string().optional(),
16
+ branchName: z.string().optional(),
17
+ }),
18
+ timeouts: z.object({
19
+ claude: z.number().int().min(1),
20
+ test: z.number().int().min(1),
21
+ staleTask: z.number().int().min(1),
22
+ }),
23
+ retry: z.object({
24
+ maxRetries: z.number().int().min(0),
25
+ }),
26
+ debug: z.boolean(),
27
+ adaptiveEffort: z.boolean(),
28
+ teams: z.object({
29
+ splitter: z.boolean(),
30
+ reviewer: z.boolean(),
31
+ }),
32
+ memorySync: z.boolean(),
33
+ auth: z.object({
34
+ preferOAuth: z.boolean(),
35
+ }).optional(),
36
+ dashboard: z.object({
37
+ enabled: z.boolean(),
38
+ port: z.number().int().min(1).max(65535),
39
+ host: z.string(),
40
+ }),
41
+ repos: z.array(z.object({
42
+ name: z.string().optional(),
43
+ path: z.string(),
44
+ ref: z.string().optional(),
45
+ })).optional(),
46
+ multiRepoStrategy: z.enum(["independent", "coordinated"]).optional(),
47
+ plugins: z.array(z.string()).optional(),
48
+ });
49
+
50
+ export type Config = z.infer<typeof configSchema>;
51
+
52
+ function parseIntOrDefault(value: string | undefined, defaultValue: number): number {
53
+ if (!value) return defaultValue;
54
+ const parsed = parseInt(value, 10);
55
+ if (isNaN(parsed)) {
56
+ throw new ConfigError(`Invalid number: ${value}`);
57
+ }
58
+ return parsed;
59
+ }
60
+
61
+ function parseBoolOrDefault(value: string | undefined, defaultValue: boolean): boolean {
62
+ if (!value) return defaultValue;
63
+ return value === "1" || value.toLowerCase() === "true";
64
+ }
65
+
66
+ export function loadConfig(env: Record<string, string | undefined> = process.env): Config {
67
+ const numWorkers = parseIntOrDefault(env.AAD_NUM_WORKERS, 2);
68
+ const maxWorkers = parseIntOrDefault(env.AAD_MAX_WORKERS, 8);
69
+
70
+ if (numWorkers > maxWorkers) {
71
+ throw new ConfigError(
72
+ `AAD_NUM_WORKERS (${numWorkers}) exceeds AAD_MAX_WORKERS (${maxWorkers})`,
73
+ { numWorkers, maxWorkers }
74
+ );
75
+ }
76
+
77
+ const rawConfig = {
78
+ workers: {
79
+ num: numWorkers,
80
+ max: maxWorkers,
81
+ },
82
+ models: {
83
+ default: env.AAD_MODEL,
84
+ splitter: env.AAD_MODEL_SPLITTER,
85
+ tester: env.AAD_MODEL_TESTER,
86
+ implementer: env.AAD_MODEL_IMPLEMENTER,
87
+ reviewer: env.AAD_MODEL_REVIEWER,
88
+ mergeResolver: env.AAD_MODEL_MERGE,
89
+ branchName: env.AAD_MODEL_BRANCH_NAME,
90
+ },
91
+ timeouts: {
92
+ claude: parseIntOrDefault(env.CLAUDE_TIMEOUT, 1200),
93
+ test: parseIntOrDefault(env.TEST_TIMEOUT, 600),
94
+ staleTask: parseIntOrDefault(env.AAD_STALE_TASK_TIMEOUT, 5400),
95
+ },
96
+ retry: {
97
+ maxRetries: parseIntOrDefault(env.MAX_PHASE_RETRIES, 2),
98
+ },
99
+ debug: parseBoolOrDefault(env.DEBUG, false),
100
+ adaptiveEffort: parseBoolOrDefault(env.AAD_ADAPTIVE_EFFORT, false),
101
+ teams: {
102
+ splitter: parseBoolOrDefault(env.AAD_USE_TEAMS_FOR_SPLITTER, false),
103
+ reviewer: parseBoolOrDefault(env.AAD_USE_TEAMS_FOR_REVIEW, false),
104
+ },
105
+ memorySync: parseBoolOrDefault(env.AAD_MEMORY_SYNC_ENABLED, true),
106
+ auth: {
107
+ preferOAuth: parseBoolOrDefault(env.AAD_AUTH_PREFER_OAUTH, false),
108
+ },
109
+ dashboard: {
110
+ enabled: parseBoolOrDefault(env.AAD_DASHBOARD_ENABLED, true),
111
+ port: parseIntOrDefault(env.AAD_DASHBOARD_PORT, 7333),
112
+ host: env.AAD_DASHBOARD_HOST ?? "localhost",
113
+ },
114
+ repos: env.AAD_REPOS
115
+ ? env.AAD_REPOS.split(",").map((p) => ({ path: p.trim() }))
116
+ : undefined,
117
+ multiRepoStrategy: env.AAD_MULTI_REPO_STRATEGY as "independent" | "coordinated" | undefined,
118
+ plugins: env.AAD_PLUGINS
119
+ ? env.AAD_PLUGINS.split(",").map((p) => p.trim()).filter(Boolean)
120
+ : undefined,
121
+ };
122
+
123
+ try {
124
+ return configSchema.parse(rawConfig);
125
+ } catch (error) {
126
+ if (error instanceof z.ZodError) {
127
+ throw new ConfigError(`Invalid configuration: ${error.message}`, {
128
+ errors: error.errors,
129
+ });
130
+ }
131
+ throw error;
132
+ }
133
+ }
@@ -0,0 +1,128 @@
1
+ export class AadError extends Error {
2
+ public readonly code: string;
3
+ public readonly context: Record<string, unknown>;
4
+
5
+ constructor(
6
+ code: string,
7
+ message: string,
8
+ context: Record<string, unknown> = {}
9
+ ) {
10
+ super(message);
11
+ this.code = code;
12
+ this.context = context;
13
+ this.name = "AadError";
14
+ Error.captureStackTrace(this, this.constructor);
15
+ }
16
+ }
17
+
18
+ export class ConfigError extends AadError {
19
+ constructor(message: string, context: Record<string, unknown> = {}) {
20
+ super("CONFIG_ERROR", message, context);
21
+ this.name = "ConfigError";
22
+ }
23
+ }
24
+
25
+ export class ClaudeProviderError extends AadError {
26
+ constructor(message: string, context: Record<string, unknown> = {}) {
27
+ super("CLAUDE_PROVIDER_ERROR", message, context);
28
+ this.name = "ClaudeProviderError";
29
+ }
30
+ }
31
+
32
+ export class TimeoutError extends AadError {
33
+ constructor(message: string, context: Record<string, unknown> = {}) {
34
+ super("TIMEOUT_ERROR", message, context);
35
+ this.name = "TimeoutError";
36
+ }
37
+ }
38
+
39
+ export class ValidationError extends AadError {
40
+ constructor(message: string, context: Record<string, unknown> = {}) {
41
+ super("VALIDATION_ERROR", message, context);
42
+ this.name = "ValidationError";
43
+ }
44
+ }
45
+
46
+ export class DashboardError extends AadError {
47
+ constructor(message: string, context: Record<string, unknown> = {}) {
48
+ super("DASHBOARD_ERROR", message, context);
49
+ this.name = "DashboardError";
50
+ }
51
+ }
52
+
53
+ export class PersistenceError extends AadError {
54
+ constructor(message: string, context: Record<string, unknown> = {}) {
55
+ super("PERSISTENCE_ERROR", message, context);
56
+ this.name = "PersistenceError";
57
+ }
58
+ }
59
+
60
+ export class FileLockError extends AadError {
61
+ constructor(message: string, context: Record<string, unknown> = {}) {
62
+ super("FILE_LOCK_ERROR", message, context);
63
+ this.name = "FileLockError";
64
+ }
65
+ }
66
+
67
+ export class TaskQueueError extends AadError {
68
+ constructor(message: string, context: Record<string, unknown> = {}) {
69
+ super("TASK_QUEUE_ERROR", message, context);
70
+ this.name = "TaskQueueError";
71
+ }
72
+ }
73
+
74
+ export class CircularDependencyError extends AadError {
75
+ constructor(message: string, context: Record<string, unknown> = {}) {
76
+ super("CIRCULAR_DEPENDENCY_ERROR", message, context);
77
+ this.name = "CircularDependencyError";
78
+ }
79
+ }
80
+
81
+ export class GitWorkspaceError extends AadError {
82
+ constructor(message: string, context: Record<string, unknown> = {}) {
83
+ super("GIT_WORKSPACE_ERROR", message, context);
84
+ this.name = "GitWorkspaceError";
85
+ }
86
+ }
87
+
88
+ export class ProcessManagerError extends AadError {
89
+ constructor(message: string, context: Record<string, unknown> = {}) {
90
+ super("PROCESS_MANAGER_ERROR", message, context);
91
+ this.name = "ProcessManagerError";
92
+ }
93
+ }
94
+
95
+ export class PlanningError extends AadError {
96
+ constructor(message: string, context: Record<string, unknown> = {}) {
97
+ super("PLANNING_ERROR", message, context);
98
+ this.name = "PlanningError";
99
+ }
100
+ }
101
+
102
+ export class TaskExecutionError extends AadError {
103
+ constructor(message: string, context: Record<string, unknown> = {}) {
104
+ super("TASK_EXECUTION_ERROR", message, context);
105
+ this.name = "TaskExecutionError";
106
+ }
107
+ }
108
+
109
+ export class PhaseError extends AadError {
110
+ constructor(message: string, context: Record<string, unknown> = {}) {
111
+ super("PHASE_ERROR", message, context);
112
+ this.name = "PhaseError";
113
+ }
114
+ }
115
+
116
+ export class TestRunnerError extends AadError {
117
+ constructor(message: string, context: Record<string, unknown> = {}) {
118
+ super("TEST_RUNNER_ERROR", message, context);
119
+ this.name = "TestRunnerError";
120
+ }
121
+ }
122
+
123
+ export class MergeConflictError extends AadError {
124
+ constructor(message: string, context: Record<string, unknown> = {}) {
125
+ super("MERGE_CONFLICT_ERROR", message, context);
126
+ this.name = "MergeConflictError";
127
+ }
128
+ }