gsd-pi 2.60.0-dev.2580e65 → 2.60.0-dev.d9052f5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +4 -7
  2. package/dist/resources/extensions/gsd/auto/phases.js +7 -15
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -21
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +3 -6
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +9 -58
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +2 -3
  7. package/dist/resources/extensions/gsd/auto-prompts.js +20 -36
  8. package/dist/resources/extensions/gsd/auto-recovery.js +18 -37
  9. package/dist/resources/extensions/gsd/auto-start.js +5 -9
  10. package/dist/resources/extensions/gsd/auto-timers.js +5 -11
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +3 -5
  12. package/dist/resources/extensions/gsd/auto-verification.js +2 -3
  13. package/dist/resources/extensions/gsd/auto-worktree.js +55 -120
  14. package/dist/resources/extensions/gsd/auto.js +17 -39
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -6
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +10 -4
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +1 -2
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +0 -7
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -11
  21. package/dist/resources/extensions/gsd/commands/catalog.js +0 -2
  22. package/dist/resources/extensions/gsd/commands-codebase.js +21 -48
  23. package/dist/resources/extensions/gsd/commands-inspect.js +1 -2
  24. package/dist/resources/extensions/gsd/commands-maintenance.js +19 -32
  25. package/dist/resources/extensions/gsd/complexity-classifier.js +4 -8
  26. package/dist/resources/extensions/gsd/custom-verification.js +2 -3
  27. package/dist/resources/extensions/gsd/gsd-db.js +13 -33
  28. package/dist/resources/extensions/gsd/guided-flow.js +9 -19
  29. package/dist/resources/extensions/gsd/init-wizard.js +0 -12
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +9 -11
  31. package/dist/resources/extensions/gsd/md-importer.js +4 -5
  32. package/dist/resources/extensions/gsd/milestone-actions.js +2 -3
  33. package/dist/resources/extensions/gsd/milestone-ids.js +1 -2
  34. package/dist/resources/extensions/gsd/model-router.js +121 -156
  35. package/dist/resources/extensions/gsd/parallel-merge.js +3 -5
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +14 -26
  37. package/dist/resources/extensions/gsd/preferences-types.js +0 -1
  38. package/dist/resources/extensions/gsd/preferences-validation.js +0 -45
  39. package/dist/resources/extensions/gsd/preferences.js +3 -15
  40. package/dist/resources/extensions/gsd/prompt-loader.js +2 -3
  41. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  42. package/dist/resources/extensions/gsd/rule-registry.js +6 -7
  43. package/dist/resources/extensions/gsd/safe-fs.js +8 -6
  44. package/dist/resources/extensions/gsd/tools/complete-milestone.js +2 -3
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -3
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -3
  47. package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -3
  48. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -3
  49. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -2
  50. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  51. package/dist/resources/extensions/gsd/tools/reopen-slice.js +1 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-task.js +1 -2
  53. package/dist/resources/extensions/gsd/tools/replan-slice.js +1 -2
  54. package/dist/resources/extensions/gsd/tools/validate-milestone.js +1 -2
  55. package/dist/resources/extensions/gsd/triage-resolution.js +4 -11
  56. package/dist/resources/extensions/gsd/workflow-events.js +1 -2
  57. package/dist/resources/extensions/gsd/workflow-logger.js +4 -37
  58. package/dist/resources/extensions/gsd/workflow-migration.js +12 -14
  59. package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
  60. package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
  61. package/dist/resources/extensions/gsd/worktree-manager.js +14 -26
  62. package/dist/resources/extensions/shared/interview-ui.js +1 -3
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  65. package/dist/web/standalone/.next/build-manifest.json +2 -2
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  68. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.html +1 -1
  84. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  91. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  92. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  93. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  94. package/package.json +1 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/loader.js +0 -5
  97. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -2
  99. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/runner.js +0 -16
  101. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +0 -26
  103. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/lsp/config.js +1 -6
  107. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
  109. package/packages/pi-coding-agent/src/core/extensions/loader.ts +0 -6
  110. package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -19
  111. package/packages/pi-coding-agent/src/core/extensions/types.ts +0 -26
  112. package/packages/pi-coding-agent/src/core/lsp/config.ts +1 -7
  113. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  114. package/src/resources/extensions/ask-user-questions.ts +3 -7
  115. package/src/resources/extensions/gsd/auto/phases.ts +7 -17
  116. package/src/resources/extensions/gsd/auto-dashboard.ts +8 -22
  117. package/src/resources/extensions/gsd/auto-dispatch.ts +3 -7
  118. package/src/resources/extensions/gsd/auto-model-selection.ts +15 -77
  119. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
  120. package/src/resources/extensions/gsd/auto-prompts.ts +20 -37
  121. package/src/resources/extensions/gsd/auto-recovery.ts +18 -38
  122. package/src/resources/extensions/gsd/auto-start.ts +9 -10
  123. package/src/resources/extensions/gsd/auto-timers.ts +5 -12
  124. package/src/resources/extensions/gsd/auto-unit-closeout.ts +2 -6
  125. package/src/resources/extensions/gsd/auto-verification.ts +6 -3
  126. package/src/resources/extensions/gsd/auto-worktree.ts +55 -121
  127. package/src/resources/extensions/gsd/auto.ts +17 -40
  128. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -4
  129. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
  130. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -4
  131. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +1 -2
  132. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +0 -8
  133. package/src/resources/extensions/gsd/bootstrap/system-context.ts +10 -11
  134. package/src/resources/extensions/gsd/commands/catalog.ts +0 -2
  135. package/src/resources/extensions/gsd/commands-codebase.ts +20 -52
  136. package/src/resources/extensions/gsd/commands-inspect.ts +1 -2
  137. package/src/resources/extensions/gsd/commands-maintenance.ts +19 -28
  138. package/src/resources/extensions/gsd/complexity-classifier.ts +4 -9
  139. package/src/resources/extensions/gsd/custom-verification.ts +2 -3
  140. package/src/resources/extensions/gsd/gsd-db.ts +14 -12
  141. package/src/resources/extensions/gsd/guided-flow.ts +8 -9
  142. package/src/resources/extensions/gsd/init-wizard.ts +0 -12
  143. package/src/resources/extensions/gsd/markdown-renderer.ts +17 -11
  144. package/src/resources/extensions/gsd/md-importer.ts +4 -5
  145. package/src/resources/extensions/gsd/milestone-actions.ts +2 -3
  146. package/src/resources/extensions/gsd/milestone-ids.ts +1 -2
  147. package/src/resources/extensions/gsd/model-router.ts +173 -199
  148. package/src/resources/extensions/gsd/parallel-merge.ts +3 -5
  149. package/src/resources/extensions/gsd/parallel-orchestrator.ts +14 -18
  150. package/src/resources/extensions/gsd/preferences-types.ts +0 -13
  151. package/src/resources/extensions/gsd/preferences-validation.ts +0 -45
  152. package/src/resources/extensions/gsd/preferences.ts +3 -16
  153. package/src/resources/extensions/gsd/prompt-loader.ts +2 -3
  154. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  155. package/src/resources/extensions/gsd/rule-registry.ts +6 -7
  156. package/src/resources/extensions/gsd/safe-fs.ts +5 -6
  157. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +0 -63
  158. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +2 -27
  159. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/model-router.test.ts +3 -403
  161. package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -62
  162. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +0 -21
  163. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  164. package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -3
  165. package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -3
  166. package/src/resources/extensions/gsd/tools/complete-task.ts +6 -3
  167. package/src/resources/extensions/gsd/tools/plan-milestone.ts +6 -3
  168. package/src/resources/extensions/gsd/tools/plan-slice.ts +6 -3
  169. package/src/resources/extensions/gsd/tools/plan-task.ts +3 -2
  170. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -4
  171. package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -2
  172. package/src/resources/extensions/gsd/tools/reopen-task.ts +3 -2
  173. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -2
  174. package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -2
  175. package/src/resources/extensions/gsd/triage-resolution.ts +4 -11
  176. package/src/resources/extensions/gsd/types.ts +0 -1
  177. package/src/resources/extensions/gsd/workflow-events.ts +1 -2
  178. package/src/resources/extensions/gsd/workflow-logger.ts +5 -52
  179. package/src/resources/extensions/gsd/workflow-migration.ts +12 -14
  180. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  181. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  182. package/src/resources/extensions/gsd/worktree-manager.ts +14 -16
  183. package/src/resources/extensions/shared/interview-ui.ts +1 -3
  184. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +0 -2
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +0 -1
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +0 -47
  187. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +0 -1
  188. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +0 -70
  189. package/src/resources/extensions/gsd/tests/capability-router.test.ts +0 -347
  190. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +0 -1188
  191. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +0 -841
  192. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +0 -284
  193. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +0 -120
  194. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +0 -144
  195. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
  196. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_ssgManifest.js +0 -0
@@ -1,70 +0,0 @@
1
- // GSD2 — Regression test for LSP legacy server key aliases
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
-
4
- /**
5
- * When a default server key is renamed (e.g., kotlin-language-server → kotlin-lsp),
6
- * user overrides referencing the old key must still merge correctly via LEGACY_ALIASES.
7
- *
8
- * This test exercises the merge path through loadConfig() with a temp project
9
- * containing an lsp.json that uses the legacy key.
10
- */
11
-
12
- import { describe, it, beforeEach, afterEach } from "node:test";
13
- import assert from "node:assert/strict";
14
- import * as fs from "node:fs";
15
- import * as path from "node:path";
16
- import * as os from "node:os";
17
- import { loadConfig } from "./config.js";
18
-
19
- describe("LSP legacy server key aliases", () => {
20
- let tmpDir: string;
21
-
22
- beforeEach(() => {
23
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lsp-alias-test-"));
24
- });
25
-
26
- afterEach(() => {
27
- fs.rmSync(tmpDir, { recursive: true, force: true });
28
- });
29
-
30
- it("merges user override with legacy key 'kotlin-language-server' into 'kotlin-lsp'", () => {
31
- // Write an lsp.json that uses the old key name with a command that exists (node)
32
- // so resolveCommand doesn't filter it out.
33
- const overrideConfig = {
34
- servers: {
35
- "kotlin-language-server": {
36
- command: "node",
37
- },
38
- },
39
- };
40
- fs.writeFileSync(
41
- path.join(tmpDir, "lsp.json"),
42
- JSON.stringify(overrideConfig),
43
- );
44
-
45
- // Also add root markers so the server is detected
46
- fs.writeFileSync(path.join(tmpDir, "build.gradle.kts"), "");
47
-
48
- const config = loadConfig(tmpDir);
49
-
50
- // The merged config should have kotlin-lsp (new key) with the user's command override
51
- const kotlinServer = config.servers["kotlin-lsp"];
52
- assert.ok(kotlinServer, "kotlin-lsp should exist in merged config");
53
- assert.equal(
54
- kotlinServer.command,
55
- "node",
56
- "command should be overridden from user config via legacy alias",
57
- );
58
- assert.ok(
59
- kotlinServer.fileTypes.includes(".kt"),
60
- "fileTypes should be inherited from defaults",
61
- );
62
-
63
- // The old key should NOT appear as a separate entry
64
- assert.equal(
65
- config.servers["kotlin-language-server"],
66
- undefined,
67
- "legacy key should not appear as separate server",
68
- );
69
- });
70
- });
@@ -1,347 +0,0 @@
1
- // GSD Extension — Capability-Aware Router Tests
2
- // Tests for new capability scoring functions and data tables (Plan 01-01)
3
-
4
- import { describe, test } from "node:test";
5
- import assert from "node:assert/strict";
6
-
7
- import {
8
- scoreModel,
9
- computeTaskRequirements,
10
- scoreEligibleModels,
11
- getEligibleModels,
12
- resolveModelForComplexity,
13
- MODEL_CAPABILITY_PROFILES,
14
- BASE_REQUIREMENTS,
15
- defaultRoutingConfig,
16
- } from "../model-router.js";
17
- import type { ModelCapabilities, DynamicRoutingConfig, RoutingDecision } from "../model-router.js";
18
-
19
- // ─── scoreModel ──────────────────────────────────────────────────────────────
20
-
21
- describe("scoreModel", () => {
22
- const sonnetProfile: ModelCapabilities = {
23
- coding: 85, debugging: 80, research: 75, reasoning: 80,
24
- speed: 60, longContext: 75, instruction: 85,
25
- };
26
-
27
- test("produces correct weighted average for single dimension", () => {
28
- // Only coding weight 1.0 → result should be the coding score
29
- const score = scoreModel(sonnetProfile, { coding: 1.0 });
30
- assert.equal(score, 85);
31
- });
32
-
33
- test("produces correct weighted average for two dimensions (coding 0.9, instruction 0.7)", () => {
34
- // (0.9*85 + 0.7*85) / (0.9+0.7) = (76.5+59.5)/1.6 = 136/1.6 = 85.0
35
- const score = scoreModel(sonnetProfile, { coding: 0.9, instruction: 0.7 });
36
- assert.ok(Math.abs(score - 85.0) < 0.01, `Expected ~85.0, got ${score}`);
37
- });
38
-
39
- test("returns 50 when requirements is empty", () => {
40
- const score = scoreModel(sonnetProfile, {});
41
- assert.equal(score, 50);
42
- });
43
-
44
- test("uses 50 as fallback for unknown dimension in requirements", () => {
45
- // 'unknown' dimension not in profile → treated as 50
46
- const score = scoreModel(sonnetProfile, { coding: 0.5, unknown: 1.0 } as any);
47
- // (0.5*85 + 1.0*50) / (0.5+1.0) = (42.5+50)/1.5 = 92.5/1.5 = 61.67
48
- assert.ok(score > 61 && score < 62, `Expected ~61.67, got ${score}`);
49
- });
50
- });
51
-
52
- // ─── computeTaskRequirements ─────────────────────────────────────────────────
53
-
54
- describe("computeTaskRequirements", () => {
55
- test("execute-task with no metadata returns base requirements", () => {
56
- const req = computeTaskRequirements("execute-task", undefined);
57
- assert.deepStrictEqual(req, { coding: 0.9, instruction: 0.7, speed: 0.3 });
58
- });
59
-
60
- test("execute-task with docs tag returns docs-adjusted requirements", () => {
61
- const req = computeTaskRequirements("execute-task", { tags: ["docs"] });
62
- assert.equal(req.instruction, 0.9);
63
- assert.equal(req.coding, 0.3);
64
- assert.equal(req.speed, 0.7);
65
- });
66
-
67
- test("execute-task with readme tag returns docs-adjusted requirements", () => {
68
- const req = computeTaskRequirements("execute-task", { tags: ["readme"] });
69
- assert.equal(req.instruction, 0.9);
70
- });
71
-
72
- test("execute-task with concurrency keyword boosts debugging and reasoning", () => {
73
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["concurrency"] });
74
- assert.equal(req.debugging, 0.9);
75
- assert.equal(req.reasoning, 0.8);
76
- });
77
-
78
- test("execute-task with compatibility keyword boosts debugging and reasoning", () => {
79
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["compatibility"] });
80
- assert.equal(req.debugging, 0.9);
81
- assert.equal(req.reasoning, 0.8);
82
- });
83
-
84
- test("execute-task with migration keyword boosts reasoning and coding", () => {
85
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["migration"] });
86
- assert.equal(req.reasoning, 0.9);
87
- assert.equal(req.coding, 0.8);
88
- });
89
-
90
- test("execute-task with architecture keyword boosts reasoning and coding", () => {
91
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["architecture"] });
92
- assert.equal(req.reasoning, 0.9);
93
- assert.equal(req.coding, 0.8);
94
- });
95
-
96
- test("execute-task with fileCount >= 6 boosts coding and reasoning", () => {
97
- const req = computeTaskRequirements("execute-task", { fileCount: 8 });
98
- assert.equal(req.coding, 0.9);
99
- assert.equal(req.reasoning, 0.7);
100
- });
101
-
102
- test("execute-task with fileCount exactly 6 triggers large-file boost", () => {
103
- const req = computeTaskRequirements("execute-task", { fileCount: 6 });
104
- assert.equal(req.coding, 0.9);
105
- assert.equal(req.reasoning, 0.7);
106
- });
107
-
108
- test("execute-task with estimatedLines >= 500 boosts coding and reasoning", () => {
109
- const req = computeTaskRequirements("execute-task", { estimatedLines: 500 });
110
- assert.equal(req.coding, 0.9);
111
- assert.equal(req.reasoning, 0.7);
112
- });
113
-
114
- test("research-milestone with no metadata returns base requirements", () => {
115
- const req = computeTaskRequirements("research-milestone", undefined);
116
- assert.deepStrictEqual(req, { research: 0.9, longContext: 0.7, reasoning: 0.5 });
117
- });
118
-
119
- test("unknown unit type returns default reasoning requirement", () => {
120
- const req = computeTaskRequirements("unknown-type", undefined);
121
- assert.deepStrictEqual(req, { reasoning: 0.5 });
122
- });
123
- });
124
-
125
- // ─── MODEL_CAPABILITY_PROFILES ───────────────────────────────────────────────
126
-
127
- describe("MODEL_CAPABILITY_PROFILES", () => {
128
- test("contains all 9 required models", () => {
129
- const required = [
130
- "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5",
131
- "gpt-4o", "gpt-4o-mini", "gemini-2.5-pro", "gemini-2.0-flash",
132
- "deepseek-chat", "o3",
133
- ];
134
- for (const model of required) {
135
- assert.ok(MODEL_CAPABILITY_PROFILES[model], `Missing profile for ${model}`);
136
- }
137
- });
138
-
139
- test("each profile has all 7 capability dimensions", () => {
140
- const dims: Array<keyof ModelCapabilities> = [
141
- "coding", "debugging", "research", "reasoning",
142
- "speed", "longContext", "instruction",
143
- ];
144
- for (const [modelId, profile] of Object.entries(MODEL_CAPABILITY_PROFILES)) {
145
- for (const dim of dims) {
146
- assert.ok(profile[dim] !== undefined, `${modelId} missing dimension ${dim}`);
147
- assert.ok(profile[dim] >= 0 && profile[dim] <= 100, `${modelId}.${dim} out of range`);
148
- }
149
- }
150
- });
151
-
152
- test("claude-opus-4-6 has high reasoning and coding", () => {
153
- const opus = MODEL_CAPABILITY_PROFILES["claude-opus-4-6"];
154
- assert.ok(opus.reasoning >= 90, `Expected reasoning >= 90, got ${opus.reasoning}`);
155
- assert.ok(opus.coding >= 90, `Expected coding >= 90, got ${opus.coding}`);
156
- });
157
-
158
- test("claude-haiku-4-5 has high speed but lower reasoning", () => {
159
- const haiku = MODEL_CAPABILITY_PROFILES["claude-haiku-4-5"];
160
- assert.ok(haiku.speed >= 90, `Expected speed >= 90, got ${haiku.speed}`);
161
- assert.ok(haiku.reasoning < 70, `Expected reasoning < 70, got ${haiku.reasoning}`);
162
- });
163
- });
164
-
165
- // ─── BASE_REQUIREMENTS ───────────────────────────────────────────────────────
166
-
167
- describe("BASE_REQUIREMENTS", () => {
168
- test("contains all 11 unit types", () => {
169
- const required = [
170
- "execute-task", "research-milestone", "research-slice",
171
- "plan-milestone", "plan-slice", "replan-slice",
172
- "reassess-roadmap", "complete-slice", "run-uat",
173
- "discuss-milestone", "complete-milestone",
174
- ];
175
- for (const unitType of required) {
176
- assert.ok(BASE_REQUIREMENTS[unitType], `Missing requirements for ${unitType}`);
177
- }
178
- });
179
- });
180
-
181
- // ─── scoreEligibleModels ─────────────────────────────────────────────────────
182
-
183
- describe("scoreEligibleModels", () => {
184
- test("returns array sorted by score descending", () => {
185
- const requirements = { research: 0.9, longContext: 0.7, reasoning: 0.5 };
186
- const results = scoreEligibleModels(["claude-sonnet-4-6", "gpt-4o"], requirements);
187
- assert.ok(results.length === 2);
188
- assert.ok(results[0].score >= results[1].score, "Should be sorted descending by score");
189
- });
190
-
191
- test("returns single model when only one eligible", () => {
192
- const requirements = { coding: 0.9 };
193
- const results = scoreEligibleModels(["claude-sonnet-4-6"], requirements);
194
- assert.equal(results.length, 1);
195
- assert.equal(results[0].modelId, "claude-sonnet-4-6");
196
- });
197
-
198
- test("models without profiles get uniform 50s score", () => {
199
- const requirements = { coding: 1.0 };
200
- const results = scoreEligibleModels(["unknown-model-xyz"], requirements);
201
- assert.equal(results[0].score, 50);
202
- });
203
-
204
- test("when two models score within 2 points, prefers cheaper model", () => {
205
- // gemini-2.0-flash is cheaper than gpt-4o-mini ($0.0001 vs $0.00015)
206
- // Use a requirement that causes similar scores for both
207
- const requirements = { speed: 1.0 };
208
- const results = scoreEligibleModels(["gpt-4o-mini", "gemini-2.0-flash"], requirements);
209
- // Both are high-speed: gpt-4o-mini=90, gemini-2.0-flash=95 — scores differ by 5, not within 2
210
- // So top should be gemini-2.0-flash by score
211
- assert.equal(results[0].modelId, "gemini-2.0-flash");
212
- });
213
-
214
- test("tie-breaks by lexicographic model ID when cost and score are equal", () => {
215
- // Use models without cost entries — both get Infinity cost
216
- const requirements = { coding: 1.0 };
217
- const results = scoreEligibleModels(["model-z", "model-a"], requirements);
218
- // Both unknown → score=50, cost=Infinity → tiebreak by ID
219
- assert.equal(results[0].modelId, "model-a");
220
- });
221
-
222
- test("scoreEligibleModels respects capabilityOverrides", () => {
223
- const requirements = { coding: 1.0 };
224
- // Override claude-sonnet-4-6's coding to 30 (worse)
225
- const results = scoreEligibleModels(
226
- ["claude-sonnet-4-6", "gpt-4o"],
227
- requirements,
228
- { "claude-sonnet-4-6": { coding: 30 } },
229
- );
230
- // gpt-4o coding=80 should beat overridden sonnet coding=30
231
- assert.equal(results[0].modelId, "gpt-4o");
232
- });
233
- });
234
-
235
- // ─── getEligibleModels ───────────────────────────────────────────────────────
236
-
237
- describe("getEligibleModels", () => {
238
- const MODELS = [
239
- "claude-opus-4-6", // heavy
240
- "claude-sonnet-4-6", // standard
241
- "claude-haiku-4-5", // light
242
- "gpt-4o-mini", // light
243
- ];
244
-
245
- test("returns light-tier models sorted by cost when no explicit config", () => {
246
- const config: DynamicRoutingConfig = defaultRoutingConfig();
247
- const result = getEligibleModels("light", MODELS, config);
248
- assert.ok(result.length >= 1);
249
- // All results should be light-tier
250
- for (const id of result) {
251
- assert.ok(
252
- ["claude-haiku-4-5", "gpt-4o-mini"].includes(id),
253
- `Expected light-tier model, got ${id}`,
254
- );
255
- }
256
- });
257
-
258
- test("returns explicit tier_models when configured and available", () => {
259
- const config: DynamicRoutingConfig = {
260
- ...defaultRoutingConfig(),
261
- tier_models: { light: "gpt-4o-mini" },
262
- };
263
- const result = getEligibleModels("light", MODELS, config);
264
- assert.deepStrictEqual(result, ["gpt-4o-mini"]);
265
- });
266
-
267
- test("returns empty array when no eligible models for tier", () => {
268
- const config: DynamicRoutingConfig = defaultRoutingConfig();
269
- // Only heavy model available, requesting light
270
- const result = getEligibleModels("light", ["claude-opus-4-6"], config);
271
- assert.equal(result.length, 0);
272
- });
273
- });
274
-
275
- // ─── DynamicRoutingConfig extension ─────────────────────────────────────────
276
-
277
- describe("DynamicRoutingConfig.capability_routing", () => {
278
- test("defaultRoutingConfig includes capability_routing: true", () => {
279
- const config = defaultRoutingConfig();
280
- assert.equal(config.capability_routing, true);
281
- });
282
- });
283
-
284
- // ─── RoutingDecision.selectionMethod ─────────────────────────────────────────
285
-
286
- describe("RoutingDecision.selectionMethod", () => {
287
- const MODELS = ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "gpt-4o-mini"];
288
-
289
- function makeClassification(tier: "light" | "standard" | "heavy") {
290
- return { tier, reason: "test", downgraded: false };
291
- }
292
-
293
- test("returns selectionMethod: tier-only when routing is disabled", () => {
294
- const config = { ...defaultRoutingConfig(), enabled: false };
295
- const result: RoutingDecision = resolveModelForComplexity(
296
- makeClassification("light"),
297
- { primary: "claude-opus-4-6", fallbacks: [] },
298
- config,
299
- MODELS,
300
- );
301
- assert.equal(result.selectionMethod, "tier-only");
302
- });
303
-
304
- test("returns selectionMethod: tier-only for no phase config passthrough", () => {
305
- const config = { ...defaultRoutingConfig(), enabled: true };
306
- const result: RoutingDecision = resolveModelForComplexity(
307
- makeClassification("light"),
308
- undefined,
309
- config,
310
- MODELS,
311
- );
312
- assert.equal(result.selectionMethod, "tier-only");
313
- });
314
-
315
- test("returns selectionMethod: tier-only for unknown model passthrough", () => {
316
- const config = { ...defaultRoutingConfig(), enabled: true };
317
- const result: RoutingDecision = resolveModelForComplexity(
318
- makeClassification("light"),
319
- { primary: "custom-provider/my-model-v3", fallbacks: [] },
320
- config,
321
- ["custom-provider/my-model-v3", ...MODELS],
322
- );
323
- assert.equal(result.selectionMethod, "tier-only");
324
- });
325
-
326
- test("returns selectionMethod: tier-only for no-downgrade passthrough", () => {
327
- const config = { ...defaultRoutingConfig(), enabled: true };
328
- const result: RoutingDecision = resolveModelForComplexity(
329
- makeClassification("heavy"),
330
- { primary: "claude-opus-4-6", fallbacks: [] },
331
- config,
332
- MODELS,
333
- );
334
- assert.equal(result.selectionMethod, "tier-only");
335
- });
336
-
337
- test("returns selectionMethod: tier-only when downgraded", () => {
338
- const config = { ...defaultRoutingConfig(), enabled: true };
339
- const result: RoutingDecision = resolveModelForComplexity(
340
- makeClassification("light"),
341
- { primary: "claude-opus-4-6", fallbacks: [] },
342
- config,
343
- MODELS,
344
- );
345
- assert.equal(result.selectionMethod, "tier-only");
346
- });
347
- });