gsd-pi 2.70.0 → 2.70.1-dev.bef631a
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 +152 -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 +28 -12
- package/dist/resources/extensions/gsd/auto.js +12 -8
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
- 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 +33 -20
- package/dist/resources/extensions/gsd/init-wizard.js +3 -11
- package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
- package/dist/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
- package/dist/resources/extensions/gsd/validate-directory.js +30 -12
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +12 -1
- 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 +10 -10
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- 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/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 +10 -10
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/2826.dd3dc8bbd3025fa5.js +9 -0
- 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/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
- 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/mcp-server/dist/workflow-tools.d.ts +2 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +35 -3
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/import-candidates.test.ts +48 -0
- package/packages/mcp-server/src/workflow-tools.ts +34 -1
- 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-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 +229 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +205 -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 +37 -14
- package/src/resources/extensions/gsd/auto.ts +12 -8
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- 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 +36 -17
- package/src/resources/extensions/gsd/init-wizard.ts +3 -13
- package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
- 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/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-auto-prep.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +180 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
- package/src/resources/extensions/gsd/validate-directory.ts +33 -11
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +16 -1
- 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/2826.821e01b07d92e948.js +0 -9
- 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/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → UlX0WGGZ8aBPN0uSZ5Ki4}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → UlX0WGGZ8aBPN0uSZ5Ki4}/_ssgManifest.js +0 -0
|
@@ -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 {
|
|
@@ -335,19 +335,9 @@ export async function bootstrapAutoSession(
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const result = ensureProjectWorkflowMcpConfig(base);
|
|
342
|
-
if (result.status !== "unchanged") {
|
|
343
|
-
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
344
|
-
}
|
|
345
|
-
} catch (err) {
|
|
346
|
-
ctx.ui.notify(
|
|
347
|
-
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
348
|
-
"warning",
|
|
349
|
-
);
|
|
350
|
-
}
|
|
338
|
+
{
|
|
339
|
+
const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
|
|
340
|
+
prepareWorkflowMcpForProject(ctx, base);
|
|
351
341
|
}
|
|
352
342
|
|
|
353
343
|
// Initialize GitServiceImpl
|
|
@@ -778,6 +768,39 @@ export async function bootstrapAutoSession(
|
|
|
778
768
|
: "Will loop until milestone complete.";
|
|
779
769
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
780
770
|
|
|
771
|
+
// Show dynamic routing status so users know upfront if models will be
|
|
772
|
+
// downgraded for simple tasks (#3962).
|
|
773
|
+
// Use the same effective logic as selectAndApplyModel: check flat-rate
|
|
774
|
+
// provider suppression and resolve the actual ceiling model.
|
|
775
|
+
const routingConfig = resolveDynamicRoutingConfig();
|
|
776
|
+
const startModelLabel = s.autoModeStartModel
|
|
777
|
+
? `${s.autoModeStartModel.provider}/${s.autoModeStartModel.id}`
|
|
778
|
+
: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "default";
|
|
779
|
+
|
|
780
|
+
// Flat-rate providers (e.g. GitHub Copilot, claude-code) suppress routing
|
|
781
|
+
// at dispatch time (#3453) — reflect that in the banner.
|
|
782
|
+
const { isFlatRateProvider } = await import("./auto-model-selection.js");
|
|
783
|
+
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
784
|
+
const effectivelyEnabled = routingConfig.enabled
|
|
785
|
+
&& !(effectiveProvider && isFlatRateProvider(effectiveProvider));
|
|
786
|
+
|
|
787
|
+
// The actual ceiling may come from tier_models.heavy, not the start model.
|
|
788
|
+
const effectiveCeiling = (routingConfig.enabled && routingConfig.tier_models?.heavy)
|
|
789
|
+
? routingConfig.tier_models.heavy
|
|
790
|
+
: startModelLabel;
|
|
791
|
+
|
|
792
|
+
if (effectivelyEnabled) {
|
|
793
|
+
ctx.ui.notify(
|
|
794
|
+
`Dynamic routing: enabled — simple tasks may use cheaper models (ceiling: ${effectiveCeiling})`,
|
|
795
|
+
"info",
|
|
796
|
+
);
|
|
797
|
+
} else {
|
|
798
|
+
ctx.ui.notify(
|
|
799
|
+
`Dynamic routing: disabled — all tasks will use ${startModelLabel}`,
|
|
800
|
+
"info",
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
781
804
|
updateSessionLock(
|
|
782
805
|
lockBase(),
|
|
783
806
|
"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).
|
|
@@ -45,6 +45,8 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
45
45
|
resetToolCallLoopGuard();
|
|
46
46
|
resetAskUserQuestionsCache();
|
|
47
47
|
await syncServiceTierStatus(ctx);
|
|
48
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
49
|
+
prepareWorkflowMcpForProject(ctx, process.cwd());
|
|
48
50
|
|
|
49
51
|
// Apply show_token_cost preference (#1515)
|
|
50
52
|
try {
|
|
@@ -85,6 +87,8 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
85
87
|
resetAskUserQuestionsCache();
|
|
86
88
|
clearDiscussionFlowState();
|
|
87
89
|
await syncServiceTierStatus(ctx);
|
|
90
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
91
|
+
prepareWorkflowMcpForProject(ctx, process.cwd());
|
|
88
92
|
loadToolApiKeys();
|
|
89
93
|
});
|
|
90
94
|
|
|
@@ -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.
|
|
@@ -410,8 +426,9 @@ function resolveAvailableModel<T extends { id: string; provider: string }>(
|
|
|
410
426
|
* Build the discuss-and-plan prompt for a new milestone.
|
|
411
427
|
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
412
428
|
*/
|
|
413
|
-
function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string, preparationContext?: string): string {
|
|
429
|
+
function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string, pi: ExtensionAPI, ctx: ExtensionCommandContext, preparationContext?: string): string {
|
|
414
430
|
const milestoneRel = `.gsd/milestones/${nextId}`;
|
|
431
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
415
432
|
const inlinedTemplates = [
|
|
416
433
|
inlineTemplate("project", "Project"),
|
|
417
434
|
inlineTemplate("requirements", "Requirements"),
|
|
@@ -423,6 +440,7 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
|
|
|
423
440
|
milestoneId: nextId,
|
|
424
441
|
preamble,
|
|
425
442
|
preparationContext: preparationContext ?? "",
|
|
443
|
+
structuredQuestionsAvailable,
|
|
426
444
|
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
427
445
|
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
428
446
|
inlinedTemplates,
|
|
@@ -470,6 +488,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
|
|
|
470
488
|
*/
|
|
471
489
|
async function prepareAndBuildDiscussPrompt(
|
|
472
490
|
ctx: ExtensionCommandContext,
|
|
491
|
+
pi: ExtensionAPI,
|
|
473
492
|
nextId: string,
|
|
474
493
|
preamble: string,
|
|
475
494
|
basePath: string,
|
|
@@ -504,7 +523,7 @@ async function prepareAndBuildDiscussPrompt(
|
|
|
504
523
|
}
|
|
505
524
|
}
|
|
506
525
|
|
|
507
|
-
return buildDiscussPrompt(nextId, preamble, basePath, preparationContext);
|
|
526
|
+
return buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext);
|
|
508
527
|
}
|
|
509
528
|
|
|
510
529
|
/**
|
|
@@ -739,7 +758,7 @@ export async function showDiscuss(
|
|
|
739
758
|
|
|
740
759
|
if (choice === "discuss_draft") {
|
|
741
760
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
742
|
-
const structuredQuestionsAvailable = pi
|
|
761
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
743
762
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
744
763
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
745
764
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -752,7 +771,7 @@ export async function showDiscuss(
|
|
|
752
771
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
753
772
|
} else if (choice === "discuss_fresh") {
|
|
754
773
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
755
|
-
const structuredQuestionsAvailable = pi
|
|
774
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
756
775
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
|
|
757
776
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
758
777
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -764,7 +783,7 @@ export async function showDiscuss(
|
|
|
764
783
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
765
784
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
766
785
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
|
|
767
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
786
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
768
787
|
}
|
|
769
788
|
return;
|
|
770
789
|
}
|
|
@@ -910,7 +929,7 @@ export async function showDiscuss(
|
|
|
910
929
|
if (confirm !== "rediscuss") continue;
|
|
911
930
|
}
|
|
912
931
|
|
|
913
|
-
const sqAvail = pi
|
|
932
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
914
933
|
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
|
|
915
934
|
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
|
|
916
935
|
|
|
@@ -1020,7 +1039,7 @@ async function dispatchDiscussForMilestone(
|
|
|
1020
1039
|
].join("\n")
|
|
1021
1040
|
: "";
|
|
1022
1041
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1023
|
-
const structuredQuestionsAvailable = pi
|
|
1042
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1024
1043
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1025
1044
|
milestoneId: mid,
|
|
1026
1045
|
milestoneTitle,
|
|
@@ -1169,7 +1188,7 @@ async function handleMilestoneActions(
|
|
|
1169
1188
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1170
1189
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1171
1190
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1172
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1191
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1173
1192
|
`New milestone ${nextId}.`,
|
|
1174
1193
|
basePath
|
|
1175
1194
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1359,7 +1378,7 @@ export async function showSmartEntry(
|
|
|
1359
1378
|
if (isFirst) {
|
|
1360
1379
|
// First ever — skip wizard, just ask directly
|
|
1361
1380
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1362
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1381
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1363
1382
|
`New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
|
|
1364
1383
|
basePath
|
|
1365
1384
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1380,7 +1399,7 @@ export async function showSmartEntry(
|
|
|
1380
1399
|
|
|
1381
1400
|
if (choice === "new_milestone") {
|
|
1382
1401
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1383
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1402
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1384
1403
|
`New milestone ${nextId}.`,
|
|
1385
1404
|
basePath
|
|
1386
1405
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1419,7 +1438,7 @@ export async function showSmartEntry(
|
|
|
1419
1438
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1420
1439
|
|
|
1421
1440
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1422
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1441
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1423
1442
|
`New milestone ${nextId}.`,
|
|
1424
1443
|
basePath
|
|
1425
1444
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1461,7 +1480,7 @@ export async function showSmartEntry(
|
|
|
1461
1480
|
|
|
1462
1481
|
if (choice === "discuss_draft") {
|
|
1463
1482
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1464
|
-
const structuredQuestionsAvailable = pi
|
|
1483
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1465
1484
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1466
1485
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1467
1486
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1474,7 +1493,7 @@ export async function showSmartEntry(
|
|
|
1474
1493
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1475
1494
|
} else if (choice === "discuss_fresh") {
|
|
1476
1495
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1477
|
-
const structuredQuestionsAvailable = pi
|
|
1496
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1478
1497
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1479
1498
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1480
1499
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -1486,7 +1505,7 @@ export async function showSmartEntry(
|
|
|
1486
1505
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1487
1506
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1488
1507
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1489
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1508
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1490
1509
|
`New milestone ${nextId}.`,
|
|
1491
1510
|
basePath
|
|
1492
1511
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1572,7 +1591,7 @@ export async function showSmartEntry(
|
|
|
1572
1591
|
}), "gsd-run", ctx, "plan-milestone");
|
|
1573
1592
|
} else if (choice === "discuss") {
|
|
1574
1593
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1575
|
-
const structuredQuestionsAvailable = pi
|
|
1594
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1576
1595
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1577
1596
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1578
1597
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1583,7 +1602,7 @@ export async function showSmartEntry(
|
|
|
1583
1602
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1584
1603
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1585
1604
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1586
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1605
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1587
1606
|
`New milestone ${nextId}.`,
|
|
1588
1607
|
basePath
|
|
1589
1608
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1712,7 +1731,7 @@ export async function showSmartEntry(
|
|
|
1712
1731
|
}),
|
|
1713
1732
|
}), "gsd-run", ctx, "plan-slice");
|
|
1714
1733
|
} else if (choice === "discuss") {
|
|
1715
|
-
const sqAvail = pi
|
|
1734
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
1716
1735
|
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
|
|
1717
1736
|
} else if (choice === "research") {
|
|
1718
1737
|
const researchTemplates = inlineTemplate("research", "Research");
|
|
@@ -274,19 +274,9 @@ export async function showProjectInit(
|
|
|
274
274
|
// Non-fatal — STATE.md will be regenerated on next /gsd invocation
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const result = ensureProjectWorkflowMcpConfig(basePath);
|
|
281
|
-
if (result.status !== "unchanged") {
|
|
282
|
-
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
283
|
-
}
|
|
284
|
-
} catch (err) {
|
|
285
|
-
ctx.ui.notify(
|
|
286
|
-
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
-
"warning",
|
|
288
|
-
);
|
|
289
|
-
}
|
|
277
|
+
{
|
|
278
|
+
const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
|
|
279
|
+
prepareWorkflowMcpForProject(ctx, basePath);
|
|
290
280
|
}
|
|
291
281
|
|
|
292
282
|
ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
|
|
@@ -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+.+$/);
|
|
@@ -49,6 +49,26 @@ This happens ONCE, before the first round. The goal: your first questions should
|
|
|
49
49
|
|
|
50
50
|
For subsequent rounds, continue investigating between rounds — check docs, search, or scout as needed to make each round's questions smarter. But the first-round investigation is mandatory and explicit. Distribute searches across turns rather than clustering them in one turn.
|
|
51
51
|
|
|
52
|
+
## Question Rounds
|
|
53
|
+
|
|
54
|
+
Ask **1–3 questions per round**. Keep each round tightly focused on one or two of the depth checklist dimensions — do not try to cover all six in one round.
|
|
55
|
+
|
|
56
|
+
**If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` for each round. 1–3 questions per call, each as a separate question object. Keep option labels short (3–5 words). Always include a freeform "Other / let me explain" option. When the user picks that option or writes a long freeform answer, switch to plain text follow-up for that thread before resuming structured questions. **IMPORTANT: Call `ask_user_questions` exactly once per turn. Never make multiple calls with the same or overlapping questions — wait for the user's response before asking the next round.**
|
|
57
|
+
|
|
58
|
+
**If `{{structuredQuestionsAvailable}}` is `false`:** ask questions in plain text. Keep each round to 1–3 focused questions. Wait for answers before asking the next round.
|
|
59
|
+
|
|
60
|
+
After each answer set, investigate further if any answer opens a new unknown, then ask the next round.
|
|
61
|
+
|
|
62
|
+
### Round cadence
|
|
63
|
+
|
|
64
|
+
After each round of answers, decide whether you already have enough depth to write strong output.
|
|
65
|
+
|
|
66
|
+
- **Incremental persistence:** After every 2 question rounds, silently save a `{{milestoneId}}-CONTEXT-DRAFT.md` using `gsd_summary_save` with `artifact_type: "CONTEXT-DRAFT"` and `milestone_id: "{{milestoneId}}"`. This protects confirmed work against session crashes. Do NOT mention this save to the user.
|
|
67
|
+
- If not ready, continue to the next round immediately. Do **not** ask a meta "ready to wrap up?" question after every round.
|
|
68
|
+
- **Depth-matching rule:** Simple, well-defined work needs fewer rounds — maybe 1–2. Large, ambiguous visions need more — maybe 4+. Do not pad rounds to hit a number. Stop when the Depth Enforcement checklist below is fully satisfied.
|
|
69
|
+
- Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
|
|
70
|
+
- When you genuinely believe the depth checklist is satisfied, move to the Depth Verification step below. Do not ask a separate "ready to wrap up?" gate — the depth verification IS the gate.
|
|
71
|
+
|
|
52
72
|
## Questioning Philosophy
|
|
53
73
|
|
|
54
74
|
You are a thinking partner, not an interviewer.
|
|
@@ -94,29 +114,27 @@ Do NOT offer to proceed until ALL of the following are satisfied. Track these in
|
|
|
94
114
|
|
|
95
115
|
Before offering to proceed, demonstrate absorption: reference specific things the user emphasized, specific terminology they used, specific nuance they sharpened — and show how those shaped your understanding. Synthesize, don't recite. "Your emphasis on X led me to prioritize Y over Z" is good. "You said X, you said Y, you said Z" is not. The user should feel heard in the specifics, not just acknowledged in the abstract.
|
|
96
116
|
|
|
97
|
-
**Questioning depth should match scope.** Simple, well-defined work needs fewer rounds — maybe 1-2. Large, ambiguous visions need more — maybe 4+. Don't pad rounds to hit a number. Stop when the depth checklist is satisfied and you genuinely understand the work.
|
|
98
|
-
|
|
99
|
-
Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
|
|
100
|
-
|
|
101
117
|
## Depth Verification
|
|
102
118
|
|
|
103
119
|
Before moving to the wrap-up gate, present a structured depth summary as a checkpoint.
|
|
104
120
|
|
|
105
121
|
**Print the summary as normal chat text first** — this is where the formatting renders properly. Structure the summary across the depth checklist dimensions using the user's own terminology and framing. Cover: what you understood them to be building, what shaped your understanding most (their emphasis, constraints, concerns), and any areas where you're least confident in your understanding.
|
|
106
122
|
|
|
107
|
-
**Then
|
|
123
|
+
**Then confirm:**
|
|
108
124
|
|
|
109
|
-
**
|
|
125
|
+
**If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` with:
|
|
126
|
+
- header: "Depth Check"
|
|
127
|
+
- question: "Did I capture the depth right?"
|
|
128
|
+
- options: "Yes, you got it (Recommended)", "Not quite — let me clarify"
|
|
129
|
+
- **The question ID must contain `depth_verification`** (e.g., `depth_verification_confirm`) — this naming convention enables downstream mechanical detection and the write-gate.
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
1. Print in chat: the full depth summary with markdown formatting (headers, bold, bullets)
|
|
113
|
-
2. Call `ask_user_questions` with: header "Depth Check", question "Did I capture the depth right?", options "Yes, you got it (Recommended)" and "Not quite — let me clarify"
|
|
131
|
+
**If `{{structuredQuestionsAvailable}}` is `false`:** ask in plain text: "Did I capture that correctly? If not, tell me what I missed." Wait for explicit confirmation before proceeding. **The same non-bypassable gate applies to the plain-text path** — if the user does not respond, gives an ambiguous answer, or does not explicitly confirm, you MUST re-ask. Never rationalize past a missing confirmation.
|
|
114
132
|
|
|
115
133
|
If they clarify, absorb the correction and re-verify.
|
|
116
134
|
|
|
117
135
|
The depth verification is the required write-gate. Do **not** add another meta "ready to proceed?" checkpoint immediately after it unless there is still material ambiguity.
|
|
118
136
|
|
|
119
|
-
**CRITICAL — Non-bypassable gate:** The system mechanically blocks CONTEXT.md writes until the user selects the "(Recommended)" option. If the user declines, cancels, or the tool fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). The gate exists to protect the user's work; treat a block as an instruction, not an obstacle to work around.
|
|
137
|
+
**CRITICAL — Non-bypassable gate:** The system mechanically blocks CONTEXT.md writes until the user selects the "(Recommended)" option (structured path) or explicitly confirms (plain-text path). If the user declines, cancels, does not respond, or the tool fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). The gate exists to protect the user's work; treat a block as an instruction, not an obstacle to work around.
|
|
120
138
|
|
|
121
139
|
## Wrap-up Gate
|
|
122
140
|
|
|
@@ -244,7 +262,7 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f
|
|
|
244
262
|
|
|
245
263
|
#### Phase 3: Sequential readiness gate for remaining milestones
|
|
246
264
|
|
|
247
|
-
For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then use `ask_user_questions`
|
|
265
|
+
For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then present the three options below to the user. **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions`. **If `{{structuredQuestionsAvailable}}` is `false`:** present the options as a plain-text numbered list and ask the user to type their choice. **Non-bypassable:** If the user does not respond, gives an ambiguous answer, or the tool fails, you MUST re-ask — never rationalize past the block or auto-select a readiness mode. Present three options:
|
|
248
266
|
|
|
249
267
|
- **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone.
|
|
250
268
|
- **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When auto-mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /gsd." The `/gsd` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted.
|
|
@@ -256,9 +274,9 @@ Before writing each milestone's CONTEXT.md (whether primary or secondary), you M
|
|
|
256
274
|
|
|
257
275
|
1. **Read the actual code** for every file or module you reference. Confirm APIs exist, check what functions actually do, identify phantom capabilities (code that exists but isn't wired up).
|
|
258
276
|
2. **Check for stale assumptions** — the codebase changes. Verify referenced modules still work as described.
|
|
259
|
-
3. **Present findings** — use `ask_user_questions` with a question ID containing BOTH `depth_verification` AND the milestone ID (e.g., `depth_verification_M002`). Present: what you're about to write, key technical findings from investigation, risks the code review surfaced.
|
|
277
|
+
3. **Present findings** — **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` with a question ID containing BOTH `depth_verification` AND the milestone ID (e.g., `depth_verification_M002`). Present: what you're about to write, key technical findings from investigation, risks the code review surfaced. **If `{{structuredQuestionsAvailable}}` is `false`:** present the same findings in plain text and ask for explicit confirmation before proceeding.
|
|
260
278
|
|
|
261
|
-
**The system mechanically blocks CONTEXT.md writes until the per-milestone depth verification passes
|
|
279
|
+
**The system mechanically blocks CONTEXT.md writes until the per-milestone depth verification passes** (structured path: user selects "(Recommended)" option; plain-text path: user explicitly confirms). Each milestone needs its own verification — one global verification does not unlock all milestones.
|
|
262
280
|
|
|
263
281
|
**Why sequential, not batch:** After writing the primary milestone's context and roadmap, the agent still has context window capacity. Asking one milestone at a time lets the user decide per-milestone whether to invest that remaining capacity in a focused discussion now, or defer to a future session. A batch question ("Ready/Draft/Queue for M002, M003, M004?") forces the user to decide everything upfront without knowing how much session capacity remains.
|
|
264
282
|
|
|
@@ -27,10 +27,19 @@ describe("discuss incremental persistence (#2152)", () => {
|
|
|
27
27
|
assert.match(content, /Incremental persistence/, "should have incremental persistence section");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("new-project discuss prompt includes CONTEXT-DRAFT save instruction", () => {
|
|
31
|
+
const content = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
|
|
32
|
+
assert.match(content, /CONTEXT-DRAFT/, "should mention CONTEXT-DRAFT");
|
|
33
|
+
assert.match(content, /Incremental persistence/, "should have incremental persistence section");
|
|
34
|
+
assert.match(content, /gsd_summary_save/, "should use gsd_summary_save tool");
|
|
35
|
+
});
|
|
36
|
+
|
|
30
37
|
test("drafts are saved silently without user notification", () => {
|
|
31
38
|
const milestone = readFileSync(join(promptsDir, "guided-discuss-milestone.md"), "utf-8");
|
|
32
39
|
const slice = readFileSync(join(promptsDir, "guided-discuss-slice.md"), "utf-8");
|
|
40
|
+
const discuss = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
|
|
33
41
|
assert.match(milestone, /Do NOT mention this save to the user/);
|
|
34
42
|
assert.match(slice, /Do NOT mention this to the user/);
|
|
43
|
+
assert.match(discuss, /Do NOT mention this save to the user/);
|
|
35
44
|
});
|
|
36
45
|
});
|
|
@@ -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
|
+
});
|