gsd-pi 2.69.0 → 2.70.0-dev.7ebda5e
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/loader.js +4 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +150 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +33 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
- package/dist/resources/extensions/gsd/auto-start.js +25 -1
- package/dist/resources/extensions/gsd/auto.js +12 -8
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -2
- package/dist/resources/extensions/gsd/commands-cmux.js +30 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
- package/dist/resources/extensions/gsd/doctor-format.js +2 -0
- package/dist/resources/extensions/gsd/guided-flow.js +21 -10
- package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
- package/dist/resources/extensions/gsd/validate-directory.js +30 -12
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -6
- package/dist/resources/extensions/slash-commands/audit.js +2 -1
- package/dist/resources/extensions/subagent/isolation.js +4 -2
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +30 -27
- package/dist/update-cmd.js +3 -11
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/required-server-files.json +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- 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/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/web-mode.js +4 -0
- package/package.json +11 -11
- package/packages/daemon/src/orchestrator.ts +9 -84
- package/packages/mcp-server/README.md +25 -3
- package/packages/mcp-server/dist/cli.d.ts +0 -1
- package/packages/mcp-server/dist/cli.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli.js +4 -2
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +32 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +118 -1
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/tool-credentials.d.ts +6 -0
- package/packages/mcp-server/dist/tool-credentials.d.ts.map +1 -0
- package/packages/mcp-server/dist/tool-credentials.js +90 -0
- package/packages/mcp-server/dist/tool-credentials.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts +3 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +308 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/cli.ts +5 -3
- package/packages/mcp-server/src/import-candidates.test.ts +48 -0
- package/packages/mcp-server/src/mcp-server.test.ts +85 -1
- package/packages/mcp-server/src/server.ts +188 -1
- package/packages/mcp-server/src/tool-credentials.test.ts +95 -0
- package/packages/mcp-server/src/tool-credentials.ts +97 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +32 -25
- package/packages/mcp-server/src/workflow-tools.ts +398 -2
- package/packages/pi-agent-core/dist/agent.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +3 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +82 -0
- package/packages/pi-agent-core/src/agent.ts +12 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +1 -23
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.d.ts +3 -2
- package/packages/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.js +3 -5
- package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +1 -31
- package/packages/pi-ai/src/utils/oauth/index.ts +3 -5
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +38 -15
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/lsp/config.ts +43 -17
- package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -5
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +227 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +172 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +39 -25
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
- package/src/resources/extensions/gsd/auto-start.ts +34 -1
- package/src/resources/extensions/gsd/auto.ts +12 -8
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +9 -5
- package/src/resources/extensions/gsd/commands-cmux.ts +32 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
- package/src/resources/extensions/gsd/doctor-format.ts +1 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +24 -8
- package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +67 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +48 -7
- package/src/resources/extensions/gsd/validate-directory.ts +33 -11
- package/src/resources/extensions/gsd/workflow-mcp.ts +74 -5
- package/src/resources/extensions/slash-commands/audit.ts +2 -1
- package/src/resources/extensions/subagent/isolation.ts +4 -3
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts +0 -17
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +0 -106
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +0 -140
- /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → yvFbuOJuph5517lR7HBt2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → yvFbuOJuph5517lR7HBt2}/_ssgManifest.js +0 -0
|
@@ -25,10 +25,17 @@ export interface ModelSelectionResult {
|
|
|
25
25
|
export function resolvePreferredModelConfig(
|
|
26
26
|
unitType: string,
|
|
27
27
|
autoModeStartModel: { provider: string; id: string } | null,
|
|
28
|
+
/** When false, only return explicit per-phase model configs — do not
|
|
29
|
+
* synthesize a routing ceiling from dynamic_routing.tier_models (#3962). */
|
|
30
|
+
isAutoMode = true,
|
|
28
31
|
) {
|
|
29
32
|
const explicitConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
30
33
|
if (explicitConfig) return explicitConfig;
|
|
31
34
|
|
|
35
|
+
// In interactive mode, don't synthesize a routing-based model config.
|
|
36
|
+
// The user's session model (/model) should be used as-is (#3962).
|
|
37
|
+
if (!isAutoMode) return undefined;
|
|
38
|
+
|
|
32
39
|
const routingConfig = resolveDynamicRoutingConfig();
|
|
33
40
|
if (!routingConfig.enabled || !routingConfig.tier_models) return undefined;
|
|
34
41
|
|
|
@@ -62,8 +69,11 @@ export async function selectAndApplyModel(
|
|
|
62
69
|
verbose: boolean,
|
|
63
70
|
autoModeStartModel: { provider: string; id: string } | null,
|
|
64
71
|
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
72
|
+
/** When false (interactive/guided-flow), skip dynamic routing and use the session model.
|
|
73
|
+
* Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */
|
|
74
|
+
isAutoMode = true,
|
|
65
75
|
): Promise<ModelSelectionResult> {
|
|
66
|
-
const modelConfig = resolvePreferredModelConfig(unitType, autoModeStartModel);
|
|
76
|
+
const modelConfig = resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode);
|
|
67
77
|
let routing: { tier: string; modelDowngraded: boolean } | null = null;
|
|
68
78
|
let appliedModel: Model<Api> | null = null;
|
|
69
79
|
|
|
@@ -71,7 +81,13 @@ export async function selectAndApplyModel(
|
|
|
71
81
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
72
82
|
|
|
73
83
|
// ─── Dynamic Model Routing ─────────────────────────────────────────
|
|
84
|
+
// Dynamic routing (complexity-based downgrading) only applies in auto-mode.
|
|
85
|
+
// Interactive/guided-flow dispatches use the user's session model directly,
|
|
86
|
+
// respecting their /model selection without silent downgrades (#3962).
|
|
74
87
|
const routingConfig = resolveDynamicRoutingConfig();
|
|
88
|
+
if (!isAutoMode) {
|
|
89
|
+
routingConfig.enabled = false;
|
|
90
|
+
}
|
|
75
91
|
let effectiveModelConfig = modelConfig;
|
|
76
92
|
let routingTierLabel = "";
|
|
77
93
|
|
|
@@ -123,12 +139,11 @@ export async function selectAndApplyModel(
|
|
|
123
139
|
const escalated = escalateTier(retryContext.previousTier as ComplexityTier);
|
|
124
140
|
if (escalated) {
|
|
125
141
|
classification = { ...classification, tier: escalated, reason: "escalated after failure" };
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
142
|
+
// Always notify on tier escalation — model changes should be visible (#3962)
|
|
143
|
+
ctx.ui.notify(
|
|
144
|
+
`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`,
|
|
145
|
+
"info",
|
|
146
|
+
);
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
|
|
@@ -195,24 +210,23 @@ export async function selectAndApplyModel(
|
|
|
195
210
|
primary: routingResult.modelId,
|
|
196
211
|
fallbacks: routingResult.fallbacks,
|
|
197
212
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
213
|
+
// Always notify on model downgrade — users should see when their
|
|
214
|
+
// model selection is overridden, not just in verbose mode (#3962).
|
|
215
|
+
if (routingResult.selectionMethod === "capability-scored" && routingResult.capabilityScores) {
|
|
216
|
+
const tierLbl = tierLabel(classification.tier);
|
|
217
|
+
const scores = Object.entries(routingResult.capabilityScores)
|
|
218
|
+
.sort(([, a], [, b]) => b - a)
|
|
219
|
+
.map(([id, score]) => `${id}: ${score.toFixed(1)}`)
|
|
220
|
+
.join(", ");
|
|
221
|
+
ctx.ui.notify(
|
|
222
|
+
`Dynamic routing [${tierLbl}]: ${routingResult.modelId} (capability-scored) — ${scores}`,
|
|
223
|
+
"info",
|
|
224
|
+
);
|
|
225
|
+
} else {
|
|
226
|
+
ctx.ui.notify(
|
|
227
|
+
`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${classification.reason})`,
|
|
228
|
+
"info",
|
|
229
|
+
);
|
|
216
230
|
}
|
|
217
231
|
}
|
|
218
232
|
routingTierLabel = ` [${tierLabel(classification.tier)}]`;
|
|
@@ -997,7 +997,7 @@ export async function buildDiscussMilestonePrompt(mid: string, midTitle: string,
|
|
|
997
997
|
milestoneId: mid,
|
|
998
998
|
milestoneTitle: midTitle,
|
|
999
999
|
inlinedTemplates: discussTemplates,
|
|
1000
|
-
structuredQuestionsAvailable: "
|
|
1000
|
+
structuredQuestionsAvailable: "false",
|
|
1001
1001
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1002
1002
|
fastPathInstruction: "",
|
|
1003
1003
|
});
|
|
@@ -1503,7 +1503,9 @@ export async function buildCompleteMilestonePrompt(
|
|
|
1503
1503
|
try {
|
|
1504
1504
|
const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
|
|
1505
1505
|
if (isDbAvailable()) {
|
|
1506
|
-
sliceIds = getMilestoneSlices(mid)
|
|
1506
|
+
sliceIds = getMilestoneSlices(mid)
|
|
1507
|
+
.filter(s => s.status !== "skipped")
|
|
1508
|
+
.map(s => s.id);
|
|
1507
1509
|
}
|
|
1508
1510
|
} catch (err) {
|
|
1509
1511
|
logWarning("prompt", `buildCompleteMilestonePrompt DB lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1597,7 +1599,9 @@ export async function buildValidateMilestonePrompt(
|
|
|
1597
1599
|
try {
|
|
1598
1600
|
const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
|
|
1599
1601
|
if (isDbAvailable()) {
|
|
1600
|
-
valSliceIds = getMilestoneSlices(mid)
|
|
1602
|
+
valSliceIds = getMilestoneSlices(mid)
|
|
1603
|
+
.filter(s => s.status !== "skipped")
|
|
1604
|
+
.map(s => s.id);
|
|
1601
1605
|
}
|
|
1602
1606
|
} catch (err) {
|
|
1603
1607
|
logWarning("prompt", `buildValidateMilestonePrompt slice IDs lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -83,7 +83,7 @@ import { join } from "node:path";
|
|
|
83
83
|
import { sep as pathSep } from "node:path";
|
|
84
84
|
|
|
85
85
|
import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
|
|
86
|
-
import { resolveDefaultSessionModel } from "./preferences-models.js";
|
|
86
|
+
import { resolveDefaultSessionModel, resolveDynamicRoutingConfig } from "./preferences-models.js";
|
|
87
87
|
import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
88
88
|
|
|
89
89
|
export interface BootstrapDeps {
|
|
@@ -778,6 +778,39 @@ export async function bootstrapAutoSession(
|
|
|
778
778
|
: "Will loop until milestone complete.";
|
|
779
779
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
780
780
|
|
|
781
|
+
// Show dynamic routing status so users know upfront if models will be
|
|
782
|
+
// downgraded for simple tasks (#3962).
|
|
783
|
+
// Use the same effective logic as selectAndApplyModel: check flat-rate
|
|
784
|
+
// provider suppression and resolve the actual ceiling model.
|
|
785
|
+
const routingConfig = resolveDynamicRoutingConfig();
|
|
786
|
+
const startModelLabel = s.autoModeStartModel
|
|
787
|
+
? `${s.autoModeStartModel.provider}/${s.autoModeStartModel.id}`
|
|
788
|
+
: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "default";
|
|
789
|
+
|
|
790
|
+
// Flat-rate providers (e.g. GitHub Copilot, claude-code) suppress routing
|
|
791
|
+
// at dispatch time (#3453) — reflect that in the banner.
|
|
792
|
+
const { isFlatRateProvider } = await import("./auto-model-selection.js");
|
|
793
|
+
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
794
|
+
const effectivelyEnabled = routingConfig.enabled
|
|
795
|
+
&& !(effectiveProvider && isFlatRateProvider(effectiveProvider));
|
|
796
|
+
|
|
797
|
+
// The actual ceiling may come from tier_models.heavy, not the start model.
|
|
798
|
+
const effectiveCeiling = (routingConfig.enabled && routingConfig.tier_models?.heavy)
|
|
799
|
+
? routingConfig.tier_models.heavy
|
|
800
|
+
: startModelLabel;
|
|
801
|
+
|
|
802
|
+
if (effectivelyEnabled) {
|
|
803
|
+
ctx.ui.notify(
|
|
804
|
+
`Dynamic routing: enabled — simple tasks may use cheaper models (ceiling: ${effectiveCeiling})`,
|
|
805
|
+
"info",
|
|
806
|
+
);
|
|
807
|
+
} else {
|
|
808
|
+
ctx.ui.notify(
|
|
809
|
+
`Dynamic routing: disabled — all tasks will use ${startModelLabel}`,
|
|
810
|
+
"info",
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
781
814
|
updateSessionLock(
|
|
782
815
|
lockBase(),
|
|
783
816
|
"starting",
|
|
@@ -125,9 +125,9 @@ import {
|
|
|
125
125
|
} from "./metrics.js";
|
|
126
126
|
import { setLogBasePath, logWarning, logError } from "./workflow-logger.js";
|
|
127
127
|
import { homedir } from "node:os";
|
|
128
|
-
import { join
|
|
128
|
+
import { join } from "node:path";
|
|
129
|
+
import { pathToFileURL } from "node:url";
|
|
129
130
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
130
|
-
import { createRequire } from "node:module";
|
|
131
131
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
132
132
|
import {
|
|
133
133
|
autoCommitCurrentBranch,
|
|
@@ -1334,13 +1334,17 @@ export async function startAuto(
|
|
|
1334
1334
|
restoreHookState(s.basePath);
|
|
1335
1335
|
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
1336
1336
|
// bundled extension updates before resume-time verification/state logic runs.
|
|
1337
|
+
// GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
|
|
1338
|
+
// The relative import ("../../../resource-loader.js") only works from the source
|
|
1339
|
+
// tree; deployed extensions live at ~/.gsd/agent/extensions/gsd/ where the
|
|
1340
|
+
// relative path resolves to ~/.gsd/agent/resource-loader.js which doesn't exist.
|
|
1341
|
+
// Using GSD_PKG_ROOT constructs a correct absolute path in both contexts (#3949).
|
|
1337
1342
|
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
const { initResources } = await import(join(pkgRoot, "dist", "resource-loader.js"));
|
|
1343
|
+
const pkgRoot = process.env.GSD_PKG_ROOT;
|
|
1344
|
+
const resourceLoaderPath = pkgRoot
|
|
1345
|
+
? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href
|
|
1346
|
+
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1347
|
+
const { initResources } = await import(resourceLoaderPath);
|
|
1344
1348
|
initResources(agentDir);
|
|
1345
1349
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1346
1350
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
@@ -19,6 +19,7 @@ import { deriveState } from "../state.js";
|
|
|
19
19
|
import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
|
|
20
20
|
import { toPosixPath } from "../../shared/mod.js";
|
|
21
21
|
import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.js";
|
|
22
|
+
import { autoEnableCmuxPreferences } from "../commands-cmux.js";
|
|
22
23
|
|
|
23
24
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
24
25
|
|
|
@@ -76,13 +77,16 @@ export async function buildBeforeAgentStartResult(
|
|
|
76
77
|
shortcutDashboard: formatShortcut("Ctrl+Alt+G"),
|
|
77
78
|
shortcutShell: formatShortcut("Ctrl+Alt+B"),
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
let loadedPreferences = loadEffectiveGSDPreferences();
|
|
80
81
|
if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
|
|
81
82
|
markCmuxPromptShown();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (autoEnableCmuxPreferences()) {
|
|
84
|
+
loadedPreferences = loadEffectiveGSDPreferences();
|
|
85
|
+
ctx.ui.notify(
|
|
86
|
+
"cmux detected — auto-enabled. Run /gsd cmux off to disable.",
|
|
87
|
+
"info",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
let preferenceBlock = "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
|
|
4
4
|
import { saveFile } from "./files.js";
|
|
5
5
|
import {
|
|
@@ -9,6 +9,37 @@ import {
|
|
|
9
9
|
} from "./preferences.js";
|
|
10
10
|
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Auto-enable cmux in project preferences when detected but never configured.
|
|
14
|
+
* Called at boot (before agent start) — no ExtensionCommandContext needed.
|
|
15
|
+
* Returns true if preferences were written, false if skipped.
|
|
16
|
+
*/
|
|
17
|
+
export function autoEnableCmuxPreferences(): boolean {
|
|
18
|
+
const path = getProjectGSDPreferencesPath();
|
|
19
|
+
if (!existsSync(path)) return false;
|
|
20
|
+
|
|
21
|
+
const existing = loadProjectGSDPreferences();
|
|
22
|
+
const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : { version: 1 };
|
|
23
|
+
prefs.cmux = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
notifications: true,
|
|
26
|
+
sidebar: true,
|
|
27
|
+
splits: false,
|
|
28
|
+
browser: false,
|
|
29
|
+
...((prefs.cmux as Record<string, unknown> | undefined) ?? {}),
|
|
30
|
+
};
|
|
31
|
+
(prefs.cmux as Record<string, unknown>).enabled = true;
|
|
32
|
+
prefs.version = prefs.version || 1;
|
|
33
|
+
|
|
34
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
35
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
36
|
+
const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
|
|
37
|
+
if (preserved) body = preserved;
|
|
38
|
+
|
|
39
|
+
writeFileSync(path, `---\n${frontmatter}---${body}`, "utf-8");
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
12
43
|
function extractBodyAfterFrontmatter(content: string): string | null {
|
|
13
44
|
const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
|
|
14
45
|
if (start === -1) return null;
|
|
@@ -25,6 +25,26 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
|
25
25
|
import { projectRoot } from "./commands/context.js";
|
|
26
26
|
import { loadPrompt } from "./prompt-loader.js";
|
|
27
27
|
|
|
28
|
+
const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
|
|
29
|
+
const UPDATE_FETCH_TIMEOUT_MS = 5000;
|
|
30
|
+
|
|
31
|
+
async function fetchLatestVersionForCommand(): Promise<string | null> {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
|
|
37
|
+
if (!res.ok) return null;
|
|
38
|
+
const data = (await res.json()) as { version?: string };
|
|
39
|
+
const latest = typeof data.version === "string" ? data.version.trim().replace(/^v/, "") : "";
|
|
40
|
+
return latest.length > 0 ? latest : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
29
49
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
30
50
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
@@ -394,13 +414,8 @@ export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void>
|
|
|
394
414
|
|
|
395
415
|
ctx.ui.notify(`Current version: v${current}\nChecking npm registry...`, "info");
|
|
396
416
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
latest = execSync(`npm view ${NPM_PACKAGE} version`, {
|
|
400
|
-
encoding: "utf-8",
|
|
401
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
402
|
-
}).trim();
|
|
403
|
-
} catch {
|
|
417
|
+
const latest = await fetchLatestVersionForCommand();
|
|
418
|
+
if (!latest) {
|
|
404
419
|
ctx.ui.notify("Failed to reach npm registry. Check your network connection.", "error");
|
|
405
420
|
return;
|
|
406
421
|
}
|
|
@@ -13,6 +13,20 @@ export async function checkEngineHealth(
|
|
|
13
13
|
issues: DoctorIssue[],
|
|
14
14
|
fixesApplied: string[],
|
|
15
15
|
): Promise<void> {
|
|
16
|
+
const dbPath = join(basePath, ".gsd", "gsd.db");
|
|
17
|
+
|
|
18
|
+
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
19
|
+
issues.push({
|
|
20
|
+
severity: "warning",
|
|
21
|
+
code: "db_unavailable",
|
|
22
|
+
scope: "project",
|
|
23
|
+
unitId: "project",
|
|
24
|
+
message: "Database unavailable — using filesystem state derivation (degraded mode). State queries may be slower and less reliable.",
|
|
25
|
+
file: ".gsd/gsd.db",
|
|
26
|
+
fixable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
// ── DB constraint violation detection (full doctor only, not pre-dispatch per D-10) ──
|
|
17
31
|
try {
|
|
18
32
|
if (isDbAvailable()) {
|
|
@@ -2,6 +2,7 @@ import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary } from "
|
|
|
2
2
|
|
|
3
3
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
4
4
|
if (!scope) return true;
|
|
5
|
+
if (unitId === "project" || unitId === "environment") return true;
|
|
5
6
|
return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
|
|
6
7
|
}
|
|
7
8
|
|
|
@@ -48,6 +48,7 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
|
48
48
|
import {
|
|
49
49
|
getWorkflowTransportSupportError,
|
|
50
50
|
getRequiredWorkflowToolsForGuidedUnit,
|
|
51
|
+
supportsStructuredQuestions,
|
|
51
52
|
} from "./workflow-mcp.js";
|
|
52
53
|
import {
|
|
53
54
|
runPreparation,
|
|
@@ -294,6 +295,7 @@ async function dispatchWorkflow(
|
|
|
294
295
|
const result = await selectAndApplyModel(
|
|
295
296
|
ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(),
|
|
296
297
|
prefs, /* verbose */ false, /* autoModeStartModel */ null,
|
|
298
|
+
/* retryContext */ undefined, /* isAutoMode */ false,
|
|
297
299
|
);
|
|
298
300
|
if (result.appliedModel) {
|
|
299
301
|
debugLog("guided-flow-model-applied", {
|
|
@@ -367,6 +369,20 @@ async function dispatchWorkflow(
|
|
|
367
369
|
}
|
|
368
370
|
}
|
|
369
371
|
|
|
372
|
+
function getStructuredQuestionsAvailability(
|
|
373
|
+
pi: ExtensionAPI,
|
|
374
|
+
ctx: ExtensionContext | undefined,
|
|
375
|
+
): "true" | "false" {
|
|
376
|
+
if (!ctx) return "false";
|
|
377
|
+
|
|
378
|
+
const provider = ctx.model?.provider;
|
|
379
|
+
const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined;
|
|
380
|
+
return supportsStructuredQuestions(pi.getActiveTools(), {
|
|
381
|
+
authMode,
|
|
382
|
+
baseUrl: ctx.model?.baseUrl,
|
|
383
|
+
}) ? "true" : "false";
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
/**
|
|
371
387
|
* Resolve a model ID string to a model object from available models.
|
|
372
388
|
* Handles "provider/model" and bare ID formats.
|
|
@@ -739,7 +755,7 @@ export async function showDiscuss(
|
|
|
739
755
|
|
|
740
756
|
if (choice === "discuss_draft") {
|
|
741
757
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
742
|
-
const structuredQuestionsAvailable = pi
|
|
758
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
743
759
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
744
760
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
745
761
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -752,7 +768,7 @@ export async function showDiscuss(
|
|
|
752
768
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
753
769
|
} else if (choice === "discuss_fresh") {
|
|
754
770
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
755
|
-
const structuredQuestionsAvailable = pi
|
|
771
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
756
772
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
|
|
757
773
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
758
774
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -910,7 +926,7 @@ export async function showDiscuss(
|
|
|
910
926
|
if (confirm !== "rediscuss") continue;
|
|
911
927
|
}
|
|
912
928
|
|
|
913
|
-
const sqAvail = pi
|
|
929
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
914
930
|
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
|
|
915
931
|
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
|
|
916
932
|
|
|
@@ -1020,7 +1036,7 @@ async function dispatchDiscussForMilestone(
|
|
|
1020
1036
|
].join("\n")
|
|
1021
1037
|
: "";
|
|
1022
1038
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1023
|
-
const structuredQuestionsAvailable = pi
|
|
1039
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1024
1040
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1025
1041
|
milestoneId: mid,
|
|
1026
1042
|
milestoneTitle,
|
|
@@ -1461,7 +1477,7 @@ export async function showSmartEntry(
|
|
|
1461
1477
|
|
|
1462
1478
|
if (choice === "discuss_draft") {
|
|
1463
1479
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1464
|
-
const structuredQuestionsAvailable = pi
|
|
1480
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1465
1481
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1466
1482
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1467
1483
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1474,7 +1490,7 @@ export async function showSmartEntry(
|
|
|
1474
1490
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1475
1491
|
} else if (choice === "discuss_fresh") {
|
|
1476
1492
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1477
|
-
const structuredQuestionsAvailable = pi
|
|
1493
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1478
1494
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1479
1495
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1480
1496
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -1572,7 +1588,7 @@ export async function showSmartEntry(
|
|
|
1572
1588
|
}), "gsd-run", ctx, "plan-milestone");
|
|
1573
1589
|
} else if (choice === "discuss") {
|
|
1574
1590
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1575
|
-
const structuredQuestionsAvailable = pi
|
|
1591
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1576
1592
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1577
1593
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1578
1594
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1712,7 +1728,7 @@ export async function showSmartEntry(
|
|
|
1712
1728
|
}),
|
|
1713
1729
|
}), "gsd-run", ctx, "plan-slice");
|
|
1714
1730
|
} else if (choice === "discuss") {
|
|
1715
|
-
const sqAvail = pi
|
|
1731
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
1716
1732
|
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
|
|
1717
1733
|
} else if (choice === "research") {
|
|
1718
1734
|
const researchTemplates = inlineTemplate("research", "Research");
|
|
@@ -20,6 +20,8 @@ import { resolve } from "node:path";
|
|
|
20
20
|
import type { TaskRow } from "./gsd-db.ts";
|
|
21
21
|
import type { PreExecutionCheckJSON } from "./verification-evidence.ts";
|
|
22
22
|
|
|
23
|
+
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
24
|
+
|
|
23
25
|
// ─── Result Types ────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
25
27
|
export interface PreExecutionResult {
|
|
@@ -126,9 +128,10 @@ async function checkPackageOnNpm(
|
|
|
126
128
|
timeoutMs = 5000
|
|
127
129
|
): Promise<{ exists: boolean; error?: string }> {
|
|
128
130
|
return new Promise((resolve) => {
|
|
129
|
-
const child = spawn(
|
|
131
|
+
const child = spawn(NPM_COMMAND, ["view", packageName, "name"], {
|
|
130
132
|
stdio: ["ignore", "pipe", "pipe"],
|
|
131
133
|
timeout: timeoutMs,
|
|
134
|
+
shell: process.platform === "win32",
|
|
132
135
|
});
|
|
133
136
|
|
|
134
137
|
let stdout = "";
|
|
@@ -263,9 +266,9 @@ function extractPathFromAnnotation(raw: string): string {
|
|
|
263
266
|
const trimmed = raw.trim();
|
|
264
267
|
if (!trimmed) return trimmed;
|
|
265
268
|
|
|
266
|
-
const backtickMatch = trimmed.match(
|
|
269
|
+
const backtickMatch = trimmed.match(/^(`+)([^`]+)\1(?:(?:\s+[—–-]\s+.+)|(?:\s+\([^()]+\)))?$/);
|
|
267
270
|
if (backtickMatch) {
|
|
268
|
-
return backtickMatch[
|
|
271
|
+
return backtickMatch[2].trim();
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import test, { describe } from "node:test";
|
|
1
|
+
import test, { describe, beforeEach, afterEach } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import {
|
|
7
8
|
buildCmuxProgress,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
resolveCmuxConfig,
|
|
13
14
|
shouldPromptToEnableCmux,
|
|
14
15
|
} from "../../cmux/index.ts";
|
|
16
|
+
import { autoEnableCmuxPreferences } from "../commands-cmux.ts";
|
|
15
17
|
import type { GSDState } from "../types.ts";
|
|
16
18
|
|
|
17
19
|
test("detectCmuxEnvironment requires workspace, surface, and socket", () => {
|
|
@@ -79,6 +81,70 @@ test("shouldPromptToEnableCmux only prompts once per session", () => {
|
|
|
79
81
|
resetCmuxPromptState();
|
|
80
82
|
});
|
|
81
83
|
|
|
84
|
+
describe("autoEnableCmuxPreferences", () => {
|
|
85
|
+
let tmp: string;
|
|
86
|
+
let originalCwd: string;
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
originalCwd = process.cwd();
|
|
90
|
+
tmp = fs.mkdtempSync(path.join(tmpdir(), "cmux-auto-test-"));
|
|
91
|
+
fs.mkdirSync(path.join(tmp, ".gsd"), { recursive: true });
|
|
92
|
+
process.chdir(tmp);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
process.chdir(originalCwd);
|
|
97
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("writes cmux.enabled true when preferences file exists with no cmux config", () => {
|
|
101
|
+
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
|
102
|
+
fs.writeFileSync(prefsPath, [
|
|
103
|
+
"---",
|
|
104
|
+
"version: 1",
|
|
105
|
+
"---",
|
|
106
|
+
"",
|
|
107
|
+
"# GSD Skill Preferences",
|
|
108
|
+
].join("\n"));
|
|
109
|
+
|
|
110
|
+
const result = autoEnableCmuxPreferences();
|
|
111
|
+
assert.equal(result, true);
|
|
112
|
+
|
|
113
|
+
const content = fs.readFileSync(prefsPath, "utf-8");
|
|
114
|
+
assert.ok(content.includes("enabled: true"), "should write enabled: true");
|
|
115
|
+
assert.ok(content.includes("notifications: true"), "should default notifications on");
|
|
116
|
+
assert.ok(content.includes("sidebar: true"), "should default sidebar on");
|
|
117
|
+
assert.ok(content.includes("splits: false"), "should default splits off");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("returns false when preferences file does not exist", () => {
|
|
121
|
+
const result = autoEnableCmuxPreferences();
|
|
122
|
+
assert.equal(result, false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("preserves existing cmux sub-preferences when auto-enabling", () => {
|
|
126
|
+
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
|
127
|
+
fs.writeFileSync(prefsPath, [
|
|
128
|
+
"---",
|
|
129
|
+
"version: 1",
|
|
130
|
+
"cmux:",
|
|
131
|
+
" splits: true",
|
|
132
|
+
" browser: true",
|
|
133
|
+
"---",
|
|
134
|
+
"",
|
|
135
|
+
"# GSD Skill Preferences",
|
|
136
|
+
].join("\n"));
|
|
137
|
+
|
|
138
|
+
const result = autoEnableCmuxPreferences();
|
|
139
|
+
assert.equal(result, true);
|
|
140
|
+
|
|
141
|
+
const content = fs.readFileSync(prefsPath, "utf-8");
|
|
142
|
+
assert.ok(content.includes("enabled: true"), "should set enabled: true");
|
|
143
|
+
assert.ok(content.includes("splits: true"), "should preserve existing splits: true");
|
|
144
|
+
assert.ok(content.includes("browser: true"), "should preserve existing browser: true");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
82
148
|
test("buildCmuxStatusLabel and progress prefer deepest active unit", () => {
|
|
83
149
|
const state: GSDState = {
|
|
84
150
|
activeMilestone: { id: "M001", title: "Milestone" },
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { afterEach, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { closeDatabase } from "../gsd-db.ts";
|
|
4
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { filterDoctorIssues } from "../doctor-format.ts";
|
|
8
|
+
import { checkEngineHealth } from "../doctor-engine-checks.ts";
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
closeDatabase();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("filterDoctorIssues keeps project and environment issues in scoped reports", () => {
|
|
15
|
+
const issues = [
|
|
16
|
+
{ severity: "error", code: "env_dependencies", scope: "project", unitId: "environment", message: "node_modules missing", fixable: false },
|
|
17
|
+
{ severity: "warning", code: "db_unavailable", scope: "project", unitId: "project", message: "DB unavailable", fixable: false },
|
|
18
|
+
{ severity: "warning", code: "state_file_missing", scope: "slice", unitId: "M016/S01", message: "slice warning", fixable: false },
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
const filtered = filterDoctorIssues([...issues], { scope: "M016", includeWarnings: true });
|
|
22
|
+
assert.deepEqual(
|
|
23
|
+
filtered.map((issue) => issue.unitId),
|
|
24
|
+
["environment", "project", "M016/S01"],
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("checkEngineHealth reports db_unavailable when gsd.db exists but the DB is closed", async (t) => {
|
|
29
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-db-unavailable-"));
|
|
30
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
31
|
+
|
|
32
|
+
const gsdDir = join(base, ".gsd");
|
|
33
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
34
|
+
writeFileSync(join(gsdDir, "gsd.db"), "");
|
|
35
|
+
|
|
36
|
+
const issues: any[] = [];
|
|
37
|
+
await checkEngineHealth(base, issues, []);
|
|
38
|
+
|
|
39
|
+
const dbIssue = issues.find((issue) => issue.code === "db_unavailable");
|
|
40
|
+
assert.ok(dbIssue, "doctor should surface degraded DB mode when a DB file exists");
|
|
41
|
+
assert.equal(dbIssue.unitId, "project");
|
|
42
|
+
assert.equal(dbIssue.file, ".gsd/gsd.db");
|
|
43
|
+
});
|