@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,217 @@
|
|
|
1
|
+
import type { ClaudeProvider, ClaudeRequest, ClaudeResponse } from "./claude-provider.port";
|
|
2
|
+
import type { Config } from "../../shared/config";
|
|
3
|
+
import type pino from "pino";
|
|
4
|
+
import { ClaudeProviderError } from "../../shared/errors";
|
|
5
|
+
import {
|
|
6
|
+
query,
|
|
7
|
+
type Options,
|
|
8
|
+
type SDKMessage,
|
|
9
|
+
type SDKAssistantMessage,
|
|
10
|
+
type SDKResultMessage,
|
|
11
|
+
type SDKResultSuccess,
|
|
12
|
+
type SDKResultError,
|
|
13
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Claude SDK Adapter
|
|
17
|
+
* @anthropic-ai/claude-agent-sdkを使用したClaudeプロバイダーの実装
|
|
18
|
+
*/
|
|
19
|
+
export class ClaudeSdkAdapter implements ClaudeProvider {
|
|
20
|
+
constructor(
|
|
21
|
+
private config: Config,
|
|
22
|
+
private logger: pino.Logger
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
async call(request: ClaudeRequest): Promise<ClaudeResponse> {
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
const childLogger = this.logger.child({ adapter: "sdk", prompt: request.prompt.slice(0, 50) });
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
childLogger.info("Starting SDK query");
|
|
31
|
+
|
|
32
|
+
const options = this.buildOptions(request);
|
|
33
|
+
const q = query({ prompt: request.prompt, options });
|
|
34
|
+
|
|
35
|
+
// AsyncGeneratorを消費してClaudeResponseにマッピング
|
|
36
|
+
let lastAssistantMessage = "";
|
|
37
|
+
let exitCode = 0;
|
|
38
|
+
let actualModel = request.model ?? this.config.models.default ?? "claude-sonnet-4-5-20250929";
|
|
39
|
+
let error: string | undefined;
|
|
40
|
+
let resultMessage: SDKResultMessage | null = null;
|
|
41
|
+
|
|
42
|
+
for await (const message of q) {
|
|
43
|
+
this.logMessage(childLogger, message);
|
|
44
|
+
|
|
45
|
+
if (message.type === "assistant") {
|
|
46
|
+
const assistantMsg = message as SDKAssistantMessage;
|
|
47
|
+
// アシスタントメッセージから結果テキストを抽出
|
|
48
|
+
const textBlocks = assistantMsg.message.content.filter(
|
|
49
|
+
(block: { type: string }) => block.type === "text"
|
|
50
|
+
);
|
|
51
|
+
if (textBlocks.length > 0) {
|
|
52
|
+
lastAssistantMessage = textBlocks
|
|
53
|
+
.map((block: { text?: string }) => block.text ?? "")
|
|
54
|
+
.join("\n");
|
|
55
|
+
}
|
|
56
|
+
actualModel = assistantMsg.message.model ?? actualModel;
|
|
57
|
+
|
|
58
|
+
// エラーチェック
|
|
59
|
+
if (assistantMsg.error) {
|
|
60
|
+
error = this.mapSdkError(assistantMsg.error);
|
|
61
|
+
exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
} else if (message.type === "result") {
|
|
64
|
+
resultMessage = message as SDKResultMessage;
|
|
65
|
+
if (resultMessage.subtype === "success") {
|
|
66
|
+
const successMsg = resultMessage as SDKResultSuccess;
|
|
67
|
+
lastAssistantMessage = successMsg.result;
|
|
68
|
+
exitCode = 0;
|
|
69
|
+
} else {
|
|
70
|
+
const errorMsg = resultMessage as SDKResultError;
|
|
71
|
+
error = errorMsg.errors.join("; ");
|
|
72
|
+
exitCode = 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
|
|
79
|
+
if (exitCode !== 0) {
|
|
80
|
+
childLogger.error({ exitCode, error, duration }, "SDK query failed");
|
|
81
|
+
throw new ClaudeProviderError(
|
|
82
|
+
`SDK query failed with exit code ${exitCode}: ${error ?? "unknown error"}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
childLogger.info({ duration, model: actualModel }, "SDK query completed");
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
result: lastAssistantMessage,
|
|
90
|
+
exitCode,
|
|
91
|
+
model: actualModel,
|
|
92
|
+
effortLevel: request.effortLevel ?? "medium",
|
|
93
|
+
duration,
|
|
94
|
+
};
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const duration = Date.now() - startTime;
|
|
97
|
+
childLogger.error({ err, duration }, "SDK query error");
|
|
98
|
+
|
|
99
|
+
if (err instanceof ClaudeProviderError) {
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new ClaudeProviderError(
|
|
104
|
+
`SDK adapter error: ${err instanceof Error ? err.message : String(err)}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private buildOptions(request: ClaudeRequest): Options {
|
|
110
|
+
const options: Options = {
|
|
111
|
+
cwd: request.cwd ?? process.cwd(),
|
|
112
|
+
model: request.model ?? this.config.models.default,
|
|
113
|
+
systemPrompt: request.systemPrompt,
|
|
114
|
+
allowedTools: request.allowedTools,
|
|
115
|
+
permissionMode: request.permissionMode ?? "bypassPermissions",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// env: 認証情報(ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN)+ effort level
|
|
119
|
+
options.env = this.buildEnv(request);
|
|
120
|
+
|
|
121
|
+
// Output Format
|
|
122
|
+
if (request.outputFormat === "json") {
|
|
123
|
+
// JSON出力を要求する場合、schema指定が必要
|
|
124
|
+
// ここでは汎用的なschemaを使用
|
|
125
|
+
options.outputFormat = {
|
|
126
|
+
type: "json_schema",
|
|
127
|
+
schema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
result: { type: "string" },
|
|
131
|
+
},
|
|
132
|
+
required: ["result"],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// タイムアウト
|
|
138
|
+
if (request.timeout) {
|
|
139
|
+
const abortController = new AbortController();
|
|
140
|
+
setTimeout(() => abortController.abort(), request.timeout);
|
|
141
|
+
options.abortController = abortController;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Resume
|
|
145
|
+
if (request.resume) {
|
|
146
|
+
options.resume = request.resume;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// System Prompt追記
|
|
150
|
+
if (request.appendSystemPrompt) {
|
|
151
|
+
if (typeof options.systemPrompt === "string") {
|
|
152
|
+
options.systemPrompt = `${options.systemPrompt}\n\n${request.appendSystemPrompt}`;
|
|
153
|
+
} else {
|
|
154
|
+
options.systemPrompt = {
|
|
155
|
+
type: "preset",
|
|
156
|
+
preset: "claude_code",
|
|
157
|
+
append: request.appendSystemPrompt,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Subagents
|
|
163
|
+
if (request.subagents && request.subagents.length > 0) {
|
|
164
|
+
(options as Options & { subagents?: Array<{ name: string; prompt: string; model?: string }> }).subagents = request.subagents.map(sa => ({
|
|
165
|
+
name: sa.name,
|
|
166
|
+
prompt: sa.prompt,
|
|
167
|
+
...(sa.model ? { model: sa.model } : {}),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return options;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
private buildEnv(request: ClaudeRequest): Record<string, string | undefined> {
|
|
176
|
+
const env: Record<string, string | undefined> = { ...process.env };
|
|
177
|
+
if (request.effortLevel) {
|
|
178
|
+
env.CLAUDE_CODE_EFFORT_LEVEL = request.effortLevel;
|
|
179
|
+
}
|
|
180
|
+
return env;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private mapSdkError(error: string): string {
|
|
184
|
+
const errorMap: Record<string, string> = {
|
|
185
|
+
authentication_failed: "Authentication failed",
|
|
186
|
+
billing_error: "Billing error",
|
|
187
|
+
rate_limit: "Rate limit exceeded",
|
|
188
|
+
invalid_request: "Invalid request",
|
|
189
|
+
server_error: "Server error",
|
|
190
|
+
unknown: "Unknown error",
|
|
191
|
+
};
|
|
192
|
+
return errorMap[error] ?? error;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
private logMessage(logger: pino.Logger, message: SDKMessage): void {
|
|
197
|
+
switch (message.type) {
|
|
198
|
+
case "assistant":
|
|
199
|
+
{
|
|
200
|
+
const assistantMsg = message as SDKAssistantMessage;
|
|
201
|
+
logger.debug({ model: assistantMsg.message.model }, "Assistant message");
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case "user":
|
|
205
|
+
logger.debug("User message");
|
|
206
|
+
break;
|
|
207
|
+
case "result":
|
|
208
|
+
{
|
|
209
|
+
const resultMsg = message as SDKResultMessage;
|
|
210
|
+
logger.debug({ subtype: resultMsg.subtype }, "Result message");
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
logger.trace({ type: message.type }, "SDK message");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { EffortLevel, PhaseName, Task } from "../../shared/types";
|
|
2
|
+
import type { Config } from "../../shared/config";
|
|
3
|
+
|
|
4
|
+
export type TaskComplexity = "low" | "medium" | "high";
|
|
5
|
+
|
|
6
|
+
export function estimateTaskComplexity(task: Task): TaskComplexity {
|
|
7
|
+
let score = 0;
|
|
8
|
+
|
|
9
|
+
// ファイル数
|
|
10
|
+
const fileCount = task.filesToModify.length;
|
|
11
|
+
if (fileCount >= 5) score += 3;
|
|
12
|
+
else if (fileCount >= 3) score += 2;
|
|
13
|
+
else if (fileCount >= 1) score += 1;
|
|
14
|
+
|
|
15
|
+
// 依存タスク数
|
|
16
|
+
const depCount = task.dependsOn.length;
|
|
17
|
+
if (depCount >= 3) score += 2;
|
|
18
|
+
else if (depCount >= 1) score += 1;
|
|
19
|
+
|
|
20
|
+
// 説明の長さ(複雑な要件 = 長い説明)
|
|
21
|
+
const descLen = task.description.length;
|
|
22
|
+
if (descLen >= 500) score += 2;
|
|
23
|
+
else if (descLen >= 200) score += 1;
|
|
24
|
+
|
|
25
|
+
// スコアから複雑度を判定
|
|
26
|
+
if (score >= 5) return "high";
|
|
27
|
+
if (score >= 3) return "medium";
|
|
28
|
+
return "low";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getEffortLevel(phase: PhaseName): EffortLevel {
|
|
32
|
+
switch (phase) {
|
|
33
|
+
case "splitter":
|
|
34
|
+
return "high";
|
|
35
|
+
case "tester":
|
|
36
|
+
return "medium";
|
|
37
|
+
case "implementer":
|
|
38
|
+
return "medium";
|
|
39
|
+
case "reviewer":
|
|
40
|
+
return "high";
|
|
41
|
+
case "merge-resolver":
|
|
42
|
+
return "low";
|
|
43
|
+
default:
|
|
44
|
+
return "medium";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getAdaptiveEffortLevel(
|
|
49
|
+
phase: PhaseName,
|
|
50
|
+
complexity: TaskComplexity,
|
|
51
|
+
config: Config
|
|
52
|
+
): EffortLevel {
|
|
53
|
+
const baseEffort = getEffortLevel(phase);
|
|
54
|
+
|
|
55
|
+
// Adaptive effort無効の場合はベースをそのまま返す
|
|
56
|
+
if (!config.adaptiveEffort) {
|
|
57
|
+
return baseEffort;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 複雑度に応じてeffort levelを調整
|
|
61
|
+
if (complexity === "high" && baseEffort === "medium") {
|
|
62
|
+
return "high";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (complexity === "low" && baseEffort === "medium") {
|
|
66
|
+
return "low";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return baseEffort;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getPhaseModel(
|
|
73
|
+
phase: PhaseName,
|
|
74
|
+
config: Config
|
|
75
|
+
): string | undefined {
|
|
76
|
+
// フェーズ別モデル設定を確認
|
|
77
|
+
const phaseModelMap: Record<PhaseName, string | undefined> = {
|
|
78
|
+
splitter: config.models.splitter,
|
|
79
|
+
tester: config.models.tester,
|
|
80
|
+
implementer: config.models.implementer,
|
|
81
|
+
reviewer: config.models.reviewer,
|
|
82
|
+
"merge-resolver": config.models.mergeResolver,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const phaseModel = phaseModelMap[phase];
|
|
86
|
+
|
|
87
|
+
// merge-resolverは未設定時にhaikuデフォルト
|
|
88
|
+
if (phase === "merge-resolver" && !phaseModel) {
|
|
89
|
+
return "haiku";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// フェーズ別モデルがあればそれを使用、なければデフォルトモデルにフォールバック
|
|
93
|
+
return phaseModel ?? config.models.default;
|
|
94
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClaudeProvider } from "./claude-provider.port";
|
|
2
|
+
import type { Config } from "../../shared/config";
|
|
3
|
+
import type pino from "pino";
|
|
4
|
+
import { ClaudeCliAdapter } from "./claude-cli.adapter";
|
|
5
|
+
import { ClaudeSdkAdapter } from "./claude-sdk.adapter";
|
|
6
|
+
|
|
7
|
+
export type { ClaudeProvider, ClaudeRequest, ClaudeResponse, SubagentConfig } from "./claude-provider.port";
|
|
8
|
+
export { getEffortLevel, getAdaptiveEffortLevel, getPhaseModel, estimateTaskComplexity } from "./effort-strategy";
|
|
9
|
+
export type { TaskComplexity } from "./effort-strategy";
|
|
10
|
+
export { withRetry, type RetryResult, type RetryOptions } from "./retry";
|
|
11
|
+
export {
|
|
12
|
+
ProviderRegistry,
|
|
13
|
+
createProviderRegistry,
|
|
14
|
+
type ProviderConfig,
|
|
15
|
+
} from "./provider-registry";
|
|
16
|
+
|
|
17
|
+
export type ProviderType = "cli" | "sdk";
|
|
18
|
+
|
|
19
|
+
export function createClaudeProvider(
|
|
20
|
+
type: ProviderType,
|
|
21
|
+
config: Config,
|
|
22
|
+
logger: pino.Logger
|
|
23
|
+
): ClaudeProvider {
|
|
24
|
+
switch (type) {
|
|
25
|
+
case "cli":
|
|
26
|
+
return new ClaudeCliAdapter(config, logger);
|
|
27
|
+
case "sdk":
|
|
28
|
+
return new ClaudeSdkAdapter(config, logger);
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type pino from "pino";
|
|
2
|
+
import type { ClaudeProvider } from "./claude-provider.port";
|
|
3
|
+
import type { PhaseName } from "../../shared/types";
|
|
4
|
+
import type { Config } from "../../shared/config";
|
|
5
|
+
import type { ProviderType } from "./index";
|
|
6
|
+
import { createClaudeProvider } from "./index";
|
|
7
|
+
|
|
8
|
+
export interface ProviderConfig {
|
|
9
|
+
default: ProviderType;
|
|
10
|
+
overrides?: Partial<Record<PhaseName, ProviderType>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ProviderRegistry {
|
|
14
|
+
private readonly providers: Map<ProviderType, ClaudeProvider>;
|
|
15
|
+
private readonly config: ProviderConfig;
|
|
16
|
+
private readonly logger: pino.Logger;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
providers: Map<ProviderType, ClaudeProvider>,
|
|
20
|
+
config: ProviderConfig,
|
|
21
|
+
logger: pino.Logger
|
|
22
|
+
) {
|
|
23
|
+
this.providers = providers;
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
|
|
27
|
+
// Validate default provider exists
|
|
28
|
+
if (!this.providers.has(config.default)) {
|
|
29
|
+
throw new Error(`Default provider '${config.default}' not found in providers map`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate all override providers exist
|
|
33
|
+
if (config.overrides) {
|
|
34
|
+
for (const [phase, providerType] of Object.entries(config.overrides)) {
|
|
35
|
+
if (providerType && !this.providers.has(providerType)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Override provider '${providerType}' for phase '${phase}' not found in providers map`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getProvider(phase?: PhaseName): ClaudeProvider {
|
|
45
|
+
const providerType = this.getProviderType(phase);
|
|
46
|
+
const provider = this.providers.get(providerType);
|
|
47
|
+
|
|
48
|
+
if (!provider) {
|
|
49
|
+
throw new Error(`Provider '${providerType}' not found`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.logger.debug(
|
|
53
|
+
{ phase, providerType },
|
|
54
|
+
`Selected provider for phase`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return provider;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getProviderType(phase?: PhaseName): ProviderType {
|
|
61
|
+
if (phase && this.config.overrides?.[phase]) {
|
|
62
|
+
return this.config.overrides[phase]!;
|
|
63
|
+
}
|
|
64
|
+
return this.config.default;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createProviderRegistry(
|
|
69
|
+
config: ProviderConfig,
|
|
70
|
+
appConfig: Config,
|
|
71
|
+
logger: pino.Logger
|
|
72
|
+
): ProviderRegistry {
|
|
73
|
+
const providers = new Map<ProviderType, ClaudeProvider>();
|
|
74
|
+
|
|
75
|
+
// Determine which providers need to be instantiated
|
|
76
|
+
const requiredTypes = new Set<ProviderType>([config.default]);
|
|
77
|
+
|
|
78
|
+
if (config.overrides) {
|
|
79
|
+
for (const providerType of Object.values(config.overrides)) {
|
|
80
|
+
if (providerType) {
|
|
81
|
+
requiredTypes.add(providerType);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create only the required providers
|
|
87
|
+
for (const type of requiredTypes) {
|
|
88
|
+
providers.set(type, createClaudeProvider(type, appConfig, logger));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return new ProviderRegistry(providers, config, logger);
|
|
92
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { EffortLevel } from "../../shared/types";
|
|
2
|
+
import { AadError } from "../../shared/errors";
|
|
3
|
+
|
|
4
|
+
export interface RetryOptions {
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
initialEffort: EffortLevel;
|
|
7
|
+
backoffMs?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RetryResult<T> =
|
|
11
|
+
| {
|
|
12
|
+
success: true;
|
|
13
|
+
data: T;
|
|
14
|
+
attempts: number;
|
|
15
|
+
finalEffort: EffortLevel;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
success: false;
|
|
19
|
+
error: AadError;
|
|
20
|
+
attempts: number;
|
|
21
|
+
finalEffort: EffortLevel;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function escalateEffort(currentEffort: EffortLevel): EffortLevel {
|
|
25
|
+
if (currentEffort === "low") return "medium";
|
|
26
|
+
if (currentEffort === "medium") return "high";
|
|
27
|
+
return "high"; // already at max
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function sleep(ms: number): Promise<void> {
|
|
31
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function withRetry<T>(
|
|
35
|
+
fn: (effort: EffortLevel) => Promise<T>,
|
|
36
|
+
options: RetryOptions
|
|
37
|
+
): Promise<RetryResult<T>> {
|
|
38
|
+
const { maxRetries, initialEffort, backoffMs = 5000 } = options;
|
|
39
|
+
|
|
40
|
+
let currentEffort = initialEffort;
|
|
41
|
+
let lastError: AadError | null = null;
|
|
42
|
+
|
|
43
|
+
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
const result = await fn(currentEffort);
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
data: result,
|
|
49
|
+
attempts: attempt,
|
|
50
|
+
finalEffort: currentEffort,
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// AadError以外の例外はラップする
|
|
54
|
+
if (!(error instanceof AadError)) {
|
|
55
|
+
lastError = new AadError(
|
|
56
|
+
"UNKNOWN_ERROR",
|
|
57
|
+
error instanceof Error ? error.message : String(error)
|
|
58
|
+
);
|
|
59
|
+
} else {
|
|
60
|
+
lastError = error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 最後の試行でない場合
|
|
64
|
+
if (attempt <= maxRetries) {
|
|
65
|
+
// Effort levelをエスカレート
|
|
66
|
+
currentEffort = escalateEffort(currentEffort);
|
|
67
|
+
|
|
68
|
+
// Exponential backoff: attempt * backoffMs
|
|
69
|
+
const delayMs = attempt * backoffMs;
|
|
70
|
+
await sleep(delayMs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: lastError!,
|
|
78
|
+
attempts: maxRetries + 1,
|
|
79
|
+
finalEffort: currentEffort,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI App - Composition Root Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect, afterEach } from "bun:test";
|
|
6
|
+
import { createApp, type App } from "../app";
|
|
7
|
+
|
|
8
|
+
describe("createApp", () => {
|
|
9
|
+
let app: App | undefined;
|
|
10
|
+
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
if (app) {
|
|
13
|
+
await app.shutdown();
|
|
14
|
+
app = undefined;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("creates app with default options", async () => {
|
|
19
|
+
app = await createApp({ dashboard: false }); // デフォルトでdashboardを無効化
|
|
20
|
+
|
|
21
|
+
expect(app).toBeDefined();
|
|
22
|
+
expect(app.config).toBeDefined();
|
|
23
|
+
expect(app.eventBus).toBeDefined();
|
|
24
|
+
expect(app.logger).toBeDefined();
|
|
25
|
+
expect(app.stores).toBeDefined();
|
|
26
|
+
expect(app.dispatcher).toBeDefined();
|
|
27
|
+
expect(app.processManager).toBeDefined();
|
|
28
|
+
expect(app.planningService).toBeDefined();
|
|
29
|
+
expect(app.providerRegistry).toBeDefined();
|
|
30
|
+
expect(app.worktreeManager).toBeDefined();
|
|
31
|
+
expect(app.branchManager).toBeDefined();
|
|
32
|
+
expect(app.mergeService).toBeDefined();
|
|
33
|
+
expect(typeof app.shutdown).toBe("function");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("creates app with custom workers", async () => {
|
|
37
|
+
app = await createApp({ workers: 4, dashboard: false });
|
|
38
|
+
|
|
39
|
+
expect(app.config.workers.num).toBe(4);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("creates app with debug enabled", async () => {
|
|
43
|
+
app = await createApp({ debug: true, dashboard: false });
|
|
44
|
+
|
|
45
|
+
expect(app.config.debug).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("creates app with debug disabled", async () => {
|
|
49
|
+
app = await createApp({ debug: false, dashboard: false });
|
|
50
|
+
|
|
51
|
+
expect(app.config.debug).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("creates app with memory persistence", async () => {
|
|
55
|
+
app = await createApp({ persist: "memory", dashboard: false });
|
|
56
|
+
|
|
57
|
+
expect(app.stores).toBeDefined();
|
|
58
|
+
expect(app.stores.taskStore).toBeDefined();
|
|
59
|
+
expect(app.stores.workerStore).toBeDefined();
|
|
60
|
+
expect(app.stores.runStore).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("creates app with memory persistence (default)", async () => {
|
|
64
|
+
app = await createApp({ dashboard: false });
|
|
65
|
+
|
|
66
|
+
expect(app.stores).toBeDefined();
|
|
67
|
+
expect(app.stores.taskStore).toBeDefined();
|
|
68
|
+
expect(app.stores.workerStore).toBeDefined();
|
|
69
|
+
expect(app.stores.runStore).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("creates app with dashboard enabled on custom port", async () => {
|
|
73
|
+
// ランダムポートでテスト
|
|
74
|
+
const randomPort = 10000 + Math.floor(Math.random() * 10000);
|
|
75
|
+
process.env.AAD_DASHBOARD_PORT = String(randomPort);
|
|
76
|
+
|
|
77
|
+
app = await createApp({ dashboard: true });
|
|
78
|
+
|
|
79
|
+
expect(app.config.dashboard.enabled).toBe(true);
|
|
80
|
+
expect(app.dashboardServer).toBeDefined();
|
|
81
|
+
expect(app.config.dashboard.port).toBe(randomPort);
|
|
82
|
+
|
|
83
|
+
// 環境変数リセット
|
|
84
|
+
delete process.env.AAD_DASHBOARD_PORT;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("creates app with dashboard disabled", async () => {
|
|
88
|
+
app = await createApp({ dashboard: false });
|
|
89
|
+
|
|
90
|
+
expect(app.config.dashboard.enabled).toBe(false);
|
|
91
|
+
expect(app.dashboardServer).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("creates app with CLI provider default", async () => {
|
|
95
|
+
app = await createApp({ providerDefault: "cli", dashboard: false });
|
|
96
|
+
|
|
97
|
+
expect(app.providerRegistry).toBeDefined();
|
|
98
|
+
expect(app.providerRegistry.getProviderType()).toBe("cli");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("creates app with SDK provider default", async () => {
|
|
102
|
+
app = await createApp({ providerDefault: "sdk", dashboard: false });
|
|
103
|
+
|
|
104
|
+
expect(app.providerRegistry).toBeDefined();
|
|
105
|
+
expect(app.providerRegistry.getProviderType()).toBe("sdk");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("creates app with provider overrides", async () => {
|
|
109
|
+
app = await createApp({
|
|
110
|
+
providerDefault: "cli",
|
|
111
|
+
providerOverrides: {
|
|
112
|
+
reviewer: "sdk",
|
|
113
|
+
},
|
|
114
|
+
dashboard: false,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(app.providerRegistry.getProviderType()).toBe("cli");
|
|
118
|
+
expect(app.providerRegistry.getProviderType("reviewer")).toBe("sdk");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("shutdown stops all services", async () => {
|
|
122
|
+
app = await createApp({ dashboard: false });
|
|
123
|
+
|
|
124
|
+
// shutdown should not throw
|
|
125
|
+
await expect(app.shutdown()).resolves.toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("shutdown stops dashboard if enabled", async () => {
|
|
129
|
+
// ランダムポートでテスト
|
|
130
|
+
const randomPort = 10000 + Math.floor(Math.random() * 10000);
|
|
131
|
+
process.env.AAD_DASHBOARD_PORT = String(randomPort);
|
|
132
|
+
|
|
133
|
+
app = await createApp({ dashboard: true });
|
|
134
|
+
|
|
135
|
+
expect(app.dashboardServer).toBeDefined();
|
|
136
|
+
|
|
137
|
+
await app.shutdown();
|
|
138
|
+
|
|
139
|
+
// Dashboard should be stopped (no error thrown)
|
|
140
|
+
|
|
141
|
+
// 環境変数リセット
|
|
142
|
+
delete process.env.AAD_DASHBOARD_PORT;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("multiple options combined", async () => {
|
|
146
|
+
app = await createApp({
|
|
147
|
+
workers: 3,
|
|
148
|
+
debug: true,
|
|
149
|
+
persist: "memory",
|
|
150
|
+
dashboard: false,
|
|
151
|
+
providerDefault: "sdk",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(app.config.workers.num).toBe(3);
|
|
155
|
+
expect(app.config.debug).toBe(true);
|
|
156
|
+
expect(app.config.dashboard.enabled).toBe(false);
|
|
157
|
+
expect(app.dashboardServer).toBeUndefined();
|
|
158
|
+
expect(app.providerRegistry.getProviderType()).toBe("sdk");
|
|
159
|
+
});
|
|
160
|
+
});
|