@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,5 +1,49 @@
|
|
|
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.
|
|
12
|
+
import { debugCount, debugTime } from "../debug-logger.js";
|
|
13
|
+
import { reconcileBeforeDispatch } from "../state-reconciliation.js";
|
|
14
|
+
import { resolveDispatch } from "../auto-dispatch.js";
|
|
15
|
+
import { classifyFailure } from "../recovery-classification.js";
|
|
16
|
+
import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
17
|
+
import { invalidateAllCaches } from "../cache.js";
|
|
18
|
+
import { compileUnitToolContract } from "../tool-contract.js";
|
|
19
|
+
import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
20
|
+
import { repairAutoWorktreeSafetyFailure } from "../auto-worktree-repair.js";
|
|
21
|
+
import { resolveManifest } from "../unit-context-manifest.js";
|
|
22
|
+
import { preDispatchHealthGate, recordHealthSnapshot, } from "../doctor-proactive.js";
|
|
23
|
+
import { checkResourcesStale, autoWorktreeBranch, mergeMilestoneToMain } from "../auto-worktree.js";
|
|
24
|
+
import { getSessionLockStatus } from "../session-lock.js";
|
|
25
|
+
import { resolveUokFlags } from "../uok/flags.js";
|
|
26
|
+
import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
|
|
27
|
+
import { loadEffectiveGSDPreferences, getIsolationMode } from "../preferences.js";
|
|
28
|
+
import { detectWorktreeName, getMainBranch, resolveProjectRoot, resolveWorktreeProjectRoot, } from "../worktree.js";
|
|
29
|
+
import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
|
30
|
+
import { GitServiceImpl } from "../git-service.js";
|
|
31
|
+
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
32
|
+
import { WorktreeLifecycle } from "../worktree-lifecycle.js";
|
|
33
|
+
import { createMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
|
|
34
|
+
import { createWorkspace, scopeMilestone } from "../workspace.js";
|
|
35
|
+
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
36
|
+
import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
|
|
37
|
+
import { deriveState } from "../state.js";
|
|
38
|
+
import { parseUnitId } from "../unit-id.js";
|
|
39
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
40
|
+
import { isDbAvailable, getSlice, getTask, } from "../gsd-db.js";
|
|
41
|
+
import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
|
|
42
|
+
import { getErrorMessage } from "../error-utils.js";
|
|
43
|
+
import { logWarning } from "../workflow-logger.js";
|
|
44
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
45
|
+
import { join } from "node:path";
|
|
46
|
+
import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
|
|
3
47
|
function now() {
|
|
4
48
|
return Date.now();
|
|
5
49
|
}
|
|
@@ -12,40 +56,600 @@ function now() {
|
|
|
12
56
|
* preserved across the eventual cutover (issue #5791).
|
|
13
57
|
*/
|
|
14
58
|
export const STUCK_WINDOW_SIZE = 6;
|
|
15
|
-
function
|
|
59
|
+
function noRemainingUnitsOutcome(stateSnapshot) {
|
|
16
60
|
if (stateSnapshot.phase === "complete") {
|
|
17
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
code: "all-complete",
|
|
63
|
+
displayReason: "All milestones complete",
|
|
64
|
+
allMilestonesComplete: true,
|
|
65
|
+
};
|
|
18
66
|
}
|
|
19
|
-
return
|
|
67
|
+
return {
|
|
68
|
+
code: "no-remaining-units",
|
|
69
|
+
displayReason: "No remaining units",
|
|
70
|
+
allMilestonesComplete: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function getAlreadyClosedDispatchReason(unitType, unitId) {
|
|
74
|
+
if (!isDbAvailable())
|
|
75
|
+
return null;
|
|
76
|
+
refreshWorkflowDatabaseFromDisk();
|
|
77
|
+
const { milestone, slice, task } = parseUnitId(unitId);
|
|
78
|
+
if (unitType === "execute-task" && milestone && slice && task) {
|
|
79
|
+
const row = getTask(milestone, slice, task);
|
|
80
|
+
return row && isClosedStatus(row.status)
|
|
81
|
+
? `execute-task ${unitId} is already ${row.status}`
|
|
82
|
+
: null;
|
|
83
|
+
}
|
|
84
|
+
if (unitType === "complete-slice" && milestone && slice) {
|
|
85
|
+
const row = getSlice(milestone, slice);
|
|
86
|
+
return row && isClosedStatus(row.status)
|
|
87
|
+
? `complete-slice ${unitId} is already ${row.status}`
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath) {
|
|
93
|
+
const activeMilestoneId = state.activeMilestone?.id;
|
|
94
|
+
const currentMilestoneId = activeSession?.currentMilestoneId;
|
|
95
|
+
if (!activeSession || !activeMilestoneId || !currentMilestoneId || activeMilestoneId === currentMilestoneId) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const scopedWorktreeMilestone = (activeSession.basePath ? detectWorktreeName(activeSession.basePath) : null) ??
|
|
99
|
+
detectWorktreeName(activeDispatchBasePath);
|
|
100
|
+
if (scopedWorktreeMilestone && scopedWorktreeMilestone !== activeMilestoneId) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const currentMilestone = state.registry.find((milestone) => milestone.id === currentMilestoneId);
|
|
104
|
+
return !!currentMilestone && isClosedStatus(currentMilestone.status);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Pure dispatch-decision function — formerly `createWiredDispatchAdapter`'s
|
|
108
|
+
* `decideNextUnit`. Folded out of the closure so the orchestrator can call it
|
|
109
|
+
* directly and tests can drive the exact dispatch decision logic against real
|
|
110
|
+
* fixtures without re-introducing an adapter seam.
|
|
111
|
+
*
|
|
112
|
+
* Derives session-derived dispatch inputs the same way phases.ts:runDispatch
|
|
113
|
+
* does (#5789): prefers caller-supplied values when present so test harnesses
|
|
114
|
+
* and alternative wirings can inject deterministic snapshots; otherwise pulls
|
|
115
|
+
* from the captured pi/ctx references.
|
|
116
|
+
*/
|
|
117
|
+
export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, session, input) {
|
|
118
|
+
const state = input.stateSnapshot;
|
|
119
|
+
const active = state.activeMilestone;
|
|
120
|
+
if (!active)
|
|
121
|
+
return null;
|
|
122
|
+
const activeSession = input.session ?? session;
|
|
123
|
+
const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
|
|
124
|
+
if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
|
|
125
|
+
activeSession.currentMilestoneId = active.id;
|
|
126
|
+
}
|
|
127
|
+
const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
|
|
128
|
+
// Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
|
|
129
|
+
// (#5789). Prefer caller-supplied values when present so test harnesses and
|
|
130
|
+
// alternative wirings can inject deterministic snapshots; otherwise pull from
|
|
131
|
+
// the captured pi/ctx references.
|
|
132
|
+
const sessionProvider = input.sessionProvider ?? ctx.model?.provider;
|
|
133
|
+
const sessionContextWindow = input.sessionContextWindow ?? ctx.model?.contextWindow;
|
|
134
|
+
const modelRegistry = input.modelRegistry ?? ctx.modelRegistry;
|
|
135
|
+
const authMode = sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
|
|
136
|
+
? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
|
|
137
|
+
: undefined;
|
|
138
|
+
// Use baseline snapshot — same reason as phases.ts:runDispatch: the live
|
|
139
|
+
// active set may be narrowed by the prior unit before selectAndApplyModel
|
|
140
|
+
// restores it, causing false transport-preflight failures (#477 follow-up).
|
|
141
|
+
const activeTools = getToolBaselineSnapshot(pi);
|
|
142
|
+
const registeredTools = getRegisteredToolSnapshot(pi);
|
|
143
|
+
// Mirrors runDispatch: deep-planning keeps approval gates in plain chat
|
|
144
|
+
// because structured questions can be cancelled outside the chat turn on
|
|
145
|
+
// some transports.
|
|
146
|
+
const structuredQuestionsAvailable = input.structuredQuestionsAvailable ??
|
|
147
|
+
(prefs?.planning_depth === "deep"
|
|
148
|
+
? "false"
|
|
149
|
+
: supportsStructuredQuestions(activeTools, {
|
|
150
|
+
authMode,
|
|
151
|
+
baseUrl: ctx.model?.baseUrl,
|
|
152
|
+
})
|
|
153
|
+
? "true"
|
|
154
|
+
: "false");
|
|
155
|
+
const pendingRetry = session?.pendingVerificationRetryDispatch;
|
|
156
|
+
if (session && pendingRetry) {
|
|
157
|
+
session.pendingVerificationRetryDispatch = null;
|
|
158
|
+
const alreadyClosedReason = getAlreadyClosedDispatchReason(pendingRetry.unitType, pendingRetry.unitId);
|
|
159
|
+
if (alreadyClosedReason) {
|
|
160
|
+
session.pendingOrchestrationDispatch = null;
|
|
161
|
+
session.pendingVerificationRetry = null;
|
|
162
|
+
return { kind: "skipped", reason: alreadyClosedReason };
|
|
163
|
+
}
|
|
164
|
+
session.pendingOrchestrationDispatch = pendingRetry;
|
|
165
|
+
return {
|
|
166
|
+
unitType: pendingRetry.unitType,
|
|
167
|
+
unitId: pendingRetry.unitId,
|
|
168
|
+
reason: "verification-retry",
|
|
169
|
+
preconditions: [],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const action = await resolveDispatch({
|
|
173
|
+
basePath: activeDispatchBasePath,
|
|
174
|
+
mid: active.id,
|
|
175
|
+
midTitle: active.title,
|
|
176
|
+
state,
|
|
177
|
+
prefs,
|
|
178
|
+
session: activeSession,
|
|
179
|
+
structuredQuestionsAvailable,
|
|
180
|
+
sessionContextWindow,
|
|
181
|
+
sessionProvider,
|
|
182
|
+
modelRegistry,
|
|
183
|
+
activeTools,
|
|
184
|
+
registeredTools,
|
|
185
|
+
sessionAuthMode: authMode,
|
|
186
|
+
sessionBaseUrl: ctx.model?.baseUrl,
|
|
187
|
+
});
|
|
188
|
+
if (action.action === "stop") {
|
|
189
|
+
if (session)
|
|
190
|
+
session.pendingOrchestrationDispatch = null;
|
|
191
|
+
return {
|
|
192
|
+
kind: "blocked",
|
|
193
|
+
reason: action.reason,
|
|
194
|
+
action: action.level === "warning" ? "pause" : "stop",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (action.action !== "dispatch") {
|
|
198
|
+
if (session)
|
|
199
|
+
session.pendingOrchestrationDispatch = null;
|
|
200
|
+
return {
|
|
201
|
+
kind: "skipped",
|
|
202
|
+
reason: action.matchedRule ?? "dispatch-skip",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const alreadyClosedReason = getAlreadyClosedDispatchReason(action.unitType, action.unitId);
|
|
206
|
+
if (alreadyClosedReason) {
|
|
207
|
+
if (session) {
|
|
208
|
+
session.pendingOrchestrationDispatch = null;
|
|
209
|
+
session.pendingVerificationRetry = null;
|
|
210
|
+
}
|
|
211
|
+
return { kind: "skipped", reason: alreadyClosedReason };
|
|
212
|
+
}
|
|
213
|
+
if (session) {
|
|
214
|
+
const pending = {
|
|
215
|
+
unitType: action.unitType,
|
|
216
|
+
unitId: action.unitId,
|
|
217
|
+
prompt: action.prompt,
|
|
218
|
+
pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
|
|
219
|
+
state,
|
|
220
|
+
mid: active.id,
|
|
221
|
+
midTitle: active.title,
|
|
222
|
+
};
|
|
223
|
+
session.pendingOrchestrationDispatch = pending;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
unitType: action.unitType,
|
|
227
|
+
unitId: action.unitId,
|
|
228
|
+
reason: action.matchedRule ?? "dispatch",
|
|
229
|
+
preconditions: [],
|
|
230
|
+
};
|
|
20
231
|
}
|
|
21
232
|
export class AutoOrchestrator {
|
|
22
233
|
status = {
|
|
23
234
|
phase: "idle",
|
|
24
235
|
transitionCount: 0,
|
|
25
236
|
};
|
|
26
|
-
|
|
237
|
+
ctx;
|
|
238
|
+
pi;
|
|
239
|
+
dispatchBasePath;
|
|
240
|
+
runtimeBasePath;
|
|
241
|
+
s;
|
|
242
|
+
flowId;
|
|
243
|
+
seq = 0;
|
|
27
244
|
lastAdvanceKey = null;
|
|
28
245
|
lastFinalizedUnitKey = null;
|
|
29
246
|
dispatchKeyWindow = [];
|
|
30
|
-
|
|
31
|
-
|
|
247
|
+
// #442: the unit key we last attempted graduated stuck-recovery for. Bounds
|
|
248
|
+
// recovery to one attempt per stuck episode per run (reset on start/resume/
|
|
249
|
+
// stop), mirroring the legacy Level-1-then-Level-2 escalation in phases.ts.
|
|
250
|
+
lastStuckRecoveryKey = null;
|
|
251
|
+
constructor(context) {
|
|
252
|
+
this.ctx = context.ctx;
|
|
253
|
+
this.pi = context.pi;
|
|
254
|
+
this.dispatchBasePath = context.dispatchBasePath;
|
|
255
|
+
this.runtimeBasePath = context.runtimeBasePath;
|
|
256
|
+
this.s = context.session;
|
|
257
|
+
this.flowId = `auto-orchestrator-${Date.now()}`;
|
|
258
|
+
}
|
|
259
|
+
// ── Live base-path resolution (was the wiring factory's getLiveDispatchBasePath) ──
|
|
260
|
+
getLiveDispatchBasePath() {
|
|
261
|
+
return resolveLiveOrchestratorBasePath({
|
|
262
|
+
capturedBasePath: this.dispatchBasePath,
|
|
263
|
+
runtimeBasePath: this.runtimeBasePath,
|
|
264
|
+
sessionBasePath: this.s.basePath,
|
|
265
|
+
originalBasePath: this.s.originalBasePath,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// ── RuntimePersistenceAdapter (folded) ───────────────────────────────────
|
|
269
|
+
ensureLockOwnership() {
|
|
270
|
+
const status = getSessionLockStatus(this.runtimeBasePath);
|
|
271
|
+
if (!status.valid || status.failureReason === "pid-mismatch") {
|
|
272
|
+
throw new Error("session lock held by another process");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Map an orchestrator lifecycle event name to its journal eventType and emit
|
|
277
|
+
* it. The name→eventType ternary is preserved byte-for-byte from the legacy
|
|
278
|
+
* wired RuntimePersistenceAdapter.journalTransition.
|
|
279
|
+
*/
|
|
280
|
+
journalTransition(event) {
|
|
281
|
+
const eventType = event.name === "start"
|
|
282
|
+
? "orchestrator-iteration-start"
|
|
283
|
+
: event.name === "resume"
|
|
284
|
+
? "orchestrator-iteration-start"
|
|
285
|
+
: event.name === "advance"
|
|
286
|
+
? "orchestrator-dispatch-match"
|
|
287
|
+
: event.name === "advance-blocked"
|
|
288
|
+
? "orchestrator-guard-block"
|
|
289
|
+
: event.name === "advance-stopped"
|
|
290
|
+
? "orchestrator-dispatch-stop"
|
|
291
|
+
: event.name === "advance-error"
|
|
292
|
+
? "orchestrator-iteration-end"
|
|
293
|
+
: event.name === "advance-paused" || event.name === "advance-retry"
|
|
294
|
+
? "orchestrator-guard-block"
|
|
295
|
+
: event.name === "stop"
|
|
296
|
+
? "orchestrator-terminal"
|
|
297
|
+
: "orchestrator-iteration-end";
|
|
298
|
+
_emitJournalEvent(this.runtimeBasePath, {
|
|
299
|
+
ts: new Date().toISOString(),
|
|
300
|
+
flowId: this.flowId,
|
|
301
|
+
seq: ++this.seq,
|
|
302
|
+
eventType,
|
|
303
|
+
data: {
|
|
304
|
+
source: "auto-orchestrator",
|
|
305
|
+
name: event.name,
|
|
306
|
+
reason: event.reason,
|
|
307
|
+
unitType: event.unitType,
|
|
308
|
+
unitId: event.unitId,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
// ── NotificationAdapter (folded) ─────────────────────────────────────────
|
|
313
|
+
notifyLifecycle(event) {
|
|
314
|
+
if (event.name === "error") {
|
|
315
|
+
this.ctx.ui.notify(event.detail ?? "auto orchestration error", "error");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ── HealthAdapter (folded) ───────────────────────────────────────────────
|
|
319
|
+
checkResourcesStale() {
|
|
320
|
+
return checkResourcesStale(this.s.resourceVersionOnStart);
|
|
321
|
+
}
|
|
322
|
+
async preAdvanceGate() {
|
|
323
|
+
try {
|
|
324
|
+
const gate = await preDispatchHealthGate(this.getLiveDispatchBasePath());
|
|
325
|
+
if (gate.proceed) {
|
|
326
|
+
return {
|
|
327
|
+
kind: "pass",
|
|
328
|
+
fixesApplied: gate.fixesApplied,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
kind: "fail",
|
|
333
|
+
reason: gate.reason ?? "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
334
|
+
action: gate.severity ?? "pause",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return { kind: "threw", error };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
postAdvanceRecord(result) {
|
|
342
|
+
if (result.kind === "error") {
|
|
343
|
+
recordHealthSnapshot(1, 0, 0, [{
|
|
344
|
+
code: "orchestration-error",
|
|
345
|
+
message: result.reason ?? "orchestration error",
|
|
346
|
+
severity: "error",
|
|
347
|
+
unitId: "orchestration",
|
|
348
|
+
}], [], "orchestration");
|
|
349
|
+
}
|
|
350
|
+
else if (result.kind === "blocked") {
|
|
351
|
+
recordHealthSnapshot(0, 1, 0, [{
|
|
352
|
+
code: "orchestration-blocked",
|
|
353
|
+
message: result.reason ?? "orchestration blocked",
|
|
354
|
+
severity: "warning",
|
|
355
|
+
unitId: "orchestration",
|
|
356
|
+
}], [], "orchestration");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// ── UokGateAdapter (folded) ──────────────────────────────────────────────
|
|
360
|
+
async emitUokGate(input) {
|
|
361
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
362
|
+
const prefs = loadEffectiveGSDPreferences(activeBasePath)?.preferences;
|
|
363
|
+
const uokFlags = resolveUokFlags(prefs);
|
|
364
|
+
if (!uokFlags.gates)
|
|
365
|
+
return;
|
|
366
|
+
const milestoneId = input.milestoneId ?? this.s.currentMilestoneId ?? undefined;
|
|
367
|
+
try {
|
|
368
|
+
const { UokGateRunner } = await import("../uok/gate-runner.js");
|
|
369
|
+
const runner = new UokGateRunner();
|
|
370
|
+
runner.register({
|
|
371
|
+
id: input.gateId,
|
|
372
|
+
type: input.gateType,
|
|
373
|
+
execute: async () => ({
|
|
374
|
+
outcome: input.outcome,
|
|
375
|
+
failureClass: input.failureClass,
|
|
376
|
+
rationale: input.rationale,
|
|
377
|
+
findings: input.findings ?? "",
|
|
378
|
+
}),
|
|
379
|
+
});
|
|
380
|
+
await runner.run(input.gateId, {
|
|
381
|
+
basePath: activeBasePath,
|
|
382
|
+
traceId: `pre-dispatch:${this.flowId}`,
|
|
383
|
+
turnId: `orch-${this.seq}`,
|
|
384
|
+
milestoneId,
|
|
385
|
+
unitType: "pre-dispatch",
|
|
386
|
+
unitId: `orch-${this.seq}`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
logWarning("engine", `uok gate emit failed: ${getErrorMessage(err)}`, {
|
|
391
|
+
file: "orchestrator.ts",
|
|
392
|
+
gateId: input.gateId,
|
|
393
|
+
gateType: input.gateType,
|
|
394
|
+
...(milestoneId ? { milestoneId } : {}),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// ── StateReconciliationAdapter (folded) ──────────────────────────────────
|
|
399
|
+
async reconcileBeforeDispatch() {
|
|
400
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
401
|
+
const result = await reconcileBeforeDispatch(activeBasePath);
|
|
402
|
+
// Failure-path summaries written by gsd_summary_save create
|
|
403
|
+
// artifact-db-status-divergence blockers for tasks that are still
|
|
404
|
+
// pending (gsd_task_complete never ran). These tasks can still be
|
|
405
|
+
// dispatched and the drift self-heals once they complete successfully.
|
|
406
|
+
const hardBlockers = result.blockers.filter((b) => !b.includes("has SUMMARY artifact while DB status is") &&
|
|
407
|
+
!b.includes("has SUMMARY on disk while DB status is") &&
|
|
408
|
+
!b.includes("has task SUMMARY artifacts but no DB tasks"));
|
|
409
|
+
if (hardBlockers.length > 0) {
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
reason: hardBlockers[0],
|
|
413
|
+
stateSnapshot: result.stateSnapshot,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const repairedKinds = result.repaired.map((d) => d.kind);
|
|
417
|
+
return {
|
|
418
|
+
ok: true,
|
|
419
|
+
reason: repairedKinds.length > 0
|
|
420
|
+
? `repaired: ${repairedKinds.join(", ")}`
|
|
421
|
+
: "clean",
|
|
422
|
+
stateSnapshot: result.stateSnapshot,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
// ── DispatchAdapter (folded) ─────────────────────────────────────────────
|
|
426
|
+
decideNextUnit(input) {
|
|
427
|
+
return decideOrchestratorDispatch(this.ctx, this.pi, this.dispatchBasePath, this.s, input);
|
|
428
|
+
}
|
|
429
|
+
evaluateNoRemainingUnitsSettlement(stateSnapshot) {
|
|
430
|
+
const settlement = evaluateAllCompleteSettlement({
|
|
431
|
+
milestoneId: this.s.currentMilestoneId ?? stateSnapshot.activeMilestone?.id,
|
|
432
|
+
statePhase: stateSnapshot.phase,
|
|
433
|
+
basePath: this.s.basePath || this.getLiveDispatchBasePath(),
|
|
434
|
+
originalBasePath: this.s.originalBasePath || this.runtimeBasePath,
|
|
435
|
+
milestoneMerged: this.s.milestoneMergedInPhases,
|
|
436
|
+
});
|
|
437
|
+
this.s.milestoneSettlement = settlement;
|
|
438
|
+
if (settlement.ok)
|
|
439
|
+
return null;
|
|
440
|
+
return {
|
|
441
|
+
kind: "blocked",
|
|
442
|
+
reason: settlement.message,
|
|
443
|
+
action: settlement.action,
|
|
444
|
+
stateSnapshot,
|
|
445
|
+
terminalOutcome: {
|
|
446
|
+
code: "settlement-blocked",
|
|
447
|
+
displayReason: settlement.message,
|
|
448
|
+
nextAction: settlement.nextAction,
|
|
449
|
+
milestoneId: settlement.milestoneId,
|
|
450
|
+
allMilestonesComplete: false,
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
clearPendingDispatch() {
|
|
455
|
+
this.s.pendingOrchestrationDispatch = null;
|
|
456
|
+
}
|
|
457
|
+
findPriorSliceCompletionBlocker(unitType, unitId) {
|
|
458
|
+
const guardBasePath = resolveWorktreeProjectRoot(this.getLiveDispatchBasePath(), this.s.originalBasePath);
|
|
459
|
+
let mainBranch = "main";
|
|
460
|
+
try {
|
|
461
|
+
mainBranch = getMainBranch(guardBasePath);
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
// Preserve legacy dispatch behavior: fall back to main when branch
|
|
465
|
+
// discovery fails, then let the guard make the progression decision.
|
|
466
|
+
logWarning("engine", `branch discovery failed, falling back to main: ${getErrorMessage(err)}`, { file: "orchestrator.ts" });
|
|
467
|
+
}
|
|
468
|
+
return getPriorSliceCompletionBlocker(guardBasePath, mainBranch, unitType, unitId);
|
|
469
|
+
}
|
|
470
|
+
// ── ToolContractAdapter (folded) ─────────────────────────────────────────
|
|
471
|
+
compileUnitToolContract(unitType) {
|
|
472
|
+
const result = compileUnitToolContract(unitType);
|
|
473
|
+
if (!result.ok)
|
|
474
|
+
return { ok: false, reason: result.detail };
|
|
475
|
+
return { ok: true, reason: result.contract.validationRules.join(", ") };
|
|
476
|
+
}
|
|
477
|
+
// ── WorktreeAdapter (folded) ─────────────────────────────────────────────
|
|
478
|
+
getEffectiveUnitIsolationMode(basePath) {
|
|
479
|
+
const configuredMode = getIsolationMode(basePath);
|
|
480
|
+
return configuredMode === "worktree" && this.s.isolationDegraded ? "branch" : configuredMode;
|
|
481
|
+
}
|
|
482
|
+
buildLifecycle() {
|
|
483
|
+
return new WorktreeLifecycle(this.s, {
|
|
484
|
+
gitServiceFactory: (basePath) => {
|
|
485
|
+
const gitConfig = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
486
|
+
return new GitServiceImpl(basePath, gitConfig);
|
|
487
|
+
},
|
|
488
|
+
worktreeProjection: new WorktreeStateProjection(),
|
|
489
|
+
mergeMilestoneToMain: createMilestoneMergeTransaction(mergeMilestoneToMain),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
rebuildScope(rawPath, milestoneId) {
|
|
493
|
+
if (!milestoneId) {
|
|
494
|
+
this.s.scope = null;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const workspace = createWorkspace(rawPath);
|
|
499
|
+
this.s.scope = scopeMilestone(workspace, milestoneId);
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
// Non-fatal — scope is additive. Existing readers still use basePath.
|
|
503
|
+
this.s.scope = null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async prepareWorktreeForUnit(unitType, unitId) {
|
|
507
|
+
const isolationMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);
|
|
508
|
+
const manifest = resolveManifest(unitType);
|
|
509
|
+
if (!manifest) {
|
|
510
|
+
return {
|
|
511
|
+
ok: false,
|
|
512
|
+
reason: `No Unit manifest is registered for ${unitType}`,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (isolationMode !== "worktree") {
|
|
516
|
+
return { ok: true, reason: "not-required" };
|
|
517
|
+
}
|
|
518
|
+
const writeScope = manifest.tools.mode === "all" || manifest.tools.mode === "docs"
|
|
519
|
+
? "source-writing"
|
|
520
|
+
: "planning-only";
|
|
521
|
+
const safety = createWorktreeSafetyModule();
|
|
522
|
+
const activeBasePath = this.getLiveDispatchBasePath();
|
|
523
|
+
const snapshot = await deriveState(activeBasePath);
|
|
524
|
+
const milestoneId = snapshot.activeMilestone?.id ?? null;
|
|
525
|
+
const expectedBranch = milestoneId ? autoWorktreeBranch(milestoneId) : null;
|
|
526
|
+
let result = safety.validateUnitRoot({
|
|
527
|
+
unitType,
|
|
528
|
+
unitId,
|
|
529
|
+
writeScope,
|
|
530
|
+
projectRoot: this.runtimeBasePath,
|
|
531
|
+
unitRoot: activeBasePath,
|
|
532
|
+
milestoneId,
|
|
533
|
+
isolationMode,
|
|
534
|
+
expectedBranch,
|
|
535
|
+
});
|
|
536
|
+
if (!result.ok) {
|
|
537
|
+
const repaired = await repairAutoWorktreeSafetyFailure({
|
|
538
|
+
safetyResult: result,
|
|
539
|
+
projectRoot: this.runtimeBasePath,
|
|
540
|
+
activeRoot: activeBasePath,
|
|
541
|
+
milestoneId,
|
|
542
|
+
enterMilestone: async (id) => {
|
|
543
|
+
this.buildLifecycle().adoptSessionRoot(this.runtimeBasePath, this.s.originalBasePath || this.runtimeBasePath);
|
|
544
|
+
const enterResult = this.buildLifecycle().enterMilestone(id, {
|
|
545
|
+
notify: this.ctx.ui.notify.bind(this.ctx.ui),
|
|
546
|
+
});
|
|
547
|
+
if (!enterResult.ok)
|
|
548
|
+
return { ok: false, reason: enterResult.reason };
|
|
549
|
+
this.rebuildScope(this.s.basePath, this.s.currentMilestoneId);
|
|
550
|
+
return { ok: true };
|
|
551
|
+
},
|
|
552
|
+
revalidate: () => safety.validateUnitRoot({
|
|
553
|
+
unitType,
|
|
554
|
+
unitId,
|
|
555
|
+
writeScope,
|
|
556
|
+
projectRoot: this.runtimeBasePath,
|
|
557
|
+
unitRoot: this.getLiveDispatchBasePath(),
|
|
558
|
+
milestoneId,
|
|
559
|
+
isolationMode: this.getEffectiveUnitIsolationMode(this.runtimeBasePath),
|
|
560
|
+
expectedBranch,
|
|
561
|
+
}),
|
|
562
|
+
});
|
|
563
|
+
result = repaired.result;
|
|
564
|
+
if (result.ok) {
|
|
565
|
+
return { ok: true, reason: repaired.repaired ? `repaired-${result.kind}` : result.kind };
|
|
566
|
+
}
|
|
567
|
+
const repairDetail = repaired.repairReason
|
|
568
|
+
? ` (repair skipped: ${repaired.repairReason})`
|
|
569
|
+
: "";
|
|
570
|
+
return { ok: false, reason: `${result.kind}: ${result.reason}${repairDetail}` };
|
|
571
|
+
}
|
|
572
|
+
return { ok: true, reason: result.kind };
|
|
573
|
+
}
|
|
574
|
+
// ── RecoveryAdapter (folded) ─────────────────────────────────────────────
|
|
575
|
+
classifyAndRecover(input) {
|
|
576
|
+
const recovery = classifyFailure(input);
|
|
577
|
+
return { action: recovery.action, reason: recovery.reason };
|
|
578
|
+
}
|
|
579
|
+
// ── Lifecycle verbs ──────────────────────────────────────────────────────
|
|
580
|
+
/**
|
|
581
|
+
* #442: graduated stuck recovery, ported from the legacy
|
|
582
|
+
* auto/phases.ts:runDispatch path that Phase 3 retires. The ring-buffer
|
|
583
|
+
* hard-stops (stuck-loop saturation and finalized-repeat) would otherwise
|
|
584
|
+
* KILL a unit that actually completed on disk but whose DB row is still
|
|
585
|
+
* stale. Before hard-stopping, verify the expected artifact exists; if so,
|
|
586
|
+
* refresh the DB from it, invalidate caches and reset the dispatch ring so
|
|
587
|
+
* the next advance picks the correct next unit. Bounded to one attempt per
|
|
588
|
+
* stuck key per episode (reset on lifecycle + genuine finalize) to avoid an
|
|
589
|
+
* unbounded recover→re-saturate→recover loop — mirrors the legacy
|
|
590
|
+
* Level-1-recover-then-Level-2-hard-stop escalation.
|
|
591
|
+
*
|
|
592
|
+
* Returns true when recovery succeeded; the caller should re-loop (return a
|
|
593
|
+
* skipped result) instead of stopping.
|
|
594
|
+
*/
|
|
595
|
+
tryStuckArtifactRecovery(unitType, unitId) {
|
|
596
|
+
const key = `${unitType}:${unitId}`;
|
|
597
|
+
if (this.lastStuckRecoveryKey === key)
|
|
598
|
+
return false; // already tried this episode
|
|
599
|
+
const basePath = this.getLiveDispatchBasePath();
|
|
600
|
+
if (!verifyExpectedArtifact(unitType, unitId, basePath))
|
|
601
|
+
return false;
|
|
602
|
+
const refreshed = refreshRecoveryDbForArtifact(unitType, unitId, basePath);
|
|
603
|
+
// Fatal failures cannot be recovered — hard-stop. Non-fatal (e.g. plan-slice
|
|
604
|
+
// DB refresh hiccup) still fall through: invalidating caches and resetting
|
|
605
|
+
// the ring gives the next advance a clean slate to pick up the correct state,
|
|
606
|
+
// mirroring the legacy Level-1 "continue" escalation path.
|
|
607
|
+
if (!refreshed.ok && refreshed.fatal)
|
|
608
|
+
return false;
|
|
609
|
+
this.lastStuckRecoveryKey = key;
|
|
610
|
+
invalidateAllCaches();
|
|
611
|
+
this.dispatchKeyWindow = [];
|
|
612
|
+
this.lastAdvanceKey = null;
|
|
613
|
+
this.lastFinalizedUnitKey = null;
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
stuckRecovered(decision, stateSnapshot) {
|
|
617
|
+
const recovered = {
|
|
618
|
+
kind: "skipped",
|
|
619
|
+
reason: `stuck-recovery: ${decision.unitType} ${decision.unitId} artifact found on disk; DB refreshed`,
|
|
620
|
+
stateSnapshot,
|
|
621
|
+
};
|
|
622
|
+
this.status.phase = "running";
|
|
623
|
+
this.status.activeUnit = undefined;
|
|
624
|
+
this.bumpTransition();
|
|
625
|
+
this.journalTransition({
|
|
626
|
+
name: "advance-skipped",
|
|
627
|
+
reason: recovered.reason,
|
|
628
|
+
unitType: decision.unitType,
|
|
629
|
+
unitId: decision.unitId,
|
|
630
|
+
});
|
|
631
|
+
this.postAdvanceRecord(recovered);
|
|
632
|
+
return recovered;
|
|
32
633
|
}
|
|
33
634
|
async start(_sessionContext) {
|
|
34
635
|
this.lastAdvanceKey = null;
|
|
35
636
|
this.lastFinalizedUnitKey = null;
|
|
36
637
|
this.dispatchKeyWindow = [];
|
|
638
|
+
this.lastStuckRecoveryKey = null;
|
|
37
639
|
this.status.phase = "running";
|
|
38
640
|
this.bumpTransition();
|
|
39
|
-
|
|
40
|
-
|
|
641
|
+
this.journalTransition({ name: "start" });
|
|
642
|
+
this.notifyLifecycle({ name: "start" });
|
|
41
643
|
return { kind: "started" };
|
|
42
644
|
}
|
|
43
645
|
async advance() {
|
|
646
|
+
debugCount("dispatches");
|
|
647
|
+
const stopAdvanceTimer = debugTime("orchestrator-advance");
|
|
44
648
|
try {
|
|
45
|
-
|
|
46
|
-
const staleMsg = this.
|
|
649
|
+
this.ensureLockOwnership();
|
|
650
|
+
const staleMsg = this.checkResourcesStale();
|
|
47
651
|
if (staleMsg) {
|
|
48
|
-
await this.
|
|
652
|
+
await this.emitUokGate({
|
|
49
653
|
gateId: "resource-version-guard",
|
|
50
654
|
gateType: "policy",
|
|
51
655
|
outcome: "fail",
|
|
@@ -54,20 +658,20 @@ export class AutoOrchestrator {
|
|
|
54
658
|
findings: staleMsg,
|
|
55
659
|
});
|
|
56
660
|
const blocked = { kind: "blocked", reason: staleMsg, action: "pause" };
|
|
57
|
-
|
|
58
|
-
|
|
661
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
662
|
+
this.postAdvanceRecord(blocked);
|
|
59
663
|
return blocked;
|
|
60
664
|
}
|
|
61
|
-
await this.
|
|
665
|
+
await this.emitUokGate({
|
|
62
666
|
gateId: "resource-version-guard",
|
|
63
667
|
gateType: "policy",
|
|
64
668
|
outcome: "pass",
|
|
65
669
|
failureClass: "none",
|
|
66
670
|
rationale: "resource version guard passed",
|
|
67
671
|
});
|
|
68
|
-
const gate = await this.
|
|
672
|
+
const gate = await this.preAdvanceGate();
|
|
69
673
|
if (gate.kind === "fail") {
|
|
70
|
-
await this.
|
|
674
|
+
await this.emitUokGate({
|
|
71
675
|
gateId: "pre-dispatch-health-gate",
|
|
72
676
|
gateType: "execution",
|
|
73
677
|
outcome: "manual-attention",
|
|
@@ -80,12 +684,12 @@ export class AutoOrchestrator {
|
|
|
80
684
|
reason: gate.reason,
|
|
81
685
|
action: gate.action ?? "pause",
|
|
82
686
|
};
|
|
83
|
-
|
|
84
|
-
|
|
687
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
688
|
+
this.postAdvanceRecord(blocked);
|
|
85
689
|
return blocked;
|
|
86
690
|
}
|
|
87
691
|
if (gate.kind === "threw") {
|
|
88
|
-
await this.
|
|
692
|
+
await this.emitUokGate({
|
|
89
693
|
gateId: "pre-dispatch-health-gate",
|
|
90
694
|
gateType: "execution",
|
|
91
695
|
outcome: "manual-attention",
|
|
@@ -96,7 +700,7 @@ export class AutoOrchestrator {
|
|
|
96
700
|
// intentional fall-through: matches runPreDispatch behaviour
|
|
97
701
|
}
|
|
98
702
|
else {
|
|
99
|
-
await this.
|
|
703
|
+
await this.emitUokGate({
|
|
100
704
|
gateId: "pre-dispatch-health-gate",
|
|
101
705
|
gateType: "execution",
|
|
102
706
|
outcome: "pass",
|
|
@@ -105,7 +709,7 @@ export class AutoOrchestrator {
|
|
|
105
709
|
findings: gate.fixesApplied?.join(", ") ?? "",
|
|
106
710
|
});
|
|
107
711
|
}
|
|
108
|
-
const reconciliation = await this.
|
|
712
|
+
const reconciliation = await this.reconcileBeforeDispatch();
|
|
109
713
|
if (!reconciliation.ok || !reconciliation.stateSnapshot) {
|
|
110
714
|
const blocked = {
|
|
111
715
|
kind: "blocked",
|
|
@@ -113,24 +717,37 @@ export class AutoOrchestrator {
|
|
|
113
717
|
action: "pause",
|
|
114
718
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
115
719
|
};
|
|
116
|
-
|
|
117
|
-
|
|
720
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
721
|
+
this.postAdvanceRecord(blocked);
|
|
118
722
|
return blocked;
|
|
119
723
|
}
|
|
120
|
-
const decision = await this.
|
|
724
|
+
const decision = await this.decideNextUnit({ stateSnapshot: reconciliation.stateSnapshot });
|
|
121
725
|
if (!decision) {
|
|
726
|
+
const settlementBlock = this.evaluateNoRemainingUnitsSettlement(reconciliation.stateSnapshot);
|
|
727
|
+
if (settlementBlock) {
|
|
728
|
+
this.status.phase = "paused";
|
|
729
|
+
this.status.activeUnit = undefined;
|
|
730
|
+
this.lastAdvanceKey = null;
|
|
731
|
+
this.dispatchKeyWindow = [];
|
|
732
|
+
this.bumpTransition();
|
|
733
|
+
this.journalTransition({ name: "advance-blocked", reason: settlementBlock.reason });
|
|
734
|
+
this.postAdvanceRecord(settlementBlock);
|
|
735
|
+
return settlementBlock;
|
|
736
|
+
}
|
|
737
|
+
const terminalOutcome = noRemainingUnitsOutcome(reconciliation.stateSnapshot);
|
|
122
738
|
const stopped = {
|
|
123
739
|
kind: "stopped",
|
|
124
|
-
reason:
|
|
740
|
+
reason: terminalOutcome.displayReason,
|
|
125
741
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
742
|
+
terminalOutcome,
|
|
126
743
|
};
|
|
127
744
|
this.status.phase = "stopped";
|
|
128
745
|
this.status.activeUnit = undefined;
|
|
129
746
|
this.lastAdvanceKey = null;
|
|
130
747
|
this.dispatchKeyWindow = [];
|
|
131
748
|
this.bumpTransition();
|
|
132
|
-
|
|
133
|
-
|
|
749
|
+
this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
|
|
750
|
+
this.postAdvanceRecord(stopped);
|
|
134
751
|
return stopped;
|
|
135
752
|
}
|
|
136
753
|
if ("kind" in decision && decision.kind === "skipped") {
|
|
@@ -142,8 +759,8 @@ export class AutoOrchestrator {
|
|
|
142
759
|
this.status.phase = "running";
|
|
143
760
|
this.status.activeUnit = undefined;
|
|
144
761
|
this.bumpTransition();
|
|
145
|
-
|
|
146
|
-
|
|
762
|
+
this.journalTransition({ name: "advance-skipped", reason: skipped.reason });
|
|
763
|
+
this.postAdvanceRecord(skipped);
|
|
147
764
|
return skipped;
|
|
148
765
|
}
|
|
149
766
|
if (!("unitType" in decision)) {
|
|
@@ -153,8 +770,26 @@ export class AutoOrchestrator {
|
|
|
153
770
|
action: decision.action,
|
|
154
771
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
155
772
|
};
|
|
156
|
-
|
|
157
|
-
|
|
773
|
+
this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
774
|
+
this.postAdvanceRecord(blocked);
|
|
775
|
+
return blocked;
|
|
776
|
+
}
|
|
777
|
+
const priorSliceBlocker = this.findPriorSliceCompletionBlocker(decision.unitType, decision.unitId);
|
|
778
|
+
if (priorSliceBlocker) {
|
|
779
|
+
this.clearPendingDispatch();
|
|
780
|
+
const blocked = {
|
|
781
|
+
kind: "blocked",
|
|
782
|
+
reason: priorSliceBlocker,
|
|
783
|
+
action: "stop",
|
|
784
|
+
stateSnapshot: reconciliation.stateSnapshot,
|
|
785
|
+
};
|
|
786
|
+
this.journalTransition({
|
|
787
|
+
name: "advance-blocked",
|
|
788
|
+
reason: blocked.reason,
|
|
789
|
+
unitType: decision.unitType,
|
|
790
|
+
unitId: decision.unitId,
|
|
791
|
+
});
|
|
792
|
+
this.postAdvanceRecord(blocked);
|
|
158
793
|
return blocked;
|
|
159
794
|
}
|
|
160
795
|
const nextKey = `${decision.unitType}:${decision.unitId}`;
|
|
@@ -168,19 +803,27 @@ export class AutoOrchestrator {
|
|
|
168
803
|
}
|
|
169
804
|
const matchingCount = this.dispatchKeyWindow.filter((k) => k === nextKey).length;
|
|
170
805
|
if (this.lastFinalizedUnitKey === nextKey) {
|
|
806
|
+
// #442: the unit re-dispatched immediately after finalizing may have
|
|
807
|
+
// actually completed on disk with a stale DB. Verify + recover before
|
|
808
|
+
// hard-stopping (legacy graduated stuck-recovery parity).
|
|
809
|
+
if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
|
|
810
|
+
this.clearPendingDispatch();
|
|
811
|
+
return this.stuckRecovered(decision, reconciliation.stateSnapshot);
|
|
812
|
+
}
|
|
813
|
+
this.clearPendingDispatch();
|
|
171
814
|
const blocked = {
|
|
172
815
|
kind: "blocked",
|
|
173
816
|
reason: `state did not advance after finalized ${decision.unitType} ${decision.unitId}`,
|
|
174
817
|
action: "stop",
|
|
175
818
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
176
819
|
};
|
|
177
|
-
|
|
820
|
+
this.journalTransition({
|
|
178
821
|
name: "advance-blocked",
|
|
179
822
|
reason: blocked.reason,
|
|
180
823
|
unitType: decision.unitType,
|
|
181
824
|
unitId: decision.unitId,
|
|
182
825
|
});
|
|
183
|
-
|
|
826
|
+
this.postAdvanceRecord(blocked);
|
|
184
827
|
return blocked;
|
|
185
828
|
}
|
|
186
829
|
// Idempotency: same key as immediately previous successful advance.
|
|
@@ -190,14 +833,15 @@ export class AutoOrchestrator {
|
|
|
190
833
|
// checks coexist: idempotency for the common immediate-repeat case,
|
|
191
834
|
// stuck-loop for the saturated-window case.
|
|
192
835
|
if (this.lastAdvanceKey === nextKey && matchingCount < STUCK_WINDOW_SIZE) {
|
|
836
|
+
this.clearPendingDispatch();
|
|
193
837
|
const blocked = { kind: "blocked", reason: "idempotent advance: unit already active", action: "pause" };
|
|
194
|
-
|
|
838
|
+
this.journalTransition({
|
|
195
839
|
name: "advance-blocked",
|
|
196
840
|
reason: blocked.reason,
|
|
197
841
|
unitType: decision.unitType,
|
|
198
842
|
unitId: decision.unitId,
|
|
199
843
|
});
|
|
200
|
-
|
|
844
|
+
this.postAdvanceRecord(blocked);
|
|
201
845
|
return blocked;
|
|
202
846
|
}
|
|
203
847
|
// Stuck-loop detection: when the ring is saturated with copies of
|
|
@@ -205,75 +849,85 @@ export class AutoOrchestrator {
|
|
|
205
849
|
// picking the same unit across the whole window and must hard-stop with
|
|
206
850
|
// a diagnosable reason.
|
|
207
851
|
if (matchingCount >= STUCK_WINDOW_SIZE) {
|
|
852
|
+
// #442: before declaring a stuck loop, verify the unit didn't actually
|
|
853
|
+
// complete on disk (stale DB) and recover if so — legacy graduated
|
|
854
|
+
// stuck-recovery parity. Otherwise hard-stop with a diagnosable reason.
|
|
855
|
+
if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
|
|
856
|
+
this.clearPendingDispatch();
|
|
857
|
+
return this.stuckRecovered(decision, reconciliation.stateSnapshot);
|
|
858
|
+
}
|
|
859
|
+
this.clearPendingDispatch();
|
|
208
860
|
const blocked = {
|
|
209
861
|
kind: "blocked",
|
|
210
862
|
reason: `stuck-loop: ${nextKey} picked ${matchingCount} times`,
|
|
211
863
|
action: "stop",
|
|
212
864
|
};
|
|
213
|
-
|
|
865
|
+
this.journalTransition({
|
|
214
866
|
name: "advance-blocked",
|
|
215
867
|
reason: blocked.reason,
|
|
216
868
|
unitType: decision.unitType,
|
|
217
869
|
unitId: decision.unitId,
|
|
218
870
|
});
|
|
219
|
-
|
|
871
|
+
this.postAdvanceRecord(blocked);
|
|
220
872
|
return blocked;
|
|
221
873
|
}
|
|
222
|
-
const contract =
|
|
874
|
+
const contract = this.compileUnitToolContract(decision.unitType);
|
|
223
875
|
if (!contract.ok) {
|
|
876
|
+
this.clearPendingDispatch();
|
|
224
877
|
const blocked = {
|
|
225
878
|
kind: "blocked",
|
|
226
879
|
reason: contract.reason,
|
|
227
880
|
action: "pause",
|
|
228
881
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
229
882
|
};
|
|
230
|
-
|
|
883
|
+
this.journalTransition({
|
|
231
884
|
name: "advance-blocked",
|
|
232
885
|
reason: blocked.reason,
|
|
233
886
|
unitType: decision.unitType,
|
|
234
887
|
unitId: decision.unitId,
|
|
235
888
|
});
|
|
236
|
-
|
|
889
|
+
this.postAdvanceRecord(blocked);
|
|
237
890
|
return blocked;
|
|
238
891
|
}
|
|
239
|
-
const worktree = await this.
|
|
892
|
+
const worktree = await this.prepareWorktreeForUnit(decision.unitType, decision.unitId);
|
|
240
893
|
if (!worktree.ok) {
|
|
894
|
+
this.clearPendingDispatch();
|
|
241
895
|
const blocked = {
|
|
242
896
|
kind: "blocked",
|
|
243
897
|
reason: worktree.reason,
|
|
244
898
|
action: "pause",
|
|
245
899
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
246
900
|
};
|
|
247
|
-
|
|
901
|
+
this.journalTransition({
|
|
248
902
|
name: "advance-blocked",
|
|
249
903
|
reason: blocked.reason,
|
|
250
904
|
unitType: decision.unitType,
|
|
251
905
|
unitId: decision.unitId,
|
|
252
906
|
});
|
|
253
|
-
|
|
907
|
+
this.postAdvanceRecord(blocked);
|
|
254
908
|
return blocked;
|
|
255
909
|
}
|
|
256
910
|
this.status.activeUnit = { unitType: decision.unitType, unitId: decision.unitId };
|
|
257
911
|
this.status.phase = "running";
|
|
258
912
|
this.lastAdvanceKey = nextKey;
|
|
259
913
|
this.bumpTransition();
|
|
260
|
-
|
|
914
|
+
this.journalTransition({
|
|
261
915
|
name: "advance",
|
|
262
916
|
reason: decision.reason,
|
|
263
917
|
unitType: decision.unitType,
|
|
264
918
|
unitId: decision.unitId,
|
|
265
919
|
});
|
|
266
|
-
|
|
920
|
+
// syncAfterUnit was a no-op in the wired WorktreeAdapter.
|
|
267
921
|
const advanced = {
|
|
268
922
|
kind: "advanced",
|
|
269
923
|
unit: { unitType: decision.unitType, unitId: decision.unitId },
|
|
270
924
|
stateSnapshot: reconciliation.stateSnapshot,
|
|
271
925
|
};
|
|
272
|
-
|
|
926
|
+
this.postAdvanceRecord(advanced);
|
|
273
927
|
return advanced;
|
|
274
928
|
}
|
|
275
929
|
catch (error) {
|
|
276
|
-
const recovery =
|
|
930
|
+
const recovery = this.classifyAndRecover({
|
|
277
931
|
error,
|
|
278
932
|
unitType: this.status.activeUnit?.unitType,
|
|
279
933
|
unitId: this.status.activeUnit?.unitId,
|
|
@@ -304,43 +958,53 @@ export class AutoOrchestrator {
|
|
|
304
958
|
: result.kind === "stopped"
|
|
305
959
|
? "advance-stopped"
|
|
306
960
|
: "advance-error";
|
|
307
|
-
|
|
961
|
+
this.journalTransition({ name: journalName, reason: recovery.reason });
|
|
308
962
|
if (result.kind === "paused") {
|
|
309
|
-
|
|
963
|
+
this.notifyLifecycle({ name: "pause", detail: recovery.reason });
|
|
310
964
|
}
|
|
311
965
|
else if (result.kind === "stopped") {
|
|
312
|
-
|
|
966
|
+
this.notifyLifecycle({ name: "stopped", detail: recovery.reason });
|
|
313
967
|
}
|
|
314
968
|
else if (result.kind === "error") {
|
|
315
|
-
|
|
969
|
+
this.notifyLifecycle({ name: "error", detail: recovery.reason });
|
|
316
970
|
}
|
|
317
|
-
|
|
971
|
+
this.postAdvanceRecord(result);
|
|
318
972
|
return result;
|
|
319
973
|
}
|
|
974
|
+
finally {
|
|
975
|
+
stopAdvanceTimer();
|
|
976
|
+
}
|
|
320
977
|
}
|
|
321
978
|
async resume() {
|
|
322
979
|
this.lastAdvanceKey = null;
|
|
323
980
|
this.lastFinalizedUnitKey = null;
|
|
324
|
-
|
|
981
|
+
// Preserve dispatchKeyWindow across resume so stuck-loop detection
|
|
982
|
+
// accumulates across pause/resume cycles rather than resetting each time.
|
|
983
|
+
this.lastStuckRecoveryKey = null;
|
|
325
984
|
this.status.phase = "running";
|
|
326
985
|
this.bumpTransition();
|
|
327
|
-
|
|
328
|
-
|
|
986
|
+
this.journalTransition({ name: "resume" });
|
|
987
|
+
this.notifyLifecycle({ name: "resume" });
|
|
329
988
|
return { kind: "resumed" };
|
|
330
989
|
}
|
|
331
990
|
async stop(reason) {
|
|
332
991
|
if (this.status.phase === "stopped") {
|
|
333
992
|
return { kind: "stopped", reason };
|
|
334
993
|
}
|
|
335
|
-
|
|
994
|
+
// cleanupOnStop was a no-op in the wired WorktreeAdapter.
|
|
336
995
|
this.status.phase = "stopped";
|
|
337
996
|
this.status.activeUnit = undefined;
|
|
338
997
|
this.lastAdvanceKey = null;
|
|
339
998
|
this.lastFinalizedUnitKey = null;
|
|
340
|
-
|
|
999
|
+
// Preserve dispatchKeyWindow on pause so stuck-loop detection accumulates
|
|
1000
|
+
// across pause/resume cycles. Only clear on a hard stop.
|
|
1001
|
+
if (reason !== "pause") {
|
|
1002
|
+
this.dispatchKeyWindow = [];
|
|
1003
|
+
}
|
|
1004
|
+
this.lastStuckRecoveryKey = null;
|
|
341
1005
|
this.bumpTransition();
|
|
342
|
-
|
|
343
|
-
|
|
1006
|
+
this.journalTransition({ name: "stop", reason });
|
|
1007
|
+
this.notifyLifecycle({ name: "stop", detail: reason });
|
|
344
1008
|
return { kind: "stopped", reason };
|
|
345
1009
|
}
|
|
346
1010
|
getStatus() {
|
|
@@ -356,8 +1020,10 @@ export class AutoOrchestrator {
|
|
|
356
1020
|
this.status.activeUnit = undefined;
|
|
357
1021
|
this.lastAdvanceKey = null;
|
|
358
1022
|
this.lastFinalizedUnitKey = unitKey;
|
|
1023
|
+
// Genuine progress — re-enable graduated stuck recovery for future episodes.
|
|
1024
|
+
this.lastStuckRecoveryKey = null;
|
|
359
1025
|
this.bumpTransition();
|
|
360
|
-
|
|
1026
|
+
this.journalTransition({
|
|
361
1027
|
name: "unit-finalized",
|
|
362
1028
|
unitType: unit.unitType,
|
|
363
1029
|
unitId: unit.unitId,
|
|
@@ -376,7 +1042,7 @@ export class AutoOrchestrator {
|
|
|
376
1042
|
this.lastAdvanceKey = null;
|
|
377
1043
|
this.lastFinalizedUnitKey = null;
|
|
378
1044
|
this.bumpTransition();
|
|
379
|
-
|
|
1045
|
+
this.journalTransition({
|
|
380
1046
|
name: "unit-retry",
|
|
381
1047
|
reason: "finalize-retry",
|
|
382
1048
|
unitType: unit.unitType,
|
|
@@ -388,6 +1054,40 @@ export class AutoOrchestrator {
|
|
|
388
1054
|
this.status.lastTransitionAt = now();
|
|
389
1055
|
}
|
|
390
1056
|
}
|
|
391
|
-
|
|
392
|
-
|
|
1057
|
+
function isUsableLiveOrchestratorBasePath(basePath) {
|
|
1058
|
+
if (!basePath || !existsSync(basePath))
|
|
1059
|
+
return false;
|
|
1060
|
+
if (!detectWorktreeName(basePath))
|
|
1061
|
+
return true;
|
|
1062
|
+
try {
|
|
1063
|
+
return readFileSync(join(basePath, ".git"), "utf8").trim().startsWith("gitdir: ");
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Resolve the base path the live orchestrator should dispatch from, falling
|
|
1071
|
+
* back to the project root when the captured worktree path has been removed
|
|
1072
|
+
* (e.g. after milestone-merge cleanup). Exported for the closeout-regression
|
|
1073
|
+
* tests and reused by the orchestrator's getLiveDispatchBasePath.
|
|
1074
|
+
*/
|
|
1075
|
+
export function resolveLiveOrchestratorBasePath(input) {
|
|
1076
|
+
const primary = input.sessionBasePath || input.capturedBasePath;
|
|
1077
|
+
if (isUsableLiveOrchestratorBasePath(primary))
|
|
1078
|
+
return primary;
|
|
1079
|
+
const fallbacks = [
|
|
1080
|
+
input.originalBasePath,
|
|
1081
|
+
input.runtimeBasePath,
|
|
1082
|
+
resolveProjectRoot(input.capturedBasePath),
|
|
1083
|
+
];
|
|
1084
|
+
for (const candidate of fallbacks) {
|
|
1085
|
+
if (candidate && isUsableLiveOrchestratorBasePath(candidate)) {
|
|
1086
|
+
return candidate;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return input.runtimeBasePath || input.capturedBasePath;
|
|
1090
|
+
}
|
|
1091
|
+
export function createAutoOrchestrator(context) {
|
|
1092
|
+
return new AutoOrchestrator(context);
|
|
393
1093
|
}
|