gsd-pi 2.50.0-dev.d210a87 → 2.51.0-dev.7d435fe
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/claude-code-cli/stream-adapter.js +18 -19
- 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 +4 -2
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +95 -69
- 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 +1 -1
- package/dist/resources/extensions/gsd/detection.js +6 -6
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/error-classifier.js +105 -0
- 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/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.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 +21 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -10
- package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -17
- package/dist/resources/extensions/gsd/tools/complete-task.js +7 -18
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +26 -17
- package/dist/resources/extensions/gsd/tools/plan-slice.js +25 -14
- package/dist/resources/extensions/gsd/tools/plan-task.js +21 -11
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +47 -37
- package/dist/resources/extensions/gsd/tools/replan-slice.js +49 -38
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
- 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/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +21 -21
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- 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/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_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 +2 -2
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.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_client-reference-manifest.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_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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +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 +21 -21
- package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
- 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/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/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
- package/dist/wizard.js +4 -1
- package/package.json +2 -2
- 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/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
- 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-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 +5 -2
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +115 -72
- 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 +1 -1
- package/src/resources/extensions/gsd/detection.ts +6 -6
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/error-classifier.ts +139 -0
- 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/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.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 +22 -2
- 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 +1 -1
- 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 +1 -1
- 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/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/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 +3 -14
- package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -21
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -22
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +28 -18
- package/src/resources/extensions/gsd/tools/plan-slice.ts +28 -16
- package/src/resources/extensions/gsd/tools/plan-task.ts +24 -12
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +54 -42
- package/src/resources/extensions/gsd/tools/replan-slice.ts +53 -40
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
- 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/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
- 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/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -15,6 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { resolveMilestonePath, resolveMilestoneFile, buildMilestoneFileName, } from "./paths.js";
|
|
16
16
|
import { invalidateAllCaches } from "./cache.js";
|
|
17
17
|
import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
18
|
+
import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
18
19
|
// ─── Park ──────────────────────────────────────────────────────────────────
|
|
19
20
|
/**
|
|
20
21
|
* Park a milestone — creates a PARKED.md marker file with reason and timestamp.
|
|
@@ -44,6 +45,15 @@ export function parkMilestone(basePath, milestoneId, reason) {
|
|
|
44
45
|
"",
|
|
45
46
|
].join("\n");
|
|
46
47
|
writeFileSync(parkedPath, content, "utf-8");
|
|
48
|
+
// Sync DB status so deriveStateFromDb also skips this milestone (#2694)
|
|
49
|
+
if (isDbAvailable()) {
|
|
50
|
+
try {
|
|
51
|
+
updateMilestoneStatus(milestoneId, "parked");
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
process.stderr.write(`gsd: parkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
47
57
|
invalidateAllCaches();
|
|
48
58
|
return true;
|
|
49
59
|
}
|
|
@@ -60,6 +70,15 @@ export function unparkMilestone(basePath, milestoneId) {
|
|
|
60
70
|
if (!existsSync(parkedPath))
|
|
61
71
|
return false; // not parked
|
|
62
72
|
unlinkSync(parkedPath);
|
|
73
|
+
// Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
|
|
74
|
+
if (isDbAvailable()) {
|
|
75
|
+
try {
|
|
76
|
+
updateMilestoneStatus(milestoneId, "active");
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
process.stderr.write(`gsd: unparkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
63
82
|
invalidateAllCaches();
|
|
64
83
|
return true;
|
|
65
84
|
}
|
|
@@ -98,18 +98,6 @@ export function getNextFallbackModel(currentModelId, modelConfig) {
|
|
|
98
98
|
return modelsToTry[0];
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Detect whether an error message indicates a transient network error
|
|
103
|
-
* (worth retrying the same model) vs a permanent provider error
|
|
104
|
-
* (auth failure, quota exceeded, etc. -- should fall back immediately).
|
|
105
|
-
*/
|
|
106
|
-
export function isTransientNetworkError(errorMsg) {
|
|
107
|
-
if (!errorMsg)
|
|
108
|
-
return false;
|
|
109
|
-
const hasNetworkSignal = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i.test(errorMsg);
|
|
110
|
-
const hasPermanentSignal = /auth|unauthorized|forbidden|invalid.*key|quota|billing/i.test(errorMsg);
|
|
111
|
-
return hasNetworkSignal && !hasPermanentSignal;
|
|
112
|
-
}
|
|
113
101
|
/**
|
|
114
102
|
* Validate a model ID string.
|
|
115
103
|
* Returns true if the ID looks like a valid model identifier.
|
|
@@ -273,7 +261,7 @@ export function resolveContextSelection() {
|
|
|
273
261
|
return profile === "budget" ? "smart" : "full";
|
|
274
262
|
}
|
|
275
263
|
/**
|
|
276
|
-
* Resolve the search provider preference from
|
|
264
|
+
* Resolve the search provider preference from PREFERENCES.md.
|
|
277
265
|
* Returns undefined if not configured (caller falls back to existing behavior).
|
|
278
266
|
*/
|
|
279
267
|
export function resolveSearchProviderFromPreferences() {
|
|
@@ -24,27 +24,27 @@ export { validatePreferences } from "./preferences-validation.js";
|
|
|
24
24
|
// ─── Re-exports: skills ─────────────────────────────────────────────────────
|
|
25
25
|
export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
|
|
26
26
|
// ─── Re-exports: models ─────────────────────────────────────────────────────
|
|
27
|
-
export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel,
|
|
27
|
+
export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
|
|
28
28
|
// ─── Path Constants & Getters ───────────────────────────────────────────────
|
|
29
29
|
function gsdHome() {
|
|
30
30
|
return process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
31
31
|
}
|
|
32
32
|
function globalPreferencesPath() {
|
|
33
|
-
return join(gsdHome(), "
|
|
33
|
+
return join(gsdHome(), "PREFERENCES.md");
|
|
34
34
|
}
|
|
35
35
|
function legacyGlobalPreferencesPath() {
|
|
36
36
|
return join(homedir(), ".pi", "agent", "gsd-preferences.md");
|
|
37
37
|
}
|
|
38
38
|
function projectPreferencesPath() {
|
|
39
|
-
return join(gsdRoot(process.cwd()), "
|
|
39
|
+
return join(gsdRoot(process.cwd()), "PREFERENCES.md");
|
|
40
40
|
}
|
|
41
|
-
//
|
|
42
|
-
// Check
|
|
43
|
-
function
|
|
44
|
-
return join(gsdHome(), "
|
|
41
|
+
// Legacy: older versions used lowercase preferences.md.
|
|
42
|
+
// Check lowercase as a fallback so those files aren't silently ignored.
|
|
43
|
+
function globalPreferencesPathLegacy() {
|
|
44
|
+
return join(gsdHome(), "preferences.md");
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
return join(gsdRoot(process.cwd()), "
|
|
46
|
+
function projectPreferencesPathLegacy() {
|
|
47
|
+
return join(gsdRoot(process.cwd()), "preferences.md");
|
|
48
48
|
}
|
|
49
49
|
export function getGlobalGSDPreferencesPath() {
|
|
50
50
|
return globalPreferencesPath();
|
|
@@ -58,12 +58,12 @@ export function getProjectGSDPreferencesPath() {
|
|
|
58
58
|
// ─── Loading ────────────────────────────────────────────────────────────────
|
|
59
59
|
export function loadGlobalGSDPreferences() {
|
|
60
60
|
return loadPreferencesFile(globalPreferencesPath(), "global")
|
|
61
|
-
?? loadPreferencesFile(
|
|
61
|
+
?? loadPreferencesFile(globalPreferencesPathLegacy(), "global")
|
|
62
62
|
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
|
63
63
|
}
|
|
64
64
|
export function loadProjectGSDPreferences() {
|
|
65
65
|
return loadPreferencesFile(projectPreferencesPath(), "project")
|
|
66
|
-
?? loadPreferencesFile(
|
|
66
|
+
?? loadPreferencesFile(projectPreferencesPathLegacy(), "project");
|
|
67
67
|
}
|
|
68
68
|
export function loadEffectiveGSDPreferences() {
|
|
69
69
|
const globalPreferences = loadGlobalGSDPreferences();
|
|
@@ -149,7 +149,7 @@ export function parsePreferencesMarkdown(content) {
|
|
|
149
149
|
}
|
|
150
150
|
if (!_warnedUnrecognizedFormat) {
|
|
151
151
|
_warnedUnrecognizedFormat = true;
|
|
152
|
-
console.warn("[parsePreferencesMarkdown]
|
|
152
|
+
console.warn("[parsePreferencesMarkdown] PREFERENCES.md exists but uses an unrecognized format — skipping.");
|
|
153
153
|
}
|
|
154
154
|
return null;
|
|
155
155
|
}
|
|
@@ -398,7 +398,7 @@ export function resolvePreDispatchHooks() {
|
|
|
398
398
|
* Resolve the effective git isolation mode from preferences.
|
|
399
399
|
* Returns "none" (default), "worktree", or "branch".
|
|
400
400
|
*
|
|
401
|
-
* Default is "none" so GSD works out of the box without
|
|
401
|
+
* Default is "none" so GSD works out of the box without PREFERENCES.md.
|
|
402
402
|
* Worktree isolation requires explicit opt-in because it depends on git
|
|
403
403
|
* branch infrastructure that must be set up before use.
|
|
404
404
|
*/
|
|
@@ -92,7 +92,7 @@ Titles live inside file content (headings, frontmatter), not in file or director
|
|
|
92
92
|
|
|
93
93
|
### Isolation Model
|
|
94
94
|
|
|
95
|
-
Auto-mode supports three isolation modes (configured in `.gsd/
|
|
95
|
+
Auto-mode supports three isolation modes (configured in `.gsd/PREFERENCES.md` under `taskIsolation.mode`):
|
|
96
96
|
|
|
97
97
|
- **worktree** (default): Work happens in `.gsd/worktrees/<MID>/`, a full git worktree on the `milestone/<MID>` branch. Each worktree has its own working copy and `.gsd/` directory. Squash-merged back to the integration branch on milestone completion.
|
|
98
98
|
- **branch**: Work happens in the project root on a `milestone/<MID>` branch. No worktree directory — files are checked out in-place.
|
|
@@ -1,47 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Classify a provider error as transient (auto-resume) or permanent (manual resume).
|
|
3
|
-
*
|
|
4
|
-
* Transient: rate limits, server errors (500/502/503), overloaded, internal errors.
|
|
5
|
-
* These are expected to self-resolve and should auto-resume after a delay.
|
|
6
|
-
*
|
|
7
|
-
* Permanent: auth errors, invalid API key, billing issues.
|
|
8
|
-
* These require user intervention and should pause indefinitely.
|
|
9
|
-
*/
|
|
10
|
-
export function classifyProviderError(errorMsg) {
|
|
11
|
-
const isRateLimit = /rate.?limit|too many requests|429/i.test(errorMsg);
|
|
12
|
-
const isServerError = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i.test(errorMsg);
|
|
13
|
-
// Connection/process errors — transient, auto-resume after brief backoff (#2309).
|
|
14
|
-
// These indicate the process was killed, the connection was reset, or a network
|
|
15
|
-
// blip occurred. They are NOT permanent failures.
|
|
16
|
-
const isConnectionError = /terminated|connection.?reset|connection.?refused|other side closed|fetch failed|network.?(?:is\s+)?unavailable|ECONNREFUSED|ECONNRESET|EPIPE/i.test(errorMsg);
|
|
17
|
-
// Permanent errors — never auto-resume
|
|
18
|
-
const isPermanent = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i.test(errorMsg);
|
|
19
|
-
if (isPermanent && !isRateLimit) {
|
|
20
|
-
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
|
21
|
-
}
|
|
22
|
-
if (isRateLimit) {
|
|
23
|
-
// Try to extract retry-after from the message
|
|
24
|
-
const resetMatch = errorMsg.match(/reset in (\d+)s/i);
|
|
25
|
-
const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000; // default 60s for rate limits
|
|
26
|
-
return { isTransient: true, isRateLimit: true, suggestedDelayMs: delayMs };
|
|
27
|
-
}
|
|
28
|
-
if (isServerError) {
|
|
29
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 30_000 }; // 30s for server errors
|
|
30
|
-
}
|
|
31
|
-
if (isConnectionError) {
|
|
32
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
|
|
33
|
-
}
|
|
34
|
-
// Stream-truncation JSON parse errors — transient (#2572).
|
|
35
|
-
// When the API stream is cut mid-chunk, pi tries to reassemble the partial
|
|
36
|
-
// tool-call JSON and gets a SyntaxError. This is the downstream symptom of
|
|
37
|
-
// a connection drop — same root cause as ECONNRESET, one layer up.
|
|
38
|
-
const isMalformedStream = /Unexpected end of JSON|Unexpected token.*JSON|Expected double-quoted property name|SyntaxError.*JSON/i.test(errorMsg);
|
|
39
|
-
if (isMalformedStream) {
|
|
40
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s, same as connection errors
|
|
41
|
-
}
|
|
42
|
-
// Unknown error — treat as permanent (user reviews)
|
|
43
|
-
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
|
44
|
-
}
|
|
45
1
|
/**
|
|
46
2
|
* Pause auto-mode due to a provider error.
|
|
47
3
|
*
|
|
@@ -426,7 +426,7 @@ export class RuleRegistry {
|
|
|
426
426
|
formatHookStatus() {
|
|
427
427
|
const entries = this.getHookStatus();
|
|
428
428
|
if (entries.length === 0) {
|
|
429
|
-
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/
|
|
429
|
+
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/PREFERENCES.md";
|
|
430
430
|
}
|
|
431
431
|
const lines = ["Configured Hooks:", ""];
|
|
432
432
|
const postHooks = entries.filter(e => e.type === "post");
|
|
@@ -13,16 +13,27 @@ import { getGlobalGSDPreferencesPath, loadEffectiveGSDPreferences, loadGlobalGSD
|
|
|
13
13
|
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
14
14
|
const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of provider.";
|
|
15
15
|
// ─── Gating ──────────────────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Model ID prefixes (bare, without provider) that support OpenAI service tiers.
|
|
18
|
+
*
|
|
19
|
+
* This list is the fallback for callers that only have a model ID string.
|
|
20
|
+
* The authoritative source of truth is `model.capabilities.supportsServiceTier`
|
|
21
|
+
* (set via CAPABILITY_PATCHES in packages/pi-ai/src/models.ts). When callers
|
|
22
|
+
* have access to the full Model object, prefer reading capabilities directly.
|
|
23
|
+
*
|
|
24
|
+
* See: https://github.com/gsd-build/gsd-2/issues/2546
|
|
25
|
+
*/
|
|
26
|
+
const SERVICE_TIER_MODEL_PREFIXES = ["gpt-5.4"];
|
|
16
27
|
/**
|
|
17
28
|
* Returns true when the given model ID supports OpenAI service tiers.
|
|
18
|
-
*
|
|
29
|
+
* Reads from SERVICE_TIER_MODEL_PREFIXES — update that list, not this function.
|
|
19
30
|
*/
|
|
20
31
|
export function supportsServiceTier(modelId) {
|
|
21
32
|
if (!modelId)
|
|
22
33
|
return false;
|
|
23
34
|
// Strip provider prefix if present (e.g. "openai/gpt-5.4" → "gpt-5.4")
|
|
24
35
|
const bare = modelId.includes("/") ? modelId.split("/").pop() : modelId;
|
|
25
|
-
return bare.startsWith(
|
|
36
|
+
return SERVICE_TIER_MODEL_PREFIXES.some((prefix) => bare.startsWith(prefix));
|
|
26
37
|
}
|
|
27
38
|
// ─── Status Formatting ───────────────────────────────────────────────────────
|
|
28
39
|
/**
|
|
@@ -144,7 +144,23 @@ export async function deriveState(basePath) {
|
|
|
144
144
|
let result;
|
|
145
145
|
// Dual-path: try DB-backed derivation first when hierarchy tables are populated
|
|
146
146
|
if (isDbAvailable()) {
|
|
147
|
-
|
|
147
|
+
let dbMilestones = getAllMilestones();
|
|
148
|
+
// Disk→DB reconciliation (#2631): when the milestones table is empty
|
|
149
|
+
// (e.g. failed initial migration per #2529), the reconciliation code
|
|
150
|
+
// inside deriveStateFromDb is unreachable. Populate from disk here so
|
|
151
|
+
// the DB path activates correctly.
|
|
152
|
+
if (dbMilestones.length === 0) {
|
|
153
|
+
const diskIds = findMilestoneIds(basePath);
|
|
154
|
+
let synced = false;
|
|
155
|
+
for (const diskId of diskIds) {
|
|
156
|
+
if (!isGhostMilestone(basePath, diskId)) {
|
|
157
|
+
insertMilestone({ id: diskId, status: 'active' });
|
|
158
|
+
synced = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (synced)
|
|
162
|
+
dbMilestones = getAllMilestones();
|
|
163
|
+
}
|
|
148
164
|
if (dbMilestones.length > 0) {
|
|
149
165
|
const stopDbTimer = debugTime("derive-state-db");
|
|
150
166
|
result = await deriveStateFromDb(basePath);
|
|
@@ -465,7 +481,10 @@ export async function deriveStateFromDb(basePath) {
|
|
|
465
481
|
};
|
|
466
482
|
}
|
|
467
483
|
// ── All slices done → validating/completing ─────────────────────────
|
|
468
|
-
|
|
484
|
+
// Guard: [].every() === true (vacuous truth). Without the length check,
|
|
485
|
+
// an empty slice array causes a premature phase transition to
|
|
486
|
+
// validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
|
|
487
|
+
const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isStatusDone(s.status));
|
|
469
488
|
if (allSlicesDone) {
|
|
470
489
|
const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
|
|
471
490
|
const validationContent = validationFile ? await loadFile(validationFile) : null;
|
|
@@ -7,7 +7,7 @@
|
|
|
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
12
|
import { saveFile, clearParseCache } from "../files.js";
|
|
13
13
|
import { invalidateStateCache } from "../state.js";
|
|
@@ -116,11 +116,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
// 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
|
-
});
|
|
119
|
+
updateMilestoneStatus(params.milestoneId, 'complete', completedAt);
|
|
124
120
|
});
|
|
125
121
|
if (guardError) {
|
|
126
122
|
return { error: guardError };
|
|
@@ -144,10 +140,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
144
140
|
catch (renderErr) {
|
|
145
141
|
// Disk render failed — roll back DB status so state stays consistent
|
|
146
142
|
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
|
-
}
|
|
143
|
+
updateMilestoneStatus(params.milestoneId, 'active', null);
|
|
151
144
|
invalidateStateCache();
|
|
152
145
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
153
146
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
|
-
import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus,
|
|
11
|
+
import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
|
|
12
12
|
import { resolveSlicePath, clearPathCache } from "../paths.js";
|
|
13
13
|
import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
|
|
14
14
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -240,26 +240,12 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
240
240
|
catch (renderErr) {
|
|
241
241
|
// Disk render failed — roll back DB status so state stays consistent
|
|
242
242
|
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
|
-
}
|
|
243
|
+
updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
|
|
250
244
|
invalidateStateCache();
|
|
251
245
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
252
246
|
}
|
|
253
247
|
// 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
|
-
}
|
|
248
|
+
setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
|
|
263
249
|
// Invalidate all caches
|
|
264
250
|
invalidateStateCache();
|
|
265
251
|
clearPathCache();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
|
-
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask,
|
|
11
|
+
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, } from "../gsd-db.js";
|
|
12
12
|
import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
|
|
13
13
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
14
14
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -202,27 +202,16 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
202
202
|
catch (renderErr) {
|
|
203
203
|
// Disk render failed — roll back DB status so state stays consistent
|
|
204
204
|
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
|
-
}
|
|
205
|
+
// Delete orphaned verification_evidence rows first (FK constraint
|
|
206
|
+
// references tasks, so evidence must go before status change).
|
|
207
|
+
// Without this, retries accumulate duplicate evidence rows (#2724).
|
|
208
|
+
deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
|
|
209
|
+
updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, 'pending');
|
|
213
210
|
invalidateStateCache();
|
|
214
211
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
215
212
|
}
|
|
216
213
|
// 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
|
-
}
|
|
214
|
+
setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
|
|
226
215
|
// Invalidate all caches
|
|
227
216
|
invalidateStateCache();
|
|
228
217
|
clearPathCache();
|
|
@@ -145,25 +145,31 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
145
145
|
catch (err) {
|
|
146
146
|
return { error: `validation failed: ${err.message}` };
|
|
147
147
|
}
|
|
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
|
-
}
|
|
148
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
149
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
150
|
+
// change between the read and the write (#2723).
|
|
151
|
+
let guardError = null;
|
|
165
152
|
try {
|
|
166
153
|
transaction(() => {
|
|
154
|
+
const existingMilestone = getMilestone(params.milestoneId);
|
|
155
|
+
if (existingMilestone && (existingMilestone.status === "complete" || existingMilestone.status === "done")) {
|
|
156
|
+
guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Validate depends_on: all dependencies must exist and be complete
|
|
160
|
+
if (params.dependsOn && params.dependsOn.length > 0) {
|
|
161
|
+
for (const depId of params.dependsOn) {
|
|
162
|
+
const dep = getMilestone(depId);
|
|
163
|
+
if (!dep) {
|
|
164
|
+
guardError = `depends_on references unknown milestone: ${depId}`;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (dep.status !== "complete" && dep.status !== "done") {
|
|
168
|
+
guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
167
173
|
insertMilestone({
|
|
168
174
|
id: params.milestoneId,
|
|
169
175
|
title: params.title,
|
|
@@ -206,6 +212,9 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
206
212
|
catch (err) {
|
|
207
213
|
return { error: `db write failed: ${err.message}` };
|
|
208
214
|
}
|
|
215
|
+
if (guardError) {
|
|
216
|
+
return { error: guardError };
|
|
217
|
+
}
|
|
209
218
|
let roadmapPath;
|
|
210
219
|
try {
|
|
211
220
|
const renderResult = await renderRoadmapFromDb(basePath, params.milestoneId);
|
|
@@ -102,22 +102,30 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
102
102
|
catch (err) {
|
|
103
103
|
return { error: `validation failed: ${err.message}` };
|
|
104
104
|
}
|
|
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
|
-
}
|
|
105
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
106
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
107
|
+
// change between the read and the write (#2723).
|
|
108
|
+
let guardError = null;
|
|
119
109
|
try {
|
|
120
110
|
transaction(() => {
|
|
111
|
+
const parentMilestone = getMilestone(params.milestoneId);
|
|
112
|
+
if (!parentMilestone) {
|
|
113
|
+
guardError = `milestone not found: ${params.milestoneId}`;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
|
|
117
|
+
guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
121
|
+
if (!parentSlice) {
|
|
122
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
126
|
+
guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
121
129
|
upsertSlicePlanning(params.milestoneId, params.sliceId, {
|
|
122
130
|
goal: params.goal,
|
|
123
131
|
successCriteria: params.successCriteria,
|
|
@@ -163,6 +171,9 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
163
171
|
catch (err) {
|
|
164
172
|
return { error: `db write failed: ${err.message}` };
|
|
165
173
|
}
|
|
174
|
+
if (guardError) {
|
|
175
|
+
return { error: guardError };
|
|
176
|
+
}
|
|
166
177
|
try {
|
|
167
178
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
168
179
|
invalidateStateCache();
|
|
@@ -50,19 +50,26 @@ export async function handlePlanTask(rawParams, basePath) {
|
|
|
50
50
|
catch (err) {
|
|
51
51
|
return { error: `validation failed: ${err.message}` };
|
|
52
52
|
}
|
|
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
|
-
}
|
|
53
|
+
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
54
|
+
// Guards must be inside the transaction so the state they check cannot
|
|
55
|
+
// change between the read and the write (#2723).
|
|
56
|
+
let guardError = null;
|
|
64
57
|
try {
|
|
65
58
|
transaction(() => {
|
|
59
|
+
const parentSlice = getSlice(params.milestoneId, params.sliceId);
|
|
60
|
+
if (!parentSlice) {
|
|
61
|
+
guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (parentSlice.status === "complete" || parentSlice.status === "done") {
|
|
65
|
+
guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
69
|
+
if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
|
|
70
|
+
guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
66
73
|
if (!existingTask) {
|
|
67
74
|
insertTask({
|
|
68
75
|
id: params.taskId,
|
|
@@ -88,6 +95,9 @@ export async function handlePlanTask(rawParams, basePath) {
|
|
|
88
95
|
catch (err) {
|
|
89
96
|
return { error: `db write failed: ${err.message}` };
|
|
90
97
|
}
|
|
98
|
+
if (guardError) {
|
|
99
|
+
return { error: guardError };
|
|
100
|
+
}
|
|
91
101
|
try {
|
|
92
102
|
const renderResult = await renderTaskPlanFromDb(basePath, params.milestoneId, params.sliceId, params.taskId);
|
|
93
103
|
invalidateStateCache();
|