@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,286 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ runImplementerGreen,
4
+ buildGreenPhasePrompt,
5
+ } from "../phases/implementer-green";
6
+ import type { Task, WorkspaceInfo } from "@aad/shared/types";
7
+ import type { ClaudeProvider, ClaudeRequest, ClaudeResponse } from "@aad/claude-provider";
8
+ import { createTaskId } from "@aad/shared/types";
9
+
10
+ describe("buildGreenPhasePrompt", () => {
11
+ test("builds Green phase prompt with task context", () => {
12
+ const task: Task = {
13
+ taskId: createTaskId("task-001"),
14
+ title: "Implement login endpoint",
15
+ description: "Create POST /login with JWT response",
16
+ filesToModify: ["src/routes/auth.ts", "src/services/auth.ts"],
17
+ dependsOn: [],
18
+ priority: 1,
19
+ status: "running",
20
+ retryCount: 0,
21
+ };
22
+
23
+ const workspace: WorkspaceInfo = {
24
+ path: "/workspace",
25
+ language: "typescript",
26
+ packageManager: "bun",
27
+ framework: "hono",
28
+ testFramework: "bun-test",
29
+ };
30
+
31
+ const prompt = buildGreenPhasePrompt(task, workspace);
32
+
33
+ expect(prompt).toContain("TDD Green フェーズ");
34
+ expect(prompt).toContain("task-001");
35
+ expect(prompt).toContain("Implement login endpoint");
36
+ expect(prompt).toContain("POST /login");
37
+ expect(prompt).toContain("最小限の実装");
38
+ expect(prompt).toContain("テストをパスする");
39
+ });
40
+
41
+ test("includes language-specific coding conventions", () => {
42
+ const task: Task = {
43
+ taskId: createTaskId("task-002"),
44
+ title: "Test",
45
+ description: "Test",
46
+ filesToModify: [],
47
+ dependsOn: [],
48
+ priority: 1,
49
+ status: "running",
50
+ retryCount: 0,
51
+ };
52
+
53
+ const workspaceGo: WorkspaceInfo = {
54
+ path: "/go-project",
55
+ language: "go",
56
+ packageManager: "go-modules",
57
+ framework: "gin",
58
+ testFramework: "go-test",
59
+ };
60
+
61
+ const promptGo = buildGreenPhasePrompt(task, workspaceGo);
62
+ expect(promptGo).toContain("effective Go");
63
+
64
+ const workspacePython: WorkspaceInfo = {
65
+ path: "/py-project",
66
+ language: "python",
67
+ packageManager: "pip",
68
+ framework: "flask",
69
+ testFramework: "pytest",
70
+ };
71
+
72
+ const promptPython = buildGreenPhasePrompt(task, workspacePython);
73
+ expect(promptPython).toContain("PEP 8");
74
+
75
+ const workspaceRust: WorkspaceInfo = {
76
+ path: "/rust-project",
77
+ language: "rust",
78
+ packageManager: "cargo",
79
+ framework: "axum",
80
+ testFramework: "cargo",
81
+ };
82
+
83
+ const promptRust = buildGreenPhasePrompt(task, workspaceRust);
84
+ expect(promptRust).toContain("Rust API guidelines");
85
+
86
+ const workspaceJava: WorkspaceInfo = {
87
+ path: "/java-project",
88
+ language: "java",
89
+ packageManager: "maven",
90
+ framework: "spring",
91
+ testFramework: "maven",
92
+ };
93
+ const promptJava = buildGreenPhasePrompt(task, workspaceJava);
94
+ expect(promptJava).toContain("Google Java Style Guide");
95
+
96
+ const workspaceCSharp: WorkspaceInfo = {
97
+ path: "/csharp-project",
98
+ language: "c#",
99
+ packageManager: "dotnet",
100
+ framework: "aspnet",
101
+ testFramework: "jest",
102
+ };
103
+ const promptCSharp = buildGreenPhasePrompt(task, workspaceCSharp);
104
+ expect(promptCSharp).toContain("C# Coding Conventions");
105
+
106
+ const workspaceUnknown: WorkspaceInfo = {
107
+ path: "/unknown-project",
108
+ language: "elixir",
109
+ packageManager: "mix",
110
+ framework: "phoenix",
111
+ testFramework: "unknown",
112
+ };
113
+ const promptUnknown = buildGreenPhasePrompt(task, workspaceUnknown);
114
+ expect(promptUnknown).toContain("コーディング規約");
115
+ });
116
+ });
117
+
118
+ describe("runImplementerGreen", () => {
119
+ test("calls Claude provider with Green phase prompt", async () => {
120
+ const task: Task = {
121
+ taskId: createTaskId("task-003"),
122
+ title: "Add user model",
123
+ description: "Create User struct with validation",
124
+ filesToModify: ["src/models/user.ts"],
125
+ dependsOn: [],
126
+ priority: 1,
127
+ status: "running",
128
+ retryCount: 0,
129
+ };
130
+
131
+ const workspace: WorkspaceInfo = {
132
+ path: "/workspace",
133
+ language: "typescript",
134
+ packageManager: "bun",
135
+ framework: "hono",
136
+ testFramework: "bun-test",
137
+ };
138
+
139
+ const mockProvider: ClaudeProvider = {
140
+ async call(request: ClaudeRequest): Promise<ClaudeResponse> {
141
+ expect(request.prompt).toContain("TDD Green");
142
+ expect(request.allowedTools).toEqual([
143
+ "Read",
144
+ "Write",
145
+ "Edit",
146
+ "Glob",
147
+ "Grep",
148
+ "Bash",
149
+ ]);
150
+ expect(request.permissionMode).toBe("bypassPermissions");
151
+ expect(request.cwd).toBe("/workspace");
152
+
153
+ return {
154
+ result: "実装を完了しました",
155
+ exitCode: 0,
156
+ model: "claude-sonnet-4-5",
157
+ effortLevel: "medium",
158
+ duration: 1500,
159
+ };
160
+ },
161
+ };
162
+
163
+ const result = await runImplementerGreen(task, workspace, mockProvider);
164
+
165
+ expect(result.success).toBe(true);
166
+ expect(result.output).toContain("実装を完了しました");
167
+ });
168
+
169
+ test("handles implementation failure", async () => {
170
+ const task: Task = {
171
+ taskId: createTaskId("task-004"),
172
+ title: "Test task",
173
+ description: "Test description",
174
+ filesToModify: [],
175
+ dependsOn: [],
176
+ priority: 1,
177
+ status: "running",
178
+ retryCount: 0,
179
+ };
180
+
181
+ const workspace: WorkspaceInfo = {
182
+ path: "/workspace",
183
+ language: "typescript",
184
+ packageManager: "bun",
185
+ framework: "hono",
186
+ testFramework: "bun-test",
187
+ };
188
+
189
+ const mockProvider: ClaudeProvider = {
190
+ async call(): Promise<ClaudeResponse> {
191
+ return {
192
+ result: "実装に失敗しました",
193
+ exitCode: 1,
194
+ model: "claude-sonnet-4-5",
195
+ effortLevel: "medium",
196
+ duration: 800,
197
+ };
198
+ },
199
+ };
200
+
201
+ const result = await runImplementerGreen(task, workspace, mockProvider);
202
+
203
+ expect(result.success).toBe(false);
204
+ expect(result.output).toContain("実装に失敗しました");
205
+ });
206
+
207
+ test("passes custom effort level and model", async () => {
208
+ const task: Task = {
209
+ taskId: createTaskId("task-005"),
210
+ title: "Complex implementation",
211
+ description: "Complex task",
212
+ filesToModify: [],
213
+ dependsOn: [],
214
+ priority: 1,
215
+ status: "running",
216
+ retryCount: 0,
217
+ };
218
+
219
+ const workspace: WorkspaceInfo = {
220
+ path: "/workspace",
221
+ language: "typescript",
222
+ packageManager: "bun",
223
+ framework: "hono",
224
+ testFramework: "bun-test",
225
+ };
226
+
227
+ const mockProvider: ClaudeProvider = {
228
+ async call(request: ClaudeRequest): Promise<ClaudeResponse> {
229
+ expect(request.effortLevel).toBe("high");
230
+ expect(request.model).toBe("claude-opus-4-6");
231
+
232
+ return {
233
+ result: "OK",
234
+ exitCode: 0,
235
+ model: "claude-opus-4-6",
236
+ effortLevel: "high",
237
+ duration: 2500,
238
+ };
239
+ },
240
+ };
241
+
242
+ await runImplementerGreen(task, workspace, mockProvider, {
243
+ effortLevel: "high",
244
+ model: "claude-opus-4-6",
245
+ });
246
+ });
247
+
248
+ test("respects timeout configuration", async () => {
249
+ const task: Task = {
250
+ taskId: createTaskId("task-006"),
251
+ title: "Test task",
252
+ description: "Test description",
253
+ filesToModify: [],
254
+ dependsOn: [],
255
+ priority: 1,
256
+ status: "running",
257
+ retryCount: 0,
258
+ };
259
+
260
+ const workspace: WorkspaceInfo = {
261
+ path: "/workspace",
262
+ language: "typescript",
263
+ packageManager: "bun",
264
+ framework: "hono",
265
+ testFramework: "bun-test",
266
+ };
267
+
268
+ const mockProvider: ClaudeProvider = {
269
+ async call(request: ClaudeRequest): Promise<ClaudeResponse> {
270
+ expect(request.timeout).toBe(90000);
271
+
272
+ return {
273
+ result: "OK",
274
+ exitCode: 0,
275
+ model: "claude-sonnet-4-5",
276
+ effortLevel: "medium",
277
+ duration: 1000,
278
+ };
279
+ },
280
+ };
281
+
282
+ await runImplementerGreen(task, workspace, mockProvider, {
283
+ timeout: 90000,
284
+ });
285
+ });
286
+ });
@@ -0,0 +1,368 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ runMergePhase,
4
+ buildConflictResolutionPrompt,
5
+ } from "../phases/merge";
6
+ import type { Task, WorkspaceInfo } from "@aad/shared/types";
7
+ import type { ClaudeProvider, ClaudeRequest, ClaudeResponse } from "@aad/claude-provider";
8
+ import type { MergeService, MergeResult } from "@aad/git-workspace";
9
+ import { createTaskId } from "@aad/shared/types";
10
+
11
+ describe("buildConflictResolutionPrompt", () => {
12
+ test("builds conflict resolution prompt with file list", () => {
13
+ const task: Task = {
14
+ taskId: createTaskId("task-001"),
15
+ title: "Add auth",
16
+ description: "JWT auth",
17
+ filesToModify: ["src/auth.ts"],
18
+ dependsOn: [],
19
+ priority: 1,
20
+ status: "running",
21
+ retryCount: 0,
22
+ };
23
+
24
+ const conflicts = ["src/auth.ts", "src/middleware.ts"];
25
+
26
+ const prompt = buildConflictResolutionPrompt(task, conflicts);
27
+
28
+ expect(prompt).toContain("merge-resolverエージェント");
29
+ expect(prompt).toContain("マージ競合を解決");
30
+ expect(prompt).toContain("task-001");
31
+ expect(prompt).toContain("src/auth.ts");
32
+ expect(prompt).toContain("src/middleware.ts");
33
+ expect(prompt).toContain("git add");
34
+ });
35
+ });
36
+
37
+ describe("runMergePhase", () => {
38
+ test("merges successfully without conflicts", async () => {
39
+ const task: Task = {
40
+ taskId: createTaskId("task-002"),
41
+ title: "Add feature",
42
+ description: "New feature",
43
+ filesToModify: ["src/feature.ts"],
44
+ dependsOn: [],
45
+ priority: 1,
46
+ status: "running",
47
+ retryCount: 0,
48
+ };
49
+
50
+ const workspace: WorkspaceInfo = {
51
+ path: "/workspace",
52
+ language: "typescript",
53
+ packageManager: "bun",
54
+ framework: "hono",
55
+ testFramework: "bun-test",
56
+ };
57
+
58
+ const taskBranch = "task/002-add-feature";
59
+ const parentBranch = "main";
60
+ const parentWorktree = "/parent/worktree";
61
+
62
+ const mockMergeService = {
63
+ async mergeToParent(): Promise<MergeResult> {
64
+ return {
65
+ success: true,
66
+ message: "Successfully merged",
67
+ };
68
+ },
69
+ } as unknown as MergeService;
70
+
71
+ const mockProvider: ClaudeProvider = {
72
+ async call(): Promise<ClaudeResponse> {
73
+ throw new Error("Should not be called when merge succeeds");
74
+ },
75
+ };
76
+
77
+ const result = await runMergePhase(
78
+ task,
79
+ workspace,
80
+ taskBranch,
81
+ parentBranch,
82
+ parentWorktree,
83
+ mockMergeService,
84
+ mockProvider
85
+ );
86
+
87
+ expect(result.success).toBe(true);
88
+ expect(result.output).toContain("Successfully merged");
89
+ expect(result.hadConflict).toBe(false);
90
+ });
91
+
92
+ test("handles merge conflict and resolves with Claude", async () => {
93
+ const task: Task = {
94
+ taskId: createTaskId("task-003"),
95
+ title: "Conflicting change",
96
+ description: "Will conflict",
97
+ filesToModify: ["src/conflict.ts"],
98
+ dependsOn: [],
99
+ priority: 1,
100
+ status: "running",
101
+ retryCount: 0,
102
+ };
103
+
104
+ const workspace: WorkspaceInfo = {
105
+ path: "/workspace",
106
+ language: "typescript",
107
+ packageManager: "bun",
108
+ framework: "hono",
109
+ testFramework: "bun-test",
110
+ };
111
+
112
+ const taskBranch = "task/003-conflicting-change";
113
+ const parentBranch = "main";
114
+ const parentWorktree = "/parent/worktree";
115
+
116
+ const mockMergeService = {
117
+ async mergeToParent(): Promise<MergeResult> {
118
+ return {
119
+ success: false,
120
+ conflicts: ["src/conflict.ts", "src/utils.ts"],
121
+ message: "Merge conflict detected",
122
+ };
123
+ },
124
+ } as unknown as MergeService;
125
+
126
+ const mockProvider: ClaudeProvider = {
127
+ async call(request: ClaudeRequest): Promise<ClaudeResponse> {
128
+ expect(request.prompt).toContain("merge-resolver");
129
+ expect(request.prompt).toContain("src/conflict.ts");
130
+ expect(request.prompt).toContain("src/utils.ts");
131
+ expect(request.cwd).toBe("/parent/worktree");
132
+ expect(request.effortLevel).toBe("low");
133
+ expect(request.model).toContain("haiku");
134
+
135
+ return {
136
+ result: "競合を解決しました",
137
+ exitCode: 0,
138
+ model: "claude-haiku-4-5",
139
+ effortLevel: "low",
140
+ duration: 500,
141
+ };
142
+ },
143
+ };
144
+
145
+ const result = await runMergePhase(
146
+ task,
147
+ workspace,
148
+ taskBranch,
149
+ parentBranch,
150
+ parentWorktree,
151
+ mockMergeService,
152
+ mockProvider
153
+ );
154
+
155
+ expect(result.success).toBe(true);
156
+ expect(result.hadConflict).toBe(true);
157
+ expect(result.output).toContain("競合を解決しました");
158
+ });
159
+
160
+ test("fails when conflict resolution fails", async () => {
161
+ const task: Task = {
162
+ taskId: createTaskId("task-004"),
163
+ title: "Failed resolution",
164
+ description: "Will fail to resolve",
165
+ filesToModify: ["src/bad.ts"],
166
+ dependsOn: [],
167
+ priority: 1,
168
+ status: "running",
169
+ retryCount: 0,
170
+ };
171
+
172
+ const workspace: WorkspaceInfo = {
173
+ path: "/workspace",
174
+ language: "typescript",
175
+ packageManager: "bun",
176
+ framework: "hono",
177
+ testFramework: "bun-test",
178
+ };
179
+
180
+ const taskBranch = "task/004-failed-resolution";
181
+ const parentBranch = "main";
182
+ const parentWorktree = "/parent/worktree";
183
+
184
+ const mockMergeService = {
185
+ async mergeToParent(): Promise<MergeResult> {
186
+ return {
187
+ success: false,
188
+ conflicts: ["src/bad.ts"],
189
+ message: "Conflict",
190
+ };
191
+ },
192
+ } as unknown as MergeService;
193
+
194
+ const mockProvider: ClaudeProvider = {
195
+ async call(): Promise<ClaudeResponse> {
196
+ return {
197
+ result: "解決失敗",
198
+ exitCode: 1,
199
+ model: "claude-haiku-4-5",
200
+ effortLevel: "low",
201
+ duration: 300,
202
+ };
203
+ },
204
+ };
205
+
206
+ const result = await runMergePhase(
207
+ task,
208
+ workspace,
209
+ taskBranch,
210
+ parentBranch,
211
+ parentWorktree,
212
+ mockMergeService,
213
+ mockProvider
214
+ );
215
+
216
+ expect(result.success).toBe(false);
217
+ expect(result.hadConflict).toBe(true);
218
+ expect(result.output).toContain("解決失敗");
219
+ });
220
+
221
+ test("uses custom model for conflict resolution", async () => {
222
+ const task: Task = {
223
+ taskId: createTaskId("task-005"),
224
+ title: "Custom model",
225
+ description: "Test",
226
+ filesToModify: [],
227
+ dependsOn: [],
228
+ priority: 1,
229
+ status: "running",
230
+ retryCount: 0,
231
+ };
232
+
233
+ const workspace: WorkspaceInfo = {
234
+ path: "/workspace",
235
+ language: "typescript",
236
+ packageManager: "bun",
237
+ framework: "hono",
238
+ testFramework: "bun-test",
239
+ };
240
+
241
+ const taskBranch = "task/005-custom-model";
242
+ const parentBranch = "main";
243
+ const parentWorktree = "/parent/worktree";
244
+
245
+ const mockMergeService = {
246
+ async mergeToParent(): Promise<MergeResult> {
247
+ return {
248
+ success: false,
249
+ conflicts: ["src/file.ts"],
250
+ };
251
+ },
252
+ } as unknown as MergeService;
253
+
254
+ const mockProvider: ClaudeProvider = {
255
+ async call(request: ClaudeRequest): Promise<ClaudeResponse> {
256
+ expect(request.model).toBe("claude-sonnet-4-5");
257
+
258
+ return {
259
+ result: "OK",
260
+ exitCode: 0,
261
+ model: "claude-sonnet-4-5",
262
+ effortLevel: "low",
263
+ duration: 500,
264
+ };
265
+ },
266
+ };
267
+
268
+ await runMergePhase(
269
+ task,
270
+ workspace,
271
+ taskBranch,
272
+ parentBranch,
273
+ parentWorktree,
274
+ mockMergeService,
275
+ mockProvider,
276
+ {
277
+ mergeResolverModel: "claude-sonnet-4-5",
278
+ }
279
+ );
280
+ });
281
+
282
+ test("fails with no conflicts (unexpected merge error)", async () => {
283
+ const task: Task = {
284
+ taskId: createTaskId("task-006"),
285
+ title: "No conflict failure",
286
+ description: "Merge fails without conflicts",
287
+ filesToModify: [],
288
+ dependsOn: [],
289
+ priority: 1,
290
+ status: "running",
291
+ retryCount: 0,
292
+ };
293
+
294
+ const workspace: WorkspaceInfo = {
295
+ path: "/workspace",
296
+ language: "typescript",
297
+ packageManager: "bun",
298
+ framework: "hono",
299
+ testFramework: "bun-test",
300
+ };
301
+
302
+ const mockMergeService = {
303
+ async mergeToParent(): Promise<MergeResult> {
304
+ return {
305
+ success: false,
306
+ conflicts: [],
307
+ message: "Unknown merge error",
308
+ };
309
+ },
310
+ } as unknown as MergeService;
311
+
312
+ const mockProvider: ClaudeProvider = {
313
+ async call(): Promise<ClaudeResponse> {
314
+ throw new Error("Should not be called");
315
+ },
316
+ };
317
+
318
+ const result = await runMergePhase(
319
+ task, workspace, "branch", "main", "/parent",
320
+ mockMergeService, mockProvider
321
+ );
322
+
323
+ expect(result.success).toBe(false);
324
+ expect(result.hadConflict).toBe(false);
325
+ expect(result.output).toContain("Unknown merge error");
326
+ });
327
+
328
+ test("fails with no conflicts and no message", async () => {
329
+ const task: Task = {
330
+ taskId: createTaskId("task-007"),
331
+ title: "No message failure",
332
+ description: "Merge fails without message",
333
+ filesToModify: [],
334
+ dependsOn: [],
335
+ priority: 1,
336
+ status: "running",
337
+ retryCount: 0,
338
+ };
339
+
340
+ const workspace: WorkspaceInfo = {
341
+ path: "/workspace",
342
+ language: "typescript",
343
+ packageManager: "bun",
344
+ framework: "hono",
345
+ testFramework: "bun-test",
346
+ };
347
+
348
+ const mockMergeService = {
349
+ async mergeToParent(): Promise<MergeResult> {
350
+ return { success: false };
351
+ },
352
+ } as unknown as MergeService;
353
+
354
+ const mockProvider: ClaudeProvider = {
355
+ async call(): Promise<ClaudeResponse> {
356
+ throw new Error("Should not be called");
357
+ },
358
+ };
359
+
360
+ const result = await runMergePhase(
361
+ task, workspace, "branch", "main", "/parent",
362
+ mockMergeService, mockProvider
363
+ );
364
+
365
+ expect(result.success).toBe(false);
366
+ expect(result.hadConflict).toBe(false);
367
+ });
368
+ });