gsd-pi 2.51.0 → 2.52.0-dev.585e355
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 +4 -4
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- 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/gsd/auto/phases.js +6 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
- 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 +21 -0
- package/dist/resources/extensions/gsd/auto.js +8 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +105 -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/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/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 +33 -19
- 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 +5 -3
- 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 +15 -15
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +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 +15 -15
- 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.21054f459af5cc78.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-024d82be84800e52.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/gsd/auto/phases.ts +6 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
- 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 +21 -0
- package/src/resources/extensions/gsd/auto.ts +10 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -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/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/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 +34 -20
- package/src/resources/extensions/gsd/status-guards.ts +13 -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/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/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 +37 -4
- 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/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -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/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/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/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 +12 -3
- 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 → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_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 {
|
|
3
5
|
transaction,
|
|
4
6
|
getMilestone,
|
|
@@ -6,7 +8,6 @@ import {
|
|
|
6
8
|
insertSlice,
|
|
7
9
|
upsertMilestonePlanning,
|
|
8
10
|
upsertSlicePlanning,
|
|
9
|
-
_getAdapter,
|
|
10
11
|
} from "../gsd-db.js";
|
|
11
12
|
import { invalidateStateCache } from "../state.js";
|
|
12
13
|
import { renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
@@ -55,20 +56,6 @@ export interface PlanMilestoneResult {
|
|
|
55
56
|
roadmapPath: string;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
59
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function validateStringArray(value: unknown, field: string): string[] {
|
|
63
|
-
if (!Array.isArray(value)) {
|
|
64
|
-
throw new Error(`${field} must be an array`);
|
|
65
|
-
}
|
|
66
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
67
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
68
|
-
}
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
59
|
function validateRiskEntries(value: unknown): Array<{ risk: string; whyItMatters: string }> {
|
|
73
60
|
if (!Array.isArray(value)) {
|
|
74
61
|
throw new Error("keyRisks must be an array");
|
|
@@ -189,27 +176,34 @@ export async function handlePlanMilestone(
|
|
|
189
176
|
return { error: `validation failed: ${(err as Error).message}` };
|
|
190
177
|
}
|
|
191
178
|
|
|
192
|
-
// ──
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
179
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
180
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
181
|
+
// change between the read and the write (#2723).
|
|
182
|
+
let guardError: string | null = null;
|
|
197
183
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return
|
|
184
|
+
try {
|
|
185
|
+
transaction(() => {
|
|
186
|
+
const existingMilestone = getMilestone(params.milestoneId);
|
|
187
|
+
if (existingMilestone && isClosedStatus(existingMilestone.status)) {
|
|
188
|
+
guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
|
|
189
|
+
return;
|
|
204
190
|
}
|
|
205
|
-
|
|
206
|
-
|
|
191
|
+
|
|
192
|
+
// Validate depends_on: all dependencies must exist and be complete
|
|
193
|
+
if (params.dependsOn && params.dependsOn.length > 0) {
|
|
194
|
+
for (const depId of params.dependsOn) {
|
|
195
|
+
const dep = getMilestone(depId);
|
|
196
|
+
if (!dep) {
|
|
197
|
+
guardError = `depends_on references unknown milestone: ${depId}`;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!isClosedStatus(dep.status)) {
|
|
201
|
+
guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
207
205
|
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
206
|
|
|
211
|
-
try {
|
|
212
|
-
transaction(() => {
|
|
213
207
|
insertMilestone({
|
|
214
208
|
id: params.milestoneId,
|
|
215
209
|
title: params.title,
|
|
@@ -254,6 +248,10 @@ export async function handlePlanMilestone(
|
|
|
254
248
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
255
249
|
}
|
|
256
250
|
|
|
251
|
+
if (guardError) {
|
|
252
|
+
return { error: guardError };
|
|
253
|
+
}
|
|
254
|
+
|
|
257
255
|
let roadmapPath: string;
|
|
258
256
|
try {
|
|
259
257
|
const renderResult = await renderRoadmapFromDb(basePath, params.milestoneId);
|
|
@@ -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 {
|
|
3
5
|
transaction,
|
|
4
6
|
getMilestone,
|
|
@@ -7,7 +9,6 @@ import {
|
|
|
7
9
|
upsertSlicePlanning,
|
|
8
10
|
upsertTaskPlanning,
|
|
9
11
|
insertGateRow,
|
|
10
|
-
_getAdapter,
|
|
11
12
|
} from "../gsd-db.js";
|
|
12
13
|
import type { GateId } from "../types.js";
|
|
13
14
|
import { invalidateStateCache } from "../state.js";
|
|
@@ -51,20 +52,6 @@ export interface PlanSliceResult {
|
|
|
51
52
|
taskPlanPaths: string[];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function isNonEmptyString(value: unknown): value is string {
|
|
55
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function validateStringArray(value: unknown, field: string): string[] {
|
|
59
|
-
if (!Array.isArray(value)) {
|
|
60
|
-
throw new Error(`${field} must be an array`);
|
|
61
|
-
}
|
|
62
|
-
if (value.some((item) => !isNonEmptyString(item))) {
|
|
63
|
-
throw new Error(`${field} must contain only non-empty strings`);
|
|
64
|
-
}
|
|
65
|
-
return value;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
55
|
function validateTasks(value: unknown): PlanSliceTaskInput[] {
|
|
69
56
|
if (!Array.isArray(value) || value.length === 0) {
|
|
70
57
|
throw new Error("tasks must be a non-empty array");
|
|
@@ -146,24 +133,33 @@ export async function handlePlanSlice(
|
|
|
146
133
|
return { error: `validation failed: ${(err as Error).message}` };
|
|
147
134
|
}
|
|
148
135
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
|
|
154
|
-
return { error: `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})` };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
158
|
-
if (!parentSlice) {
|
|
159
|
-
return { error: `missing parent slice: ${params.milestoneId}/${params.sliceId}` };
|
|
160
|
-
}
|
|
161
|
-
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
162
|
-
return { error: `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first` };
|
|
163
|
-
}
|
|
136
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
137
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
138
|
+
// change between the read and the write (#2723).
|
|
139
|
+
let guardError: string | null = null;
|
|
164
140
|
|
|
165
141
|
try {
|
|
166
142
|
transaction(() => {
|
|
143
|
+
const parentMilestone = getMilestone(params.milestoneId);
|
|
144
|
+
if (!parentMilestone) {
|
|
145
|
+
guardError = `milestone not found: ${params.milestoneId}`;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (isClosedStatus(parentMilestone.status)) {
|
|
149
|
+
guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
154
|
+
if (!parentSlice) {
|
|
155
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (isClosedStatus(parentSlice.status)) {
|
|
159
|
+
guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
167
163
|
upsertSlicePlanning(params.milestoneId, params.sliceId, {
|
|
168
164
|
goal: params.goal,
|
|
169
165
|
successCriteria: params.successCriteria,
|
|
@@ -211,6 +207,10 @@ export async function handlePlanSlice(
|
|
|
211
207
|
return { error: `db write failed: ${(err as Error).message}` };
|
|
212
208
|
}
|
|
213
209
|
|
|
210
|
+
if (guardError) {
|
|
211
|
+
return { error: guardError };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
214
|
try {
|
|
215
215
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
216
216
|
invalidateStateCache();
|
|
@@ -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);
|