@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.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/aad.js +2 -0
- package/package.json +78 -0
- package/src/__tests__/e2e/pipeline-e2e.test.ts +279 -0
- package/src/__tests__/e2e/resume-e2e.test.ts +200 -0
- package/src/__tests__/integration/cli-smoke.test.ts +175 -0
- package/src/__tests__/integration/pipeline.test.ts +346 -0
- package/src/bun-imports.d.ts +14 -0
- package/src/main.ts +52 -0
- package/src/modules/claude-provider/__tests__/claude-cli.adapter.test.ts +277 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +127 -0
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +347 -0
- package/src/modules/claude-provider/__tests__/effort-strategy.test.ts +212 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +251 -0
- package/src/modules/claude-provider/__tests__/retry.test.ts +201 -0
- package/src/modules/claude-provider/claude-cli.adapter.ts +156 -0
- package/src/modules/claude-provider/claude-provider.port.ts +35 -0
- package/src/modules/claude-provider/claude-sdk.adapter.ts +217 -0
- package/src/modules/claude-provider/effort-strategy.ts +94 -0
- package/src/modules/claude-provider/index.ts +32 -0
- package/src/modules/claude-provider/provider-registry.ts +92 -0
- package/src/modules/claude-provider/retry.ts +81 -0
- package/src/modules/cli/__tests__/app.test.ts +160 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +111 -0
- package/src/modules/cli/__tests__/commands.test.ts +186 -0
- package/src/modules/cli/__tests__/output.test.ts +329 -0
- package/src/modules/cli/__tests__/resume.test.ts +324 -0
- package/src/modules/cli/__tests__/run.test.ts +168 -0
- package/src/modules/cli/__tests__/shutdown.test.ts +168 -0
- package/src/modules/cli/__tests__/status.test.ts +144 -0
- package/src/modules/cli/app.ts +241 -0
- package/src/modules/cli/commands/cleanup.ts +120 -0
- package/src/modules/cli/commands/resume.ts +156 -0
- package/src/modules/cli/commands/run.ts +322 -0
- package/src/modules/cli/commands/status.ts +101 -0
- package/src/modules/cli/index.ts +29 -0
- package/src/modules/cli/output.ts +256 -0
- package/src/modules/cli/shutdown.ts +122 -0
- package/src/modules/dashboard/__tests__/api-routes.test.ts +204 -0
- package/src/modules/dashboard/__tests__/file-watcher.test.ts +34 -0
- package/src/modules/dashboard/__tests__/server.test.ts +120 -0
- package/src/modules/dashboard/__tests__/sse-broadcaster.test.ts +163 -0
- package/src/modules/dashboard/__tests__/sse-routes.test.ts +58 -0
- package/src/modules/dashboard/__tests__/state-aggregator.test.ts +330 -0
- package/src/modules/dashboard/index.ts +8 -0
- package/src/modules/dashboard/routes/api.ts +84 -0
- package/src/modules/dashboard/routes/sse.ts +37 -0
- package/src/modules/dashboard/server.ts +111 -0
- package/src/modules/dashboard/services/file-watcher.ts +36 -0
- package/src/modules/dashboard/services/sse-broadcaster.ts +81 -0
- package/src/modules/dashboard/services/state-aggregator.ts +132 -0
- package/src/modules/dashboard/ui/dashboard.html +405 -0
- package/src/modules/git-workspace/__tests__/branch-manager.test.ts +335 -0
- package/src/modules/git-workspace/__tests__/git-exec.test.ts +91 -0
- package/src/modules/git-workspace/__tests__/memory-sync.test.ts +273 -0
- package/src/modules/git-workspace/__tests__/merge-service.test.ts +286 -0
- package/src/modules/git-workspace/__tests__/settings-merge.test.ts +163 -0
- package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +247 -0
- package/src/modules/git-workspace/branch-manager.ts +191 -0
- package/src/modules/git-workspace/git-exec.ts +124 -0
- package/src/modules/git-workspace/index.ts +17 -0
- package/src/modules/git-workspace/memory-sync.ts +89 -0
- package/src/modules/git-workspace/merge-service.ts +156 -0
- package/src/modules/git-workspace/settings-merge.ts +95 -0
- package/src/modules/git-workspace/worktree-manager.ts +199 -0
- package/src/modules/logging/__tests__/log-store.test.ts +242 -0
- package/src/modules/logging/__tests__/logger.test.ts +81 -0
- package/src/modules/logging/__tests__/sse-transport.test.ts +93 -0
- package/src/modules/logging/index.ts +7 -0
- package/src/modules/logging/log-store.ts +80 -0
- package/src/modules/logging/logger.ts +55 -0
- package/src/modules/logging/transports/sse-transport.ts +28 -0
- package/src/modules/multi-repo/__tests__/multi-repo-planner.test.ts +93 -0
- package/src/modules/multi-repo/__tests__/repo-context.test.ts +79 -0
- package/src/modules/multi-repo/index.ts +12 -0
- package/src/modules/multi-repo/multi-repo-planner.ts +112 -0
- package/src/modules/multi-repo/repo-context.ts +71 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/file-lock.test.ts +141 -0
- package/src/modules/persistence/__tests__/index.test.ts +38 -0
- package/src/modules/persistence/__tests__/stores.test.ts +594 -0
- package/src/modules/persistence/file-lock.ts +158 -0
- package/src/modules/persistence/fs-run-store.ts +73 -0
- package/src/modules/persistence/fs-task-store.ts +152 -0
- package/src/modules/persistence/fs-worker-store.ts +116 -0
- package/src/modules/persistence/in-memory-stores.ts +98 -0
- package/src/modules/persistence/index.ts +60 -0
- package/src/modules/persistence/stores.port.ts +60 -0
- package/src/modules/planning/__tests__/file-conflict-validator.test.ts +256 -0
- package/src/modules/planning/__tests__/planning-service.test.ts +366 -0
- package/src/modules/planning/__tests__/project-detection.test.ts +707 -0
- package/src/modules/planning/file-conflict-validator.ts +135 -0
- package/src/modules/planning/index.ts +40 -0
- package/src/modules/planning/planning.service.ts +262 -0
- package/src/modules/planning/project-detection.ts +525 -0
- package/src/modules/plugin/__tests__/plugin-loader.test.ts +83 -0
- package/src/modules/plugin/__tests__/plugin-manager.test.ts +187 -0
- package/src/modules/plugin/index.ts +3 -0
- package/src/modules/plugin/plugin-loader.ts +46 -0
- package/src/modules/plugin/plugin-manager.ts +90 -0
- package/src/modules/plugin/plugin.types.ts +37 -0
- package/src/modules/process-manager/__tests__/process-manager.test.ts +210 -0
- package/src/modules/process-manager/__tests__/worker.test.ts +89 -0
- package/src/modules/process-manager/index.ts +5 -0
- package/src/modules/process-manager/process-manager.ts +193 -0
- package/src/modules/process-manager/worker.ts +106 -0
- package/src/modules/task-execution/__tests__/default-spawner.test.ts +154 -0
- package/src/modules/task-execution/__tests__/executor.test.ts +760 -0
- package/src/modules/task-execution/__tests__/implementer-green.test.ts +286 -0
- package/src/modules/task-execution/__tests__/merge-phase.test.ts +368 -0
- package/src/modules/task-execution/__tests__/reviewer.test.ts +302 -0
- package/src/modules/task-execution/__tests__/tester-red.test.ts +281 -0
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +313 -0
- package/src/modules/task-execution/executor.ts +303 -0
- package/src/modules/task-execution/index.ts +45 -0
- package/src/modules/task-execution/phases/default-spawner.ts +49 -0
- package/src/modules/task-execution/phases/implementer-green.ts +100 -0
- package/src/modules/task-execution/phases/merge.ts +122 -0
- package/src/modules/task-execution/phases/reviewer.ts +160 -0
- package/src/modules/task-execution/phases/tester-red.ts +100 -0
- package/src/modules/task-execution/phases/tester-verify.ts +120 -0
- package/src/modules/task-queue/__tests__/dependency-resolver.test.ts +456 -0
- package/src/modules/task-queue/__tests__/dispatcher.test.ts +824 -0
- package/src/modules/task-queue/__tests__/task-plan.test.ts +122 -0
- package/src/modules/task-queue/__tests__/task.test.ts +130 -0
- package/src/modules/task-queue/dependency-resolver.ts +171 -0
- package/src/modules/task-queue/dispatcher.ts +372 -0
- package/src/modules/task-queue/index.ts +16 -0
- package/src/modules/task-queue/task-plan.ts +40 -0
- package/src/modules/task-queue/task.ts +67 -0
- package/src/shared/__tests__/config.test.ts +204 -0
- package/src/shared/__tests__/errors.test.ts +285 -0
- package/src/shared/__tests__/events.test.ts +496 -0
- package/src/shared/__tests__/types.test.ts +360 -0
- package/src/shared/config.ts +133 -0
- package/src/shared/errors.ts +128 -0
- package/src/shared/events.ts +171 -0
- package/src/shared/types.ts +143 -0
- 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
|
+
}
|