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
package/pkg/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
5
|
import type { GSDPreferences } from "../gsd/preferences.js";
|
|
7
6
|
import type { GSDState, Phase } from "../gsd/types.js";
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile);
|
|
10
7
|
const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
|
|
11
8
|
const STATUS_KEY = "gsd";
|
|
12
9
|
const lastSidebarSnapshots = new Map<string, string>();
|
|
@@ -200,6 +197,7 @@ export class CmuxClient {
|
|
|
200
197
|
return execFileSync("cmux", args, {
|
|
201
198
|
encoding: "utf-8",
|
|
202
199
|
timeout: 3000,
|
|
200
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
203
201
|
env: process.env,
|
|
204
202
|
});
|
|
205
203
|
} catch {
|
|
@@ -209,16 +207,24 @@ export class CmuxClient {
|
|
|
209
207
|
|
|
210
208
|
private async runAsync(args: string[]): Promise<string | null> {
|
|
211
209
|
if (!this.canRun()) return null;
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
timeout: 5000,
|
|
210
|
+
return new Promise<string | null>((resolve) => {
|
|
211
|
+
const child = spawn("cmux", args, {
|
|
212
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
216
213
|
env: process.env,
|
|
217
214
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
const chunks: Buffer[] = [];
|
|
216
|
+
let settled = false;
|
|
217
|
+
const done = (result: string | null) => {
|
|
218
|
+
if (!settled) { settled = true; resolve(result); }
|
|
219
|
+
};
|
|
220
|
+
const timer = setTimeout(() => { child.kill(); done(null); }, 5000);
|
|
221
|
+
child.stdout!.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
222
|
+
child.on("close", (code) => {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
done(code === 0 ? Buffer.concat(chunks).toString("utf-8") : null);
|
|
225
|
+
});
|
|
226
|
+
child.on("error", () => { clearTimeout(timer); done(null); });
|
|
227
|
+
});
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
getCapabilities(): unknown | null {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto/finalize-timeout.ts — Timeout guard for post-unit finalization.
|
|
3
|
+
*
|
|
4
|
+
* Prevents the auto-loop from hanging indefinitely when
|
|
5
|
+
* postUnitPostVerification() never resolves (#2344).
|
|
6
|
+
*
|
|
7
|
+
* Leaf module — no imports from auto/ to avoid circular dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Timeout for postUnitPostVerification in runFinalize (ms). */
|
|
11
|
+
export const FINALIZE_POST_TIMEOUT_MS = 60_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Race a promise against a timeout. Returns an object indicating whether
|
|
15
|
+
* the timeout fired and the resolved value (if any).
|
|
16
|
+
*
|
|
17
|
+
* Unlike Promise.race with a rejection, this returns a discriminated
|
|
18
|
+
* result so callers can handle timeouts as a recoverable condition
|
|
19
|
+
* rather than an exception.
|
|
20
|
+
*
|
|
21
|
+
* The timeout timer is always cleaned up, whether the promise resolves
|
|
22
|
+
* or the timeout fires.
|
|
23
|
+
*/
|
|
24
|
+
export async function withTimeout<T>(
|
|
25
|
+
promise: Promise<T>,
|
|
26
|
+
timeoutMs: number,
|
|
27
|
+
label: string,
|
|
28
|
+
): Promise<{ value: T; timedOut: false } | { value: undefined; timedOut: true }> {
|
|
29
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
|
|
31
|
+
const timeoutPromise = new Promise<{ value: undefined; timedOut: true }>((resolve) => {
|
|
32
|
+
timeoutHandle = setTimeout(() => {
|
|
33
|
+
resolve({ value: undefined, timedOut: true });
|
|
34
|
+
}, timeoutMs);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await Promise.race([
|
|
39
|
+
promise.then((value) => ({ value, timedOut: false as const })),
|
|
40
|
+
timeoutPromise,
|
|
41
|
+
]);
|
|
42
|
+
return result;
|
|
43
|
+
} finally {
|
|
44
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -48,6 +48,7 @@ export async function autoLoop(
|
|
|
48
48
|
let iteration = 0;
|
|
49
49
|
const loopState: LoopState = { recentUnits: [], stuckRecoveryAttempts: 0 };
|
|
50
50
|
let consecutiveErrors = 0;
|
|
51
|
+
const recentErrorMessages: string[] = [];
|
|
51
52
|
|
|
52
53
|
while (s.active) {
|
|
53
54
|
iteration++;
|
|
@@ -202,6 +203,7 @@ export async function autoLoop(
|
|
|
202
203
|
|
|
203
204
|
deps.clearUnitTimeout();
|
|
204
205
|
consecutiveErrors = 0;
|
|
206
|
+
recentErrorMessages.length = 0;
|
|
205
207
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
206
208
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
207
209
|
continue;
|
|
@@ -250,12 +252,18 @@ export async function autoLoop(
|
|
|
250
252
|
if (finalizeResult.action === "continue") continue;
|
|
251
253
|
|
|
252
254
|
consecutiveErrors = 0; // Iteration completed successfully
|
|
255
|
+
recentErrorMessages.length = 0;
|
|
253
256
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
254
257
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
255
258
|
} catch (loopErr) {
|
|
256
259
|
// ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
|
|
257
260
|
const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
|
|
258
261
|
|
|
262
|
+
// Always emit iteration-end on error so the journal records iteration
|
|
263
|
+
// completion even on failure (#2344). Without this, errors in
|
|
264
|
+
// runFinalize leave the journal incomplete, making diagnosis harder.
|
|
265
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration, error: msg } });
|
|
266
|
+
|
|
259
267
|
// ── Infrastructure errors: immediate stop, no retry ──
|
|
260
268
|
// These are unrecoverable (disk full, OOM, etc.). Retrying just burns
|
|
261
269
|
// LLM budget on guaranteed failures.
|
|
@@ -280,6 +288,7 @@ export async function autoLoop(
|
|
|
280
288
|
}
|
|
281
289
|
|
|
282
290
|
consecutiveErrors++;
|
|
291
|
+
recentErrorMessages.push(msg.length > 120 ? msg.slice(0, 120) + "..." : msg);
|
|
283
292
|
debugLog("autoLoop", {
|
|
284
293
|
phase: "iteration-error",
|
|
285
294
|
iteration,
|
|
@@ -289,8 +298,11 @@ export async function autoLoop(
|
|
|
289
298
|
|
|
290
299
|
if (consecutiveErrors >= 3) {
|
|
291
300
|
// 3+ consecutive: hard stop — something is fundamentally broken
|
|
301
|
+
const errorHistory = recentErrorMessages
|
|
302
|
+
.map((m, i) => ` ${i + 1}. ${m}`)
|
|
303
|
+
.join("\n");
|
|
292
304
|
ctx.ui.notify(
|
|
293
|
-
`Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures
|
|
305
|
+
`Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures:\n${errorHistory}`,
|
|
294
306
|
"error",
|
|
295
307
|
);
|
|
296
308
|
await deps.stopAuto(
|
|
@@ -26,13 +26,17 @@ import { runUnit } from "./run-unit.js";
|
|
|
26
26
|
import { debugLog } from "../debug-logger.js";
|
|
27
27
|
import { PROJECT_FILES } from "../detection.js";
|
|
28
28
|
import { MergeConflictError } from "../git-service.js";
|
|
29
|
-
import { join, basename } from "node:path";
|
|
30
|
-
import { existsSync, cpSync } from "node:fs";
|
|
29
|
+
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
30
|
+
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
31
31
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
32
32
|
import { gsdRoot } from "../paths.js";
|
|
33
33
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
34
|
-
import { verifyExpectedArtifact } from "../auto-recovery.js";
|
|
34
|
+
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
35
35
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
36
|
+
import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
37
|
+
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
38
|
+
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
39
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
36
40
|
|
|
37
41
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
38
42
|
|
|
@@ -182,7 +186,7 @@ export async function runPreDispatch(
|
|
|
182
186
|
}
|
|
183
187
|
if (!healthGate.proceed) {
|
|
184
188
|
ctx.ui.notify(
|
|
185
|
-
healthGate.reason
|
|
189
|
+
healthGate.reason || "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
186
190
|
"error",
|
|
187
191
|
);
|
|
188
192
|
await deps.pauseAuto(ctx, pi);
|
|
@@ -218,6 +222,63 @@ export async function runPreDispatch(
|
|
|
218
222
|
statePhase: state.phase,
|
|
219
223
|
});
|
|
220
224
|
|
|
225
|
+
// ── Slice-level parallelism gate (#2340) ─────────────────────────────
|
|
226
|
+
// When slice_parallel is enabled, check if multiple slices are eligible
|
|
227
|
+
// for parallel execution. If so, dispatch them in parallel and stop the
|
|
228
|
+
// sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
|
|
229
|
+
if (
|
|
230
|
+
prefs?.slice_parallel?.enabled &&
|
|
231
|
+
mid &&
|
|
232
|
+
!process.env.GSD_PARALLEL_WORKER &&
|
|
233
|
+
isDbAvailable()
|
|
234
|
+
) {
|
|
235
|
+
try {
|
|
236
|
+
const dbSlices = getMilestoneSlices(mid);
|
|
237
|
+
if (dbSlices.length > 0) {
|
|
238
|
+
const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
|
|
239
|
+
const sliceInputs = dbSlices.map(sl => ({
|
|
240
|
+
id: sl.id,
|
|
241
|
+
done: doneIds.has(sl.id),
|
|
242
|
+
depends: sl.depends ?? [],
|
|
243
|
+
}));
|
|
244
|
+
const eligible = getEligibleSlices(sliceInputs, doneIds);
|
|
245
|
+
if (eligible.length > 1) {
|
|
246
|
+
debugLog("autoLoop", {
|
|
247
|
+
phase: "slice-parallel-dispatch",
|
|
248
|
+
iteration: ic.iteration,
|
|
249
|
+
mid,
|
|
250
|
+
eligibleSlices: eligible.map(e => e.id),
|
|
251
|
+
});
|
|
252
|
+
ctx.ui.notify(
|
|
253
|
+
`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`,
|
|
254
|
+
"info",
|
|
255
|
+
);
|
|
256
|
+
const result = await startSliceParallel(
|
|
257
|
+
s.basePath,
|
|
258
|
+
mid,
|
|
259
|
+
eligible,
|
|
260
|
+
{ maxWorkers: prefs.slice_parallel.max_workers ?? 2 },
|
|
261
|
+
);
|
|
262
|
+
if (result.started.length > 0) {
|
|
263
|
+
ctx.ui.notify(
|
|
264
|
+
`Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`,
|
|
265
|
+
"info",
|
|
266
|
+
);
|
|
267
|
+
await deps.stopAuto(ctx, pi, `Slice-parallel dispatched for ${mid}`);
|
|
268
|
+
return { action: "break", reason: "slice-parallel-dispatched" };
|
|
269
|
+
}
|
|
270
|
+
// Fall through to sequential if no workers started
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
debugLog("autoLoop", {
|
|
275
|
+
phase: "slice-parallel-check-error",
|
|
276
|
+
error: err instanceof Error ? err.message : String(err),
|
|
277
|
+
});
|
|
278
|
+
// Non-fatal — fall through to sequential dispatch
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
221
282
|
// ── Milestone transition ────────────────────────────────────────────
|
|
222
283
|
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
223
284
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
|
|
@@ -628,15 +689,17 @@ export async function runDispatch(
|
|
|
628
689
|
unitId,
|
|
629
690
|
reason: stuckSignal.reason,
|
|
630
691
|
});
|
|
692
|
+
const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
693
|
+
const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
694
|
+
const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
|
|
695
|
+
if (stuckDiag) stuckParts.push(`Expected: ${stuckDiag}`);
|
|
696
|
+
if (stuckRemediation) stuckParts.push(`To recover:\n${stuckRemediation}`);
|
|
697
|
+
ctx.ui.notify(stuckParts.join(" "), "error");
|
|
631
698
|
await deps.stopAuto(
|
|
632
699
|
ctx,
|
|
633
700
|
pi,
|
|
634
701
|
`Stuck: ${stuckSignal.reason}`,
|
|
635
702
|
);
|
|
636
|
-
ctx.ui.notify(
|
|
637
|
-
`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}. The expected artifact was not written.`,
|
|
638
|
-
"error",
|
|
639
|
-
);
|
|
640
703
|
return { action: "break", reason: "stuck-detected" };
|
|
641
704
|
}
|
|
642
705
|
} else {
|
|
@@ -946,11 +1009,38 @@ export async function runUnitPhase(
|
|
|
946
1009
|
}
|
|
947
1010
|
const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
|
|
948
1011
|
const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
|
|
949
|
-
|
|
1012
|
+
// Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
|
|
1013
|
+
// that cannot be matched by exact filename — scan the directory by suffix.
|
|
1014
|
+
let hasXcodeBundle = false;
|
|
1015
|
+
try {
|
|
1016
|
+
const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
|
|
1017
|
+
hasXcodeBundle = entries.some((e: string) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
|
|
1020
|
+
}
|
|
1021
|
+
// Monorepo support (#2347): if no project files in the worktree directory,
|
|
1022
|
+
// walk parent directories up to the filesystem root. In monorepos,
|
|
1023
|
+
// package.json / Cargo.toml etc. live in a parent directory.
|
|
1024
|
+
let hasProjectFileInParent = false;
|
|
1025
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle) {
|
|
1026
|
+
let checkDir = dirname(s.basePath);
|
|
1027
|
+
const { root } = parsePath(checkDir);
|
|
1028
|
+
while (checkDir !== root) {
|
|
1029
|
+
// Stop at git repository boundary — ancestors above the repo root
|
|
1030
|
+
// (e.g. ~ or /usr/local) may contain unrelated project files.
|
|
1031
|
+
if (deps.existsSync(join(checkDir, ".git"))) break;
|
|
1032
|
+
if (PROJECT_FILES.some((f) => deps.existsSync(join(checkDir, f)))) {
|
|
1033
|
+
hasProjectFileInParent = true;
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
checkDir = dirname(checkDir);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
|
|
950
1040
|
// Greenfield projects won't have project files yet — the first task creates them.
|
|
951
1041
|
// Log a warning but allow execution to proceed. The .git check above is sufficient
|
|
952
1042
|
// to ensure we're in a valid working directory.
|
|
953
|
-
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir });
|
|
1043
|
+
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
|
|
954
1044
|
ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
|
|
955
1045
|
}
|
|
956
1046
|
}
|
|
@@ -964,6 +1054,7 @@ export async function runUnitPhase(
|
|
|
964
1054
|
const previousTier = s.currentUnitRouting?.tier;
|
|
965
1055
|
|
|
966
1056
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
1057
|
+
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
967
1058
|
const unitStartSeq = ic.nextSeq();
|
|
968
1059
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
969
1060
|
deps.captureAvailableSkills();
|
|
@@ -983,30 +1074,10 @@ export async function runUnitPhase(
|
|
|
983
1074
|
},
|
|
984
1075
|
);
|
|
985
1076
|
|
|
986
|
-
//
|
|
987
|
-
const modelResult = await deps.selectAndApplyModel(
|
|
988
|
-
ctx,
|
|
989
|
-
pi,
|
|
990
|
-
unitType,
|
|
991
|
-
unitId,
|
|
992
|
-
s.basePath,
|
|
993
|
-
prefs,
|
|
994
|
-
s.verbose,
|
|
995
|
-
s.autoModeStartModel,
|
|
996
|
-
sidecarItem ? undefined : { isRetry, previousTier },
|
|
997
|
-
);
|
|
998
|
-
s.currentUnitRouting =
|
|
999
|
-
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
1000
|
-
s.currentUnitModel =
|
|
1001
|
-
modelResult.appliedModel as AutoSession["currentUnitModel"];
|
|
1002
|
-
|
|
1003
|
-
// Status bar + progress widget
|
|
1077
|
+
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
1004
1078
|
ctx.ui.setStatus("gsd-auto", "auto");
|
|
1005
1079
|
if (mid)
|
|
1006
1080
|
deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
1007
|
-
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
1008
|
-
|
|
1009
|
-
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
1010
1081
|
|
|
1011
1082
|
// Prompt injection
|
|
1012
1083
|
let finalPrompt = prompt;
|
|
@@ -1072,6 +1143,23 @@ export async function runUnitPhase(
|
|
|
1072
1143
|
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
1073
1144
|
}
|
|
1074
1145
|
|
|
1146
|
+
// Select and apply model (with tier escalation on retry — normal units only)
|
|
1147
|
+
const modelResult = await deps.selectAndApplyModel(
|
|
1148
|
+
ctx,
|
|
1149
|
+
pi,
|
|
1150
|
+
unitType,
|
|
1151
|
+
unitId,
|
|
1152
|
+
s.basePath,
|
|
1153
|
+
prefs,
|
|
1154
|
+
s.verbose,
|
|
1155
|
+
s.autoModeStartModel,
|
|
1156
|
+
sidecarItem ? undefined : { isRetry, previousTier },
|
|
1157
|
+
);
|
|
1158
|
+
s.currentUnitRouting =
|
|
1159
|
+
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
1160
|
+
s.currentUnitModel =
|
|
1161
|
+
modelResult.appliedModel as AutoSession["currentUnitModel"];
|
|
1162
|
+
|
|
1075
1163
|
// Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
|
|
1076
1164
|
const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
|
|
1077
1165
|
if (hookModelOverride) {
|
|
@@ -1097,6 +1185,17 @@ export async function runUnitPhase(
|
|
|
1097
1185
|
}
|
|
1098
1186
|
}
|
|
1099
1187
|
|
|
1188
|
+
// Store the final dispatched model ID so the dashboard can read it (#2899).
|
|
1189
|
+
// This accounts for hook model overrides applied after selectAndApplyModel.
|
|
1190
|
+
s.currentDispatchedModelId = s.currentUnitModel
|
|
1191
|
+
? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
|
|
1192
|
+
: null;
|
|
1193
|
+
|
|
1194
|
+
// Progress widget + preconditions — deferred to after model selection so the
|
|
1195
|
+
// widget's first render tick shows the correct model (#2899).
|
|
1196
|
+
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
1197
|
+
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
1198
|
+
|
|
1100
1199
|
// Start unit supervision
|
|
1101
1200
|
deps.clearUnitTimeout();
|
|
1102
1201
|
deps.startUnitSupervision({
|
|
@@ -1209,11 +1308,13 @@ export async function runUnitPhase(
|
|
|
1209
1308
|
);
|
|
1210
1309
|
}
|
|
1211
1310
|
|
|
1212
|
-
// ── Zero tool-call guard (#1833)
|
|
1213
|
-
//
|
|
1214
|
-
//
|
|
1215
|
-
// the
|
|
1216
|
-
|
|
1311
|
+
// ── Zero tool-call guard (#1833, #2653) ──────────────────────────
|
|
1312
|
+
// Any unit that completes with 0 tool calls made no real progress —
|
|
1313
|
+
// likely context exhaustion where all tool calls errored out. Treat
|
|
1314
|
+
// as failed so the unit is retried in a fresh context instead of
|
|
1315
|
+
// silently passing through to artifact verification (which loops
|
|
1316
|
+
// forever when the unit never produced its artifact).
|
|
1317
|
+
{
|
|
1217
1318
|
const currentLedger = deps.getLedger() as { units: Array<{ type: string; id: string; startedAt: number; toolCalls: number }> } | null;
|
|
1218
1319
|
if (currentLedger?.units) {
|
|
1219
1320
|
const lastUnit = [...currentLedger.units].reverse().find(
|
|
@@ -1224,14 +1325,14 @@ export async function runUnitPhase(
|
|
|
1224
1325
|
phase: "zero-tool-calls",
|
|
1225
1326
|
unitType,
|
|
1226
1327
|
unitId,
|
|
1227
|
-
warning: "
|
|
1328
|
+
warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
|
|
1228
1329
|
});
|
|
1229
1330
|
ctx.ui.notify(
|
|
1230
|
-
`${unitType} ${unitId} completed with 0 tool calls —
|
|
1331
|
+
`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`,
|
|
1231
1332
|
"warning",
|
|
1232
1333
|
);
|
|
1233
1334
|
// Fall through to next iteration where dispatch will re-derive
|
|
1234
|
-
// and re-dispatch this
|
|
1335
|
+
// and re-dispatch this unit.
|
|
1235
1336
|
return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
|
|
1236
1337
|
}
|
|
1237
1338
|
}
|
|
@@ -1375,7 +1476,30 @@ export async function runFinalize(
|
|
|
1375
1476
|
}
|
|
1376
1477
|
|
|
1377
1478
|
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
|
1378
|
-
|
|
1479
|
+
// Timeout guard: if postUnitPostVerification hangs (e.g., module import
|
|
1480
|
+
// deadlock, SQLite transaction hang), force-continue after timeout so the
|
|
1481
|
+
// auto-loop is not permanently frozen (#2344).
|
|
1482
|
+
const postResultGuard = await withTimeout(
|
|
1483
|
+
deps.postUnitPostVerification(postUnitCtx),
|
|
1484
|
+
FINALIZE_POST_TIMEOUT_MS,
|
|
1485
|
+
"postUnitPostVerification",
|
|
1486
|
+
);
|
|
1487
|
+
|
|
1488
|
+
if (postResultGuard.timedOut) {
|
|
1489
|
+
debugLog("autoLoop", {
|
|
1490
|
+
phase: "post-verification-timeout",
|
|
1491
|
+
iteration: ic.iteration,
|
|
1492
|
+
unitType: iterData.unitType,
|
|
1493
|
+
unitId: iterData.unitId,
|
|
1494
|
+
});
|
|
1495
|
+
ctx.ui.notify(
|
|
1496
|
+
`postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`,
|
|
1497
|
+
"warning",
|
|
1498
|
+
);
|
|
1499
|
+
return { action: "next", data: undefined as void };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const postResult = postResultGuard.value;
|
|
1379
1503
|
|
|
1380
1504
|
if (postResult === "stopped") {
|
|
1381
1505
|
debugLog("autoLoop", {
|
|
@@ -105,6 +105,8 @@ export class AutoSession {
|
|
|
105
105
|
// ── Model state ──────────────────────────────────────────────────────────
|
|
106
106
|
autoModeStartModel: StartModel | null = null;
|
|
107
107
|
currentUnitModel: Model<Api> | null = null;
|
|
108
|
+
/** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
|
|
109
|
+
currentDispatchedModelId: string | null = null;
|
|
108
110
|
originalModelId: string | null = null;
|
|
109
111
|
originalModelProvider: string | null = null;
|
|
110
112
|
lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
@@ -120,6 +122,11 @@ export class AutoSession {
|
|
|
120
122
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
121
123
|
sidecarQueue: SidecarItem[] = [];
|
|
122
124
|
|
|
125
|
+
// ── Tool invocation errors (#2883) ──────────────────────────────────
|
|
126
|
+
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
127
|
+
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
128
|
+
lastToolInvocationError: string | null = null;
|
|
129
|
+
|
|
123
130
|
// ── Isolation degradation ────────────────────────────────────────────
|
|
124
131
|
/** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
|
|
125
132
|
isolationDegraded = false;
|
|
@@ -193,6 +200,7 @@ export class AutoSession {
|
|
|
193
200
|
// Model
|
|
194
201
|
this.autoModeStartModel = null;
|
|
195
202
|
this.currentUnitModel = null;
|
|
203
|
+
this.currentDispatchedModelId = null;
|
|
196
204
|
this.originalModelId = null;
|
|
197
205
|
this.originalModelProvider = null;
|
|
198
206
|
this.lastBudgetAlertLevel = 0;
|
|
@@ -212,6 +220,7 @@ export class AutoSession {
|
|
|
212
220
|
this.pendingQuickTasks = [];
|
|
213
221
|
this.sidecarQueue = [];
|
|
214
222
|
this.rewriteAttemptCount = 0;
|
|
223
|
+
this.lastToolInvocationError = null;
|
|
215
224
|
this.isolationDegraded = false;
|
|
216
225
|
this.milestoneMergedInPhases = false;
|
|
217
226
|
|
|
@@ -438,6 +438,8 @@ export interface WidgetStateAccessors {
|
|
|
438
438
|
isVerbose(): boolean;
|
|
439
439
|
/** True while newSession() is in-flight — render must not access session state. */
|
|
440
440
|
isSessionSwitching(): boolean;
|
|
441
|
+
/** Fully-qualified dispatched model ID (provider/id) set after model selection + hook overrides (#2899). */
|
|
442
|
+
getCurrentDispatchedModelId(): string | null;
|
|
441
443
|
}
|
|
442
444
|
|
|
443
445
|
export function updateProgressWidget(
|
|
@@ -629,9 +631,15 @@ export function updateProgressWidget(
|
|
|
629
631
|
const cxPctVal = cxUsage?.percent ?? 0;
|
|
630
632
|
const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
|
|
631
633
|
|
|
632
|
-
// Model display —
|
|
633
|
-
|
|
634
|
-
const
|
|
634
|
+
// Model display — prefer dispatched model ID (set after selectAndApplyModel
|
|
635
|
+
// + hook overrides) over cmdCtx?.model which can be stale (#2899).
|
|
636
|
+
const dispatchedModelId = accessors.getCurrentDispatchedModelId();
|
|
637
|
+
const modelId = dispatchedModelId
|
|
638
|
+
? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
|
|
639
|
+
: (cmdCtx?.model?.id ?? "");
|
|
640
|
+
const modelProvider = dispatchedModelId
|
|
641
|
+
? dispatchedModelId.split("/")[0] || ""
|
|
642
|
+
: (cmdCtx?.model?.provider ?? "");
|
|
635
643
|
const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
|
|
636
644
|
const modelDisplay = (modelProvider && modelId
|
|
637
645
|
? `${modelProvider}/${modelId}`
|
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
verifyExpectedArtifact,
|
|
35
35
|
resolveExpectedArtifactPath,
|
|
36
|
+
writeBlockerPlaceholder,
|
|
37
|
+
diagnoseExpectedArtifact,
|
|
36
38
|
} from "./auto-recovery.js";
|
|
37
39
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
38
40
|
import { syncStateToProjectRoot } from "./auto-worktree.js";
|
|
@@ -51,6 +53,9 @@ import { debugLog } from "./debug-logger.js";
|
|
|
51
53
|
import { runSafely } from "./auto-utils.js";
|
|
52
54
|
import type { AutoSession, SidecarItem } from "./auto/session.js";
|
|
53
55
|
|
|
56
|
+
/** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
|
|
57
|
+
const MAX_VERIFICATION_RETRIES = 3;
|
|
58
|
+
|
|
54
59
|
|
|
55
60
|
/** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
|
|
56
61
|
* drain via runUnit. Logs the enqueue event and notifies the UI. */
|
|
@@ -467,6 +472,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
467
472
|
// When artifact verification fails for a unit type that has a known expected
|
|
468
473
|
// artifact, return "retry" so the caller re-dispatches with failure context
|
|
469
474
|
// instead of blindly re-dispatching the same unit (#1571).
|
|
475
|
+
// After MAX_VERIFICATION_RETRIES, escalate to writeBlockerPlaceholder so the
|
|
476
|
+
// pipeline can advance instead of looping forever (#2653).
|
|
470
477
|
//
|
|
471
478
|
// HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
|
|
472
479
|
// written because the completion tool failed at the infra level. Retrying
|
|
@@ -476,27 +483,64 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
476
483
|
// db_unavailable so the artifact was never written. Retrying would
|
|
477
484
|
// produce an infinite re-dispatch loop (#2517).
|
|
478
485
|
debugLog("postUnit", { phase: "artifact-verify-skip-db-unavailable", unitType: s.currentUnit.type, unitId: s.currentUnit.id });
|
|
486
|
+
const dbSkipDiag = diagnoseExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
479
487
|
ctx.ui.notify(
|
|
480
|
-
`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id}
|
|
488
|
+
`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — DB unavailable, skipping retry.${dbSkipDiag ? ` Expected: ${dbSkipDiag}` : ""}`,
|
|
481
489
|
"error",
|
|
482
490
|
);
|
|
483
491
|
} else if (!triggerArtifactVerified) {
|
|
492
|
+
// #2883: If the artifact is missing because the tool invocation itself
|
|
493
|
+
// failed (malformed/truncated JSON arguments), retrying will produce the
|
|
494
|
+
// same failure. Pause auto-mode instead of entering a stuck retry loop.
|
|
495
|
+
if (s.lastToolInvocationError) {
|
|
496
|
+
const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
|
|
497
|
+
debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
|
|
498
|
+
ctx.ui.notify(errMsg, "error");
|
|
499
|
+
s.lastToolInvocationError = null;
|
|
500
|
+
await pauseAuto(ctx, pi);
|
|
501
|
+
return "dispatched";
|
|
502
|
+
}
|
|
503
|
+
|
|
484
504
|
const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
|
|
485
505
|
if (hasExpectedArtifact) {
|
|
486
506
|
const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
|
|
487
507
|
const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
|
|
488
508
|
s.verificationRetryCount.set(retryKey, attempt);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
509
|
+
|
|
510
|
+
if (attempt > MAX_VERIFICATION_RETRIES) {
|
|
511
|
+
// Retries exhausted — write a blocker placeholder so the pipeline
|
|
512
|
+
// can advance past this stuck unit (#2653).
|
|
513
|
+
debugLog("postUnit", {
|
|
514
|
+
phase: "artifact-verify-escalate",
|
|
515
|
+
unitType: s.currentUnit.type,
|
|
516
|
+
unitId: s.currentUnit.id,
|
|
517
|
+
attempt,
|
|
518
|
+
maxRetries: MAX_VERIFICATION_RETRIES,
|
|
519
|
+
});
|
|
520
|
+
const reason = `Artifact verification failed after ${MAX_VERIFICATION_RETRIES} retries for ${s.currentUnit.type} "${s.currentUnit.id}".`;
|
|
521
|
+
writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
|
|
522
|
+
ctx.ui.notify(
|
|
523
|
+
`${s.currentUnit.type} ${s.currentUnit.id} — verification retries exhausted (${MAX_VERIFICATION_RETRIES}), wrote blocker placeholder to advance pipeline`,
|
|
524
|
+
"warning",
|
|
525
|
+
);
|
|
526
|
+
// Reset retry count and fall through to "continue" so the loop
|
|
527
|
+
// re-derives state with the placeholder in place.
|
|
528
|
+
s.verificationRetryCount.delete(retryKey);
|
|
529
|
+
s.pendingVerificationRetry = null;
|
|
530
|
+
// Do NOT return "retry" — fall through to "continue" below.
|
|
531
|
+
} else {
|
|
532
|
+
s.pendingVerificationRetry = {
|
|
533
|
+
unitId: s.currentUnit.id,
|
|
534
|
+
failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
|
|
535
|
+
attempt,
|
|
536
|
+
};
|
|
537
|
+
debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
|
|
538
|
+
ctx.ui.notify(
|
|
539
|
+
`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`,
|
|
540
|
+
"warning",
|
|
541
|
+
);
|
|
542
|
+
return "retry";
|
|
543
|
+
}
|
|
500
544
|
}
|
|
501
545
|
}
|
|
502
546
|
} else {
|