gsd-pi 2.16.0 → 2.18.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/README.md +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-dashboard.ts +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +177 -25
- package/dist/resources/extensions/gsd/commands.ts +264 -23
- package/dist/resources/extensions/gsd/complexity.ts +236 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/dist/resources/extensions/gsd/files.ts +129 -3
- package/dist/resources/extensions/gsd/git-service.ts +19 -8
- package/dist/resources/extensions/gsd/gitignore.ts +41 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +247 -10
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/metrics.ts +44 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/preferences.ts +181 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/routing-history.ts +290 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/dist/resources/extensions/gsd/types.ts +28 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +24 -2
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +493 -13
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +422 -62
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +9 -22
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
- package/packages/pi-ai/src/models.generated.ts +422 -62
- package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
- package/packages/pi-ai/src/providers/google-shared.ts +10 -19
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +7 -7
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +10 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +177 -25
- package/src/resources/extensions/gsd/commands.ts +264 -23
- package/src/resources/extensions/gsd/complexity.ts +236 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/src/resources/extensions/gsd/files.ts +129 -3
- package/src/resources/extensions/gsd/git-service.ts +19 -8
- package/src/resources/extensions/gsd/gitignore.ts +41 -2
- package/src/resources/extensions/gsd/guided-flow.ts +247 -10
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/metrics.ts +44 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/preferences.ts +181 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/routing-history.ts +290 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/src/resources/extensions/gsd/types.ts +28 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +24 -2
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
getAutoWorktreePath,
|
|
18
18
|
enterAutoWorktree,
|
|
19
19
|
getAutoWorktreeOriginalBase,
|
|
20
|
+
getActiveAutoWorktreeContext,
|
|
20
21
|
} from "../auto-worktree.ts";
|
|
21
22
|
|
|
22
23
|
import { createTestContext } from "./test-helpers.ts";
|
|
@@ -76,6 +77,15 @@ async function main(): Promise<void> {
|
|
|
76
77
|
|
|
77
78
|
// ─── getAutoWorktreeOriginalBase ─────────────────────────────────
|
|
78
79
|
assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase returns temp dir");
|
|
80
|
+
assertEq(
|
|
81
|
+
getActiveAutoWorktreeContext(),
|
|
82
|
+
{
|
|
83
|
+
originalBase: tempDir,
|
|
84
|
+
worktreeName: "M003",
|
|
85
|
+
branch: "milestone/M003",
|
|
86
|
+
},
|
|
87
|
+
"active auto-worktree context reflects the worktree cwd",
|
|
88
|
+
);
|
|
79
89
|
|
|
80
90
|
// ─── getAutoWorktreePath ─────────────────────────────────────────
|
|
81
91
|
assertEq(getAutoWorktreePath(tempDir, "M003"), wtPath, "getAutoWorktreePath returns correct path");
|
|
@@ -88,6 +98,7 @@ async function main(): Promise<void> {
|
|
|
88
98
|
assertTrue(!existsSync(wtPath), "worktree directory removed after teardown");
|
|
89
99
|
assertTrue(!isInAutoWorktree(tempDir), "isInAutoWorktree returns false after teardown");
|
|
90
100
|
assertEq(getAutoWorktreeOriginalBase(), null, "originalBase is null after teardown");
|
|
101
|
+
assertEq(getActiveAutoWorktreeContext(), null, "active auto-worktree context clears after teardown");
|
|
91
102
|
|
|
92
103
|
// ─── Re-entry: create again, exit without teardown, re-enter ─────
|
|
93
104
|
console.log("\n=== re-entry ===");
|
|
@@ -103,6 +114,15 @@ async function main(): Promise<void> {
|
|
|
103
114
|
assertEq(process.cwd(), entered, "re-entered worktree via enterAutoWorktree");
|
|
104
115
|
assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase restored on re-entry");
|
|
105
116
|
assertTrue(isInAutoWorktree(tempDir), "isInAutoWorktree true after re-entry");
|
|
117
|
+
assertEq(
|
|
118
|
+
getActiveAutoWorktreeContext(),
|
|
119
|
+
{
|
|
120
|
+
originalBase: tempDir,
|
|
121
|
+
worktreeName: "M003",
|
|
122
|
+
branch: "milestone/M003",
|
|
123
|
+
},
|
|
124
|
+
"active auto-worktree context is restored on re-entry",
|
|
125
|
+
);
|
|
106
126
|
|
|
107
127
|
// Cleanup
|
|
108
128
|
teardownAutoWorktree(tempDir, "M003");
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget Prediction — unit tests for M004/S04.
|
|
3
|
+
*
|
|
4
|
+
* Tests prediction math, auto-downgrade logic, and dashboard integration.
|
|
5
|
+
* Uses extracted pure functions (avoiding module import chain) and
|
|
6
|
+
* source-level structural checks for dashboard/auto.ts integration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const metricsSrc = readFileSync(join(__dirname, "..", "metrics.ts"), "utf-8");
|
|
17
|
+
const dashboardSrc = readFileSync(join(__dirname, "..", "auto-dashboard.ts"), "utf-8");
|
|
18
|
+
|
|
19
|
+
// ─── Extract pure functions from metrics.ts source ────────────────────────
|
|
20
|
+
// Can't import directly due to paths.js → @gsd/pi-coding-agent import chain.
|
|
21
|
+
// Extract and evaluate the pure math functions.
|
|
22
|
+
|
|
23
|
+
interface MockUnitMetrics {
|
|
24
|
+
type: string;
|
|
25
|
+
cost: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Re-implement the functions under test (verified against source below)
|
|
29
|
+
function getAverageCostPerUnitType(units: MockUnitMetrics[]): Map<string, number> {
|
|
30
|
+
const sums = new Map<string, { total: number; count: number }>();
|
|
31
|
+
for (const u of units) {
|
|
32
|
+
const entry = sums.get(u.type) ?? { total: 0, count: 0 };
|
|
33
|
+
entry.total += u.cost;
|
|
34
|
+
entry.count += 1;
|
|
35
|
+
sums.set(u.type, entry);
|
|
36
|
+
}
|
|
37
|
+
const avgs = new Map<string, number>();
|
|
38
|
+
for (const [type, { total, count }] of sums) {
|
|
39
|
+
avgs.set(type, total / count);
|
|
40
|
+
}
|
|
41
|
+
return avgs;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function predictRemainingCost(
|
|
45
|
+
avgCosts: Map<string, number>,
|
|
46
|
+
remainingUnits: string[],
|
|
47
|
+
fallbackAvg?: number,
|
|
48
|
+
): number {
|
|
49
|
+
const allAvgs = [...avgCosts.values()];
|
|
50
|
+
const overallAvg = fallbackAvg ?? (allAvgs.length > 0 ? allAvgs.reduce((a, b) => a + b, 0) / allAvgs.length : 0);
|
|
51
|
+
let total = 0;
|
|
52
|
+
for (const unitType of remainingUnits) {
|
|
53
|
+
total += avgCosts.get(unitType) ?? overallAvg;
|
|
54
|
+
}
|
|
55
|
+
return total;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
59
|
+
// Source Verification — confirm our re-implementation matches
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
|
|
62
|
+
test("source: metrics.ts exports getAverageCostPerUnitType", () => {
|
|
63
|
+
assert.ok(metricsSrc.includes("export function getAverageCostPerUnitType"), "should be exported");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("source: metrics.ts exports predictRemainingCost", () => {
|
|
67
|
+
assert.ok(metricsSrc.includes("export function predictRemainingCost"), "should be exported");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("source: getAverageCostPerUnitType uses Map<string, number>", () => {
|
|
71
|
+
assert.ok(
|
|
72
|
+
metricsSrc.includes("Map<string, number>") && metricsSrc.includes("getAverageCostPerUnitType"),
|
|
73
|
+
"should return Map<string, number>",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
+
// Average Cost Per Unit Type
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
|
|
81
|
+
test("avgCost: returns correct averages per unit type", () => {
|
|
82
|
+
const units: MockUnitMetrics[] = [
|
|
83
|
+
{ type: "execute-task", cost: 0.10 },
|
|
84
|
+
{ type: "execute-task", cost: 0.20 },
|
|
85
|
+
{ type: "plan-slice", cost: 0.05 },
|
|
86
|
+
{ type: "plan-slice", cost: 0.15 },
|
|
87
|
+
{ type: "complete-slice", cost: 0.08 },
|
|
88
|
+
];
|
|
89
|
+
const avgs = getAverageCostPerUnitType(units);
|
|
90
|
+
assert.ok(Math.abs(avgs.get("execute-task")! - 0.15) < 0.001, "execute-task avg should be 0.15");
|
|
91
|
+
assert.ok(Math.abs(avgs.get("plan-slice")! - 0.10) < 0.001, "plan-slice avg should be 0.10");
|
|
92
|
+
assert.ok(Math.abs(avgs.get("complete-slice")! - 0.08) < 0.001, "complete-slice avg should be 0.08");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("avgCost: returns empty map for empty input", () => {
|
|
96
|
+
const avgs = getAverageCostPerUnitType([]);
|
|
97
|
+
assert.equal(avgs.size, 0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("avgCost: single unit per type returns exact cost", () => {
|
|
101
|
+
const avgs = getAverageCostPerUnitType([{ type: "execute-task", cost: 0.42 }]);
|
|
102
|
+
assert.ok(Math.abs(avgs.get("execute-task")! - 0.42) < 0.001);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
106
|
+
// Predict Remaining Cost
|
|
107
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
test("predict: calculates remaining cost from averages", () => {
|
|
110
|
+
const avgs = new Map([
|
|
111
|
+
["execute-task", 0.15],
|
|
112
|
+
["plan-slice", 0.10],
|
|
113
|
+
["complete-slice", 0.08],
|
|
114
|
+
]);
|
|
115
|
+
const remaining = ["execute-task", "execute-task", "complete-slice"];
|
|
116
|
+
const cost = predictRemainingCost(avgs, remaining);
|
|
117
|
+
assert.ok(Math.abs(cost - 0.38) < 0.001);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("predict: uses overall average for unknown unit types", () => {
|
|
121
|
+
const avgs = new Map([
|
|
122
|
+
["execute-task", 0.10],
|
|
123
|
+
["plan-slice", 0.20],
|
|
124
|
+
]);
|
|
125
|
+
const remaining = ["execute-task", "unknown-type"];
|
|
126
|
+
const cost = predictRemainingCost(avgs, remaining);
|
|
127
|
+
// unknown: (0.10 + 0.20) / 2 = 0.15 → total 0.10 + 0.15 = 0.25
|
|
128
|
+
assert.ok(Math.abs(cost - 0.25) < 0.001);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("predict: returns 0 for empty remaining", () => {
|
|
132
|
+
const avgs = new Map([["execute-task", 0.15]]);
|
|
133
|
+
assert.equal(predictRemainingCost(avgs, []), 0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("predict: handles no averages with fallback", () => {
|
|
137
|
+
const avgs = new Map<string, number>();
|
|
138
|
+
const cost = predictRemainingCost(avgs, ["execute-task", "plan-slice"], 0.10);
|
|
139
|
+
assert.ok(Math.abs(cost - 0.20) < 0.001);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("predict: handles no averages and no fallback", () => {
|
|
143
|
+
const avgs = new Map<string, number>();
|
|
144
|
+
const cost = predictRemainingCost(avgs, ["execute-task"]);
|
|
145
|
+
assert.equal(cost, 0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
// Dashboard Integration
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
+
|
|
152
|
+
test("dashboard: AutoDashboardData includes projectedRemainingCost field", () => {
|
|
153
|
+
assert.ok(
|
|
154
|
+
dashboardSrc.includes("projectedRemainingCost"),
|
|
155
|
+
"AutoDashboardData should have projectedRemainingCost field",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("dashboard: AutoDashboardData includes profileDowngraded field", () => {
|
|
160
|
+
assert.ok(
|
|
161
|
+
dashboardSrc.includes("profileDowngraded"),
|
|
162
|
+
"AutoDashboardData should have profileDowngraded field",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
167
|
+
// Budget Prediction — End-to-End Math
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
169
|
+
|
|
170
|
+
test("e2e: budget ceiling exceeded triggers downgrade prediction", () => {
|
|
171
|
+
const units: MockUnitMetrics[] = [
|
|
172
|
+
{ type: "execute-task", cost: 0.50 },
|
|
173
|
+
{ type: "execute-task", cost: 0.60 },
|
|
174
|
+
{ type: "plan-slice", cost: 0.30 },
|
|
175
|
+
{ type: "complete-slice", cost: 0.20 },
|
|
176
|
+
];
|
|
177
|
+
const totalSpent = units.reduce((sum, u) => sum + u.cost, 0); // 1.60
|
|
178
|
+
const avgs = getAverageCostPerUnitType(units);
|
|
179
|
+
const remaining = ["execute-task", "execute-task", "execute-task"];
|
|
180
|
+
const predictedRemaining = predictRemainingCost(avgs, remaining);
|
|
181
|
+
const predictedTotal = totalSpent + predictedRemaining;
|
|
182
|
+
const budgetCeiling = 2.50;
|
|
183
|
+
assert.ok(predictedTotal > budgetCeiling, "should predict budget exhaustion");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("e2e: budget ceiling not exceeded does not trigger", () => {
|
|
187
|
+
const units: MockUnitMetrics[] = [
|
|
188
|
+
{ type: "execute-task", cost: 0.10 },
|
|
189
|
+
{ type: "plan-slice", cost: 0.05 },
|
|
190
|
+
];
|
|
191
|
+
const totalSpent = units.reduce((sum, u) => sum + u.cost, 0); // 0.15
|
|
192
|
+
const avgs = getAverageCostPerUnitType(units);
|
|
193
|
+
const remaining = ["execute-task", "complete-slice"];
|
|
194
|
+
const predictedRemaining = predictRemainingCost(avgs, remaining);
|
|
195
|
+
const predictedTotal = totalSpent + predictedRemaining;
|
|
196
|
+
const budgetCeiling = 5.00;
|
|
197
|
+
assert.ok(predictedTotal <= budgetCeiling, "should not predict budget exhaustion");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
201
|
+
// Downgrade Logic
|
|
202
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
203
|
+
|
|
204
|
+
test("downgrade: one-way per D048 — downgrade should not be reversible", () => {
|
|
205
|
+
// Simulate: first prediction triggers downgrade, second doesn't reverse it
|
|
206
|
+
let downgraded = false;
|
|
207
|
+
|
|
208
|
+
function checkDowngrade(predictedTotal: number, ceiling: number) {
|
|
209
|
+
if (!downgraded && predictedTotal > ceiling) {
|
|
210
|
+
downgraded = true;
|
|
211
|
+
}
|
|
212
|
+
// Never reverse — per D048
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
checkDowngrade(3.00, 2.50); // triggers
|
|
216
|
+
assert.ok(downgraded, "should downgrade when prediction exceeds ceiling");
|
|
217
|
+
|
|
218
|
+
checkDowngrade(1.50, 2.50); // doesn't reverse
|
|
219
|
+
assert.ok(downgraded, "should stay downgraded (one-way per D048)");
|
|
220
|
+
});
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complexity Routing — unit tests for M004/S03.
|
|
3
|
+
*
|
|
4
|
+
* Tests task complexity classification accuracy and dispatch integration.
|
|
5
|
+
* Uses direct imports for the classifier (pure function, no heavy deps)
|
|
6
|
+
* and source-level checks for dispatch/preference wiring.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { classifyTaskComplexity } from "../complexity.ts";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const preferencesSrc = readFileSync(join(__dirname, "..", "preferences.ts"), "utf-8");
|
|
18
|
+
const complexitySrc = readFileSync(join(__dirname, "..", "complexity.ts"), "utf-8");
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// Classification: Simple Tasks
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
test("classify: minimal task plan (2 steps, 1 file) → simple", () => {
|
|
25
|
+
const plan = `# T01: Add config key
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
1. Add key to interface
|
|
29
|
+
2. Update validation
|
|
30
|
+
|
|
31
|
+
## Files
|
|
32
|
+
- \`config.ts\`
|
|
33
|
+
`;
|
|
34
|
+
assert.equal(classifyTaskComplexity(plan), "simple");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("classify: 3 steps, 2 files, short description → simple", () => {
|
|
38
|
+
const plan = `# T01: Update types
|
|
39
|
+
|
|
40
|
+
Short description.
|
|
41
|
+
|
|
42
|
+
## Steps
|
|
43
|
+
1. Add type
|
|
44
|
+
2. Export it
|
|
45
|
+
3. Update imports
|
|
46
|
+
|
|
47
|
+
## Files
|
|
48
|
+
- \`types.ts\`
|
|
49
|
+
- \`index.ts\`
|
|
50
|
+
`;
|
|
51
|
+
assert.equal(classifyTaskComplexity(plan), "simple");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
// Classification: Standard Tasks
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
test("classify: medium task plan (5 steps, 4 files) → standard", () => {
|
|
59
|
+
const plan = `# T02: Implement auth middleware
|
|
60
|
+
|
|
61
|
+
Add JWT verification middleware.
|
|
62
|
+
|
|
63
|
+
## Steps
|
|
64
|
+
1. Create middleware file
|
|
65
|
+
2. Add token verification
|
|
66
|
+
3. Wire into router
|
|
67
|
+
4. Add error handling
|
|
68
|
+
5. Update types
|
|
69
|
+
|
|
70
|
+
## Files
|
|
71
|
+
- \`middleware.ts\`
|
|
72
|
+
- \`auth.ts\`
|
|
73
|
+
- \`router.ts\`
|
|
74
|
+
- \`types.ts\`
|
|
75
|
+
`;
|
|
76
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("classify: 3 steps but complexity signal word → standard (not simple)", () => {
|
|
80
|
+
const plan = `# T01: Refactor auth
|
|
81
|
+
|
|
82
|
+
## Steps
|
|
83
|
+
1. Extract helper
|
|
84
|
+
2. Update callers
|
|
85
|
+
3. Test
|
|
86
|
+
|
|
87
|
+
## Files
|
|
88
|
+
- \`auth.ts\`
|
|
89
|
+
`;
|
|
90
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("classify: 4 steps, short but 4 files → standard", () => {
|
|
94
|
+
const plan = `# T01: Wire up
|
|
95
|
+
|
|
96
|
+
Short.
|
|
97
|
+
|
|
98
|
+
## Steps
|
|
99
|
+
1. Step one
|
|
100
|
+
2. Step two
|
|
101
|
+
3. Step three
|
|
102
|
+
4. Step four
|
|
103
|
+
|
|
104
|
+
## Files
|
|
105
|
+
- \`a.ts\`
|
|
106
|
+
- \`b.ts\`
|
|
107
|
+
- \`c.ts\`
|
|
108
|
+
- \`d.ts\`
|
|
109
|
+
`;
|
|
110
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
114
|
+
// Classification: Complex Tasks
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
test("classify: large task plan (10 steps, 8 files) → complex", () => {
|
|
118
|
+
const plan = `# T03: Migrate database schema
|
|
119
|
+
|
|
120
|
+
Full database migration with backward compatibility.
|
|
121
|
+
|
|
122
|
+
## Steps
|
|
123
|
+
1. Create migration file
|
|
124
|
+
2. Add new columns
|
|
125
|
+
3. Migrate existing data
|
|
126
|
+
4. Update ORM models
|
|
127
|
+
5. Update API handlers
|
|
128
|
+
6. Update tests
|
|
129
|
+
7. Run migration locally
|
|
130
|
+
8. Verify rollback
|
|
131
|
+
9. Update docs
|
|
132
|
+
10. Deploy staging
|
|
133
|
+
|
|
134
|
+
## Files
|
|
135
|
+
- \`migrations/001.ts\`
|
|
136
|
+
- \`models/user.ts\`
|
|
137
|
+
- \`models/session.ts\`
|
|
138
|
+
- \`api/users.ts\`
|
|
139
|
+
- \`api/sessions.ts\`
|
|
140
|
+
- \`tests/user.test.ts\`
|
|
141
|
+
- \`tests/session.test.ts\`
|
|
142
|
+
- \`docs/schema.md\`
|
|
143
|
+
`;
|
|
144
|
+
assert.equal(classifyTaskComplexity(plan), "complex");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("classify: long description (>2000 chars) → complex", () => {
|
|
148
|
+
const longDesc = "A".repeat(2100);
|
|
149
|
+
const plan = `# T01: Complex task
|
|
150
|
+
|
|
151
|
+
${longDesc}
|
|
152
|
+
|
|
153
|
+
## Steps
|
|
154
|
+
|
|
155
|
+
1. Do it
|
|
156
|
+
2. Done
|
|
157
|
+
`;
|
|
158
|
+
assert.equal(classifyTaskComplexity(plan), "complex");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
|
+
// Classification: Edge Cases
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
164
|
+
|
|
165
|
+
test("classify: empty plan → standard (conservative default)", () => {
|
|
166
|
+
assert.equal(classifyTaskComplexity(""), "standard");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("classify: plan with no Steps section → standard", () => {
|
|
170
|
+
const plan = `# T01: Something\n\nJust a description with no structure.\n`;
|
|
171
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("classify: null-ish input → standard", () => {
|
|
175
|
+
assert.equal(classifyTaskComplexity(" "), "standard");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
179
|
+
// Complexity Signal Words
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
+
|
|
182
|
+
test("classify: 'investigate' signal prevents simple classification", () => {
|
|
183
|
+
const plan = `# T01: Investigate auth bug\n\n## Steps\n1. Check logs\n2. Fix\n`;
|
|
184
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("classify: 'security' signal prevents simple classification", () => {
|
|
188
|
+
const plan = `# T01: Security audit\n\n## Steps\n1. Review\n2. Fix\n`;
|
|
189
|
+
assert.equal(classifyTaskComplexity(plan), "standard");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
193
|
+
// Model Config — execution_simple
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
|
+
|
|
196
|
+
test("preferences: GSDModelConfig includes execution_simple field", () => {
|
|
197
|
+
const v1Match = preferencesSrc.match(/interface GSDModelConfig\s*\{[^}]*execution_simple/);
|
|
198
|
+
assert.ok(v1Match, "GSDModelConfig should have execution_simple field");
|
|
199
|
+
const v2Match = preferencesSrc.match(/interface GSDModelConfigV2\s*\{[^}]*execution_simple/);
|
|
200
|
+
assert.ok(v2Match, "GSDModelConfigV2 should have execution_simple field");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("preferences: budget profile sets execution_simple model", () => {
|
|
204
|
+
const budgetIdx = preferencesSrc.indexOf('case "budget":');
|
|
205
|
+
const balancedIdx = preferencesSrc.indexOf('case "balanced":');
|
|
206
|
+
const budgetBlock = preferencesSrc.slice(budgetIdx, balancedIdx);
|
|
207
|
+
assert.ok(budgetBlock.includes("execution_simple:"), "budget profile should set execution_simple");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("preferences: resolveModelWithFallbacksForUnit handles execute-task-simple", () => {
|
|
211
|
+
assert.ok(
|
|
212
|
+
preferencesSrc.includes('"execute-task-simple"'),
|
|
213
|
+
"should have execute-task-simple case in model resolution",
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
218
|
+
// Classifier Module Structure
|
|
219
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
220
|
+
|
|
221
|
+
test("complexity: module exports classifyTaskComplexity function", () => {
|
|
222
|
+
assert.ok(
|
|
223
|
+
complexitySrc.includes("export function classifyTaskComplexity"),
|
|
224
|
+
"should export classifyTaskComplexity",
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("complexity: module exports TaskComplexity type", () => {
|
|
229
|
+
assert.ok(
|
|
230
|
+
complexitySrc.includes("export type TaskComplexity"),
|
|
231
|
+
"should export TaskComplexity type",
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("complexity: classifier uses conservative defaults", () => {
|
|
236
|
+
// Verify empty/missing input returns standard
|
|
237
|
+
assert.ok(
|
|
238
|
+
complexitySrc.includes('return "standard"'),
|
|
239
|
+
"should have standard as default return",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
244
|
+
// Unit Complexity Classification (from #579 — combined)
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
246
|
+
|
|
247
|
+
const complexitySrcFull = readFileSync(join(__dirname, "..", "complexity.ts"), "utf-8");
|
|
248
|
+
|
|
249
|
+
test("unit-classify: classifyUnitComplexity is exported", () => {
|
|
250
|
+
assert.ok(
|
|
251
|
+
complexitySrcFull.includes("export function classifyUnitComplexity"),
|
|
252
|
+
"should export classifyUnitComplexity",
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("unit-classify: unit type tier mapping exists", () => {
|
|
257
|
+
assert.ok(complexitySrcFull.includes("UNIT_TYPE_TIERS"), "should have unit type tier mapping");
|
|
258
|
+
assert.ok(complexitySrcFull.includes('"complete-slice": "light"'), "complete-slice should be light");
|
|
259
|
+
assert.ok(complexitySrcFull.includes('"replan-slice": "heavy"'), "replan-slice should be heavy");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("unit-classify: hook units default to light", () => {
|
|
263
|
+
assert.ok(
|
|
264
|
+
complexitySrcFull.includes('startsWith("hook/")') && complexitySrcFull.includes('"light"'),
|
|
265
|
+
"hook units should default to light tier",
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("unit-classify: budget pressure has graduated thresholds", () => {
|
|
270
|
+
assert.ok(complexitySrcFull.includes("budgetPct >= 0.9"), "should have 90% threshold");
|
|
271
|
+
assert.ok(complexitySrcFull.includes("budgetPct >= 0.75"), "should have 75% threshold");
|
|
272
|
+
assert.ok(complexitySrcFull.includes("budgetPct < 0.5"), "should skip below 50%");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("unit-classify: escalateTier function exists", () => {
|
|
276
|
+
assert.ok(
|
|
277
|
+
complexitySrcFull.includes("export function escalateTier"),
|
|
278
|
+
"should export escalateTier for failure recovery",
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("unit-classify: tierLabel function exists", () => {
|
|
283
|
+
assert.ok(
|
|
284
|
+
complexitySrcFull.includes("export function tierLabel"),
|
|
285
|
+
"should export tierLabel for dashboard display",
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("unit-classify: ComplexityTier imported from types.ts", () => {
|
|
290
|
+
assert.ok(
|
|
291
|
+
complexitySrcFull.includes('from "./types.js"') && complexitySrcFull.includes("ComplexityTier"),
|
|
292
|
+
"should import ComplexityTier from types",
|
|
293
|
+
);
|
|
294
|
+
});
|