@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,313 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ buildTestCommand,
4
+ runTests,
5
+ type ProcessSpawner,
6
+ } from "../phases/tester-verify";
7
+ import type { WorkspaceInfo } from "@aad/shared/types";
8
+
9
+ describe("buildTestCommand", () => {
10
+ test("builds bun test command", () => {
11
+ const workspace: WorkspaceInfo = {
12
+ path: "/path/to/workspace",
13
+ language: "typescript",
14
+ packageManager: "bun",
15
+ framework: "hono",
16
+ testFramework: "bun-test",
17
+ };
18
+
19
+ expect(buildTestCommand(workspace)).toEqual(["bun", "test"]);
20
+ });
21
+
22
+ test("builds vitest command", () => {
23
+ const workspace: WorkspaceInfo = {
24
+ path: "/path/to/workspace",
25
+ language: "typescript",
26
+ packageManager: "npm",
27
+ framework: "vite",
28
+ testFramework: "vitest",
29
+ };
30
+
31
+ expect(buildTestCommand(workspace)).toEqual(["npm", "run", "test"]);
32
+ });
33
+
34
+ test("builds jest command", () => {
35
+ const workspace: WorkspaceInfo = {
36
+ path: "/path/to/workspace",
37
+ language: "javascript",
38
+ packageManager: "yarn",
39
+ framework: "react",
40
+ testFramework: "jest",
41
+ };
42
+
43
+ expect(buildTestCommand(workspace)).toEqual(["yarn", "test"]);
44
+ });
45
+
46
+ test("builds pytest command", () => {
47
+ const workspace: WorkspaceInfo = {
48
+ path: "/path/to/workspace",
49
+ language: "python",
50
+ packageManager: "pip",
51
+ framework: "flask",
52
+ testFramework: "pytest",
53
+ };
54
+
55
+ expect(buildTestCommand(workspace)).toEqual(["pytest", "-v"]);
56
+ });
57
+
58
+ test("builds go test command", () => {
59
+ const workspace: WorkspaceInfo = {
60
+ path: "/path/to/workspace",
61
+ language: "go",
62
+ packageManager: "go-modules",
63
+ framework: "gin",
64
+ testFramework: "go-test",
65
+ };
66
+
67
+ expect(buildTestCommand(workspace)).toEqual(["go", "test", "./..."]);
68
+ });
69
+
70
+ test("builds cargo test command", () => {
71
+ const workspace: WorkspaceInfo = {
72
+ path: "/path/to/workspace",
73
+ language: "rust",
74
+ packageManager: "cargo",
75
+ framework: "axum",
76
+ testFramework: "cargo",
77
+ };
78
+
79
+ expect(buildTestCommand(workspace)).toEqual(["cargo", "test"]);
80
+ });
81
+
82
+ test("builds vitest command with yarn", () => {
83
+ const workspace: WorkspaceInfo = {
84
+ path: "/path",
85
+ language: "typescript",
86
+ packageManager: "yarn",
87
+ framework: "vite",
88
+ testFramework: "vitest",
89
+ };
90
+ expect(buildTestCommand(workspace)).toEqual(["yarn", "test"]);
91
+ });
92
+
93
+ test("builds vitest command with pnpm", () => {
94
+ const workspace: WorkspaceInfo = {
95
+ path: "/path",
96
+ language: "typescript",
97
+ packageManager: "pnpm",
98
+ framework: "vite",
99
+ testFramework: "vitest",
100
+ };
101
+ expect(buildTestCommand(workspace)).toEqual(["pnpm", "test"]);
102
+ });
103
+
104
+ test("builds vitest command with default (npx)", () => {
105
+ const workspace: WorkspaceInfo = {
106
+ path: "/path",
107
+ language: "typescript",
108
+ packageManager: "bun",
109
+ framework: "vite",
110
+ testFramework: "vitest",
111
+ };
112
+ expect(buildTestCommand(workspace)).toEqual(["npx", "vitest", "run"]);
113
+ });
114
+
115
+ test("builds jest command with npm", () => {
116
+ const workspace: WorkspaceInfo = {
117
+ path: "/path",
118
+ language: "javascript",
119
+ packageManager: "npm",
120
+ framework: "react",
121
+ testFramework: "jest",
122
+ };
123
+ expect(buildTestCommand(workspace)).toEqual(["npm", "test"]);
124
+ });
125
+
126
+ test("builds jest command with pnpm", () => {
127
+ const workspace: WorkspaceInfo = {
128
+ path: "/path",
129
+ language: "javascript",
130
+ packageManager: "pnpm",
131
+ framework: "react",
132
+ testFramework: "jest",
133
+ };
134
+ expect(buildTestCommand(workspace)).toEqual(["pnpm", "test"]);
135
+ });
136
+
137
+ test("builds jest command with default (npx)", () => {
138
+ const workspace: WorkspaceInfo = {
139
+ path: "/path",
140
+ language: "javascript",
141
+ packageManager: "bun",
142
+ framework: "react",
143
+ testFramework: "jest",
144
+ };
145
+ expect(buildTestCommand(workspace)).toEqual(["npx", "jest"]);
146
+ });
147
+
148
+ test("builds mocha command with npm", () => {
149
+ const workspace: WorkspaceInfo = {
150
+ path: "/path",
151
+ language: "javascript",
152
+ packageManager: "npm",
153
+ framework: "express",
154
+ testFramework: "mocha",
155
+ };
156
+ expect(buildTestCommand(workspace)).toEqual(["npm", "test"]);
157
+ });
158
+
159
+ test("builds mocha command with yarn", () => {
160
+ const workspace: WorkspaceInfo = {
161
+ path: "/path",
162
+ language: "javascript",
163
+ packageManager: "yarn",
164
+ framework: "express",
165
+ testFramework: "mocha",
166
+ };
167
+ expect(buildTestCommand(workspace)).toEqual(["yarn", "test"]);
168
+ });
169
+
170
+ test("builds mocha command with default (npx)", () => {
171
+ const workspace: WorkspaceInfo = {
172
+ path: "/path",
173
+ language: "javascript",
174
+ packageManager: "pnpm",
175
+ framework: "express",
176
+ testFramework: "mocha",
177
+ };
178
+ expect(buildTestCommand(workspace)).toEqual(["npx", "mocha"]);
179
+ });
180
+
181
+ test("builds maven test command", () => {
182
+ const workspace: WorkspaceInfo = {
183
+ path: "/path",
184
+ language: "java",
185
+ packageManager: "maven",
186
+ framework: "spring",
187
+ testFramework: "maven",
188
+ };
189
+ expect(buildTestCommand(workspace)).toEqual(["mvn", "test"]);
190
+ });
191
+
192
+ test("builds gradle test command", () => {
193
+ const workspace: WorkspaceInfo = {
194
+ path: "/path",
195
+ language: "java",
196
+ packageManager: "gradle",
197
+ framework: "spring",
198
+ testFramework: "gradle",
199
+ };
200
+ expect(buildTestCommand(workspace)).toEqual(["./gradlew", "test"]);
201
+ });
202
+
203
+ test("throws error for unknown test framework", () => {
204
+ const workspace: WorkspaceInfo = {
205
+ path: "/path/to/workspace",
206
+ language: "unknown",
207
+ packageManager: "unknown",
208
+ framework: "unknown",
209
+ testFramework: "unknown",
210
+ };
211
+
212
+ expect(() => buildTestCommand(workspace)).toThrow("Unsupported test framework");
213
+ });
214
+ });
215
+
216
+ describe("runTests", () => {
217
+ test("runs tests successfully with exit code 0", async () => {
218
+ const workspace: WorkspaceInfo = {
219
+ path: "/workspace",
220
+ language: "typescript",
221
+ packageManager: "bun",
222
+ framework: "hono",
223
+ testFramework: "bun-test",
224
+ };
225
+
226
+ const mockSpawner: ProcessSpawner = {
227
+ async spawn(cmd, args, opts) {
228
+ expect(cmd).toBe("bun");
229
+ expect(args).toEqual(["test"]);
230
+ expect(opts.cwd).toBe("/workspace");
231
+ return {
232
+ exitCode: 0,
233
+ stdout: "All tests passed",
234
+ stderr: "",
235
+ };
236
+ },
237
+ };
238
+
239
+ const result = await runTests(workspace, mockSpawner);
240
+
241
+ expect(result.success).toBe(true);
242
+ expect(result.output).toContain("All tests passed");
243
+ });
244
+
245
+ test("runs tests with failure (exit code 1)", async () => {
246
+ const workspace: WorkspaceInfo = {
247
+ path: "/workspace",
248
+ language: "python",
249
+ packageManager: "pip",
250
+ framework: "flask",
251
+ testFramework: "pytest",
252
+ };
253
+
254
+ const mockSpawner: ProcessSpawner = {
255
+ async spawn(cmd, args, _opts) {
256
+ expect(cmd).toBe("pytest");
257
+ expect(args).toEqual(["-v"]);
258
+ return {
259
+ exitCode: 1,
260
+ stdout: "FAILED test_foo.py::test_bar",
261
+ stderr: "AssertionError",
262
+ };
263
+ },
264
+ };
265
+
266
+ const result = await runTests(workspace, mockSpawner);
267
+
268
+ expect(result.success).toBe(false);
269
+ expect(result.output).toContain("FAILED");
270
+ expect(result.error).toContain("AssertionError");
271
+ });
272
+
273
+ test("respects timeout configuration", async () => {
274
+ const workspace: WorkspaceInfo = {
275
+ path: "/workspace",
276
+ language: "typescript",
277
+ packageManager: "bun",
278
+ framework: "hono",
279
+ testFramework: "bun-test",
280
+ };
281
+
282
+ const mockSpawner: ProcessSpawner = {
283
+ async spawn(_cmd, _args, opts) {
284
+ expect(opts.timeout).toBe(60000);
285
+ return {
286
+ exitCode: 0,
287
+ stdout: "OK",
288
+ stderr: "",
289
+ };
290
+ },
291
+ };
292
+
293
+ await runTests(workspace, mockSpawner, 60000);
294
+ });
295
+
296
+ test("handles process spawn errors", async () => {
297
+ const workspace: WorkspaceInfo = {
298
+ path: "/workspace",
299
+ language: "go",
300
+ packageManager: "go-modules",
301
+ framework: "gin",
302
+ testFramework: "go-test",
303
+ };
304
+
305
+ const mockSpawner: ProcessSpawner = {
306
+ async spawn() {
307
+ throw new Error("Command not found: go");
308
+ },
309
+ };
310
+
311
+ await expect(runTests(workspace, mockSpawner)).rejects.toThrow("Command not found: go");
312
+ });
313
+ });
@@ -0,0 +1,303 @@
1
+ import type {
2
+ Task,
3
+ WorkspaceInfo,
4
+ RunId,
5
+ TaskExecutionResult,
6
+ } from "@aad/shared/types";
7
+ import type { Config } from "@aad/shared/config";
8
+ import type { ClaudeProvider } from "@aad/claude-provider";
9
+ import type { MergeService } from "@aad/git-workspace";
10
+ import type { EventBus } from "@aad/shared/events";
11
+ import type { ProcessSpawner } from "./phases/tester-verify";
12
+
13
+ import { estimateTaskComplexity, getAdaptiveEffortLevel } from "@aad/claude-provider";
14
+ import { runTesterRed } from "./phases/tester-red";
15
+ import { runImplementerGreen } from "./phases/implementer-green";
16
+ import { runTests } from "./phases/tester-verify";
17
+ import { runReviewer } from "./phases/reviewer";
18
+ import { runMergePhase } from "./phases/merge";
19
+ import { PhaseError } from "@aad/shared/errors";
20
+
21
+ /**
22
+ * Execute full TDD pipeline for a task
23
+ * Phases: Red → Green → Verify → Review → Merge
24
+ */
25
+ export async function executeTddPipeline(
26
+ task: Task,
27
+ workspace: WorkspaceInfo,
28
+ taskBranch: string,
29
+ parentBranch: string,
30
+ parentWorktree: string,
31
+ runId: RunId,
32
+ config: Config,
33
+ provider: ClaudeProvider,
34
+ mergeService: MergeService,
35
+ eventBus: EventBus,
36
+ testSpawner?: ProcessSpawner
37
+ ): Promise<TaskExecutionResult> {
38
+ const startTime = Date.now();
39
+
40
+ try {
41
+ // Estimate task complexity for adaptive effort
42
+ const complexity = estimateTaskComplexity(task);
43
+ const testerEffort = getAdaptiveEffortLevel("tester", complexity, config);
44
+ const implementerEffort = getAdaptiveEffortLevel("implementer", complexity, config);
45
+ const reviewerEffort = getAdaptiveEffortLevel("reviewer", complexity, config);
46
+
47
+ eventBus.emit({
48
+ type: "log:entry",
49
+ entry: {
50
+ level: "info",
51
+ service: "task-execution",
52
+ message: `Task complexity estimated`,
53
+ timestamp: Date.now(),
54
+ taskId: task.taskId as string,
55
+ complexity,
56
+ adaptiveEffort: config.adaptiveEffort,
57
+ fileCount: task.filesToModify.length,
58
+ dependencyCount: task.dependsOn.length,
59
+ descriptionLength: task.description.length,
60
+ effortLevels: { tester: testerEffort, implementer: implementerEffort, reviewer: reviewerEffort },
61
+ },
62
+ });
63
+
64
+ // ===== Phase 1: Red - Create failing tests =====
65
+ eventBus.emit({
66
+ type: "execution:phase:started",
67
+ taskId: task.taskId,
68
+ phase: "red",
69
+ });
70
+
71
+ const redStart = Date.now();
72
+ const redResult = await runTesterRed(task, workspace, provider, {
73
+ effortLevel: testerEffort,
74
+ model: config.models.tester,
75
+ timeout: config.timeouts.claude * 1000,
76
+ });
77
+
78
+ if (!redResult.success) {
79
+ eventBus.emit({
80
+ type: "execution:phase:failed",
81
+ taskId: task.taskId,
82
+ phase: "red",
83
+ error: redResult.output,
84
+ });
85
+
86
+ throw new PhaseError("Red phase failed: failed to create tests", {
87
+ taskId: task.taskId,
88
+ phase: "red",
89
+ output: redResult.output,
90
+ });
91
+ }
92
+
93
+ eventBus.emit({
94
+ type: "execution:phase:completed",
95
+ taskId: task.taskId,
96
+ phase: "red",
97
+ duration: Date.now() - redStart,
98
+ });
99
+
100
+ // ===== Phase 2: Green - Implement minimal code =====
101
+ eventBus.emit({
102
+ type: "execution:phase:started",
103
+ taskId: task.taskId,
104
+ phase: "green",
105
+ });
106
+
107
+ const greenStart = Date.now();
108
+ const greenResult = await runImplementerGreen(task, workspace, provider, {
109
+ effortLevel: implementerEffort,
110
+ model: config.models.implementer,
111
+ timeout: config.timeouts.claude * 1000,
112
+ });
113
+
114
+ if (!greenResult.success) {
115
+ eventBus.emit({
116
+ type: "execution:phase:failed",
117
+ taskId: task.taskId,
118
+ phase: "green",
119
+ error: greenResult.output,
120
+ });
121
+
122
+ throw new PhaseError("Green phase failed: implementation error", {
123
+ taskId: task.taskId,
124
+ phase: "green",
125
+ output: greenResult.output,
126
+ });
127
+ }
128
+
129
+ eventBus.emit({
130
+ type: "execution:phase:completed",
131
+ taskId: task.taskId,
132
+ phase: "green",
133
+ duration: Date.now() - greenStart,
134
+ });
135
+
136
+ // ===== Commit generated code =====
137
+ // Commit changes after Green phase so they can be merged later
138
+ try {
139
+ await Bun.$`git -C ${workspace.path} add -A`.quiet();
140
+ await Bun.$`git -C ${workspace.path} commit -m ${"feat: Implement " + task.title}`.quiet();
141
+ } catch (_error) {
142
+ // If commit fails (e.g., no changes), log but don't fail the pipeline
143
+ // This can happen if Claude didn't generate any new files
144
+ }
145
+
146
+ // ===== Phase 3: Verify - Run tests =====
147
+ eventBus.emit({
148
+ type: "execution:phase:started",
149
+ taskId: task.taskId,
150
+ phase: "verify",
151
+ });
152
+
153
+ const verifyStart = Date.now();
154
+ const verifyResult = await runTests(
155
+ workspace,
156
+ testSpawner,
157
+ config.timeouts.test * 1000
158
+ );
159
+
160
+ if (!verifyResult.success) {
161
+ eventBus.emit({
162
+ type: "execution:phase:failed",
163
+ taskId: task.taskId,
164
+ phase: "verify",
165
+ error: verifyResult.error ?? "Tests failed",
166
+ });
167
+
168
+ throw new PhaseError("Verify phase failed: tests did not pass", {
169
+ taskId: task.taskId,
170
+ phase: "verify",
171
+ output: verifyResult.output,
172
+ error: verifyResult.error,
173
+ });
174
+ }
175
+
176
+ eventBus.emit({
177
+ type: "execution:phase:completed",
178
+ taskId: task.taskId,
179
+ phase: "verify",
180
+ duration: Date.now() - verifyStart,
181
+ });
182
+
183
+ // ===== Phase 4: Review - Code review (non-fatal) =====
184
+ eventBus.emit({
185
+ type: "execution:phase:started",
186
+ taskId: task.taskId,
187
+ phase: "review",
188
+ });
189
+
190
+ const reviewStart = Date.now();
191
+ const reviewResult = await runReviewer(
192
+ task,
193
+ workspace,
194
+ runId,
195
+ config,
196
+ provider,
197
+ {
198
+ model: config.models.reviewer,
199
+ timeout: config.timeouts.claude * 1000,
200
+ }
201
+ );
202
+
203
+ if (!reviewResult.success) {
204
+ // Review failure is non-fatal; log and continue
205
+ eventBus.emit({
206
+ type: "execution:phase:failed",
207
+ taskId: task.taskId,
208
+ phase: "review",
209
+ error: reviewResult.output,
210
+ });
211
+ } else {
212
+ eventBus.emit({
213
+ type: "execution:phase:completed",
214
+ taskId: task.taskId,
215
+ phase: "review",
216
+ duration: Date.now() - reviewStart,
217
+ });
218
+ }
219
+
220
+ // ===== Phase 5: Merge - Merge to parent branch =====
221
+ eventBus.emit({
222
+ type: "execution:phase:started",
223
+ taskId: task.taskId,
224
+ phase: "merge",
225
+ });
226
+
227
+ const mergeStart = Date.now();
228
+ const mergeResult = await runMergePhase(
229
+ task,
230
+ workspace,
231
+ taskBranch,
232
+ parentBranch,
233
+ parentWorktree,
234
+ mergeService,
235
+ provider,
236
+ {
237
+ mergeResolverModel: config.models.mergeResolver,
238
+ timeout: config.timeouts.claude * 1000,
239
+ }
240
+ );
241
+
242
+ if (!mergeResult.success) {
243
+ eventBus.emit({
244
+ type: "execution:phase:failed",
245
+ taskId: task.taskId,
246
+ phase: "merge",
247
+ error: mergeResult.output,
248
+ });
249
+
250
+ throw new PhaseError("Merge phase failed", {
251
+ taskId: task.taskId,
252
+ phase: "merge",
253
+ output: mergeResult.output,
254
+ hadConflict: mergeResult.hadConflict,
255
+ });
256
+ }
257
+
258
+ if (mergeResult.hadConflict) {
259
+ // Emit conflict event for monitoring
260
+ eventBus.emit({
261
+ type: "execution:merge:conflict",
262
+ taskId: task.taskId,
263
+ conflictedFiles: [], // MergeService would provide this
264
+ });
265
+ }
266
+
267
+ eventBus.emit({
268
+ type: "execution:phase:completed",
269
+ taskId: task.taskId,
270
+ phase: "merge",
271
+ duration: Date.now() - mergeStart,
272
+ });
273
+
274
+ // ===== Pipeline completed successfully =====
275
+ const duration = Date.now() - startTime;
276
+
277
+ return {
278
+ taskId: task.taskId,
279
+ status: "completed",
280
+ duration,
281
+ output: "TDD pipeline completed successfully",
282
+ };
283
+ } catch (error) {
284
+ const duration = Date.now() - startTime;
285
+
286
+ if (error instanceof PhaseError) {
287
+ return {
288
+ taskId: task.taskId,
289
+ status: "failed",
290
+ duration,
291
+ error: error.message,
292
+ output: JSON.stringify(error.context),
293
+ };
294
+ }
295
+
296
+ return {
297
+ taskId: task.taskId,
298
+ status: "failed",
299
+ duration,
300
+ error: error instanceof Error ? error.message : "Unknown error",
301
+ };
302
+ }
303
+ }
@@ -0,0 +1,45 @@
1
+ // Main executor
2
+ export { executeTddPipeline } from "./executor";
3
+
4
+ // Phase functions
5
+ export { runTesterRed, buildRedPhasePrompt } from "./phases/tester-red";
6
+ export type { PhaseResult as TesterRedResult, TesterRedOptions } from "./phases/tester-red";
7
+
8
+ export {
9
+ runImplementerGreen,
10
+ buildGreenPhasePrompt,
11
+ } from "./phases/implementer-green";
12
+ export type {
13
+ PhaseResult as ImplementerGreenResult,
14
+ ImplementerGreenOptions,
15
+ } from "./phases/implementer-green";
16
+
17
+ export { runTests, buildTestCommand } from "./phases/tester-verify";
18
+ export type {
19
+ TestResult,
20
+ ProcessSpawner,
21
+ ProcessResult,
22
+ } from "./phases/tester-verify";
23
+
24
+ export {
25
+ runReviewer,
26
+ buildReviewPrompt,
27
+ buildTeamsReviewPrompt,
28
+ } from "./phases/reviewer";
29
+ export type {
30
+ PhaseResult as ReviewerResult,
31
+ ReviewerOptions,
32
+ ReviewConfig,
33
+ } from "./phases/reviewer";
34
+
35
+ export {
36
+ runMergePhase,
37
+ buildConflictResolutionPrompt,
38
+ } from "./phases/merge";
39
+ export type {
40
+ MergePhaseResult,
41
+ MergePhaseOptions,
42
+ } from "./phases/merge";
43
+
44
+ // Default spawner
45
+ export { createDefaultSpawner } from "./phases/default-spawner";
@@ -0,0 +1,49 @@
1
+ import type { ProcessSpawner, ProcessResult } from "./tester-verify";
2
+ import { TimeoutError } from "@aad/shared/errors";
3
+
4
+ /**
5
+ * Create default ProcessSpawner using Bun.spawn
6
+ */
7
+ export function createDefaultSpawner(): ProcessSpawner {
8
+ return {
9
+ async spawn(cmd, args, opts): Promise<ProcessResult> {
10
+ const proc = Bun.spawn([cmd, ...args], {
11
+ cwd: opts.cwd,
12
+ stdout: "pipe",
13
+ stderr: "pipe",
14
+ });
15
+
16
+ const timeout = opts.timeout ?? 300000;
17
+ const timer = setTimeout(() => {
18
+ proc.kill();
19
+ }, timeout);
20
+
21
+ try {
22
+ const [stdout, stderr, exitCode] = await Promise.all([
23
+ new Response(proc.stdout).text(),
24
+ new Response(proc.stderr).text(),
25
+ proc.exited,
26
+ ]);
27
+
28
+ clearTimeout(timer);
29
+
30
+ return {
31
+ exitCode,
32
+ stdout,
33
+ stderr,
34
+ };
35
+ } catch (error) {
36
+ clearTimeout(timer);
37
+
38
+ if (error instanceof Error && error.message.includes("killed")) {
39
+ throw new TimeoutError(
40
+ `Process timed out after ${timeout}ms`,
41
+ { cmd, args, timeout }
42
+ );
43
+ }
44
+
45
+ throw error;
46
+ }
47
+ },
48
+ };
49
+ }