@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,144 @@
1
+ /**
2
+ * Status Command Tests
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, mock } from "bun:test";
6
+ import type { App } from "../app";
7
+ import { displayStatus } from "../commands/status";
8
+ import { createRunId, createTaskId, type Task, type RunState } from "../../../shared/types";
9
+
10
+ describe("displayStatus", () => {
11
+ let mockApp: App;
12
+
13
+ beforeEach(() => {
14
+ const mockRunState: RunState = {
15
+ runId: createRunId("test-run"),
16
+ parentBranch: "main",
17
+ totalTasks: 3,
18
+ pending: 1,
19
+ running: 1,
20
+ completed: 1,
21
+ failed: 0,
22
+ startTime: "2024-01-01T00:00:00Z",
23
+ };
24
+
25
+ const mockTasks: Task[] = [
26
+ {
27
+ taskId: createTaskId("task-1"),
28
+ title: "Task 1",
29
+ description: "First task",
30
+ filesToModify: [],
31
+ dependsOn: [],
32
+ priority: 1,
33
+ status: "completed",
34
+ retryCount: 0,
35
+ },
36
+ {
37
+ taskId: createTaskId("task-2"),
38
+ title: "Task 2",
39
+ description: "Second task",
40
+ filesToModify: [],
41
+ dependsOn: [],
42
+ priority: 2,
43
+ status: "running",
44
+ retryCount: 0,
45
+ },
46
+ {
47
+ taskId: createTaskId("task-3"),
48
+ title: "Task 3",
49
+ description: "Third task",
50
+ filesToModify: [],
51
+ dependsOn: [],
52
+ priority: 3,
53
+ status: "pending",
54
+ retryCount: 0,
55
+ },
56
+ ];
57
+
58
+ mockApp = {
59
+ config: {} as any,
60
+ eventBus: {} as any,
61
+ logger: {
62
+ info: mock(() => {}),
63
+ error: mock(() => {}),
64
+ } as any,
65
+ stores: {
66
+ runStore: {
67
+ get: mock(async () => mockRunState),
68
+ getLatest: mock(async () => null),
69
+ } as any,
70
+ taskStore: {
71
+ getAll: mock(async () => mockTasks),
72
+ } as any,
73
+ workerStore: {} as any,
74
+ },
75
+ dispatcher: {} as any,
76
+ processManager: {} as any,
77
+ planningService: {} as any,
78
+ providerRegistry: {} as any,
79
+ worktreeManager: {} as any,
80
+ branchManager: {} as any,
81
+ mergeService: {} as any,
82
+ pluginManager: { runHook: mock(async (_p: string, d: unknown) => d), deactivateAll: mock(async () => {}), register: mock(async () => {}), loadFromConfig: mock(async () => {}), addHook: mock(() => {}), list: mock(() => []) } as any,
83
+ shutdown: mock(async () => {}),
84
+ };
85
+ });
86
+
87
+ test("displays status for valid run", async () => {
88
+ await displayStatus(mockApp, "test-run");
89
+
90
+ expect(mockApp.stores.runStore.get).toHaveBeenCalledWith(createRunId("test-run"));
91
+ expect(mockApp.stores.taskStore.getAll).toHaveBeenCalled();
92
+ });
93
+
94
+ test("throws error for non-existent run", async () => {
95
+ mockApp.stores.runStore.get = mock(async () => null);
96
+
97
+ await expect(displayStatus(mockApp, "non-existent")).rejects.toThrow(
98
+ "Run not found: non-existent"
99
+ );
100
+ });
101
+
102
+ test("throws error when no run ID provided and no runs exist", async () => {
103
+ await expect(displayStatus(mockApp)).rejects.toThrow(
104
+ "No runs found. Please provide a run ID or create a new run with 'aad run'."
105
+ );
106
+ });
107
+
108
+ test("handles empty task list", async () => {
109
+ mockApp.stores.taskStore.getAll = mock(async () => []);
110
+
111
+ await displayStatus(mockApp, "test-run");
112
+ // Should complete without throwing
113
+ expect(true).toBe(true);
114
+ });
115
+
116
+ test("auto-detects latest run when no runId provided", async () => {
117
+ const latestRunState: RunState = {
118
+ runId: createRunId("latest-run"),
119
+ parentBranch: "main",
120
+ totalTasks: 2,
121
+ pending: 0,
122
+ running: 0,
123
+ completed: 2,
124
+ failed: 0,
125
+ startTime: "2024-01-02T00:00:00Z",
126
+ };
127
+
128
+ mockApp.stores.runStore.getLatest = mock(async () => latestRunState);
129
+ mockApp.stores.runStore.get = mock(async () => latestRunState);
130
+
131
+ await displayStatus(mockApp);
132
+
133
+ expect(mockApp.stores.runStore.getLatest).toHaveBeenCalled();
134
+ expect(mockApp.stores.runStore.get).toHaveBeenCalledWith(createRunId("latest-run"));
135
+ });
136
+
137
+ test("throws error when no runs exist and no runId provided", async () => {
138
+ mockApp.stores.runStore.getLatest = mock(async () => null);
139
+
140
+ await expect(displayStatus(mockApp)).rejects.toThrow(
141
+ "No runs found. Please provide a run ID or create a new run with 'aad run'."
142
+ );
143
+ });
144
+ });
@@ -0,0 +1,241 @@
1
+ /**
2
+ * CLI App - Composition Root
3
+ * DI配線とライフサイクル管理
4
+ */
5
+
6
+ import type { Logger } from "pino";
7
+ import type { Config } from "../../shared/config";
8
+ import type { EventBus } from "../../shared/events";
9
+ import type { Stores } from "../persistence";
10
+ import type { Dispatcher } from "../task-queue";
11
+ import type { ProcessManager } from "../process-manager";
12
+ import type { PlanningService } from "../planning";
13
+ import type { ProviderRegistry } from "../claude-provider";
14
+ import type { WorktreeManager, BranchManager, MergeService } from "../git-workspace";
15
+ import type { DashboardServer } from "../dashboard";
16
+ import type { PluginManager } from "../plugin";
17
+
18
+ import { loadConfig } from "../../shared/config";
19
+ import { EventBus as EventBusImpl } from "../../shared/events";
20
+ import { createLogger, LogStore, createSSETransport } from "../logging";
21
+ import { createProviderRegistry, type ProviderType } from "../claude-provider";
22
+ import { createStores } from "../persistence";
23
+ import { Dispatcher as DispatcherImpl } from "../task-queue";
24
+ import { ProcessManager as ProcessManagerImpl } from "../process-manager";
25
+ import { PlanningService as PlanningServiceImpl } from "../planning";
26
+ import { WorktreeManager as WorktreeManagerImpl, BranchManager as BranchManagerImpl, MergeService as MergeServiceImpl } from "../git-workspace";
27
+ import { DashboardServer as DashboardServerImpl } from "../dashboard";
28
+ import { PluginManager as PluginManagerImpl } from "../plugin";
29
+
30
+ export interface AppOptions {
31
+ workers?: number;
32
+ persist?: "memory" | "fs";
33
+ debug?: boolean;
34
+ dashboard?: boolean;
35
+ providerDefault?: ProviderType;
36
+ providerOverrides?: Partial<Record<string, ProviderType>>;
37
+ }
38
+
39
+ export interface App {
40
+ config: Config;
41
+ eventBus: EventBus;
42
+ logger: Logger;
43
+ stores: Stores;
44
+ dispatcher: Dispatcher;
45
+ processManager: ProcessManager;
46
+ planningService: PlanningService;
47
+ providerRegistry: ProviderRegistry;
48
+ worktreeManager: WorktreeManager;
49
+ branchManager: BranchManager;
50
+ mergeService: MergeService;
51
+ dashboardServer?: DashboardServer;
52
+ pluginManager: PluginManager;
53
+ shutdown(): Promise<void>;
54
+ }
55
+
56
+ /**
57
+ * アプリケーションのComposition Root
58
+ * 全モジュールのDI配線とライフサイクル管理
59
+ */
60
+ export function createApp(options: AppOptions = {}): App {
61
+ // 1. 設定読み込み (CLI optionsでenv overrides)
62
+ const envOverrides: Record<string, string> = {};
63
+ if (options.workers !== undefined) {
64
+ envOverrides.AAD_NUM_WORKERS = String(options.workers);
65
+ // Also override max workers to avoid validation error
66
+ envOverrides.AAD_MAX_WORKERS = String(Math.max(options.workers, 8));
67
+ }
68
+ if (options.debug !== undefined) {
69
+ envOverrides.DEBUG = options.debug ? "1" : "0";
70
+ }
71
+ if (options.dashboard !== undefined) {
72
+ envOverrides.AAD_DASHBOARD_ENABLED = options.dashboard ? "1" : "0";
73
+ }
74
+
75
+ const config = loadConfig({ ...process.env, ...envOverrides });
76
+
77
+ // 2. EventBus初期化
78
+ const eventBus = new EventBusImpl();
79
+
80
+ // 3. Logger初期化
81
+ const logger = createLogger({
82
+ service: "aad-cli",
83
+ debug: config.debug,
84
+ });
85
+ logger.debug({ config, options }, "AAD CLI starting");
86
+
87
+ // 4. LogStore + SSE Transport初期化
88
+ const logStore = new LogStore();
89
+ const sseTransport = createSSETransport({ logStore, eventBus });
90
+ void sseTransport;
91
+
92
+ // 5. ProviderRegistry初期化
93
+ const providerRegistry = createProviderRegistry(
94
+ {
95
+ default: options.providerDefault ?? "cli",
96
+ overrides: options.providerOverrides,
97
+ },
98
+ config,
99
+ logger.child({ service: "claude-provider" })
100
+ );
101
+ logger.debug({ default: options.providerDefault ?? "cli" }, "ProviderRegistry initialized");
102
+
103
+ // 6. Persistence stores初期化
104
+ const persistMode = options.persist ?? "memory";
105
+ const stores = createStores(persistMode, {
106
+ basePath: persistMode === "fs" ? `${process.cwd()}/.aad/data` : undefined,
107
+ logger: logger.child({ service: "persistence" }),
108
+ });
109
+ logger.debug({ mode: persistMode }, "Persistence stores initialized");
110
+
111
+ // 7. ProcessManager初期化
112
+ const processManager = new ProcessManagerImpl({
113
+ eventBus,
114
+ config,
115
+ logger: logger.child({ service: "process-manager" }),
116
+ });
117
+ logger.debug("ProcessManager initialized");
118
+
119
+ // 8. Dispatcher初期化
120
+ const dispatcher = new DispatcherImpl({
121
+ taskStore: stores.taskStore,
122
+ workerStore: stores.workerStore,
123
+ runStore: stores.runStore,
124
+ eventBus,
125
+ config: {
126
+ maxRetries: config.retry.maxRetries,
127
+ staleTaskCheckInterval: 60000,
128
+ staleTaskThreshold: config.timeouts.staleTask * 1000, // Convert seconds to milliseconds
129
+ },
130
+ logger: logger.child({ service: "task-queue" }),
131
+ });
132
+ logger.debug("Dispatcher initialized");
133
+
134
+ // 9. PlanningService初期化
135
+ const planningService = new PlanningServiceImpl(
136
+ providerRegistry.getProvider("splitter"),
137
+ eventBus,
138
+ config,
139
+ logger.child({ service: "planning" })
140
+ );
141
+ logger.debug("PlanningService initialized");
142
+
143
+ // 10. Git Workspace Services初期化
144
+ const repoRoot = process.cwd();
145
+ const worktreeBase = `${repoRoot}/.aad/worktrees`;
146
+
147
+ const worktreeManager = new WorktreeManagerImpl({
148
+ repoRoot,
149
+ worktreeBase,
150
+ logger: logger.child({ service: "git-workspace" }),
151
+ });
152
+ const branchManager = new BranchManagerImpl({
153
+ repoRoot,
154
+ logger: logger.child({ service: "git-workspace" }),
155
+ });
156
+ const mergeService = new MergeServiceImpl({
157
+ repoRoot,
158
+ logger: logger.child({ service: "git-workspace" }),
159
+ });
160
+ logger.debug({ repoRoot, worktreeBase }, "Git Workspace services initialized");
161
+
162
+ // 11. Dashboard Server初期化 (optional)
163
+ let dashboardServer: DashboardServer | undefined;
164
+ if (config.dashboard.enabled) {
165
+ // Dashboard uses TaskStore and ProcessManager directly
166
+ dashboardServer = new DashboardServerImpl({
167
+ eventBus,
168
+ logStore,
169
+ taskStore: stores.taskStore,
170
+ processManager: processManager,
171
+ port: config.dashboard.port,
172
+ host: config.dashboard.host,
173
+ logger,
174
+ });
175
+
176
+ dashboardServer.start();
177
+ logger.info(
178
+ { port: config.dashboard.port, host: config.dashboard.host },
179
+ "Dashboard server started"
180
+ );
181
+ }
182
+
183
+ // 12. PluginManager初期化
184
+ const pluginManager = new PluginManagerImpl({
185
+ config,
186
+ eventBus,
187
+ logger: logger.child({ service: "plugin" }),
188
+ });
189
+
190
+ // Load plugins from config if present
191
+ if (config.plugins && config.plugins.length > 0) {
192
+ void pluginManager.loadFromConfig(config.plugins);
193
+ }
194
+
195
+ // 13. Shutdown handler
196
+ const shutdown = async (): Promise<void> => {
197
+ logger.debug("Shutting down AAD...");
198
+
199
+ try {
200
+ // Plugin deactivation
201
+ await pluginManager.deactivateAll();
202
+ logger.debug("Plugins deactivated");
203
+
204
+ // Dashboard停止
205
+ if (dashboardServer) {
206
+ dashboardServer.stop();
207
+ logger.debug("Dashboard server stopped");
208
+ }
209
+
210
+ // ProcessManager停止
211
+ await processManager.stopAll();
212
+ logger.debug("ProcessManager stopped");
213
+
214
+ // Dispatcher停止
215
+ dispatcher.stop();
216
+ logger.debug("Dispatcher stopped");
217
+
218
+ logger.debug("AAD shutdown complete");
219
+ } catch (error) {
220
+ logger.error({ error }, "Error during shutdown");
221
+ throw error;
222
+ }
223
+ };
224
+
225
+ return {
226
+ config,
227
+ eventBus,
228
+ logger,
229
+ stores,
230
+ dispatcher,
231
+ processManager,
232
+ planningService,
233
+ providerRegistry,
234
+ worktreeManager,
235
+ branchManager,
236
+ mergeService,
237
+ dashboardServer,
238
+ pluginManager,
239
+ shutdown,
240
+ };
241
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Cleanup Command
3
+ * Remove worktrees and orphaned branches
4
+ */
5
+
6
+ import { Command } from "commander";
7
+ import type { App } from "../app";
8
+ import { createRunId } from "../../../shared/types";
9
+
10
+ export interface CleanupCommandOptions {
11
+ force?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Create cleanup command
16
+ */
17
+ export function createCleanupCommand(getApp: () => App): Command {
18
+ const command = new Command("cleanup");
19
+
20
+ command
21
+ .description("Remove worktrees and orphaned branches")
22
+ .argument("[run-id]", "Run ID to cleanup (defaults to all AAD worktrees)")
23
+ .option("-f, --force", "Force remove worktrees and branches", false)
24
+ .action(async (runIdArg?: string, options?: CleanupCommandOptions) => {
25
+ let app: App | null = null;
26
+
27
+ try {
28
+ app = getApp();
29
+ await cleanupWorktrees(app, runIdArg, options?.force ?? false);
30
+ await app.shutdown();
31
+ process.exit(0);
32
+ } catch (error) {
33
+ if (error instanceof Error) {
34
+ console.error(`Error: ${error.message}`);
35
+ } else {
36
+ console.error("Unknown error:", error);
37
+ }
38
+
39
+ if (app) {
40
+ try {
41
+ await app.shutdown();
42
+ } catch {
43
+ // Ignore shutdown errors
44
+ }
45
+ }
46
+
47
+ process.exit(1);
48
+ }
49
+ });
50
+
51
+ return command;
52
+ }
53
+
54
+ /**
55
+ * Cleanup worktrees and branches
56
+ */
57
+ export async function cleanupWorktrees(
58
+ app: App,
59
+ runIdArg?: string,
60
+ force = false
61
+ ): Promise<void> {
62
+ const { worktreeManager, branchManager, logger } = app;
63
+
64
+ const runId = runIdArg ? createRunId(runIdArg) : undefined;
65
+
66
+ logger.info({ runId, force }, "Starting cleanup");
67
+
68
+ // 1. List all worktrees
69
+ const worktrees = await worktreeManager.listWorktrees();
70
+ logger.debug({ count: worktrees.length }, "Found worktrees");
71
+
72
+ const worktreeBase = `${process.cwd()}/.aad/worktrees`;
73
+ const aadWorktrees = worktrees.filter((wt) => wt.path.startsWith(worktreeBase));
74
+
75
+ if (aadWorktrees.length === 0) {
76
+ console.log("No AAD worktrees found.");
77
+ } else {
78
+ console.log(`\nFound ${aadWorktrees.length} AAD worktree(s):`);
79
+
80
+ // 2. Remove each worktree
81
+ let removed = 0;
82
+ for (const worktree of aadWorktrees) {
83
+ try {
84
+ // Filter by runId if specified
85
+ if (runId && !worktree.path.includes(runId)) {
86
+ continue;
87
+ }
88
+
89
+ console.log(` Removing: ${worktree.path}`);
90
+ await worktreeManager.removeWorktree(worktree.path, force);
91
+ removed++;
92
+ } catch (error) {
93
+ const errorMsg = error instanceof Error ? error.message : String(error);
94
+ console.error(` Failed to remove ${worktree.path}: ${errorMsg}`);
95
+ }
96
+ }
97
+
98
+ console.log(`\nRemoved ${removed} worktree(s).`);
99
+ }
100
+
101
+ // 3. Prune orphaned worktrees
102
+ await worktreeManager.pruneWorktrees();
103
+ logger.info("Pruned orphaned worktrees");
104
+
105
+ // 4. Cleanup orphaned branches
106
+ const deletedBranches = runId
107
+ ? await branchManager.cleanupOrphanBranches(runId)
108
+ : await branchManager.cleanupOrphanBranches();
109
+
110
+ if (deletedBranches.length > 0) {
111
+ console.log(`\nDeleted ${deletedBranches.length} orphan branch(es):`);
112
+ for (const branch of deletedBranches) {
113
+ console.log(` - ${branch}`);
114
+ }
115
+ } else {
116
+ console.log("\nNo orphan branches found.");
117
+ }
118
+
119
+ console.log("\nCleanup complete.");
120
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Resume Command
3
+ * 中断されたrunを再開
4
+ */
5
+
6
+ import { Command } from "commander";
7
+ import type { App } from "../app";
8
+ import { createRunId } from "../../../shared/types";
9
+
10
+ export function createResumeCommand(getApp: () => App): Command {
11
+ const command = new Command("resume")
12
+ .description("Resume interrupted run")
13
+ .argument("<runId>", "Run ID to resume")
14
+ .action(async (runIdStr: string) => {
15
+ try {
16
+ const app = getApp();
17
+ await resumeRun(app, runIdStr);
18
+ } catch (error) {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ console.error("Resume failed:", message);
21
+ process.exit(1);
22
+ }
23
+ });
24
+
25
+ return command;
26
+ }
27
+
28
+ /**
29
+ * Run再開
30
+ */
31
+ export async function resumeRun(app: App, runIdStr: string): Promise<void> {
32
+ const { logger, stores, dispatcher, processManager } = app;
33
+ const runId = createRunId(runIdStr);
34
+
35
+ logger.info({ runId }, "Resuming run");
36
+ console.log(`\n🔄 Resuming Run: ${runId}\n`);
37
+
38
+ // 1. RunStateを読み込み
39
+ const runState = await stores.runStore.get(runId);
40
+ if (!runState) {
41
+ throw new Error(`Run not found: ${runId}`);
42
+ }
43
+
44
+ logger.info({ runState }, "Run state loaded");
45
+
46
+ // 2. タスク一覧を読み込み
47
+ const allTasks = await stores.taskStore.getAll();
48
+ const runTasks = allTasks.filter((task) => {
49
+ return task.taskId.includes(runIdStr);
50
+ });
51
+
52
+ logger.info({ taskCount: runTasks.length }, "Tasks loaded");
53
+
54
+ // 2.5. 早期リターン: タスクが0件の場合
55
+ if (runTasks.length === 0) {
56
+ logger.info({ runId }, "No tasks found for run");
57
+ console.log("⚠️ No tasks found for this run\n");
58
+ await app.shutdown();
59
+ return;
60
+ }
61
+
62
+ // 2.6. 早期リターン: 全タスク完了済みまたは全タスク失敗済み
63
+ const pendingOrRunning = runTasks.filter(
64
+ (t) => t.status === "pending" || t.status === "running"
65
+ );
66
+ if (pendingOrRunning.length === 0) {
67
+ const failedCount = runTasks.filter((t) => t.status === "failed").length;
68
+ const completedCount = runTasks.filter((t) => t.status === "completed").length;
69
+ if (failedCount > 0 && completedCount === 0) {
70
+ logger.info({ runId, failedCount }, "All tasks already failed");
71
+ console.log(`⚠️ All ${failedCount} tasks already failed\n`);
72
+ } else {
73
+ logger.info({ runId, completedCount, failedCount }, "All tasks already finished");
74
+ console.log(`✅ All tasks already finished (${completedCount} completed, ${failedCount} failed)\n`);
75
+ }
76
+ await app.shutdown();
77
+ return;
78
+ }
79
+
80
+ // 3. running → pending にリセット
81
+ let resetCount = 0;
82
+ for (const task of runTasks) {
83
+ if (task.status === "running") {
84
+ await stores.taskStore.save({
85
+ ...task,
86
+ status: "pending",
87
+ workerId: undefined,
88
+ });
89
+ resetCount++;
90
+ }
91
+ }
92
+
93
+ logger.info({ resetCount }, "Reset running tasks to pending");
94
+ console.log(`✅ Reset ${resetCount} running tasks to pending\n`);
95
+
96
+ // 4. TaskPlanを再構築
97
+ const taskPlan = {
98
+ runId,
99
+ parentBranch: runState.parentBranch,
100
+ tasks: runTasks,
101
+ };
102
+
103
+ // 5. Dispatcher再初期化
104
+ await dispatcher.initialize(taskPlan);
105
+ await processManager.initializePool(app.config.workers.num);
106
+
107
+ logger.info("Dispatcher and ProcessManager re-initialized");
108
+ console.log("✅ Dispatcher and ProcessManager re-initialized\n");
109
+
110
+ // 6. Dispatcher.start()
111
+ dispatcher.start();
112
+ logger.info({ runId }, "Run resumed");
113
+ console.log("✅ Run resumed. Waiting for completion...\n");
114
+
115
+ // 7. Wait for run completion with timeout and error handling
116
+ const timeoutMs = (app.config.timeouts.staleTask || 5400) * 1000;
117
+
118
+ await new Promise<void>((resolve, reject) => {
119
+ let settled = false;
120
+ const settle = (fn: () => void) => {
121
+ if (settled) return;
122
+ settled = true;
123
+ cleanup();
124
+ fn();
125
+ };
126
+
127
+ const onCompleted = (event: { type: string; runId?: string }) => {
128
+ if (event.type === "run:completed" && event.runId === runId) {
129
+ settle(resolve);
130
+ }
131
+ };
132
+
133
+ const onFailed = (event: { type: string; runId?: string; error?: string }) => {
134
+ if (event.type === "run:failed" && event.runId === runId) {
135
+ settle(() => reject(new Error(`Run failed: ${event.error ?? "unknown error"}`)));
136
+ }
137
+ };
138
+
139
+ const timer = setTimeout(() => {
140
+ settle(() => reject(new Error(`Resume timed out after ${timeoutMs}ms`)));
141
+ }, timeoutMs);
142
+
143
+ const cleanup = () => {
144
+ clearTimeout(timer);
145
+ app.eventBus.off("run:completed", onCompleted);
146
+ app.eventBus.off("run:failed", onFailed);
147
+ };
148
+
149
+ app.eventBus.on("run:completed", onCompleted);
150
+ app.eventBus.on("run:failed", onFailed);
151
+ });
152
+
153
+ // 8. Shutdown gracefully
154
+ await app.shutdown();
155
+ console.log("\n✅ Run completed successfully\n");
156
+ }