gsd-pi 2.63.0-dev.026d309 → 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 +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +123 -22
- 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 +45 -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-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 +74 -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 +40 -26
- 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-projections.js +21 -5
- 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 +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +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/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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- 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/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/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/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 +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +156 -34
- 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 +53 -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-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 +67 -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/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 +41 -26
- 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-projections.ts +23 -5
- 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/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → 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
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.
|
|
@@ -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 } });
|
|
@@ -669,11 +717,40 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
669
717
|
}
|
|
670
718
|
const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
|
|
671
719
|
const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
|
|
672
|
-
|
|
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) {
|
|
673
750
|
// Greenfield projects won't have project files yet — the first task creates them.
|
|
674
751
|
// Log a warning but allow execution to proceed. The .git check above is sufficient
|
|
675
752
|
// to ensure we're in a valid working directory.
|
|
676
|
-
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 });
|
|
677
754
|
ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
|
|
678
755
|
}
|
|
679
756
|
}
|
|
@@ -683,6 +760,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
683
760
|
s.currentUnit.id === unitId);
|
|
684
761
|
const previousTier = s.currentUnitRouting?.tier;
|
|
685
762
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
763
|
+
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
686
764
|
const unitStartSeq = ic.nextSeq();
|
|
687
765
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
688
766
|
deps.captureAvailableSkills();
|
|
@@ -695,18 +773,10 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
695
773
|
lastProgressKind: "dispatch",
|
|
696
774
|
recoveryAttempts: 0, // Reset so re-dispatched units get full recovery budget (#2322)
|
|
697
775
|
});
|
|
698
|
-
//
|
|
699
|
-
const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
|
|
700
|
-
s.currentUnitRouting =
|
|
701
|
-
modelResult.routing;
|
|
702
|
-
s.currentUnitModel =
|
|
703
|
-
modelResult.appliedModel;
|
|
704
|
-
// Status bar + progress widget
|
|
776
|
+
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
705
777
|
ctx.ui.setStatus("gsd-auto", "auto");
|
|
706
778
|
if (mid)
|
|
707
779
|
deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
708
|
-
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
709
|
-
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
710
780
|
// Prompt injection
|
|
711
781
|
let finalPrompt = prompt;
|
|
712
782
|
if (s.pendingVerificationRetry) {
|
|
@@ -764,6 +834,12 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
764
834
|
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
|
|
765
835
|
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
766
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;
|
|
767
843
|
// Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
|
|
768
844
|
const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
|
|
769
845
|
if (hookModelOverride) {
|
|
@@ -784,6 +860,15 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
784
860
|
`Ensure the model is defined in models.json and has auth configured.`, "warning");
|
|
785
861
|
}
|
|
786
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);
|
|
787
872
|
// Start unit supervision
|
|
788
873
|
deps.clearUnitTimeout();
|
|
789
874
|
deps.startUnitSupervision({
|
|
@@ -860,11 +945,13 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
860
945
|
if (s.currentUnit) {
|
|
861
946
|
await deps.closeoutUnit(ctx, s.basePath, unitType, unitId, s.currentUnit.startedAt, deps.buildSnapshotOpts(unitType, unitId));
|
|
862
947
|
}
|
|
863
|
-
// ── Zero tool-call guard (#1833)
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
// the
|
|
867
|
-
|
|
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
|
+
{
|
|
868
955
|
const currentLedger = deps.getLedger();
|
|
869
956
|
if (currentLedger?.units) {
|
|
870
957
|
const lastUnit = [...currentLedger.units].reverse().find((u) => u.type === unitType && u.id === unitId && u.startedAt === s.currentUnit?.startedAt);
|
|
@@ -873,11 +960,11 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
873
960
|
phase: "zero-tool-calls",
|
|
874
961
|
unitType,
|
|
875
962
|
unitId,
|
|
876
|
-
warning: "
|
|
963
|
+
warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
|
|
877
964
|
});
|
|
878
|
-
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");
|
|
879
966
|
// Fall through to next iteration where dispatch will re-derive
|
|
880
|
-
// and re-dispatch this
|
|
967
|
+
// and re-dispatch this unit.
|
|
881
968
|
return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
|
|
882
969
|
}
|
|
883
970
|
}
|
|
@@ -993,7 +1080,21 @@ export async function runFinalize(ic, iterData, sidecarItem) {
|
|
|
993
1080
|
}
|
|
994
1081
|
}
|
|
995
1082
|
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
|
996
|
-
|
|
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;
|
|
997
1098
|
if (postResult === "stopped") {
|
|
998
1099
|
debugLog("autoLoop", {
|
|
999
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, diagnoseExpectedArtifact, } 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
|
|
@@ -387,20 +391,51 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
387
391
|
ctx.ui.notify(`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — DB unavailable, skipping retry.${dbSkipDiag ? ` Expected: ${dbSkipDiag}` : ""}`, "error");
|
|
388
392
|
}
|
|
389
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
|
+
}
|
|
390
405
|
const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
|
|
391
406
|
if (hasExpectedArtifact) {
|
|
392
407
|
const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
|
|
393
408
|
const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
|
|
394
409
|
s.verificationRetryCount.set(retryKey, attempt);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
}
|
|
404
439
|
}
|
|
405
440
|
}
|
|
406
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
|
+
}
|
|
@@ -14,7 +14,7 @@ import { atomicWriteSync } from "./atomic-write.js";
|
|
|
14
14
|
import { execFileSync } from "node:child_process";
|
|
15
15
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
|
-
import { createWorktree, removeWorktree, resolveGitDir, worktreePath, } from "./worktree-manager.js";
|
|
17
|
+
import { createWorktree, removeWorktree, resolveGitDir, worktreePath, isInsideWorktreesDir, } from "./worktree-manager.js";
|
|
18
18
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
19
19
|
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
20
20
|
import { debugLog } from "./debug-logger.js";
|
|
@@ -1025,13 +1025,19 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
1025
1025
|
logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
|
|
1026
1026
|
`This is likely an orphaned directory consuming disk space. ` +
|
|
1027
1027
|
`Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
|
|
1028
|
-
// Attempt a direct filesystem removal as a fallback
|
|
1029
|
-
|
|
1030
|
-
|
|
1028
|
+
// Attempt a direct filesystem removal as a fallback — but ONLY if the
|
|
1029
|
+
// path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
|
|
1030
|
+
if (isInsideWorktreesDir(originalBasePath, wtDir)) {
|
|
1031
|
+
try {
|
|
1032
|
+
rmSync(wtDir, { recursive: true, force: true });
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
// Non-fatal — the warning above tells the user how to clean up
|
|
1036
|
+
logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1037
|
+
}
|
|
1031
1038
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1039
|
+
else {
|
|
1040
|
+
console.error(`[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`);
|
|
1035
1041
|
}
|
|
1036
1042
|
}
|
|
1037
1043
|
}
|