gsd-pi 2.18.0 → 2.20.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 +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +690 -39
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +654 -36
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +84 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/dist/resources/extensions/gsd/preferences.ts +198 -150
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/dist/resources/extensions/remote-questions/format.ts +166 -14
- package/dist/resources/extensions/remote-questions/manager.ts +14 -4
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -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 +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +690 -39
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +654 -36
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +84 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/src/resources/extensions/gsd/preferences.ts +198 -150
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/src/resources/extensions/remote-questions/format.ts +166 -14
- package/src/resources/extensions/remote-questions/manager.ts +14 -4
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* milestone-transition-worktree.test.ts — Tests for #616 fix.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that when auto-mode transitions between milestones, the
|
|
5
|
+
* worktree lifecycle is handled: old worktree merged, new worktree created.
|
|
6
|
+
*
|
|
7
|
+
* Uses source-level checks since the full auto-mode dispatch loop
|
|
8
|
+
* requires the @gsd/pi-coding-agent runtime.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, realpathSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { execSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
import { dirname } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
createAutoWorktree,
|
|
23
|
+
teardownAutoWorktree,
|
|
24
|
+
isInAutoWorktree,
|
|
25
|
+
getAutoWorktreeOriginalBase,
|
|
26
|
+
mergeMilestoneToMain,
|
|
27
|
+
} from "../auto-worktree.ts";
|
|
28
|
+
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
|
|
31
|
+
function run(command: string, cwd: string): string {
|
|
32
|
+
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createTempRepo(): string {
|
|
36
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-mt-wt-test-")));
|
|
37
|
+
run("git init", dir);
|
|
38
|
+
run("git config user.email test@test.com", dir);
|
|
39
|
+
run("git config user.name Test", dir);
|
|
40
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
41
|
+
run("git add .", dir);
|
|
42
|
+
run("git commit -m init", dir);
|
|
43
|
+
run("git branch -M main", dir);
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createMilestoneArtifacts(dir: string, mid: string): void {
|
|
48
|
+
const msDir = join(dir, ".gsd", "milestones", mid);
|
|
49
|
+
mkdirSync(msDir, { recursive: true });
|
|
50
|
+
writeFileSync(join(msDir, "CONTEXT.md"), `# ${mid} Context\n`);
|
|
51
|
+
const roadmap = [
|
|
52
|
+
`# ${mid}: Test Milestone`,
|
|
53
|
+
"**Vision**: testing",
|
|
54
|
+
"## Success Criteria",
|
|
55
|
+
"- It works",
|
|
56
|
+
"## Slices",
|
|
57
|
+
"- [x] S01 — First slice",
|
|
58
|
+
].join("\n");
|
|
59
|
+
writeFileSync(join(msDir, `${mid}-ROADMAP.md`), roadmap);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Milestone transition: worktree swap ─────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
test("worktree swap on milestone transition: merge old, create new", () => {
|
|
65
|
+
const savedCwd = process.cwd();
|
|
66
|
+
let tempDir = "";
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
tempDir = createTempRepo();
|
|
70
|
+
|
|
71
|
+
// Set up M001 and M002 milestone artifacts
|
|
72
|
+
createMilestoneArtifacts(tempDir, "M001");
|
|
73
|
+
createMilestoneArtifacts(tempDir, "M002");
|
|
74
|
+
run("git add .", tempDir);
|
|
75
|
+
run("git commit -m \"add milestones\"", tempDir);
|
|
76
|
+
|
|
77
|
+
// Phase 1: Create worktree for M001 (simulates auto-mode start)
|
|
78
|
+
const wt1 = createAutoWorktree(tempDir, "M001");
|
|
79
|
+
assert.equal(process.cwd(), wt1, "cwd should be in M001 worktree");
|
|
80
|
+
assert.ok(isInAutoWorktree(tempDir), "should be in auto-worktree");
|
|
81
|
+
assert.equal(getAutoWorktreeOriginalBase(), tempDir, "original base preserved");
|
|
82
|
+
|
|
83
|
+
// Add a commit in M001 worktree to simulate work
|
|
84
|
+
writeFileSync(join(wt1, "feature-m001.txt"), "M001 work\n");
|
|
85
|
+
run("git add .", wt1);
|
|
86
|
+
run("git commit -m \"feat(M001): add feature\"", wt1);
|
|
87
|
+
|
|
88
|
+
// Phase 2: Simulate milestone transition — merge M001, exit worktree
|
|
89
|
+
const roadmapPath = join(tempDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
90
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
91
|
+
mergeMilestoneToMain(tempDir, "M001", roadmapContent);
|
|
92
|
+
|
|
93
|
+
// After merge: cwd should be back at project root
|
|
94
|
+
assert.equal(process.cwd(), tempDir, "cwd restored to project root after merge");
|
|
95
|
+
assert.ok(!isInAutoWorktree(tempDir), "no longer in auto-worktree after merge");
|
|
96
|
+
|
|
97
|
+
// Verify M001 work was merged to main
|
|
98
|
+
const mainLog = run("git log --oneline -3", tempDir);
|
|
99
|
+
assert.ok(mainLog.includes("M001"), "M001 squash commit should be on main");
|
|
100
|
+
|
|
101
|
+
// Phase 3: Create new worktree for M002 (simulates new milestone)
|
|
102
|
+
const wt2 = createAutoWorktree(tempDir, "M002");
|
|
103
|
+
assert.equal(process.cwd(), wt2, "cwd should be in M002 worktree");
|
|
104
|
+
assert.ok(isInAutoWorktree(tempDir), "should be in M002 auto-worktree");
|
|
105
|
+
|
|
106
|
+
// The new worktree should have the M001 feature file (merged to main)
|
|
107
|
+
assert.ok(existsSync(join(wt2, "feature-m001.txt")), "M002 worktree inherits M001 merged work");
|
|
108
|
+
|
|
109
|
+
// Verify branch is correct
|
|
110
|
+
const branch = run("git branch --show-current", wt2);
|
|
111
|
+
assert.equal(branch, "milestone/M002", "M002 worktree on correct branch");
|
|
112
|
+
|
|
113
|
+
// Cleanup
|
|
114
|
+
teardownAutoWorktree(tempDir, "M002");
|
|
115
|
+
} finally {
|
|
116
|
+
process.chdir(savedCwd);
|
|
117
|
+
if (tempDir && existsSync(tempDir)) {
|
|
118
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ─── Verify the transition code path exists in auto.ts ──────────────────────
|
|
124
|
+
|
|
125
|
+
test("auto.ts milestone transition block contains worktree lifecycle", () => {
|
|
126
|
+
const autoSrc = readFileSync(
|
|
127
|
+
join(__dirname, "..", "auto.ts"),
|
|
128
|
+
"utf-8",
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// The fix adds worktree merge + create inside the milestone transition block
|
|
132
|
+
assert.ok(
|
|
133
|
+
autoSrc.includes("Worktree lifecycle on milestone transition"),
|
|
134
|
+
"auto.ts should contain the worktree lifecycle comment marker",
|
|
135
|
+
);
|
|
136
|
+
assert.ok(
|
|
137
|
+
autoSrc.includes("mergeMilestoneToMain") && autoSrc.includes("mid !== currentMilestoneId"),
|
|
138
|
+
"auto.ts should call mergeMilestoneToMain during milestone transition",
|
|
139
|
+
);
|
|
140
|
+
assert.ok(
|
|
141
|
+
autoSrc.includes("createAutoWorktree") && autoSrc.includes("Created auto-worktree for"),
|
|
142
|
+
"auto.ts should create new worktree for incoming milestone",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { lookupModelCost, compareModelCost, BUNDLED_COST_TABLE } from "../model-cost-table.js";
|
|
5
|
+
|
|
6
|
+
// ─── lookupModelCost ─────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
test("lookupModelCost finds exact match", () => {
|
|
9
|
+
const entry = lookupModelCost("claude-opus-4-6");
|
|
10
|
+
assert.ok(entry);
|
|
11
|
+
assert.equal(entry.id, "claude-opus-4-6");
|
|
12
|
+
assert.ok(entry.inputPer1k > 0);
|
|
13
|
+
assert.ok(entry.outputPer1k > 0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("lookupModelCost strips provider prefix", () => {
|
|
17
|
+
const entry = lookupModelCost("anthropic/claude-opus-4-6");
|
|
18
|
+
assert.ok(entry);
|
|
19
|
+
assert.equal(entry.id, "claude-opus-4-6");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("lookupModelCost returns undefined for unknown model", () => {
|
|
23
|
+
const entry = lookupModelCost("totally-unknown-model");
|
|
24
|
+
assert.equal(entry, undefined);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("lookupModelCost finds haiku", () => {
|
|
28
|
+
const entry = lookupModelCost("claude-haiku-4-5");
|
|
29
|
+
assert.ok(entry);
|
|
30
|
+
assert.ok(entry.inputPer1k < 0.001, "haiku should be cheap");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ─── compareModelCost ────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
test("haiku is cheaper than opus", () => {
|
|
36
|
+
assert.ok(compareModelCost("claude-haiku-4-5", "claude-opus-4-6") < 0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("opus is more expensive than sonnet", () => {
|
|
40
|
+
assert.ok(compareModelCost("claude-opus-4-6", "claude-sonnet-4-6") > 0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("same model has equal cost", () => {
|
|
44
|
+
assert.equal(compareModelCost("claude-opus-4-6", "claude-opus-4-6"), 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ─── BUNDLED_COST_TABLE ──────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
test("cost table has entries for all major providers", () => {
|
|
50
|
+
const ids = BUNDLED_COST_TABLE.map(e => e.id);
|
|
51
|
+
// Anthropic
|
|
52
|
+
assert.ok(ids.includes("claude-opus-4-6"));
|
|
53
|
+
assert.ok(ids.includes("claude-sonnet-4-6"));
|
|
54
|
+
assert.ok(ids.includes("claude-haiku-4-5"));
|
|
55
|
+
// OpenAI
|
|
56
|
+
assert.ok(ids.includes("gpt-4o"));
|
|
57
|
+
assert.ok(ids.includes("gpt-4o-mini"));
|
|
58
|
+
// Google
|
|
59
|
+
assert.ok(ids.includes("gemini-2.0-flash"));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("all cost table entries have valid data", () => {
|
|
63
|
+
for (const entry of BUNDLED_COST_TABLE) {
|
|
64
|
+
assert.ok(entry.id, `entry missing id`);
|
|
65
|
+
assert.ok(entry.inputPer1k >= 0, `${entry.id} inputPer1k should be >= 0`);
|
|
66
|
+
assert.ok(entry.outputPer1k >= 0, `${entry.id} outputPer1k should be >= 0`);
|
|
67
|
+
assert.ok(entry.updatedAt, `${entry.id} missing updatedAt`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for model config isolation between concurrent instances (#650).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
|
|
11
|
+
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function makeTmpDir(suffix: string): string {
|
|
14
|
+
const dir = join(tmpdir(), `gsd-test-650-${suffix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── Settings Manager Model Scoping ───────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe("model config isolation (#650)", () => {
|
|
22
|
+
let tmpGlobal: string;
|
|
23
|
+
let tmpProjectA: string;
|
|
24
|
+
let tmpProjectB: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tmpGlobal = makeTmpDir("global");
|
|
28
|
+
tmpProjectA = makeTmpDir("project-a");
|
|
29
|
+
tmpProjectB = makeTmpDir("project-b");
|
|
30
|
+
// Create .pi directories for project settings
|
|
31
|
+
mkdirSync(join(tmpProjectA, ".pi"), { recursive: true });
|
|
32
|
+
mkdirSync(join(tmpProjectB, ".pi"), { recursive: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
try { rmSync(tmpGlobal, { recursive: true, force: true }); } catch {}
|
|
37
|
+
try { rmSync(tmpProjectA, { recursive: true, force: true }); } catch {}
|
|
38
|
+
try { rmSync(tmpProjectB, { recursive: true, force: true }); } catch {}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("project settings file isolates model from global", async () => {
|
|
42
|
+
// Write project settings for project A
|
|
43
|
+
const projectSettingsPath = join(tmpProjectA, ".pi", "settings.json");
|
|
44
|
+
writeFileSync(projectSettingsPath, JSON.stringify({
|
|
45
|
+
defaultProvider: "anthropic",
|
|
46
|
+
defaultModel: "claude-opus-4-6",
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// Write global settings with a different model
|
|
50
|
+
const globalSettingsPath = join(tmpGlobal, "settings.json");
|
|
51
|
+
writeFileSync(globalSettingsPath, JSON.stringify({
|
|
52
|
+
defaultProvider: "openai",
|
|
53
|
+
defaultModel: "gpt-5.4",
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Verify project settings exist and have independent data
|
|
57
|
+
const projectData = JSON.parse(readFileSync(projectSettingsPath, "utf-8"));
|
|
58
|
+
const globalData = JSON.parse(readFileSync(globalSettingsPath, "utf-8"));
|
|
59
|
+
|
|
60
|
+
assert.equal(projectData.defaultModel, "claude-opus-4-6");
|
|
61
|
+
assert.equal(globalData.defaultModel, "gpt-5.4");
|
|
62
|
+
assert.notEqual(projectData.defaultModel, globalData.defaultModel,
|
|
63
|
+
"Project and global should have different models");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("two projects have independent model configs", () => {
|
|
67
|
+
const settingsA = join(tmpProjectA, ".pi", "settings.json");
|
|
68
|
+
const settingsB = join(tmpProjectB, ".pi", "settings.json");
|
|
69
|
+
|
|
70
|
+
writeFileSync(settingsA, JSON.stringify({
|
|
71
|
+
defaultProvider: "anthropic",
|
|
72
|
+
defaultModel: "claude-opus-4-6",
|
|
73
|
+
}));
|
|
74
|
+
writeFileSync(settingsB, JSON.stringify({
|
|
75
|
+
defaultProvider: "openai-codex",
|
|
76
|
+
defaultModel: "gpt-5.4",
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const dataA = JSON.parse(readFileSync(settingsA, "utf-8"));
|
|
80
|
+
const dataB = JSON.parse(readFileSync(settingsB, "utf-8"));
|
|
81
|
+
|
|
82
|
+
assert.equal(dataA.defaultModel, "claude-opus-4-6");
|
|
83
|
+
assert.equal(dataB.defaultModel, "gpt-5.4");
|
|
84
|
+
assert.notEqual(dataA.defaultProvider, dataB.defaultProvider);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("autoModeStartModel concept prevents model drift", () => {
|
|
88
|
+
// Simulate the auto-mode start model capture pattern
|
|
89
|
+
const autoModeStartModel = { provider: "anthropic", id: "claude-opus-4-6" };
|
|
90
|
+
|
|
91
|
+
// Simulate another instance writing to global settings
|
|
92
|
+
const globalSettings = { defaultProvider: "openai-codex", defaultModel: "gpt-5.4" };
|
|
93
|
+
|
|
94
|
+
// The captured model should be used, not the global settings
|
|
95
|
+
assert.notEqual(autoModeStartModel.id, globalSettings.defaultModel);
|
|
96
|
+
assert.equal(autoModeStartModel.id, "claude-opus-4-6",
|
|
97
|
+
"Captured model should be preserved regardless of global settings changes");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveModelForComplexity,
|
|
6
|
+
escalateTier,
|
|
7
|
+
defaultRoutingConfig,
|
|
8
|
+
} from "../model-router.js";
|
|
9
|
+
import type { DynamicRoutingConfig, RoutingDecision } from "../model-router.js";
|
|
10
|
+
import type { ClassificationResult } from "../complexity-classifier.js";
|
|
11
|
+
|
|
12
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
function makeClassification(tier: "light" | "standard" | "heavy", reason = "test"): ClassificationResult {
|
|
15
|
+
return { tier, reason, downgraded: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AVAILABLE_MODELS = [
|
|
19
|
+
"claude-opus-4-6",
|
|
20
|
+
"claude-sonnet-4-6",
|
|
21
|
+
"claude-haiku-4-5",
|
|
22
|
+
"gpt-4o-mini",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// ─── Passthrough when disabled ───────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
test("returns configured model when routing is disabled", () => {
|
|
28
|
+
const config = { ...defaultRoutingConfig(), enabled: false };
|
|
29
|
+
const result = resolveModelForComplexity(
|
|
30
|
+
makeClassification("light"),
|
|
31
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
32
|
+
config,
|
|
33
|
+
AVAILABLE_MODELS,
|
|
34
|
+
);
|
|
35
|
+
assert.equal(result.modelId, "claude-opus-4-6");
|
|
36
|
+
assert.equal(result.wasDowngraded, false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("returns configured model when no phase config", () => {
|
|
40
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
41
|
+
const result = resolveModelForComplexity(
|
|
42
|
+
makeClassification("light"),
|
|
43
|
+
undefined,
|
|
44
|
+
config,
|
|
45
|
+
AVAILABLE_MODELS,
|
|
46
|
+
);
|
|
47
|
+
assert.equal(result.modelId, "");
|
|
48
|
+
assert.equal(result.wasDowngraded, false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ─── Downgrade-only semantics ────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
test("does not downgrade when tier matches configured model tier", () => {
|
|
54
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
55
|
+
const result = resolveModelForComplexity(
|
|
56
|
+
makeClassification("heavy"),
|
|
57
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
58
|
+
config,
|
|
59
|
+
AVAILABLE_MODELS,
|
|
60
|
+
);
|
|
61
|
+
assert.equal(result.modelId, "claude-opus-4-6");
|
|
62
|
+
assert.equal(result.wasDowngraded, false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("does not upgrade beyond configured model", () => {
|
|
66
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
67
|
+
// Configured model is sonnet (standard), classification says heavy
|
|
68
|
+
const result = resolveModelForComplexity(
|
|
69
|
+
makeClassification("heavy"),
|
|
70
|
+
{ primary: "claude-sonnet-4-6", fallbacks: [] },
|
|
71
|
+
config,
|
|
72
|
+
AVAILABLE_MODELS,
|
|
73
|
+
);
|
|
74
|
+
assert.equal(result.modelId, "claude-sonnet-4-6");
|
|
75
|
+
assert.equal(result.wasDowngraded, false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("downgrades from opus to haiku for light tier", () => {
|
|
79
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
80
|
+
const result = resolveModelForComplexity(
|
|
81
|
+
makeClassification("light"),
|
|
82
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
83
|
+
config,
|
|
84
|
+
AVAILABLE_MODELS,
|
|
85
|
+
);
|
|
86
|
+
// Should pick haiku or gpt-4o-mini (cheapest light tier)
|
|
87
|
+
assert.ok(
|
|
88
|
+
result.modelId === "claude-haiku-4-5" || result.modelId === "gpt-4o-mini",
|
|
89
|
+
`Expected light-tier model, got ${result.modelId}`,
|
|
90
|
+
);
|
|
91
|
+
assert.equal(result.wasDowngraded, true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("downgrades from opus to sonnet for standard tier", () => {
|
|
95
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
96
|
+
const result = resolveModelForComplexity(
|
|
97
|
+
makeClassification("standard"),
|
|
98
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
99
|
+
config,
|
|
100
|
+
AVAILABLE_MODELS,
|
|
101
|
+
);
|
|
102
|
+
assert.equal(result.modelId, "claude-sonnet-4-6");
|
|
103
|
+
assert.equal(result.wasDowngraded, true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ─── Explicit tier_models ────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
test("uses explicit tier_models when configured", () => {
|
|
109
|
+
const config: DynamicRoutingConfig = {
|
|
110
|
+
...defaultRoutingConfig(),
|
|
111
|
+
enabled: true,
|
|
112
|
+
tier_models: { light: "gpt-4o-mini", standard: "claude-sonnet-4-6" },
|
|
113
|
+
};
|
|
114
|
+
const result = resolveModelForComplexity(
|
|
115
|
+
makeClassification("light"),
|
|
116
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
117
|
+
config,
|
|
118
|
+
AVAILABLE_MODELS,
|
|
119
|
+
);
|
|
120
|
+
assert.equal(result.modelId, "gpt-4o-mini");
|
|
121
|
+
assert.equal(result.wasDowngraded, true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ─── Fallback chain construction ─────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
test("fallback chain includes configured primary as last resort", () => {
|
|
127
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
128
|
+
const result = resolveModelForComplexity(
|
|
129
|
+
makeClassification("light"),
|
|
130
|
+
{ primary: "claude-opus-4-6", fallbacks: ["claude-sonnet-4-6"] },
|
|
131
|
+
config,
|
|
132
|
+
AVAILABLE_MODELS,
|
|
133
|
+
);
|
|
134
|
+
assert.ok(result.wasDowngraded);
|
|
135
|
+
// Fallbacks should include the configured fallbacks and primary
|
|
136
|
+
assert.ok(result.fallbacks.includes("claude-opus-4-6"), "primary should be in fallbacks");
|
|
137
|
+
assert.ok(result.fallbacks.includes("claude-sonnet-4-6"), "configured fallback should be in fallbacks");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ─── Escalation ──────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
test("escalateTier moves light → standard", () => {
|
|
143
|
+
assert.equal(escalateTier("light"), "standard");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("escalateTier moves standard → heavy", () => {
|
|
147
|
+
assert.equal(escalateTier("standard"), "heavy");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("escalateTier returns null for heavy (max)", () => {
|
|
151
|
+
assert.equal(escalateTier("heavy"), null);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ─── No suitable model available ─────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
test("falls back to configured model when no light-tier model available", () => {
|
|
157
|
+
const config = { ...defaultRoutingConfig(), enabled: true };
|
|
158
|
+
// Only heavy-tier models available
|
|
159
|
+
const result = resolveModelForComplexity(
|
|
160
|
+
makeClassification("light"),
|
|
161
|
+
{ primary: "claude-opus-4-6", fallbacks: [] },
|
|
162
|
+
config,
|
|
163
|
+
["claude-opus-4-6"],
|
|
164
|
+
);
|
|
165
|
+
assert.equal(result.modelId, "claude-opus-4-6");
|
|
166
|
+
assert.equal(result.wasDowngraded, false);
|
|
167
|
+
});
|
|
@@ -1661,4 +1661,44 @@ console.log('\n=== LLM round-trip: extra blank lines ===');
|
|
|
1661
1661
|
assertTrue(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
|
|
1662
1662
|
}
|
|
1663
1663
|
|
|
1664
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1665
|
+
// parseRoadmap: boundary map with embedded code fences (#468)
|
|
1666
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1667
|
+
|
|
1668
|
+
console.log('\n=== parseRoadmap: boundary map with code fences (#468) ===');
|
|
1669
|
+
{
|
|
1670
|
+
const content = `# M001: Test
|
|
1671
|
+
|
|
1672
|
+
**Vision:** Test
|
|
1673
|
+
|
|
1674
|
+
## Slices
|
|
1675
|
+
|
|
1676
|
+
- [ ] **S01: Core** \`risk:low\` \`depends:[]\`
|
|
1677
|
+
- [ ] **S02: API** \`risk:low\` \`depends:[S01]\`
|
|
1678
|
+
|
|
1679
|
+
## Boundary Map
|
|
1680
|
+
|
|
1681
|
+
### S01 → S02
|
|
1682
|
+
|
|
1683
|
+
Produces:
|
|
1684
|
+
types.ts — all types
|
|
1685
|
+
\`\`\`
|
|
1686
|
+
const x = 1;
|
|
1687
|
+
\`\`\`
|
|
1688
|
+
|
|
1689
|
+
Consumes: nothing
|
|
1690
|
+
`;
|
|
1691
|
+
|
|
1692
|
+
// This test ensures the boundary map parser does not hang or
|
|
1693
|
+
// catastrophically backtrack when content contains code fences.
|
|
1694
|
+
const start = Date.now();
|
|
1695
|
+
const r = parseRoadmap(content);
|
|
1696
|
+
const elapsed = Date.now() - start;
|
|
1697
|
+
|
|
1698
|
+
assertTrue(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
|
|
1699
|
+
assertEq(r.slices.length, 2, 'code-fence roadmap: slice count');
|
|
1700
|
+
// Boundary map should still parse (may not capture perfectly with code fences, but must not hang)
|
|
1701
|
+
assertTrue(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1664
1704
|
report();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// GSD Extension — Hook Engine Tests (Post-Unit, Pre-Dispatch, State Persistence)
|
|
2
|
-
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
2
|
|
|
4
3
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
5
4
|
import { join } from "node:path";
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
clearPersistedHookState,
|
|
19
18
|
getHookStatus,
|
|
20
19
|
formatHookStatus,
|
|
20
|
+
triggerHookManually,
|
|
21
21
|
} from "../post-unit-hooks.ts";
|
|
22
22
|
|
|
23
23
|
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
@@ -294,4 +294,44 @@ console.log("\n=== Hook status: no hooks ===");
|
|
|
294
294
|
assertMatch(formatted, /No hooks configured/, "status message says no hooks");
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
298
|
+
// Phase 4: Manual Hook Trigger Tests
|
|
299
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
300
|
+
|
|
301
|
+
console.log("\n=== triggerHookManually: hook not found ===");
|
|
302
|
+
|
|
303
|
+
{
|
|
304
|
+
resetHookState();
|
|
305
|
+
const base = createFixtureBase();
|
|
306
|
+
try {
|
|
307
|
+
const result = triggerHookManually("nonexistent-hook", "execute-task", "M001/S01/T01", base);
|
|
308
|
+
assertEq(result, null, "returns null when hook not found");
|
|
309
|
+
} finally {
|
|
310
|
+
rmSync(base, { recursive: true, force: true });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log("\n=== triggerHookManually: with configured hook ===");
|
|
315
|
+
|
|
316
|
+
{
|
|
317
|
+
resetHookState();
|
|
318
|
+
const base = createFixtureBase();
|
|
319
|
+
try {
|
|
320
|
+
// This test will work when preferences are configured
|
|
321
|
+
// For now, just verify the function exists and handles missing hooks
|
|
322
|
+
const result = triggerHookManually("code-review", "execute-task", "M001/S01/T01", base);
|
|
323
|
+
// Result depends on whether code-review hook is configured in preferences
|
|
324
|
+
// The function should either return null or a valid HookDispatchResult
|
|
325
|
+
assertTrue(result === null || typeof result === "object", "returns null or object");
|
|
326
|
+
if (result) {
|
|
327
|
+
assertEq(result.hookName, "code-review", "hook name in result");
|
|
328
|
+
assertEq(result.unitType, "hook/code-review", "unit type is hook-prefixed");
|
|
329
|
+
assertEq(result.unitId, "M001/S01/T01", "unit ID preserved");
|
|
330
|
+
assertTrue(typeof result.prompt === "string", "prompt is a string");
|
|
331
|
+
}
|
|
332
|
+
} finally {
|
|
333
|
+
rmSync(base, { recursive: true, force: true });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
297
337
|
report();
|