gsd-pi 2.62.1 → 2.63.0-dev.351157b
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 +46 -134
- package/dist/cli.js +44 -6
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +12 -1
- package/dist/resources/extensions/gsd/auto/phases.js +133 -25
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +47 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-verification.js +14 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +75 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +43 -29
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-logger.js +13 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +6 -12
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- 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 +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +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.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.js.nft.json +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.js.nft.json +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 +4 -4
- 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 +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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/6897.js +12 -0
- 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.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/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-0c485498795110d6.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/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/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -1
- 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/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +13 -1
- package/src/resources/extensions/gsd/auto/phases.ts +164 -40
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +56 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-verification.ts +14 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +68 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +17 -41
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +44 -31
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-logger.ts +13 -8
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +6 -14
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.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/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
|
@@ -13,13 +13,17 @@ import { runUnit } from "./run-unit.js";
|
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { PROJECT_FILES } from "../detection.js";
|
|
15
15
|
import { MergeConflictError } from "../git-service.js";
|
|
16
|
-
import { join, basename } from "node:path";
|
|
17
|
-
import { existsSync, cpSync } from "node:fs";
|
|
16
|
+
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
17
|
+
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
18
18
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
19
19
|
import { gsdRoot } from "../paths.js";
|
|
20
20
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
21
|
-
import { verifyExpectedArtifact } from "../auto-recovery.js";
|
|
21
|
+
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
22
22
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
23
|
+
import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
24
|
+
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
25
|
+
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
26
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
23
27
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
24
28
|
/**
|
|
25
29
|
* Resolve the base path for milestone reports.
|
|
@@ -116,7 +120,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
116
120
|
ctx.ui.notify(`Pre-dispatch: ${healthGate.fixesApplied.join(", ")}`, "info");
|
|
117
121
|
}
|
|
118
122
|
if (!healthGate.proceed) {
|
|
119
|
-
ctx.ui.notify(healthGate.reason
|
|
123
|
+
ctx.ui.notify(healthGate.reason || "Pre-dispatch health check failed — run /gsd doctor for details.", "error");
|
|
120
124
|
await deps.pauseAuto(ctx, pi);
|
|
121
125
|
debugLog("autoLoop", { phase: "exit", reason: "health-gate-failed" });
|
|
122
126
|
return { action: "break", reason: "health-gate-failed" };
|
|
@@ -142,6 +146,50 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
142
146
|
mid,
|
|
143
147
|
statePhase: state.phase,
|
|
144
148
|
});
|
|
149
|
+
// ── Slice-level parallelism gate (#2340) ─────────────────────────────
|
|
150
|
+
// When slice_parallel is enabled, check if multiple slices are eligible
|
|
151
|
+
// for parallel execution. If so, dispatch them in parallel and stop the
|
|
152
|
+
// sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
|
|
153
|
+
if (prefs?.slice_parallel?.enabled &&
|
|
154
|
+
mid &&
|
|
155
|
+
!process.env.GSD_PARALLEL_WORKER &&
|
|
156
|
+
isDbAvailable()) {
|
|
157
|
+
try {
|
|
158
|
+
const dbSlices = getMilestoneSlices(mid);
|
|
159
|
+
if (dbSlices.length > 0) {
|
|
160
|
+
const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
|
|
161
|
+
const sliceInputs = dbSlices.map(sl => ({
|
|
162
|
+
id: sl.id,
|
|
163
|
+
done: doneIds.has(sl.id),
|
|
164
|
+
depends: sl.depends ?? [],
|
|
165
|
+
}));
|
|
166
|
+
const eligible = getEligibleSlices(sliceInputs, doneIds);
|
|
167
|
+
if (eligible.length > 1) {
|
|
168
|
+
debugLog("autoLoop", {
|
|
169
|
+
phase: "slice-parallel-dispatch",
|
|
170
|
+
iteration: ic.iteration,
|
|
171
|
+
mid,
|
|
172
|
+
eligibleSlices: eligible.map(e => e.id),
|
|
173
|
+
});
|
|
174
|
+
ctx.ui.notify(`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`, "info");
|
|
175
|
+
const result = await startSliceParallel(s.basePath, mid, eligible, { maxWorkers: prefs.slice_parallel.max_workers ?? 2 });
|
|
176
|
+
if (result.started.length > 0) {
|
|
177
|
+
ctx.ui.notify(`Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`, "info");
|
|
178
|
+
await deps.stopAuto(ctx, pi, `Slice-parallel dispatched for ${mid}`);
|
|
179
|
+
return { action: "break", reason: "slice-parallel-dispatched" };
|
|
180
|
+
}
|
|
181
|
+
// Fall through to sequential if no workers started
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
debugLog("autoLoop", {
|
|
187
|
+
phase: "slice-parallel-check-error",
|
|
188
|
+
error: err instanceof Error ? err.message : String(err),
|
|
189
|
+
});
|
|
190
|
+
// Non-fatal — fall through to sequential dispatch
|
|
191
|
+
}
|
|
192
|
+
}
|
|
145
193
|
// ── Milestone transition ────────────────────────────────────────────
|
|
146
194
|
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
147
195
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
|
|
@@ -431,8 +479,15 @@ export async function runDispatch(ic, preData, loopState) {
|
|
|
431
479
|
unitId,
|
|
432
480
|
reason: stuckSignal.reason,
|
|
433
481
|
});
|
|
482
|
+
const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
483
|
+
const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
484
|
+
const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
|
|
485
|
+
if (stuckDiag)
|
|
486
|
+
stuckParts.push(`Expected: ${stuckDiag}`);
|
|
487
|
+
if (stuckRemediation)
|
|
488
|
+
stuckParts.push(`To recover:\n${stuckRemediation}`);
|
|
489
|
+
ctx.ui.notify(stuckParts.join(" "), "error");
|
|
434
490
|
await deps.stopAuto(ctx, pi, `Stuck: ${stuckSignal.reason}`);
|
|
435
|
-
ctx.ui.notify(`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}. The expected artifact was not written.`, "error");
|
|
436
491
|
return { action: "break", reason: "stuck-detected" };
|
|
437
492
|
}
|
|
438
493
|
}
|
|
@@ -662,11 +717,40 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
662
717
|
}
|
|
663
718
|
const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
|
|
664
719
|
const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
|
|
665
|
-
|
|
720
|
+
// Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
|
|
721
|
+
// that cannot be matched by exact filename — scan the directory by suffix.
|
|
722
|
+
let hasXcodeBundle = false;
|
|
723
|
+
try {
|
|
724
|
+
const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
|
|
725
|
+
hasXcodeBundle = entries.some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
|
|
729
|
+
}
|
|
730
|
+
// Monorepo support (#2347): if no project files in the worktree directory,
|
|
731
|
+
// walk parent directories up to the filesystem root. In monorepos,
|
|
732
|
+
// package.json / Cargo.toml etc. live in a parent directory.
|
|
733
|
+
let hasProjectFileInParent = false;
|
|
734
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle) {
|
|
735
|
+
let checkDir = dirname(s.basePath);
|
|
736
|
+
const { root } = parsePath(checkDir);
|
|
737
|
+
while (checkDir !== root) {
|
|
738
|
+
// Stop at git repository boundary — ancestors above the repo root
|
|
739
|
+
// (e.g. ~ or /usr/local) may contain unrelated project files.
|
|
740
|
+
if (deps.existsSync(join(checkDir, ".git")))
|
|
741
|
+
break;
|
|
742
|
+
if (PROJECT_FILES.some((f) => deps.existsSync(join(checkDir, f)))) {
|
|
743
|
+
hasProjectFileInParent = true;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
checkDir = dirname(checkDir);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
|
|
666
750
|
// Greenfield projects won't have project files yet — the first task creates them.
|
|
667
751
|
// Log a warning but allow execution to proceed. The .git check above is sufficient
|
|
668
752
|
// to ensure we're in a valid working directory.
|
|
669
|
-
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir });
|
|
753
|
+
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
|
|
670
754
|
ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
|
|
671
755
|
}
|
|
672
756
|
}
|
|
@@ -676,6 +760,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
676
760
|
s.currentUnit.id === unitId);
|
|
677
761
|
const previousTier = s.currentUnitRouting?.tier;
|
|
678
762
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
763
|
+
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
679
764
|
const unitStartSeq = ic.nextSeq();
|
|
680
765
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
681
766
|
deps.captureAvailableSkills();
|
|
@@ -688,18 +773,10 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
688
773
|
lastProgressKind: "dispatch",
|
|
689
774
|
recoveryAttempts: 0, // Reset so re-dispatched units get full recovery budget (#2322)
|
|
690
775
|
});
|
|
691
|
-
//
|
|
692
|
-
const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
|
|
693
|
-
s.currentUnitRouting =
|
|
694
|
-
modelResult.routing;
|
|
695
|
-
s.currentUnitModel =
|
|
696
|
-
modelResult.appliedModel;
|
|
697
|
-
// Status bar + progress widget
|
|
776
|
+
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
698
777
|
ctx.ui.setStatus("gsd-auto", "auto");
|
|
699
778
|
if (mid)
|
|
700
779
|
deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
701
|
-
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
702
|
-
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
703
780
|
// Prompt injection
|
|
704
781
|
let finalPrompt = prompt;
|
|
705
782
|
if (s.pendingVerificationRetry) {
|
|
@@ -757,6 +834,12 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
757
834
|
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
|
|
758
835
|
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
759
836
|
}
|
|
837
|
+
// Select and apply model (with tier escalation on retry — normal units only)
|
|
838
|
+
const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
|
|
839
|
+
s.currentUnitRouting =
|
|
840
|
+
modelResult.routing;
|
|
841
|
+
s.currentUnitModel =
|
|
842
|
+
modelResult.appliedModel;
|
|
760
843
|
// Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
|
|
761
844
|
const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
|
|
762
845
|
if (hookModelOverride) {
|
|
@@ -777,6 +860,15 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
777
860
|
`Ensure the model is defined in models.json and has auth configured.`, "warning");
|
|
778
861
|
}
|
|
779
862
|
}
|
|
863
|
+
// Store the final dispatched model ID so the dashboard can read it (#2899).
|
|
864
|
+
// This accounts for hook model overrides applied after selectAndApplyModel.
|
|
865
|
+
s.currentDispatchedModelId = s.currentUnitModel
|
|
866
|
+
? `${s.currentUnitModel.provider ?? ""}/${s.currentUnitModel.id ?? ""}`
|
|
867
|
+
: null;
|
|
868
|
+
// Progress widget + preconditions — deferred to after model selection so the
|
|
869
|
+
// widget's first render tick shows the correct model (#2899).
|
|
870
|
+
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
871
|
+
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
780
872
|
// Start unit supervision
|
|
781
873
|
deps.clearUnitTimeout();
|
|
782
874
|
deps.startUnitSupervision({
|
|
@@ -853,11 +945,13 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
853
945
|
if (s.currentUnit) {
|
|
854
946
|
await deps.closeoutUnit(ctx, s.basePath, unitType, unitId, s.currentUnit.startedAt, deps.buildSnapshotOpts(unitType, unitId));
|
|
855
947
|
}
|
|
856
|
-
// ── Zero tool-call guard (#1833)
|
|
857
|
-
//
|
|
858
|
-
//
|
|
859
|
-
// the
|
|
860
|
-
|
|
948
|
+
// ── Zero tool-call guard (#1833, #2653) ──────────────────────────
|
|
949
|
+
// Any unit that completes with 0 tool calls made no real progress —
|
|
950
|
+
// likely context exhaustion where all tool calls errored out. Treat
|
|
951
|
+
// as failed so the unit is retried in a fresh context instead of
|
|
952
|
+
// silently passing through to artifact verification (which loops
|
|
953
|
+
// forever when the unit never produced its artifact).
|
|
954
|
+
{
|
|
861
955
|
const currentLedger = deps.getLedger();
|
|
862
956
|
if (currentLedger?.units) {
|
|
863
957
|
const lastUnit = [...currentLedger.units].reverse().find((u) => u.type === unitType && u.id === unitId && u.startedAt === s.currentUnit?.startedAt);
|
|
@@ -866,11 +960,11 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
866
960
|
phase: "zero-tool-calls",
|
|
867
961
|
unitType,
|
|
868
962
|
unitId,
|
|
869
|
-
warning: "
|
|
963
|
+
warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
|
|
870
964
|
});
|
|
871
|
-
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls —
|
|
965
|
+
ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`, "warning");
|
|
872
966
|
// Fall through to next iteration where dispatch will re-derive
|
|
873
|
-
// and re-dispatch this
|
|
967
|
+
// and re-dispatch this unit.
|
|
874
968
|
return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
|
|
875
969
|
}
|
|
876
970
|
}
|
|
@@ -986,7 +1080,21 @@ export async function runFinalize(ic, iterData, sidecarItem) {
|
|
|
986
1080
|
}
|
|
987
1081
|
}
|
|
988
1082
|
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
|
989
|
-
|
|
1083
|
+
// Timeout guard: if postUnitPostVerification hangs (e.g., module import
|
|
1084
|
+
// deadlock, SQLite transaction hang), force-continue after timeout so the
|
|
1085
|
+
// auto-loop is not permanently frozen (#2344).
|
|
1086
|
+
const postResultGuard = await withTimeout(deps.postUnitPostVerification(postUnitCtx), FINALIZE_POST_TIMEOUT_MS, "postUnitPostVerification");
|
|
1087
|
+
if (postResultGuard.timedOut) {
|
|
1088
|
+
debugLog("autoLoop", {
|
|
1089
|
+
phase: "post-verification-timeout",
|
|
1090
|
+
iteration: ic.iteration,
|
|
1091
|
+
unitType: iterData.unitType,
|
|
1092
|
+
unitId: iterData.unitId,
|
|
1093
|
+
});
|
|
1094
|
+
ctx.ui.notify(`postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`, "warning");
|
|
1095
|
+
return { action: "next", data: undefined };
|
|
1096
|
+
}
|
|
1097
|
+
const postResult = postResultGuard.value;
|
|
990
1098
|
if (postResult === "stopped") {
|
|
991
1099
|
debugLog("autoLoop", {
|
|
992
1100
|
phase: "exit",
|
|
@@ -50,6 +50,8 @@ export class AutoSession {
|
|
|
50
50
|
// ── Model state ──────────────────────────────────────────────────────────
|
|
51
51
|
autoModeStartModel = null;
|
|
52
52
|
currentUnitModel = null;
|
|
53
|
+
/** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
|
|
54
|
+
currentDispatchedModelId = null;
|
|
53
55
|
originalModelId = null;
|
|
54
56
|
originalModelProvider = null;
|
|
55
57
|
lastBudgetAlertLevel = 0;
|
|
@@ -62,6 +64,10 @@ export class AutoSession {
|
|
|
62
64
|
lastStateRebuildAt = 0;
|
|
63
65
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
64
66
|
sidecarQueue = [];
|
|
67
|
+
// ── Tool invocation errors (#2883) ──────────────────────────────────
|
|
68
|
+
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
69
|
+
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
70
|
+
lastToolInvocationError = null;
|
|
65
71
|
// ── Isolation degradation ────────────────────────────────────────────
|
|
66
72
|
/** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
|
|
67
73
|
isolationDegraded = false;
|
|
@@ -132,6 +138,7 @@ export class AutoSession {
|
|
|
132
138
|
// Model
|
|
133
139
|
this.autoModeStartModel = null;
|
|
134
140
|
this.currentUnitModel = null;
|
|
141
|
+
this.currentDispatchedModelId = null;
|
|
135
142
|
this.originalModelId = null;
|
|
136
143
|
this.originalModelProvider = null;
|
|
137
144
|
this.lastBudgetAlertLevel = 0;
|
|
@@ -149,6 +156,7 @@ export class AutoSession {
|
|
|
149
156
|
this.pendingQuickTasks = [];
|
|
150
157
|
this.sidecarQueue = [];
|
|
151
158
|
this.rewriteAttemptCount = 0;
|
|
159
|
+
this.lastToolInvocationError = null;
|
|
152
160
|
this.isolationDegraded = false;
|
|
153
161
|
this.milestoneMergedInPhases = false;
|
|
154
162
|
// Signal handler
|
|
@@ -516,9 +516,15 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
516
516
|
const cxWindow = cxUsage?.contextWindow ?? cmdCtx?.model?.contextWindow ?? 0;
|
|
517
517
|
const cxPctVal = cxUsage?.percent ?? 0;
|
|
518
518
|
const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
|
|
519
|
-
// Model display —
|
|
520
|
-
|
|
521
|
-
const
|
|
519
|
+
// Model display — prefer dispatched model ID (set after selectAndApplyModel
|
|
520
|
+
// + hook overrides) over cmdCtx?.model which can be stale (#2899).
|
|
521
|
+
const dispatchedModelId = accessors.getCurrentDispatchedModelId();
|
|
522
|
+
const modelId = dispatchedModelId
|
|
523
|
+
? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
|
|
524
|
+
: (cmdCtx?.model?.id ?? "");
|
|
525
|
+
const modelProvider = dispatchedModelId
|
|
526
|
+
? dispatchedModelId.split("/")[0] || ""
|
|
527
|
+
: (cmdCtx?.model?.provider ?? "");
|
|
522
528
|
const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
|
|
523
529
|
const modelDisplay = (modelProvider && modelId
|
|
524
530
|
? `${modelProvider}/${modelId}`
|
|
@@ -19,7 +19,7 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
19
19
|
import { parseUnitId } from "./unit-id.js";
|
|
20
20
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
21
21
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
22
|
-
import { verifyExpectedArtifact, resolveExpectedArtifactPath, } from "./auto-recovery.js";
|
|
22
|
+
import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceholder, diagnoseExpectedArtifact, } from "./auto-recovery.js";
|
|
23
23
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
24
24
|
import { syncStateToProjectRoot } from "./auto-worktree.js";
|
|
25
25
|
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
@@ -29,6 +29,8 @@ import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookSta
|
|
|
29
29
|
import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
|
|
30
30
|
import { debugLog } from "./debug-logger.js";
|
|
31
31
|
import { runSafely } from "./auto-utils.js";
|
|
32
|
+
/** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
|
|
33
|
+
const MAX_VERIFICATION_RETRIES = 3;
|
|
32
34
|
/** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
|
|
33
35
|
* drain via runUnit. Logs the enqueue event and notifies the UI. */
|
|
34
36
|
function enqueueSidecar(s, ctx, entry, debugExtra, notification) {
|
|
@@ -374,6 +376,8 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
374
376
|
// When artifact verification fails for a unit type that has a known expected
|
|
375
377
|
// artifact, return "retry" so the caller re-dispatches with failure context
|
|
376
378
|
// instead of blindly re-dispatching the same unit (#1571).
|
|
379
|
+
// After MAX_VERIFICATION_RETRIES, escalate to writeBlockerPlaceholder so the
|
|
380
|
+
// pipeline can advance instead of looping forever (#2653).
|
|
377
381
|
//
|
|
378
382
|
// HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
|
|
379
383
|
// written because the completion tool failed at the infra level. Retrying
|
|
@@ -383,22 +387,55 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
383
387
|
// db_unavailable so the artifact was never written. Retrying would
|
|
384
388
|
// produce an infinite re-dispatch loop (#2517).
|
|
385
389
|
debugLog("postUnit", { phase: "artifact-verify-skip-db-unavailable", unitType: s.currentUnit.type, unitId: s.currentUnit.id });
|
|
386
|
-
|
|
390
|
+
const dbSkipDiag = diagnoseExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
391
|
+
ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — DB unavailable, skipping retry.${dbSkipDiag ? ` Expected: ${dbSkipDiag}` : ""}`, "error");
|
|
387
392
|
}
|
|
388
393
|
else if (!triggerArtifactVerified) {
|
|
394
|
+
// #2883: If the artifact is missing because the tool invocation itself
|
|
395
|
+
// failed (malformed/truncated JSON arguments), retrying will produce the
|
|
396
|
+
// same failure. Pause auto-mode instead of entering a stuck retry loop.
|
|
397
|
+
if (s.lastToolInvocationError) {
|
|
398
|
+
const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
|
|
399
|
+
debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
|
|
400
|
+
ctx.ui.notify(errMsg, "error");
|
|
401
|
+
s.lastToolInvocationError = null;
|
|
402
|
+
await pauseAuto(ctx, pi);
|
|
403
|
+
return "dispatched";
|
|
404
|
+
}
|
|
389
405
|
const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
|
|
390
406
|
if (hasExpectedArtifact) {
|
|
391
407
|
const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
|
|
392
408
|
const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
|
|
393
409
|
s.verificationRetryCount.set(retryKey, attempt);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
410
|
+
if (attempt > MAX_VERIFICATION_RETRIES) {
|
|
411
|
+
// Retries exhausted — write a blocker placeholder so the pipeline
|
|
412
|
+
// can advance past this stuck unit (#2653).
|
|
413
|
+
debugLog("postUnit", {
|
|
414
|
+
phase: "artifact-verify-escalate",
|
|
415
|
+
unitType: s.currentUnit.type,
|
|
416
|
+
unitId: s.currentUnit.id,
|
|
417
|
+
attempt,
|
|
418
|
+
maxRetries: MAX_VERIFICATION_RETRIES,
|
|
419
|
+
});
|
|
420
|
+
const reason = `Artifact verification failed after ${MAX_VERIFICATION_RETRIES} retries for ${s.currentUnit.type} "${s.currentUnit.id}".`;
|
|
421
|
+
writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
|
|
422
|
+
ctx.ui.notify(`${s.currentUnit.type} ${s.currentUnit.id} — verification retries exhausted (${MAX_VERIFICATION_RETRIES}), wrote blocker placeholder to advance pipeline`, "warning");
|
|
423
|
+
// Reset retry count and fall through to "continue" so the loop
|
|
424
|
+
// re-derives state with the placeholder in place.
|
|
425
|
+
s.verificationRetryCount.delete(retryKey);
|
|
426
|
+
s.pendingVerificationRetry = null;
|
|
427
|
+
// Do NOT return "retry" — fall through to "continue" below.
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
s.pendingVerificationRetry = {
|
|
431
|
+
unitId: s.currentUnit.id,
|
|
432
|
+
failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
|
|
433
|
+
attempt,
|
|
434
|
+
};
|
|
435
|
+
debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
|
|
436
|
+
ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`, "warning");
|
|
437
|
+
return "retry";
|
|
438
|
+
}
|
|
402
439
|
}
|
|
403
440
|
}
|
|
404
441
|
}
|
|
@@ -880,11 +880,16 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
880
880
|
const contextRel = relMilestoneFile(base, mid, "CONTEXT");
|
|
881
881
|
const milestoneResearchPath = resolveMilestoneFile(base, mid, "RESEARCH");
|
|
882
882
|
const milestoneResearchRel = relMilestoneFile(base, mid, "RESEARCH");
|
|
883
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
884
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
883
885
|
const inlined = [];
|
|
884
886
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
885
887
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
886
888
|
if (contextInline)
|
|
887
889
|
inlined.push(contextInline);
|
|
890
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
891
|
+
if (sliceCtxInline)
|
|
892
|
+
inlined.push(sliceCtxInline);
|
|
888
893
|
const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
|
|
889
894
|
if (researchInline)
|
|
890
895
|
inlined.push(researchInline);
|
|
@@ -931,12 +936,17 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
931
936
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
932
937
|
const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
|
|
933
938
|
const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
|
|
939
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
940
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
934
941
|
const inlined = [];
|
|
935
942
|
// Inject phase handoff anchor from research phase (if available)
|
|
936
943
|
const researchSliceAnchor = readPhaseAnchor(base, mid, "research-slice");
|
|
937
944
|
if (researchSliceAnchor)
|
|
938
945
|
inlined.push(formatAnchorForPrompt(researchSliceAnchor));
|
|
939
946
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
947
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
948
|
+
if (sliceCtxInline)
|
|
949
|
+
inlined.push(sliceCtxInline);
|
|
940
950
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
941
951
|
if (researchInline)
|
|
942
952
|
inlined.push(researchInline);
|
|
@@ -1097,8 +1107,13 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
1097
1107
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1098
1108
|
const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
|
|
1099
1109
|
const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
|
|
1110
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
1111
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
1100
1112
|
const inlined = [];
|
|
1101
1113
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1114
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1115
|
+
if (sliceCtxInline)
|
|
1116
|
+
inlined.push(sliceCtxInline);
|
|
1102
1117
|
inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
|
|
1103
1118
|
if (inlineLevel !== "minimal") {
|
|
1104
1119
|
const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
|
|
@@ -1351,8 +1366,13 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
1351
1366
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1352
1367
|
const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
|
|
1353
1368
|
const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
|
|
1369
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
1370
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
1354
1371
|
const inlined = [];
|
|
1355
1372
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1373
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1374
|
+
if (sliceCtxInline)
|
|
1375
|
+
inlined.push(sliceCtxInline);
|
|
1356
1376
|
inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Current Slice Plan"));
|
|
1357
1377
|
// Find the blocker task summary — the completed task with blocker_discovered: true
|
|
1358
1378
|
let blockerTaskId = "";
|
|
@@ -1454,8 +1474,13 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1454
1474
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1455
1475
|
const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY");
|
|
1456
1476
|
const summaryRel = relSliceFile(base, mid, completedSliceId, "SUMMARY");
|
|
1477
|
+
const sliceContextPath = resolveSliceFile(base, mid, completedSliceId, "CONTEXT");
|
|
1478
|
+
const sliceContextRel = relSliceFile(base, mid, completedSliceId, "CONTEXT");
|
|
1457
1479
|
const inlined = [];
|
|
1458
1480
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
|
|
1481
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1482
|
+
if (sliceCtxInline)
|
|
1483
|
+
inlined.push(sliceCtxInline);
|
|
1459
1484
|
inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
|
|
1460
1485
|
if (inlineLevel !== "minimal") {
|
|
1461
1486
|
const projectInline = await inlineProjectFromDb(base);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { parseUnitId } from "./unit-id.js";
|
|
10
10
|
import { clearParseCache } from "./files.js";
|
|
11
11
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
12
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus } from "./gsd-db.js";
|
|
12
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus, updateSliceStatus } from "./gsd-db.js";
|
|
13
13
|
import { isValidationTerminal } from "./state.js";
|
|
14
14
|
import { getErrorMessage } from "./error-utils.js";
|
|
15
15
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -377,17 +377,25 @@ export function writeBlockerPlaceholder(unitType, unitId, base, reason) {
|
|
|
377
377
|
`Review and replace this file before relying on downstream artifacts.`,
|
|
378
378
|
].join("\n");
|
|
379
379
|
writeFileSync(absPath, content, "utf-8");
|
|
380
|
-
// Mark the task as complete in the DB so verifyExpectedArtifact passes.
|
|
380
|
+
// Mark the task/slice as complete in the DB so verifyExpectedArtifact passes.
|
|
381
381
|
// Without this, the DB status stays "pending" and the dispatch loop
|
|
382
|
-
// re-derives the same
|
|
383
|
-
if (
|
|
382
|
+
// re-derives the same unit indefinitely (#2531, #2653).
|
|
383
|
+
if (isDbAvailable()) {
|
|
384
384
|
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
385
|
-
if (mid && sid && tid) {
|
|
385
|
+
if (unitType === "execute-task" && mid && sid && tid) {
|
|
386
386
|
try {
|
|
387
387
|
updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString());
|
|
388
388
|
}
|
|
389
|
-
catch (
|
|
390
|
-
|
|
389
|
+
catch (e) {
|
|
390
|
+
logWarning("recovery", `updateTaskStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (unitType === "complete-slice" && mid && sid) {
|
|
394
|
+
try {
|
|
395
|
+
updateSliceStatus(mid, sid, "complete", new Date().toISOString());
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
logWarning("recovery", `updateSliceStatus failed during context exhaustion: ${e instanceof Error ? e.message : String(e)}`);
|
|
391
399
|
}
|
|
392
400
|
}
|
|
393
401
|
}
|
|
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
30
30
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
31
31
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
32
32
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
33
|
-
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
33
|
+
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
|
34
34
|
import { hideFooter } from "./auto-dashboard.js";
|
|
35
35
|
import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
|
|
36
36
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -48,34 +48,23 @@ import { resolveDefaultSessionModel } from "./preferences-models.js";
|
|
|
48
48
|
* Returns false if the bootstrap aborted (e.g., guided flow returned,
|
|
49
49
|
* concurrent session detected). Returns true when ready to dispatch.
|
|
50
50
|
*/
|
|
51
|
-
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
async function openProjectDbIfPresent(basePath) {
|
|
51
|
+
/** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
|
|
52
|
+
* Prevents the recursive dialog loop described in #1348 where
|
|
53
|
+
* bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
|
|
54
|
+
* cycles indefinitely when the discuss workflow doesn't produce a milestone. */
|
|
55
|
+
let _consecutiveCompleteBootstraps = 0;
|
|
56
|
+
const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
|
57
|
+
export async function openProjectDbIfPresent(basePath) {
|
|
59
58
|
const gsdDbPath = resolveProjectRootDbPath(basePath);
|
|
60
|
-
if (!existsSync(gsdDbPath))
|
|
61
|
-
return;
|
|
62
|
-
if (isDbAvailable())
|
|
59
|
+
if (!existsSync(gsdDbPath) || isDbAvailable())
|
|
63
60
|
return;
|
|
64
61
|
try {
|
|
65
|
-
const { openDatabase } = await import("./gsd-db.js");
|
|
66
62
|
openDatabase(gsdDbPath);
|
|
67
63
|
}
|
|
68
64
|
catch (err) {
|
|
69
|
-
|
|
70
|
-
logWarning("engine", `DB open failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
|
|
71
66
|
}
|
|
72
67
|
}
|
|
73
|
-
/** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
|
|
74
|
-
* Prevents the recursive dialog loop described in #1348 where
|
|
75
|
-
* bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
|
|
76
|
-
* cycles indefinitely when the discuss workflow doesn't produce a milestone. */
|
|
77
|
-
let _consecutiveCompleteBootstraps = 0;
|
|
78
|
-
const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
|
79
68
|
export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, deps) {
|
|
80
69
|
const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase, buildResolver, } = deps;
|
|
81
70
|
const lockResult = acquireSessionLock(base);
|
|
@@ -75,3 +75,20 @@ export function hasInteractiveToolInFlight() {
|
|
|
75
75
|
export function clearInFlightTools() {
|
|
76
76
|
inFlightTools.clear();
|
|
77
77
|
}
|
|
78
|
+
// ─── Tool invocation error classification (#2883) ────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Patterns that indicate a tool invocation failed due to malformed or truncated
|
|
81
|
+
* JSON arguments — as opposed to a normal business-logic error from the tool
|
|
82
|
+
* handler. When these errors occur, retrying the same unit will produce the same
|
|
83
|
+
* failure, so the retry loop must be broken.
|
|
84
|
+
*/
|
|
85
|
+
const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON|Unexpected end of JSON|Unexpected token.*in JSON/i;
|
|
86
|
+
/**
|
|
87
|
+
* Returns true if the error message indicates a tool invocation failure due to
|
|
88
|
+
* malformed/truncated arguments (as opposed to a normal tool execution error).
|
|
89
|
+
*/
|
|
90
|
+
export function isToolInvocationError(errorMsg) {
|
|
91
|
+
if (!errorMsg)
|
|
92
|
+
return false;
|
|
93
|
+
return TOOL_INVOCATION_ERROR_RE.test(errorMsg);
|
|
94
|
+
}
|
|
@@ -142,16 +142,27 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
|
|
|
142
142
|
failureContext: formatFailureContext(result),
|
|
143
143
|
attempt: nextAttempt,
|
|
144
144
|
};
|
|
145
|
-
|
|
145
|
+
const failedCmds = result.checks
|
|
146
|
+
.filter((c) => c.exitCode !== 0)
|
|
147
|
+
.map((c) => c.command);
|
|
148
|
+
const cmdSummary = failedCmds.length <= 3
|
|
149
|
+
? failedCmds.join(", ")
|
|
150
|
+
: `${failedCmds.slice(0, 3).join(", ")}... and ${failedCmds.length - 3} more`;
|
|
151
|
+
ctx.ui.notify(`Verification failed (${cmdSummary}) — auto-fix attempt ${nextAttempt}/${maxRetries}`, "warning");
|
|
146
152
|
// Return "retry" — the autoLoop while loop will re-iterate with the retry context
|
|
147
153
|
return "retry";
|
|
148
154
|
}
|
|
149
155
|
else {
|
|
150
156
|
// Gate failed, retries exhausted
|
|
151
|
-
const exhaustedAttempt = attempt + 1;
|
|
152
157
|
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
153
158
|
s.pendingVerificationRetry = null;
|
|
154
|
-
|
|
159
|
+
const exhaustedFails = result.checks
|
|
160
|
+
.filter((c) => c.exitCode !== 0)
|
|
161
|
+
.map((c) => c.command);
|
|
162
|
+
const exhaustedSummary = exhaustedFails.length <= 3
|
|
163
|
+
? exhaustedFails.join(", ")
|
|
164
|
+
: `${exhaustedFails.slice(0, 3).join(", ")}... and ${exhaustedFails.length - 3} more`;
|
|
165
|
+
ctx.ui.notify(`Verification gate FAILED after ${attempt} ${attempt === 1 ? "retry" : "retries"} (${exhaustedSummary}) — pausing for human review`, "error");
|
|
155
166
|
await pauseAuto(ctx, pi);
|
|
156
167
|
return "pause";
|
|
157
168
|
}
|