gsd-pi 2.70.1 → 2.71.0-dev.06b86c6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/dist/cli.js +12 -3
- package/dist/mcp-server.js +6 -6
- package/dist/provider-migrations.d.ts +10 -0
- package/dist/provider-migrations.js +12 -0
- package/dist/resource-loader.js +136 -13
- package/dist/resources/GSD-WORKFLOW.md +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +129 -30
- package/dist/resources/extensions/get-secrets-from-user.js +17 -1
- package/dist/resources/extensions/gsd/auto-start.js +4 -12
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +6 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
- package/dist/resources/extensions/gsd/commands/context.js +15 -6
- package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +16 -12
- package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
- package/dist/resources/extensions/gsd/error-classifier.js +1 -1
- package/dist/resources/extensions/gsd/file-lock.js +60 -0
- package/dist/resources/extensions/gsd/guided-flow.js +12 -10
- package/dist/resources/extensions/gsd/init-wizard.js +3 -11
- package/dist/resources/extensions/gsd/notification-store.js +21 -1
- package/dist/resources/extensions/gsd/notification-widget.js +1 -1
- package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +33 -13
- package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
- package/dist/resources/extensions/gsd/state.js +234 -332
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
- package/dist/resources/extensions/gsd/workflow-events.js +25 -13
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
- package/dist/resources/skills/create-skill/SKILL.md +2 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- 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 +3 -3
- 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 +15 -15
- 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/package.json +1 -1
- package/packages/mcp-server/dist/env-writer.d.ts +39 -0
- package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
- package/packages/mcp-server/dist/env-writer.js +158 -0
- package/packages/mcp-server/dist/env-writer.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +11 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +102 -2
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +21 -11
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/env-writer.test.ts +280 -0
- package/packages/mcp-server/src/env-writer.ts +183 -0
- package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
- package/packages/mcp-server/src/server.ts +137 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
- package/packages/mcp-server/src/workflow-tools.ts +31 -11
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
- package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
- package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +388 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +168 -23
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +58 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +468 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +198 -29
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +66 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +1 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js +9 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
- package/packages/pi-tui/dist/components/input.d.ts +2 -0
- package/packages/pi-tui/dist/components/input.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/input.js +7 -4
- package/packages/pi-tui/dist/components/input.js.map +1 -1
- package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
- package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/markdown.js +17 -1
- package/packages/pi-tui/dist/components/markdown.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/input.test.ts +11 -0
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
- package/packages/pi-tui/src/components/input.ts +7 -4
- package/packages/pi-tui/src/components/markdown.ts +22 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +166 -31
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +145 -0
- package/src/resources/extensions/get-secrets-from-user.ts +24 -1
- package/src/resources/extensions/gsd/auto-start.ts +4 -14
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -5
- package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +19 -14
- package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
- package/src/resources/extensions/gsd/error-classifier.ts +1 -1
- package/src/resources/extensions/gsd/file-lock.ts +59 -0
- package/src/resources/extensions/gsd/guided-flow.ts +12 -9
- package/src/resources/extensions/gsd/init-wizard.ts +3 -13
- package/src/resources/extensions/gsd/notification-store.ts +19 -1
- package/src/resources/extensions/gsd/notification-widget.ts +1 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +33 -13
- package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
- package/src/resources/extensions/gsd/state.ts +274 -344
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/file-lock.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -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/workflow-events.ts +34 -25
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
- package/src/resources/skills/create-skill/SKILL.md +2 -0
- 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/{9pw9EXtXjdM7EFrCXUEPf → dYVdRaunb2ZSEA8fjkT-V}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → dYVdRaunb2ZSEA8fjkT-V}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
extractToolResultsFromSdkUserMessage,
|
|
12
12
|
getClaudeLookupCommand,
|
|
13
13
|
parseAskUserQuestionsElicitation,
|
|
14
|
+
parseTextInputElicitation,
|
|
14
15
|
parseClaudeLookupOutput,
|
|
15
16
|
roundResultToElicitationContent,
|
|
16
17
|
} from "../stream-adapter.ts";
|
|
@@ -220,6 +221,35 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
220
221
|
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
221
222
|
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
222
223
|
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
224
|
+
assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
|
|
225
|
+
} finally {
|
|
226
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
227
|
+
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
228
|
+
process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
|
|
229
|
+
process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
|
|
230
|
+
process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("buildSdkOptions disables AskUserQuestion for custom workflow MCP server names", () => {
|
|
235
|
+
const prev = {
|
|
236
|
+
GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
|
|
237
|
+
GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
|
|
238
|
+
GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
|
|
239
|
+
GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
|
|
240
|
+
GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
244
|
+
process.env.GSD_WORKFLOW_MCP_NAME = "custom-workflow";
|
|
245
|
+
process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
|
|
246
|
+
process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
|
|
247
|
+
process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
|
|
248
|
+
|
|
249
|
+
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
250
|
+
const mcpServers = options.mcpServers as Record<string, any>;
|
|
251
|
+
assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
|
|
252
|
+
assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
|
|
223
253
|
} finally {
|
|
224
254
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
225
255
|
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
@@ -255,6 +285,9 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
255
285
|
const mcpServers = (options as any).mcpServers;
|
|
256
286
|
if (mcpServers) {
|
|
257
287
|
assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
|
|
288
|
+
assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
|
|
289
|
+
} else {
|
|
290
|
+
assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
|
|
258
291
|
}
|
|
259
292
|
rmSync(emptyDir, { recursive: true, force: true });
|
|
260
293
|
} finally {
|
|
@@ -301,6 +334,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
301
334
|
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
302
335
|
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
303
336
|
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
|
|
337
|
+
assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
|
|
304
338
|
} finally {
|
|
305
339
|
process.chdir(originalCwd);
|
|
306
340
|
rmSync(repoDir, { recursive: true, force: true });
|
|
@@ -481,6 +515,117 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
481
515
|
},
|
|
482
516
|
});
|
|
483
517
|
});
|
|
518
|
+
|
|
519
|
+
test("parseTextInputElicitation recognizes secure free-text MCP forms", () => {
|
|
520
|
+
const request = {
|
|
521
|
+
serverName: "gsd-workflow",
|
|
522
|
+
message: "Enter values for environment variables.",
|
|
523
|
+
mode: "form" as const,
|
|
524
|
+
requestedSchema: {
|
|
525
|
+
type: "object" as const,
|
|
526
|
+
properties: {
|
|
527
|
+
TEST_PASSWORD: {
|
|
528
|
+
type: "string",
|
|
529
|
+
title: "TEST_PASSWORD",
|
|
530
|
+
description: "Format: min 8 characters\nLeave empty to skip.",
|
|
531
|
+
},
|
|
532
|
+
PROJECT_NAME: {
|
|
533
|
+
type: "string",
|
|
534
|
+
title: "PROJECT_NAME",
|
|
535
|
+
description: "Human-readable project name.",
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const parsed = parseTextInputElicitation(request as any);
|
|
542
|
+
assert.deepEqual(parsed, [
|
|
543
|
+
{
|
|
544
|
+
id: "TEST_PASSWORD",
|
|
545
|
+
title: "TEST_PASSWORD",
|
|
546
|
+
description: "Format: min 8 characters\nLeave empty to skip.",
|
|
547
|
+
required: false,
|
|
548
|
+
secure: true,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
id: "PROJECT_NAME",
|
|
552
|
+
title: "PROJECT_NAME",
|
|
553
|
+
description: "Human-readable project name.",
|
|
554
|
+
required: false,
|
|
555
|
+
secure: false,
|
|
556
|
+
},
|
|
557
|
+
]);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("parseTextInputElicitation accepts legacy keys schema and skips unsupported fields", () => {
|
|
561
|
+
const request = {
|
|
562
|
+
serverName: "gsd-workflow",
|
|
563
|
+
message: "Enter secure values",
|
|
564
|
+
mode: "form" as const,
|
|
565
|
+
requestedSchema: {
|
|
566
|
+
type: "object" as const,
|
|
567
|
+
keys: {
|
|
568
|
+
API_TOKEN: {
|
|
569
|
+
type: "string",
|
|
570
|
+
title: "API_TOKEN",
|
|
571
|
+
description: "Leave empty to skip.",
|
|
572
|
+
},
|
|
573
|
+
META: {
|
|
574
|
+
type: "object",
|
|
575
|
+
title: "metadata",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const parsed = parseTextInputElicitation(request as any);
|
|
582
|
+
assert.deepEqual(parsed, [
|
|
583
|
+
{
|
|
584
|
+
id: "API_TOKEN",
|
|
585
|
+
title: "API_TOKEN",
|
|
586
|
+
description: "Leave empty to skip.",
|
|
587
|
+
required: false,
|
|
588
|
+
secure: true,
|
|
589
|
+
},
|
|
590
|
+
]);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("createClaudeCodeElicitationHandler collects secure_env_collect fields through input dialogs", async () => {
|
|
594
|
+
const secureRequest = {
|
|
595
|
+
serverName: "gsd-workflow",
|
|
596
|
+
message: "Enter values for environment variables.",
|
|
597
|
+
mode: "form" as const,
|
|
598
|
+
requestedSchema: {
|
|
599
|
+
type: "object" as const,
|
|
600
|
+
properties: {
|
|
601
|
+
TEST_PASSWORD: {
|
|
602
|
+
type: "string",
|
|
603
|
+
title: "TEST_PASSWORD",
|
|
604
|
+
description: "Format: Your secure testing password\nLeave empty to skip.",
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const inputCalls: Array<{ opts?: { secure?: boolean } }> = [];
|
|
611
|
+
const handler = createClaudeCodeElicitationHandler({
|
|
612
|
+
input: async (_title: string, _placeholder?: string, opts?: { secure?: boolean }) => {
|
|
613
|
+
inputCalls.push({ opts });
|
|
614
|
+
return "super-secret";
|
|
615
|
+
},
|
|
616
|
+
} as any);
|
|
617
|
+
assert.ok(handler);
|
|
618
|
+
|
|
619
|
+
const result = await handler!(secureRequest as any, { signal: new AbortController().signal });
|
|
620
|
+
assert.deepEqual(result, {
|
|
621
|
+
action: "accept",
|
|
622
|
+
content: {
|
|
623
|
+
TEST_PASSWORD: "super-secret",
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
assert.equal(inputCalls.length, 1);
|
|
627
|
+
assert.equal(inputCalls[0]?.opts?.secure, true, "secure_env_collect fields should request secure input");
|
|
628
|
+
});
|
|
484
629
|
});
|
|
485
630
|
|
|
486
631
|
describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
|
|
@@ -126,7 +126,7 @@ async function collectOneSecret(
|
|
|
126
126
|
): Promise<string | null> {
|
|
127
127
|
if (!ctx.hasUI) return null;
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
const customResult = await ctx.ui.custom((tui: any, theme: any, _kb: any, done: (r: string | null) => void) => {
|
|
130
130
|
let value = "";
|
|
131
131
|
let cachedLines: string[] | undefined;
|
|
132
132
|
|
|
@@ -223,6 +223,29 @@ async function collectOneSecret(
|
|
|
223
223
|
handleInput,
|
|
224
224
|
};
|
|
225
225
|
});
|
|
226
|
+
|
|
227
|
+
// RPC/web surfaces may not implement ctx.ui.custom(). Fall back to a
|
|
228
|
+
// standard input prompt so users can still provide the secret.
|
|
229
|
+
if (customResult !== undefined) {
|
|
230
|
+
return customResult;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (typeof ctx.ui?.input !== "function") {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const inputTitle = `Secure value for ${keyName} (${pageIndex + 1}/${totalPages})`;
|
|
238
|
+
const inputPlaceholder = hint || "Enter secret value";
|
|
239
|
+
const inputResult = await ctx.ui.input(
|
|
240
|
+
inputTitle,
|
|
241
|
+
inputPlaceholder,
|
|
242
|
+
{ secure: true },
|
|
243
|
+
);
|
|
244
|
+
if (typeof inputResult !== "string") {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const trimmed = inputResult.trim();
|
|
248
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
226
249
|
}
|
|
227
250
|
|
|
228
251
|
/**
|
|
@@ -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
|
|
@@ -688,7 +678,7 @@ export async function bootstrapAutoSession(
|
|
|
688
678
|
}
|
|
689
679
|
|
|
690
680
|
// ── DB lifecycle ──
|
|
691
|
-
const gsdDbPath =
|
|
681
|
+
const gsdDbPath = resolveProjectRootDbPath(s.basePath);
|
|
692
682
|
const gsdDirPath = join(s.basePath, ".gsd");
|
|
693
683
|
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
694
684
|
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
@@ -92,7 +92,7 @@ export function clearInFlightTools(): void {
|
|
|
92
92
|
* handler. When these errors occur, retrying the same unit will produce the same
|
|
93
93
|
* failure, so the retry loop must be broken.
|
|
94
94
|
*/
|
|
95
|
-
const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON
|
|
95
|
+
const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}'(?: after property value)?(?: in JSON)?|Unexpected end of JSON|Unexpected token.*in JSON/i;
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Returns true if the error message indicates a tool invocation failure due to
|
|
@@ -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
|
|
|
@@ -117,6 +121,8 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
117
121
|
return { cancel: true };
|
|
118
122
|
}
|
|
119
123
|
const basePath = process.cwd();
|
|
124
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
125
|
+
await ensureDbOpen();
|
|
120
126
|
const state = await deriveState(basePath);
|
|
121
127
|
if (!state.activeMilestone || !state.activeSlice || !state.activeTask) return;
|
|
122
128
|
if (state.phase !== "executing") return;
|
|
@@ -293,6 +293,11 @@ function buildWorktreeContextBlock(): string {
|
|
|
293
293
|
const RESUME_INTENT_PATTERNS = /^(continue|resume|ok|go|go ahead|proceed|keep going|carry on|next|yes|yeah|yep|sure|do it|let's go|pick up where you left off)$/;
|
|
294
294
|
|
|
295
295
|
async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
|
|
296
|
+
const ensureStateDbOpen = async () => {
|
|
297
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
298
|
+
await ensureDbOpen();
|
|
299
|
+
};
|
|
300
|
+
|
|
296
301
|
const executeMatch = prompt.match(/Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
|
|
297
302
|
if (executeMatch) {
|
|
298
303
|
const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
|
|
@@ -302,6 +307,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
|
|
|
302
307
|
const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
|
|
303
308
|
if (resumeMatch) {
|
|
304
309
|
const [, sliceId, milestoneId] = resumeMatch;
|
|
310
|
+
await ensureStateDbOpen();
|
|
305
311
|
const state = await deriveState(basePath);
|
|
306
312
|
if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
|
|
307
313
|
return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
|
|
@@ -317,6 +323,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
|
|
|
317
323
|
// replanning, gate evaluation, or other non-execution phases.
|
|
318
324
|
const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
|
|
319
325
|
if (RESUME_INTENT_PATTERNS.test(trimmed)) {
|
|
326
|
+
await ensureStateDbOpen();
|
|
320
327
|
const state = await deriveState(basePath);
|
|
321
328
|
if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
|
|
322
329
|
return buildTaskExecutionContextInjection(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
4
|
-
import {
|
|
4
|
+
import { validateDirectory } from "../validate-directory.js";
|
|
5
5
|
import { resolveProjectRoot } from "../worktree.js";
|
|
6
6
|
import { showNextAction } from "../../shared/tui.js";
|
|
7
7
|
import { handleStatus } from "./handlers/core.js";
|
|
@@ -12,6 +12,17 @@ export interface GsdDispatchContext {
|
|
|
12
12
|
trimmed: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Typed error for when GSD is run outside a valid project directory.
|
|
17
|
+
* Command handlers catch this to show a friendly message instead of a raw exception.
|
|
18
|
+
*/
|
|
19
|
+
export class GSDNoProjectError extends Error {
|
|
20
|
+
constructor(reason: string) {
|
|
21
|
+
super(reason);
|
|
22
|
+
this.name = "GSDNoProjectError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
export function projectRoot(): string {
|
|
16
27
|
let cwd: string;
|
|
17
28
|
try {
|
|
@@ -21,10 +32,10 @@ export function projectRoot(): string {
|
|
|
21
32
|
cwd = process.env.HOME ?? "/";
|
|
22
33
|
}
|
|
23
34
|
const root = resolveProjectRoot(cwd);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
const pathToCheck = root !== cwd ? cwd : root;
|
|
36
|
+
const result = validateDirectory(pathToCheck);
|
|
37
|
+
if (result.severity === "blocked") {
|
|
38
|
+
throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory.");
|
|
28
39
|
}
|
|
29
40
|
return root;
|
|
30
41
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { GSDNoProjectError } from "./context.js";
|
|
3
4
|
import { handleAutoCommand } from "./handlers/auto.js";
|
|
4
5
|
import { handleCoreCommand } from "./handlers/core.js";
|
|
5
6
|
import { handleOpsCommand } from "./handlers/ops.js";
|
|
@@ -21,10 +22,21 @@ export async function handleGSDCommand(
|
|
|
21
22
|
() => handleOpsCommand(trimmed, ctx, pi),
|
|
22
23
|
];
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
try {
|
|
26
|
+
for (const handler of handlers) {
|
|
27
|
+
if (await handler()) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err instanceof GSDNoProjectError) {
|
|
33
|
+
ctx.ui.notify(
|
|
34
|
+
`${err.message} \`cd\` into a project directory first.`,
|
|
35
|
+
"warning",
|
|
36
|
+
);
|
|
26
37
|
return;
|
|
27
38
|
}
|
|
39
|
+
throw err;
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { injectContext } from "./context-injector.js";
|
|
35
35
|
import type { WorkflowDefinition, StepDefinition } from "./definition-loader.js";
|
|
36
36
|
import { parseUnitId } from "./unit-id.js";
|
|
37
|
+
import { withFileLock } from "./file-lock.js";
|
|
37
38
|
|
|
38
39
|
/** Read and parse the frozen DEFINITION.yaml from a run directory. */
|
|
39
40
|
export function readFrozenDefinition(runDir: string): WorkflowDefinition {
|
|
@@ -179,24 +180,28 @@ export class CustomWorkflowEngine implements WorkflowEngine {
|
|
|
179
180
|
state: EngineState,
|
|
180
181
|
completedStep: CompletedStep,
|
|
181
182
|
): Promise<ReconcileResult> {
|
|
182
|
-
|
|
183
|
-
// workflow edits with a stale in-memory snapshot from deriveState().
|
|
184
|
-
const graph = readGraph(this.runDir);
|
|
183
|
+
const graphPath = join(this.runDir, "GRAPH.yaml");
|
|
185
184
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
return await withFileLock(graphPath, () => {
|
|
186
|
+
// Re-read the graph from disk so we do not overwrite concurrent
|
|
187
|
+
// workflow edits with a stale in-memory snapshot from deriveState().
|
|
188
|
+
const graph = readGraph(this.runDir);
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
// Extract stepId from "<workflowName>/<stepId>"
|
|
191
|
+
const { milestone, slice, task } = parseUnitId(completedStep.unitId);
|
|
192
|
+
const stepId = task ?? slice ?? milestone;
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
(
|
|
195
|
-
);
|
|
194
|
+
const updatedGraph = markStepComplete(graph, stepId);
|
|
195
|
+
writeGraph(this.runDir, updatedGraph);
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
const allDone = updatedGraph.steps.every(
|
|
198
|
+
(s) => s.status === "complete" || s.status === "expanded",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
outcome: allDone ? "milestone-complete" : "continue",
|
|
203
|
+
};
|
|
204
|
+
});
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
/**
|
|
@@ -107,10 +107,27 @@ export function getPriorSliceCompletionBlocker(
|
|
|
107
107
|
// it may be a cross-milestone reference handled elsewhere.
|
|
108
108
|
}
|
|
109
109
|
} else {
|
|
110
|
+
// Positional fallback is only a heuristic for legacy slices with no
|
|
111
|
+
// declared dependencies. Skip any earlier slice that depends on the
|
|
112
|
+
// target, directly or transitively, or we can deadlock a valid zero-dep
|
|
113
|
+
// slice behind its own downstream dependents (#3720).
|
|
114
|
+
const reverseDependents = new Set<string>();
|
|
115
|
+
let changed = true;
|
|
116
|
+
while (changed) {
|
|
117
|
+
changed = false;
|
|
118
|
+
for (const slice of slices) {
|
|
119
|
+
if (reverseDependents.has(slice.id)) continue;
|
|
120
|
+
if (slice.depends.some((depId) => depId === targetSid || reverseDependents.has(depId))) {
|
|
121
|
+
reverseDependents.add(slice.id);
|
|
122
|
+
changed = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
110
127
|
const targetIndex = slices.findIndex((slice) => slice.id === targetSid);
|
|
111
128
|
const incomplete = slices
|
|
112
129
|
.slice(0, targetIndex)
|
|
113
|
-
.find((slice) => !slice.done);
|
|
130
|
+
.find((slice) => !slice.done && !reverseDependents.has(slice.id));
|
|
114
131
|
if (incomplete) {
|
|
115
132
|
return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${targetMid}/${incomplete.id} is not complete.`;
|
|
116
133
|
}
|
|
@@ -47,7 +47,7 @@ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i;
|
|
|
47
47
|
const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i;
|
|
48
48
|
const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
|
|
49
49
|
// ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
|
|
50
|
-
const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
50
|
+
const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
51
51
|
// Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
|
|
52
52
|
// This eliminates the need to enumerate every error message variant individually.
|
|
53
53
|
const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
function _require(name: string) {
|
|
4
|
+
try {
|
|
5
|
+
return require(name);
|
|
6
|
+
} catch {
|
|
7
|
+
try {
|
|
8
|
+
const gsdPiRequire = require("module").createRequire(
|
|
9
|
+
require("path").join(process.cwd(), "node_modules", "gsd-pi", "index.js")
|
|
10
|
+
);
|
|
11
|
+
return gsdPiRequire(name);
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function withFileLockSync<T>(filePath: string, fn: () => T): T {
|
|
19
|
+
const lockfile = _require("proper-lockfile");
|
|
20
|
+
if (!lockfile) return fn();
|
|
21
|
+
|
|
22
|
+
if (!existsSync(filePath)) return fn();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const release = lockfile.lockSync(filePath, { retries: 5, stale: 10000 });
|
|
26
|
+
try {
|
|
27
|
+
return fn();
|
|
28
|
+
} finally {
|
|
29
|
+
release();
|
|
30
|
+
}
|
|
31
|
+
} catch (err: any) {
|
|
32
|
+
if (err.code === "ELOCKED") {
|
|
33
|
+
// Could not get lock after retries, let's fallback to un-locked instead of crashing the whole state machine
|
|
34
|
+
return fn();
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function withFileLock<T>(filePath: string, fn: () => Promise<T> | T): Promise<T> {
|
|
41
|
+
const lockfile = _require("proper-lockfile");
|
|
42
|
+
if (!lockfile) return await fn();
|
|
43
|
+
|
|
44
|
+
if (!existsSync(filePath)) return await fn();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const release = await lockfile.lock(filePath, { retries: 5, stale: 10000 });
|
|
48
|
+
try {
|
|
49
|
+
return await fn();
|
|
50
|
+
} finally {
|
|
51
|
+
await release();
|
|
52
|
+
}
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
if (err.code === "ELOCKED") {
|
|
55
|
+
return await fn();
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -426,8 +426,9 @@ function resolveAvailableModel<T extends { id: string; provider: string }>(
|
|
|
426
426
|
* Build the discuss-and-plan prompt for a new milestone.
|
|
427
427
|
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
428
428
|
*/
|
|
429
|
-
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 {
|
|
430
430
|
const milestoneRel = `.gsd/milestones/${nextId}`;
|
|
431
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
431
432
|
const inlinedTemplates = [
|
|
432
433
|
inlineTemplate("project", "Project"),
|
|
433
434
|
inlineTemplate("requirements", "Requirements"),
|
|
@@ -439,6 +440,7 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
|
|
|
439
440
|
milestoneId: nextId,
|
|
440
441
|
preamble,
|
|
441
442
|
preparationContext: preparationContext ?? "",
|
|
443
|
+
structuredQuestionsAvailable,
|
|
442
444
|
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
443
445
|
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
444
446
|
inlinedTemplates,
|
|
@@ -486,6 +488,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
|
|
|
486
488
|
*/
|
|
487
489
|
async function prepareAndBuildDiscussPrompt(
|
|
488
490
|
ctx: ExtensionCommandContext,
|
|
491
|
+
pi: ExtensionAPI,
|
|
489
492
|
nextId: string,
|
|
490
493
|
preamble: string,
|
|
491
494
|
basePath: string,
|
|
@@ -520,7 +523,7 @@ async function prepareAndBuildDiscussPrompt(
|
|
|
520
523
|
}
|
|
521
524
|
}
|
|
522
525
|
|
|
523
|
-
return buildDiscussPrompt(nextId, preamble, basePath, preparationContext);
|
|
526
|
+
return buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext);
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
/**
|
|
@@ -780,7 +783,7 @@ export async function showDiscuss(
|
|
|
780
783
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
781
784
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
782
785
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
|
|
783
|
-
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");
|
|
784
787
|
}
|
|
785
788
|
return;
|
|
786
789
|
}
|
|
@@ -1185,7 +1188,7 @@ async function handleMilestoneActions(
|
|
|
1185
1188
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1186
1189
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1187
1190
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1188
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1191
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1189
1192
|
`New milestone ${nextId}.`,
|
|
1190
1193
|
basePath
|
|
1191
1194
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1375,7 +1378,7 @@ export async function showSmartEntry(
|
|
|
1375
1378
|
if (isFirst) {
|
|
1376
1379
|
// First ever — skip wizard, just ask directly
|
|
1377
1380
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1378
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1381
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1379
1382
|
`New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
|
|
1380
1383
|
basePath
|
|
1381
1384
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1396,7 +1399,7 @@ export async function showSmartEntry(
|
|
|
1396
1399
|
|
|
1397
1400
|
if (choice === "new_milestone") {
|
|
1398
1401
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1399
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1402
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1400
1403
|
`New milestone ${nextId}.`,
|
|
1401
1404
|
basePath
|
|
1402
1405
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1435,7 +1438,7 @@ export async function showSmartEntry(
|
|
|
1435
1438
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1436
1439
|
|
|
1437
1440
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1438
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1441
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1439
1442
|
`New milestone ${nextId}.`,
|
|
1440
1443
|
basePath
|
|
1441
1444
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1502,7 +1505,7 @@ export async function showSmartEntry(
|
|
|
1502
1505
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1503
1506
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1504
1507
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1505
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1508
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1506
1509
|
`New milestone ${nextId}.`,
|
|
1507
1510
|
basePath
|
|
1508
1511
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -1599,7 +1602,7 @@ export async function showSmartEntry(
|
|
|
1599
1602
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1600
1603
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1601
1604
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1602
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
|
|
1605
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1603
1606
|
`New milestone ${nextId}.`,
|
|
1604
1607
|
basePath
|
|
1605
1608
|
), "gsd-run", ctx, "discuss-milestone");
|
|
@@ -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");
|