gsd-pi 2.70.1 → 2.71.0-dev.72557e1
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 +139 -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 +13 -13
- 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 +13 -13
- 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 → DpE_M0QWUMtGDPkq_Dvfr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → DpE_M0QWUMtGDPkq_Dvfr}/_ssgManifest.js +0 -0
|
@@ -248,17 +248,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
248
248
|
logWarning("engine", `mkdir failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const result = ensureProjectWorkflowMcpConfig(base);
|
|
255
|
-
if (result.status !== "unchanged") {
|
|
256
|
-
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
catch (err) {
|
|
260
|
-
ctx.ui.notify(`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
261
|
-
}
|
|
251
|
+
{
|
|
252
|
+
const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
|
|
253
|
+
prepareWorkflowMcpForProject(ctx, base);
|
|
262
254
|
}
|
|
263
255
|
// Initialize GitServiceImpl
|
|
264
256
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
@@ -531,7 +523,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
531
523
|
}
|
|
532
524
|
}
|
|
533
525
|
// ── DB lifecycle ──
|
|
534
|
-
const gsdDbPath =
|
|
526
|
+
const gsdDbPath = resolveProjectRootDbPath(s.basePath);
|
|
535
527
|
const gsdDirPath = join(s.basePath, ".gsd");
|
|
536
528
|
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
537
529
|
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
@@ -82,7 +82,7 @@ export function clearInFlightTools() {
|
|
|
82
82
|
* handler. When these errors occur, retrying the same unit will produce the same
|
|
83
83
|
* failure, so the retry loop must be broken.
|
|
84
84
|
*/
|
|
85
|
-
const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON
|
|
85
|
+
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;
|
|
86
86
|
/**
|
|
87
87
|
* Returns true if the error message indicates a tool invocation failure due to
|
|
88
88
|
* malformed/truncated arguments (as opposed to a normal tool execution error).
|
|
@@ -39,6 +39,8 @@ export function registerHooks(pi) {
|
|
|
39
39
|
resetToolCallLoopGuard();
|
|
40
40
|
resetAskUserQuestionsCache();
|
|
41
41
|
await syncServiceTierStatus(ctx);
|
|
42
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
43
|
+
prepareWorkflowMcpForProject(ctx, process.cwd());
|
|
42
44
|
// Apply show_token_cost preference (#1515)
|
|
43
45
|
try {
|
|
44
46
|
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
@@ -78,6 +80,8 @@ export function registerHooks(pi) {
|
|
|
78
80
|
resetAskUserQuestionsCache();
|
|
79
81
|
clearDiscussionFlowState();
|
|
80
82
|
await syncServiceTierStatus(ctx);
|
|
83
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
84
|
+
prepareWorkflowMcpForProject(ctx, process.cwd());
|
|
81
85
|
loadToolApiKeys();
|
|
82
86
|
});
|
|
83
87
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
@@ -107,6 +111,8 @@ export function registerHooks(pi) {
|
|
|
107
111
|
return { cancel: true };
|
|
108
112
|
}
|
|
109
113
|
const basePath = process.cwd();
|
|
114
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
115
|
+
await ensureDbOpen();
|
|
110
116
|
const state = await deriveState(basePath);
|
|
111
117
|
if (!state.activeMilestone || !state.activeSlice || !state.activeTask)
|
|
112
118
|
return;
|
|
@@ -257,6 +257,10 @@ function buildWorktreeContextBlock() {
|
|
|
257
257
|
*/
|
|
258
258
|
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)$/;
|
|
259
259
|
async function buildGuidedExecuteContextInjection(prompt, basePath) {
|
|
260
|
+
const ensureStateDbOpen = async () => {
|
|
261
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
262
|
+
await ensureDbOpen();
|
|
263
|
+
};
|
|
260
264
|
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);
|
|
261
265
|
if (executeMatch) {
|
|
262
266
|
const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
|
|
@@ -265,6 +269,7 @@ async function buildGuidedExecuteContextInjection(prompt, basePath) {
|
|
|
265
269
|
const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
|
|
266
270
|
if (resumeMatch) {
|
|
267
271
|
const [, sliceId, milestoneId] = resumeMatch;
|
|
272
|
+
await ensureStateDbOpen();
|
|
268
273
|
const state = await deriveState(basePath);
|
|
269
274
|
if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
|
|
270
275
|
return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
|
|
@@ -279,6 +284,7 @@ async function buildGuidedExecuteContextInjection(prompt, basePath) {
|
|
|
279
284
|
// replanning, gate evaluation, or other non-execution phases.
|
|
280
285
|
const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
|
|
281
286
|
if (RESUME_INTENT_PATTERNS.test(trimmed)) {
|
|
287
|
+
await ensureStateDbOpen();
|
|
282
288
|
const state = await deriveState(basePath);
|
|
283
289
|
if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
|
|
284
290
|
return buildTaskExecutionContextInjection(basePath, state.activeMilestone.id, state.activeSlice.id, state.activeTask.id, state.activeTask.title);
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
2
|
-
import {
|
|
2
|
+
import { validateDirectory } from "../validate-directory.js";
|
|
3
3
|
import { resolveProjectRoot } from "../worktree.js";
|
|
4
4
|
import { showNextAction } from "../../shared/tui.js";
|
|
5
5
|
import { handleStatus } from "./handlers/core.js";
|
|
6
|
+
/**
|
|
7
|
+
* Typed error for when GSD is run outside a valid project directory.
|
|
8
|
+
* Command handlers catch this to show a friendly message instead of a raw exception.
|
|
9
|
+
*/
|
|
10
|
+
export class GSDNoProjectError extends Error {
|
|
11
|
+
constructor(reason) {
|
|
12
|
+
super(reason);
|
|
13
|
+
this.name = "GSDNoProjectError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
6
16
|
export function projectRoot() {
|
|
7
17
|
let cwd;
|
|
8
18
|
try {
|
|
@@ -13,11 +23,10 @@ export function projectRoot() {
|
|
|
13
23
|
cwd = process.env.HOME ?? "/";
|
|
14
24
|
}
|
|
15
25
|
const root = resolveProjectRoot(cwd);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
assertSafeDirectory(root);
|
|
26
|
+
const pathToCheck = root !== cwd ? cwd : root;
|
|
27
|
+
const result = validateDirectory(pathToCheck);
|
|
28
|
+
if (result.severity === "blocked") {
|
|
29
|
+
throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory.");
|
|
21
30
|
}
|
|
22
31
|
return root;
|
|
23
32
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GSDNoProjectError } from "./context.js";
|
|
1
2
|
import { handleAutoCommand } from "./handlers/auto.js";
|
|
2
3
|
import { handleCoreCommand } from "./handlers/core.js";
|
|
3
4
|
import { handleOpsCommand } from "./handlers/ops.js";
|
|
@@ -12,10 +13,19 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
12
13
|
() => handleWorkflowCommand(trimmed, ctx, pi),
|
|
13
14
|
() => handleOpsCommand(trimmed, ctx, pi),
|
|
14
15
|
];
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
for (const handler of handlers) {
|
|
18
|
+
if (await handler()) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err instanceof GSDNoProjectError) {
|
|
25
|
+
ctx.ui.notify(`${err.message} \`cd\` into a project directory first.`, "warning");
|
|
17
26
|
return;
|
|
18
27
|
}
|
|
28
|
+
throw err;
|
|
19
29
|
}
|
|
20
30
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
21
31
|
}
|
|
@@ -17,6 +17,7 @@ import { parse } from "yaml";
|
|
|
17
17
|
import { readGraph, writeGraph, getNextPendingStep, markStepComplete, expandIteration, } from "./graph.js";
|
|
18
18
|
import { injectContext } from "./context-injector.js";
|
|
19
19
|
import { parseUnitId } from "./unit-id.js";
|
|
20
|
+
import { withFileLock } from "./file-lock.js";
|
|
20
21
|
/** Read and parse the frozen DEFINITION.yaml from a run directory. */
|
|
21
22
|
export function readFrozenDefinition(runDir) {
|
|
22
23
|
const defPath = join(runDir, "DEFINITION.yaml");
|
|
@@ -135,18 +136,21 @@ export class CustomWorkflowEngine {
|
|
|
135
136
|
* Returns "milestone-complete" when all steps are now done, "continue" otherwise.
|
|
136
137
|
*/
|
|
137
138
|
async reconcile(state, completedStep) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
const graphPath = join(this.runDir, "GRAPH.yaml");
|
|
140
|
+
return await withFileLock(graphPath, () => {
|
|
141
|
+
// Re-read the graph from disk so we do not overwrite concurrent
|
|
142
|
+
// workflow edits with a stale in-memory snapshot from deriveState().
|
|
143
|
+
const graph = readGraph(this.runDir);
|
|
144
|
+
// Extract stepId from "<workflowName>/<stepId>"
|
|
145
|
+
const { milestone, slice, task } = parseUnitId(completedStep.unitId);
|
|
146
|
+
const stepId = task ?? slice ?? milestone;
|
|
147
|
+
const updatedGraph = markStepComplete(graph, stepId);
|
|
148
|
+
writeGraph(this.runDir, updatedGraph);
|
|
149
|
+
const allDone = updatedGraph.steps.every((s) => s.status === "complete" || s.status === "expanded");
|
|
150
|
+
return {
|
|
151
|
+
outcome: allDone ? "milestone-complete" : "continue",
|
|
152
|
+
};
|
|
153
|
+
});
|
|
150
154
|
}
|
|
151
155
|
/**
|
|
152
156
|
* Return UI-facing metadata for progress display.
|
|
@@ -102,10 +102,27 @@ export function getPriorSliceCompletionBlocker(base, _mainBranch, unitType, unit
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
|
+
// Positional fallback is only a heuristic for legacy slices with no
|
|
106
|
+
// declared dependencies. Skip any earlier slice that depends on the
|
|
107
|
+
// target, directly or transitively, or we can deadlock a valid zero-dep
|
|
108
|
+
// slice behind its own downstream dependents (#3720).
|
|
109
|
+
const reverseDependents = new Set();
|
|
110
|
+
let changed = true;
|
|
111
|
+
while (changed) {
|
|
112
|
+
changed = false;
|
|
113
|
+
for (const slice of slices) {
|
|
114
|
+
if (reverseDependents.has(slice.id))
|
|
115
|
+
continue;
|
|
116
|
+
if (slice.depends.some((depId) => depId === targetSid || reverseDependents.has(depId))) {
|
|
117
|
+
reverseDependents.add(slice.id);
|
|
118
|
+
changed = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
105
122
|
const targetIndex = slices.findIndex((slice) => slice.id === targetSid);
|
|
106
123
|
const incomplete = slices
|
|
107
124
|
.slice(0, targetIndex)
|
|
108
|
-
.find((slice) => !slice.done);
|
|
125
|
+
.find((slice) => !slice.done && !reverseDependents.has(slice.id));
|
|
109
126
|
if (incomplete) {
|
|
110
127
|
return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${targetMid}/${incomplete.id} is not complete.`;
|
|
111
128
|
}
|
|
@@ -23,7 +23,7 @@ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i;
|
|
|
23
23
|
const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i;
|
|
24
24
|
const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
|
|
25
25
|
// ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
|
|
26
|
-
const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
26
|
+
const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
27
27
|
// Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
|
|
28
28
|
// This eliminates the need to enumerate every error message variant individually.
|
|
29
29
|
const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
function _require(name) {
|
|
3
|
+
try {
|
|
4
|
+
return require(name);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
try {
|
|
8
|
+
const gsdPiRequire = require("module").createRequire(require("path").join(process.cwd(), "node_modules", "gsd-pi", "index.js"));
|
|
9
|
+
return gsdPiRequire(name);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function withFileLockSync(filePath, fn) {
|
|
17
|
+
const lockfile = _require("proper-lockfile");
|
|
18
|
+
if (!lockfile)
|
|
19
|
+
return fn();
|
|
20
|
+
if (!existsSync(filePath))
|
|
21
|
+
return fn();
|
|
22
|
+
try {
|
|
23
|
+
const release = lockfile.lockSync(filePath, { retries: 5, stale: 10000 });
|
|
24
|
+
try {
|
|
25
|
+
return fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
release();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
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
|
+
export async function withFileLock(filePath, fn) {
|
|
40
|
+
const lockfile = _require("proper-lockfile");
|
|
41
|
+
if (!lockfile)
|
|
42
|
+
return await fn();
|
|
43
|
+
if (!existsSync(filePath))
|
|
44
|
+
return await fn();
|
|
45
|
+
try {
|
|
46
|
+
const release = await lockfile.lock(filePath, { retries: 5, stale: 10000 });
|
|
47
|
+
try {
|
|
48
|
+
return await fn();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await release();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err.code === "ELOCKED") {
|
|
56
|
+
return await fn();
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -334,8 +334,9 @@ function resolveAvailableModel(modelId, availableModels, currentProvider) {
|
|
|
334
334
|
* Build the discuss-and-plan prompt for a new milestone.
|
|
335
335
|
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
336
336
|
*/
|
|
337
|
-
function buildDiscussPrompt(nextId, preamble, _basePath, preparationContext) {
|
|
337
|
+
function buildDiscussPrompt(nextId, preamble, _basePath, pi, ctx, preparationContext) {
|
|
338
338
|
const milestoneRel = `.gsd/milestones/${nextId}`;
|
|
339
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
339
340
|
const inlinedTemplates = [
|
|
340
341
|
inlineTemplate("project", "Project"),
|
|
341
342
|
inlineTemplate("requirements", "Requirements"),
|
|
@@ -347,6 +348,7 @@ function buildDiscussPrompt(nextId, preamble, _basePath, preparationContext) {
|
|
|
347
348
|
milestoneId: nextId,
|
|
348
349
|
preamble,
|
|
349
350
|
preparationContext: preparationContext ?? "",
|
|
351
|
+
structuredQuestionsAvailable,
|
|
350
352
|
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
351
353
|
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
352
354
|
inlinedTemplates,
|
|
@@ -390,7 +392,7 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
|
|
|
390
392
|
* @param basePath - Root directory of the project
|
|
391
393
|
* @returns The discuss prompt string
|
|
392
394
|
*/
|
|
393
|
-
async function prepareAndBuildDiscussPrompt(ctx, nextId, preamble, basePath) {
|
|
395
|
+
async function prepareAndBuildDiscussPrompt(ctx, pi, nextId, preamble, basePath) {
|
|
394
396
|
const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
|
|
395
397
|
// Run preparation if enabled (default: true) — results are injected as
|
|
396
398
|
// supplementary context into the standard discuss prompt, NOT as a
|
|
@@ -421,7 +423,7 @@ async function prepareAndBuildDiscussPrompt(ctx, nextId, preamble, basePath) {
|
|
|
421
423
|
logWarning("guided", `preparation failed, proceeding without context: ${err.message}`);
|
|
422
424
|
}
|
|
423
425
|
}
|
|
424
|
-
return buildDiscussPrompt(nextId, preamble, basePath, preparationContext);
|
|
426
|
+
return buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext);
|
|
425
427
|
}
|
|
426
428
|
/**
|
|
427
429
|
* Bootstrap a .gsd/ project from scratch for headless use.
|
|
@@ -638,7 +640,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
638
640
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
639
641
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
640
642
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
|
|
641
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
643
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
642
644
|
}
|
|
643
645
|
return;
|
|
644
646
|
}
|
|
@@ -994,7 +996,7 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
|
|
|
994
996
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
995
997
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
996
998
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
997
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
999
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
998
1000
|
return true;
|
|
999
1001
|
}
|
|
1000
1002
|
// "back" or null
|
|
@@ -1161,7 +1163,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1161
1163
|
if (isFirst) {
|
|
1162
1164
|
// First ever — skip wizard, just ask directly
|
|
1163
1165
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1164
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1166
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1165
1167
|
}
|
|
1166
1168
|
else {
|
|
1167
1169
|
const choice = await showNextAction(ctx, {
|
|
@@ -1179,7 +1181,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1179
1181
|
});
|
|
1180
1182
|
if (choice === "new_milestone") {
|
|
1181
1183
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1182
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1184
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1183
1185
|
}
|
|
1184
1186
|
}
|
|
1185
1187
|
return;
|
|
@@ -1211,7 +1213,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1211
1213
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1212
1214
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1213
1215
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1214
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1216
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1215
1217
|
}
|
|
1216
1218
|
else if (choice === "status") {
|
|
1217
1219
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
@@ -1275,7 +1277,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1275
1277
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1276
1278
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1277
1279
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1278
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1280
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1279
1281
|
}
|
|
1280
1282
|
return;
|
|
1281
1283
|
}
|
|
@@ -1365,7 +1367,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1365
1367
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1366
1368
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1367
1369
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1368
|
-
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1370
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1369
1371
|
}
|
|
1370
1372
|
else if (choice === "discard_milestone") {
|
|
1371
1373
|
const confirmed = await showConfirm(ctx, {
|
|
@@ -225,17 +225,9 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
|
|
|
225
225
|
catch {
|
|
226
226
|
// Non-fatal — STATE.md will be regenerated on next /gsd invocation
|
|
227
227
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const result = ensureProjectWorkflowMcpConfig(basePath);
|
|
232
|
-
if (result.status !== "unchanged") {
|
|
233
|
-
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
ctx.ui.notify(`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
238
|
-
}
|
|
228
|
+
{
|
|
229
|
+
const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
|
|
230
|
+
prepareWorkflowMcpForProject(ctx, basePath);
|
|
239
231
|
}
|
|
240
232
|
ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
|
|
241
233
|
return { completed: true, bootstrapped: true };
|
|
@@ -9,16 +9,22 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
const MAX_ENTRIES = 500;
|
|
10
10
|
const FILENAME = "notifications.jsonl";
|
|
11
11
|
const LOCKFILE = "notifications.lock";
|
|
12
|
+
const DEDUP_WINDOW_MS = 30_000;
|
|
13
|
+
const DEDUP_PRUNE_THRESHOLD = 200;
|
|
12
14
|
// ─── Module State ───────────────────────────────────────────────────────
|
|
13
15
|
let _basePath = null;
|
|
14
16
|
let _lineCount = 0; // Hint for rotation — not authoritative for public API
|
|
15
17
|
let _suppressCount = 0;
|
|
18
|
+
let _recentMessageTimestamps = new Map();
|
|
16
19
|
// ─── Public API ─────────────────────────────────────────────────────────
|
|
17
20
|
/**
|
|
18
21
|
* Initialize the notification store. Call once at session start with the
|
|
19
22
|
* project root. Seeds in-memory counters from the existing file on disk.
|
|
20
23
|
*/
|
|
21
24
|
export function initNotificationStore(basePath) {
|
|
25
|
+
if (_basePath !== basePath) {
|
|
26
|
+
_recentMessageTimestamps.clear();
|
|
27
|
+
}
|
|
22
28
|
_basePath = basePath;
|
|
23
29
|
// Seed line count hint for rotation — public counters read from disk
|
|
24
30
|
_lineCount = _readEntriesFromDisk(basePath).length;
|
|
@@ -32,11 +38,24 @@ export function appendNotification(message, severity, source = "notify") {
|
|
|
32
38
|
return;
|
|
33
39
|
if (_suppressCount > 0)
|
|
34
40
|
return;
|
|
41
|
+
const persistedMessage = message.length > 500 ? message.slice(0, 500) + "…" : message;
|
|
42
|
+
const dedupKey = `${_basePath}:${severity}:${source}:${persistedMessage}`;
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const lastSeen = _recentMessageTimestamps.get(dedupKey);
|
|
45
|
+
if (lastSeen !== undefined && now - lastSeen < DEDUP_WINDOW_MS)
|
|
46
|
+
return;
|
|
47
|
+
_recentMessageTimestamps.set(dedupKey, now);
|
|
48
|
+
if (_recentMessageTimestamps.size > DEDUP_PRUNE_THRESHOLD) {
|
|
49
|
+
for (const [key, ts] of _recentMessageTimestamps) {
|
|
50
|
+
if (now - ts > DEDUP_WINDOW_MS)
|
|
51
|
+
_recentMessageTimestamps.delete(key);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
35
54
|
const entry = {
|
|
36
55
|
id: randomUUID(),
|
|
37
56
|
ts: new Date().toISOString(),
|
|
38
57
|
severity,
|
|
39
|
-
message:
|
|
58
|
+
message: persistedMessage,
|
|
40
59
|
source,
|
|
41
60
|
read: false,
|
|
42
61
|
};
|
|
@@ -154,6 +173,7 @@ export function _resetNotificationStore() {
|
|
|
154
173
|
_basePath = null;
|
|
155
174
|
_lineCount = 0;
|
|
156
175
|
_suppressCount = 0;
|
|
176
|
+
_recentMessageTimestamps = new Map();
|
|
157
177
|
}
|
|
158
178
|
// ─── Internal ───────────────────────────────────────────────────────────
|
|
159
179
|
function _readEntriesFromDisk(basePath) {
|
|
@@ -19,7 +19,7 @@ export function buildNotificationWidgetLines() {
|
|
|
19
19
|
const truncated = latest.message.length > msgMax
|
|
20
20
|
? latest.message.slice(0, msgMax - 1) + "…"
|
|
21
21
|
: latest.message;
|
|
22
|
-
return [` ${icon} [${badge}] ${truncated} (${formatShortcut("Ctrl+Alt+N")}
|
|
22
|
+
return [` ${icon} [${badge}] ${truncated} (${formatShortcut("Ctrl+Alt+N")} or /gsd notifications)`];
|
|
23
23
|
}
|
|
24
24
|
// ─── Widget init ────────────────────────────────────────────────────────
|
|
25
25
|
const REFRESH_INTERVAL_MS = 5_000;
|
|
@@ -229,6 +229,33 @@ function extractPathFromAnnotation(raw) {
|
|
|
229
229
|
// Fall back to the original behavior for already-plain paths.
|
|
230
230
|
return trimmed.replace(/`/g, "");
|
|
231
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Planning units sometimes use task.inputs for prose like "Current enum shape"
|
|
234
|
+
* instead of concrete file paths. Those entries should not fail path checks.
|
|
235
|
+
* Keep validation for anything that still looks like a real file reference:
|
|
236
|
+
* explicit backticks, globs, separators, dot-paths, or single-token basenames
|
|
237
|
+
* like Dockerfile.
|
|
238
|
+
*/
|
|
239
|
+
function shouldValidateInputAsPath(raw) {
|
|
240
|
+
const trimmed = raw.trim();
|
|
241
|
+
if (!trimmed)
|
|
242
|
+
return false;
|
|
243
|
+
if (/^`+[^`]+`+/.test(trimmed)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const candidate = extractPathFromAnnotation(trimmed);
|
|
247
|
+
if (!candidate)
|
|
248
|
+
return false;
|
|
249
|
+
if (!/\s/.test(candidate)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return (candidate.startsWith("/") ||
|
|
253
|
+
candidate.startsWith("./") ||
|
|
254
|
+
candidate.startsWith("../") ||
|
|
255
|
+
candidate.startsWith("~/") ||
|
|
256
|
+
/[\\/]/.test(candidate) ||
|
|
257
|
+
/[*?[\]{}]/.test(candidate));
|
|
258
|
+
}
|
|
232
259
|
/**
|
|
233
260
|
* Build a set of files that will be created by tasks up to (but not including) taskIndex.
|
|
234
261
|
* All paths are normalized for consistent comparison.
|
|
@@ -262,6 +289,8 @@ export function checkFilePathConsistency(tasks, basePath) {
|
|
|
262
289
|
// Skip empty strings
|
|
263
290
|
if (!file.trim())
|
|
264
291
|
continue;
|
|
292
|
+
if (!shouldValidateInputAsPath(file))
|
|
293
|
+
continue;
|
|
265
294
|
// Normalize path for consistent comparison
|
|
266
295
|
const normalizedFile = normalizeFilePath(file);
|
|
267
296
|
// Check if file exists on disk
|
|
@@ -289,7 +318,7 @@ export function checkFilePathConsistency(tasks, basePath) {
|
|
|
289
318
|
*
|
|
290
319
|
* All paths are normalized before comparison to ensure ./src/a.ts matches src/a.ts.
|
|
291
320
|
*/
|
|
292
|
-
export function checkTaskOrdering(tasks,
|
|
321
|
+
export function checkTaskOrdering(tasks, basePath) {
|
|
293
322
|
const results = [];
|
|
294
323
|
// Build map: normalized file → task index that creates it
|
|
295
324
|
const fileCreators = new Map();
|
|
@@ -309,9 +338,13 @@ export function checkTaskOrdering(tasks, _basePath) {
|
|
|
309
338
|
const task = tasks[i];
|
|
310
339
|
const filesToCheck = [...task.inputs];
|
|
311
340
|
for (const file of filesToCheck) {
|
|
341
|
+
if (!shouldValidateInputAsPath(file))
|
|
342
|
+
continue;
|
|
312
343
|
const normalizedFile = normalizeFilePath(file);
|
|
313
344
|
const creator = fileCreators.get(normalizedFile);
|
|
314
|
-
|
|
345
|
+
const absolutePath = resolve(basePath, normalizedFile);
|
|
346
|
+
const existsOnDisk = existsSync(absolutePath);
|
|
347
|
+
if (creator && creator.index > i && !existsOnDisk) {
|
|
315
348
|
// Task reads file that is created later — impossible ordering
|
|
316
349
|
results.push({
|
|
317
350
|
category: "file",
|
|
@@ -21,7 +21,7 @@ All relevant context has been preloaded below — the slice plan, all task summa
|
|
|
21
21
|
Then:
|
|
22
22
|
1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
|
|
23
23
|
2. {{skillActivation}}
|
|
24
|
-
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
|
|
24
|
+
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
|
|
25
25
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
26
26
|
5. If the slice involved runtime behavior, fill the **Operational Readiness** section (Q8) in the slice summary: health signal, failure signal, recovery procedure, and monitoring gaps. Omit entirely for simple slices with no runtime concerns.
|
|
27
27
|
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
|
|
@@ -35,7 +35,7 @@ Then:
|
|
|
35
35
|
|
|
36
36
|
**Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. You are running in auto-mode — there is no human available to answer questions. Make reasonable assumptions and document them in the slice summary. If a decision genuinely requires human input, note it in the summary and proceed with the best available option.
|
|
37
37
|
|
|
38
|
-
**File system safety:** Task summaries are preloaded in the inlined context above. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first
|
|
38
|
+
**File system safety:** Task summaries are preloaded in the inlined context above. Task artifacts use a **flat file layout** — files such as `T01-SUMMARY.md` and `T02-SUMMARY.md` live directly inside the `tasks/` directory, not inside per-task subdirectories like `tasks/T01/SUMMARY.md`. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first. Never use `tasks/*/SUMMARY.md`, and never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
39
39
|
|
|
40
40
|
**You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
|
|
41
41
|
|