@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,127 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { ClaudeSdkAdapter } from "../claude-sdk.adapter";
|
|
3
|
+
import { loadConfig } from "../../../shared/config";
|
|
4
|
+
import pino from "pino";
|
|
5
|
+
import { ClaudeProviderError } from "../../../shared/errors";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 実環境バリデーションテスト
|
|
9
|
+
* ANTHROPIC_API_KEY または CLAUDE_CODE_OAUTH_TOKEN が設定されている場合のみ実行
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
13
|
+
const hasOAuth = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
14
|
+
const hasAuth = hasApiKey || hasOAuth;
|
|
15
|
+
|
|
16
|
+
const logger = pino({ level: "silent" });
|
|
17
|
+
|
|
18
|
+
function createAdapter() {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
return new ClaudeSdkAdapter(config, logger);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe.skipIf(!hasAuth)("ClaudeSdkAdapter - Real Environment", () => {
|
|
24
|
+
test("simple query returns valid response", async () => {
|
|
25
|
+
const adapter = createAdapter();
|
|
26
|
+
const response = await adapter.call({
|
|
27
|
+
prompt: "Reply with exactly: PING",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(response.result).toBeDefined();
|
|
31
|
+
expect(response.result.length).toBeGreaterThan(0);
|
|
32
|
+
expect(response.exitCode).toBe(0);
|
|
33
|
+
expect(response.model).toBeDefined();
|
|
34
|
+
expect(response.duration).toBeGreaterThan(0);
|
|
35
|
+
}, 30_000);
|
|
36
|
+
|
|
37
|
+
test("detects authentication method", () => {
|
|
38
|
+
if (hasApiKey) {
|
|
39
|
+
expect(process.env.ANTHROPIC_API_KEY).toBeDefined();
|
|
40
|
+
console.log("Auth: ANTHROPIC_API_KEY");
|
|
41
|
+
}
|
|
42
|
+
if (hasOAuth) {
|
|
43
|
+
expect(process.env.CLAUDE_CODE_OAUTH_TOKEN).toBeDefined();
|
|
44
|
+
console.log("Auth: CLAUDE_CODE_OAUTH_TOKEN");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("throws ClaudeProviderError on invalid auth", async () => {
|
|
49
|
+
const originalKey = process.env.ANTHROPIC_API_KEY;
|
|
50
|
+
const originalOAuth = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// 無効な認証情報に差し替え
|
|
54
|
+
process.env.ANTHROPIC_API_KEY = "sk-ant-invalid-key-for-testing";
|
|
55
|
+
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
56
|
+
|
|
57
|
+
const adapter = createAdapter();
|
|
58
|
+
await expect(
|
|
59
|
+
adapter.call({ prompt: "test" })
|
|
60
|
+
).rejects.toThrow(ClaudeProviderError);
|
|
61
|
+
} finally {
|
|
62
|
+
// 復元
|
|
63
|
+
if (originalKey) process.env.ANTHROPIC_API_KEY = originalKey;
|
|
64
|
+
else delete process.env.ANTHROPIC_API_KEY;
|
|
65
|
+
if (originalOAuth) process.env.CLAUDE_CODE_OAUTH_TOKEN = originalOAuth;
|
|
66
|
+
else delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
67
|
+
}
|
|
68
|
+
}, 15_000);
|
|
69
|
+
|
|
70
|
+
test("respects timeout/abort", async () => {
|
|
71
|
+
const adapter = createAdapter();
|
|
72
|
+
|
|
73
|
+
await expect(
|
|
74
|
+
adapter.call({
|
|
75
|
+
prompt: "Write a very long essay about the history of computing.",
|
|
76
|
+
timeout: 100, // 100ms — 即タイムアウト
|
|
77
|
+
})
|
|
78
|
+
).rejects.toThrow();
|
|
79
|
+
}, 10_000);
|
|
80
|
+
|
|
81
|
+
test("passes effort level correctly", async () => {
|
|
82
|
+
const adapter = createAdapter();
|
|
83
|
+
const response = await adapter.call({
|
|
84
|
+
prompt: "Reply with exactly: OK",
|
|
85
|
+
effortLevel: "low",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(response.result).toBeDefined();
|
|
89
|
+
expect(response.exitCode).toBe(0);
|
|
90
|
+
expect(response.effortLevel).toBe("low");
|
|
91
|
+
}, 30_000);
|
|
92
|
+
|
|
93
|
+
test("respects model override", async () => {
|
|
94
|
+
const adapter = createAdapter();
|
|
95
|
+
const response = await adapter.call({
|
|
96
|
+
prompt: "Reply with exactly: HI",
|
|
97
|
+
model: "claude-haiku-3-5-20241022",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(response.result).toBeDefined();
|
|
101
|
+
expect(response.exitCode).toBe(0);
|
|
102
|
+
// model名はSDKが返す値に依存するが、haiku系であることを確認
|
|
103
|
+
expect(response.model).toContain("haiku");
|
|
104
|
+
}, 30_000);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe.skipIf(!hasOAuth)("ClaudeSdkAdapter - OAuth Authentication", () => {
|
|
108
|
+
test("authenticates with CLAUDE_CODE_OAUTH_TOKEN", async () => {
|
|
109
|
+
const originalKey = process.env.ANTHROPIC_API_KEY;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// API Keyを一時的に除外してOAuthのみで認証
|
|
113
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
114
|
+
|
|
115
|
+
const adapter = createAdapter();
|
|
116
|
+
const response = await adapter.call({
|
|
117
|
+
prompt: "Reply with exactly: OAUTH_OK",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(response.result).toBeDefined();
|
|
121
|
+
expect(response.exitCode).toBe(0);
|
|
122
|
+
} finally {
|
|
123
|
+
if (originalKey) process.env.ANTHROPIC_API_KEY = originalKey;
|
|
124
|
+
else delete process.env.ANTHROPIC_API_KEY;
|
|
125
|
+
}
|
|
126
|
+
}, 30_000);
|
|
127
|
+
});
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from "bun:test";
|
|
2
|
+
import { ClaudeSdkAdapter } from "../claude-sdk.adapter";
|
|
3
|
+
import type { Config } from "../../../shared/config";
|
|
4
|
+
import type { ClaudeRequest } from "../claude-provider.port";
|
|
5
|
+
import pino from "pino";
|
|
6
|
+
import { ClaudeProviderError } from "../../../shared/errors";
|
|
7
|
+
|
|
8
|
+
// SDK型のモック
|
|
9
|
+
type MockSDKMessage =
|
|
10
|
+
| {
|
|
11
|
+
type: "assistant";
|
|
12
|
+
message: {
|
|
13
|
+
model: string;
|
|
14
|
+
content: Array<{ type: string; text?: string }>;
|
|
15
|
+
};
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
type: "result";
|
|
20
|
+
subtype: "success";
|
|
21
|
+
result: string;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
type: "result";
|
|
25
|
+
subtype: "error_during_execution";
|
|
26
|
+
errors: string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// @anthropic-ai/claude-agent-sdk のモック
|
|
30
|
+
const mockQuery = mock(
|
|
31
|
+
(_params: { prompt: string; options?: unknown }): AsyncGenerator<MockSDKMessage, void, unknown> => {
|
|
32
|
+
return (async function* () {
|
|
33
|
+
// デフォルトは成功レスポンス
|
|
34
|
+
yield {
|
|
35
|
+
type: "assistant",
|
|
36
|
+
message: {
|
|
37
|
+
model: "claude-sonnet-4-5-20250929",
|
|
38
|
+
content: [{ type: "text", text: "Test response" }],
|
|
39
|
+
},
|
|
40
|
+
} as MockSDKMessage;
|
|
41
|
+
yield {
|
|
42
|
+
type: "result",
|
|
43
|
+
subtype: "success",
|
|
44
|
+
result: "Test response",
|
|
45
|
+
} as MockSDKMessage;
|
|
46
|
+
})() as AsyncGenerator<MockSDKMessage, void, unknown>;
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// SDKモジュール全体をモック
|
|
51
|
+
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
|
|
52
|
+
query: mockQuery,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
describe("ClaudeSdkAdapter", () => {
|
|
56
|
+
let adapter: ClaudeSdkAdapter;
|
|
57
|
+
let config: Config;
|
|
58
|
+
let logger: pino.Logger;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
config = {
|
|
62
|
+
workers: { num: 2, max: 8 },
|
|
63
|
+
models: {
|
|
64
|
+
default: "claude-sonnet-4-5-20250929",
|
|
65
|
+
},
|
|
66
|
+
timeouts: { claude: 1200, test: 600, staleTask: 5400 },
|
|
67
|
+
retry: { maxRetries: 2 },
|
|
68
|
+
debug: false,
|
|
69
|
+
adaptiveEffort: false,
|
|
70
|
+
teams: { splitter: false, reviewer: false },
|
|
71
|
+
memorySync: false,
|
|
72
|
+
dashboard: { enabled: false, port: 7333, host: "localhost" },
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
logger = pino({ level: "silent" });
|
|
76
|
+
adapter = new ClaudeSdkAdapter(config, logger);
|
|
77
|
+
|
|
78
|
+
// モックをリセット
|
|
79
|
+
mockQuery.mockClear();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("call() - 成功レスポンス", async () => {
|
|
83
|
+
const request: ClaudeRequest = {
|
|
84
|
+
prompt: "Test prompt",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const response = await adapter.call(request);
|
|
88
|
+
|
|
89
|
+
expect(response.exitCode).toBe(0);
|
|
90
|
+
expect(response.result).toBe("Test response");
|
|
91
|
+
expect(response.model).toBe("claude-sonnet-4-5-20250929");
|
|
92
|
+
expect(response.effortLevel).toBe("medium");
|
|
93
|
+
expect(response.duration).toBeGreaterThanOrEqual(0);
|
|
94
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("call() - カスタムオプション", async () => {
|
|
98
|
+
const request: ClaudeRequest = {
|
|
99
|
+
prompt: "Test prompt",
|
|
100
|
+
model: "claude-opus-4-20250514",
|
|
101
|
+
effortLevel: "high",
|
|
102
|
+
systemPrompt: "Custom system prompt",
|
|
103
|
+
allowedTools: ["Bash", "Read"],
|
|
104
|
+
cwd: "/custom/path",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
await adapter.call(request);
|
|
108
|
+
|
|
109
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
110
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
111
|
+
{ prompt: string; options: { model?: string; systemPrompt?: string; allowedTools?: string[]; cwd?: string; env?: Record<string, string> } }
|
|
112
|
+
];
|
|
113
|
+
expect(callArgs[0]!.prompt).toBe("Test prompt");
|
|
114
|
+
expect(callArgs[0]!.options).toMatchObject({
|
|
115
|
+
model: "claude-opus-4-20250514",
|
|
116
|
+
systemPrompt: "Custom system prompt",
|
|
117
|
+
allowedTools: ["Bash", "Read"],
|
|
118
|
+
cwd: "/custom/path",
|
|
119
|
+
});
|
|
120
|
+
expect(callArgs[0]!.options.env?.CLAUDE_CODE_EFFORT_LEVEL).toBe("high");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("call() - エラーレスポンス", async () => {
|
|
124
|
+
// SDKモジュールを一時的に上書き
|
|
125
|
+
mockQuery.mockImplementationOnce(
|
|
126
|
+
(_params: { prompt: string; options?: unknown }): AsyncGenerator<MockSDKMessage, void, unknown> => {
|
|
127
|
+
return (async function* () {
|
|
128
|
+
yield {
|
|
129
|
+
type: "result",
|
|
130
|
+
subtype: "error_during_execution",
|
|
131
|
+
errors: ["Test error"],
|
|
132
|
+
} as MockSDKMessage;
|
|
133
|
+
})() as AsyncGenerator<MockSDKMessage, void, unknown>;
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const request: ClaudeRequest = {
|
|
138
|
+
prompt: "Test prompt",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await expect(adapter.call(request)).rejects.toThrow(ClaudeProviderError);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("call() - アシスタントメッセージエラー", async () => {
|
|
145
|
+
mockQuery.mockReturnValueOnce(
|
|
146
|
+
(async function* () {
|
|
147
|
+
yield {
|
|
148
|
+
type: "assistant",
|
|
149
|
+
message: {
|
|
150
|
+
model: "claude-sonnet-4-5-20250929",
|
|
151
|
+
content: [{ type: "text", text: "Error response" }],
|
|
152
|
+
},
|
|
153
|
+
error: "rate_limit",
|
|
154
|
+
} as MockSDKMessage;
|
|
155
|
+
yield {
|
|
156
|
+
type: "result",
|
|
157
|
+
subtype: "error_during_execution",
|
|
158
|
+
errors: ["Rate limit exceeded"],
|
|
159
|
+
} as MockSDKMessage;
|
|
160
|
+
})() as AsyncGenerator<MockSDKMessage, void, unknown>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const request: ClaudeRequest = {
|
|
164
|
+
prompt: "Test prompt",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await expect(adapter.call(request)).rejects.toThrow(ClaudeProviderError);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("call() - タイムアウト設定", async () => {
|
|
171
|
+
const request: ClaudeRequest = {
|
|
172
|
+
prompt: "Test prompt",
|
|
173
|
+
timeout: 5000,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
await adapter.call(request);
|
|
177
|
+
|
|
178
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
179
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
180
|
+
{ prompt: string; options: { abortController?: AbortController } }
|
|
181
|
+
];
|
|
182
|
+
expect(callArgs[0]!.options.abortController).toBeDefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("call() - JSON出力フォーマット", async () => {
|
|
186
|
+
const request: ClaudeRequest = {
|
|
187
|
+
prompt: "Test prompt",
|
|
188
|
+
outputFormat: "json",
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await adapter.call(request);
|
|
192
|
+
|
|
193
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
194
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
195
|
+
{ prompt: string; options: { outputFormat?: { type: string; schema: unknown } } }
|
|
196
|
+
];
|
|
197
|
+
expect(callArgs[0]!.options.outputFormat).toMatchObject({
|
|
198
|
+
type: "json_schema",
|
|
199
|
+
schema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
result: { type: "string" },
|
|
203
|
+
},
|
|
204
|
+
required: ["result"],
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("call() - パーミッションモード設定", async () => {
|
|
210
|
+
const requestBypass: ClaudeRequest = {
|
|
211
|
+
prompt: "Test prompt",
|
|
212
|
+
permissionMode: "bypassPermissions",
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
await adapter.call(requestBypass);
|
|
216
|
+
let callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
217
|
+
{ prompt: string; options: { permissionMode?: string } }
|
|
218
|
+
];
|
|
219
|
+
expect(callArgs[0]!.options.permissionMode).toBe("bypassPermissions");
|
|
220
|
+
|
|
221
|
+
mockQuery.mockClear();
|
|
222
|
+
|
|
223
|
+
const requestDefault: ClaudeRequest = {
|
|
224
|
+
prompt: "Test prompt",
|
|
225
|
+
permissionMode: "default",
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
await adapter.call(requestDefault);
|
|
229
|
+
callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
230
|
+
{ prompt: string; options: { permissionMode?: string } }
|
|
231
|
+
];
|
|
232
|
+
expect(callArgs[0]!.options.permissionMode).toBe("default");
|
|
233
|
+
|
|
234
|
+
mockQuery.mockClear();
|
|
235
|
+
|
|
236
|
+
const requestNoMode: ClaudeRequest = {
|
|
237
|
+
prompt: "Test prompt",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
await adapter.call(requestNoMode);
|
|
241
|
+
callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
242
|
+
{ prompt: string; options: { permissionMode?: string } }
|
|
243
|
+
];
|
|
244
|
+
expect(callArgs[0]!.options.permissionMode).toBe("bypassPermissions");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("call() - systemPrompt追記", async () => {
|
|
248
|
+
const request: ClaudeRequest = {
|
|
249
|
+
prompt: "Test prompt",
|
|
250
|
+
systemPrompt: "Base prompt",
|
|
251
|
+
appendSystemPrompt: "Additional instructions",
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await adapter.call(request);
|
|
255
|
+
|
|
256
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
257
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
258
|
+
{ prompt: string; options: { systemPrompt?: string } }
|
|
259
|
+
];
|
|
260
|
+
expect(callArgs[0]!.options.systemPrompt).toBe("Base prompt\n\nAdditional instructions");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("call() - resume指定", async () => {
|
|
264
|
+
const request: ClaudeRequest = {
|
|
265
|
+
prompt: "Test prompt",
|
|
266
|
+
resume: "session-id-123",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
await adapter.call(request);
|
|
270
|
+
|
|
271
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
272
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
273
|
+
{ prompt: string; options: { resume?: string } }
|
|
274
|
+
];
|
|
275
|
+
expect(callArgs[0]!.options.resume).toBe("session-id-123");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("call() - subagentsオプションが正しく渡される", async () => {
|
|
279
|
+
const request: ClaudeRequest = {
|
|
280
|
+
prompt: "Test prompt",
|
|
281
|
+
subagents: [
|
|
282
|
+
{ name: "analyzer", prompt: "Analyze code" },
|
|
283
|
+
{ name: "reviewer", prompt: "Review code", model: "claude-opus-4-20250514" },
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
await adapter.call(request);
|
|
288
|
+
|
|
289
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
290
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
291
|
+
{ prompt: string; options: { subagents?: Array<{ name: string; prompt: string; model?: string }> } }
|
|
292
|
+
];
|
|
293
|
+
expect(callArgs[0]!.options.subagents).toEqual([
|
|
294
|
+
{ name: "analyzer", prompt: "Analyze code" },
|
|
295
|
+
{ name: "reviewer", prompt: "Review code", model: "claude-opus-4-20250514" },
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("call() - subagentsが空の場合はオプションに含まれない", async () => {
|
|
300
|
+
const request: ClaudeRequest = {
|
|
301
|
+
prompt: "Test prompt",
|
|
302
|
+
subagents: [],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
await adapter.call(request);
|
|
306
|
+
|
|
307
|
+
const callArgs = mockQuery.mock.calls[0] as unknown as [
|
|
308
|
+
{ prompt: string; options: { subagents?: unknown } }
|
|
309
|
+
];
|
|
310
|
+
expect(callArgs[0]!.options.subagents).toBeUndefined();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("call() - 複数のアシスタントメッセージ", async () => {
|
|
314
|
+
mockQuery.mockReturnValueOnce(
|
|
315
|
+
(async function* () {
|
|
316
|
+
yield {
|
|
317
|
+
type: "assistant",
|
|
318
|
+
message: {
|
|
319
|
+
model: "claude-sonnet-4-5-20250929",
|
|
320
|
+
content: [{ type: "text", text: "First response" }],
|
|
321
|
+
},
|
|
322
|
+
} as MockSDKMessage;
|
|
323
|
+
yield {
|
|
324
|
+
type: "assistant",
|
|
325
|
+
message: {
|
|
326
|
+
model: "claude-sonnet-4-5-20250929",
|
|
327
|
+
content: [{ type: "text", text: "Second response" }],
|
|
328
|
+
},
|
|
329
|
+
} as MockSDKMessage;
|
|
330
|
+
yield {
|
|
331
|
+
type: "result",
|
|
332
|
+
subtype: "success",
|
|
333
|
+
result: "Final result",
|
|
334
|
+
} as MockSDKMessage;
|
|
335
|
+
})() as AsyncGenerator<MockSDKMessage, void, unknown>
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const request: ClaudeRequest = {
|
|
339
|
+
prompt: "Test prompt",
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const response = await adapter.call(request);
|
|
343
|
+
|
|
344
|
+
expect(response.exitCode).toBe(0);
|
|
345
|
+
expect(response.result).toBe("Final result");
|
|
346
|
+
});
|
|
347
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
getEffortLevel,
|
|
4
|
+
getAdaptiveEffortLevel,
|
|
5
|
+
getPhaseModel,
|
|
6
|
+
estimateTaskComplexity,
|
|
7
|
+
} from "../effort-strategy";
|
|
8
|
+
import type { Config } from "../../../shared/config";
|
|
9
|
+
import type { Task } from "../../../shared/types";
|
|
10
|
+
|
|
11
|
+
function makeTask(overrides: Partial<Task> = {}): Task {
|
|
12
|
+
return {
|
|
13
|
+
taskId: "task-1" as any,
|
|
14
|
+
title: "Test task",
|
|
15
|
+
description: "Short desc",
|
|
16
|
+
filesToModify: [],
|
|
17
|
+
dependsOn: [],
|
|
18
|
+
priority: 1,
|
|
19
|
+
status: "pending" as any,
|
|
20
|
+
retryCount: 0,
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("getEffortLevel", () => {
|
|
26
|
+
test("splitter returns high", () => {
|
|
27
|
+
expect(getEffortLevel("splitter")).toBe("high");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("tester returns medium", () => {
|
|
31
|
+
expect(getEffortLevel("tester")).toBe("medium");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("implementer returns medium", () => {
|
|
35
|
+
expect(getEffortLevel("implementer")).toBe("medium");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("reviewer returns high", () => {
|
|
39
|
+
expect(getEffortLevel("reviewer")).toBe("high");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("merge-resolver returns low", () => {
|
|
43
|
+
expect(getEffortLevel("merge-resolver")).toBe("low");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("getAdaptiveEffortLevel", () => {
|
|
48
|
+
const disabledConfig: Config = {
|
|
49
|
+
adaptiveEffort: false,
|
|
50
|
+
} as Config;
|
|
51
|
+
|
|
52
|
+
const enabledConfig: Config = {
|
|
53
|
+
adaptiveEffort: true,
|
|
54
|
+
} as Config;
|
|
55
|
+
|
|
56
|
+
describe("when adaptiveEffort is disabled", () => {
|
|
57
|
+
test("returns base effort for splitter with high complexity", () => {
|
|
58
|
+
const result = getAdaptiveEffortLevel("splitter", "high", disabledConfig);
|
|
59
|
+
expect(result).toBe("high");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("returns base effort for splitter with medium complexity", () => {
|
|
63
|
+
const result = getAdaptiveEffortLevel("splitter", "medium", disabledConfig);
|
|
64
|
+
expect(result).toBe("high");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("returns base effort for implementer with low complexity", () => {
|
|
68
|
+
const result = getAdaptiveEffortLevel("implementer", "low", disabledConfig);
|
|
69
|
+
expect(result).toBe("medium");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("when adaptiveEffort is enabled with high complexity", () => {
|
|
74
|
+
test("promotes implementer medium to high", () => {
|
|
75
|
+
const result = getAdaptiveEffortLevel("implementer", "high", enabledConfig);
|
|
76
|
+
expect(result).toBe("high");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("promotes tester medium to high", () => {
|
|
80
|
+
const result = getAdaptiveEffortLevel("tester", "high", enabledConfig);
|
|
81
|
+
expect(result).toBe("high");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("when adaptiveEffort is enabled with low complexity", () => {
|
|
86
|
+
test("demotes implementer medium to low", () => {
|
|
87
|
+
const result = getAdaptiveEffortLevel("implementer", "low", enabledConfig);
|
|
88
|
+
expect(result).toBe("low");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("demotes tester medium to low", () => {
|
|
92
|
+
const result = getAdaptiveEffortLevel("tester", "low", enabledConfig);
|
|
93
|
+
expect(result).toBe("low");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("when adaptiveEffort is enabled with medium complexity", () => {
|
|
98
|
+
test("keeps implementer at medium", () => {
|
|
99
|
+
const result = getAdaptiveEffortLevel("implementer", "medium", enabledConfig);
|
|
100
|
+
expect(result).toBe("medium");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("high/low base effort levels should not change", () => {
|
|
105
|
+
test("splitter (base=high) with low complexity remains high", () => {
|
|
106
|
+
const result = getAdaptiveEffortLevel("splitter", "low", enabledConfig);
|
|
107
|
+
expect(result).toBe("high");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("merge-resolver (base=low) with high complexity remains low", () => {
|
|
111
|
+
const result = getAdaptiveEffortLevel("merge-resolver", "high", enabledConfig);
|
|
112
|
+
expect(result).toBe("low");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("getPhaseModel", () => {
|
|
118
|
+
test("returns phase-specific model when configured", () => {
|
|
119
|
+
const config: Config = {
|
|
120
|
+
models: {
|
|
121
|
+
splitter: "opus",
|
|
122
|
+
tester: "sonnet",
|
|
123
|
+
implementer: "sonnet",
|
|
124
|
+
reviewer: "opus",
|
|
125
|
+
mergeResolver: "haiku",
|
|
126
|
+
},
|
|
127
|
+
} as Config;
|
|
128
|
+
|
|
129
|
+
expect(getPhaseModel("splitter", config)).toBe("opus");
|
|
130
|
+
expect(getPhaseModel("tester", config)).toBe("sonnet");
|
|
131
|
+
expect(getPhaseModel("implementer", config)).toBe("sonnet");
|
|
132
|
+
expect(getPhaseModel("reviewer", config)).toBe("opus");
|
|
133
|
+
expect(getPhaseModel("merge-resolver", config)).toBe("haiku");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("falls back to default model when phase model not configured", () => {
|
|
137
|
+
const config: Config = {
|
|
138
|
+
models: {
|
|
139
|
+
default: "sonnet",
|
|
140
|
+
},
|
|
141
|
+
} as Config;
|
|
142
|
+
|
|
143
|
+
expect(getPhaseModel("splitter", config)).toBe("sonnet");
|
|
144
|
+
expect(getPhaseModel("tester", config)).toBe("sonnet");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("returns undefined when no models configured", () => {
|
|
148
|
+
const config: Config = {
|
|
149
|
+
models: {},
|
|
150
|
+
} as Config;
|
|
151
|
+
|
|
152
|
+
expect(getPhaseModel("splitter", config)).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("merge-resolver defaults to haiku when not configured", () => {
|
|
156
|
+
const config: Config = {
|
|
157
|
+
models: {},
|
|
158
|
+
} as Config;
|
|
159
|
+
|
|
160
|
+
expect(getPhaseModel("merge-resolver", config)).toBe("haiku");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("estimateTaskComplexity", () => {
|
|
165
|
+
test("returns low for minimal task", () => {
|
|
166
|
+
const task = makeTask();
|
|
167
|
+
expect(estimateTaskComplexity(task)).toBe("low");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns low for single file, no deps, short description", () => {
|
|
171
|
+
const task = makeTask({ filesToModify: ["a.ts"], description: "fix bug" });
|
|
172
|
+
expect(estimateTaskComplexity(task)).toBe("low");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("returns medium for 3 files, 1 dep, medium description", () => {
|
|
176
|
+
const task = makeTask({
|
|
177
|
+
filesToModify: ["a.ts", "b.ts", "c.ts"],
|
|
178
|
+
dependsOn: ["task-0" as any],
|
|
179
|
+
description: "a".repeat(200),
|
|
180
|
+
});
|
|
181
|
+
// score: 2 (files) + 1 (dep) + 1 (desc) = 4 → medium
|
|
182
|
+
expect(estimateTaskComplexity(task)).toBe("medium");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("returns high for 5+ files, 3+ deps, long description", () => {
|
|
186
|
+
const task = makeTask({
|
|
187
|
+
filesToModify: ["a.ts", "b.ts", "c.ts", "d.ts", "e.ts"],
|
|
188
|
+
dependsOn: ["t1", "t2", "t3"] as any[],
|
|
189
|
+
description: "a".repeat(500),
|
|
190
|
+
});
|
|
191
|
+
// score: 3 (files) + 2 (deps) + 2 (desc) = 7 → high
|
|
192
|
+
expect(estimateTaskComplexity(task)).toBe("high");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("high files alone can push to medium", () => {
|
|
196
|
+
const task = makeTask({
|
|
197
|
+
filesToModify: ["a.ts", "b.ts", "c.ts", "d.ts", "e.ts"],
|
|
198
|
+
});
|
|
199
|
+
// score: 3 → medium
|
|
200
|
+
expect(estimateTaskComplexity(task)).toBe("medium");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("combination of medium factors reaches high", () => {
|
|
204
|
+
const task = makeTask({
|
|
205
|
+
filesToModify: ["a.ts", "b.ts", "c.ts"],
|
|
206
|
+
dependsOn: ["t1", "t2", "t3"] as any[],
|
|
207
|
+
description: "a".repeat(200),
|
|
208
|
+
});
|
|
209
|
+
// score: 2 + 2 + 1 = 5 → high
|
|
210
|
+
expect(estimateTaskComplexity(task)).toBe("high");
|
|
211
|
+
});
|
|
212
|
+
});
|