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
|
@@ -6,6 +6,7 @@ import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn,
|
|
|
6
6
|
import { resolveMilestoneFile, resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTasksDir, resolveGsdRootFile, gsdRoot, } from './paths.js';
|
|
7
7
|
import { findMilestoneIds } from './milestone-ids.js';
|
|
8
8
|
import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
|
|
9
|
+
import { isClosedStatus } from './status-guards.js';
|
|
9
10
|
import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
|
|
10
11
|
import { join, resolve } from 'path';
|
|
11
12
|
import { existsSync, readdirSync } from 'node:fs';
|
|
@@ -39,19 +40,13 @@ export function isMilestoneComplete(roadmap) {
|
|
|
39
40
|
return roadmap.slices.length > 0 && roadmap.slices.every(s => s.done);
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
|
-
* Check whether a VALIDATION file's verdict is terminal
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* Check whether a VALIDATION file's verdict is terminal.
|
|
44
|
+
* Any successfully extracted verdict (pass, needs-attention, needs-remediation,
|
|
45
|
+
* fail, etc.) means validation completed. Only return false when no verdict
|
|
46
|
+
* could be parsed — i.e. extractVerdict() returns undefined (#2769).
|
|
45
47
|
*/
|
|
46
48
|
export function isValidationTerminal(validationContent) {
|
|
47
|
-
|
|
48
|
-
if (!v)
|
|
49
|
-
return false;
|
|
50
|
-
// 'pass' and 'needs-attention' are always terminal.
|
|
51
|
-
// 'needs-remediation' is treated as terminal to prevent infinite loops
|
|
52
|
-
// when no remediation slices exist in the roadmap (#832). The validation
|
|
53
|
-
// report is preserved on disk for manual review.
|
|
54
|
-
return v === 'pass' || v === 'needs-attention' || v === 'needs-remediation';
|
|
49
|
+
return extractVerdict(validationContent) != null;
|
|
55
50
|
}
|
|
56
51
|
const CACHE_TTL_MS = 100;
|
|
57
52
|
let _stateCache = null;
|
|
@@ -144,7 +139,23 @@ export async function deriveState(basePath) {
|
|
|
144
139
|
let result;
|
|
145
140
|
// Dual-path: try DB-backed derivation first when hierarchy tables are populated
|
|
146
141
|
if (isDbAvailable()) {
|
|
147
|
-
|
|
142
|
+
let dbMilestones = getAllMilestones();
|
|
143
|
+
// Disk→DB reconciliation (#2631): when the milestones table is empty
|
|
144
|
+
// (e.g. failed initial migration per #2529), the reconciliation code
|
|
145
|
+
// inside deriveStateFromDb is unreachable. Populate from disk here so
|
|
146
|
+
// the DB path activates correctly.
|
|
147
|
+
if (dbMilestones.length === 0) {
|
|
148
|
+
const diskIds = findMilestoneIds(basePath);
|
|
149
|
+
let synced = false;
|
|
150
|
+
for (const diskId of diskIds) {
|
|
151
|
+
if (!isGhostMilestone(basePath, diskId)) {
|
|
152
|
+
insertMilestone({ id: diskId, status: 'active' });
|
|
153
|
+
synced = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (synced)
|
|
157
|
+
dbMilestones = getAllMilestones();
|
|
158
|
+
}
|
|
148
159
|
if (dbMilestones.length > 0) {
|
|
149
160
|
const stopDbTimer = debugTime("derive-state-db");
|
|
150
161
|
result = await deriveStateFromDb(basePath);
|
|
@@ -187,12 +198,6 @@ function extractContextTitle(content, fallback) {
|
|
|
187
198
|
return stripMilestonePrefix(h1.slice(2).trim()) || fallback;
|
|
188
199
|
}
|
|
189
200
|
// ─── DB-backed State Derivation ────────────────────────────────────────────
|
|
190
|
-
/**
|
|
191
|
-
* Helper: check if a DB status counts as "done" (handles K002 ambiguity).
|
|
192
|
-
*/
|
|
193
|
-
function isStatusDone(status) {
|
|
194
|
-
return status === 'complete' || status === 'done';
|
|
195
|
-
}
|
|
196
201
|
/**
|
|
197
202
|
* Derive GSD state from the milestones/slices/tasks DB tables.
|
|
198
203
|
* Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
|
|
@@ -276,7 +281,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
276
281
|
parkedMilestoneIds.add(m.id);
|
|
277
282
|
continue;
|
|
278
283
|
}
|
|
279
|
-
if (
|
|
284
|
+
if (isClosedStatus(m.status)) {
|
|
280
285
|
completeMilestoneIds.add(m.id);
|
|
281
286
|
continue;
|
|
282
287
|
}
|
|
@@ -288,7 +293,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
288
293
|
}
|
|
289
294
|
// Check roadmap: all slices done means milestone is complete
|
|
290
295
|
const slices = getMilestoneSlices(m.id);
|
|
291
|
-
if (slices.length > 0 && slices.every(s =>
|
|
296
|
+
if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
|
|
292
297
|
// All slices done but no summary — still counts as complete for dep resolution
|
|
293
298
|
// if a summary file exists
|
|
294
299
|
// Note: without summary file, the milestone is in validating/completing state, not complete
|
|
@@ -307,7 +312,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
307
312
|
}
|
|
308
313
|
// Ghost milestone check: no slices in DB AND no substantive files on disk
|
|
309
314
|
const slices = getMilestoneSlices(m.id);
|
|
310
|
-
if (slices.length === 0 && !
|
|
315
|
+
if (slices.length === 0 && !isClosedStatus(m.status)) {
|
|
311
316
|
// Check disk for ghost detection
|
|
312
317
|
if (isGhostMilestone(basePath, m.id))
|
|
313
318
|
continue;
|
|
@@ -328,7 +333,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
328
333
|
continue;
|
|
329
334
|
}
|
|
330
335
|
// Not complete — determine if it should be active
|
|
331
|
-
const allSlicesDone = slices.length > 0 && slices.every(s =>
|
|
336
|
+
const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
|
|
332
337
|
// Get title — prefer DB, fall back to context file extraction
|
|
333
338
|
let title = stripMilestonePrefix(m.title) || m.id;
|
|
334
339
|
if (title === m.id) {
|
|
@@ -465,7 +470,10 @@ export async function deriveStateFromDb(basePath) {
|
|
|
465
470
|
};
|
|
466
471
|
}
|
|
467
472
|
// ── All slices done → validating/completing ─────────────────────────
|
|
468
|
-
|
|
473
|
+
// Guard: [].every() === true (vacuous truth). Without the length check,
|
|
474
|
+
// an empty slice array causes a premature phase transition to
|
|
475
|
+
// validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
|
|
476
|
+
const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
|
|
469
477
|
if (allSlicesDone) {
|
|
470
478
|
const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
|
|
471
479
|
const validationContent = validationFile ? await loadFile(validationFile) : null;
|
|
@@ -495,14 +503,14 @@ export async function deriveStateFromDb(basePath) {
|
|
|
495
503
|
}
|
|
496
504
|
// ── Find active slice (first incomplete with deps satisfied) ─────────
|
|
497
505
|
const sliceProgress = {
|
|
498
|
-
done: activeMilestoneSlices.filter(s =>
|
|
506
|
+
done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
|
|
499
507
|
total: activeMilestoneSlices.length,
|
|
500
508
|
};
|
|
501
|
-
const doneSliceIds = new Set(activeMilestoneSlices.filter(s =>
|
|
509
|
+
const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id));
|
|
502
510
|
let activeSlice = null;
|
|
503
511
|
let activeSliceRow = null;
|
|
504
512
|
for (const s of activeMilestoneSlices) {
|
|
505
|
-
if (
|
|
513
|
+
if (isClosedStatus(s.status))
|
|
506
514
|
continue;
|
|
507
515
|
if (s.depends.every(dep => doneSliceIds.has(dep))) {
|
|
508
516
|
activeSlice = { id: s.id, title: s.title };
|
|
@@ -542,7 +550,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
542
550
|
// causing the dispatcher to re-dispatch the same completed task forever.
|
|
543
551
|
let reconciled = false;
|
|
544
552
|
for (const t of tasks) {
|
|
545
|
-
if (
|
|
553
|
+
if (isClosedStatus(t.status))
|
|
546
554
|
continue;
|
|
547
555
|
const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
|
|
548
556
|
if (summaryPath && existsSync(summaryPath)) {
|
|
@@ -562,10 +570,10 @@ export async function deriveStateFromDb(basePath) {
|
|
|
562
570
|
tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
|
|
563
571
|
}
|
|
564
572
|
const taskProgress = {
|
|
565
|
-
done: tasks.filter(t =>
|
|
573
|
+
done: tasks.filter(t => isClosedStatus(t.status)).length,
|
|
566
574
|
total: tasks.length,
|
|
567
575
|
};
|
|
568
|
-
const activeTaskRow = tasks.find(t => !
|
|
576
|
+
const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
|
|
569
577
|
if (!activeTaskRow && tasks.length > 0) {
|
|
570
578
|
// All tasks done but slice not marked complete → summarizing
|
|
571
579
|
return {
|
|
@@ -620,7 +628,7 @@ export async function deriveStateFromDb(basePath) {
|
|
|
620
628
|
};
|
|
621
629
|
}
|
|
622
630
|
// ── Blocker detection: check completed tasks for blocker_discovered ──
|
|
623
|
-
const completedTasks = tasks.filter(t =>
|
|
631
|
+
const completedTasks = tasks.filter(t => isClosedStatus(t.status));
|
|
624
632
|
let blockerTaskId = null;
|
|
625
633
|
for (const ct of completedTasks) {
|
|
626
634
|
if (ct.blocker_discovered) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status predicates for GSD state-machine guards.
|
|
3
|
+
*
|
|
4
|
+
* The DB stores status as free-form strings. Two values indicate
|
|
5
|
+
* "closed": "complete" (canonical) and "done" (legacy / alias).
|
|
6
|
+
* Every inline `status === "complete" || status === "done"` should
|
|
7
|
+
* use isClosedStatus() instead.
|
|
8
|
+
*/
|
|
9
|
+
/** Returns true when a milestone, slice, or task status indicates closure. */
|
|
10
|
+
export function isClosedStatus(status) {
|
|
11
|
+
return status === "complete" || status === "done";
|
|
12
|
+
}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { mkdirSync } from "node:fs";
|
|
10
|
-
import { transaction, getMilestone, getMilestoneSlices, getSliceTasks,
|
|
10
|
+
import { transaction, getMilestone, getMilestoneSlices, getSliceTasks, updateMilestoneStatus, } from "../gsd-db.js";
|
|
11
11
|
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
12
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
12
13
|
import { saveFile, clearParseCache } from "../files.js";
|
|
13
14
|
import { invalidateStateCache } from "../state.js";
|
|
14
15
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
@@ -89,7 +90,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
89
90
|
guardError = `milestone not found: ${params.milestoneId}`;
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
92
|
-
if (milestone.status
|
|
93
|
+
if (isClosedStatus(milestone.status)) {
|
|
93
94
|
guardError = `milestone ${params.milestoneId} is already complete`;
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
@@ -99,7 +100,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
99
100
|
guardError = `no slices found for milestone ${params.milestoneId}`;
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
|
-
const incompleteSlices = slices.filter(s => s.status
|
|
103
|
+
const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
|
|
103
104
|
if (incompleteSlices.length > 0) {
|
|
104
105
|
const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
|
|
105
106
|
guardError = `incomplete slices: ${incompleteIds}`;
|
|
@@ -108,7 +109,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
108
109
|
// Deep check: verify all tasks in all slices are complete
|
|
109
110
|
for (const slice of slices) {
|
|
110
111
|
const tasks = getSliceTasks(params.milestoneId, slice.id);
|
|
111
|
-
const incompleteTasks = tasks.filter(t => t.status
|
|
112
|
+
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
112
113
|
if (incompleteTasks.length > 0) {
|
|
113
114
|
const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
114
115
|
guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
|
|
@@ -116,11 +117,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
// All guards passed — perform write
|
|
119
|
-
|
|
120
|
-
adapter.prepare(`UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid`).run({
|
|
121
|
-
":completed_at": completedAt,
|
|
122
|
-
":mid": params.milestoneId,
|
|
123
|
-
});
|
|
120
|
+
updateMilestoneStatus(params.milestoneId, 'complete', completedAt);
|
|
124
121
|
});
|
|
125
122
|
if (guardError) {
|
|
126
123
|
return { error: guardError };
|
|
@@ -144,10 +141,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
144
141
|
catch (renderErr) {
|
|
145
142
|
// Disk render failed — roll back DB status so state stays consistent
|
|
146
143
|
process.stderr.write(`gsd-db: complete_milestone — disk render failed, rolling back DB status: ${renderErr.message}\n`);
|
|
147
|
-
|
|
148
|
-
if (rollbackAdapter) {
|
|
149
|
-
rollbackAdapter.prepare(`UPDATE milestones SET status = 'active', completed_at = NULL WHERE id = :mid`).run({ ":mid": params.milestoneId });
|
|
150
|
-
}
|
|
144
|
+
updateMilestoneStatus(params.milestoneId, 'active', null);
|
|
151
145
|
invalidateStateCache();
|
|
152
146
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
153
147
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
|
-
import {
|
|
11
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
12
|
+
import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
|
|
12
13
|
import { resolveSlicePath, clearPathCache } from "../paths.js";
|
|
13
14
|
import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
|
|
14
15
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -179,12 +180,12 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
179
180
|
// Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
|
|
180
181
|
// Only block if they exist and are closed.
|
|
181
182
|
const milestone = getMilestone(params.milestoneId);
|
|
182
|
-
if (milestone && (milestone.status
|
|
183
|
+
if (milestone && isClosedStatus(milestone.status)) {
|
|
183
184
|
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
184
185
|
return;
|
|
185
186
|
}
|
|
186
187
|
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
187
|
-
if (slice && (slice.status
|
|
188
|
+
if (slice && isClosedStatus(slice.status)) {
|
|
188
189
|
guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
|
|
189
190
|
return;
|
|
190
191
|
}
|
|
@@ -194,7 +195,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
194
195
|
guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
|
|
195
196
|
return;
|
|
196
197
|
}
|
|
197
|
-
const incompleteTasks = tasks.filter(t => t.status
|
|
198
|
+
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
198
199
|
if (incompleteTasks.length > 0) {
|
|
199
200
|
const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
200
201
|
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
@@ -240,26 +241,12 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
240
241
|
catch (renderErr) {
|
|
241
242
|
// Disk render failed — roll back DB status so state stays consistent
|
|
242
243
|
process.stderr.write(`gsd-db: complete_slice — disk render failed, rolling back DB status: ${renderErr.message}\n`);
|
|
243
|
-
|
|
244
|
-
if (rollbackAdapter) {
|
|
245
|
-
rollbackAdapter.prepare(`UPDATE slices SET status = 'pending' WHERE milestone_id = :mid AND id = :sid`).run({
|
|
246
|
-
":mid": params.milestoneId,
|
|
247
|
-
":sid": params.sliceId,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
244
|
+
updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
|
|
250
245
|
invalidateStateCache();
|
|
251
246
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
252
247
|
}
|
|
253
248
|
// Store rendered markdown in DB for D004 recovery
|
|
254
|
-
|
|
255
|
-
if (adapter) {
|
|
256
|
-
adapter.prepare(`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`).run({
|
|
257
|
-
":summary_md": summaryMd,
|
|
258
|
-
":uat_md": uatMd,
|
|
259
|
-
":mid": params.milestoneId,
|
|
260
|
-
":sid": params.sliceId,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
249
|
+
setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
|
|
263
250
|
// Invalidate all caches
|
|
264
251
|
invalidateStateCache();
|
|
265
252
|
clearPathCache();
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
|
-
import {
|
|
11
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
12
|
+
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, } from "../gsd-db.js";
|
|
12
13
|
import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
|
|
13
14
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
14
15
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -122,17 +123,17 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
122
123
|
// Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
|
|
123
124
|
// Only block if they exist and are closed.
|
|
124
125
|
const milestone = getMilestone(params.milestoneId);
|
|
125
|
-
if (milestone && (milestone.status
|
|
126
|
+
if (milestone && isClosedStatus(milestone.status)) {
|
|
126
127
|
guardError = `cannot complete task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
130
|
-
if (slice && (slice.status
|
|
131
|
+
if (slice && isClosedStatus(slice.status)) {
|
|
131
132
|
guardError = `cannot complete task in a closed slice: ${params.sliceId} (status: ${slice.status})`;
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
135
|
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
135
|
-
if (existingTask && (existingTask.status
|
|
136
|
+
if (existingTask && isClosedStatus(existingTask.status)) {
|
|
136
137
|
guardError = `task ${params.taskId} is already complete — use gsd_task_reopen first if you need to redo it`;
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
@@ -202,27 +203,16 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
202
203
|
catch (renderErr) {
|
|
203
204
|
// Disk render failed — roll back DB status so state stays consistent
|
|
204
205
|
process.stderr.write(`gsd-db: complete_task — disk render failed, rolling back DB status: ${renderErr.message}\n`);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
":tid": params.taskId,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
206
|
+
// Delete orphaned verification_evidence rows first (FK constraint
|
|
207
|
+
// references tasks, so evidence must go before status change).
|
|
208
|
+
// Without this, retries accumulate duplicate evidence rows (#2724).
|
|
209
|
+
deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
|
|
210
|
+
updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, 'pending');
|
|
213
211
|
invalidateStateCache();
|
|
214
212
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
215
213
|
}
|
|
216
214
|
// Store rendered markdown in DB for D004 recovery
|
|
217
|
-
|
|
218
|
-
if (adapter) {
|
|
219
|
-
adapter.prepare(`UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({
|
|
220
|
-
":md": summaryMd,
|
|
221
|
-
":mid": params.milestoneId,
|
|
222
|
-
":sid": params.sliceId,
|
|
223
|
-
":tid": params.taskId,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
215
|
+
setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
|
|
226
216
|
// Invalidate all caches
|
|
227
217
|
invalidateStateCache();
|
|
228
218
|
clearPathCache();
|
|
@@ -1,22 +1,12 @@
|
|
|
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, getMilestone, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "../gsd-db.js";
|
|
3
5
|
import { invalidateStateCache } from "../state.js";
|
|
4
6
|
import { renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
5
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
6
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
7
9
|
import { appendEvent } from "../workflow-events.js";
|
|
8
|
-
function isNonEmptyString(value) {
|
|
9
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
10
|
-
}
|
|
11
|
-
function validateStringArray(value, field) {
|
|
12
|
-
if (!Array.isArray(value)) {
|
|
13
|
-
throw new Error(`${field} must be an array`);
|
|
14
|
-
}
|
|
15
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
16
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
10
|
function validateRiskEntries(value) {
|
|
21
11
|
if (!Array.isArray(value)) {
|
|
22
12
|
throw new Error("keyRisks must be an array");
|
|
@@ -145,25 +135,31 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
145
135
|
catch (err) {
|
|
146
136
|
return { error: `validation failed: ${err.message}` };
|
|
147
137
|
}
|
|
148
|
-
// ──
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
// Validate depends_on: all dependencies must exist and be complete
|
|
154
|
-
if (params.dependsOn && params.dependsOn.length > 0) {
|
|
155
|
-
for (const depId of params.dependsOn) {
|
|
156
|
-
const dep = getMilestone(depId);
|
|
157
|
-
if (!dep) {
|
|
158
|
-
return { error: `depends_on references unknown milestone: ${depId}` };
|
|
159
|
-
}
|
|
160
|
-
if (dep.status !== "complete" && dep.status !== "done") {
|
|
161
|
-
return { error: `depends_on milestone ${depId} is not yet complete (status: ${dep.status})` };
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
138
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
139
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
140
|
+
// change between the read and the write (#2723).
|
|
141
|
+
let guardError = null;
|
|
165
142
|
try {
|
|
166
143
|
transaction(() => {
|
|
144
|
+
const existingMilestone = getMilestone(params.milestoneId);
|
|
145
|
+
if (existingMilestone && isClosedStatus(existingMilestone.status)) {
|
|
146
|
+
guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Validate depends_on: all dependencies must exist and be complete
|
|
150
|
+
if (params.dependsOn && params.dependsOn.length > 0) {
|
|
151
|
+
for (const depId of params.dependsOn) {
|
|
152
|
+
const dep = getMilestone(depId);
|
|
153
|
+
if (!dep) {
|
|
154
|
+
guardError = `depends_on references unknown milestone: ${depId}`;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!isClosedStatus(dep.status)) {
|
|
158
|
+
guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
167
163
|
insertMilestone({
|
|
168
164
|
id: params.milestoneId,
|
|
169
165
|
title: params.title,
|
|
@@ -206,6 +202,9 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
206
202
|
catch (err) {
|
|
207
203
|
return { error: `db write failed: ${err.message}` };
|
|
208
204
|
}
|
|
205
|
+
if (guardError) {
|
|
206
|
+
return { error: guardError };
|
|
207
|
+
}
|
|
209
208
|
let roadmapPath;
|
|
210
209
|
try {
|
|
211
210
|
const renderResult = await renderRoadmapFromDb(basePath, params.milestoneId);
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { clearParseCache } from "../files.js";
|
|
2
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
3
|
+
import { isNonEmptyString } from "../validation.js";
|
|
2
4
|
import { transaction, getMilestone, getSlice, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, } from "../gsd-db.js";
|
|
3
5
|
import { invalidateStateCache } from "../state.js";
|
|
4
6
|
import { renderPlanFromDb } from "../markdown-renderer.js";
|
|
5
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
6
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
7
9
|
import { appendEvent } from "../workflow-events.js";
|
|
8
|
-
function isNonEmptyString(value) {
|
|
9
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
10
|
-
}
|
|
11
|
-
function validateStringArray(value, field) {
|
|
12
|
-
if (!Array.isArray(value)) {
|
|
13
|
-
throw new Error(`${field} must be an array`);
|
|
14
|
-
}
|
|
15
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
16
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
10
|
function validateTasks(value) {
|
|
21
11
|
if (!Array.isArray(value) || value.length === 0) {
|
|
22
12
|
throw new Error("tasks must be a non-empty array");
|
|
@@ -102,22 +92,30 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
102
92
|
catch (err) {
|
|
103
93
|
return { error: `validation failed: ${err.message}` };
|
|
104
94
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
|
|
110
|
-
return { error: `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})` };
|
|
111
|
-
}
|
|
112
|
-
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
113
|
-
if (!parentSlice) {
|
|
114
|
-
return { error: `missing parent slice: ${params.milestoneId}/${params.sliceId}` };
|
|
115
|
-
}
|
|
116
|
-
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
117
|
-
return { error: `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first` };
|
|
118
|
-
}
|
|
95
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
96
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
97
|
+
// change between the read and the write (#2723).
|
|
98
|
+
let guardError = null;
|
|
119
99
|
try {
|
|
120
100
|
transaction(() => {
|
|
101
|
+
const parentMilestone = getMilestone(params.milestoneId);
|
|
102
|
+
if (!parentMilestone) {
|
|
103
|
+
guardError = `milestone not found: ${params.milestoneId}`;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (isClosedStatus(parentMilestone.status)) {
|
|
107
|
+
guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
111
|
+
if (!parentSlice) {
|
|
112
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (isClosedStatus(parentSlice.status)) {
|
|
116
|
+
guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
121
119
|
upsertSlicePlanning(params.milestoneId, params.sliceId, {
|
|
122
120
|
goal: params.goal,
|
|
123
121
|
successCriteria: params.successCriteria,
|
|
@@ -163,6 +161,9 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
163
161
|
catch (err) {
|
|
164
162
|
return { error: `db write failed: ${err.message}` };
|
|
165
163
|
}
|
|
164
|
+
if (guardError) {
|
|
165
|
+
return { error: guardError };
|
|
166
|
+
}
|
|
166
167
|
try {
|
|
167
168
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
168
169
|
invalidateStateCache();
|
|
@@ -1,22 +1,12 @@
|
|
|
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";
|
|
5
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
6
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
7
9
|
import { appendEvent } from "../workflow-events.js";
|
|
8
|
-
function isNonEmptyString(value) {
|
|
9
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
10
|
-
}
|
|
11
|
-
function validateStringArray(value, field) {
|
|
12
|
-
if (!Array.isArray(value)) {
|
|
13
|
-
throw new Error(`${field} must be an array`);
|
|
14
|
-
}
|
|
15
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
16
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
10
|
function validateParams(params) {
|
|
21
11
|
if (!isNonEmptyString(params?.milestoneId))
|
|
22
12
|
throw new Error("milestoneId is required");
|
|
@@ -50,19 +40,26 @@ export async function handlePlanTask(rawParams, basePath) {
|
|
|
50
40
|
catch (err) {
|
|
51
41
|
return { error: `validation failed: ${err.message}` };
|
|
52
42
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
58
|
-
return { error: `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})` };
|
|
59
|
-
}
|
|
60
|
-
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
61
|
-
if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
|
|
62
|
-
return { error: `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first` };
|
|
63
|
-
}
|
|
43
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
44
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
45
|
+
// change between the read and the write (#2723).
|
|
46
|
+
let guardError = null;
|
|
64
47
|
try {
|
|
65
48
|
transaction(() => {
|
|
49
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
50
|
+
if (!parentSlice) {
|
|
51
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (isClosedStatus(parentSlice.status)) {
|
|
55
|
+
guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
59
|
+
if (existingTask && isClosedStatus(existingTask.status)) {
|
|
60
|
+
guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
66
63
|
if (!existingTask) {
|
|
67
64
|
insertTask({
|
|
68
65
|
id: params.taskId,
|
|
@@ -88,6 +85,9 @@ export async function handlePlanTask(rawParams, basePath) {
|
|
|
88
85
|
catch (err) {
|
|
89
86
|
return { error: `db write failed: ${err.message}` };
|
|
90
87
|
}
|
|
88
|
+
if (guardError) {
|
|
89
|
+
return { error: guardError };
|
|
90
|
+
}
|
|
91
91
|
try {
|
|
92
92
|
const renderResult = await renderTaskPlanFromDb(basePath, params.milestoneId, params.sliceId, params.taskId);
|
|
93
93
|
invalidateStateCache();
|