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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// GSD Extension — Interactive Routing Bypass Tests
|
|
2
|
+
// Verifies that dynamic routing is skipped for interactive (guided-flow) dispatches
|
|
3
|
+
// and that model downgrade notifications always fire (#3962).
|
|
4
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
5
|
+
|
|
6
|
+
import test, { describe } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// ─── Source-level structural tests ──────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const modelSelectionSrc = readFileSync(
|
|
17
|
+
join(__dirname, "..", "auto-model-selection.ts"),
|
|
18
|
+
"utf-8",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const guidedFlowSrc = readFileSync(
|
|
22
|
+
join(__dirname, "..", "guided-flow.ts"),
|
|
23
|
+
"utf-8",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const autoStartSrc = readFileSync(
|
|
27
|
+
join(__dirname, "..", "auto-start.ts"),
|
|
28
|
+
"utf-8",
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
describe("interactive routing bypass (#3962)", () => {
|
|
32
|
+
test("selectAndApplyModel accepts isAutoMode parameter", () => {
|
|
33
|
+
// The function signature should include isAutoMode with a default of true
|
|
34
|
+
assert.ok(
|
|
35
|
+
modelSelectionSrc.includes("isAutoMode"),
|
|
36
|
+
"selectAndApplyModel should have isAutoMode parameter",
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
modelSelectionSrc.includes("isAutoMode = true"),
|
|
40
|
+
"isAutoMode should default to true (auto-mode behavior preserved)",
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("routing is disabled when isAutoMode is false", () => {
|
|
45
|
+
// The code should disable routing when not in auto-mode
|
|
46
|
+
assert.ok(
|
|
47
|
+
modelSelectionSrc.includes("if (!isAutoMode)"),
|
|
48
|
+
"should check isAutoMode flag to disable routing",
|
|
49
|
+
);
|
|
50
|
+
assert.ok(
|
|
51
|
+
modelSelectionSrc.includes("routingConfig.enabled = false"),
|
|
52
|
+
"should set routingConfig.enabled = false for interactive mode",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("resolvePreferredModelConfig skips routing synthesis when isAutoMode is false", () => {
|
|
57
|
+
// resolvePreferredModelConfig should accept isAutoMode and bail early
|
|
58
|
+
// before synthesizing a routing ceiling from tier_models (#3962 codex review)
|
|
59
|
+
assert.ok(
|
|
60
|
+
modelSelectionSrc.includes("function resolvePreferredModelConfig"),
|
|
61
|
+
"resolvePreferredModelConfig should exist",
|
|
62
|
+
);
|
|
63
|
+
// The function should check isAutoMode before routing synthesis
|
|
64
|
+
const fnIdx = modelSelectionSrc.indexOf("function resolvePreferredModelConfig");
|
|
65
|
+
const fnBody = modelSelectionSrc.slice(fnIdx, fnIdx + 600);
|
|
66
|
+
assert.ok(
|
|
67
|
+
fnBody.includes("isAutoMode"),
|
|
68
|
+
"resolvePreferredModelConfig should accept isAutoMode parameter",
|
|
69
|
+
);
|
|
70
|
+
assert.ok(
|
|
71
|
+
fnBody.includes("if (!isAutoMode) return undefined"),
|
|
72
|
+
"should return undefined (skip routing synthesis) when not in auto-mode",
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("selectAndApplyModel threads isAutoMode to resolvePreferredModelConfig", () => {
|
|
77
|
+
// The call to resolvePreferredModelConfig inside selectAndApplyModel
|
|
78
|
+
// should pass isAutoMode as the third argument
|
|
79
|
+
const callSite = "resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode)";
|
|
80
|
+
assert.ok(
|
|
81
|
+
modelSelectionSrc.includes(callSite),
|
|
82
|
+
"selectAndApplyModel should pass isAutoMode to resolvePreferredModelConfig",
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("guided-flow passes isAutoMode=false", () => {
|
|
87
|
+
// guided-flow.ts should explicitly pass isAutoMode as false
|
|
88
|
+
assert.ok(
|
|
89
|
+
guidedFlowSrc.includes("/* isAutoMode */ false"),
|
|
90
|
+
"guided-flow should pass isAutoMode=false to selectAndApplyModel",
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("auto/phases.ts does NOT pass isAutoMode=false", () => {
|
|
95
|
+
// auto/phases.ts should use the default (true) — it's auto-mode
|
|
96
|
+
const phasesSrc = readFileSync(
|
|
97
|
+
join(__dirname, "..", "auto", "phases.ts"),
|
|
98
|
+
"utf-8",
|
|
99
|
+
);
|
|
100
|
+
assert.ok(
|
|
101
|
+
!phasesSrc.includes("isAutoMode"),
|
|
102
|
+
"auto/phases.ts should use default isAutoMode=true (not pass it explicitly)",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("model downgrade notifications always visible (#3962)", () => {
|
|
108
|
+
test("downgrade notification is not gated by verbose flag", () => {
|
|
109
|
+
// The downgrade notification block should NOT be wrapped in `if (verbose)`
|
|
110
|
+
// Find the downgrade block and verify it's not behind a verbose check
|
|
111
|
+
const downgradeBlock = "if (routingResult.wasDowngraded)";
|
|
112
|
+
const downgradeIdx = modelSelectionSrc.indexOf(downgradeBlock);
|
|
113
|
+
assert.ok(downgradeIdx > 0, "downgrade block should exist");
|
|
114
|
+
|
|
115
|
+
// Extract the code between wasDowngraded check and the next routing label assignment
|
|
116
|
+
const afterDowngrade = modelSelectionSrc.slice(
|
|
117
|
+
downgradeIdx,
|
|
118
|
+
modelSelectionSrc.indexOf("routingTierLabel =", downgradeIdx),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// The notification calls should NOT be wrapped in `if (verbose)`
|
|
122
|
+
assert.ok(
|
|
123
|
+
!afterDowngrade.includes("if (verbose)"),
|
|
124
|
+
"downgrade notifications should not be gated by verbose flag",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// But the notification calls should exist
|
|
128
|
+
assert.ok(
|
|
129
|
+
afterDowngrade.includes('ctx.ui.notify('),
|
|
130
|
+
"downgrade notifications should still fire",
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("tier escalation notification is not gated by verbose flag", () => {
|
|
135
|
+
// Extract the escalation block: from "if (escalated)" to its closing
|
|
136
|
+
// and verify the notification is present but `if (verbose)` is not.
|
|
137
|
+
const escalatedIdx = modelSelectionSrc.indexOf("if (escalated)");
|
|
138
|
+
assert.ok(escalatedIdx > 0, "escalation block should exist");
|
|
139
|
+
|
|
140
|
+
// Get the block from "if (escalated)" to the next closing brace pattern
|
|
141
|
+
const block = modelSelectionSrc.slice(escalatedIdx, escalatedIdx + 400);
|
|
142
|
+
assert.ok(
|
|
143
|
+
block.includes("Tier escalation:"),
|
|
144
|
+
"escalation block should contain the notification",
|
|
145
|
+
);
|
|
146
|
+
assert.ok(
|
|
147
|
+
!block.includes("if (verbose)"),
|
|
148
|
+
"escalation block should not gate notification behind verbose flag",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("auto-mode start routing banner (#3962)", () => {
|
|
154
|
+
test("auto-start shows dynamic routing status on startup", () => {
|
|
155
|
+
assert.ok(
|
|
156
|
+
autoStartSrc.includes("Dynamic routing:"),
|
|
157
|
+
"auto-start should display routing status banner",
|
|
158
|
+
);
|
|
159
|
+
assert.ok(
|
|
160
|
+
autoStartSrc.includes("resolveDynamicRoutingConfig"),
|
|
161
|
+
"auto-start should import resolveDynamicRoutingConfig",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("banner shows different messages for enabled vs disabled routing", () => {
|
|
166
|
+
assert.ok(
|
|
167
|
+
autoStartSrc.includes("Dynamic routing: enabled"),
|
|
168
|
+
"should show message when routing is enabled",
|
|
169
|
+
);
|
|
170
|
+
assert.ok(
|
|
171
|
+
autoStartSrc.includes("Dynamic routing: disabled"),
|
|
172
|
+
"should show message when routing is disabled",
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("banner shows the ceiling model", () => {
|
|
177
|
+
assert.ok(
|
|
178
|
+
autoStartSrc.includes("startModelLabel"),
|
|
179
|
+
"banner should reference the start/ceiling model",
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("banner accounts for flat-rate provider suppression", () => {
|
|
184
|
+
// The banner should check isFlatRateProvider to accurately reflect
|
|
185
|
+
// whether routing will actually be active at dispatch time (#3962 codex review)
|
|
186
|
+
assert.ok(
|
|
187
|
+
autoStartSrc.includes("isFlatRateProvider"),
|
|
188
|
+
"banner should check flat-rate provider status",
|
|
189
|
+
);
|
|
190
|
+
assert.ok(
|
|
191
|
+
autoStartSrc.includes("effectivelyEnabled"),
|
|
192
|
+
"banner should compute effective routing state, not just raw config",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("banner uses effective ceiling from tier_models.heavy when configured", () => {
|
|
197
|
+
// The actual ceiling may come from tier_models.heavy, not the start model
|
|
198
|
+
assert.ok(
|
|
199
|
+
autoStartSrc.includes("tier_models?.heavy"),
|
|
200
|
+
"banner should check tier_models.heavy for the effective ceiling",
|
|
201
|
+
);
|
|
202
|
+
assert.ok(
|
|
203
|
+
autoStartSrc.includes("effectiveCeiling"),
|
|
204
|
+
"banner should compute the effective ceiling model",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -26,8 +26,12 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with the workflow server"
|
|
|
26
26
|
assert.equal(typeof server?.command, "string");
|
|
27
27
|
assert.equal(Array.isArray(server?.args), true);
|
|
28
28
|
assert.equal(server?.env?.GSD_WORKFLOW_PROJECT_ROOT, projectRoot);
|
|
29
|
-
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.js$/);
|
|
30
|
-
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.js$/);
|
|
29
|
+
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
30
|
+
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
31
|
+
if ((server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "").endsWith(".ts")) {
|
|
32
|
+
assert.match(server?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
33
|
+
assert.match(server?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
34
|
+
}
|
|
31
35
|
} finally {
|
|
32
36
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
33
37
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { describe, it } from 'node:test'
|
|
13
13
|
import assert from 'node:assert/strict'
|
|
14
14
|
import { normalizeFilePath, checkFilePathConsistency } from '../pre-execution-checks.ts'
|
|
15
|
-
import { readFileSync } from 'node:fs'
|
|
15
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
16
16
|
import { resolve } from 'node:path'
|
|
17
17
|
|
|
18
18
|
const src = readFileSync(
|
|
@@ -25,6 +25,11 @@ describe('normalizeFilePath backtick stripping (#3649)', () => {
|
|
|
25
25
|
assert.equal(normalizeFilePath('`src/foo.ts`'), 'src/foo.ts')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
+
it('strips doubled backticks and trailing notes from file paths', () => {
|
|
29
|
+
assert.equal(normalizeFilePath('``src/foo.ts`` - current state'), 'src/foo.ts')
|
|
30
|
+
assert.equal(normalizeFilePath('``src/foo.ts`` (current state)'), 'src/foo.ts')
|
|
31
|
+
})
|
|
32
|
+
|
|
28
33
|
it('strips backticks even when mixed with other normalization', () => {
|
|
29
34
|
assert.equal(normalizeFilePath('`./src//bar.ts`'), 'src/bar.ts')
|
|
30
35
|
})
|
|
@@ -66,3 +71,45 @@ describe('checkFilePathConsistency checks task.inputs not task.files (#3626)', (
|
|
|
66
71
|
)
|
|
67
72
|
})
|
|
68
73
|
})
|
|
74
|
+
|
|
75
|
+
describe('checkFilePathConsistency handles doubled-backtick annotations (#3892)', () => {
|
|
76
|
+
it('accepts existing files when task.inputs include doubled-backtick notes', () => {
|
|
77
|
+
const task = {
|
|
78
|
+
milestone_id: 'M001',
|
|
79
|
+
slice_id: 'S01',
|
|
80
|
+
id: 'T01',
|
|
81
|
+
title: 'Test Task',
|
|
82
|
+
status: 'pending',
|
|
83
|
+
one_liner: '',
|
|
84
|
+
narrative: '',
|
|
85
|
+
verification_result: '',
|
|
86
|
+
duration: '',
|
|
87
|
+
completed_at: null,
|
|
88
|
+
blocker_discovered: false,
|
|
89
|
+
deviations: '',
|
|
90
|
+
known_issues: '',
|
|
91
|
+
key_files: [],
|
|
92
|
+
key_decisions: [],
|
|
93
|
+
full_summary_md: '',
|
|
94
|
+
description: '',
|
|
95
|
+
estimate: '',
|
|
96
|
+
files: [],
|
|
97
|
+
verify: '',
|
|
98
|
+
inputs: ['``src/foo.ts`` (current state)'],
|
|
99
|
+
expected_output: [],
|
|
100
|
+
observability_impact: '',
|
|
101
|
+
full_plan_md: '',
|
|
102
|
+
sequence: 0,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const tmp = resolve(process.cwd(), '.tmp-pre-exec-3892')
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(resolve(tmp, 'src'), { recursive: true })
|
|
108
|
+
writeFileSync(resolve(tmp, 'src', 'foo.ts'), '// ok')
|
|
109
|
+
const results = checkFilePathConsistency([task as any], tmp)
|
|
110
|
+
assert.deepEqual(results, [])
|
|
111
|
+
} finally {
|
|
112
|
+
rmSync(tmp, { recursive: true, force: true })
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -22,16 +22,17 @@ describe("resource-loader import path", () => {
|
|
|
22
22
|
);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test("uses
|
|
26
|
-
// The fix uses
|
|
27
|
-
// dist/resource-loader.js
|
|
25
|
+
test("uses GSD_PKG_ROOT to resolve resource-loader from package root", () => {
|
|
26
|
+
// The fix uses GSD_PKG_ROOT (set by loader.ts) to construct an absolute
|
|
27
|
+
// file URL to dist/resource-loader.js — works in both source and deployed,
|
|
28
|
+
// and on Windows where raw paths fail with ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
28
29
|
assert.ok(
|
|
29
|
-
autoSrc.includes('
|
|
30
|
-
"auto.ts should use
|
|
30
|
+
autoSrc.includes('process.env.GSD_PKG_ROOT'),
|
|
31
|
+
"auto.ts should use GSD_PKG_ROOT to resolve resource-loader",
|
|
31
32
|
);
|
|
32
33
|
assert.ok(
|
|
33
|
-
autoSrc.includes('
|
|
34
|
-
"auto.ts should
|
|
34
|
+
autoSrc.includes('pathToFileURL'),
|
|
35
|
+
"auto.ts should convert path to file URL for cross-platform import()",
|
|
35
36
|
);
|
|
36
37
|
});
|
|
37
38
|
});
|
|
@@ -74,6 +74,27 @@ test("validateDirectory: C:\\Windows is blocked", { skip: !isWindows ? "Windows-
|
|
|
74
74
|
assert.equal(result.severity, "blocked");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
test("validateDirectory: D:\\Windows is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
78
|
+
const result = validateDirectory("D:\\Windows");
|
|
79
|
+
assert.equal(result.safe, false);
|
|
80
|
+
assert.equal(result.severity, "blocked");
|
|
81
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("validateDirectory: E:\\Program Files is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
85
|
+
const result = validateDirectory("E:\\Program Files");
|
|
86
|
+
assert.equal(result.safe, false);
|
|
87
|
+
assert.equal(result.severity, "blocked");
|
|
88
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("validateDirectory: any Windows drive root is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
92
|
+
const result = validateDirectory("D:\\");
|
|
93
|
+
assert.equal(result.safe, false);
|
|
94
|
+
assert.equal(result.severity, "blocked");
|
|
95
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
96
|
+
});
|
|
97
|
+
|
|
77
98
|
// ─── Home directory (cross-platform) ─────────────────────────────────────────────
|
|
78
99
|
|
|
79
100
|
test("validateDirectory: home directory itself is blocked", () => {
|
|
@@ -104,7 +125,13 @@ test("validateDirectory: subdirectory of home is NOT blocked", () => {
|
|
|
104
125
|
// Regression test for #1317: GSD worktree inside $HOME must not be blocked even
|
|
105
126
|
// when the resolved project root equals $HOME (e.g. home dir is a git repo).
|
|
106
127
|
test("validateDirectory: GSD worktree path nested under home is NOT blocked (#1317)", () => {
|
|
128
|
+
const originalHome = process.env.HOME;
|
|
129
|
+
const originalUserProfile = process.env.USERPROFILE;
|
|
130
|
+
const fakeHome = makeTempDir("fake-home");
|
|
131
|
+
process.env.HOME = fakeHome;
|
|
132
|
+
process.env.USERPROFILE = fakeHome;
|
|
107
133
|
const worktreePath = join(homedir(), ".gsd", "worktrees", "M001");
|
|
134
|
+
const worktreeRoot = join(fakeHome, ".gsd", "worktrees", "M001");
|
|
108
135
|
mkdirSync(worktreePath, { recursive: true });
|
|
109
136
|
try {
|
|
110
137
|
// The worktree CWD itself is a valid location — it must pass.
|
|
@@ -112,7 +139,12 @@ test("validateDirectory: GSD worktree path nested under home is NOT blocked (#13
|
|
|
112
139
|
assert.equal(result.safe, true, "GSD worktree path should be safe to run in");
|
|
113
140
|
assert.equal(result.severity, "ok");
|
|
114
141
|
} finally {
|
|
115
|
-
|
|
142
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
143
|
+
else process.env.HOME = originalHome;
|
|
144
|
+
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
|
|
145
|
+
else process.env.USERPROFILE = originalUserProfile;
|
|
146
|
+
rmSync(worktreeRoot, { recursive: true, force: true });
|
|
147
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
116
148
|
}
|
|
117
149
|
});
|
|
118
150
|
|
|
@@ -9,10 +9,11 @@ import { deriveState, isValidationTerminal } from "../state.ts";
|
|
|
9
9
|
import { resolveExpectedArtifactPath, diagnoseExpectedArtifact } from "../auto-artifact-paths.ts";
|
|
10
10
|
import { verifyExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts";
|
|
11
11
|
import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
|
|
12
|
-
import { buildValidateMilestonePrompt } from "../auto-prompts.ts";
|
|
12
|
+
import { buildCompleteMilestonePrompt, buildValidateMilestonePrompt } from "../auto-prompts.ts";
|
|
13
13
|
import type { GSDState } from "../types.ts";
|
|
14
14
|
import { clearPathCache } from "../paths.ts";
|
|
15
15
|
import { clearParseCache } from "../files.ts";
|
|
16
|
+
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
16
17
|
|
|
17
18
|
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
18
19
|
|
|
@@ -25,9 +26,15 @@ function makeTmpBase(): string {
|
|
|
25
26
|
function cleanup(base: string): void {
|
|
26
27
|
clearPathCache();
|
|
27
28
|
clearParseCache();
|
|
29
|
+
closeDatabase();
|
|
28
30
|
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
function openTestDb(base: string): void {
|
|
34
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
35
|
+
assert.equal(openDatabase(dbPath), true, "test DB should open");
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
function writeRoadmap(base: string, mid: string, content: string): void {
|
|
32
39
|
const dir = join(base, ".gsd", "milestones", mid);
|
|
33
40
|
mkdirSync(dir, { recursive: true });
|
|
@@ -218,6 +225,85 @@ test("buildValidateMilestonePrompt inlines ASSESSMENT evidence instead of UAT sp
|
|
|
218
225
|
}
|
|
219
226
|
});
|
|
220
227
|
|
|
228
|
+
test("buildCompleteMilestonePrompt skips skipped slices from DB-backed summary inlining", async () => {
|
|
229
|
+
const base = makeTmpBase();
|
|
230
|
+
try {
|
|
231
|
+
writeRoadmap(base, "M001", `# M001: Test Milestone
|
|
232
|
+
|
|
233
|
+
## Vision
|
|
234
|
+
Test
|
|
235
|
+
|
|
236
|
+
## Success Criteria
|
|
237
|
+
- It works
|
|
238
|
+
|
|
239
|
+
## Slices
|
|
240
|
+
|
|
241
|
+
- [x] **S01: First slice** \`risk:low\` \`depends:[]\`
|
|
242
|
+
> Done
|
|
243
|
+
- [ ] **S02: Skipped slice** \`risk:low\` \`depends:[]\`
|
|
244
|
+
> Intentionally skipped
|
|
245
|
+
|
|
246
|
+
## Boundary Map
|
|
247
|
+
|
|
248
|
+
| From | To | Produces | Consumes |
|
|
249
|
+
|------|-----|----------|----------|
|
|
250
|
+
| S01 | terminal | output | nothing |
|
|
251
|
+
`);
|
|
252
|
+
openTestDb(base);
|
|
253
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
|
|
254
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First slice", status: "complete", depends: [], sequence: 1 });
|
|
255
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Skipped slice", status: "skipped", depends: [], sequence: 2 });
|
|
256
|
+
writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
|
|
257
|
+
|
|
258
|
+
const prompt = await buildCompleteMilestonePrompt("M001", "Test Milestone", base);
|
|
259
|
+
assert.match(prompt, /S01 Summary/i, "prompt should inline non-skipped slice summaries");
|
|
260
|
+
assert.doesNotMatch(prompt, /### S02 Summary/i, "prompt should not inline skipped slice summaries");
|
|
261
|
+
assert.doesNotMatch(prompt, /not found — file does not exist yet/i, "prompt should not emit skipped-slice missing-file placeholders");
|
|
262
|
+
} finally {
|
|
263
|
+
cleanup(base);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("buildValidateMilestonePrompt skips skipped slices from DB-backed summary inlining", async () => {
|
|
268
|
+
const base = makeTmpBase();
|
|
269
|
+
try {
|
|
270
|
+
writeRoadmap(base, "M001", `# M001: Test Milestone
|
|
271
|
+
|
|
272
|
+
## Vision
|
|
273
|
+
Test
|
|
274
|
+
|
|
275
|
+
## Success Criteria
|
|
276
|
+
- It works
|
|
277
|
+
|
|
278
|
+
## Slices
|
|
279
|
+
|
|
280
|
+
- [x] **S01: First slice** \`risk:low\` \`depends:[]\`
|
|
281
|
+
> Done
|
|
282
|
+
- [ ] **S02: Skipped slice** \`risk:low\` \`depends:[]\`
|
|
283
|
+
> Intentionally skipped
|
|
284
|
+
|
|
285
|
+
## Boundary Map
|
|
286
|
+
|
|
287
|
+
| From | To | Produces | Consumes |
|
|
288
|
+
|------|-----|----------|----------|
|
|
289
|
+
| S01 | terminal | output | nothing |
|
|
290
|
+
`);
|
|
291
|
+
openTestDb(base);
|
|
292
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
|
|
293
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First slice", status: "complete", depends: [], sequence: 1 });
|
|
294
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Skipped slice", status: "skipped", depends: [], sequence: 2 });
|
|
295
|
+
writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
|
|
296
|
+
writeSliceAssessment(base, "M001", "S01", "---\nverdict: PASS\n---\n# Assessment\nEvidence captured.");
|
|
297
|
+
|
|
298
|
+
const prompt = await buildValidateMilestonePrompt("M001", "Test Milestone", base);
|
|
299
|
+
assert.match(prompt, /S01 Summary/i, "prompt should inline non-skipped slice summaries");
|
|
300
|
+
assert.doesNotMatch(prompt, /### S02 Summary/i, "prompt should not inline skipped slice summaries");
|
|
301
|
+
assert.doesNotMatch(prompt, /not found — file does not exist yet/i, "prompt should not emit skipped-slice missing-file placeholders");
|
|
302
|
+
} finally {
|
|
303
|
+
cleanup(base);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
221
307
|
// ─── Dispatch rule ────────────────────────────────────────────────────────
|
|
222
308
|
|
|
223
309
|
test("dispatch rule matches validating-milestone phase", async () => {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getWorkflowTransportSupportError,
|
|
14
14
|
getRequiredWorkflowToolsForAutoUnit,
|
|
15
15
|
getRequiredWorkflowToolsForGuidedUnit,
|
|
16
|
+
supportsStructuredQuestions,
|
|
16
17
|
usesWorkflowMcpTransport,
|
|
17
18
|
} from "../workflow-mcp.ts";
|
|
18
19
|
|
|
@@ -141,7 +142,11 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the
|
|
|
141
142
|
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
142
143
|
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
143
144
|
assert.equal(typeof launch?.args?.[0], "string");
|
|
144
|
-
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
145
|
+
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\](dist[\/\\]cli\.js|src[\/\\]cli\.ts)$/);
|
|
146
|
+
if ((launch?.args?.[0] ?? "").endsWith(".ts")) {
|
|
147
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
148
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
149
|
+
}
|
|
145
150
|
});
|
|
146
151
|
|
|
147
152
|
test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the package without env hints", () => {
|
|
@@ -154,7 +159,11 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the
|
|
|
154
159
|
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
155
160
|
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
156
161
|
assert.equal(typeof launch?.args?.[0], "string");
|
|
157
|
-
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
162
|
+
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\](dist[\/\\]cli\.js|src[\/\\]cli\.ts)$/);
|
|
163
|
+
if ((launch?.args?.[0] ?? "").endsWith(".ts")) {
|
|
164
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
165
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
166
|
+
}
|
|
158
167
|
});
|
|
159
168
|
|
|
160
169
|
test("workflow MCP launch config reaches mutation tools over stdio", async () => {
|
|
@@ -165,12 +174,16 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
|
|
|
165
174
|
assert.ok(launch, "expected a workflow MCP launch config");
|
|
166
175
|
assert.match(
|
|
167
176
|
launch.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "",
|
|
168
|
-
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.js$/,
|
|
177
|
+
/(dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.js|src[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.(js|ts))$/,
|
|
169
178
|
);
|
|
170
179
|
assert.match(
|
|
171
180
|
launch.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "",
|
|
172
|
-
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.js$/,
|
|
181
|
+
/(dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.js|src[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.(js|ts))$/,
|
|
173
182
|
);
|
|
183
|
+
if ((launch.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "").endsWith(".ts")) {
|
|
184
|
+
assert.match(launch.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
185
|
+
assert.match(launch.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
186
|
+
}
|
|
174
187
|
|
|
175
188
|
const client = new Client({ name: "workflow-mcp-transport-test", version: "1.0.0" });
|
|
176
189
|
const transport = new StdioClientTransport({
|
|
@@ -189,6 +202,10 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
|
|
|
189
202
|
(tools.tools ?? []).some((tool) => tool.name === "gsd_plan_slice"),
|
|
190
203
|
"expected workflow MCP surface to expose gsd_plan_slice",
|
|
191
204
|
);
|
|
205
|
+
assert.ok(
|
|
206
|
+
(tools.tools ?? []).some((tool) => tool.name === "ask_user_questions"),
|
|
207
|
+
"expected workflow MCP surface to expose ask_user_questions",
|
|
208
|
+
);
|
|
192
209
|
|
|
193
210
|
const milestoneResult = await client.callTool(
|
|
194
211
|
{
|
|
@@ -275,6 +292,30 @@ test("usesWorkflowMcpTransport matches local externalCli providers", () => {
|
|
|
275
292
|
assert.equal(usesWorkflowMcpTransport("oauth", "local://custom"), false);
|
|
276
293
|
});
|
|
277
294
|
|
|
295
|
+
test("supportsStructuredQuestions disables structured ask flow on workflow MCP transports", () => {
|
|
296
|
+
assert.equal(
|
|
297
|
+
supportsStructuredQuestions(["ask_user_questions"], {
|
|
298
|
+
authMode: "externalCli",
|
|
299
|
+
baseUrl: "local://claude-code",
|
|
300
|
+
}),
|
|
301
|
+
false,
|
|
302
|
+
);
|
|
303
|
+
assert.equal(
|
|
304
|
+
supportsStructuredQuestions(["ask_user_questions"], {
|
|
305
|
+
authMode: "oauth",
|
|
306
|
+
baseUrl: "https://api.anthropic.com",
|
|
307
|
+
}),
|
|
308
|
+
true,
|
|
309
|
+
);
|
|
310
|
+
assert.equal(
|
|
311
|
+
supportsStructuredQuestions([], {
|
|
312
|
+
authMode: "oauth",
|
|
313
|
+
baseUrl: "https://api.anthropic.com",
|
|
314
|
+
}),
|
|
315
|
+
false,
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
278
319
|
test("transport compatibility passes when required tools fit current MCP surface", () => {
|
|
279
320
|
const error = getWorkflowTransportSupportError(
|
|
280
321
|
"claude-code",
|
|
@@ -465,18 +506,18 @@ test("transport compatibility now allows replan-slice over workflow MCP surface"
|
|
|
465
506
|
test("transport compatibility still blocks units whose MCP tools are not exposed", () => {
|
|
466
507
|
const error = getWorkflowTransportSupportError(
|
|
467
508
|
"claude-code",
|
|
468
|
-
["
|
|
509
|
+
["secure_env_collect"],
|
|
469
510
|
{
|
|
470
511
|
projectRoot: "/tmp/project",
|
|
471
512
|
env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
|
|
472
513
|
surface: "auto-mode",
|
|
473
|
-
unitType: "
|
|
514
|
+
unitType: "guided-discussion",
|
|
474
515
|
authMode: "externalCli",
|
|
475
516
|
baseUrl: "local://claude-code",
|
|
476
517
|
},
|
|
477
518
|
);
|
|
478
519
|
|
|
479
|
-
assert.match(error ?? "", /requires
|
|
520
|
+
assert.match(error ?? "", /requires secure_env_collect/);
|
|
480
521
|
assert.match(error ?? "", /currently exposes only/);
|
|
481
522
|
});
|
|
482
523
|
|