gsd-pi 2.66.1-dev.ed243f2 → 2.67.0-dev.43b0159
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/dist/claude-cli-check.d.ts +8 -0
- package/dist/claude-cli-check.js +36 -0
- package/dist/cli.js +40 -0
- package/dist/onboarding.js +19 -2
- package/dist/resources/extensions/ask-user-questions.js +79 -11
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
- package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
- package/dist/resources/extensions/gsd/auto/loop.js +13 -1
- package/dist/resources/extensions/gsd/auto/phases.js +22 -3
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
- package/dist/resources/extensions/gsd/auto-model-selection.js +12 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +173 -25
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
- package/dist/resources/extensions/gsd/auto.js +13 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
- package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/dispatcher.js +1 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +94 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +49 -9
- package/dist/resources/extensions/gsd/context-store.js +134 -2
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
- package/dist/resources/extensions/gsd/detection.js +6 -0
- package/dist/resources/extensions/gsd/files.js +19 -2
- package/dist/resources/extensions/gsd/guided-flow.js +12 -8
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
- package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
- package/dist/resources/extensions/gsd/preferences.js +6 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
- package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
- package/dist/resources/extensions/remote-questions/manager.js +8 -0
- package/dist/resources/extensions/shared/interview-ui.js +10 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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/settings-data/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 +17 -17
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{6502.8874bcae249c02e1.js → 6502.b804e48b7919f55e.js} +3 -3
- package/dist/web/standalone/.next/static/chunks/{webpack-9fed74684e1c5bb1.js → webpack-65f0501b197d1c49.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +11 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
- package/packages/pi-ai/src/utils/json-parse.ts +11 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
- package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +16 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +58 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +69 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +66 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +9 -7
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +3 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +14 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +6 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
- package/packages/pi-tui/src/autocomplete.ts +9 -7
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
- package/packages/pi-tui/src/components/editor.ts +14 -3
- package/packages/pi-tui/src/stdin-buffer.ts +7 -0
- package/packages/pi-tui/src/tui.ts +9 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +103 -11
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
- package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +27 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
- package/src/resources/extensions/gsd/auto/session.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
- package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +195 -25
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
- package/src/resources/extensions/gsd/auto.ts +12 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
- package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/dispatcher.ts +1 -2
- package/src/resources/extensions/gsd/commands/handlers/core.ts +113 -8
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +49 -11
- package/src/resources/extensions/gsd/context-store.ts +167 -2
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
- package/src/resources/extensions/gsd/detection.ts +6 -0
- package/src/resources/extensions/gsd/files.ts +21 -2
- package/src/resources/extensions/gsd/guided-flow.ts +15 -8
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
- package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
- package/src/resources/extensions/gsd/preferences.ts +6 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +21 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
- package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +26 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
- package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -0
- package/src/resources/extensions/shared/interview-ui.ts +13 -0
- /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
2
|
+
const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
|
|
3
|
+
const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Path segment that identifies .gsd/ planning artifacts.
|
|
@@ -26,11 +28,53 @@ const QUEUE_SAFE_TOOLS = new Set([
|
|
|
26
28
|
*/
|
|
27
29
|
const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s)/;
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
const verifiedDepthMilestones = new Set<string>();
|
|
30
32
|
let activeQueuePhase = false;
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Discussion gate enforcement state.
|
|
36
|
+
*
|
|
37
|
+
* When ask_user_questions is called with a recognized gate question ID,
|
|
38
|
+
* we track the pending gate. Until the gate is confirmed (user selects the
|
|
39
|
+
* first/recommended option), all non-read-only tool calls are blocked.
|
|
40
|
+
* This mechanically prevents the model from rationalizing past failed or
|
|
41
|
+
* cancelled gate questions.
|
|
42
|
+
*/
|
|
43
|
+
let pendingGateId: string | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Recognized gate question ID patterns.
|
|
47
|
+
* These appear in both discuss-prepared.md (4-layer) and discuss.md (depth/requirements/roadmap).
|
|
48
|
+
*/
|
|
49
|
+
const GATE_QUESTION_PATTERNS = [
|
|
50
|
+
"layer1_scope_gate",
|
|
51
|
+
"layer2_architecture_gate",
|
|
52
|
+
"layer3_error_gate",
|
|
53
|
+
"layer4_quality_gate",
|
|
54
|
+
"depth_verification",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tools that are safe to call while a gate is pending.
|
|
59
|
+
* Includes read-only tools and ask_user_questions itself (so the model can re-ask).
|
|
60
|
+
*/
|
|
61
|
+
const GATE_SAFE_TOOLS = new Set([
|
|
62
|
+
"ask_user_questions",
|
|
63
|
+
"read", "grep", "find", "ls", "glob",
|
|
64
|
+
"search-the-web", "resolve_library", "get_library_docs", "fetch_page",
|
|
65
|
+
"search_and_read",
|
|
66
|
+
]);
|
|
67
|
+
|
|
32
68
|
export function isDepthVerified(): boolean {
|
|
33
|
-
return
|
|
69
|
+
return verifiedDepthMilestones.size > 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check whether a specific milestone has passed depth verification.
|
|
74
|
+
*/
|
|
75
|
+
export function isMilestoneDepthVerified(milestoneId: string | null | undefined): boolean {
|
|
76
|
+
if (!milestoneId) return false;
|
|
77
|
+
return verifiedDepthMilestones.has(milestoneId);
|
|
34
78
|
}
|
|
35
79
|
|
|
36
80
|
export function isQueuePhaseActive(): boolean {
|
|
@@ -42,16 +86,120 @@ export function setQueuePhaseActive(active: boolean): void {
|
|
|
42
86
|
}
|
|
43
87
|
|
|
44
88
|
export function resetWriteGateState(): void {
|
|
45
|
-
|
|
89
|
+
verifiedDepthMilestones.clear();
|
|
90
|
+
pendingGateId = null;
|
|
46
91
|
}
|
|
47
92
|
|
|
48
93
|
export function clearDiscussionFlowState(): void {
|
|
49
|
-
|
|
94
|
+
verifiedDepthMilestones.clear();
|
|
50
95
|
activeQueuePhase = false;
|
|
96
|
+
pendingGateId = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function markDepthVerified(milestoneId?: string | null): void {
|
|
100
|
+
if (!milestoneId) return;
|
|
101
|
+
verifiedDepthMilestones.add(milestoneId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check whether a question ID matches a recognized gate pattern.
|
|
106
|
+
*/
|
|
107
|
+
export function isGateQuestionId(questionId: string): boolean {
|
|
108
|
+
return GATE_QUESTION_PATTERNS.some(pattern => questionId.includes(pattern));
|
|
51
109
|
}
|
|
52
110
|
|
|
53
|
-
|
|
54
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Extract the milestone ID embedded in a depth-verification question id.
|
|
113
|
+
* Prompts are expected to use ids like `depth_verification_M001_confirm`.
|
|
114
|
+
*/
|
|
115
|
+
export function extractDepthVerificationMilestoneId(questionId: string): string | null {
|
|
116
|
+
const match = questionId.match(DEPTH_VERIFICATION_MILESTONE_RE);
|
|
117
|
+
return match?.[1] ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract the milestone ID from a milestone CONTEXT file path.
|
|
122
|
+
*/
|
|
123
|
+
function extractContextMilestoneId(inputPath: string): string | null {
|
|
124
|
+
const match = inputPath.match(CONTEXT_MILESTONE_RE);
|
|
125
|
+
return match?.[1] ?? null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
|
|
130
|
+
*/
|
|
131
|
+
export function setPendingGate(gateId: string): void {
|
|
132
|
+
pendingGateId = gateId;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clear the pending gate (called when the user confirms).
|
|
137
|
+
*/
|
|
138
|
+
export function clearPendingGate(): void {
|
|
139
|
+
pendingGateId = null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the currently pending gate, if any.
|
|
144
|
+
*/
|
|
145
|
+
export function getPendingGate(): string | null {
|
|
146
|
+
return pendingGateId;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check whether a tool call should be blocked because a discussion gate
|
|
151
|
+
* is pending (ask_user_questions was called but not confirmed).
|
|
152
|
+
*
|
|
153
|
+
* Returns { block: true, reason } if the tool should be blocked.
|
|
154
|
+
* Read-only tools and ask_user_questions itself are always allowed.
|
|
155
|
+
*/
|
|
156
|
+
export function shouldBlockPendingGate(
|
|
157
|
+
toolName: string,
|
|
158
|
+
_milestoneId: string | null,
|
|
159
|
+
_queuePhaseActive?: boolean,
|
|
160
|
+
): { block: boolean; reason?: string } {
|
|
161
|
+
if (!pendingGateId) return { block: false };
|
|
162
|
+
|
|
163
|
+
if (GATE_SAFE_TOOLS.has(toolName)) return { block: false };
|
|
164
|
+
|
|
165
|
+
// Bash read-only commands are also safe
|
|
166
|
+
if (toolName === "bash") return { block: false }; // bash is checked separately below
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
block: true,
|
|
170
|
+
reason: [
|
|
171
|
+
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
172
|
+
`You MUST re-call ask_user_questions with the gate question before making any other tool calls.`,
|
|
173
|
+
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
174
|
+
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
175
|
+
`Do NOT proceed, do NOT use alternative approaches, do NOT skip the gate.`,
|
|
176
|
+
].join(" "),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check whether a bash command should be blocked because a discussion gate is pending.
|
|
182
|
+
* Read-only bash commands are allowed; mutating commands are blocked.
|
|
183
|
+
*/
|
|
184
|
+
export function shouldBlockPendingGateBash(
|
|
185
|
+
command: string,
|
|
186
|
+
_milestoneId: string | null,
|
|
187
|
+
_queuePhaseActive?: boolean,
|
|
188
|
+
): { block: boolean; reason?: string } {
|
|
189
|
+
if (!pendingGateId) return { block: false };
|
|
190
|
+
|
|
191
|
+
// Allow read-only bash commands
|
|
192
|
+
if (BASH_READ_ONLY_RE.test(command)) return { block: false };
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
block: true,
|
|
196
|
+
reason: [
|
|
197
|
+
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
198
|
+
`You MUST re-call ask_user_questions with the gate question before running mutating commands.`,
|
|
199
|
+
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
200
|
+
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
201
|
+
].join(" "),
|
|
202
|
+
};
|
|
55
203
|
}
|
|
56
204
|
|
|
57
205
|
/**
|
|
@@ -87,16 +235,24 @@ export function shouldBlockContextWrite(
|
|
|
87
235
|
toolName: string,
|
|
88
236
|
inputPath: string,
|
|
89
237
|
milestoneId: string | null,
|
|
90
|
-
|
|
91
|
-
queuePhaseActive?: boolean,
|
|
238
|
+
_queuePhaseActive?: boolean,
|
|
92
239
|
): { block: boolean; reason?: string } {
|
|
93
240
|
if (toolName !== "write") return { block: false };
|
|
94
|
-
|
|
95
|
-
const inDiscussion = milestoneId !== null;
|
|
96
|
-
const inQueue = queuePhaseActive ?? false;
|
|
97
|
-
if (!inDiscussion && !inQueue) return { block: false };
|
|
98
241
|
if (!MILESTONE_CONTEXT_RE.test(inputPath)) return { block: false };
|
|
99
|
-
|
|
242
|
+
|
|
243
|
+
const targetMilestoneId = extractContextMilestoneId(inputPath) ?? milestoneId;
|
|
244
|
+
if (!targetMilestoneId) {
|
|
245
|
+
return {
|
|
246
|
+
block: true,
|
|
247
|
+
reason: [
|
|
248
|
+
`HARD BLOCK: Cannot write milestone CONTEXT.md without knowing which milestone it belongs to.`,
|
|
249
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
250
|
+
`Required action: call ask_user_questions with question id containing "depth_verification" and the milestone id.`,
|
|
251
|
+
].join(" "),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (isMilestoneDepthVerified(targetMilestoneId)) return { block: false };
|
|
100
256
|
|
|
101
257
|
return {
|
|
102
258
|
block: true,
|
|
@@ -110,6 +266,40 @@ export function shouldBlockContextWrite(
|
|
|
110
266
|
};
|
|
111
267
|
}
|
|
112
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Check whether a gsd_summary_save CONTEXT artifact should be blocked.
|
|
271
|
+
* Slice-level CONTEXT artifacts are allowed; milestone-level CONTEXT writes
|
|
272
|
+
* require the milestone to be depth-verified first.
|
|
273
|
+
*/
|
|
274
|
+
export function shouldBlockContextArtifactSave(
|
|
275
|
+
artifactType: string,
|
|
276
|
+
milestoneId: string | null,
|
|
277
|
+
sliceId?: string | null,
|
|
278
|
+
): { block: boolean; reason?: string } {
|
|
279
|
+
if (artifactType !== "CONTEXT") return { block: false };
|
|
280
|
+
if (sliceId) return { block: false };
|
|
281
|
+
if (!milestoneId) {
|
|
282
|
+
return {
|
|
283
|
+
block: true,
|
|
284
|
+
reason: [
|
|
285
|
+
`HARD BLOCK: Cannot save milestone CONTEXT without a milestone_id.`,
|
|
286
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
287
|
+
].join(" "),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (isMilestoneDepthVerified(milestoneId)) return { block: false };
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
block: true,
|
|
294
|
+
reason: [
|
|
295
|
+
`HARD BLOCK: Cannot save milestone CONTEXT without depth verification for ${milestoneId}.`,
|
|
296
|
+
`This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
|
|
297
|
+
`Required action: call ask_user_questions with question id containing "depth_verification_${milestoneId}".`,
|
|
298
|
+
`The user MUST select the "(Recommended)" confirmation option to unlock this gate.`,
|
|
299
|
+
].join(" "),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
113
303
|
/**
|
|
114
304
|
* Queue-mode execution guard (#2545).
|
|
115
305
|
*
|
|
@@ -155,7 +345,10 @@ export function shouldBlockQueueExecution(
|
|
|
155
345
|
};
|
|
156
346
|
}
|
|
157
347
|
|
|
158
|
-
// Unknown tools —
|
|
159
|
-
|
|
348
|
+
// Unknown tools — block by default in queue mode so custom tools cannot
|
|
349
|
+
// bypass execution restrictions.
|
|
350
|
+
return {
|
|
351
|
+
block: true,
|
|
352
|
+
reason: `Blocked: /gsd queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`,
|
|
353
|
+
};
|
|
160
354
|
}
|
|
161
|
-
|
|
@@ -15,7 +15,7 @@ export interface GsdCommandDefinition {
|
|
|
15
15
|
type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
|
|
16
16
|
|
|
17
17
|
export const GSD_COMMAND_DESCRIPTION =
|
|
18
|
-
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications";
|
|
18
|
+
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications";
|
|
19
19
|
|
|
20
20
|
export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
21
21
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -41,6 +41,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
41
41
|
{ cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
|
|
42
42
|
{ cmd: "export", desc: "Export milestone/slice results" },
|
|
43
43
|
{ cmd: "cleanup", desc: "Remove merged branches or snapshots" },
|
|
44
|
+
{ cmd: "model", desc: "Switch the active session model or open a picker" },
|
|
44
45
|
{ cmd: "mode", desc: "Switch workflow mode (solo/team)" },
|
|
45
46
|
{ cmd: "prefs", desc: "Manage preferences (model selection, timeouts, etc.)" },
|
|
46
47
|
{ cmd: "config", desc: "Set API keys for external tools" },
|
|
@@ -14,7 +14,7 @@ export async function handleGSDCommand(
|
|
|
14
14
|
const trimmed = (typeof args === "string" ? args : "").trim();
|
|
15
15
|
|
|
16
16
|
const handlers = [
|
|
17
|
-
() => handleCoreCommand(trimmed, ctx),
|
|
17
|
+
() => handleCoreCommand(trimmed, ctx, pi),
|
|
18
18
|
() => handleAutoCommand(trimmed, ctx, pi),
|
|
19
19
|
() => handleParallelCommand(trimmed, ctx, pi),
|
|
20
20
|
() => handleWorkflowCommand(trimmed, ctx, pi),
|
|
@@ -29,4 +29,3 @@ export async function handleGSDCommand(
|
|
|
29
29
|
|
|
30
30
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
31
31
|
}
|
|
32
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ExtensionCommandContext, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
2
|
+
import type { Model } from "@gsd/pi-ai";
|
|
2
3
|
import type { GSDState } from "../../types.js";
|
|
3
4
|
|
|
4
5
|
import { computeProgressScore, formatProgressLine } from "../../progress-score.js";
|
|
@@ -48,6 +49,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
48
49
|
"SETUP & CONFIGURATION",
|
|
49
50
|
" /gsd init Project init wizard — detect, configure, bootstrap .gsd/",
|
|
50
51
|
" /gsd setup Global setup status [llm|search|remote|keys|prefs]",
|
|
52
|
+
" /gsd model Switch active session model [provider/model|model-id]",
|
|
51
53
|
" /gsd mode Set workflow mode (solo/team) [global|project]",
|
|
52
54
|
" /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
|
53
55
|
" /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
|
@@ -84,8 +86,8 @@ export async function handleStatus(ctx: ExtensionCommandContext): Promise<void>
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const { GSDDashboardOverlay } = await import("../../dashboard-overlay.js");
|
|
87
|
-
const result = await ctx.ui.custom<
|
|
88
|
-
(tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()),
|
|
89
|
+
const result = await ctx.ui.custom<boolean>(
|
|
90
|
+
(tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)),
|
|
89
91
|
{
|
|
90
92
|
overlay: true,
|
|
91
93
|
overlayOptions: {
|
|
@@ -113,8 +115,8 @@ export async function handleVisualize(ctx: ExtensionCommandContext): Promise<voi
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
const { GSDVisualizerOverlay } = await import("../../visualizer-overlay.js");
|
|
116
|
-
const result = await ctx.ui.custom<
|
|
117
|
-
(tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done()),
|
|
118
|
+
const result = await ctx.ui.custom<boolean>(
|
|
119
|
+
(tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done(true)),
|
|
118
120
|
{
|
|
119
121
|
overlay: true,
|
|
120
122
|
overlayOptions: {
|
|
@@ -179,7 +181,106 @@ export async function handleSetup(args: string, ctx: ExtensionCommandContext): P
|
|
|
179
181
|
);
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
function sortModelsForSelection(models: Model<any>[], currentModel: Model<any> | undefined): Model<any>[] {
|
|
185
|
+
return [...models].sort((a, b) => {
|
|
186
|
+
const aCurrent = currentModel && a.provider === currentModel.provider && a.id === currentModel.id;
|
|
187
|
+
const bCurrent = currentModel && b.provider === currentModel.provider && b.id === currentModel.id;
|
|
188
|
+
if (aCurrent && !bCurrent) return -1;
|
|
189
|
+
if (!aCurrent && bCurrent) return 1;
|
|
190
|
+
const providerCmp = a.provider.localeCompare(b.provider);
|
|
191
|
+
if (providerCmp !== 0) return providerCmp;
|
|
192
|
+
return a.id.localeCompare(b.id);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function resolveRequestedModel(
|
|
197
|
+
query: string,
|
|
198
|
+
ctx: ExtensionCommandContext,
|
|
199
|
+
): Promise<Model<any> | undefined> {
|
|
200
|
+
const { resolveModelId } = await import("../../auto-model-selection.js");
|
|
201
|
+
const models = ctx.modelRegistry.getAvailable();
|
|
202
|
+
const exact = resolveModelId(query, models, ctx.model?.provider);
|
|
203
|
+
if (exact) return exact;
|
|
204
|
+
|
|
205
|
+
const lowerQuery = query.toLowerCase();
|
|
206
|
+
const partialMatches = models.filter((model) =>
|
|
207
|
+
model.id.toLowerCase().includes(lowerQuery)
|
|
208
|
+
|| `${model.provider}/${model.id}`.toLowerCase().includes(lowerQuery),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (partialMatches.length === 1) return partialMatches[0];
|
|
212
|
+
if (partialMatches.length === 0 || !ctx.hasUI) return undefined;
|
|
213
|
+
|
|
214
|
+
const sorted = sortModelsForSelection(partialMatches, ctx.model);
|
|
215
|
+
const optionToModel = new Map<string, Model<any>>();
|
|
216
|
+
const options = sorted.map((model) => {
|
|
217
|
+
const label = `${model.provider}/${model.id}`;
|
|
218
|
+
optionToModel.set(label, model);
|
|
219
|
+
return label;
|
|
220
|
+
});
|
|
221
|
+
options.push("(cancel)");
|
|
222
|
+
|
|
223
|
+
const choice = await ctx.ui.select(`Multiple models match "${query}" — choose one:`, options);
|
|
224
|
+
if (!choice || typeof choice !== "string" || choice === "(cancel)") return undefined;
|
|
225
|
+
return optionToModel.get(choice);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function handleModel(trimmedArgs: string, ctx: ExtensionCommandContext, pi: ExtensionAPI | undefined): Promise<void> {
|
|
229
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
230
|
+
if (availableModels.length === 0) {
|
|
231
|
+
ctx.ui.notify("No available models found. Check provider auth and model discovery.", "warning");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!pi) {
|
|
235
|
+
ctx.ui.notify("Model switching is unavailable in this context.", "warning");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const trimmed = trimmedArgs.trim();
|
|
240
|
+
let targetModel: Model<any> | undefined;
|
|
241
|
+
|
|
242
|
+
if (!trimmed) {
|
|
243
|
+
if (!ctx.hasUI) {
|
|
244
|
+
const current = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "(none)";
|
|
245
|
+
ctx.ui.notify(`Current model: ${current}\nUsage: /gsd model <provider/model|model-id>`, "info");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const optionToModel = new Map<string, Model<any>>();
|
|
250
|
+
const options = sortModelsForSelection(availableModels, ctx.model).map((model) => {
|
|
251
|
+
const isCurrent = ctx.model && model.provider === ctx.model.provider && model.id === ctx.model.id;
|
|
252
|
+
const label = `${isCurrent ? "* " : ""}${model.provider}/${model.id}`;
|
|
253
|
+
optionToModel.set(label, model);
|
|
254
|
+
return label;
|
|
255
|
+
});
|
|
256
|
+
options.push("(cancel)");
|
|
257
|
+
|
|
258
|
+
const choice = await ctx.ui.select("Select session model:", options);
|
|
259
|
+
if (!choice || typeof choice !== "string" || choice === "(cancel)") return;
|
|
260
|
+
targetModel = optionToModel.get(choice);
|
|
261
|
+
} else {
|
|
262
|
+
targetModel = await resolveRequestedModel(trimmed, ctx);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!targetModel) {
|
|
266
|
+
ctx.ui.notify(`Model "${trimmed}" not found. Use /gsd model with an exact provider/model or a unique model ID.`, "warning");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const ok = await pi.setModel(targetModel);
|
|
271
|
+
if (!ok) {
|
|
272
|
+
ctx.ui.notify(`No API key for ${targetModel.provider}/${targetModel.id}`, "warning");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
ctx.ui.notify(`Model: ${targetModel.provider}/${targetModel.id}`, "info");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export async function handleCoreCommand(
|
|
280
|
+
trimmed: string,
|
|
281
|
+
ctx: ExtensionCommandContext,
|
|
282
|
+
pi?: ExtensionAPI,
|
|
283
|
+
): Promise<boolean> {
|
|
183
284
|
if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
|
|
184
285
|
showHelp(ctx);
|
|
185
286
|
return true;
|
|
@@ -203,6 +304,10 @@ export async function handleCoreCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
203
304
|
ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
|
|
204
305
|
return true;
|
|
205
306
|
}
|
|
307
|
+
if (trimmed === "model" || trimmed.startsWith("model ")) {
|
|
308
|
+
await handleModel(trimmed.replace(/^model\s*/, "").trim(), ctx, pi);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
206
311
|
if (trimmed === "mode" || trimmed.startsWith("mode ")) {
|
|
207
312
|
const modeArgs = trimmed.replace(/^mode\s*/, "").trim();
|
|
208
313
|
const scope = modeArgs === "project" ? "project" : "global";
|
|
@@ -221,8 +326,8 @@ export async function handleCoreCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
221
326
|
}
|
|
222
327
|
if (trimmed === "show-config") {
|
|
223
328
|
const { GSDConfigOverlay, formatConfigText } = await import("../../config-overlay.js");
|
|
224
|
-
const result = await ctx.ui.custom<
|
|
225
|
-
(tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done()),
|
|
329
|
+
const result = await ctx.ui.custom<boolean>(
|
|
330
|
+
(tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done(true)),
|
|
226
331
|
{
|
|
227
332
|
overlay: true,
|
|
228
333
|
overlayOptions: {
|
|
@@ -165,10 +165,10 @@ export function buildCategorySummaries(prefs: Record<string, unknown>): Record<s
|
|
|
165
165
|
const modeSummary = mode ?? "(not set)";
|
|
166
166
|
|
|
167
167
|
// Models
|
|
168
|
-
const models = prefs.models as Record<string,
|
|
168
|
+
const models = prefs.models as Record<string, unknown> | undefined;
|
|
169
169
|
let modelsSummary = "(not configured)";
|
|
170
170
|
if (models && Object.keys(models).length > 0) {
|
|
171
|
-
const parts = Object.entries(models).map(([phase, model]) => `${phase}: ${model}`);
|
|
171
|
+
const parts = Object.entries(models).map(([phase, model]) => `${phase}: ${formatConfiguredModel(model)}`);
|
|
172
172
|
modelsSummary = parts.join(", ");
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -255,9 +255,38 @@ export function buildCategorySummaries(prefs: Record<string, unknown>): Record<s
|
|
|
255
255
|
|
|
256
256
|
// ─── Category configuration functions ────────────────────────────────────────
|
|
257
257
|
|
|
258
|
+
export function formatConfiguredModel(config: unknown): string {
|
|
259
|
+
if (typeof config === "string") return config;
|
|
260
|
+
if (!config || typeof config !== "object") return "(invalid)";
|
|
261
|
+
const maybeConfig = config as { model?: unknown; provider?: unknown };
|
|
262
|
+
if (typeof maybeConfig.model !== "string" || maybeConfig.model.trim() === "") return "(invalid)";
|
|
263
|
+
if (typeof maybeConfig.provider === "string" && maybeConfig.provider && !maybeConfig.model.includes("/")) {
|
|
264
|
+
return `${maybeConfig.provider}/${maybeConfig.model}`;
|
|
265
|
+
}
|
|
266
|
+
return maybeConfig.model;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function toPersistedModelId(provider: string, modelId: string): string {
|
|
270
|
+
if (!provider.trim()) return modelId;
|
|
271
|
+
const normalizedProvider = provider.trim();
|
|
272
|
+
const normalizedModelId = modelId.trim();
|
|
273
|
+
return normalizedModelId.startsWith(`${normalizedProvider}/`)
|
|
274
|
+
? normalizedModelId
|
|
275
|
+
: `${normalizedProvider}/${normalizedModelId}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
258
278
|
async function configureModels(ctx: ExtensionCommandContext, prefs: Record<string, unknown>): Promise<void> {
|
|
259
|
-
const modelPhases = [
|
|
260
|
-
|
|
279
|
+
const modelPhases = [
|
|
280
|
+
"research",
|
|
281
|
+
"planning",
|
|
282
|
+
"discuss",
|
|
283
|
+
"execution",
|
|
284
|
+
"execution_simple",
|
|
285
|
+
"completion",
|
|
286
|
+
"validation",
|
|
287
|
+
"subagent",
|
|
288
|
+
] as const;
|
|
289
|
+
const models: Record<string, unknown> = (prefs.models as Record<string, unknown>) ?? {};
|
|
261
290
|
|
|
262
291
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
263
292
|
if (availableModels.length > 0) {
|
|
@@ -277,15 +306,22 @@ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<strin
|
|
|
277
306
|
group.sort((a, b) => a.id.localeCompare(b.id));
|
|
278
307
|
}
|
|
279
308
|
|
|
280
|
-
//
|
|
309
|
+
// Display names for providers in the preferences wizard UI.
|
|
310
|
+
const PROVIDER_DISPLAY_NAMES: Record<string, string> = { anthropic: "anthropic-api" };
|
|
311
|
+
const displayName = (p: string) => PROVIDER_DISPLAY_NAMES[p] ?? p;
|
|
312
|
+
|
|
313
|
+
// Build provider menu with model counts (display name → real name lookup)
|
|
314
|
+
const displayToReal = new Map<string, string>();
|
|
281
315
|
const providerOptions = providers.map(p => {
|
|
282
316
|
const count = byProvider.get(p)!.length;
|
|
283
|
-
|
|
317
|
+
const label = `${displayName(p)} (${count} models)`;
|
|
318
|
+
displayToReal.set(label, p);
|
|
319
|
+
return label;
|
|
284
320
|
});
|
|
285
321
|
providerOptions.push("(keep current)", "(clear)", "(type manually)");
|
|
286
322
|
|
|
287
323
|
for (const phase of modelPhases) {
|
|
288
|
-
const current = models[phase]
|
|
324
|
+
const current = formatConfiguredModel(models[phase]);
|
|
289
325
|
const phaseLabel = `Model for ${phase} phase${current ? ` (current: ${current})` : ""}`;
|
|
290
326
|
|
|
291
327
|
// Step 1: pick provider
|
|
@@ -310,25 +346,25 @@ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<strin
|
|
|
310
346
|
}
|
|
311
347
|
|
|
312
348
|
// Step 2: pick model within provider
|
|
313
|
-
const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
349
|
+
const providerName = displayToReal.get(providerChoice) ?? providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
314
350
|
const group = byProvider.get(providerName);
|
|
315
351
|
if (!group) continue;
|
|
316
352
|
|
|
317
353
|
const modelOptions = group.map(m => m.id);
|
|
318
354
|
modelOptions.push("(keep current)", "(clear)");
|
|
319
355
|
|
|
320
|
-
const modelChoice = await ctx.ui.select(`${phaseLabel} — ${providerName}:`, modelOptions);
|
|
356
|
+
const modelChoice = await ctx.ui.select(`${phaseLabel} — ${displayName(providerName)}:`, modelOptions);
|
|
321
357
|
if (modelChoice && typeof modelChoice === "string" && modelChoice !== "(keep current)") {
|
|
322
358
|
if (modelChoice === "(clear)") {
|
|
323
359
|
delete models[phase];
|
|
324
360
|
} else {
|
|
325
|
-
models[phase] = modelChoice;
|
|
361
|
+
models[phase] = toPersistedModelId(providerName, modelChoice);
|
|
326
362
|
}
|
|
327
363
|
}
|
|
328
364
|
}
|
|
329
365
|
} else {
|
|
330
366
|
for (const phase of modelPhases) {
|
|
331
|
-
const current = models[phase]
|
|
367
|
+
const current = formatConfiguredModel(models[phase]);
|
|
332
368
|
const input = await ctx.ui.input(
|
|
333
369
|
`Model for ${phase} phase${current ? ` (current: ${current})` : ""}:`,
|
|
334
370
|
current || "e.g. claude-sonnet-4-20250514",
|
|
@@ -345,6 +381,8 @@ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<strin
|
|
|
345
381
|
}
|
|
346
382
|
if (Object.keys(models).length > 0) {
|
|
347
383
|
prefs.models = models;
|
|
384
|
+
} else {
|
|
385
|
+
delete prefs.models;
|
|
348
386
|
}
|
|
349
387
|
}
|
|
350
388
|
|