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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// parseRoadmap(), parsePlan(), parseSummary() in files.ts.
|
|
10
10
|
|
|
11
11
|
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
12
13
|
import { join, relative } from "node:path";
|
|
13
14
|
import { createRequire } from "node:module";
|
|
14
15
|
import {
|
|
@@ -337,7 +338,7 @@ function renderSlicePlanMarkdown(slice: SliceRow, tasks: TaskRow[], gates: GateR
|
|
|
337
338
|
lines.push("## Tasks");
|
|
338
339
|
lines.push("");
|
|
339
340
|
for (const task of tasks) {
|
|
340
|
-
const done = task.status
|
|
341
|
+
const done = isClosedStatus(task.status) ? "x" : " ";
|
|
341
342
|
const estimate = task.estimate.trim() ? ` \`est:${task.estimate.trim()}\`` : "";
|
|
342
343
|
lines.push(`- [${done}] **${task.id}: ${task.title || task.id}**${estimate}`);
|
|
343
344
|
if (task.description.trim()) {
|
|
@@ -573,7 +574,7 @@ export async function renderPlanCheckboxes(
|
|
|
573
574
|
// Apply checkbox patches for each task
|
|
574
575
|
let updated = content;
|
|
575
576
|
for (const task of tasks) {
|
|
576
|
-
const isDone = task.status
|
|
577
|
+
const isDone = isClosedStatus(task.status);
|
|
577
578
|
const tid = task.id;
|
|
578
579
|
|
|
579
580
|
if (isDone) {
|
|
@@ -857,7 +858,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
857
858
|
const parsed = parsePlan(content);
|
|
858
859
|
|
|
859
860
|
for (const task of tasks) {
|
|
860
|
-
const isDoneInDb = task.status
|
|
861
|
+
const isDoneInDb = isClosedStatus(task.status);
|
|
861
862
|
const planTask = parsed.tasks.find((t: { id: string }) => t.id === task.id);
|
|
862
863
|
if (!planTask) continue;
|
|
863
864
|
|
|
@@ -880,7 +881,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
880
881
|
|
|
881
882
|
// Check missing task summary files
|
|
882
883
|
for (const task of tasks) {
|
|
883
|
-
if ((task.status
|
|
884
|
+
if (isClosedStatus(task.status) && task.full_summary_md) {
|
|
884
885
|
const slicePath = resolveSlicePath(basePath, milestone.id, slice.id);
|
|
885
886
|
if (slicePath) {
|
|
886
887
|
const tasksDir = join(slicePath, "tasks");
|
|
@@ -125,6 +125,9 @@ export function getActiveMemoriesRanked(limit = 30): Memory[] {
|
|
|
125
125
|
/**
|
|
126
126
|
* Generate the next memory ID: MEM + zero-padded 3-digit from MAX(seq).
|
|
127
127
|
* Returns MEM001 if no memories exist.
|
|
128
|
+
*
|
|
129
|
+
* NOTE: For race-safe creation, prefer createMemory() which inserts with a
|
|
130
|
+
* placeholder ID then updates to the seq-derived ID atomically.
|
|
128
131
|
*/
|
|
129
132
|
export function nextMemoryId(): string {
|
|
130
133
|
if (!isDbAvailable()) return 'MEM001';
|
|
@@ -147,7 +150,9 @@ export function nextMemoryId(): string {
|
|
|
147
150
|
// ─── Mutation Functions ─────────────────────────────────────────────────────
|
|
148
151
|
|
|
149
152
|
/**
|
|
150
|
-
* Insert a new memory with auto-assigned ID.
|
|
153
|
+
* Insert a new memory with a race-safe auto-assigned ID.
|
|
154
|
+
* Uses AUTOINCREMENT seq to derive the ID after insert, avoiding
|
|
155
|
+
* the read-then-write race in concurrent scenarios (e.g. worktrees).
|
|
151
156
|
* Returns the assigned ID, or null on failure.
|
|
152
157
|
*/
|
|
153
158
|
export function createMemory(fields: {
|
|
@@ -162,13 +167,14 @@ export function createMemory(fields: {
|
|
|
162
167
|
if (!adapter) return null;
|
|
163
168
|
|
|
164
169
|
try {
|
|
165
|
-
const id = nextMemoryId();
|
|
166
170
|
const now = new Date().toISOString();
|
|
171
|
+
// Insert with a temporary placeholder ID — seq is auto-assigned
|
|
172
|
+
const placeholder = `_TMP_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
167
173
|
adapter.prepare(
|
|
168
174
|
`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
|
|
169
175
|
VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`,
|
|
170
176
|
).run({
|
|
171
|
-
':id':
|
|
177
|
+
':id': placeholder,
|
|
172
178
|
':category': fields.category,
|
|
173
179
|
':content': fields.content,
|
|
174
180
|
':confidence': fields.confidence ?? 0.8,
|
|
@@ -177,7 +183,16 @@ export function createMemory(fields: {
|
|
|
177
183
|
':created_at': now,
|
|
178
184
|
':updated_at': now,
|
|
179
185
|
});
|
|
180
|
-
|
|
186
|
+
// Derive the real ID from the assigned seq
|
|
187
|
+
const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
|
|
188
|
+
if (!row) return placeholder; // fallback — should not happen
|
|
189
|
+
const seq = row['seq'] as number;
|
|
190
|
+
const realId = `MEM${String(seq).padStart(3, '0')}`;
|
|
191
|
+
adapter.prepare('UPDATE memories SET id = :real_id WHERE id = :placeholder').run({
|
|
192
|
+
':real_id': realId,
|
|
193
|
+
':placeholder': placeholder,
|
|
194
|
+
});
|
|
195
|
+
return realId;
|
|
181
196
|
} catch {
|
|
182
197
|
return null;
|
|
183
198
|
}
|
|
@@ -331,20 +346,16 @@ export function enforceMemoryCap(max = 50): void {
|
|
|
331
346
|
if (count <= max) return;
|
|
332
347
|
|
|
333
348
|
const excess = count - max;
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
`
|
|
337
|
-
WHERE
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
adapter.prepare(
|
|
345
|
-
'UPDATE memories SET superseded_by = :reason, updated_at = :now WHERE id = :id',
|
|
346
|
-
).run({ ':reason': 'CAP_EXCEEDED', ':now': now, ':id': row['id'] as string });
|
|
347
|
-
}
|
|
349
|
+
// Batch update: supersede lowest-ranked active memories in a single statement
|
|
350
|
+
adapter.prepare(
|
|
351
|
+
`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
|
|
352
|
+
WHERE id IN (
|
|
353
|
+
SELECT id FROM memories
|
|
354
|
+
WHERE superseded_by IS NULL
|
|
355
|
+
ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
|
|
356
|
+
LIMIT :limit
|
|
357
|
+
)`,
|
|
358
|
+
).run({ ':now': new Date().toISOString(), ':limit': excess });
|
|
348
359
|
} catch {
|
|
349
360
|
// non-fatal
|
|
350
361
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "./paths.js";
|
|
21
21
|
import { invalidateAllCaches } from "./cache.js";
|
|
22
22
|
import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
23
|
+
import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
23
24
|
|
|
24
25
|
// ─── Park ──────────────────────────────────────────────────────────────────
|
|
25
26
|
|
|
@@ -52,6 +53,14 @@ export function parkMilestone(basePath: string, milestoneId: string, reason: str
|
|
|
52
53
|
].join("\n");
|
|
53
54
|
|
|
54
55
|
writeFileSync(parkedPath, content, "utf-8");
|
|
56
|
+
// Sync DB status so deriveStateFromDb also skips this milestone (#2694)
|
|
57
|
+
if (isDbAvailable()) {
|
|
58
|
+
try {
|
|
59
|
+
updateMilestoneStatus(milestoneId, "parked");
|
|
60
|
+
} catch (err) {
|
|
61
|
+
process.stderr.write(`gsd: parkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
55
64
|
invalidateAllCaches();
|
|
56
65
|
return true;
|
|
57
66
|
}
|
|
@@ -70,6 +79,14 @@ export function unparkMilestone(basePath: string, milestoneId: string): boolean
|
|
|
70
79
|
if (!existsSync(parkedPath)) return false; // not parked
|
|
71
80
|
|
|
72
81
|
unlinkSync(parkedPath);
|
|
82
|
+
// Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
|
|
83
|
+
if (isDbAvailable()) {
|
|
84
|
+
try {
|
|
85
|
+
updateMilestoneStatus(milestoneId, "active");
|
|
86
|
+
} catch (err) {
|
|
87
|
+
process.stderr.write(`gsd: unparkMilestone DB sync failed for ${milestoneId}: ${(err as Error).message}\n`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
73
90
|
invalidateAllCaches();
|
|
74
91
|
return true;
|
|
75
92
|
}
|
|
@@ -125,18 +125,6 @@ export function getNextFallbackModel(
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/**
|
|
129
|
-
* Detect whether an error message indicates a transient network error
|
|
130
|
-
* (worth retrying the same model) vs a permanent provider error
|
|
131
|
-
* (auth failure, quota exceeded, etc. -- should fall back immediately).
|
|
132
|
-
*/
|
|
133
|
-
export function isTransientNetworkError(errorMsg: string): boolean {
|
|
134
|
-
if (!errorMsg) return false;
|
|
135
|
-
const hasNetworkSignal = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i.test(errorMsg);
|
|
136
|
-
const hasPermanentSignal = /auth|unauthorized|forbidden|invalid.*key|quota|billing/i.test(errorMsg);
|
|
137
|
-
return hasNetworkSignal && !hasPermanentSignal;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
128
|
/**
|
|
141
129
|
* Validate a model ID string.
|
|
142
130
|
* Returns true if the ID looks like a valid model identifier.
|
|
@@ -308,7 +296,7 @@ export function resolveContextSelection(): import("./types.js").ContextSelection
|
|
|
308
296
|
}
|
|
309
297
|
|
|
310
298
|
/**
|
|
311
|
-
* Resolve the search provider preference from
|
|
299
|
+
* Resolve the search provider preference from PREFERENCES.md.
|
|
312
300
|
* Returns undefined if not configured (caller falls back to existing behavior).
|
|
313
301
|
*/
|
|
314
302
|
export function resolveSearchProviderFromPreferences(): GSDPreferences["search_provider"] | undefined {
|
|
@@ -68,7 +68,6 @@ export {
|
|
|
68
68
|
resolveModelForUnit,
|
|
69
69
|
resolveModelWithFallbacksForUnit,
|
|
70
70
|
getNextFallbackModel,
|
|
71
|
-
isTransientNetworkError,
|
|
72
71
|
validateModelId,
|
|
73
72
|
updatePreferencesModels,
|
|
74
73
|
resolveDynamicRoutingConfig,
|
|
@@ -87,7 +86,7 @@ function gsdHome(): string {
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
function globalPreferencesPath(): string {
|
|
90
|
-
return join(gsdHome(), "
|
|
89
|
+
return join(gsdHome(), "PREFERENCES.md");
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
function legacyGlobalPreferencesPath(): string {
|
|
@@ -95,15 +94,15 @@ function legacyGlobalPreferencesPath(): string {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
function projectPreferencesPath(): string {
|
|
98
|
-
return join(gsdRoot(process.cwd()), "
|
|
97
|
+
return join(gsdRoot(process.cwd()), "PREFERENCES.md");
|
|
99
98
|
}
|
|
100
|
-
//
|
|
101
|
-
// Check
|
|
102
|
-
function
|
|
103
|
-
return join(gsdHome(), "
|
|
99
|
+
// Legacy: older versions used lowercase preferences.md.
|
|
100
|
+
// Check lowercase as a fallback so those files aren't silently ignored.
|
|
101
|
+
function globalPreferencesPathLegacy(): string {
|
|
102
|
+
return join(gsdHome(), "preferences.md");
|
|
104
103
|
}
|
|
105
|
-
function
|
|
106
|
-
return join(gsdRoot(process.cwd()), "
|
|
104
|
+
function projectPreferencesPathLegacy(): string {
|
|
105
|
+
return join(gsdRoot(process.cwd()), "preferences.md");
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
export function getGlobalGSDPreferencesPath(): string {
|
|
@@ -122,13 +121,13 @@ export function getProjectGSDPreferencesPath(): string {
|
|
|
122
121
|
|
|
123
122
|
export function loadGlobalGSDPreferences(): LoadedGSDPreferences | null {
|
|
124
123
|
return loadPreferencesFile(globalPreferencesPath(), "global")
|
|
125
|
-
?? loadPreferencesFile(
|
|
124
|
+
?? loadPreferencesFile(globalPreferencesPathLegacy(), "global")
|
|
126
125
|
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
|
|
130
129
|
return loadPreferencesFile(projectPreferencesPath(), "project")
|
|
131
|
-
?? loadPreferencesFile(
|
|
130
|
+
?? loadPreferencesFile(projectPreferencesPathLegacy(), "project");
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
|
@@ -223,7 +222,7 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
|
|
|
223
222
|
|
|
224
223
|
if (!_warnedUnrecognizedFormat) {
|
|
225
224
|
_warnedUnrecognizedFormat = true;
|
|
226
|
-
console.warn("[parsePreferencesMarkdown]
|
|
225
|
+
console.warn("[parsePreferencesMarkdown] PREFERENCES.md exists but uses an unrecognized format — skipping.");
|
|
227
226
|
}
|
|
228
227
|
return null;
|
|
229
228
|
}
|
|
@@ -502,7 +501,7 @@ export function resolvePreDispatchHooks(): PreDispatchHookConfig[] {
|
|
|
502
501
|
* Resolve the effective git isolation mode from preferences.
|
|
503
502
|
* Returns "none" (default), "worktree", or "branch".
|
|
504
503
|
*
|
|
505
|
-
* Default is "none" so GSD works out of the box without
|
|
504
|
+
* Default is "none" so GSD works out of the box without PREFERENCES.md.
|
|
506
505
|
* Worktree isolation requires explicit opt-in because it depends on git
|
|
507
506
|
* branch infrastructure that must be set up before use.
|
|
508
507
|
*/
|
|
@@ -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.
|
|
@@ -2,63 +2,6 @@ export type ProviderErrorPauseUI = {
|
|
|
2
2
|
notify(message: string, level?: "info" | "warning" | "error" | "success"): void;
|
|
3
3
|
};
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Classify a provider error as transient (auto-resume) or permanent (manual resume).
|
|
7
|
-
*
|
|
8
|
-
* Transient: rate limits, server errors (500/502/503), overloaded, internal errors.
|
|
9
|
-
* These are expected to self-resolve and should auto-resume after a delay.
|
|
10
|
-
*
|
|
11
|
-
* Permanent: auth errors, invalid API key, billing issues.
|
|
12
|
-
* These require user intervention and should pause indefinitely.
|
|
13
|
-
*/
|
|
14
|
-
export function classifyProviderError(errorMsg: string): {
|
|
15
|
-
isTransient: boolean;
|
|
16
|
-
isRateLimit: boolean;
|
|
17
|
-
suggestedDelayMs: number;
|
|
18
|
-
} {
|
|
19
|
-
const isRateLimit = /rate.?limit|too many requests|429/i.test(errorMsg);
|
|
20
|
-
const isServerError = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i.test(errorMsg);
|
|
21
|
-
|
|
22
|
-
// Connection/process errors — transient, auto-resume after brief backoff (#2309).
|
|
23
|
-
// These indicate the process was killed, the connection was reset, or a network
|
|
24
|
-
// blip occurred. They are NOT permanent failures.
|
|
25
|
-
const isConnectionError = /terminated|connection.?reset|connection.?refused|other side closed|fetch failed|network.?(?:is\s+)?unavailable|ECONNREFUSED|ECONNRESET|EPIPE/i.test(errorMsg);
|
|
26
|
-
|
|
27
|
-
// Permanent errors — never auto-resume
|
|
28
|
-
const isPermanent = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i.test(errorMsg);
|
|
29
|
-
|
|
30
|
-
if (isPermanent && !isRateLimit) {
|
|
31
|
-
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (isRateLimit) {
|
|
35
|
-
// Try to extract retry-after from the message
|
|
36
|
-
const resetMatch = errorMsg.match(/reset in (\d+)s/i);
|
|
37
|
-
const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000; // default 60s for rate limits
|
|
38
|
-
return { isTransient: true, isRateLimit: true, suggestedDelayMs: delayMs };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (isServerError) {
|
|
42
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 30_000 }; // 30s for server errors
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (isConnectionError) {
|
|
46
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Stream-truncation JSON parse errors — transient (#2572).
|
|
50
|
-
// When the API stream is cut mid-chunk, pi tries to reassemble the partial
|
|
51
|
-
// tool-call JSON and gets a SyntaxError. This is the downstream symptom of
|
|
52
|
-
// a connection drop — same root cause as ECONNRESET, one layer up.
|
|
53
|
-
const isMalformedStream = /Unexpected end of JSON|Unexpected token.*JSON|Expected double-quoted property name|SyntaxError.*JSON/i.test(errorMsg);
|
|
54
|
-
if (isMalformedStream) {
|
|
55
|
-
return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s, same as connection errors
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Unknown error — treat as permanent (user reviews)
|
|
59
|
-
return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
5
|
/**
|
|
63
6
|
* Pause auto-mode due to a provider error.
|
|
64
7
|
*
|
|
@@ -524,7 +524,7 @@ export class RuleRegistry {
|
|
|
524
524
|
formatHookStatus(): string {
|
|
525
525
|
const entries = this.getHookStatus();
|
|
526
526
|
if (entries.length === 0) {
|
|
527
|
-
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/
|
|
527
|
+
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/PREFERENCES.md";
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
const lines: string[] = ["Configured Hooks:", ""];
|
|
@@ -27,15 +27,27 @@ const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of prov
|
|
|
27
27
|
|
|
28
28
|
// ─── Gating ──────────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Model ID prefixes (bare, without provider) that support OpenAI service tiers.
|
|
32
|
+
*
|
|
33
|
+
* This list is the fallback for callers that only have a model ID string.
|
|
34
|
+
* The authoritative source of truth is `model.capabilities.supportsServiceTier`
|
|
35
|
+
* (set via CAPABILITY_PATCHES in packages/pi-ai/src/models.ts). When callers
|
|
36
|
+
* have access to the full Model object, prefer reading capabilities directly.
|
|
37
|
+
*
|
|
38
|
+
* See: https://github.com/gsd-build/gsd-2/issues/2546
|
|
39
|
+
*/
|
|
40
|
+
const SERVICE_TIER_MODEL_PREFIXES = ["gpt-5.4"] as const;
|
|
41
|
+
|
|
30
42
|
/**
|
|
31
43
|
* Returns true when the given model ID supports OpenAI service tiers.
|
|
32
|
-
*
|
|
44
|
+
* Reads from SERVICE_TIER_MODEL_PREFIXES — update that list, not this function.
|
|
33
45
|
*/
|
|
34
46
|
export function supportsServiceTier(modelId: string): boolean {
|
|
35
47
|
if (!modelId) return false;
|
|
36
48
|
// Strip provider prefix if present (e.g. "openai/gpt-5.4" → "gpt-5.4")
|
|
37
49
|
const bare = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
|
|
38
|
-
return bare.startsWith(
|
|
50
|
+
return SERVICE_TIER_MODEL_PREFIXES.some((prefix) => bare.startsWith(prefix));
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
// ─── Status Formatting ───────────────────────────────────────────────────────
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
|
|
37
37
|
import { findMilestoneIds } from './milestone-ids.js';
|
|
38
38
|
import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
|
|
39
|
+
import { isClosedStatus } from './status-guards.js';
|
|
39
40
|
import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
|
|
40
41
|
|
|
41
42
|
import { join, resolve } from 'path';
|
|
@@ -211,7 +212,24 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|
|
211
212
|
|
|
212
213
|
// Dual-path: try DB-backed derivation first when hierarchy tables are populated
|
|
213
214
|
if (isDbAvailable()) {
|
|
214
|
-
|
|
215
|
+
let dbMilestones = getAllMilestones();
|
|
216
|
+
|
|
217
|
+
// Disk→DB reconciliation (#2631): when the milestones table is empty
|
|
218
|
+
// (e.g. failed initial migration per #2529), the reconciliation code
|
|
219
|
+
// inside deriveStateFromDb is unreachable. Populate from disk here so
|
|
220
|
+
// the DB path activates correctly.
|
|
221
|
+
if (dbMilestones.length === 0) {
|
|
222
|
+
const diskIds = findMilestoneIds(basePath);
|
|
223
|
+
let synced = false;
|
|
224
|
+
for (const diskId of diskIds) {
|
|
225
|
+
if (!isGhostMilestone(basePath, diskId)) {
|
|
226
|
+
insertMilestone({ id: diskId, status: 'active' });
|
|
227
|
+
synced = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (synced) dbMilestones = getAllMilestones();
|
|
231
|
+
}
|
|
232
|
+
|
|
215
233
|
if (dbMilestones.length > 0) {
|
|
216
234
|
const stopDbTimer = debugTime("derive-state-db");
|
|
217
235
|
result = await deriveStateFromDb(basePath);
|
|
@@ -255,13 +273,6 @@ function extractContextTitle(content: string | null, fallback: string): string {
|
|
|
255
273
|
|
|
256
274
|
// ─── DB-backed State Derivation ────────────────────────────────────────────
|
|
257
275
|
|
|
258
|
-
/**
|
|
259
|
-
* Helper: check if a DB status counts as "done" (handles K002 ambiguity).
|
|
260
|
-
*/
|
|
261
|
-
function isStatusDone(status: string): boolean {
|
|
262
|
-
return status === 'complete' || status === 'done';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
276
|
/**
|
|
266
277
|
* Derive GSD state from the milestones/slices/tasks DB tables.
|
|
267
278
|
* Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
|
|
@@ -351,7 +362,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
351
362
|
continue;
|
|
352
363
|
}
|
|
353
364
|
|
|
354
|
-
if (
|
|
365
|
+
if (isClosedStatus(m.status)) {
|
|
355
366
|
completeMilestoneIds.add(m.id);
|
|
356
367
|
continue;
|
|
357
368
|
}
|
|
@@ -365,7 +376,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
365
376
|
|
|
366
377
|
// Check roadmap: all slices done means milestone is complete
|
|
367
378
|
const slices = getMilestoneSlices(m.id);
|
|
368
|
-
if (slices.length > 0 && slices.every(s =>
|
|
379
|
+
if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
|
|
369
380
|
// All slices done but no summary — still counts as complete for dep resolution
|
|
370
381
|
// if a summary file exists
|
|
371
382
|
// Note: without summary file, the milestone is in validating/completing state, not complete
|
|
@@ -387,7 +398,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
387
398
|
|
|
388
399
|
// Ghost milestone check: no slices in DB AND no substantive files on disk
|
|
389
400
|
const slices = getMilestoneSlices(m.id);
|
|
390
|
-
if (slices.length === 0 && !
|
|
401
|
+
if (slices.length === 0 && !isClosedStatus(m.status)) {
|
|
391
402
|
// Check disk for ghost detection
|
|
392
403
|
if (isGhostMilestone(basePath, m.id)) continue;
|
|
393
404
|
}
|
|
@@ -410,7 +421,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
410
421
|
}
|
|
411
422
|
|
|
412
423
|
// Not complete — determine if it should be active
|
|
413
|
-
const allSlicesDone = slices.length > 0 && slices.every(s =>
|
|
424
|
+
const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
|
|
414
425
|
|
|
415
426
|
// Get title — prefer DB, fall back to context file extraction
|
|
416
427
|
let title = stripMilestonePrefix(m.title) || m.id;
|
|
@@ -562,7 +573,10 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
562
573
|
}
|
|
563
574
|
|
|
564
575
|
// ── All slices done → validating/completing ─────────────────────────
|
|
565
|
-
|
|
576
|
+
// Guard: [].every() === true (vacuous truth). Without the length check,
|
|
577
|
+
// an empty slice array causes a premature phase transition to
|
|
578
|
+
// validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
|
|
579
|
+
const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
|
|
566
580
|
if (allSlicesDone) {
|
|
567
581
|
const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
|
|
568
582
|
const validationContent = validationFile ? await loadFile(validationFile) : null;
|
|
@@ -595,19 +609,19 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
595
609
|
|
|
596
610
|
// ── Find active slice (first incomplete with deps satisfied) ─────────
|
|
597
611
|
const sliceProgress = {
|
|
598
|
-
done: activeMilestoneSlices.filter(s =>
|
|
612
|
+
done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
|
|
599
613
|
total: activeMilestoneSlices.length,
|
|
600
614
|
};
|
|
601
615
|
|
|
602
616
|
const doneSliceIds = new Set(
|
|
603
|
-
activeMilestoneSlices.filter(s =>
|
|
617
|
+
activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id)
|
|
604
618
|
);
|
|
605
619
|
|
|
606
620
|
let activeSlice: ActiveRef | null = null;
|
|
607
621
|
let activeSliceRow: SliceRow | null = null;
|
|
608
622
|
|
|
609
623
|
for (const s of activeMilestoneSlices) {
|
|
610
|
-
if (
|
|
624
|
+
if (isClosedStatus(s.status)) continue;
|
|
611
625
|
if (s.depends.every(dep => doneSliceIds.has(dep))) {
|
|
612
626
|
activeSlice = { id: s.id, title: s.title };
|
|
613
627
|
activeSliceRow = s;
|
|
@@ -650,7 +664,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
650
664
|
// causing the dispatcher to re-dispatch the same completed task forever.
|
|
651
665
|
let reconciled = false;
|
|
652
666
|
for (const t of tasks) {
|
|
653
|
-
if (
|
|
667
|
+
if (isClosedStatus(t.status)) continue;
|
|
654
668
|
const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
|
|
655
669
|
if (summaryPath && existsSync(summaryPath)) {
|
|
656
670
|
try {
|
|
@@ -673,11 +687,11 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
673
687
|
}
|
|
674
688
|
|
|
675
689
|
const taskProgress = {
|
|
676
|
-
done: tasks.filter(t =>
|
|
690
|
+
done: tasks.filter(t => isClosedStatus(t.status)).length,
|
|
677
691
|
total: tasks.length,
|
|
678
692
|
};
|
|
679
693
|
|
|
680
|
-
const activeTaskRow = tasks.find(t => !
|
|
694
|
+
const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
|
|
681
695
|
|
|
682
696
|
if (!activeTaskRow && tasks.length > 0) {
|
|
683
697
|
// All tasks done but slice not marked complete → summarizing
|
|
@@ -738,7 +752,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
738
752
|
}
|
|
739
753
|
|
|
740
754
|
// ── Blocker detection: check completed tasks for blocker_discovered ──
|
|
741
|
-
const completedTasks = tasks.filter(t =>
|
|
755
|
+
const completedTasks = tasks.filter(t => isClosedStatus(t.status));
|
|
742
756
|
let blockerTaskId: string | null = null;
|
|
743
757
|
for (const ct of completedTasks) {
|
|
744
758
|
if (ct.blocker_discovered) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status predicates for GSD state-machine guards.
|
|
3
|
+
*
|
|
4
|
+
* The DB stores status as free-form strings. Two values indicate
|
|
5
|
+
* "closed": "complete" (canonical) and "done" (legacy / alias).
|
|
6
|
+
* Every inline `status === "complete" || status === "done"` should
|
|
7
|
+
* use isClosedStatus() instead.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Returns true when a milestone, slice, or task status indicates closure. */
|
|
11
|
+
export function isClosedStatus(status: string): boolean {
|
|
12
|
+
return status === "complete" || status === "done";
|
|
13
|
+
}
|
|
@@ -102,7 +102,7 @@ test("pauseAuto calls resolveAgentEndCancelled to unblock the loop", () => {
|
|
|
102
102
|
const fnBlock = source.slice(fnIdx, source.indexOf("\n/**\n * Build", fnIdx + 100));
|
|
103
103
|
|
|
104
104
|
assert.ok(
|
|
105
|
-
fnBlock.includes("resolveAgentEndCancelled(
|
|
105
|
+
fnBlock.includes("resolveAgentEndCancelled("),
|
|
106
106
|
"pauseAuto must call resolveAgentEndCancelled to unblock the auto-loop promise",
|
|
107
107
|
);
|
|
108
108
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { parseMilestoneTarget } from "../commands/handlers/auto.js";
|
|
5
|
+
|
|
6
|
+
describe("parseMilestoneTarget", () => {
|
|
7
|
+
it("extracts a simple milestone ID", () => {
|
|
8
|
+
const result = parseMilestoneTarget("auto M016");
|
|
9
|
+
assert.equal(result.milestoneId, "M016");
|
|
10
|
+
assert.equal(result.rest, "auto");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("extracts a milestone ID with unique suffix", () => {
|
|
14
|
+
const result = parseMilestoneTarget("auto M001-a3b4c5 --verbose");
|
|
15
|
+
assert.equal(result.milestoneId, "M001-a3b4c5");
|
|
16
|
+
assert.equal(result.rest, "auto --verbose");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns null when no milestone ID is present", () => {
|
|
20
|
+
const result = parseMilestoneTarget("auto --verbose");
|
|
21
|
+
assert.equal(result.milestoneId, null);
|
|
22
|
+
assert.equal(result.rest, "auto --verbose");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("extracts milestone ID with flags in any order", () => {
|
|
26
|
+
const result = parseMilestoneTarget("auto --verbose M003 --debug");
|
|
27
|
+
assert.equal(result.milestoneId, "M003");
|
|
28
|
+
assert.equal(result.rest, "auto --verbose --debug");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns null for plain 'auto'", () => {
|
|
32
|
+
const result = parseMilestoneTarget("auto");
|
|
33
|
+
assert.equal(result.milestoneId, null);
|
|
34
|
+
assert.equal(result.rest, "auto");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("extracts from 'next' command", () => {
|
|
38
|
+
const result = parseMilestoneTarget("next M012");
|
|
39
|
+
assert.equal(result.milestoneId, "M012");
|
|
40
|
+
assert.equal(result.rest, "next");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("handles milestone ID at the start of input", () => {
|
|
44
|
+
const result = parseMilestoneTarget("M007");
|
|
45
|
+
assert.equal(result.milestoneId, "M007");
|
|
46
|
+
assert.equal(result.rest, "");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("picks the first milestone ID when multiple appear", () => {
|
|
50
|
+
// Edge case: user accidentally types two. First one wins.
|
|
51
|
+
const result = parseMilestoneTarget("auto M001 M002");
|
|
52
|
+
assert.equal(result.milestoneId, "M001");
|
|
53
|
+
// M002 remains in rest since only the first match is removed
|
|
54
|
+
assert.ok(result.rest.includes("M002"));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("does not match bare numbers without M prefix", () => {
|
|
58
|
+
const result = parseMilestoneTarget("auto 016");
|
|
59
|
+
assert.equal(result.milestoneId, null);
|
|
60
|
+
});
|
|
61
|
+
});
|