@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.4813ead6
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/dist/cli-web-branch.d.ts +2 -0
- package/dist/cli-web-branch.js +9 -2
- package/dist/help-text.js +5 -0
- package/dist/project-sessions.js +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +78 -23
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +101 -237
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +224 -0
- package/dist/resources/extensions/github-sync/templates.js +3 -3
- package/dist/resources/extensions/gsd/artifact-projection.js +14 -0
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- package/dist/resources/extensions/gsd/auto/loop.js +74 -56
- package/dist/resources/extensions/gsd/auto/orchestrator.js +763 -63
- package/dist/resources/extensions/gsd/auto/phases.js +28 -3
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -1
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +16 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +6 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +8 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +191 -9
- package/dist/resources/extensions/gsd/auto-recovery.js +48 -49
- package/dist/resources/extensions/gsd/auto-runtime-state.js +17 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -23
- package/dist/resources/extensions/gsd/auto-timers.js +16 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +37 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +33 -29
- package/dist/resources/extensions/gsd/auto-verification.js +7 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +45 -36
- package/dist/resources/extensions/gsd/auto.js +73 -471
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +28 -37
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +11 -37
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +103 -138
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +63 -4
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +21 -4
- package/dist/resources/extensions/gsd/codebase-generator.js +8 -4
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +3 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +20 -0
- package/dist/resources/extensions/gsd/commands-inspect.js +4 -8
- package/dist/resources/extensions/gsd/commands-maintenance.js +61 -41
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +12 -2
- package/dist/resources/extensions/gsd/db-workspace.js +103 -0
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/delegation-policy.js +2 -10
- package/dist/resources/extensions/gsd/discussion-handoff.js +218 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/doctor.js +16 -9
- package/dist/resources/extensions/gsd/error-classifier.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-state.js +16 -1
- package/dist/resources/extensions/gsd/gsd-db.js +12 -0
- package/dist/resources/extensions/gsd/guided-flow.js +36 -470
- package/dist/resources/extensions/gsd/guided-unit-completion.js +225 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +33 -33
- package/dist/resources/extensions/gsd/mcp-filter.js +8 -1
- package/dist/resources/extensions/gsd/mcp-tool-name.js +26 -0
- package/dist/resources/extensions/gsd/md-importer.js +4 -3
- package/dist/resources/extensions/gsd/migrate/safety.js +2 -2
- package/dist/resources/extensions/gsd/migration-auto-check.js +3 -2
- package/dist/resources/extensions/gsd/milestone-closeout-proof.js +72 -0
- package/dist/resources/extensions/gsd/milestone-closeout.js +12 -4
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +10 -0
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +156 -0
- package/dist/resources/extensions/gsd/milestone-readiness.js +77 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +50 -0
- package/dist/resources/extensions/gsd/milestone-validation-evidence.js +73 -0
- package/dist/resources/extensions/gsd/milestone-validation-verdict.js +57 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
- package/dist/resources/extensions/gsd/parallel-eligibility.js +3 -6
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -2
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +67 -0
- package/dist/resources/extensions/gsd/preferences.js +147 -29
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/dist/resources/extensions/gsd/provider-payload-policy.js +83 -0
- package/dist/resources/extensions/gsd/pull-request-process.js +13 -0
- package/dist/resources/extensions/gsd/quality-gate-closure.js +109 -0
- package/dist/resources/extensions/gsd/question-transport.js +86 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +8 -2
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +3 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/state.js +13 -5
- package/dist/resources/extensions/gsd/templates/plan.md +7 -0
- package/dist/resources/extensions/gsd/templates/project.md +1 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +1 -1
- package/dist/resources/extensions/gsd/templates/uat.md +5 -1
- package/dist/resources/extensions/gsd/tool-contract.js +52 -8
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +15 -34
- package/dist/resources/extensions/gsd/tool-surface-snapshot.js +17 -0
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +15 -143
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +39 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +15 -78
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
- package/dist/resources/extensions/gsd/uat-policy.js +16 -10
- package/dist/resources/extensions/gsd/uat-run.js +9 -14
- package/dist/resources/extensions/gsd/unit-context-composer.js +40 -20
- package/dist/resources/extensions/gsd/unit-runtime.js +3 -2
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +2 -1
- package/dist/resources/extensions/gsd/user-input-boundary.js +65 -4
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/web-app-uat.js +80 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +15 -102
- package/dist/resources/extensions/gsd/workflow-reconcile.js +4 -3
- package/dist/resources/extensions/gsd/workflow-tool-surface.js +46 -0
- package/dist/resources/extensions/gsd/workspace-git-guard.js +2 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +33 -4
- package/dist/resources/extensions/gsd/worktree-telemetry.js +12 -0
- package/dist/resources/extensions/shared/interview-ui.js +2 -2
- package/dist/resources/shared/claude-runtime-floor.js +182 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/update-cmd.js +20 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +8 -8
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.nft.json +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.nft.json +1 -1
- 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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +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.nft.json +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.nft.json +1 -1
- 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.nft.json +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 +5 -5
- package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2659.b7b129ee6a769448.js +1 -0
- package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{3616.4113d484a994e411.js → 3616.3c60753b8ffcbd2e.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/4283.e4873b058df143a1.js +2 -0
- package/dist/web/standalone/.next/static/chunks/5826.a46ecdd1cfe8dabc.js +1 -0
- package/dist/web/standalone/.next/static/chunks/796.cf859a427a2cb2ac.js +10 -0
- package/dist/web/standalone/.next/static/chunks/8785.2e5a118797fb2dd2.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-dda80a1ef5587410.js → webpack-fbea77b5f9953368.js} +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
- package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +20 -8
- package/package.json +17 -11
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +2 -0
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +14 -0
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +106 -40
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.js +6 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +10 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +8 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +41 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +2 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +8 -127
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +47 -166
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/components/input.js +1 -1
- package/packages/pi-tui/dist/components/input.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +39 -30
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +22 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/scripts/link-workspace-packages.cjs +7 -40
- package/src/resources/extensions/ask-user-questions.ts +87 -24
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +126 -289
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +242 -2
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +287 -0
- package/src/resources/extensions/github-sync/templates.ts +3 -3
- package/src/resources/extensions/github-sync/tests/templates.test.ts +2 -2
- package/src/resources/extensions/gsd/artifact-projection.ts +31 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +40 -121
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop.ts +83 -61
- package/src/resources/extensions/gsd/auto/orchestrator.ts +913 -64
- package/src/resources/extensions/gsd/auto/phases.ts +35 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -1
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +18 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +20 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +8 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +220 -9
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -50
- package/src/resources/extensions/gsd/auto-runtime-state.ts +30 -0
- package/src/resources/extensions/gsd/auto-start.ts +17 -20
- package/src/resources/extensions/gsd/auto-timers.ts +16 -2
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +40 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +42 -30
- package/src/resources/extensions/gsd/auto-verification.ts +7 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +57 -42
- package/src/resources/extensions/gsd/auto.ts +96 -508
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +29 -37
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -37
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +120 -151
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +107 -3
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +27 -5
- package/src/resources/extensions/gsd/codebase-generator.ts +9 -5
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +3 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +18 -0
- package/src/resources/extensions/gsd/commands-inspect.ts +7 -8
- package/src/resources/extensions/gsd/commands-maintenance.ts +74 -40
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-verdict.ts +19 -2
- package/src/resources/extensions/gsd/db-workspace.ts +170 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/delegation-policy.ts +3 -11
- package/src/resources/extensions/gsd/discussion-handoff.ts +276 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/doctor.ts +15 -5
- package/src/resources/extensions/gsd/error-classifier.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-state.ts +17 -1
- package/src/resources/extensions/gsd/gsd-db.ts +12 -0
- package/src/resources/extensions/gsd/guided-flow.ts +49 -560
- package/src/resources/extensions/gsd/guided-unit-completion.ts +275 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +40 -20
- package/src/resources/extensions/gsd/mcp-filter.ts +9 -1
- package/src/resources/extensions/gsd/mcp-tool-name.ts +35 -0
- package/src/resources/extensions/gsd/md-importer.ts +3 -3
- package/src/resources/extensions/gsd/migrate/safety.ts +2 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/milestone-closeout-proof.ts +131 -0
- package/src/resources/extensions/gsd/milestone-closeout.ts +12 -4
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +47 -0
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +224 -0
- package/src/resources/extensions/gsd/milestone-readiness.ts +125 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +81 -0
- package/src/resources/extensions/gsd/milestone-validation-evidence.ts +95 -0
- package/src/resources/extensions/gsd/milestone-validation-verdict.ts +80 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
- package/src/resources/extensions/gsd/parallel-eligibility.ts +4 -5
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -2
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +98 -0
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +173 -28
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/src/resources/extensions/gsd/provider-payload-policy.ts +140 -0
- package/src/resources/extensions/gsd/pull-request-process.ts +41 -0
- package/src/resources/extensions/gsd/quality-gate-closure.ts +140 -0
- package/src/resources/extensions/gsd/question-transport.ts +138 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +8 -2
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +6 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/state.ts +15 -5
- package/src/resources/extensions/gsd/templates/plan.md +7 -0
- package/src/resources/extensions/gsd/templates/project.md +1 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +1 -1
- package/src/resources/extensions/gsd/templates/uat.md +5 -1
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/ask-user-questions-render.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +321 -5
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +709 -845
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +34 -3
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +1 -5
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +1 -5
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/mcp-tool-name.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout-proof.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/milestone-readiness.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/milestone-validation-evidence.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/milestone-validation-verdict.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/planning-crossval.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/preferences-diagnostics.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +183 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/provider-payload-policy.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/pull-request-process.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +21 -6
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/tool-availability-audit.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +35 -42
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +126 -9
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/worktree-projection-writers.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +79 -0
- package/src/resources/extensions/gsd/tool-contract.ts +86 -8
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +16 -33
- package/src/resources/extensions/gsd/tool-surface-snapshot.ts +47 -0
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +19 -160
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +43 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +25 -84
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
- package/src/resources/extensions/gsd/uat-policy.ts +19 -10
- package/src/resources/extensions/gsd/uat-run.ts +10 -14
- package/src/resources/extensions/gsd/unit-context-composer.ts +85 -20
- package/src/resources/extensions/gsd/unit-runtime.ts +3 -2
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +2 -1
- package/src/resources/extensions/gsd/user-input-boundary.ts +55 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/web-app-uat.ts +101 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +22 -110
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +73 -0
- package/src/resources/extensions/gsd/workspace-git-guard.ts +1 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +7 -16
- package/src/resources/extensions/gsd/worktree-state-projection.ts +55 -7
- package/src/resources/extensions/gsd/worktree-telemetry.ts +16 -0
- package/src/resources/extensions/shared/interview-ui.ts +15 -2
- package/src/resources/shared/claude-runtime-floor.ts +248 -0
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- package/dist/web/standalone/.next/static/chunks/2659.feb6499ca863ebfc.js +0 -1
- package/dist/web/standalone/.next/static/chunks/2772.151789db0edea835.js +0 -1
- package/dist/web/standalone/.next/static/chunks/4283.10a065467b5340d8.js +0 -2
- package/dist/web/standalone/.next/static/chunks/5826.960dc4634cc9b0d3.js +0 -1
- package/dist/web/standalone/.next/static/chunks/796.46f811c0fac23aab.js +0 -10
- package/dist/web/standalone/.next/static/chunks/8785.d32f7a61f55c1600.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
- package/scripts/ensure-workspace-builds.cjs +0 -129
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → tkLHUSzPA2kMmWz4DmGwI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → tkLHUSzPA2kMmWz4DmGwI}/_ssgManifest.js +0 -0
|
@@ -1,8 +1,71 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Auto Orchestration module implementation and ADR-015 invariant pipeline owner.
|
|
3
|
+
//
|
|
4
|
+
// Phase 2 of #442 collapsed the nine single-implementation adapter seams
|
|
5
|
+
// (DispatchAdapter, RecoveryAdapter, StateReconciliationAdapter,
|
|
6
|
+
// ToolContractAdapter, WorktreeAdapter, HealthAdapter, UokGateAdapter,
|
|
7
|
+
// RuntimePersistenceAdapter, NotificationAdapter) into this class. The
|
|
8
|
+
// orchestrator now constructs from the concrete extension context and calls
|
|
9
|
+
// the real collaborators (state-reconciliation, doctor-proactive,
|
|
10
|
+
// auto-dispatch, recovery-classification, tool-contract, worktree-safety,
|
|
11
|
+
// uok/gate-runner, journal, session-lock, ctx.ui.notify) directly.
|
|
3
12
|
|
|
4
|
-
import type {
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
import type { AutoAdvanceResult, AutoOrchestrationModule, AutoSessionContext, AutoStatus, AutoTerminalOutcome } from "./contracts.js";
|
|
16
|
+
import type { AutoSession, PendingOrchestrationDispatch } from "./session.js";
|
|
5
17
|
import type { GSDState } from "../types.js";
|
|
18
|
+
import type { MinimalModelRegistry } from "../context-budget.js";
|
|
19
|
+
|
|
20
|
+
type BlockedAdvanceResult = Extract<AutoAdvanceResult, { kind: "blocked" }>;
|
|
21
|
+
|
|
22
|
+
import { debugCount, debugTime } from "../debug-logger.js";
|
|
23
|
+
import { reconcileBeforeDispatch } from "../state-reconciliation.js";
|
|
24
|
+
import { resolveDispatch } from "../auto-dispatch.js";
|
|
25
|
+
import { classifyFailure } from "../recovery-classification.js";
|
|
26
|
+
import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
27
|
+
import { invalidateAllCaches } from "../cache.js";
|
|
28
|
+
import { compileUnitToolContract } from "../tool-contract.js";
|
|
29
|
+
import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
30
|
+
import { repairAutoWorktreeSafetyFailure } from "../auto-worktree-repair.js";
|
|
31
|
+
import { resolveManifest } from "../unit-context-manifest.js";
|
|
32
|
+
import {
|
|
33
|
+
preDispatchHealthGate,
|
|
34
|
+
recordHealthSnapshot,
|
|
35
|
+
} from "../doctor-proactive.js";
|
|
36
|
+
import { checkResourcesStale, autoWorktreeBranch, mergeMilestoneToMain } from "../auto-worktree.js";
|
|
37
|
+
import { getSessionLockStatus } from "../session-lock.js";
|
|
38
|
+
import { resolveUokFlags } from "../uok/flags.js";
|
|
39
|
+
import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
|
|
40
|
+
import { loadEffectiveGSDPreferences, getIsolationMode } from "../preferences.js";
|
|
41
|
+
import {
|
|
42
|
+
detectWorktreeName,
|
|
43
|
+
getMainBranch,
|
|
44
|
+
resolveProjectRoot,
|
|
45
|
+
resolveWorktreeProjectRoot,
|
|
46
|
+
} from "../worktree.js";
|
|
47
|
+
import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
|
48
|
+
import { GitServiceImpl } from "../git-service.js";
|
|
49
|
+
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
50
|
+
import { WorktreeLifecycle } from "../worktree-lifecycle.js";
|
|
51
|
+
import { createMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
|
|
52
|
+
import { createWorkspace, scopeMilestone } from "../workspace.js";
|
|
53
|
+
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
54
|
+
import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
|
|
55
|
+
import { deriveState } from "../state.js";
|
|
56
|
+
import { parseUnitId } from "../unit-id.js";
|
|
57
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
58
|
+
import {
|
|
59
|
+
isDbAvailable,
|
|
60
|
+
getSlice,
|
|
61
|
+
getTask,
|
|
62
|
+
} from "../gsd-db.js";
|
|
63
|
+
import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
|
|
64
|
+
import { getErrorMessage } from "../error-utils.js";
|
|
65
|
+
import { logWarning } from "../workflow-logger.js";
|
|
66
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
67
|
+
import { join } from "node:path";
|
|
68
|
+
import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
|
|
6
69
|
|
|
7
70
|
function now(): number {
|
|
8
71
|
return Date.now();
|
|
@@ -18,11 +81,238 @@ function now(): number {
|
|
|
18
81
|
*/
|
|
19
82
|
export const STUCK_WINDOW_SIZE = 6;
|
|
20
83
|
|
|
21
|
-
function
|
|
84
|
+
function noRemainingUnitsOutcome(stateSnapshot: GSDState): AutoTerminalOutcome {
|
|
22
85
|
if (stateSnapshot.phase === "complete") {
|
|
23
|
-
return
|
|
86
|
+
return {
|
|
87
|
+
code: "all-complete",
|
|
88
|
+
displayReason: "All milestones complete",
|
|
89
|
+
allMilestonesComplete: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
code: "no-remaining-units",
|
|
94
|
+
displayReason: "No remaining units",
|
|
95
|
+
allMilestonesComplete: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Concrete construction context for the Auto Orchestrator.
|
|
101
|
+
*
|
|
102
|
+
* Phase 2 of #442 replaced the nine adapter interfaces with this bundle of the
|
|
103
|
+
* real values the wiring factory used to close over: the extension context and
|
|
104
|
+
* API, the dispatch/runtime base paths, and the shared {@link AutoSession}
|
|
105
|
+
* singleton.
|
|
106
|
+
*/
|
|
107
|
+
export interface OrchestratorContext {
|
|
108
|
+
ctx: ExtensionContext;
|
|
109
|
+
pi: ExtensionAPI;
|
|
110
|
+
dispatchBasePath: string;
|
|
111
|
+
runtimeBasePath: string;
|
|
112
|
+
session: AutoSession;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Result type of a single dispatch decision. */
|
|
116
|
+
export type DispatchDecision =
|
|
117
|
+
| { kind: "blocked"; reason: string; action: "pause" | "stop" }
|
|
118
|
+
| { kind: "skipped"; reason: string }
|
|
119
|
+
| { unitType: string; unitId: string; reason: string; preconditions: string[] }
|
|
120
|
+
| null;
|
|
121
|
+
|
|
122
|
+
/** Inputs to a dispatch decision. Caller-supplied fields override ctx-derived ones. */
|
|
123
|
+
export interface DispatchDecisionInput {
|
|
124
|
+
stateSnapshot: GSDState;
|
|
125
|
+
/** Optional live session context, forwarded to dispatch rules that need session-derived state. */
|
|
126
|
+
session?: AutoSession;
|
|
127
|
+
/** Mirrors `DispatchContext.structuredQuestionsAvailable` — "true"/"false" string per the dispatch contract. */
|
|
128
|
+
structuredQuestionsAvailable?: "true" | "false";
|
|
129
|
+
/** Session model context window in tokens, forwarded to the budget engine. */
|
|
130
|
+
sessionContextWindow?: number;
|
|
131
|
+
/** Session model provider, used for provider-specific effective context windows. */
|
|
132
|
+
sessionProvider?: string;
|
|
133
|
+
/** Model registry for executor-model lookups inside the budget engine. */
|
|
134
|
+
modelRegistry?: MinimalModelRegistry;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getAlreadyClosedDispatchReason(unitType: string, unitId: string): string | null {
|
|
138
|
+
if (!isDbAvailable()) return null;
|
|
139
|
+
refreshWorkflowDatabaseFromDisk();
|
|
140
|
+
const { milestone, slice, task } = parseUnitId(unitId);
|
|
141
|
+
if (unitType === "execute-task" && milestone && slice && task) {
|
|
142
|
+
const row = getTask(milestone, slice, task);
|
|
143
|
+
return row && isClosedStatus(row.status)
|
|
144
|
+
? `execute-task ${unitId} is already ${row.status}`
|
|
145
|
+
: null;
|
|
146
|
+
}
|
|
147
|
+
if (unitType === "complete-slice" && milestone && slice) {
|
|
148
|
+
const row = getSlice(milestone, slice);
|
|
149
|
+
return row && isClosedStatus(row.status)
|
|
150
|
+
? `complete-slice ${unitId} is already ${row.status}`
|
|
151
|
+
: null;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function shouldAdoptActiveMilestone(
|
|
157
|
+
state: GSDState,
|
|
158
|
+
activeSession: AutoSession | undefined,
|
|
159
|
+
activeDispatchBasePath: string,
|
|
160
|
+
): boolean {
|
|
161
|
+
const activeMilestoneId = state.activeMilestone?.id;
|
|
162
|
+
const currentMilestoneId = activeSession?.currentMilestoneId;
|
|
163
|
+
if (!activeSession || !activeMilestoneId || !currentMilestoneId || activeMilestoneId === currentMilestoneId) {
|
|
164
|
+
return false;
|
|
24
165
|
}
|
|
25
|
-
|
|
166
|
+
|
|
167
|
+
const scopedWorktreeMilestone =
|
|
168
|
+
(activeSession.basePath ? detectWorktreeName(activeSession.basePath) : null) ??
|
|
169
|
+
detectWorktreeName(activeDispatchBasePath);
|
|
170
|
+
if (scopedWorktreeMilestone && scopedWorktreeMilestone !== activeMilestoneId) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const currentMilestone = state.registry.find((milestone) => milestone.id === currentMilestoneId);
|
|
175
|
+
return !!currentMilestone && isClosedStatus(currentMilestone.status);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Pure dispatch-decision function — formerly `createWiredDispatchAdapter`'s
|
|
180
|
+
* `decideNextUnit`. Folded out of the closure so the orchestrator can call it
|
|
181
|
+
* directly and tests can drive the exact dispatch decision logic against real
|
|
182
|
+
* fixtures without re-introducing an adapter seam.
|
|
183
|
+
*
|
|
184
|
+
* Derives session-derived dispatch inputs the same way phases.ts:runDispatch
|
|
185
|
+
* does (#5789): prefers caller-supplied values when present so test harnesses
|
|
186
|
+
* and alternative wirings can inject deterministic snapshots; otherwise pulls
|
|
187
|
+
* from the captured pi/ctx references.
|
|
188
|
+
*/
|
|
189
|
+
export async function decideOrchestratorDispatch(
|
|
190
|
+
ctx: ExtensionContext,
|
|
191
|
+
pi: ExtensionAPI,
|
|
192
|
+
dispatchBasePath: string,
|
|
193
|
+
session: AutoSession | undefined,
|
|
194
|
+
input: DispatchDecisionInput,
|
|
195
|
+
): Promise<DispatchDecision> {
|
|
196
|
+
const state = input.stateSnapshot;
|
|
197
|
+
const active = state.activeMilestone;
|
|
198
|
+
if (!active) return null;
|
|
199
|
+
|
|
200
|
+
const activeSession = input.session ?? session;
|
|
201
|
+
const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
|
|
202
|
+
if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
|
|
203
|
+
activeSession.currentMilestoneId = active.id;
|
|
204
|
+
}
|
|
205
|
+
const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
|
|
206
|
+
|
|
207
|
+
// Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
|
|
208
|
+
// (#5789). Prefer caller-supplied values when present so test harnesses and
|
|
209
|
+
// alternative wirings can inject deterministic snapshots; otherwise pull from
|
|
210
|
+
// the captured pi/ctx references.
|
|
211
|
+
const sessionProvider = input.sessionProvider ?? ctx.model?.provider;
|
|
212
|
+
const sessionContextWindow = input.sessionContextWindow ?? ctx.model?.contextWindow;
|
|
213
|
+
const modelRegistry = input.modelRegistry ?? (ctx.modelRegistry as MinimalModelRegistry | undefined);
|
|
214
|
+
const authMode =
|
|
215
|
+
sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
216
|
+
? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
|
|
217
|
+
: undefined;
|
|
218
|
+
// Use baseline snapshot — same reason as phases.ts:runDispatch: the live
|
|
219
|
+
// active set may be narrowed by the prior unit before selectAndApplyModel
|
|
220
|
+
// restores it, causing false transport-preflight failures (#477 follow-up).
|
|
221
|
+
const activeTools = getToolBaselineSnapshot(pi);
|
|
222
|
+
const registeredTools = getRegisteredToolSnapshot(pi);
|
|
223
|
+
// Mirrors runDispatch: deep-planning keeps approval gates in plain chat
|
|
224
|
+
// because structured questions can be cancelled outside the chat turn on
|
|
225
|
+
// some transports.
|
|
226
|
+
const structuredQuestionsAvailable =
|
|
227
|
+
input.structuredQuestionsAvailable ??
|
|
228
|
+
(prefs?.planning_depth === "deep"
|
|
229
|
+
? "false"
|
|
230
|
+
: supportsStructuredQuestions(activeTools, {
|
|
231
|
+
authMode,
|
|
232
|
+
baseUrl: ctx.model?.baseUrl,
|
|
233
|
+
})
|
|
234
|
+
? "true"
|
|
235
|
+
: "false");
|
|
236
|
+
|
|
237
|
+
const pendingRetry = session?.pendingVerificationRetryDispatch;
|
|
238
|
+
if (session && pendingRetry) {
|
|
239
|
+
session.pendingVerificationRetryDispatch = null;
|
|
240
|
+
const alreadyClosedReason = getAlreadyClosedDispatchReason(
|
|
241
|
+
pendingRetry.unitType,
|
|
242
|
+
pendingRetry.unitId,
|
|
243
|
+
);
|
|
244
|
+
if (alreadyClosedReason) {
|
|
245
|
+
session.pendingOrchestrationDispatch = null;
|
|
246
|
+
session.pendingVerificationRetry = null;
|
|
247
|
+
return { kind: "skipped", reason: alreadyClosedReason };
|
|
248
|
+
}
|
|
249
|
+
session.pendingOrchestrationDispatch = pendingRetry;
|
|
250
|
+
return {
|
|
251
|
+
unitType: pendingRetry.unitType,
|
|
252
|
+
unitId: pendingRetry.unitId,
|
|
253
|
+
reason: "verification-retry",
|
|
254
|
+
preconditions: [],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const action = await resolveDispatch({
|
|
259
|
+
basePath: activeDispatchBasePath,
|
|
260
|
+
mid: active.id,
|
|
261
|
+
midTitle: active.title,
|
|
262
|
+
state,
|
|
263
|
+
prefs,
|
|
264
|
+
session: activeSession,
|
|
265
|
+
structuredQuestionsAvailable,
|
|
266
|
+
sessionContextWindow,
|
|
267
|
+
sessionProvider,
|
|
268
|
+
modelRegistry,
|
|
269
|
+
activeTools,
|
|
270
|
+
registeredTools,
|
|
271
|
+
sessionAuthMode: authMode,
|
|
272
|
+
sessionBaseUrl: ctx.model?.baseUrl,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (action.action === "stop") {
|
|
276
|
+
if (session) session.pendingOrchestrationDispatch = null;
|
|
277
|
+
return {
|
|
278
|
+
kind: "blocked",
|
|
279
|
+
reason: action.reason,
|
|
280
|
+
action: action.level === "warning" ? "pause" : "stop",
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (action.action !== "dispatch") {
|
|
284
|
+
if (session) session.pendingOrchestrationDispatch = null;
|
|
285
|
+
return {
|
|
286
|
+
kind: "skipped",
|
|
287
|
+
reason: action.matchedRule ?? "dispatch-skip",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const alreadyClosedReason = getAlreadyClosedDispatchReason(action.unitType, action.unitId);
|
|
291
|
+
if (alreadyClosedReason) {
|
|
292
|
+
if (session) {
|
|
293
|
+
session.pendingOrchestrationDispatch = null;
|
|
294
|
+
session.pendingVerificationRetry = null;
|
|
295
|
+
}
|
|
296
|
+
return { kind: "skipped", reason: alreadyClosedReason };
|
|
297
|
+
}
|
|
298
|
+
if (session) {
|
|
299
|
+
const pending: PendingOrchestrationDispatch = {
|
|
300
|
+
unitType: action.unitType,
|
|
301
|
+
unitId: action.unitId,
|
|
302
|
+
prompt: action.prompt,
|
|
303
|
+
pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
|
|
304
|
+
state,
|
|
305
|
+
mid: active.id,
|
|
306
|
+
midTitle: active.title,
|
|
307
|
+
};
|
|
308
|
+
session.pendingOrchestrationDispatch = pending;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
unitType: action.unitType,
|
|
312
|
+
unitId: action.unitId,
|
|
313
|
+
reason: action.matchedRule ?? "dispatch",
|
|
314
|
+
preconditions: [],
|
|
315
|
+
};
|
|
26
316
|
}
|
|
27
317
|
|
|
28
318
|
export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
@@ -30,33 +320,489 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
30
320
|
phase: "idle",
|
|
31
321
|
transitionCount: 0,
|
|
32
322
|
};
|
|
33
|
-
private readonly
|
|
323
|
+
private readonly ctx: ExtensionContext;
|
|
324
|
+
private readonly pi: ExtensionAPI;
|
|
325
|
+
private readonly dispatchBasePath: string;
|
|
326
|
+
private readonly runtimeBasePath: string;
|
|
327
|
+
private readonly s: AutoSession;
|
|
328
|
+
private readonly flowId: string;
|
|
329
|
+
private seq = 0;
|
|
34
330
|
private lastAdvanceKey: string | null = null;
|
|
35
331
|
private lastFinalizedUnitKey: string | null = null;
|
|
36
332
|
private dispatchKeyWindow: string[] = [];
|
|
333
|
+
// #442: the unit key we last attempted graduated stuck-recovery for. Bounds
|
|
334
|
+
// recovery to one attempt per stuck episode per run (reset on start/resume/
|
|
335
|
+
// stop), mirroring the legacy Level-1-then-Level-2 escalation in phases.ts.
|
|
336
|
+
private lastStuckRecoveryKey: string | null = null;
|
|
337
|
+
|
|
338
|
+
public constructor(context: OrchestratorContext) {
|
|
339
|
+
this.ctx = context.ctx;
|
|
340
|
+
this.pi = context.pi;
|
|
341
|
+
this.dispatchBasePath = context.dispatchBasePath;
|
|
342
|
+
this.runtimeBasePath = context.runtimeBasePath;
|
|
343
|
+
this.s = context.session;
|
|
344
|
+
this.flowId = `auto-orchestrator-${Date.now()}`;
|
|
345
|
+
}
|
|
37
346
|
|
|
38
|
-
|
|
39
|
-
|
|
347
|
+
// ── Live base-path resolution (was the wiring factory's getLiveDispatchBasePath) ──
|
|
348
|
+
|
|
349
|
+
private getLiveDispatchBasePath(): string {
|
|
350
|
+
return resolveLiveOrchestratorBasePath({
|
|
351
|
+
capturedBasePath: this.dispatchBasePath,
|
|
352
|
+
runtimeBasePath: this.runtimeBasePath,
|
|
353
|
+
sessionBasePath: this.s.basePath,
|
|
354
|
+
originalBasePath: this.s.originalBasePath,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── RuntimePersistenceAdapter (folded) ───────────────────────────────────
|
|
359
|
+
|
|
360
|
+
private ensureLockOwnership(): void {
|
|
361
|
+
const status = getSessionLockStatus(this.runtimeBasePath);
|
|
362
|
+
if (!status.valid || status.failureReason === "pid-mismatch") {
|
|
363
|
+
throw new Error("session lock held by another process");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Map an orchestrator lifecycle event name to its journal eventType and emit
|
|
369
|
+
* it. The name→eventType ternary is preserved byte-for-byte from the legacy
|
|
370
|
+
* wired RuntimePersistenceAdapter.journalTransition.
|
|
371
|
+
*/
|
|
372
|
+
private journalTransition(event: {
|
|
373
|
+
name: string;
|
|
374
|
+
reason?: string;
|
|
375
|
+
unitType?: string;
|
|
376
|
+
unitId?: string;
|
|
377
|
+
}): void {
|
|
378
|
+
const eventType = event.name === "start"
|
|
379
|
+
? "orchestrator-iteration-start"
|
|
380
|
+
: event.name === "resume"
|
|
381
|
+
? "orchestrator-iteration-start"
|
|
382
|
+
: event.name === "advance"
|
|
383
|
+
? "orchestrator-dispatch-match"
|
|
384
|
+
: event.name === "advance-blocked"
|
|
385
|
+
? "orchestrator-guard-block"
|
|
386
|
+
: event.name === "advance-stopped"
|
|
387
|
+
? "orchestrator-dispatch-stop"
|
|
388
|
+
: event.name === "advance-error"
|
|
389
|
+
? "orchestrator-iteration-end"
|
|
390
|
+
: event.name === "advance-paused" || event.name === "advance-retry"
|
|
391
|
+
? "orchestrator-guard-block"
|
|
392
|
+
: event.name === "stop"
|
|
393
|
+
? "orchestrator-terminal"
|
|
394
|
+
: "orchestrator-iteration-end";
|
|
395
|
+
|
|
396
|
+
_emitJournalEvent(this.runtimeBasePath, {
|
|
397
|
+
ts: new Date().toISOString(),
|
|
398
|
+
flowId: this.flowId,
|
|
399
|
+
seq: ++this.seq,
|
|
400
|
+
eventType,
|
|
401
|
+
data: {
|
|
402
|
+
source: "auto-orchestrator",
|
|
403
|
+
name: event.name,
|
|
404
|
+
reason: event.reason,
|
|
405
|
+
unitType: event.unitType,
|
|
406
|
+
unitId: event.unitId,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── NotificationAdapter (folded) ─────────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
private notifyLifecycle(event: { name: string; detail?: string }): void {
|
|
414
|
+
if (event.name === "error") {
|
|
415
|
+
this.ctx.ui.notify(event.detail ?? "auto orchestration error", "error");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── HealthAdapter (folded) ───────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
private checkResourcesStale(): string | null {
|
|
422
|
+
return checkResourcesStale(this.s.resourceVersionOnStart);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private async preAdvanceGate(): Promise<
|
|
426
|
+
| { kind: "pass"; fixesApplied?: readonly string[] }
|
|
427
|
+
| { kind: "fail"; reason: string; action?: "pause" | "stop" }
|
|
428
|
+
| { kind: "threw"; error: unknown }
|
|
429
|
+
> {
|
|
430
|
+
try {
|
|
431
|
+
const gate = await preDispatchHealthGate(this.getLiveDispatchBasePath());
|
|
432
|
+
if (gate.proceed) {
|
|
433
|
+
return {
|
|
434
|
+
kind: "pass",
|
|
435
|
+
fixesApplied: gate.fixesApplied,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
kind: "fail",
|
|
440
|
+
reason: gate.reason ?? "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
441
|
+
action: gate.severity ?? "pause",
|
|
442
|
+
};
|
|
443
|
+
} catch (error) {
|
|
444
|
+
return { kind: "threw", error };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private postAdvanceRecord(result: AutoAdvanceResult): void {
|
|
449
|
+
if (result.kind === "error") {
|
|
450
|
+
recordHealthSnapshot(1, 0, 0, [{
|
|
451
|
+
code: "orchestration-error",
|
|
452
|
+
message: result.reason ?? "orchestration error",
|
|
453
|
+
severity: "error",
|
|
454
|
+
unitId: "orchestration",
|
|
455
|
+
}], [], "orchestration");
|
|
456
|
+
} else if (result.kind === "blocked") {
|
|
457
|
+
recordHealthSnapshot(0, 1, 0, [{
|
|
458
|
+
code: "orchestration-blocked",
|
|
459
|
+
message: result.reason ?? "orchestration blocked",
|
|
460
|
+
severity: "warning",
|
|
461
|
+
unitId: "orchestration",
|
|
462
|
+
}], [], "orchestration");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── UokGateAdapter (folded) ──────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
private async emitUokGate(input: {
|
|
469
|
+
gateId: string;
|
|
470
|
+
gateType: "policy" | "execution";
|
|
471
|
+
outcome: "pass" | "fail" | "manual-attention";
|
|
472
|
+
failureClass: "none" | "policy" | "manual-attention";
|
|
473
|
+
rationale: string;
|
|
474
|
+
findings?: string;
|
|
475
|
+
milestoneId?: string;
|
|
476
|
+
}): Promise<void> {
|
|
477
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
478
|
+
const prefs = loadEffectiveGSDPreferences(activeBasePath)?.preferences;
|
|
479
|
+
const uokFlags = resolveUokFlags(prefs);
|
|
480
|
+
if (!uokFlags.gates) return;
|
|
481
|
+
const milestoneId = input.milestoneId ?? this.s.currentMilestoneId ?? undefined;
|
|
482
|
+
try {
|
|
483
|
+
const { UokGateRunner } = await import("../uok/gate-runner.js");
|
|
484
|
+
const runner = new UokGateRunner();
|
|
485
|
+
runner.register({
|
|
486
|
+
id: input.gateId,
|
|
487
|
+
type: input.gateType,
|
|
488
|
+
execute: async () => ({
|
|
489
|
+
outcome: input.outcome,
|
|
490
|
+
failureClass: input.failureClass,
|
|
491
|
+
rationale: input.rationale,
|
|
492
|
+
findings: input.findings ?? "",
|
|
493
|
+
}),
|
|
494
|
+
});
|
|
495
|
+
await runner.run(input.gateId, {
|
|
496
|
+
basePath: activeBasePath,
|
|
497
|
+
traceId: `pre-dispatch:${this.flowId}`,
|
|
498
|
+
turnId: `orch-${this.seq}`,
|
|
499
|
+
milestoneId,
|
|
500
|
+
unitType: "pre-dispatch",
|
|
501
|
+
unitId: `orch-${this.seq}`,
|
|
502
|
+
});
|
|
503
|
+
} catch (err) {
|
|
504
|
+
logWarning("engine", `uok gate emit failed: ${getErrorMessage(err)}`, {
|
|
505
|
+
file: "orchestrator.ts",
|
|
506
|
+
gateId: input.gateId,
|
|
507
|
+
gateType: input.gateType,
|
|
508
|
+
...(milestoneId ? { milestoneId } : {}),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ── StateReconciliationAdapter (folded) ──────────────────────────────────
|
|
514
|
+
|
|
515
|
+
private async reconcileBeforeDispatch(): Promise<
|
|
516
|
+
{ ok: true; reason: string; stateSnapshot?: GSDState }
|
|
517
|
+
| { ok: false; reason: string; stateSnapshot?: GSDState }
|
|
518
|
+
> {
|
|
519
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
520
|
+
const result = await reconcileBeforeDispatch(activeBasePath);
|
|
521
|
+
// Failure-path summaries written by gsd_summary_save create
|
|
522
|
+
// artifact-db-status-divergence blockers for tasks that are still
|
|
523
|
+
// pending (gsd_task_complete never ran). These tasks can still be
|
|
524
|
+
// dispatched and the drift self-heals once they complete successfully.
|
|
525
|
+
const hardBlockers = result.blockers.filter(
|
|
526
|
+
(b) =>
|
|
527
|
+
!b.includes("has SUMMARY artifact while DB status is") &&
|
|
528
|
+
!b.includes("has SUMMARY on disk while DB status is") &&
|
|
529
|
+
!b.includes("has task SUMMARY artifacts but no DB tasks"),
|
|
530
|
+
);
|
|
531
|
+
if (hardBlockers.length > 0) {
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
reason: hardBlockers[0],
|
|
535
|
+
stateSnapshot: result.stateSnapshot,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const repairedKinds = result.repaired.map((d) => d.kind);
|
|
539
|
+
return {
|
|
540
|
+
ok: true,
|
|
541
|
+
reason:
|
|
542
|
+
repairedKinds.length > 0
|
|
543
|
+
? `repaired: ${repairedKinds.join(", ")}`
|
|
544
|
+
: "clean",
|
|
545
|
+
stateSnapshot: result.stateSnapshot,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ── DispatchAdapter (folded) ─────────────────────────────────────────────
|
|
550
|
+
|
|
551
|
+
private decideNextUnit(input: DispatchDecisionInput): Promise<DispatchDecision> {
|
|
552
|
+
return decideOrchestratorDispatch(this.ctx, this.pi, this.dispatchBasePath, this.s, input);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private evaluateNoRemainingUnitsSettlement(stateSnapshot: GSDState): BlockedAdvanceResult | null {
|
|
556
|
+
const settlement = evaluateAllCompleteSettlement({
|
|
557
|
+
milestoneId: this.s.currentMilestoneId ?? stateSnapshot.activeMilestone?.id,
|
|
558
|
+
statePhase: stateSnapshot.phase,
|
|
559
|
+
basePath: this.s.basePath || this.getLiveDispatchBasePath(),
|
|
560
|
+
originalBasePath: this.s.originalBasePath || this.runtimeBasePath,
|
|
561
|
+
milestoneMerged: this.s.milestoneMergedInPhases,
|
|
562
|
+
});
|
|
563
|
+
this.s.milestoneSettlement = settlement;
|
|
564
|
+
if (settlement.ok) return null;
|
|
565
|
+
return {
|
|
566
|
+
kind: "blocked",
|
|
567
|
+
reason: settlement.message,
|
|
568
|
+
action: settlement.action,
|
|
569
|
+
stateSnapshot,
|
|
570
|
+
terminalOutcome: {
|
|
571
|
+
code: "settlement-blocked",
|
|
572
|
+
displayReason: settlement.message,
|
|
573
|
+
nextAction: settlement.nextAction,
|
|
574
|
+
milestoneId: settlement.milestoneId,
|
|
575
|
+
allMilestonesComplete: false,
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private clearPendingDispatch(): void {
|
|
581
|
+
this.s.pendingOrchestrationDispatch = null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private findPriorSliceCompletionBlocker(unitType: string, unitId: string): string | null {
|
|
585
|
+
const guardBasePath = resolveWorktreeProjectRoot(
|
|
586
|
+
this.getLiveDispatchBasePath(),
|
|
587
|
+
this.s.originalBasePath,
|
|
588
|
+
);
|
|
589
|
+
let mainBranch = "main";
|
|
590
|
+
try {
|
|
591
|
+
mainBranch = getMainBranch(guardBasePath);
|
|
592
|
+
} catch (err) {
|
|
593
|
+
// Preserve legacy dispatch behavior: fall back to main when branch
|
|
594
|
+
// discovery fails, then let the guard make the progression decision.
|
|
595
|
+
logWarning(
|
|
596
|
+
"engine",
|
|
597
|
+
`branch discovery failed, falling back to main: ${getErrorMessage(err)}`,
|
|
598
|
+
{ file: "orchestrator.ts" },
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return getPriorSliceCompletionBlocker(guardBasePath, mainBranch, unitType, unitId);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ── ToolContractAdapter (folded) ─────────────────────────────────────────
|
|
605
|
+
|
|
606
|
+
private compileUnitToolContract(unitType: string): { ok: true; reason: string } | { ok: false; reason: string } {
|
|
607
|
+
const result = compileUnitToolContract(unitType);
|
|
608
|
+
if (!result.ok) return { ok: false, reason: result.detail };
|
|
609
|
+
return { ok: true, reason: result.contract.validationRules.join(", ") };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// ── WorktreeAdapter (folded) ─────────────────────────────────────────────
|
|
613
|
+
|
|
614
|
+
private getEffectiveUnitIsolationMode(basePath: string): ReturnType<typeof getIsolationMode> {
|
|
615
|
+
const configuredMode = getIsolationMode(basePath);
|
|
616
|
+
return configuredMode === "worktree" && this.s.isolationDegraded ? "branch" : configuredMode;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private buildLifecycle(): WorktreeLifecycle {
|
|
620
|
+
return new WorktreeLifecycle(this.s, {
|
|
621
|
+
gitServiceFactory: (basePath: string) => {
|
|
622
|
+
const gitConfig = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
623
|
+
return new GitServiceImpl(basePath, gitConfig);
|
|
624
|
+
},
|
|
625
|
+
worktreeProjection: new WorktreeStateProjection(),
|
|
626
|
+
mergeMilestoneToMain: createMilestoneMergeTransaction(mergeMilestoneToMain),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private rebuildScope(rawPath: string, milestoneId: string | null): void {
|
|
631
|
+
if (!milestoneId) {
|
|
632
|
+
this.s.scope = null;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
const workspace = createWorkspace(rawPath);
|
|
637
|
+
this.s.scope = scopeMilestone(workspace, milestoneId);
|
|
638
|
+
} catch {
|
|
639
|
+
// Non-fatal — scope is additive. Existing readers still use basePath.
|
|
640
|
+
this.s.scope = null;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private async prepareWorktreeForUnit(
|
|
645
|
+
unitType: string,
|
|
646
|
+
unitId: string,
|
|
647
|
+
): Promise<{ ok: true; reason: string } | { ok: false; reason: string }> {
|
|
648
|
+
const isolationMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);
|
|
649
|
+
const manifest = resolveManifest(unitType);
|
|
650
|
+
if (!manifest) {
|
|
651
|
+
return {
|
|
652
|
+
ok: false,
|
|
653
|
+
reason: `No Unit manifest is registered for ${unitType}`,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
if (isolationMode !== "worktree") {
|
|
657
|
+
return { ok: true, reason: "not-required" };
|
|
658
|
+
}
|
|
659
|
+
const writeScope =
|
|
660
|
+
manifest.tools.mode === "all" || manifest.tools.mode === "docs"
|
|
661
|
+
? "source-writing"
|
|
662
|
+
: "planning-only";
|
|
663
|
+
const safety = createWorktreeSafetyModule();
|
|
664
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
665
|
+
const snapshot = await deriveState(activeBasePath);
|
|
666
|
+
const milestoneId = snapshot.activeMilestone?.id ?? null;
|
|
667
|
+
const expectedBranch = milestoneId ? autoWorktreeBranch(milestoneId) : null;
|
|
668
|
+
let result = safety.validateUnitRoot({
|
|
669
|
+
unitType,
|
|
670
|
+
unitId,
|
|
671
|
+
writeScope,
|
|
672
|
+
projectRoot: this.runtimeBasePath,
|
|
673
|
+
unitRoot: activeBasePath,
|
|
674
|
+
milestoneId,
|
|
675
|
+
isolationMode,
|
|
676
|
+
expectedBranch,
|
|
677
|
+
});
|
|
678
|
+
if (!result.ok) {
|
|
679
|
+
const repaired = await repairAutoWorktreeSafetyFailure({
|
|
680
|
+
safetyResult: result,
|
|
681
|
+
projectRoot: this.runtimeBasePath,
|
|
682
|
+
activeRoot: activeBasePath,
|
|
683
|
+
milestoneId,
|
|
684
|
+
enterMilestone: async (id) => {
|
|
685
|
+
this.buildLifecycle().adoptSessionRoot(this.runtimeBasePath, this.s.originalBasePath || this.runtimeBasePath);
|
|
686
|
+
const enterResult = this.buildLifecycle().enterMilestone(id, {
|
|
687
|
+
notify: this.ctx.ui.notify.bind(this.ctx.ui),
|
|
688
|
+
});
|
|
689
|
+
if (!enterResult.ok) return { ok: false, reason: enterResult.reason };
|
|
690
|
+
this.rebuildScope(this.s.basePath, this.s.currentMilestoneId);
|
|
691
|
+
return { ok: true };
|
|
692
|
+
},
|
|
693
|
+
revalidate: () => safety.validateUnitRoot({
|
|
694
|
+
unitType,
|
|
695
|
+
unitId,
|
|
696
|
+
writeScope,
|
|
697
|
+
projectRoot: this.runtimeBasePath,
|
|
698
|
+
unitRoot: this.getLiveDispatchBasePath(),
|
|
699
|
+
milestoneId,
|
|
700
|
+
isolationMode: this.getEffectiveUnitIsolationMode(this.runtimeBasePath),
|
|
701
|
+
expectedBranch,
|
|
702
|
+
}),
|
|
703
|
+
});
|
|
704
|
+
result = repaired.result;
|
|
705
|
+
if (result.ok) {
|
|
706
|
+
return { ok: true, reason: repaired.repaired ? `repaired-${result.kind}` : result.kind };
|
|
707
|
+
}
|
|
708
|
+
const repairDetail = repaired.repairReason
|
|
709
|
+
? ` (repair skipped: ${repaired.repairReason})`
|
|
710
|
+
: "";
|
|
711
|
+
return { ok: false, reason: `${result.kind}: ${result.reason}${repairDetail}` };
|
|
712
|
+
}
|
|
713
|
+
return { ok: true, reason: result.kind };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ── RecoveryAdapter (folded) ─────────────────────────────────────────────
|
|
717
|
+
|
|
718
|
+
private classifyAndRecover(input: {
|
|
719
|
+
error: unknown;
|
|
720
|
+
unitType?: string;
|
|
721
|
+
unitId?: string;
|
|
722
|
+
}): { action: "retry" | "escalate" | "stop"; reason: string } {
|
|
723
|
+
const recovery = classifyFailure(input);
|
|
724
|
+
return { action: recovery.action, reason: recovery.reason };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ── Lifecycle verbs ──────────────────────────────────────────────────────
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* #442: graduated stuck recovery, ported from the legacy
|
|
731
|
+
* auto/phases.ts:runDispatch path that Phase 3 retires. The ring-buffer
|
|
732
|
+
* hard-stops (stuck-loop saturation and finalized-repeat) would otherwise
|
|
733
|
+
* KILL a unit that actually completed on disk but whose DB row is still
|
|
734
|
+
* stale. Before hard-stopping, verify the expected artifact exists; if so,
|
|
735
|
+
* refresh the DB from it, invalidate caches and reset the dispatch ring so
|
|
736
|
+
* the next advance picks the correct next unit. Bounded to one attempt per
|
|
737
|
+
* stuck key per episode (reset on lifecycle + genuine finalize) to avoid an
|
|
738
|
+
* unbounded recover→re-saturate→recover loop — mirrors the legacy
|
|
739
|
+
* Level-1-recover-then-Level-2-hard-stop escalation.
|
|
740
|
+
*
|
|
741
|
+
* Returns true when recovery succeeded; the caller should re-loop (return a
|
|
742
|
+
* skipped result) instead of stopping.
|
|
743
|
+
*/
|
|
744
|
+
private tryStuckArtifactRecovery(unitType: string, unitId: string): boolean {
|
|
745
|
+
const key = `${unitType}:${unitId}`;
|
|
746
|
+
if (this.lastStuckRecoveryKey === key) return false; // already tried this episode
|
|
747
|
+
const basePath = this.getLiveDispatchBasePath();
|
|
748
|
+
if (!verifyExpectedArtifact(unitType, unitId, basePath)) return false;
|
|
749
|
+
const refreshed = refreshRecoveryDbForArtifact(unitType, unitId, basePath);
|
|
750
|
+
// Fatal failures cannot be recovered — hard-stop. Non-fatal (e.g. plan-slice
|
|
751
|
+
// DB refresh hiccup) still fall through: invalidating caches and resetting
|
|
752
|
+
// the ring gives the next advance a clean slate to pick up the correct state,
|
|
753
|
+
// mirroring the legacy Level-1 "continue" escalation path.
|
|
754
|
+
if (!refreshed.ok && refreshed.fatal) return false;
|
|
755
|
+
this.lastStuckRecoveryKey = key;
|
|
756
|
+
invalidateAllCaches();
|
|
757
|
+
this.dispatchKeyWindow = [];
|
|
758
|
+
this.lastAdvanceKey = null;
|
|
759
|
+
this.lastFinalizedUnitKey = null;
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private stuckRecovered(
|
|
764
|
+
decision: { unitType: string; unitId: string },
|
|
765
|
+
stateSnapshot: GSDState,
|
|
766
|
+
): AutoAdvanceResult {
|
|
767
|
+
const recovered: AutoAdvanceResult = {
|
|
768
|
+
kind: "skipped",
|
|
769
|
+
reason: `stuck-recovery: ${decision.unitType} ${decision.unitId} artifact found on disk; DB refreshed`,
|
|
770
|
+
stateSnapshot,
|
|
771
|
+
};
|
|
772
|
+
this.status.phase = "running";
|
|
773
|
+
this.status.activeUnit = undefined;
|
|
774
|
+
this.bumpTransition();
|
|
775
|
+
this.journalTransition({
|
|
776
|
+
name: "advance-skipped",
|
|
777
|
+
reason: recovered.reason,
|
|
778
|
+
unitType: decision.unitType,
|
|
779
|
+
unitId: decision.unitId,
|
|
780
|
+
});
|
|
781
|
+
this.postAdvanceRecord(recovered);
|
|
782
|
+
return recovered;
|
|
40
783
|
}
|
|
41
784
|
|
|
42
785
|
public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
|
|
43
786
|
this.lastAdvanceKey = null;
|
|
44
787
|
this.lastFinalizedUnitKey = null;
|
|
45
788
|
this.dispatchKeyWindow = [];
|
|
789
|
+
this.lastStuckRecoveryKey = null;
|
|
46
790
|
this.status.phase = "running";
|
|
47
791
|
this.bumpTransition();
|
|
48
|
-
|
|
49
|
-
|
|
792
|
+
this.journalTransition({ name: "start" });
|
|
793
|
+
this.notifyLifecycle({ name: "start" });
|
|
50
794
|
return { kind: "started" };
|
|
51
795
|
}
|
|
52
796
|
|
|
53
797
|
public async advance(): Promise<AutoAdvanceResult> {
|
|
798
|
+
debugCount("dispatches");
|
|
799
|
+
const stopAdvanceTimer = debugTime("orchestrator-advance");
|
|
54
800
|
try {
|
|
55
|
-
|
|
801
|
+
this.ensureLockOwnership();
|
|
56
802
|
|
|
57
|
-
const staleMsg = this.
|
|
803
|
+
const staleMsg = this.checkResourcesStale();
|
|
58
804
|
if (staleMsg) {
|
|
59
|
-
await this.
|
|
805
|
+
await this.emitUokGate({
|
|
60
806
|
gateId: "resource-version-guard",
|
|
61
807
|
gateType: "policy",
|
|
62
808
|
outcome: "fail",
|
|
@@ -65,11 +811,11 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
65
811
|
findings: staleMsg,
|
|
66
812
|
});
|
|
67
813
|
const blocked: AutoAdvanceResult = { kind: "blocked", reason: staleMsg, action: "pause" };
|
|
68
|
-
|
|
69
|
-
|
|
814
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
815
|
+
this.postAdvanceRecord(blocked);
|
|
70
816
|
return blocked;
|
|
71
817
|
}
|
|
72
|
-
await this.
|
|
818
|
+
await this.emitUokGate({
|
|
73
819
|
gateId: "resource-version-guard",
|
|
74
820
|
gateType: "policy",
|
|
75
821
|
outcome: "pass",
|
|
@@ -77,9 +823,9 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
77
823
|
rationale: "resource version guard passed",
|
|
78
824
|
});
|
|
79
825
|
|
|
80
|
-
const gate = await this.
|
|
826
|
+
const gate = await this.preAdvanceGate();
|
|
81
827
|
if (gate.kind === "fail") {
|
|
82
|
-
await this.
|
|
828
|
+
await this.emitUokGate({
|
|
83
829
|
gateId: "pre-dispatch-health-gate",
|
|
84
830
|
gateType: "execution",
|
|
85
831
|
outcome: "manual-attention",
|
|
@@ -92,12 +838,12 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
92
838
|
reason: gate.reason,
|
|
93
839
|
action: gate.action ?? "pause",
|
|
94
840
|
};
|
|
95
|
-
|
|
96
|
-
|
|
841
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
842
|
+
this.postAdvanceRecord(blocked);
|
|
97
843
|
return blocked;
|
|
98
844
|
}
|
|
99
845
|
if (gate.kind === "threw") {
|
|
100
|
-
await this.
|
|
846
|
+
await this.emitUokGate({
|
|
101
847
|
gateId: "pre-dispatch-health-gate",
|
|
102
848
|
gateType: "execution",
|
|
103
849
|
outcome: "manual-attention",
|
|
@@ -107,7 +853,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
107
853
|
});
|
|
108
854
|
// intentional fall-through: matches runPreDispatch behaviour
|
|
109
855
|
} else {
|
|
110
|
-
await this.
|
|
856
|
+
await this.emitUokGate({
|
|
111
857
|
gateId: "pre-dispatch-health-gate",
|
|
112
858
|
gateType: "execution",
|
|
113
859
|
outcome: "pass",
|
|
@@ -117,7 +863,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
117
863
|
});
|
|
118
864
|
}
|
|
119
865
|
|
|
120
|
-
const reconciliation = await this.
|
|
866
|
+
const reconciliation = await this.reconcileBeforeDispatch();
|
|
121
867
|
if (!reconciliation.ok || !reconciliation.stateSnapshot) {
|
|
122
868
|
const blocked: AutoAdvanceResult = {
|
|
123
869
|
kind: "blocked",
|
|
@@ -125,25 +871,38 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
125
871
|
action: "pause",
|
|
126
872
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
127
873
|
};
|
|
128
|
-
|
|
129
|
-
|
|
874
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
875
|
+
this.postAdvanceRecord(blocked);
|
|
130
876
|
return blocked;
|
|
131
877
|
}
|
|
132
878
|
|
|
133
|
-
const decision = await this.
|
|
879
|
+
const decision = await this.decideNextUnit({ stateSnapshot: reconciliation.stateSnapshot });
|
|
134
880
|
if (!decision) {
|
|
881
|
+
const settlementBlock = this.evaluateNoRemainingUnitsSettlement(reconciliation.stateSnapshot);
|
|
882
|
+
if (settlementBlock) {
|
|
883
|
+
this.status.phase = "paused";
|
|
884
|
+
this.status.activeUnit = undefined;
|
|
885
|
+
this.lastAdvanceKey = null;
|
|
886
|
+
this.dispatchKeyWindow = [];
|
|
887
|
+
this.bumpTransition();
|
|
888
|
+
this.journalTransition({ name: "advance-blocked", reason: settlementBlock.reason });
|
|
889
|
+
this.postAdvanceRecord(settlementBlock);
|
|
890
|
+
return settlementBlock;
|
|
891
|
+
}
|
|
892
|
+
const terminalOutcome = noRemainingUnitsOutcome(reconciliation.stateSnapshot);
|
|
135
893
|
const stopped: AutoAdvanceResult = {
|
|
136
894
|
kind: "stopped",
|
|
137
|
-
reason:
|
|
895
|
+
reason: terminalOutcome.displayReason,
|
|
138
896
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
897
|
+
terminalOutcome,
|
|
139
898
|
};
|
|
140
899
|
this.status.phase = "stopped";
|
|
141
900
|
this.status.activeUnit = undefined;
|
|
142
901
|
this.lastAdvanceKey = null;
|
|
143
902
|
this.dispatchKeyWindow = [];
|
|
144
903
|
this.bumpTransition();
|
|
145
|
-
|
|
146
|
-
|
|
904
|
+
this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
|
|
905
|
+
this.postAdvanceRecord(stopped);
|
|
147
906
|
return stopped;
|
|
148
907
|
}
|
|
149
908
|
if ("kind" in decision && decision.kind === "skipped") {
|
|
@@ -155,8 +914,8 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
155
914
|
this.status.phase = "running";
|
|
156
915
|
this.status.activeUnit = undefined;
|
|
157
916
|
this.bumpTransition();
|
|
158
|
-
|
|
159
|
-
|
|
917
|
+
this.journalTransition({ name: "advance-skipped", reason: skipped.reason });
|
|
918
|
+
this.postAdvanceRecord(skipped);
|
|
160
919
|
return skipped;
|
|
161
920
|
}
|
|
162
921
|
if (!("unitType" in decision)) {
|
|
@@ -166,8 +925,27 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
166
925
|
action: decision.action,
|
|
167
926
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
168
927
|
};
|
|
169
|
-
|
|
170
|
-
|
|
928
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
929
|
+
this.postAdvanceRecord(blocked);
|
|
930
|
+
return blocked;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const priorSliceBlocker = this.findPriorSliceCompletionBlocker(decision.unitType, decision.unitId);
|
|
934
|
+
if (priorSliceBlocker) {
|
|
935
|
+
this.clearPendingDispatch();
|
|
936
|
+
const blocked: AutoAdvanceResult = {
|
|
937
|
+
kind: "blocked",
|
|
938
|
+
reason: priorSliceBlocker,
|
|
939
|
+
action: "stop",
|
|
940
|
+
stateSnapshot: reconciliation.stateSnapshot,
|
|
941
|
+
};
|
|
942
|
+
this.journalTransition({
|
|
943
|
+
name: "advance-blocked",
|
|
944
|
+
reason: blocked.reason,
|
|
945
|
+
unitType: decision.unitType,
|
|
946
|
+
unitId: decision.unitId,
|
|
947
|
+
});
|
|
948
|
+
this.postAdvanceRecord(blocked);
|
|
171
949
|
return blocked;
|
|
172
950
|
}
|
|
173
951
|
|
|
@@ -184,19 +962,27 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
184
962
|
|
|
185
963
|
const matchingCount = this.dispatchKeyWindow.filter((k) => k === nextKey).length;
|
|
186
964
|
if (this.lastFinalizedUnitKey === nextKey) {
|
|
965
|
+
// #442: the unit re-dispatched immediately after finalizing may have
|
|
966
|
+
// actually completed on disk with a stale DB. Verify + recover before
|
|
967
|
+
// hard-stopping (legacy graduated stuck-recovery parity).
|
|
968
|
+
if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
|
|
969
|
+
this.clearPendingDispatch();
|
|
970
|
+
return this.stuckRecovered(decision, reconciliation.stateSnapshot);
|
|
971
|
+
}
|
|
972
|
+
this.clearPendingDispatch();
|
|
187
973
|
const blocked: AutoAdvanceResult = {
|
|
188
974
|
kind: "blocked",
|
|
189
975
|
reason: `state did not advance after finalized ${decision.unitType} ${decision.unitId}`,
|
|
190
976
|
action: "stop",
|
|
191
977
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
192
978
|
};
|
|
193
|
-
|
|
979
|
+
this.journalTransition({
|
|
194
980
|
name: "advance-blocked",
|
|
195
981
|
reason: blocked.reason,
|
|
196
982
|
unitType: decision.unitType,
|
|
197
983
|
unitId: decision.unitId,
|
|
198
984
|
});
|
|
199
|
-
|
|
985
|
+
this.postAdvanceRecord(blocked);
|
|
200
986
|
return blocked;
|
|
201
987
|
}
|
|
202
988
|
|
|
@@ -207,14 +993,15 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
207
993
|
// checks coexist: idempotency for the common immediate-repeat case,
|
|
208
994
|
// stuck-loop for the saturated-window case.
|
|
209
995
|
if (this.lastAdvanceKey === nextKey && matchingCount < STUCK_WINDOW_SIZE) {
|
|
996
|
+
this.clearPendingDispatch();
|
|
210
997
|
const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active", action: "pause" };
|
|
211
|
-
|
|
998
|
+
this.journalTransition({
|
|
212
999
|
name: "advance-blocked",
|
|
213
1000
|
reason: blocked.reason,
|
|
214
1001
|
unitType: decision.unitType,
|
|
215
1002
|
unitId: decision.unitId,
|
|
216
1003
|
});
|
|
217
|
-
|
|
1004
|
+
this.postAdvanceRecord(blocked);
|
|
218
1005
|
return blocked;
|
|
219
1006
|
}
|
|
220
1007
|
|
|
@@ -223,54 +1010,64 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
223
1010
|
// picking the same unit across the whole window and must hard-stop with
|
|
224
1011
|
// a diagnosable reason.
|
|
225
1012
|
if (matchingCount >= STUCK_WINDOW_SIZE) {
|
|
1013
|
+
// #442: before declaring a stuck loop, verify the unit didn't actually
|
|
1014
|
+
// complete on disk (stale DB) and recover if so — legacy graduated
|
|
1015
|
+
// stuck-recovery parity. Otherwise hard-stop with a diagnosable reason.
|
|
1016
|
+
if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
|
|
1017
|
+
this.clearPendingDispatch();
|
|
1018
|
+
return this.stuckRecovered(decision, reconciliation.stateSnapshot);
|
|
1019
|
+
}
|
|
1020
|
+
this.clearPendingDispatch();
|
|
226
1021
|
const blocked: AutoAdvanceResult = {
|
|
227
1022
|
kind: "blocked",
|
|
228
1023
|
reason: `stuck-loop: ${nextKey} picked ${matchingCount} times`,
|
|
229
1024
|
action: "stop",
|
|
230
1025
|
};
|
|
231
|
-
|
|
1026
|
+
this.journalTransition({
|
|
232
1027
|
name: "advance-blocked",
|
|
233
1028
|
reason: blocked.reason,
|
|
234
1029
|
unitType: decision.unitType,
|
|
235
1030
|
unitId: decision.unitId,
|
|
236
1031
|
});
|
|
237
|
-
|
|
1032
|
+
this.postAdvanceRecord(blocked);
|
|
238
1033
|
return blocked;
|
|
239
1034
|
}
|
|
240
1035
|
|
|
241
|
-
const contract =
|
|
1036
|
+
const contract = this.compileUnitToolContract(decision.unitType);
|
|
242
1037
|
if (!contract.ok) {
|
|
1038
|
+
this.clearPendingDispatch();
|
|
243
1039
|
const blocked: AutoAdvanceResult = {
|
|
244
1040
|
kind: "blocked",
|
|
245
1041
|
reason: contract.reason,
|
|
246
1042
|
action: "pause",
|
|
247
1043
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
248
1044
|
};
|
|
249
|
-
|
|
1045
|
+
this.journalTransition({
|
|
250
1046
|
name: "advance-blocked",
|
|
251
1047
|
reason: blocked.reason,
|
|
252
1048
|
unitType: decision.unitType,
|
|
253
1049
|
unitId: decision.unitId,
|
|
254
1050
|
});
|
|
255
|
-
|
|
1051
|
+
this.postAdvanceRecord(blocked);
|
|
256
1052
|
return blocked;
|
|
257
1053
|
}
|
|
258
1054
|
|
|
259
|
-
const worktree = await this.
|
|
1055
|
+
const worktree = await this.prepareWorktreeForUnit(decision.unitType, decision.unitId);
|
|
260
1056
|
if (!worktree.ok) {
|
|
1057
|
+
this.clearPendingDispatch();
|
|
261
1058
|
const blocked: AutoAdvanceResult = {
|
|
262
1059
|
kind: "blocked",
|
|
263
1060
|
reason: worktree.reason,
|
|
264
1061
|
action: "pause",
|
|
265
1062
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
266
1063
|
};
|
|
267
|
-
|
|
1064
|
+
this.journalTransition({
|
|
268
1065
|
name: "advance-blocked",
|
|
269
1066
|
reason: blocked.reason,
|
|
270
1067
|
unitType: decision.unitType,
|
|
271
1068
|
unitId: decision.unitId,
|
|
272
1069
|
});
|
|
273
|
-
|
|
1070
|
+
this.postAdvanceRecord(blocked);
|
|
274
1071
|
return blocked;
|
|
275
1072
|
}
|
|
276
1073
|
|
|
@@ -279,23 +1076,23 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
279
1076
|
this.lastAdvanceKey = nextKey;
|
|
280
1077
|
this.bumpTransition();
|
|
281
1078
|
|
|
282
|
-
|
|
1079
|
+
this.journalTransition({
|
|
283
1080
|
name: "advance",
|
|
284
1081
|
reason: decision.reason,
|
|
285
1082
|
unitType: decision.unitType,
|
|
286
1083
|
unitId: decision.unitId,
|
|
287
1084
|
});
|
|
288
|
-
|
|
1085
|
+
// syncAfterUnit was a no-op in the wired WorktreeAdapter.
|
|
289
1086
|
|
|
290
1087
|
const advanced: AutoAdvanceResult = {
|
|
291
1088
|
kind: "advanced",
|
|
292
1089
|
unit: { unitType: decision.unitType, unitId: decision.unitId },
|
|
293
1090
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
294
1091
|
};
|
|
295
|
-
|
|
1092
|
+
this.postAdvanceRecord(advanced);
|
|
296
1093
|
return advanced;
|
|
297
1094
|
} catch (error) {
|
|
298
|
-
const recovery =
|
|
1095
|
+
const recovery = this.classifyAndRecover({
|
|
299
1096
|
error,
|
|
300
1097
|
unitType: this.status.activeUnit?.unitType,
|
|
301
1098
|
unitId: this.status.activeUnit?.unitId,
|
|
@@ -327,28 +1124,32 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
327
1124
|
: result.kind === "stopped"
|
|
328
1125
|
? "advance-stopped"
|
|
329
1126
|
: "advance-error";
|
|
330
|
-
|
|
1127
|
+
this.journalTransition({ name: journalName, reason: recovery.reason });
|
|
331
1128
|
|
|
332
1129
|
if (result.kind === "paused") {
|
|
333
|
-
|
|
1130
|
+
this.notifyLifecycle({ name: "pause", detail: recovery.reason });
|
|
334
1131
|
} else if (result.kind === "stopped") {
|
|
335
|
-
|
|
1132
|
+
this.notifyLifecycle({ name: "stopped", detail: recovery.reason });
|
|
336
1133
|
} else if (result.kind === "error") {
|
|
337
|
-
|
|
1134
|
+
this.notifyLifecycle({ name: "error", detail: recovery.reason });
|
|
338
1135
|
}
|
|
339
|
-
|
|
1136
|
+
this.postAdvanceRecord(result);
|
|
340
1137
|
return result;
|
|
1138
|
+
} finally {
|
|
1139
|
+
stopAdvanceTimer();
|
|
341
1140
|
}
|
|
342
1141
|
}
|
|
343
1142
|
|
|
344
1143
|
public async resume(): Promise<AutoAdvanceResult> {
|
|
345
1144
|
this.lastAdvanceKey = null;
|
|
346
1145
|
this.lastFinalizedUnitKey = null;
|
|
347
|
-
|
|
1146
|
+
// Preserve dispatchKeyWindow across resume so stuck-loop detection
|
|
1147
|
+
// accumulates across pause/resume cycles rather than resetting each time.
|
|
1148
|
+
this.lastStuckRecoveryKey = null;
|
|
348
1149
|
this.status.phase = "running";
|
|
349
1150
|
this.bumpTransition();
|
|
350
|
-
|
|
351
|
-
|
|
1151
|
+
this.journalTransition({ name: "resume" });
|
|
1152
|
+
this.notifyLifecycle({ name: "resume" });
|
|
352
1153
|
return { kind: "resumed" };
|
|
353
1154
|
}
|
|
354
1155
|
|
|
@@ -356,15 +1157,20 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
356
1157
|
if (this.status.phase === "stopped") {
|
|
357
1158
|
return { kind: "stopped", reason };
|
|
358
1159
|
}
|
|
359
|
-
|
|
1160
|
+
// cleanupOnStop was a no-op in the wired WorktreeAdapter.
|
|
360
1161
|
this.status.phase = "stopped";
|
|
361
1162
|
this.status.activeUnit = undefined;
|
|
362
1163
|
this.lastAdvanceKey = null;
|
|
363
1164
|
this.lastFinalizedUnitKey = null;
|
|
364
|
-
|
|
1165
|
+
// Preserve dispatchKeyWindow on pause so stuck-loop detection accumulates
|
|
1166
|
+
// across pause/resume cycles. Only clear on a hard stop.
|
|
1167
|
+
if (reason !== "pause") {
|
|
1168
|
+
this.dispatchKeyWindow = [];
|
|
1169
|
+
}
|
|
1170
|
+
this.lastStuckRecoveryKey = null;
|
|
365
1171
|
this.bumpTransition();
|
|
366
|
-
|
|
367
|
-
|
|
1172
|
+
this.journalTransition({ name: "stop", reason });
|
|
1173
|
+
this.notifyLifecycle({ name: "stop", detail: reason });
|
|
368
1174
|
return { kind: "stopped", reason };
|
|
369
1175
|
}
|
|
370
1176
|
|
|
@@ -382,8 +1188,10 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
382
1188
|
this.status.activeUnit = undefined;
|
|
383
1189
|
this.lastAdvanceKey = null;
|
|
384
1190
|
this.lastFinalizedUnitKey = unitKey;
|
|
1191
|
+
// Genuine progress — re-enable graduated stuck recovery for future episodes.
|
|
1192
|
+
this.lastStuckRecoveryKey = null;
|
|
385
1193
|
this.bumpTransition();
|
|
386
|
-
|
|
1194
|
+
this.journalTransition({
|
|
387
1195
|
name: "unit-finalized",
|
|
388
1196
|
unitType: unit.unitType,
|
|
389
1197
|
unitId: unit.unitId,
|
|
@@ -403,7 +1211,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
403
1211
|
this.lastAdvanceKey = null;
|
|
404
1212
|
this.lastFinalizedUnitKey = null;
|
|
405
1213
|
this.bumpTransition();
|
|
406
|
-
|
|
1214
|
+
this.journalTransition({
|
|
407
1215
|
name: "unit-retry",
|
|
408
1216
|
reason: "finalize-retry",
|
|
409
1217
|
unitType: unit.unitType,
|
|
@@ -417,6 +1225,47 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
417
1225
|
}
|
|
418
1226
|
}
|
|
419
1227
|
|
|
420
|
-
|
|
421
|
-
|
|
1228
|
+
function isUsableLiveOrchestratorBasePath(basePath: string): boolean {
|
|
1229
|
+
if (!basePath || !existsSync(basePath)) return false;
|
|
1230
|
+
if (!detectWorktreeName(basePath)) return true;
|
|
1231
|
+
|
|
1232
|
+
try {
|
|
1233
|
+
return readFileSync(join(basePath, ".git"), "utf8").trim().startsWith("gitdir: ");
|
|
1234
|
+
} catch {
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Resolve the base path the live orchestrator should dispatch from, falling
|
|
1241
|
+
* back to the project root when the captured worktree path has been removed
|
|
1242
|
+
* (e.g. after milestone-merge cleanup). Exported for the closeout-regression
|
|
1243
|
+
* tests and reused by the orchestrator's getLiveDispatchBasePath.
|
|
1244
|
+
*/
|
|
1245
|
+
export function resolveLiveOrchestratorBasePath(input: {
|
|
1246
|
+
capturedBasePath: string;
|
|
1247
|
+
runtimeBasePath: string;
|
|
1248
|
+
sessionBasePath?: string | null;
|
|
1249
|
+
originalBasePath?: string | null;
|
|
1250
|
+
}): string {
|
|
1251
|
+
const primary = input.sessionBasePath || input.capturedBasePath;
|
|
1252
|
+
if (isUsableLiveOrchestratorBasePath(primary)) return primary;
|
|
1253
|
+
|
|
1254
|
+
const fallbacks = [
|
|
1255
|
+
input.originalBasePath,
|
|
1256
|
+
input.runtimeBasePath,
|
|
1257
|
+
resolveProjectRoot(input.capturedBasePath),
|
|
1258
|
+
];
|
|
1259
|
+
|
|
1260
|
+
for (const candidate of fallbacks) {
|
|
1261
|
+
if (candidate && isUsableLiveOrchestratorBasePath(candidate)) {
|
|
1262
|
+
return candidate;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return input.runtimeBasePath || input.capturedBasePath;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
export function createAutoOrchestrator(context: OrchestratorContext): AutoOrchestrationModule {
|
|
1270
|
+
return new AutoOrchestrator(context);
|
|
422
1271
|
}
|