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
|
@@ -2,14 +2,51 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
2
2
|
|
|
3
3
|
import { checkAutoStartAfterDiscuss } from "../guided-flow.js";
|
|
4
4
|
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto } from "../auto.js";
|
|
5
|
-
import { getNextFallbackModel,
|
|
6
|
-
import {
|
|
5
|
+
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
6
|
+
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
7
7
|
import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto-loop.js";
|
|
8
|
+
import { resolveModelId } from "../auto-model-selection.js";
|
|
8
9
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
10
|
+
import {
|
|
11
|
+
classifyError,
|
|
12
|
+
createRetryState,
|
|
13
|
+
resetRetryState,
|
|
14
|
+
isTransient,
|
|
15
|
+
type ErrorClass,
|
|
16
|
+
} from "../error-classifier.js";
|
|
9
17
|
|
|
10
|
-
const
|
|
18
|
+
const retryState = createRetryState();
|
|
19
|
+
const MAX_NETWORK_RETRIES = 2;
|
|
11
20
|
const MAX_TRANSIENT_AUTO_RESUMES = 3;
|
|
12
|
-
|
|
21
|
+
|
|
22
|
+
async function pauseTransientWithBackoff(
|
|
23
|
+
cls: ErrorClass,
|
|
24
|
+
pi: ExtensionAPI,
|
|
25
|
+
ctx: ExtensionContext,
|
|
26
|
+
errorDetail: string,
|
|
27
|
+
isRateLimit: boolean,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
retryState.consecutiveTransientCount += 1;
|
|
30
|
+
const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
|
|
31
|
+
const retryAfterMs = baseRetryAfterMs * 2 ** Math.max(0, retryState.consecutiveTransientCount - 1);
|
|
32
|
+
const allowAutoResume = retryState.consecutiveTransientCount <= MAX_TRANSIENT_AUTO_RESUMES;
|
|
33
|
+
if (!allowAutoResume) {
|
|
34
|
+
ctx.ui.notify(`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`, "warning");
|
|
35
|
+
}
|
|
36
|
+
await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
|
|
37
|
+
isRateLimit,
|
|
38
|
+
isTransient: allowAutoResume,
|
|
39
|
+
retryAfterMs,
|
|
40
|
+
resume: allowAutoResume
|
|
41
|
+
? () => {
|
|
42
|
+
pi.sendMessage(
|
|
43
|
+
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution — provider error recovery delay elapsed.", display: false },
|
|
44
|
+
{ triggerTurn: true },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
: undefined,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
13
50
|
|
|
14
51
|
export async function handleAgentEnd(
|
|
15
52
|
pi: ExtensionAPI,
|
|
@@ -31,17 +68,26 @@ export async function handleAgentEnd(
|
|
|
31
68
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
32
69
|
const errorDetail = "errorMessage" in lastMsg && lastMsg.errorMessage ? `: ${lastMsg.errorMessage}` : "";
|
|
33
70
|
const errorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
71
|
+
const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
|
|
34
72
|
|
|
35
|
-
|
|
73
|
+
// ── 1. Classify ──────────────────────────────────────────────────────
|
|
74
|
+
const cls = classifyError(errorMsg, explicitRetryAfterMs);
|
|
75
|
+
|
|
76
|
+
// ── 2. Decide & Act ──────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
// --- Network errors: same-model retry with backoff ---
|
|
79
|
+
if (cls.kind === "network") {
|
|
36
80
|
const currentModelId = ctx.model?.id ?? "unknown";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
if (retryState.currentRetryModelId !== currentModelId) {
|
|
82
|
+
retryState.networkRetryCount = 0;
|
|
83
|
+
retryState.currentRetryModelId = currentModelId;
|
|
84
|
+
}
|
|
85
|
+
if (retryState.networkRetryCount < MAX_NETWORK_RETRIES) {
|
|
86
|
+
retryState.networkRetryCount += 1;
|
|
87
|
+
retryState.consecutiveTransientCount += 1;
|
|
88
|
+
const attempt = retryState.networkRetryCount;
|
|
89
|
+
const delayMs = attempt * cls.retryAfterMs;
|
|
90
|
+
ctx.ui.notify(`Network error on ${currentModelId}${errorDetail}. Retry ${attempt}/${MAX_NETWORK_RETRIES} in ${delayMs / 1000}s...`, "warning");
|
|
45
91
|
setTimeout(() => {
|
|
46
92
|
pi.sendMessage(
|
|
47
93
|
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution — retrying after transient network error.", display: false },
|
|
@@ -50,84 +96,82 @@ export async function handleAgentEnd(
|
|
|
50
96
|
}, delayMs);
|
|
51
97
|
return;
|
|
52
98
|
}
|
|
53
|
-
|
|
99
|
+
// Network retries exhausted — fall through to model fallback
|
|
100
|
+
retryState.networkRetryCount = 0;
|
|
101
|
+
retryState.currentRetryModelId = undefined;
|
|
54
102
|
ctx.ui.notify(`Network retries exhausted for ${currentModelId}. Attempting model fallback.`, "warning");
|
|
55
103
|
}
|
|
56
104
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
// --- Rate limit: skip model fallback, go straight to pause ---
|
|
106
|
+
// Rate-limiting is a provider issue, not a model issue.
|
|
107
|
+
// Switching models won't help if the provider is throttling you.
|
|
108
|
+
if (cls.kind === "rate-limit") {
|
|
109
|
+
await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- Server/connection/stream errors: try model fallback first ---
|
|
114
|
+
if (cls.kind === "network" || cls.kind === "server" || cls.kind === "connection" || cls.kind === "stream") {
|
|
115
|
+
// Try model fallback
|
|
116
|
+
const dash = getAutoDashboardData();
|
|
117
|
+
if (dash.currentUnit) {
|
|
118
|
+
const modelConfig = resolveModelWithFallbacksForUnit(dash.currentUnit.type);
|
|
119
|
+
if (modelConfig && modelConfig.fallbacks.length > 0) {
|
|
120
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
121
|
+
const nextModelId = getNextFallbackModel(ctx.model?.id, modelConfig);
|
|
122
|
+
if (nextModelId) {
|
|
123
|
+
retryState.networkRetryCount = 0;
|
|
124
|
+
retryState.currentRetryModelId = undefined;
|
|
125
|
+
const modelToSet = resolveModelId(nextModelId, availableModels, ctx.model?.provider);
|
|
126
|
+
if (modelToSet) {
|
|
127
|
+
const ok = await pi.setModel(modelToSet, { persist: false });
|
|
128
|
+
if (ok) {
|
|
129
|
+
ctx.ui.notify(`Model error${errorDetail}. Switched to fallback: ${nextModelId} and resuming.`, "warning");
|
|
130
|
+
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
75
133
|
}
|
|
76
134
|
}
|
|
77
135
|
}
|
|
78
136
|
}
|
|
79
|
-
}
|
|
80
137
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
138
|
+
// Try restoring session model
|
|
139
|
+
const sessionModel = getAutoModeStartModel();
|
|
140
|
+
if (sessionModel) {
|
|
141
|
+
if (ctx.model?.id !== sessionModel.id || ctx.model?.provider !== sessionModel.provider) {
|
|
142
|
+
const startModel = ctx.modelRegistry.getAvailable().find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
|
|
143
|
+
if (startModel) {
|
|
144
|
+
const ok = await pi.setModel(startModel, { persist: false });
|
|
145
|
+
if (ok) {
|
|
146
|
+
retryState.networkRetryCount = 0;
|
|
147
|
+
retryState.currentRetryModelId = undefined;
|
|
148
|
+
ctx.ui.notify(`Model error${errorDetail}. Restored session model: ${sessionModel.provider}/${sessionModel.id} and resuming.`, "warning");
|
|
149
|
+
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
92
152
|
}
|
|
93
153
|
}
|
|
94
154
|
}
|
|
95
155
|
}
|
|
96
156
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} else {
|
|
102
|
-
consecutiveTransientErrors = 0;
|
|
103
|
-
}
|
|
104
|
-
const baseRetryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
|
|
105
|
-
const retryAfterMs = classification.isTransient
|
|
106
|
-
? baseRetryAfterMs * 2 ** Math.max(0, consecutiveTransientErrors - 1)
|
|
107
|
-
: baseRetryAfterMs;
|
|
108
|
-
const allowAutoResume = classification.isTransient && consecutiveTransientErrors <= MAX_TRANSIENT_AUTO_RESUMES;
|
|
109
|
-
if (classification.isTransient && !allowAutoResume) {
|
|
110
|
-
ctx.ui.notify(`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`, "warning");
|
|
157
|
+
// --- Transient fallback: pause with auto-resume ---
|
|
158
|
+
if (isTransient(cls)) {
|
|
159
|
+
await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, false);
|
|
160
|
+
return;
|
|
111
161
|
}
|
|
162
|
+
|
|
163
|
+
// --- Permanent / unknown: pause indefinitely ---
|
|
112
164
|
await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
|
|
113
|
-
isRateLimit:
|
|
114
|
-
isTransient:
|
|
115
|
-
retryAfterMs,
|
|
116
|
-
resume: allowAutoResume
|
|
117
|
-
? () => {
|
|
118
|
-
pi.sendMessage(
|
|
119
|
-
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution — provider error recovery delay elapsed.", display: false },
|
|
120
|
-
{ triggerTurn: true },
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
: undefined,
|
|
165
|
+
isRateLimit: false,
|
|
166
|
+
isTransient: false,
|
|
167
|
+
retryAfterMs: 0,
|
|
124
168
|
});
|
|
125
169
|
return;
|
|
126
170
|
}
|
|
127
171
|
|
|
172
|
+
// ── Success path ─────────────────────────────────────────────────────────
|
|
128
173
|
try {
|
|
129
|
-
|
|
130
|
-
networkRetryCounters.clear();
|
|
174
|
+
resetRetryState(retryState);
|
|
131
175
|
resolveAgentEnd(event);
|
|
132
176
|
} catch (err) {
|
|
133
177
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -139,4 +183,3 @@ export async function handleAgentEnd(
|
|
|
139
183
|
}
|
|
140
184
|
}
|
|
141
185
|
}
|
|
142
|
-
|
|
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
|
5
5
|
import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
|
|
6
6
|
|
|
7
7
|
import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
|
|
8
|
+
import { setLogBasePath } from "../workflow-logger.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Resolve the correct DB path for the current working directory.
|
|
@@ -43,9 +44,14 @@ export async function ensureDbOpen(): Promise<boolean> {
|
|
|
43
44
|
const dbPath = resolveProjectRootDbPath(basePath);
|
|
44
45
|
const gsdDir = join(basePath, ".gsd");
|
|
45
46
|
|
|
47
|
+
// Derive the project root from the DB path (strip .gsd/gsd.db)
|
|
48
|
+
const projectRoot = join(dbPath, "..", "..");
|
|
49
|
+
|
|
46
50
|
// Open existing DB file (may be at project root for worktrees)
|
|
47
51
|
if (existsSync(dbPath)) {
|
|
48
|
-
|
|
52
|
+
const opened = db.openDatabase(dbPath);
|
|
53
|
+
if (opened) setLogBasePath(projectRoot);
|
|
54
|
+
return opened;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
// No DB file — create + migrate from Markdown if .gsd/ has content
|
|
@@ -56,6 +62,7 @@ export async function ensureDbOpen(): Promise<boolean> {
|
|
|
56
62
|
if (hasDecisions || hasRequirements || hasMilestones) {
|
|
57
63
|
const opened = db.openDatabase(dbPath);
|
|
58
64
|
if (opened) {
|
|
65
|
+
setLogBasePath(projectRoot);
|
|
59
66
|
try {
|
|
60
67
|
const { migrateFromMarkdown } = await import("../md-importer.js");
|
|
61
68
|
migrateFromMarkdown(basePath);
|
|
@@ -69,7 +76,9 @@ export async function ensureDbOpen(): Promise<boolean> {
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
// .gsd/ exists but has no Markdown content (fresh project) — create empty DB
|
|
72
|
-
|
|
79
|
+
const opened = db.openDatabase(dbPath);
|
|
80
|
+
if (opened) setLogBasePath(projectRoot);
|
|
81
|
+
return opened;
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
return false;
|
|
@@ -245,7 +245,7 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
245
245
|
|
|
246
246
|
pi.on("tool_execution_start", async (event) => {
|
|
247
247
|
if (!isAutoActive()) return;
|
|
248
|
-
markToolStart(event.toolCallId);
|
|
248
|
+
markToolStart(event.toolCallId, event.toolName);
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
pi.on("tool_execution_end", async (event) => {
|
|
@@ -103,16 +103,47 @@ function isMarketplacePath(pluginPath: string): boolean {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Detect which plugin roots are marketplaces and which are legacy flat paths.
|
|
106
|
+
*
|
|
107
|
+
* Claude Code stores marketplace sources under ~/.claude/plugins/marketplaces/.
|
|
108
|
+
* Each subdirectory (e.g. marketplaces/confluent/) is a marketplace repo that
|
|
109
|
+
* contains .claude-plugin/marketplace.json. The parent directory itself does not
|
|
110
|
+
* have a marketplace.json, so we scan one level deeper when the root isn't
|
|
111
|
+
* directly a marketplace.
|
|
106
112
|
*/
|
|
107
|
-
function categorizePluginRoots(pluginRoots: string[]): { marketplaces: string[]; flat: string[] } {
|
|
113
|
+
export function categorizePluginRoots(pluginRoots: string[]): { marketplaces: string[]; flat: string[] } {
|
|
108
114
|
const marketplaces: string[] = [];
|
|
109
115
|
const flat: string[] = [];
|
|
116
|
+
const seen = new Set<string>();
|
|
110
117
|
|
|
111
118
|
for (const root of pluginRoots) {
|
|
112
119
|
if (isMarketplacePath(root)) {
|
|
113
|
-
|
|
120
|
+
if (!seen.has(root)) {
|
|
121
|
+
marketplaces.push(root);
|
|
122
|
+
seen.add(root);
|
|
123
|
+
}
|
|
114
124
|
} else {
|
|
115
|
-
|
|
125
|
+
// The root itself isn't a marketplace — check if it's a container of
|
|
126
|
+
// marketplaces (e.g. ~/.claude/plugins/marketplaces/ contains subdirs
|
|
127
|
+
// like confluent/, claude-hud/, each with their own marketplace.json).
|
|
128
|
+
let foundChild = false;
|
|
129
|
+
try {
|
|
130
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (!entry.isDirectory()) continue;
|
|
133
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
134
|
+
const childPath = join(root, entry.name);
|
|
135
|
+
if (isMarketplacePath(childPath) && !seen.has(childPath)) {
|
|
136
|
+
marketplaces.push(childPath);
|
|
137
|
+
seen.add(childPath);
|
|
138
|
+
foundChild = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Can't read directory — fall through to flat
|
|
143
|
+
}
|
|
144
|
+
if (!foundChild) {
|
|
145
|
+
flat.push(root);
|
|
146
|
+
}
|
|
116
147
|
}
|
|
117
148
|
}
|
|
118
149
|
|
|
@@ -170,18 +201,36 @@ export function discoverClaudePlugins(cwd: string): ClaudePluginCandidate[] {
|
|
|
170
201
|
|
|
171
202
|
for (const root of pluginRoots) {
|
|
172
203
|
walkDirs(root, (dir) => {
|
|
204
|
+
// Recognize both npm-style plugins (package.json) and Claude Code plugins
|
|
205
|
+
// (.claude-plugin/plugin.json). Claude marketplace-installed plugins use
|
|
206
|
+
// the latter format exclusively.
|
|
173
207
|
const pkgPath = join(dir, "package.json");
|
|
174
|
-
|
|
208
|
+
const claudePluginPath = join(dir, ".claude-plugin", "plugin.json");
|
|
209
|
+
const hasPkg = existsSync(pkgPath);
|
|
210
|
+
const hasClaudePlugin = existsSync(claudePluginPath);
|
|
211
|
+
if (!hasPkg && !hasClaudePlugin) return;
|
|
212
|
+
|
|
175
213
|
const resolvedDir = resolve(dir);
|
|
176
214
|
if (seen.has(resolvedDir)) return;
|
|
177
215
|
seen.add(resolvedDir);
|
|
216
|
+
|
|
178
217
|
let packageName: string | undefined;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
218
|
+
if (hasPkg) {
|
|
219
|
+
try {
|
|
220
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: string };
|
|
221
|
+
packageName = pkg.name;
|
|
222
|
+
} catch {
|
|
223
|
+
packageName = undefined;
|
|
224
|
+
}
|
|
225
|
+
} else if (hasClaudePlugin) {
|
|
226
|
+
try {
|
|
227
|
+
const manifest = JSON.parse(readFileSync(claudePluginPath, "utf8")) as { name?: string };
|
|
228
|
+
packageName = manifest.name;
|
|
229
|
+
} catch {
|
|
230
|
+
packageName = undefined;
|
|
231
|
+
}
|
|
184
232
|
}
|
|
233
|
+
|
|
185
234
|
results.push({
|
|
186
235
|
type: "plugin",
|
|
187
236
|
name: packageName || basename(dir),
|
|
@@ -7,6 +7,7 @@ import { enableDebug } from "../../debug-logger.js";
|
|
|
7
7
|
import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
|
|
8
8
|
import { handleRate } from "../../commands-rate.js";
|
|
9
9
|
import { guardRemoteSession, projectRoot } from "../context.js";
|
|
10
|
+
import { findMilestoneIds } from "../../milestone-id-utils.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Parse --yolo flag and optional file path from the auto command string.
|
|
@@ -28,6 +29,39 @@ function parseYoloFlag(trimmed: string): { yoloSeedFile: string | null; rest: st
|
|
|
28
29
|
return { yoloSeedFile: filePath, rest };
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Extract a milestone ID (e.g. M016 or M001-a3b4c5) from the command string.
|
|
34
|
+
* Returns the matched ID and the remaining string with the ID removed.
|
|
35
|
+
* The milestone ID pattern matches the format used by findMilestoneIds: M\d+ with
|
|
36
|
+
* an optional -[a-z0-9]{6} suffix for unique milestone IDs.
|
|
37
|
+
*/
|
|
38
|
+
export function parseMilestoneTarget(input: string): { milestoneId: string | null; rest: string } {
|
|
39
|
+
const match = input.match(/\b(M\d+(?:-[a-z0-9]{6})?)\b/);
|
|
40
|
+
if (!match) return { milestoneId: null, rest: input };
|
|
41
|
+
const rest = input.replace(match[0], "").replace(/\s+/g, " ").trim();
|
|
42
|
+
return { milestoneId: match[1], rest };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set GSD_MILESTONE_LOCK to target a specific milestone, then run `fn`.
|
|
47
|
+
* Clears the env var when `fn` resolves or rejects, so the lock does not
|
|
48
|
+
* leak into subsequent commands in the same process.
|
|
49
|
+
*/
|
|
50
|
+
async function withMilestoneLock(milestoneId: string, fn: () => Promise<void>): Promise<void> {
|
|
51
|
+
const previous = process.env.GSD_MILESTONE_LOCK;
|
|
52
|
+
process.env.GSD_MILESTONE_LOCK = milestoneId;
|
|
53
|
+
try {
|
|
54
|
+
await fn();
|
|
55
|
+
} finally {
|
|
56
|
+
// Restore previous value (undefined → delete, else restore).
|
|
57
|
+
if (previous === undefined) {
|
|
58
|
+
delete process.env.GSD_MILESTONE_LOCK;
|
|
59
|
+
} else {
|
|
60
|
+
process.env.GSD_MILESTONE_LOCK = previous;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<boolean> {
|
|
32
66
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
33
67
|
if (trimmed.includes("--dry-run")) {
|
|
@@ -35,21 +69,48 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
35
69
|
await handleDryRun(ctx, projectRoot());
|
|
36
70
|
return true;
|
|
37
71
|
}
|
|
38
|
-
const
|
|
39
|
-
const
|
|
72
|
+
const { milestoneId, rest: afterMilestone } = parseMilestoneTarget(trimmed);
|
|
73
|
+
const verboseMode = afterMilestone.includes("--verbose");
|
|
74
|
+
const debugMode = afterMilestone.includes("--debug");
|
|
40
75
|
if (debugMode) enableDebug(projectRoot());
|
|
41
76
|
if (!(await guardRemoteSession(ctx, pi))) return true;
|
|
42
|
-
|
|
77
|
+
|
|
78
|
+
// Validate the milestone target exists and is not already complete.
|
|
79
|
+
if (milestoneId) {
|
|
80
|
+
const allIds = findMilestoneIds(projectRoot());
|
|
81
|
+
if (!allIds.includes(milestoneId)) {
|
|
82
|
+
ctx.ui.notify(`Milestone ${milestoneId} does not exist. Available: ${allIds.join(", ") || "(none)"}`, "error");
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (milestoneId) {
|
|
88
|
+
await withMilestoneLock(milestoneId, () =>
|
|
89
|
+
startAuto(ctx, pi, projectRoot(), verboseMode, { step: true }),
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
93
|
+
}
|
|
43
94
|
return true;
|
|
44
95
|
}
|
|
45
96
|
|
|
46
97
|
if (trimmed === "auto" || trimmed.startsWith("auto ")) {
|
|
47
|
-
const { yoloSeedFile, rest } = parseYoloFlag(trimmed);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
98
|
+
const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(trimmed);
|
|
99
|
+
const { milestoneId, rest: afterMilestone } = parseMilestoneTarget(afterYolo);
|
|
100
|
+
const verboseMode = afterMilestone.includes("--verbose");
|
|
101
|
+
const debugMode = afterMilestone.includes("--debug");
|
|
50
102
|
if (debugMode) enableDebug(projectRoot());
|
|
51
103
|
if (!(await guardRemoteSession(ctx, pi))) return true;
|
|
52
104
|
|
|
105
|
+
// Validate the milestone target exists and is not already complete.
|
|
106
|
+
if (milestoneId) {
|
|
107
|
+
const allIds = findMilestoneIds(projectRoot());
|
|
108
|
+
if (!allIds.includes(milestoneId)) {
|
|
109
|
+
ctx.ui.notify(`Milestone ${milestoneId} does not exist. Available: ${allIds.join(", ") || "(none)"}`, "error");
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
53
114
|
if (yoloSeedFile) {
|
|
54
115
|
const resolved = resolve(projectRoot(), yoloSeedFile);
|
|
55
116
|
if (!existsSync(resolved)) {
|
|
@@ -66,6 +127,12 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
66
127
|
// when the LLM says "Milestone X ready."
|
|
67
128
|
const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
|
|
68
129
|
await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
|
|
130
|
+
} else if (milestoneId) {
|
|
131
|
+
// Target a specific milestone — use GSD_MILESTONE_LOCK so state
|
|
132
|
+
// derivation only sees this milestone (#2521).
|
|
133
|
+
await withMilestoneLock(milestoneId, () =>
|
|
134
|
+
startAuto(ctx, pi, projectRoot(), verboseMode),
|
|
135
|
+
);
|
|
69
136
|
} else {
|
|
70
137
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
71
138
|
}
|
|
@@ -22,6 +22,12 @@ export const TOOL_KEYS = [
|
|
|
22
22
|
{ id: "groq", env: "GROQ_API_KEY", label: "Groq Voice", hint: "console.groq.com" },
|
|
23
23
|
] as const;
|
|
24
24
|
|
|
25
|
+
function getStoredToolKey(auth: AuthStorage, providerId: string): string | undefined {
|
|
26
|
+
const creds = auth.getCredentialsForProvider(providerId);
|
|
27
|
+
const cred = creds.find((c) => c.type === "api_key" && c.key);
|
|
28
|
+
return cred?.type === "api_key" ? cred.key : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
/**
|
|
26
32
|
* Load tool API keys from auth.json into environment variables.
|
|
27
33
|
* Called at session startup to ensure tools have access to their credentials.
|
|
@@ -33,9 +39,9 @@ export function loadToolApiKeys(): void {
|
|
|
33
39
|
|
|
34
40
|
const auth = AuthStorage.create(authPath);
|
|
35
41
|
for (const tool of TOOL_KEYS) {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
process.env[tool.env] =
|
|
42
|
+
const key = getStoredToolKey(auth, tool.id);
|
|
43
|
+
if (key && !process.env[tool.env]) {
|
|
44
|
+
process.env[tool.env] = key;
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
} catch {
|
|
@@ -55,14 +61,14 @@ export async function handleConfig(ctx: ExtensionCommandContext): Promise<void>
|
|
|
55
61
|
// Show current status
|
|
56
62
|
const statusLines = ["GSD Tool Configuration\n"];
|
|
57
63
|
for (const tool of TOOL_KEYS) {
|
|
58
|
-
const hasKey = !!process.env[tool.env] || !!(auth
|
|
64
|
+
const hasKey = !!process.env[tool.env] || !!getStoredToolKey(auth, tool.id);
|
|
59
65
|
statusLines.push(` ${hasKey ? "\u2713" : "\u2717"} ${tool.label}${hasKey ? "" : ` \u2014 get key at ${tool.hint}`}`);
|
|
60
66
|
}
|
|
61
67
|
ctx.ui.notify(statusLines.join("\n"), "info");
|
|
62
68
|
|
|
63
69
|
// Ask which tools to configure
|
|
64
70
|
const options = TOOL_KEYS.map(t => {
|
|
65
|
-
const hasKey = !!process.env[t.env] || !!(auth
|
|
71
|
+
const hasKey = !!process.env[t.env] || !!getStoredToolKey(auth, t.id);
|
|
66
72
|
return `${t.label} ${hasKey ? "(configured \u2713)" : "(not set)"}`;
|
|
67
73
|
});
|
|
68
74
|
options.push("(done)");
|
|
@@ -771,7 +771,7 @@ export async function ensurePreferencesFile(
|
|
|
771
771
|
scope: "global" | "project",
|
|
772
772
|
): Promise<void> {
|
|
773
773
|
if (!existsSync(path)) {
|
|
774
|
-
const template = await loadFile(join(dirname(fileURLToPath(import.meta.url)), "templates", "
|
|
774
|
+
const template = await loadFile(join(dirname(fileURLToPath(import.meta.url)), "templates", "PREFERENCES.md"));
|
|
775
775
|
if (!template) {
|
|
776
776
|
ctx.ui.notify("Could not load GSD preferences template.", "error");
|
|
777
777
|
return;
|
|
@@ -359,8 +359,8 @@ function detectV2Gsd(basePath: string): V2Detection | null {
|
|
|
359
359
|
if (!existsSync(gsdPath)) return null;
|
|
360
360
|
|
|
361
361
|
const hasPreferences =
|
|
362
|
-
existsSync(join(gsdPath, "
|
|
363
|
-
existsSync(join(gsdPath, "
|
|
362
|
+
existsSync(join(gsdPath, "PREFERENCES.md")) ||
|
|
363
|
+
existsSync(join(gsdPath, "preferences.md"));
|
|
364
364
|
|
|
365
365
|
const hasContext = existsSync(join(gsdPath, "CONTEXT.md"));
|
|
366
366
|
|
|
@@ -714,8 +714,8 @@ function detectVerificationCommands(
|
|
|
714
714
|
*/
|
|
715
715
|
export function hasGlobalSetup(): boolean {
|
|
716
716
|
return (
|
|
717
|
-
existsSync(join(gsdHome, "
|
|
718
|
-
existsSync(join(gsdHome, "
|
|
717
|
+
existsSync(join(gsdHome, "PREFERENCES.md")) ||
|
|
718
|
+
existsSync(join(gsdHome, "preferences.md"))
|
|
719
719
|
);
|
|
720
720
|
}
|
|
721
721
|
|
|
@@ -728,8 +728,8 @@ export function isFirstEverLaunch(): boolean {
|
|
|
728
728
|
|
|
729
729
|
// If we have preferences, not first launch
|
|
730
730
|
if (
|
|
731
|
-
existsSync(join(gsdHome, "
|
|
732
|
-
existsSync(join(gsdHome, "
|
|
731
|
+
existsSync(join(gsdHome, "PREFERENCES.md")) ||
|
|
732
|
+
existsSync(join(gsdHome, "preferences.md"))
|
|
733
733
|
) {
|
|
734
734
|
return false;
|
|
735
735
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD Preferences Reference
|
|
2
2
|
|
|
3
|
-
Full documentation for `~/.gsd/
|
|
3
|
+
Full documentation for `~/.gsd/PREFERENCES.md` (global) and `.gsd/PREFERENCES.md` (project).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -51,8 +51,8 @@ skill_rules: []
|
|
|
51
51
|
|
|
52
52
|
Preferences are loaded from two locations and merged:
|
|
53
53
|
|
|
54
|
-
1. **Global:** `~/.gsd/
|
|
55
|
-
2. **Project:** `.gsd/
|
|
54
|
+
1. **Global:** `~/.gsd/PREFERENCES.md` — applies to all projects
|
|
55
|
+
2. **Project:** `.gsd/PREFERENCES.md` — applies to the current project only
|
|
56
56
|
|
|
57
57
|
**Merge behavior** (see `mergePreferences()` in `preferences.ts`):
|
|
58
58
|
|