gsd-pi 2.51.0 → 2.52.0-dev.655ad8a
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 +59 -36
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- package/dist/headless-query.js +1 -1
- package/dist/headless-types.d.ts +28 -0
- package/dist/headless-types.js +7 -0
- package/dist/headless.d.ts +8 -3
- package/dist/headless.js +47 -16
- package/dist/help-text.js +16 -5
- package/dist/onboarding.js +5 -4
- package/dist/remote-questions-config.js +1 -1
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
- package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
- package/dist/resources/extensions/get-secrets-from-user.js +7 -0
- package/dist/resources/extensions/gsd/auto/phases.js +34 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
- package/dist/resources/extensions/gsd/auto-start.js +2 -0
- package/dist/resources/extensions/gsd/auto-timers.js +24 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
- package/dist/resources/extensions/gsd/auto.js +30 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
- package/dist/resources/extensions/gsd/claude-import.js +60 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
- package/dist/resources/extensions/gsd/commands-config.js +10 -5
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
- package/dist/resources/extensions/gsd/detection.js +6 -6
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/dist/resources/extensions/gsd/error-classifier.js +105 -0
- package/dist/resources/extensions/gsd/git-service.js +4 -3
- package/dist/resources/extensions/gsd/gitignore.js +7 -7
- package/dist/resources/extensions/gsd/gsd-db.js +298 -45
- package/dist/resources/extensions/gsd/guided-flow.js +4 -3
- package/dist/resources/extensions/gsd/init-wizard.js +2 -2
- package/dist/resources/extensions/gsd/key-manager.js +7 -16
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
- package/dist/resources/extensions/gsd/memory-store.js +28 -13
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
- package/dist/resources/extensions/gsd/preferences-models.js +1 -13
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +13 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
- package/dist/resources/extensions/gsd/rule-registry.js +1 -1
- package/dist/resources/extensions/gsd/service-tier.js +13 -2
- package/dist/resources/extensions/gsd/state.js +38 -30
- package/dist/resources/extensions/gsd/status-guards.js +12 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
- package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
- package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
- package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
- package/dist/resources/extensions/gsd/validation.js +21 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
- package/dist/resources/extensions/remote-questions/config.js +1 -1
- package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
- package/dist/resources/extensions/search-the-web/native-search.js +1 -1
- package/dist/resources/extensions/search-the-web/provider.js +1 -1
- package/dist/resources/extensions/shared/rtk.js +14 -4
- package/dist/rtk.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- 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 +2 -2
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- 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 +4 -4
- 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 +2 -2
- 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/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 +2 -2
- 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 +2 -2
- 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 +4 -4
- 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 +5 -5
- 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 +5 -5
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +11 -11
- package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- 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 +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.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-b950e4e384cc62b3.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-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/wizard.js +4 -1
- package/package.json +2 -2
- package/packages/mcp-server/README.md +202 -0
- package/packages/mcp-server/package.json +36 -0
- package/packages/mcp-server/src/cli.ts +68 -0
- package/packages/mcp-server/src/index.ts +14 -0
- package/packages/mcp-server/src/mcp-server.test.ts +628 -0
- package/packages/mcp-server/src/server.ts +278 -0
- package/packages/mcp-server/src/session-manager.ts +328 -0
- package/packages/mcp-server/src/types.ts +107 -0
- package/packages/mcp-server/tsconfig.json +24 -0
- package/packages/pi-ai/dist/models.d.ts +14 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.js +53 -10
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +102 -1
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +30 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/models.test.ts +114 -1
- package/packages/pi-ai/src/models.ts +70 -13
- package/packages/pi-ai/src/types.ts +31 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +3 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +5 -3
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.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 +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -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/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +4 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
- package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/main.ts +5 -3
- package/packages/pi-coding-agent/src/modes/index.ts +8 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
- package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
- package/packages/rpc-client/package.json +20 -0
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +36 -8
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
- package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
- package/src/resources/extensions/get-secrets-from-user.ts +8 -0
- package/src/resources/extensions/gsd/auto/phases.ts +44 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
- package/src/resources/extensions/gsd/auto-start.ts +2 -0
- package/src/resources/extensions/gsd/auto-timers.ts +25 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
- package/src/resources/extensions/gsd/auto.ts +31 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
- package/src/resources/extensions/gsd/claude-import.ts +58 -9
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
- package/src/resources/extensions/gsd/commands-config.ts +11 -5
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
- package/src/resources/extensions/gsd/detection.ts +6 -6
- package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/src/resources/extensions/gsd/error-classifier.ts +139 -0
- package/src/resources/extensions/gsd/git-service.ts +4 -3
- package/src/resources/extensions/gsd/gitignore.ts +7 -7
- package/src/resources/extensions/gsd/gsd-db.ts +355 -63
- package/src/resources/extensions/gsd/guided-flow.ts +4 -3
- package/src/resources/extensions/gsd/init-wizard.ts +2 -2
- package/src/resources/extensions/gsd/key-manager.ts +7 -16
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
- package/src/resources/extensions/gsd/memory-store.ts +29 -18
- package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
- package/src/resources/extensions/gsd/preferences-models.ts +1 -13
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +12 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
- package/src/resources/extensions/gsd/rule-registry.ts +1 -1
- package/src/resources/extensions/gsd/service-tier.ts +14 -2
- package/src/resources/extensions/gsd/state.ts +39 -30
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
- package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
- package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
- package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
- package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
- package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
- package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
- package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
- package/src/resources/extensions/gsd/validation.ts +23 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
- package/src/resources/extensions/remote-questions/config.ts +1 -1
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/search-the-web/native-search.ts +1 -1
- package/src/resources/extensions/search-the-web/provider.ts +1 -1
- package/src/resources/extensions/shared/rtk.ts +22 -4
- package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.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/css/de141508b083f922.css +0 -1
- /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { clearParseCache } from "../files.js";
|
|
2
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
3
|
+
import { isNonEmptyString, validateStringArray } from "../validation.js";
|
|
2
4
|
import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning } from "../gsd-db.js";
|
|
3
5
|
import { invalidateStateCache } from "../state.js";
|
|
4
6
|
import { renderTaskPlanFromDb } from "../markdown-renderer.js";
|
|
@@ -32,20 +34,6 @@ export interface PlanTaskResult {
|
|
|
32
34
|
taskPlanPath: string;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
36
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function validateStringArray(value: unknown, field: string): string[] {
|
|
40
|
-
if (!Array.isArray(value)) {
|
|
41
|
-
throw new Error(`${field} must be an array`);
|
|
42
|
-
}
|
|
43
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
44
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
45
|
-
}
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
37
|
function validateParams(params: PlanTaskParams): PlanTaskParams {
|
|
50
38
|
if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
|
|
51
39
|
if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
|
|
@@ -77,21 +65,29 @@ export async function handlePlanTask(
|
|
|
77
65
|
return { error: `validation failed: ${(err as Error).message}` };
|
|
78
66
|
}
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
85
|
-
return { error: `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})` };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
89
|
-
if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
|
|
90
|
-
return { error: `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first` };
|
|
91
|
-
}
|
|
68
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
69
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
70
|
+
// change between the read and the write (#2723).
|
|
71
|
+
let guardError: string | null = null;
|
|
92
72
|
|
|
93
73
|
try {
|
|
94
74
|
transaction(() => {
|
|
75
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
76
|
+
if (!parentSlice) {
|
|
77
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (isClosedStatus(parentSlice.status)) {
|
|
81
|
+
guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
86
|
+
if (existingTask && isClosedStatus(existingTask.status)) {
|
|
87
|
+
guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
95
91
|
if (!existingTask) {
|
|
96
92
|
insertTask({
|
|
97
93
|
id: params.taskId,
|
|
@@ -117,6 +113,10 @@ export async function handlePlanTask(
|
|
|
117
113
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
118
114
|
}
|
|
119
115
|
|
|
116
|
+
if (guardError) {
|
|
117
|
+
return { error: guardError };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
120
|
try {
|
|
121
121
|
const renderResult = await renderTaskPlanFromDb(basePath, params.milestoneId, params.sliceId, params.taskId);
|
|
122
122
|
invalidateStateCache();
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { clearParseCache } from "../files.js";
|
|
3
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
4
|
+
import { isNonEmptyString } from "../validation.js";
|
|
2
5
|
import {
|
|
3
6
|
transaction,
|
|
4
7
|
getMilestone,
|
|
@@ -14,7 +17,6 @@ import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-rendere
|
|
|
14
17
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
15
18
|
import { writeManifest } from "../workflow-manifest.js";
|
|
16
19
|
import { appendEvent } from "../workflow-events.js";
|
|
17
|
-
import { join } from "node:path";
|
|
18
20
|
|
|
19
21
|
export interface SliceChangeInput {
|
|
20
22
|
sliceId: string;
|
|
@@ -47,9 +49,6 @@ export interface ReassessRoadmapResult {
|
|
|
47
49
|
roadmapPath: string;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
51
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
52
|
-
}
|
|
53
52
|
|
|
54
53
|
function validateParams(params: ReassessRoadmapParams): ReassessRoadmapParams {
|
|
55
54
|
if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
|
|
@@ -104,47 +103,6 @@ export async function handleReassessRoadmap(
|
|
|
104
103
|
return { error: `validation failed: ${(err as Error).message}` };
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
// ── Verify milestone exists and is active ────────────────────────
|
|
108
|
-
const milestone = getMilestone(params.milestoneId);
|
|
109
|
-
if (!milestone) {
|
|
110
|
-
return { error: `milestone not found: ${params.milestoneId}` };
|
|
111
|
-
}
|
|
112
|
-
if (milestone.status === "complete" || milestone.status === "done") {
|
|
113
|
-
return { error: `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})` };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── Verify completedSliceId is actually complete ──────────────────
|
|
117
|
-
const completedSlice = getSlice(params.milestoneId, params.completedSliceId);
|
|
118
|
-
if (!completedSlice) {
|
|
119
|
-
return { error: `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}` };
|
|
120
|
-
}
|
|
121
|
-
if (completedSlice.status !== "complete" && completedSlice.status !== "done") {
|
|
122
|
-
return { error: `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes` };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── Structural enforcement ────────────────────────────────────────
|
|
126
|
-
const existingSlices = getMilestoneSlices(params.milestoneId);
|
|
127
|
-
const completedSliceIds = new Set<string>();
|
|
128
|
-
for (const slice of existingSlices) {
|
|
129
|
-
if (slice.status === "complete" || slice.status === "done") {
|
|
130
|
-
completedSliceIds.add(slice.id);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Reject modifications to completed slices
|
|
135
|
-
for (const modifiedSlice of params.sliceChanges.modified) {
|
|
136
|
-
if (completedSliceIds.has(modifiedSlice.sliceId)) {
|
|
137
|
-
return { error: `cannot modify completed slice ${modifiedSlice.sliceId}` };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Reject removal of completed slices
|
|
142
|
-
for (const removedId of params.sliceChanges.removed) {
|
|
143
|
-
if (completedSliceIds.has(removedId)) {
|
|
144
|
-
return { error: `cannot remove completed slice ${removedId}` };
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
106
|
// ── Compute assessment artifact path ──────────────────────────────
|
|
149
107
|
// Assessment lives in the completed slice's directory
|
|
150
108
|
const assessmentRelPath = join(
|
|
@@ -153,9 +111,58 @@ export async function handleReassessRoadmap(
|
|
|
153
111
|
`${params.completedSliceId}-ASSESSMENT.md`,
|
|
154
112
|
);
|
|
155
113
|
|
|
156
|
-
// ──
|
|
114
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
115
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
116
|
+
// change between the read and the write (#2723).
|
|
117
|
+
let guardError: string | null = null;
|
|
118
|
+
|
|
157
119
|
try {
|
|
158
120
|
transaction(() => {
|
|
121
|
+
// Verify milestone exists and is active
|
|
122
|
+
const milestone = getMilestone(params.milestoneId);
|
|
123
|
+
if (!milestone) {
|
|
124
|
+
guardError = `milestone not found: ${params.milestoneId}`;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (isClosedStatus(milestone.status)) {
|
|
128
|
+
guardError = `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Verify completedSliceId is actually complete
|
|
133
|
+
const completedSlice = getSlice(params.milestoneId, params.completedSliceId);
|
|
134
|
+
if (!completedSlice) {
|
|
135
|
+
guardError = `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}`;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!isClosedStatus(completedSlice.status)) {
|
|
139
|
+
guardError = `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes`;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Structural enforcement — reject modifications/removal of completed slices
|
|
144
|
+
const existingSlices = getMilestoneSlices(params.milestoneId);
|
|
145
|
+
const completedSliceIds = new Set<string>();
|
|
146
|
+
for (const slice of existingSlices) {
|
|
147
|
+
if (isClosedStatus(slice.status)) {
|
|
148
|
+
completedSliceIds.add(slice.id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const modifiedSlice of params.sliceChanges.modified) {
|
|
153
|
+
if (completedSliceIds.has(modifiedSlice.sliceId)) {
|
|
154
|
+
guardError = `cannot modify completed slice ${modifiedSlice.sliceId}`;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const removedId of params.sliceChanges.removed) {
|
|
160
|
+
if (completedSliceIds.has(removedId)) {
|
|
161
|
+
guardError = `cannot remove completed slice ${removedId}`;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
159
166
|
// Record assessment
|
|
160
167
|
insertAssessment({
|
|
161
168
|
path: assessmentRelPath,
|
|
@@ -198,6 +205,10 @@ export async function handleReassessRoadmap(
|
|
|
198
205
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
if (guardError) {
|
|
209
|
+
return { error: guardError };
|
|
210
|
+
}
|
|
211
|
+
|
|
201
212
|
// ── Render artifacts ──────────────────────────────────────────────
|
|
202
213
|
try {
|
|
203
214
|
const roadmapResult = await renderRoadmapFromDb(basePath, params.milestoneId);
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
transaction,
|
|
21
21
|
} from "../gsd-db.js";
|
|
22
22
|
import { invalidateStateCache } from "../state.js";
|
|
23
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
23
24
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
24
25
|
import { writeManifest } from "../workflow-manifest.js";
|
|
25
26
|
import { appendEvent } from "../workflow-events.js";
|
|
@@ -62,8 +63,8 @@ export async function handleReopenSlice(
|
|
|
62
63
|
guardError = `milestone not found: ${params.milestoneId}`;
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
|
-
if (milestone.status
|
|
66
|
-
guardError = `cannot reopen slice
|
|
66
|
+
if (isClosedStatus(milestone.status)) {
|
|
67
|
+
guardError = `cannot reopen slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -72,7 +73,7 @@ export async function handleReopenSlice(
|
|
|
72
73
|
guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
|
-
if (slice.status
|
|
76
|
+
if (!isClosedStatus(slice.status)) {
|
|
76
77
|
guardError = `slice ${params.sliceId} is not complete (status: ${slice.status}) — nothing to reopen`;
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
transaction,
|
|
19
19
|
} from "../gsd-db.js";
|
|
20
20
|
import { invalidateStateCache } from "../state.js";
|
|
21
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
21
22
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
22
23
|
import { writeManifest } from "../workflow-manifest.js";
|
|
23
24
|
import { appendEvent } from "../workflow-events.js";
|
|
@@ -63,7 +64,7 @@ export async function handleReopenTask(
|
|
|
63
64
|
guardError = `milestone not found: ${params.milestoneId}`;
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
|
-
if (milestone.status
|
|
67
|
+
if (isClosedStatus(milestone.status)) {
|
|
67
68
|
guardError = `cannot reopen task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
@@ -73,8 +74,8 @@ export async function handleReopenTask(
|
|
|
73
74
|
guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
|
-
if (slice.status
|
|
77
|
-
guardError = `cannot reopen task
|
|
77
|
+
if (isClosedStatus(slice.status)) {
|
|
78
|
+
guardError = `cannot reopen task in a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -83,7 +84,7 @@ export async function handleReopenTask(
|
|
|
83
84
|
guardError = `task not found: ${params.milestoneId}/${params.sliceId}/${params.taskId}`;
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
|
-
if (task.status
|
|
87
|
+
if (!isClosedStatus(task.status)) {
|
|
87
88
|
guardError = `task ${params.taskId} is not complete (status: ${task.status}) — nothing to reopen`;
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
deleteTask,
|
|
11
11
|
} from "../gsd-db.js";
|
|
12
12
|
import { invalidateStateCache } from "../state.js";
|
|
13
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
14
|
+
import { isNonEmptyString } from "../validation.js";
|
|
13
15
|
import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
|
|
14
16
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
15
17
|
import { writeManifest } from "../workflow-manifest.js";
|
|
@@ -48,10 +50,6 @@ export interface ReplanSliceResult {
|
|
|
48
50
|
planPath: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
52
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
53
|
function validateParams(params: ReplanSliceParams): ReplanSliceParams {
|
|
56
54
|
if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
|
|
57
55
|
if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
|
|
@@ -90,52 +88,61 @@ export async function handleReplanSlice(
|
|
|
90
88
|
return { error: `validation failed: ${(err as Error).message}` };
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
// ──
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
99
|
-
return { error: `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})` };
|
|
100
|
-
}
|
|
91
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
92
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
93
|
+
// change between the read and the write (#2723).
|
|
94
|
+
let guardError: string | null = null;
|
|
95
|
+
let existingTaskIds: Set<string> = new Set();
|
|
101
96
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
97
|
+
try {
|
|
98
|
+
transaction(() => {
|
|
99
|
+
// Verify parent slice exists and is not closed
|
|
100
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
101
|
+
if (!parentSlice) {
|
|
102
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (isClosedStatus(parentSlice.status)) {
|
|
106
|
+
guardError = `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
// Verify blocker task exists and is complete
|
|
111
|
+
const blockerTask = getTask(params.milestoneId, params.sliceId, params.blockerTaskId);
|
|
112
|
+
if (!blockerTask) {
|
|
113
|
+
guardError = `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}`;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!isClosedStatus(blockerTask.status)) {
|
|
117
|
+
guardError = `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered`;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// Structural enforcement — reject modifications/removal of completed tasks
|
|
122
|
+
const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
123
|
+
const completedTaskIds = new Set<string>();
|
|
124
|
+
for (const task of existingTasks) {
|
|
125
|
+
if (isClosedStatus(task.status)) {
|
|
126
|
+
completedTaskIds.add(task.id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
for (const updatedTask of params.updatedTasks) {
|
|
131
|
+
if (completedTaskIds.has(updatedTask.taskId)) {
|
|
132
|
+
guardError = `cannot modify completed task ${updatedTask.taskId}`;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
for (const removedId of params.removedTaskIds) {
|
|
138
|
+
if (completedTaskIds.has(removedId)) {
|
|
139
|
+
guardError = `cannot remove completed task ${removedId}`;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
existingTaskIds = new Set(existingTasks.map((t) => t.id));
|
|
136
145
|
|
|
137
|
-
try {
|
|
138
|
-
transaction(() => {
|
|
139
146
|
// Record replan history
|
|
140
147
|
insertReplanHistory({
|
|
141
148
|
milestoneId: params.milestoneId,
|
|
@@ -189,6 +196,10 @@ export async function handleReplanSlice(
|
|
|
189
196
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
190
197
|
}
|
|
191
198
|
|
|
199
|
+
if (guardError) {
|
|
200
|
+
return { error: guardError };
|
|
201
|
+
}
|
|
202
|
+
|
|
192
203
|
// ── Render artifacts ──────────────────────────────────────────────
|
|
193
204
|
try {
|
|
194
205
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
@@ -9,7 +9,8 @@ import { join } from "node:path";
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
transaction,
|
|
12
|
-
|
|
12
|
+
insertAssessment,
|
|
13
|
+
deleteAssessmentByScope,
|
|
13
14
|
} from "../gsd-db.js";
|
|
14
15
|
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
15
16
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -76,7 +77,7 @@ export async function handleValidateMilestone(
|
|
|
76
77
|
return { error: `verdict must be one of: ${VALIDATION_VERDICTS.join(", ")}` };
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
// ──
|
|
80
|
+
// ── Resolve paths and render markdown ────────────────────────────────
|
|
80
81
|
const validationMd = renderValidationMarkdown(params);
|
|
81
82
|
|
|
82
83
|
let validationPath: string;
|
|
@@ -89,32 +90,37 @@ export async function handleValidateMilestone(
|
|
|
89
90
|
validationPath = join(manualDir, `${params.milestoneId}-VALIDATION.md`);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// ── DB write first — matches complete-task/complete-slice pattern ───
|
|
94
|
+
// Write DB before disk so a crash between the two leaves a recoverable
|
|
95
|
+
// state: the DB row exists but the file is missing, which projection
|
|
96
|
+
// rendering can regenerate. The inverse (file exists, no DB row) is
|
|
97
|
+
// harder to detect and recover from (#2725).
|
|
98
|
+
const validatedAt = new Date().toISOString();
|
|
99
|
+
|
|
100
|
+
transaction(() => {
|
|
101
|
+
insertAssessment({
|
|
102
|
+
path: validationPath,
|
|
103
|
+
milestoneId: params.milestoneId,
|
|
104
|
+
sliceId: null,
|
|
105
|
+
taskId: null,
|
|
106
|
+
status: params.verdict,
|
|
107
|
+
scope: 'milestone-validation',
|
|
108
|
+
fullContent: validationMd,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ── Filesystem render (outside transaction) ────────────────────────────
|
|
113
|
+
// If disk render fails, roll back the DB row so state stays consistent.
|
|
92
114
|
try {
|
|
93
115
|
await saveFile(validationPath, validationMd);
|
|
94
116
|
} catch (renderErr) {
|
|
95
117
|
process.stderr.write(
|
|
96
|
-
`gsd-db: validate_milestone — disk render failed: ${(renderErr as Error).message}\n`,
|
|
118
|
+
`gsd-db: validate_milestone — disk render failed, rolling back DB row: ${(renderErr as Error).message}\n`,
|
|
97
119
|
);
|
|
120
|
+
deleteAssessmentByScope(params.milestoneId, 'milestone-validation');
|
|
98
121
|
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
99
122
|
}
|
|
100
123
|
|
|
101
|
-
// ── DB write — store in assessments table ──────────────────────────────
|
|
102
|
-
const validatedAt = new Date().toISOString();
|
|
103
|
-
|
|
104
|
-
transaction(() => {
|
|
105
|
-
const adapter = _getAdapter()!;
|
|
106
|
-
adapter.prepare(
|
|
107
|
-
`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
|
108
|
-
VALUES (:path, :mid, NULL, NULL, :verdict, 'milestone-validation', :content, :created_at)`,
|
|
109
|
-
).run({
|
|
110
|
-
":path": validationPath,
|
|
111
|
-
":mid": params.milestoneId,
|
|
112
|
-
":verdict": params.verdict,
|
|
113
|
-
":content": validationMd,
|
|
114
|
-
":created_at": validatedAt,
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
124
|
invalidateStateCache();
|
|
119
125
|
clearPathCache();
|
|
120
126
|
clearParseCache();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared input-validation primitives for GSD tool handlers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Type guard: value is a string with at least one non-whitespace character. */
|
|
6
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
7
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate that `value` is an array of non-empty strings.
|
|
12
|
+
* Throws with a message referencing `field` on failure.
|
|
13
|
+
* Returns the validated array (narrowed to string[]).
|
|
14
|
+
*/
|
|
15
|
+
export function validateStringArray(value: unknown, field: string): string[] {
|
|
16
|
+
if (!Array.isArray(value)) {
|
|
17
|
+
throw new Error(`${field} must be an array`);
|
|
18
|
+
}
|
|
19
|
+
if (value.some((item) => !isNonEmptyString(item))) {
|
|
20
|
+
throw new Error(`${field} must contain only non-empty strings`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
@@ -59,7 +59,7 @@ function hydrateRemoteTokensFromAuth(): void {
|
|
|
59
59
|
for (const [providerId, envVar] of needed) {
|
|
60
60
|
try {
|
|
61
61
|
const creds = auth.getCredentialsForProvider(providerId);
|
|
62
|
-
const apiKeyCred = creds.find((c: { type: string }) => c.type === "api_key") as
|
|
62
|
+
const apiKeyCred = creds.find((c: { type: string; key?: string }) => c.type === "api_key" && !!c.key) as
|
|
63
63
|
| { type: "api_key"; key: string }
|
|
64
64
|
| undefined;
|
|
65
65
|
if (apiKeyCred?.key) {
|
|
@@ -312,7 +312,7 @@ function saveProviderToken(provider: string, token: string): void {
|
|
|
312
312
|
|
|
313
313
|
function removeProviderToken(provider: string): void {
|
|
314
314
|
const auth = getAuthStorage();
|
|
315
|
-
auth.
|
|
315
|
+
auth.remove(provider);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
export function saveRemoteQuestionsConfig(channel: "slack" | "discord" | "telegram", channelId: string): void {
|
|
@@ -28,7 +28,7 @@ export const MAX_NATIVE_SEARCHES_PER_SESSION = 15;
|
|
|
28
28
|
|
|
29
29
|
/** When true, skip native web search injection and keep Brave/custom tools active on Anthropic. */
|
|
30
30
|
export function preferBraveSearch(): boolean {
|
|
31
|
-
//
|
|
31
|
+
// PREFERENCES.md takes priority over env var
|
|
32
32
|
const prefsPref = resolveSearchProviderFromPreferences();
|
|
33
33
|
if (prefsPref === "brave" || prefsPref === "tavily" || prefsPref === "ollama") return true;
|
|
34
34
|
if (prefsPref === "native") return false;
|
|
@@ -105,7 +105,7 @@ export function resolveSearchProvider(overridePreference?: string): SearchProvid
|
|
|
105
105
|
if (overridePreference && VALID_PREFERENCES.has(overridePreference)) {
|
|
106
106
|
pref = overridePreference as SearchProviderPreference
|
|
107
107
|
} else {
|
|
108
|
-
//
|
|
108
|
+
// PREFERENCES.md takes priority over auth.json
|
|
109
109
|
const mdPref = resolveSearchProviderFromPreferences()
|
|
110
110
|
if (mdPref && mdPref !== 'auto' && mdPref !== 'native') {
|
|
111
111
|
pref = mdPref as SearchProviderPreference
|
|
@@ -5,6 +5,7 @@ import { delimiter, join } from "node:path";
|
|
|
5
5
|
|
|
6
6
|
const GSD_RTK_PATH_ENV = "GSD_RTK_PATH";
|
|
7
7
|
const GSD_RTK_DISABLED_ENV = "GSD_RTK_DISABLED";
|
|
8
|
+
const GSD_RTK_REWRITE_TIMEOUT_MS_ENV = "GSD_RTK_REWRITE_TIMEOUT_MS";
|
|
8
9
|
const RTK_TELEMETRY_DISABLED_ENV = "RTK_TELEMETRY_DISABLED";
|
|
9
10
|
const RTK_REWRITE_TIMEOUT_MS = 5_000;
|
|
10
11
|
|
|
@@ -14,6 +15,14 @@ function isTruthy(value: string | undefined): boolean {
|
|
|
14
15
|
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
function getRewriteTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
19
|
+
const configured = Number.parseInt(env[GSD_RTK_REWRITE_TIMEOUT_MS_ENV] ?? "", 10);
|
|
20
|
+
if (Number.isFinite(configured) && configured > 0) {
|
|
21
|
+
return configured;
|
|
22
|
+
}
|
|
23
|
+
return RTK_REWRITE_TIMEOUT_MS;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export function isRtkEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
18
27
|
return !isTruthy(env[GSD_RTK_DISABLED_ENV]);
|
|
19
28
|
}
|
|
@@ -96,18 +105,27 @@ export function resolveRtkBinaryPath(options: ResolveRtkBinaryPathOptions = {}):
|
|
|
96
105
|
return resolveSystemRtkPath(options.pathValue ?? getPathValue(env), platform);
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
interface RewriteCommandOptions {
|
|
109
|
+
binaryPath?: string;
|
|
110
|
+
env?: NodeJS.ProcessEnv;
|
|
111
|
+
spawnSyncImpl?: typeof spawnSync;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function rewriteCommandWithRtk(command: string, options: RewriteCommandOptions = {}): string {
|
|
115
|
+
const env = options.env ?? process.env;
|
|
116
|
+
|
|
100
117
|
if (!command.trim()) return command;
|
|
101
118
|
if (!isRtkEnabled(env)) return command;
|
|
102
119
|
|
|
103
|
-
const binaryPath = resolveRtkBinaryPath({ env });
|
|
120
|
+
const binaryPath = options.binaryPath ?? resolveRtkBinaryPath({ env });
|
|
104
121
|
if (!binaryPath) return command;
|
|
105
122
|
|
|
106
|
-
const
|
|
123
|
+
const run = options.spawnSyncImpl ?? spawnSync;
|
|
124
|
+
const result = run(binaryPath, ["rewrite", command], {
|
|
107
125
|
encoding: "utf-8",
|
|
108
126
|
env: buildRtkEnv(env),
|
|
109
127
|
stdio: ["ignore", "pipe", "ignore"],
|
|
110
|
-
timeout:
|
|
128
|
+
timeout: getRewriteTimeoutMs(env),
|
|
111
129
|
// .cmd/.bat wrappers (used by fake-rtk in tests) require shell:true on Windows
|
|
112
130
|
shell: /\.(cmd|bat)$/i.test(binaryPath),
|
|
113
131
|
});
|