gsd-pi 2.77.0-dev.eaa4973bc → 2.78.0-dev.aeeb2ca00
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 +53 -17
- package/dist/claude-cli-check.js +46 -10
- package/dist/headless.js +49 -4
- package/dist/resource-loader.d.ts +40 -0
- package/dist/resource-loader.js +32 -13
- package/dist/resources/extensions/browser-tools/capture.js +9 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +72 -16
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
- package/dist/resources/extensions/github-sync/templates.js +103 -0
- package/dist/resources/extensions/google-search/index.js +3 -2
- package/dist/resources/extensions/gsd/auto/loop.js +124 -2
- package/dist/resources/extensions/gsd/auto/phases.js +57 -39
- package/dist/resources/extensions/gsd/auto/session.js +6 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +142 -29
- package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +150 -64
- package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
- package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
- package/dist/resources/extensions/gsd/auto-start.js +107 -29
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
- package/dist/resources/extensions/gsd/auto.js +76 -21
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
- package/dist/resources/extensions/gsd/component-loader.js +447 -0
- package/dist/resources/extensions/gsd/component-types.js +69 -0
- package/dist/resources/extensions/gsd/context-store.js +23 -7
- package/dist/resources/extensions/gsd/detection.js +49 -1
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/forensics.js +106 -0
- package/dist/resources/extensions/gsd/gate-registry.js +2 -2
- package/dist/resources/extensions/gsd/git-constants.js +28 -1
- package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
- package/dist/resources/extensions/gsd/git-service.js +126 -2
- package/dist/resources/extensions/gsd/gsd-db.js +6 -3
- package/dist/resources/extensions/gsd/guided-flow.js +39 -13
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
- package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
- package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
- package/dist/resources/extensions/gsd/model-router.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/session-lock.js +19 -10
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
- package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
- package/dist/resources/extensions/gsd/state.js +69 -58
- package/dist/resources/extensions/gsd/sync-lock.js +98 -42
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
- package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
- package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
- package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
- package/dist/resources/extensions/gsd/uok/writer.js +82 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +85 -8
- package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
- package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
- package/dist/resources/extensions/mcp-client/index.js +3 -1
- package/dist/resources/extensions/ollama/index.js +5 -1
- package/dist/resources/extensions/remote-questions/manager.js +11 -5
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -3
- package/packages/daemon/package.json +2 -2
- package/packages/daemon/src/logger.ts +4 -3
- package/packages/mcp-server/dist/server.d.ts +24 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +88 -87
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +15 -6
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/mcp-server.test.ts +25 -3
- package/packages/mcp-server/src/readers/graph.test.ts +87 -15
- package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
- package/packages/mcp-server/src/server.ts +131 -105
- package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
- package/packages/mcp-server/src/workflow-tools.ts +19 -6
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +2 -2
- package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
- package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
- package/packages/native/src/__tests__/ps.test.mjs +14 -8
- package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
- package/packages/native/src/__tests__/truncate.test.mjs +17 -2
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
- package/packages/pi-agent-core/src/agent.test.ts +96 -102
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.js +9 -2
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
- package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +43 -70
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +36 -11
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +44 -0
- package/packages/pi-ai/src/models/capability-patches.ts +10 -2
- package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
- package/packages/pi-ai/src/models/generated/openai.ts +17 -0
- package/packages/pi-ai/src/models.generated.test.ts +46 -73
- package/packages/pi-ai/src/models.test.ts +48 -11
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -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 +105 -13
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
- package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
- package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
- package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
- package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +37 -11
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
- package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +14 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +19 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.js.map +1 -1
- package/packages/pi-tui/dist/editor-component.d.ts +2 -0
- package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/packages/pi-tui/dist/editor-component.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +42 -11
- package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
- package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
- package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
- package/packages/pi-tui/src/components/editor.ts +22 -0
- package/packages/pi-tui/src/components/image.test.ts +10 -5
- package/packages/pi-tui/src/editor-component.ts +3 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/dist/rpc-client.test.js +101 -51
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/src/rpc-client.test.ts +109 -52
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/package.json +1 -1
- package/scripts/install.js +15 -1
- package/src/resources/extensions/browser-tools/capture.ts +12 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +75 -16
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
- package/src/resources/extensions/github-sync/templates.ts +151 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
- package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
- package/src/resources/extensions/google-search/index.ts +3 -2
- package/src/resources/extensions/gsd/auto/loop.ts +142 -2
- package/src/resources/extensions/gsd/auto/phases.ts +62 -38
- package/src/resources/extensions/gsd/auto/session.ts +7 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +156 -29
- package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +163 -73
- package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
- package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
- package/src/resources/extensions/gsd/auto-start.ts +127 -9
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
- package/src/resources/extensions/gsd/auto.ts +90 -23
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
- package/src/resources/extensions/gsd/component-loader.ts +598 -0
- package/src/resources/extensions/gsd/component-types.ts +362 -0
- package/src/resources/extensions/gsd/context-store.ts +25 -8
- package/src/resources/extensions/gsd/detection.ts +58 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/forensics.ts +118 -1
- package/src/resources/extensions/gsd/gate-registry.ts +2 -2
- package/src/resources/extensions/gsd/git-constants.ts +30 -1
- package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
- package/src/resources/extensions/gsd/git-service.ts +149 -2
- package/src/resources/extensions/gsd/gsd-db.ts +6 -3
- package/src/resources/extensions/gsd/guided-flow.ts +57 -14
- package/src/resources/extensions/gsd/journal.ts +11 -1
- package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
- package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
- package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
- package/src/resources/extensions/gsd/model-router.ts +6 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/session-lock.ts +20 -10
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
- package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
- package/src/resources/extensions/gsd/state.ts +76 -66
- package/src/resources/extensions/gsd/sync-lock.ts +97 -39
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
- package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +133 -292
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
- package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
- package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
- package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
- package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
- package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
- package/src/resources/extensions/gsd/types.ts +3 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
- package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
- package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
- package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
- package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
- package/src/resources/extensions/gsd/uok/writer.ts +113 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +108 -7
- package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
- package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
- package/src/resources/extensions/mcp-client/index.ts +3 -1
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
- package/src/resources/extensions/ollama/index.ts +5 -1
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
- package/src/resources/extensions/remote-questions/manager.ts +36 -4
- package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
- package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
- /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression coverage for the model-policy dispatch bugs (#4959, #4681, #4850).
|
|
3
|
+
*
|
|
4
|
+
* The five tests here pin the four fix layers documented in the RCA on #4959:
|
|
5
|
+
*
|
|
6
|
+
* 1. Vacuous-truth guard: with an empty unit-required tool subset and an
|
|
7
|
+
* otherwise-permitted model, dispatch must succeed. Without this test,
|
|
8
|
+
* an over-aggressive Change 1 (e.g. always denying) would still pass any
|
|
9
|
+
* "no longer throws" assertion trivially.
|
|
10
|
+
* 2. Cross-unit poisoning: per-unit narrowing at the bottom of
|
|
11
|
+
* `selectAndApplyModel` must NOT bleed into the next unit's policy
|
|
12
|
+
* evaluation. The baseline-restore path (Change 2) must restore the
|
|
13
|
+
* pre-dispatch active-tool set before policy runs.
|
|
14
|
+
* 3. Genuinely-impossible negative: when the workflow REQUIRES a tool no
|
|
15
|
+
* candidate model can carry, dispatch must throw
|
|
16
|
+
* `ModelPolicyDispatchBlockedError` — proving Change 1 didn't accidentally
|
|
17
|
+
* remove gating, and Change 3 wired the typed error.
|
|
18
|
+
* 4. Restore happened: assert call ordering on a recording fake — the
|
|
19
|
+
* baseline `setActiveTools` call must precede the next `selectAndApplyModel`
|
|
20
|
+
* reading the active set.
|
|
21
|
+
* 5. Error message carries reason: the throw must include the per-model
|
|
22
|
+
* `tool policy denied (...)` reason fragment from `applyModelPolicyFilter`,
|
|
23
|
+
* so users can act on the failure without digging through audit events.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import test from "node:test";
|
|
27
|
+
import assert from "node:assert/strict";
|
|
28
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
selectAndApplyModel,
|
|
34
|
+
ModelPolicyDispatchBlockedError,
|
|
35
|
+
clearToolBaseline,
|
|
36
|
+
} from "../auto-model-selection.js";
|
|
37
|
+
import {
|
|
38
|
+
registerToolCompatibility,
|
|
39
|
+
resetToolCompatibilityRegistry,
|
|
40
|
+
} from "@gsd/pi-coding-agent";
|
|
41
|
+
|
|
42
|
+
function makeTempProject(): { dir: string; cleanup: () => void; restoreEnv: () => void } {
|
|
43
|
+
const originalCwd = process.cwd();
|
|
44
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
45
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-policy-poison-"));
|
|
46
|
+
const home = mkdtempSync(join(tmpdir(), "gsd-policy-home-"));
|
|
47
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
48
|
+
// Empty PREFERENCES so default uok.model_policy.enabled = true applies.
|
|
49
|
+
writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), "---\n---\n", "utf-8");
|
|
50
|
+
process.env.GSD_HOME = home;
|
|
51
|
+
process.chdir(dir);
|
|
52
|
+
return {
|
|
53
|
+
dir,
|
|
54
|
+
cleanup: () => {
|
|
55
|
+
rmSync(dir, { recursive: true, force: true });
|
|
56
|
+
rmSync(home, { recursive: true, force: true });
|
|
57
|
+
},
|
|
58
|
+
restoreEnv: () => {
|
|
59
|
+
process.chdir(originalCwd);
|
|
60
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
61
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface RecordingPi {
|
|
67
|
+
setModel: (m: { provider: string; id: string }) => Promise<boolean>;
|
|
68
|
+
emitBeforeModelSelect: () => Promise<undefined>;
|
|
69
|
+
getActiveTools: () => string[];
|
|
70
|
+
emitAdjustToolSet: () => Promise<undefined>;
|
|
71
|
+
setActiveTools: (names: string[]) => void;
|
|
72
|
+
setThinkingLevel: () => void;
|
|
73
|
+
__calls: Array<{ kind: string; payload: unknown }>;
|
|
74
|
+
__activeTools: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeRecordingPi(initialActiveTools: string[]): RecordingPi {
|
|
78
|
+
const calls: Array<{ kind: string; payload: unknown }> = [];
|
|
79
|
+
let active = [...initialActiveTools];
|
|
80
|
+
return {
|
|
81
|
+
__calls: calls,
|
|
82
|
+
get __activeTools() { return active; },
|
|
83
|
+
setModel: async (m) => {
|
|
84
|
+
calls.push({ kind: "setModel", payload: `${m.provider}/${m.id}` });
|
|
85
|
+
return true;
|
|
86
|
+
},
|
|
87
|
+
emitBeforeModelSelect: async () => {
|
|
88
|
+
calls.push({ kind: "emitBeforeModelSelect", payload: null });
|
|
89
|
+
return undefined;
|
|
90
|
+
},
|
|
91
|
+
getActiveTools: () => {
|
|
92
|
+
calls.push({ kind: "getActiveTools", payload: [...active] });
|
|
93
|
+
return [...active];
|
|
94
|
+
},
|
|
95
|
+
emitAdjustToolSet: async () => {
|
|
96
|
+
calls.push({ kind: "emitAdjustToolSet", payload: null });
|
|
97
|
+
return undefined;
|
|
98
|
+
},
|
|
99
|
+
setActiveTools: (names) => {
|
|
100
|
+
active = [...names];
|
|
101
|
+
calls.push({ kind: "setActiveTools", payload: [...names] });
|
|
102
|
+
},
|
|
103
|
+
setThinkingLevel: () => {},
|
|
104
|
+
} as RecordingPi;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function makeCtx(availableModels: Array<{ id: string; provider: string; api: string }>) {
|
|
108
|
+
return {
|
|
109
|
+
modelRegistry: {
|
|
110
|
+
getAvailable: () => availableModels,
|
|
111
|
+
getProviderAuthMode: () => "apiKey",
|
|
112
|
+
},
|
|
113
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
114
|
+
ui: { notify: () => {} },
|
|
115
|
+
model: { provider: availableModels[0]?.provider, id: availableModels[0]?.id, api: availableModels[0]?.api },
|
|
116
|
+
} as any;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── 1. Vacuous-truth guard ──────────────────────────────────────────────────
|
|
120
|
+
//
|
|
121
|
+
// Two scenarios pin the empty-requiredTools branch and a permitted-tool branch.
|
|
122
|
+
// Without the empty-list scenario, a regression that mishandles `requiredTools = []`
|
|
123
|
+
// (e.g. by treating an empty array as "deny all" or by null-derefing the helper
|
|
124
|
+
// return) would still pass.
|
|
125
|
+
|
|
126
|
+
test("vacuous-truth (a): unit type with empty workflow-required tools → dispatch succeeds", async () => {
|
|
127
|
+
const env = makeTempProject();
|
|
128
|
+
try {
|
|
129
|
+
// `refine-slice` is not in the getRequiredWorkflowToolsForAutoUnit switch
|
|
130
|
+
// → returns []. Exercises the empty-requiredTools branch in
|
|
131
|
+
// applyModelPolicyFilter (CodeRabbit Minor: existing test used
|
|
132
|
+
// gate-evaluate which has non-empty required tools and never hit this path).
|
|
133
|
+
//
|
|
134
|
+
// PREFERENCES with tier_models is required so resolvePreferredModelConfig
|
|
135
|
+
// returns a non-undefined modelConfig — only then does selectAndApplyModel
|
|
136
|
+
// run the policy filter we want to exercise.
|
|
137
|
+
writeFileSync(
|
|
138
|
+
join(env.dir, ".gsd", "PREFERENCES.md"),
|
|
139
|
+
["---", "dynamic_routing:", " enabled: true", " tier_models:", " heavy: anthropic/claude-sonnet-4-6", "---"].join("\n"),
|
|
140
|
+
"utf-8",
|
|
141
|
+
);
|
|
142
|
+
const availableModels = [
|
|
143
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
144
|
+
];
|
|
145
|
+
const pi = makeRecordingPi([]);
|
|
146
|
+
clearToolBaseline(pi as unknown as object);
|
|
147
|
+
|
|
148
|
+
const result = await selectAndApplyModel(
|
|
149
|
+
makeCtx(availableModels),
|
|
150
|
+
pi as any,
|
|
151
|
+
"refine-slice",
|
|
152
|
+
"x1",
|
|
153
|
+
env.dir,
|
|
154
|
+
undefined,
|
|
155
|
+
false,
|
|
156
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
157
|
+
undefined,
|
|
158
|
+
true,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
assert.equal(result.appliedModel?.id, "claude-sonnet-4-6", "empty requiredTools must not deny dispatch");
|
|
162
|
+
const setModelCalls = pi.__calls.filter(c => c.kind === "setModel");
|
|
163
|
+
assert.equal(setModelCalls.length, 1, "setModel should have been called exactly once");
|
|
164
|
+
} finally {
|
|
165
|
+
env.restoreEnv();
|
|
166
|
+
env.cleanup();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("vacuous-truth (b): non-empty workflow tool requirement that the model carries → dispatch succeeds", async () => {
|
|
171
|
+
const env = makeTempProject();
|
|
172
|
+
try {
|
|
173
|
+
// gate-evaluate has tool requirement ["gsd_save_gate_result"]; if the
|
|
174
|
+
// model's API can carry it, policy must still allow dispatch. Counter-test
|
|
175
|
+
// to (a): proves the path with a non-empty requirement isn't denying
|
|
176
|
+
// legitimate dispatches.
|
|
177
|
+
const availableModels = [
|
|
178
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
179
|
+
];
|
|
180
|
+
const pi = makeRecordingPi(["gsd_save_gate_result"]);
|
|
181
|
+
clearToolBaseline(pi as unknown as object);
|
|
182
|
+
|
|
183
|
+
const result = await selectAndApplyModel(
|
|
184
|
+
makeCtx(availableModels),
|
|
185
|
+
pi as any,
|
|
186
|
+
"gate-evaluate",
|
|
187
|
+
"g1",
|
|
188
|
+
env.dir,
|
|
189
|
+
undefined,
|
|
190
|
+
false,
|
|
191
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
192
|
+
undefined,
|
|
193
|
+
true,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
assert.equal(result.appliedModel?.id, "claude-sonnet-4-6", "compat-required dispatch must succeed");
|
|
197
|
+
const setModelCalls = pi.__calls.filter(c => c.kind === "setModel");
|
|
198
|
+
assert.equal(setModelCalls.length, 1, "setModel should have been called exactly once");
|
|
199
|
+
} finally {
|
|
200
|
+
env.restoreEnv();
|
|
201
|
+
env.cleanup();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ─── 2. Cross-unit poisoning ─────────────────────────────────────────────────
|
|
206
|
+
test("cross-unit poisoning: prior unit narrowing must not deny next unit's eligible model", async () => {
|
|
207
|
+
const env = makeTempProject();
|
|
208
|
+
try {
|
|
209
|
+
// Unit-N runs against an `openai-completions` provider that strips a tool
|
|
210
|
+
// (e.g. "thinking_partner") via adjustToolSet's hard filter. Without the
|
|
211
|
+
// baseline-restore (Change 2), pi.getActiveTools() afterward is missing
|
|
212
|
+
// that tool, but if we used it as the policy required-set we'd erroneously
|
|
213
|
+
// deny the next unit. With Change 1+2, policy uses the workflow-required
|
|
214
|
+
// subset (NOT the live snapshot), and baseline restoration re-seeds the
|
|
215
|
+
// active set before the next unit.
|
|
216
|
+
const availableModels = [
|
|
217
|
+
{ id: "openai-narrow", provider: "openai", api: "openai-completions" },
|
|
218
|
+
{ id: "claude-wide", provider: "anthropic", api: "anthropic-messages" },
|
|
219
|
+
];
|
|
220
|
+
// The baseline contains a synthetic "thinking_partner" that openai-completions
|
|
221
|
+
// does not support.
|
|
222
|
+
const pi = makeRecordingPi(["gsd_save_gate_result", "thinking_partner"]);
|
|
223
|
+
clearToolBaseline(pi as unknown as object);
|
|
224
|
+
|
|
225
|
+
// Unit-N: dispatch on openai/openai-narrow. Soft adjustToolSet will narrow
|
|
226
|
+
// the active set, simulating production poisoning.
|
|
227
|
+
await selectAndApplyModel(
|
|
228
|
+
makeCtx(availableModels),
|
|
229
|
+
pi as any,
|
|
230
|
+
"gate-evaluate",
|
|
231
|
+
"n",
|
|
232
|
+
env.dir,
|
|
233
|
+
undefined,
|
|
234
|
+
false,
|
|
235
|
+
{ provider: "openai", id: "openai-narrow" },
|
|
236
|
+
undefined,
|
|
237
|
+
true,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const setModelCallsAfterUnitN = pi.__calls.filter(c => c.kind === "setModel").length;
|
|
241
|
+
assert.ok(setModelCallsAfterUnitN >= 1, "unit-N should have dispatched");
|
|
242
|
+
|
|
243
|
+
// Unit-N+1: now dispatch with claude-wide. If active-tool snapshot were
|
|
244
|
+
// still the policy required-set, the previous narrowing wouldn't matter
|
|
245
|
+
// (anthropic-messages can carry both tools), so we instead simulate the
|
|
246
|
+
// 4959 path: a second unit whose workflow requires "gsd_save_gate_result"
|
|
247
|
+
// (small) — must succeed reaching pi.setModel for claude-wide.
|
|
248
|
+
const beforeCount = pi.__calls.filter(c => c.kind === "setModel").length;
|
|
249
|
+
await selectAndApplyModel(
|
|
250
|
+
makeCtx(availableModels),
|
|
251
|
+
pi as any,
|
|
252
|
+
"gate-evaluate",
|
|
253
|
+
"n+1",
|
|
254
|
+
env.dir,
|
|
255
|
+
undefined,
|
|
256
|
+
false,
|
|
257
|
+
{ provider: "anthropic", id: "claude-wide" },
|
|
258
|
+
undefined,
|
|
259
|
+
true,
|
|
260
|
+
);
|
|
261
|
+
const afterCount = pi.__calls.filter(c => c.kind === "setModel").length;
|
|
262
|
+
assert.ok(afterCount > beforeCount, "unit-N+1 should reach pi.setModel — cross-unit narrowing must not block dispatch");
|
|
263
|
+
} finally {
|
|
264
|
+
env.restoreEnv();
|
|
265
|
+
env.cleanup();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ─── 3a. Genuinely-impossible: tool-compatibility denial path ────────────────
|
|
270
|
+
//
|
|
271
|
+
// Exercises the real `getRequiredWorkflowToolsForAutoUnit` →
|
|
272
|
+
// `filterToolsForProvider` path that #4959 was about (CodeRabbit Minor:
|
|
273
|
+
// existing 3b test used cross-provider denial which never hit this path).
|
|
274
|
+
// Registers `gsd_plan_slice` as `producesImages: true`, then offers only an
|
|
275
|
+
// `ollama-chat` candidate (which has `imageToolResults: false`) — the
|
|
276
|
+
// workflow-required tool is incompatible with the candidate's API, so the
|
|
277
|
+
// policy filter denies the model with a `tool policy denied (...)` reason.
|
|
278
|
+
test("genuinely-impossible (a): workflow tool incompatible with candidate API → typed error names tool + api", async () => {
|
|
279
|
+
const env = makeTempProject();
|
|
280
|
+
try {
|
|
281
|
+
// Register the workflow tool as image-producing for the duration of this
|
|
282
|
+
// test. afterEach() resets the registry below.
|
|
283
|
+
registerToolCompatibility("gsd_plan_slice", { producesImages: true });
|
|
284
|
+
|
|
285
|
+
// PREFERENCES needs tier_models so resolvePreferredModelConfig returns a
|
|
286
|
+
// non-undefined modelConfig — without that, selectAndApplyModel skips the
|
|
287
|
+
// entire policy block and we never reach the tool-compat denial path.
|
|
288
|
+
writeFileSync(
|
|
289
|
+
join(env.dir, ".gsd", "PREFERENCES.md"),
|
|
290
|
+
["---", "dynamic_routing:", " enabled: true", " tier_models:", " heavy: ollama/ollama-llama-3", "---"].join("\n"),
|
|
291
|
+
"utf-8",
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const availableModels = [
|
|
295
|
+
{ id: "ollama-llama-3", provider: "ollama", api: "ollama-chat" },
|
|
296
|
+
];
|
|
297
|
+
const pi = makeRecordingPi(["gsd_plan_slice"]);
|
|
298
|
+
clearToolBaseline(pi as unknown as object);
|
|
299
|
+
|
|
300
|
+
const ctx = makeCtx(availableModels);
|
|
301
|
+
// Same provider as candidate so the cross-provider gate doesn't fire —
|
|
302
|
+
// we want this denial to come from tool-compatibility, not provider mismatch.
|
|
303
|
+
ctx.model = { provider: "ollama", id: "ollama-llama-3", api: "ollama-chat" };
|
|
304
|
+
|
|
305
|
+
let thrown: unknown;
|
|
306
|
+
try {
|
|
307
|
+
await selectAndApplyModel(
|
|
308
|
+
ctx,
|
|
309
|
+
pi as any,
|
|
310
|
+
"plan-slice",
|
|
311
|
+
"s1",
|
|
312
|
+
env.dir,
|
|
313
|
+
undefined,
|
|
314
|
+
false,
|
|
315
|
+
{ provider: "ollama", id: "ollama-llama-3" },
|
|
316
|
+
undefined,
|
|
317
|
+
true,
|
|
318
|
+
);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
thrown = e;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
assert.ok(thrown instanceof ModelPolicyDispatchBlockedError, "should throw ModelPolicyDispatchBlockedError");
|
|
324
|
+
const err = thrown as ModelPolicyDispatchBlockedError;
|
|
325
|
+
assert.equal(err.unitType, "plan-slice");
|
|
326
|
+
assert.match(err.message, /tool policy denied/, "throw must surface the tool-compatibility deny reason");
|
|
327
|
+
assert.match(err.message, /gsd_plan_slice/, "throw must name the incompatible tool");
|
|
328
|
+
assert.match(err.message, /ollama-chat/, "throw must name the api for which the tool was filtered");
|
|
329
|
+
} finally {
|
|
330
|
+
resetToolCompatibilityRegistry();
|
|
331
|
+
env.restoreEnv();
|
|
332
|
+
env.cleanup();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ─── 3b. Genuinely-impossible: cross-provider denial path ────────────────────
|
|
337
|
+
test("genuinely-impossible (b): cross-provider routing disabled + provider mismatch → typed error", async () => {
|
|
338
|
+
const env = makeTempProject();
|
|
339
|
+
try {
|
|
340
|
+
// Use plan-slice (workflow-required: ["gsd_plan_slice"]) but pretend no
|
|
341
|
+
// candidate model can carry it. The simplest way: provide a model whose
|
|
342
|
+
// api is a fictitious "no-tools" string — `filterToolsForProvider` returns
|
|
343
|
+
// every tool as filtered for an unknown api with toolCalling=false, OR we
|
|
344
|
+
// can pick a real api that also denies the tool. We use an api that
|
|
345
|
+
// exists but has known incompatibility — no such case is portable, so we
|
|
346
|
+
// fall back to a model whose api is recognized to deny `gsd_plan_slice`.
|
|
347
|
+
//
|
|
348
|
+
// Pragmatic approach: monkey the policy via `allowCrossProvider=false` +
|
|
349
|
+
// a single candidate model on a *different* provider than current, which
|
|
350
|
+
// makes EVERY candidate denied for cross-provider-routing reasons. This
|
|
351
|
+
// exercises the same throw path with a deterministic deny reason.
|
|
352
|
+
const availableModels = [
|
|
353
|
+
{ id: "other-model", provider: "other-provider", api: "anthropic-messages" },
|
|
354
|
+
];
|
|
355
|
+
const pi = makeRecordingPi([]);
|
|
356
|
+
clearToolBaseline(pi as unknown as object);
|
|
357
|
+
|
|
358
|
+
const ctx = makeCtx(availableModels);
|
|
359
|
+
// currentProvider mismatches → cross-provider denial when disabled.
|
|
360
|
+
ctx.model = { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" };
|
|
361
|
+
|
|
362
|
+
// Set dynamic_routing.cross_provider=false via PREFERENCES so the policy
|
|
363
|
+
// disables cross-provider routing.
|
|
364
|
+
writeFileSync(
|
|
365
|
+
join(env.dir, ".gsd", "PREFERENCES.md"),
|
|
366
|
+
["---", "dynamic_routing:", " enabled: true", " cross_provider: false", " tier_models:", " heavy: other-provider/other-model", "---"].join("\n"),
|
|
367
|
+
"utf-8",
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
let thrown: unknown;
|
|
371
|
+
try {
|
|
372
|
+
await selectAndApplyModel(
|
|
373
|
+
ctx,
|
|
374
|
+
pi as any,
|
|
375
|
+
"plan-slice",
|
|
376
|
+
"s1",
|
|
377
|
+
env.dir,
|
|
378
|
+
undefined,
|
|
379
|
+
false,
|
|
380
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
381
|
+
undefined,
|
|
382
|
+
true,
|
|
383
|
+
);
|
|
384
|
+
} catch (e) {
|
|
385
|
+
thrown = e;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
assert.ok(thrown instanceof ModelPolicyDispatchBlockedError, "should throw ModelPolicyDispatchBlockedError");
|
|
389
|
+
const err = thrown as ModelPolicyDispatchBlockedError;
|
|
390
|
+
assert.equal(err.unitType, "plan-slice");
|
|
391
|
+
assert.equal(err.unitId, "s1");
|
|
392
|
+
assert.ok(err.reasons.length > 0, "deny reasons should be captured");
|
|
393
|
+
} finally {
|
|
394
|
+
env.restoreEnv();
|
|
395
|
+
env.cleanup();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// ─── 4. Restore happened ─────────────────────────────────────────────────────
|
|
400
|
+
test("restore baseline: setActiveTools(BASELINE) called between units before next dispatch", async () => {
|
|
401
|
+
const env = makeTempProject();
|
|
402
|
+
try {
|
|
403
|
+
const availableModels = [
|
|
404
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
405
|
+
];
|
|
406
|
+
const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
|
|
407
|
+
const pi = makeRecordingPi(baselineTools);
|
|
408
|
+
clearToolBaseline(pi as unknown as object);
|
|
409
|
+
|
|
410
|
+
// First call captures the baseline.
|
|
411
|
+
await selectAndApplyModel(
|
|
412
|
+
makeCtx(availableModels),
|
|
413
|
+
pi as any,
|
|
414
|
+
"gate-evaluate",
|
|
415
|
+
"u1",
|
|
416
|
+
env.dir,
|
|
417
|
+
undefined,
|
|
418
|
+
false,
|
|
419
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
420
|
+
undefined,
|
|
421
|
+
true,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Simulate a downstream caller narrowing the tool set (post-unit poisoning).
|
|
425
|
+
pi.setActiveTools(["gsd_save_gate_result"]);
|
|
426
|
+
const callsBeforeU2 = pi.__calls.length;
|
|
427
|
+
|
|
428
|
+
// Second call should restore the baseline before reading anything.
|
|
429
|
+
await selectAndApplyModel(
|
|
430
|
+
makeCtx(availableModels),
|
|
431
|
+
pi as any,
|
|
432
|
+
"gate-evaluate",
|
|
433
|
+
"u2",
|
|
434
|
+
env.dir,
|
|
435
|
+
undefined,
|
|
436
|
+
false,
|
|
437
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
438
|
+
undefined,
|
|
439
|
+
true,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const u2Calls = pi.__calls.slice(callsBeforeU2);
|
|
443
|
+
const restoreCall = u2Calls.find(
|
|
444
|
+
c => c.kind === "setActiveTools"
|
|
445
|
+
&& Array.isArray(c.payload)
|
|
446
|
+
&& (c.payload as string[]).length === baselineTools.length
|
|
447
|
+
&& baselineTools.every(t => (c.payload as string[]).includes(t)),
|
|
448
|
+
);
|
|
449
|
+
assert.ok(restoreCall, "setActiveTools(BASELINE) must be called during u2's selectAndApplyModel before dispatch");
|
|
450
|
+
|
|
451
|
+
const restoreIdx = u2Calls.indexOf(restoreCall!);
|
|
452
|
+
const setModelIdx = u2Calls.findIndex(c => c.kind === "setModel");
|
|
453
|
+
assert.ok(setModelIdx > restoreIdx, "baseline restore must precede setModel dispatch");
|
|
454
|
+
} finally {
|
|
455
|
+
env.restoreEnv();
|
|
456
|
+
env.cleanup();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// ─── 5. Error message carries reason ─────────────────────────────────────────
|
|
461
|
+
test("error carries deny reason fragment from applyModelPolicyFilter", async () => {
|
|
462
|
+
const env = makeTempProject();
|
|
463
|
+
try {
|
|
464
|
+
writeFileSync(
|
|
465
|
+
join(env.dir, ".gsd", "PREFERENCES.md"),
|
|
466
|
+
["---", "dynamic_routing:", " enabled: true", " cross_provider: false", " tier_models:", " heavy: other-provider/other-model", "---"].join("\n"),
|
|
467
|
+
"utf-8",
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const availableModels = [
|
|
471
|
+
{ id: "other-model", provider: "other-provider", api: "anthropic-messages" },
|
|
472
|
+
];
|
|
473
|
+
const pi = makeRecordingPi([]);
|
|
474
|
+
clearToolBaseline(pi as unknown as object);
|
|
475
|
+
|
|
476
|
+
const ctx = makeCtx(availableModels);
|
|
477
|
+
ctx.model = { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" };
|
|
478
|
+
|
|
479
|
+
let thrown: Error | undefined;
|
|
480
|
+
try {
|
|
481
|
+
await selectAndApplyModel(
|
|
482
|
+
ctx,
|
|
483
|
+
pi as any,
|
|
484
|
+
"plan-slice",
|
|
485
|
+
"s1",
|
|
486
|
+
env.dir,
|
|
487
|
+
undefined,
|
|
488
|
+
false,
|
|
489
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
490
|
+
undefined,
|
|
491
|
+
true,
|
|
492
|
+
);
|
|
493
|
+
} catch (e) {
|
|
494
|
+
thrown = e as Error;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
assert.ok(thrown, "should throw");
|
|
498
|
+
// The cross-provider denial path produces:
|
|
499
|
+
// "cross-provider routing disabled (other-provider != anthropic)"
|
|
500
|
+
assert.match(
|
|
501
|
+
thrown!.message,
|
|
502
|
+
/cross-provider routing disabled/,
|
|
503
|
+
"thrown error message should include the per-model deny reason",
|
|
504
|
+
);
|
|
505
|
+
assert.match(thrown!.message, /other-provider\/other-model/, "should name the rejected model");
|
|
506
|
+
} finally {
|
|
507
|
+
env.restoreEnv();
|
|
508
|
+
env.cleanup();
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ─── 6. Lifecycle: clearToolBaseline forces recapture (CodeRabbit Major) ─────
|
|
513
|
+
//
|
|
514
|
+
// The WeakMap baseline is keyed per `pi` instance, but auto sessions are NOT
|
|
515
|
+
// 1:1 with `pi` instances — a single `pi` can host multiple `/gsd auto` runs
|
|
516
|
+
// separated by stops, manual tool edits, or extension toggles. Without
|
|
517
|
+
// `clearToolBaseline(pi)` at session boundaries, the SECOND auto run on the
|
|
518
|
+
// same `pi` would silently restore the FIRST run's snapshot and undo whatever
|
|
519
|
+
// tool changes the user made between sessions. This test pins the contract
|
|
520
|
+
// that `clearToolBaseline` causes the next dispatch to RECAPTURE from the
|
|
521
|
+
// live active set rather than restoring the prior snapshot.
|
|
522
|
+
test("lifecycle: clearToolBaseline forces recapture; subsequent runs respect intervening tool edits", async () => {
|
|
523
|
+
const env = makeTempProject();
|
|
524
|
+
try {
|
|
525
|
+
const availableModels = [
|
|
526
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
527
|
+
];
|
|
528
|
+
const pi = makeRecordingPi(["A", "B", "C"]);
|
|
529
|
+
clearToolBaseline(pi as unknown as object);
|
|
530
|
+
|
|
531
|
+
// ── Run 1: captures baseline [A, B, C] ──
|
|
532
|
+
await selectAndApplyModel(
|
|
533
|
+
makeCtx(availableModels),
|
|
534
|
+
pi as any,
|
|
535
|
+
"gate-evaluate",
|
|
536
|
+
"u1",
|
|
537
|
+
env.dir,
|
|
538
|
+
undefined,
|
|
539
|
+
false,
|
|
540
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
541
|
+
undefined,
|
|
542
|
+
true,
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
// ── Simulate `/gsd auto` stop + intervening user tool edit ──
|
|
546
|
+
// (auto.ts calls clearToolBaseline in stopAuto; the user then mutates
|
|
547
|
+
// tools while auto is paused.)
|
|
548
|
+
clearToolBaseline(pi as unknown as object);
|
|
549
|
+
pi.setActiveTools(["A", "B"]); // user removed C between sessions
|
|
550
|
+
|
|
551
|
+
// ── Run 2: must capture [A, B] as the NEW baseline, not restore [A, B, C] ──
|
|
552
|
+
const callsBeforeU2 = pi.__calls.length;
|
|
553
|
+
await selectAndApplyModel(
|
|
554
|
+
makeCtx(availableModels),
|
|
555
|
+
pi as any,
|
|
556
|
+
"gate-evaluate",
|
|
557
|
+
"u2",
|
|
558
|
+
env.dir,
|
|
559
|
+
undefined,
|
|
560
|
+
false,
|
|
561
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
562
|
+
undefined,
|
|
563
|
+
true,
|
|
564
|
+
);
|
|
565
|
+
const u2Calls = pi.__calls.slice(callsBeforeU2);
|
|
566
|
+
// No setActiveTools(["A", "B", "C"]) call should appear during u2 — that
|
|
567
|
+
// would be the bug (restoring the run-1 snapshot over the user's edit).
|
|
568
|
+
const staleRestore = u2Calls.find(
|
|
569
|
+
c => c.kind === "setActiveTools"
|
|
570
|
+
&& Array.isArray(c.payload)
|
|
571
|
+
&& (c.payload as string[]).includes("C"),
|
|
572
|
+
);
|
|
573
|
+
assert.equal(
|
|
574
|
+
staleRestore,
|
|
575
|
+
undefined,
|
|
576
|
+
"after clearToolBaseline, run 2 must NOT restore the run-1 snapshot containing tool C",
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
// ── Run 3 (no clear): mutate to [A], expect restore to [A, B] (run-2 baseline) ──
|
|
580
|
+
pi.setActiveTools(["A"]);
|
|
581
|
+
const callsBeforeU3 = pi.__calls.length;
|
|
582
|
+
await selectAndApplyModel(
|
|
583
|
+
makeCtx(availableModels),
|
|
584
|
+
pi as any,
|
|
585
|
+
"gate-evaluate",
|
|
586
|
+
"u3",
|
|
587
|
+
env.dir,
|
|
588
|
+
undefined,
|
|
589
|
+
false,
|
|
590
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
591
|
+
undefined,
|
|
592
|
+
true,
|
|
593
|
+
);
|
|
594
|
+
const u3Calls = pi.__calls.slice(callsBeforeU3);
|
|
595
|
+
const restoreToRun2Baseline = u3Calls.find(
|
|
596
|
+
c => c.kind === "setActiveTools"
|
|
597
|
+
&& Array.isArray(c.payload)
|
|
598
|
+
&& (c.payload as string[]).length === 2
|
|
599
|
+
&& (c.payload as string[]).includes("A")
|
|
600
|
+
&& (c.payload as string[]).includes("B")
|
|
601
|
+
&& !(c.payload as string[]).includes("C"),
|
|
602
|
+
);
|
|
603
|
+
assert.ok(
|
|
604
|
+
restoreToRun2Baseline,
|
|
605
|
+
"run 3 must restore the run-2 baseline [A, B] — proves the recaptured baseline is in use, not the run-1 snapshot",
|
|
606
|
+
);
|
|
607
|
+
} finally {
|
|
608
|
+
env.restoreEnv();
|
|
609
|
+
env.cleanup();
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ─── 7. Cross-mode isolation (#4965) ─────────────────────────────────────────
|
|
614
|
+
//
|
|
615
|
+
// `selectAndApplyModel` is called from two places: auto-mode (`isAutoMode=true`,
|
|
616
|
+
// from auto/phases.ts) and guided-flow (`isAutoMode=false`, from guided-flow.ts).
|
|
617
|
+
// The baseline lifecycle (clearToolBaseline) is owned by startAuto/stopAuto —
|
|
618
|
+
// guided-flow has no equivalent clear hook. If `restoreToolBaseline` ran
|
|
619
|
+
// unconditionally, an interactive guided-flow dispatch on a `pi` that previously
|
|
620
|
+
// hosted an auto session would resurrect the auto-era baseline and silently
|
|
621
|
+
// overwrite any user tool edits made between the auto and guided dispatches.
|
|
622
|
+
// Therefore the restore is gated by `isAutoMode`. Guided-flow has its own
|
|
623
|
+
// narrow/restore discipline via discuss-tool-scoping at guided-flow.ts:587-622.
|
|
624
|
+
|
|
625
|
+
test("cross-mode (#4965): isAutoMode=false does NOT restore baseline even when one is recorded", async () => {
|
|
626
|
+
const env = makeTempProject();
|
|
627
|
+
try {
|
|
628
|
+
const availableModels = [
|
|
629
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
630
|
+
];
|
|
631
|
+
const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
|
|
632
|
+
const pi = makeRecordingPi(baselineTools);
|
|
633
|
+
clearToolBaseline(pi as unknown as object);
|
|
634
|
+
|
|
635
|
+
// ── Step 1: auto-mode call captures baseline [A, B, C] ──
|
|
636
|
+
await selectAndApplyModel(
|
|
637
|
+
makeCtx(availableModels),
|
|
638
|
+
pi as any,
|
|
639
|
+
"gate-evaluate",
|
|
640
|
+
"u-auto",
|
|
641
|
+
env.dir,
|
|
642
|
+
undefined,
|
|
643
|
+
false,
|
|
644
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
645
|
+
undefined,
|
|
646
|
+
/* isAutoMode */ true,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// ── Step 2: simulate user tool edit between auto and guided dispatches ──
|
|
650
|
+
pi.setActiveTools(["only_user_kept_tool"]);
|
|
651
|
+
const callsBeforeGuided = pi.__calls.length;
|
|
652
|
+
|
|
653
|
+
// ── Step 3: guided-flow dispatch (isAutoMode=false) ──
|
|
654
|
+
await selectAndApplyModel(
|
|
655
|
+
makeCtx(availableModels),
|
|
656
|
+
pi as any,
|
|
657
|
+
"gate-evaluate",
|
|
658
|
+
"u-guided",
|
|
659
|
+
env.dir,
|
|
660
|
+
undefined,
|
|
661
|
+
false,
|
|
662
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
663
|
+
undefined,
|
|
664
|
+
/* isAutoMode */ false,
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const guidedCalls = pi.__calls.slice(callsBeforeGuided);
|
|
668
|
+
// The bug we're guarding against: a setActiveTools call during the guided
|
|
669
|
+
// dispatch that contains the auto-era baseline tools (which would mean the
|
|
670
|
+
// auto-captured baseline resurrected and overwrote the user's edit).
|
|
671
|
+
const baselineRestore = guidedCalls.find(
|
|
672
|
+
c => c.kind === "setActiveTools"
|
|
673
|
+
&& Array.isArray(c.payload)
|
|
674
|
+
&& baselineTools.every(t => (c.payload as string[]).includes(t)),
|
|
675
|
+
);
|
|
676
|
+
assert.equal(
|
|
677
|
+
baselineRestore,
|
|
678
|
+
undefined,
|
|
679
|
+
"guided-flow dispatch (isAutoMode=false) must NOT restore the auto-mode baseline",
|
|
680
|
+
);
|
|
681
|
+
} finally {
|
|
682
|
+
env.restoreEnv();
|
|
683
|
+
env.cleanup();
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test("cross-mode (#4965): auto → guided → auto preserves the original auto-era baseline for the second auto run", async () => {
|
|
688
|
+
const env = makeTempProject();
|
|
689
|
+
try {
|
|
690
|
+
const availableModels = [
|
|
691
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
692
|
+
];
|
|
693
|
+
const baselineTools = ["gsd_save_gate_result", "tool_a", "tool_b"];
|
|
694
|
+
const pi = makeRecordingPi(baselineTools);
|
|
695
|
+
clearToolBaseline(pi as unknown as object);
|
|
696
|
+
|
|
697
|
+
// Auto run 1 — captures baseline.
|
|
698
|
+
await selectAndApplyModel(
|
|
699
|
+
makeCtx(availableModels), pi as any, "gate-evaluate", "u1",
|
|
700
|
+
env.dir, undefined, false,
|
|
701
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
702
|
+
undefined, /* isAutoMode */ true,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
// Guided dispatch in between — must not corrupt the baseline.
|
|
706
|
+
pi.setActiveTools(["narrow_for_guided"]);
|
|
707
|
+
await selectAndApplyModel(
|
|
708
|
+
makeCtx(availableModels), pi as any, "gate-evaluate", "u-guided",
|
|
709
|
+
env.dir, undefined, false,
|
|
710
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
711
|
+
undefined, /* isAutoMode */ false,
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
// Now narrow further (simulating any post-guided state) and run auto u2.
|
|
715
|
+
pi.setActiveTools(["something_completely_different"]);
|
|
716
|
+
const callsBeforeU2 = pi.__calls.length;
|
|
717
|
+
|
|
718
|
+
// Auto run 2 — must restore the ORIGINAL auto-era baseline, not the
|
|
719
|
+
// intervening narrow-for-guided state.
|
|
720
|
+
await selectAndApplyModel(
|
|
721
|
+
makeCtx(availableModels), pi as any, "gate-evaluate", "u2",
|
|
722
|
+
env.dir, undefined, false,
|
|
723
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
724
|
+
undefined, /* isAutoMode */ true,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
const u2Calls = pi.__calls.slice(callsBeforeU2);
|
|
728
|
+
const restoreCall = u2Calls.find(
|
|
729
|
+
c => c.kind === "setActiveTools"
|
|
730
|
+
&& Array.isArray(c.payload)
|
|
731
|
+
&& (c.payload as string[]).length === baselineTools.length
|
|
732
|
+
&& baselineTools.every(t => (c.payload as string[]).includes(t)),
|
|
733
|
+
);
|
|
734
|
+
assert.ok(
|
|
735
|
+
restoreCall,
|
|
736
|
+
"auto run 2 must restore the auto-era baseline [A, B, C] — proves guided-flow didn't corrupt it",
|
|
737
|
+
);
|
|
738
|
+
} finally {
|
|
739
|
+
env.restoreEnv();
|
|
740
|
+
env.cleanup();
|
|
741
|
+
}
|
|
742
|
+
});
|