@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.0b870afa
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-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli-web-branch.d.ts +2 -0
- package/dist/cli-web-branch.js +9 -2
- package/dist/cli.js +1 -1
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +14 -34
- package/dist/help-text.js +5 -0
- package/dist/mcp-server.js +2 -1
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/project-sessions.js +4 -2
- package/dist/resource-loader.js +2 -14
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +5 -4
- package/dist/resources/extensions/ask-user-questions.js +78 -23
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
- package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
- package/dist/resources/extensions/async-jobs/index.js +65 -0
- package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
- package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
- package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
- package/dist/resources/extensions/bg-shell/overlay.js +9 -6
- package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
- package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
- package/dist/resources/extensions/bg-shell/utilities.js +5 -2
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +209 -88
- package/dist/resources/extensions/browser-tools/engine/selection.js +73 -5
- package/dist/resources/extensions/browser-tools/index.js +69 -12
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +139 -243
- 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/custom-verify-retry-store.js +17 -2
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
- package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
- package/dist/resources/extensions/gsd/auto/loop.js +77 -56
- package/dist/resources/extensions/gsd/auto/orchestrator.js +860 -96
- package/dist/resources/extensions/gsd/auto/phases.js +81 -8
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +16 -4
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
- package/dist/resources/extensions/gsd/auto-dispatch.js +56 -63
- package/dist/resources/extensions/gsd/auto-model-selection.js +44 -13
- package/dist/resources/extensions/gsd/auto-post-unit.js +47 -17
- package/dist/resources/extensions/gsd/auto-prompts.js +271 -27
- 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 +36 -49
- package/dist/resources/extensions/gsd/auto-timers.js +16 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +55 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +33 -37
- package/dist/resources/extensions/gsd/auto-verification.js +30 -37
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +67 -375
- package/dist/resources/extensions/gsd/auto.js +112 -486
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +29 -8
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +28 -37
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +43 -49
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +319 -161
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +365 -58
- package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
- package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
- package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
- package/dist/resources/extensions/gsd/captures.js +5 -15
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +21 -4
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/codebase-generator.js +8 -4
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +3 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +66 -3
- 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/consent-question.js +353 -0
- package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
- package/dist/resources/extensions/gsd/constants.js +0 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +12 -15
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +398 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- 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/dispatch-guard.js +10 -35
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
- package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
- package/dist/resources/extensions/gsd/doctor-format.js +9 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +6 -21
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
- package/dist/resources/extensions/gsd/doctor.js +16 -9
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/error-classifier.js +10 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
- package/dist/resources/extensions/gsd/files.js +33 -19
- package/dist/resources/extensions/gsd/git-conflict-state.js +16 -1
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +184 -2048
- package/dist/resources/extensions/gsd/guidance.js +158 -0
- package/dist/resources/extensions/gsd/guided-flow.js +91 -476
- package/dist/resources/extensions/gsd/guided-unit-completion.js +225 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +43 -33
- package/dist/resources/extensions/gsd/mcp-filter.js +10 -20
- package/dist/resources/extensions/gsd/mcp-tool-name.js +18 -0
- package/dist/resources/extensions/gsd/md-importer.js +4 -3
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +22 -11
- package/dist/resources/extensions/gsd/migration-auto-check.js +27 -5
- package/dist/resources/extensions/gsd/milestone-closeout-proof.js +72 -0
- package/dist/resources/extensions/gsd/milestone-closeout.js +97 -28
- 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-reopen-events.js +3 -5
- 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/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
- package/dist/resources/extensions/gsd/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/parallel-eligibility.js +3 -6
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -2
- package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
- package/dist/resources/extensions/gsd/paths.js +37 -24
- package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +67 -0
- package/dist/resources/extensions/gsd/preferences-models.js +14 -48
- package/dist/resources/extensions/gsd/preferences.js +161 -29
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -3
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
- 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 +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -3
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/dist/resources/extensions/gsd/prompts/system.md +6 -3
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +6 -4
- package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-payload-policy.js +83 -0
- package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
- package/dist/resources/extensions/gsd/publication.js +87 -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/reactive-graph.js +8 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
- package/dist/resources/extensions/gsd/roadmap-slices.js +33 -5
- package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -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-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/state.js +19 -25
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/stop-notice.js +57 -0
- 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 +66 -11
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +17 -36
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
- package/dist/resources/extensions/gsd/tool-surface-snapshot.js +17 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +46 -55
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/exec-tool.js +10 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +15 -143
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -8
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +41 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +13 -31
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +16 -35
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +15 -78
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +236 -22
- package/dist/resources/extensions/gsd/uat-policy.js +57 -25
- package/dist/resources/extensions/gsd/uat-run.js +9 -14
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +114 -21
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +337 -0
- package/dist/resources/extensions/gsd/unit-runtime.js +3 -2
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -181
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/gsd/web-app-uat.js +117 -0
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
- package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
- package/dist/resources/extensions/gsd/workflow-events.js +6 -18
- package/dist/resources/extensions/gsd/workflow-mcp.js +15 -102
- package/dist/resources/extensions/gsd/workflow-reconcile.js +25 -59
- 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-git-recovery.js +293 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +12 -3
- package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +28 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
- 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/search-the-web/native-search.js +5 -3
- package/dist/resources/extensions/shared/browser-contract.js +59 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +116 -6
- package/dist/resources/extensions/shared/interview-ui.js +2 -2
- package/dist/resources/shared/claude-runtime-floor.js +182 -0
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/resources/shared/package.json +3 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
- package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
- package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +40 -3
- 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/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/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/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/5942.js +2 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- 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.e0bdc932325d7e03.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-f0285ce91d4ec9ef.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/standalone/package.json +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +20 -8
- package/dist/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -11
- package/package.json +17 -11
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -0
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +4 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js.map +1 -1
- 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 +5 -0
- 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 +8 -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 +113 -40
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.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/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +9 -1
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- 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 +12 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +49 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +147 -60
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
- package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +3 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/README.md +1 -0
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +183 -110
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +205 -158
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -15
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
- package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
- package/packages/pi-coding-agent/dist/utils/shell.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/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +9 -0
- package/packages/pi-tui/dist/tui.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/GSD-WORKFLOW.md +5 -4
- package/src/resources/extensions/ask-user-questions.ts +87 -24
- package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
- package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
- package/src/resources/extensions/async-jobs/index.ts +79 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
- package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
- package/src/resources/extensions/bg-shell/overlay.ts +9 -5
- package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
- package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
- package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
- package/src/resources/extensions/bg-shell/utilities.ts +5 -2
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +265 -98
- package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
- package/src/resources/extensions/browser-tools/index.ts +71 -13
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +166 -293
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +270 -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/custom-verify-retry-store.ts +21 -3
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
- package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +3 -1
- package/src/resources/extensions/gsd/auto/loop.ts +86 -61
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1023 -98
- package/src/resources/extensions/gsd/auto/phases.ts +108 -28
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -1
- package/src/resources/extensions/gsd/auto/session.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +18 -4
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
- package/src/resources/extensions/gsd/auto-dispatch.ts +68 -68
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +56 -16
- package/src/resources/extensions/gsd/auto-prompts.ts +338 -44
- 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 +41 -49
- package/src/resources/extensions/gsd/auto-timers.ts +16 -2
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +59 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -38
- package/src/resources/extensions/gsd/auto-verification.ts +33 -36
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +83 -391
- package/src/resources/extensions/gsd/auto.ts +151 -524
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +37 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +29 -37
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +43 -49
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +379 -177
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +452 -58
- package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
- package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
- package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
- package/src/resources/extensions/gsd/captures.ts +5 -16
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +27 -5
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/codebase-generator.ts +9 -5
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +3 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +64 -3
- 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/consent-question.ts +431 -0
- package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
- package/src/resources/extensions/gsd/constants.ts +0 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +13 -11
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +490 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- 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/dispatch-guard.ts +8 -31
- package/src/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
- package/src/resources/extensions/gsd/doctor-format.ts +12 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +5 -22
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
- package/src/resources/extensions/gsd/doctor.ts +15 -5
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/error-classifier.ts +12 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/files.ts +33 -12
- package/src/resources/extensions/gsd/git-conflict-state.ts +17 -1
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +188 -2375
- package/src/resources/extensions/gsd/guidance.ts +217 -0
- package/src/resources/extensions/gsd/guided-flow.ts +118 -589
- package/src/resources/extensions/gsd/guided-unit-completion.ts +275 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +51 -20
- package/src/resources/extensions/gsd/mcp-filter.ts +11 -24
- package/src/resources/extensions/gsd/mcp-tool-name.ts +30 -0
- package/src/resources/extensions/gsd/md-importer.ts +3 -3
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +20 -9
- package/src/resources/extensions/gsd/migration-auto-check.ts +30 -5
- package/src/resources/extensions/gsd/milestone-closeout-proof.ts +131 -0
- package/src/resources/extensions/gsd/milestone-closeout.ts +121 -28
- 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-reopen-events.ts +3 -6
- 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/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
- package/src/resources/extensions/gsd/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/parallel-eligibility.ts +4 -5
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -2
- package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
- package/src/resources/extensions/gsd/paths.ts +42 -22
- package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +98 -0
- package/src/resources/extensions/gsd/preferences-models.ts +12 -47
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +191 -28
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
- 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 +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -3
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/src/resources/extensions/gsd/prompts/system.md +6 -3
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +6 -4
- package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-payload-policy.ts +140 -0
- package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
- package/src/resources/extensions/gsd/publication.ts +122 -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/reactive-graph.ts +11 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
- package/src/resources/extensions/gsd/roadmap-slices.ts +36 -5
- package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/session-lock.ts +1 -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-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/state.ts +24 -26
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/stop-notice.ts +75 -0
- 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 +444 -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-model-selection.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +894 -858
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +41 -11
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
- package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -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 +46 -8
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
- package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +34 -3
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
- 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/extension-bootstrap-isolation.test.ts +35 -1
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- 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/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guidance.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +58 -15
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +74 -59
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/mcp-tool-name.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +143 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout-proof.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +120 -4
- 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/model-unittype-mapping.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -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/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
- 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/prompt-db.test.ts +124 -6
- package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
- 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/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pull-request-process.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +342 -1
- 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/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +144 -7
- 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/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/tool-availability-audit.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +35 -42
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +112 -6
- package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +114 -2
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +128 -11
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +275 -40
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-projection-writers.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +27 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
- 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-seam.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +188 -1
- package/src/resources/extensions/gsd/tool-contract.ts +124 -11
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +18 -35
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
- package/src/resources/extensions/gsd/tool-surface-snapshot.ts +47 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +45 -70
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/exec-tool.ts +9 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +19 -160
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -8
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +45 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +13 -40
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +16 -44
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +25 -84
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +264 -23
- package/src/resources/extensions/gsd/uat-policy.ts +80 -25
- package/src/resources/extensions/gsd/uat-run.ts +10 -14
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +196 -21
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +412 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +3 -2
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -191
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/gsd/web-app-uat.ts +144 -0
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
- package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
- package/src/resources/extensions/gsd/workflow-events.ts +12 -20
- package/src/resources/extensions/gsd/workflow-mcp.ts +22 -110
- package/src/resources/extensions/gsd/workflow-reconcile.ts +32 -65
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +76 -0
- package/src/resources/extensions/gsd/workspace-git-guard.ts +1 -0
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -25
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +29 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
- 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/search-the-web/native-search.ts +5 -3
- package/src/resources/extensions/shared/browser-contract.ts +66 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +141 -6
- package/src/resources/extensions/shared/interview-ui.ts +15 -2
- package/src/resources/shared/claude-runtime-floor.ts +248 -0
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- package/src/resources/shared/package.json +3 -0
- package/src/resources/skills/create-skill/references/executable-code.md +1 -1
- package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/src/resources/skills/gsd-browser/SKILL.md +1 -1
- package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/resources/extensions/gsd/user-input-boundary.js +0 -157
- 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/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -26
- package/src/resources/extensions/gsd/user-input-boundary.ts +0 -166
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → T-LTxEw5wir5Lm5T3qEVd}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → T-LTxEw5wir5Lm5T3qEVd}/_ssgManifest.js +0 -0
|
@@ -1,26 +1,207 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Auto Orchestration module contract and ADR-015 invariant sequence tests.
|
|
3
|
+
//
|
|
4
|
+
// Phase 2 of #442 collapsed the nine adapter seams into AutoOrchestrator. These
|
|
5
|
+
// tests therefore drive the REAL collapsed orchestrator against real temp
|
|
6
|
+
// SQLite + git fixtures (fixture builder modelled on
|
|
7
|
+
// state-reconciliation-drift.test.ts) and inject dispatch decisions through the
|
|
8
|
+
// real unified rule registry (setRegistry) rather than mock adapters. Decision
|
|
9
|
+
// logic is asserted on observable advance() outcomes and journal events instead
|
|
10
|
+
// of an internal calls[] array. Dispatch-decision parity (formerly the
|
|
11
|
+
// createWiredDispatchAdapter tests) is asserted against the exported pure
|
|
12
|
+
// decideOrchestratorDispatch helper.
|
|
3
13
|
|
|
4
14
|
import test from "node:test";
|
|
5
15
|
import assert from "node:assert/strict";
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
6
17
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
18
|
import { tmpdir } from "node:os";
|
|
8
19
|
import { join } from "node:path";
|
|
9
20
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
21
|
+
import {
|
|
22
|
+
createAutoOrchestrator,
|
|
23
|
+
decideOrchestratorDispatch,
|
|
24
|
+
resolveLiveOrchestratorBasePath,
|
|
25
|
+
} from "../auto/orchestrator.js";
|
|
26
|
+
import { STUCK_WINDOW_SIZE } from "../auto/dispatch-history.js";
|
|
27
|
+
import type { OrchestratorContext } from "../auto/orchestrator.js";
|
|
28
|
+
import type { AutoOrchestrationModule, AutoSessionContext } from "../auto/contracts.js";
|
|
12
29
|
import type { GSDState } from "../types.js";
|
|
13
|
-
import { createWiredDispatchAdapter, resolveLiveOrchestratorBasePath } from "../auto.js";
|
|
14
30
|
import { resolveDispatch, type DispatchContext } from "../auto-dispatch.js";
|
|
15
31
|
import { RuleRegistry, setRegistry, resetRegistry } from "../rule-registry.js";
|
|
16
32
|
import type { UnifiedRule } from "../rule-types.js";
|
|
17
33
|
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
18
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
closeDatabase,
|
|
36
|
+
insertAssessment,
|
|
37
|
+
insertGateRow,
|
|
38
|
+
insertMilestone,
|
|
39
|
+
insertSlice,
|
|
40
|
+
insertTask,
|
|
41
|
+
openDatabase,
|
|
42
|
+
} from "../gsd-db.js";
|
|
43
|
+
import { AutoSession } from "../auto/session.js";
|
|
44
|
+
import { registerAutoWorker } from "../db/auto-workers.js";
|
|
45
|
+
import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
46
|
+
import { recordDispatchClaim, markFailed } from "../db/unit-dispatches.js";
|
|
47
|
+
import { normalizeRealPath } from "../paths.js";
|
|
48
|
+
import { acquireSessionLock, releaseSessionLock } from "../session-lock.js";
|
|
49
|
+
import { queryJournal } from "../journal.js";
|
|
50
|
+
import { invalidateAllCaches } from "../cache.js";
|
|
51
|
+
import { invalidateStateCache } from "../state.js";
|
|
52
|
+
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
// Fixture builder
|
|
55
|
+
//
|
|
56
|
+
// Builds a real, isolated project: a git repo (so the pre-dispatch health gate
|
|
57
|
+
// and merge-state reconciliation have something real to probe), a SQLite DB
|
|
58
|
+
// seeded with one active milestone/slice/task, and the matching ROADMAP/PLAN
|
|
59
|
+
// markdown projection. A real session lock is acquired so the orchestrator's
|
|
60
|
+
// ensureLockOwnership passes. A fresh AutoSession is wired to the base path. A
|
|
61
|
+
// dispatch rule is installed in the real unified registry so resolveDispatch
|
|
62
|
+
// yields a deterministic decision — this is the only "injection", and it is the
|
|
63
|
+
// same public seam (setRegistry) the dispatch engine already exposes.
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
type DispatchRuleResult =
|
|
67
|
+
| { action: "dispatch"; unitType: string; unitId: string; prompt: string; pauseAfterDispatch?: boolean }
|
|
68
|
+
| { action: "stop"; reason: string; level: "info" | "warning" | "error" }
|
|
69
|
+
| { action: "skip"; matchedRule?: string };
|
|
70
|
+
|
|
71
|
+
interface FixtureOptions {
|
|
72
|
+
/** When provided, the rule returns this result. Defaults to dispatching M001/S01/T01. */
|
|
73
|
+
dispatch?: () => DispatchRuleResult | Promise<DispatchRuleResult>;
|
|
74
|
+
/** Rule name (becomes the dispatch `reason`/`matchedRule`). */
|
|
75
|
+
ruleName?: string;
|
|
76
|
+
/** Skip seeding a ready task (used for the "no remaining units" / complete scenarios). */
|
|
77
|
+
noTask?: boolean;
|
|
78
|
+
/** Mark the seeded milestone complete (drives the completion → stopped path). */
|
|
79
|
+
complete?: boolean;
|
|
80
|
+
}
|
|
19
81
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
82
|
+
interface Fixture {
|
|
83
|
+
base: string;
|
|
84
|
+
session: AutoSession;
|
|
85
|
+
ctx: OrchestratorContext;
|
|
86
|
+
orchestrator: AutoOrchestrationModule;
|
|
87
|
+
/** Names emitted to the journal by the orchestrator (data.name), in order. */
|
|
88
|
+
journalNames(): string[];
|
|
89
|
+
cleanup(): void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const DEFAULT_DISPATCH: DispatchRuleResult = {
|
|
93
|
+
action: "dispatch",
|
|
94
|
+
unitType: "execute-task",
|
|
95
|
+
unitId: "M001/S01/T01",
|
|
96
|
+
prompt: "fixture-prompt",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function gitInit(base: string): void {
|
|
100
|
+
execFileSync("git", ["init", "--initial-branch=main"], { cwd: base, stdio: "ignore" });
|
|
101
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
102
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
103
|
+
writeFileSync(join(base, ".gitkeep"), "");
|
|
104
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
105
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd: base, stdio: "ignore" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function makeFixture(opts: FixtureOptions = {}): Fixture {
|
|
109
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-"));
|
|
110
|
+
gitInit(base);
|
|
111
|
+
|
|
112
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
113
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
114
|
+
mkdirSync(join(sliceDir, "tasks"), { recursive: true });
|
|
115
|
+
|
|
116
|
+
invalidateAllCaches();
|
|
117
|
+
invalidateStateCache();
|
|
118
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
119
|
+
insertMilestone({ id: "M001", title: "Milestone", status: opts.complete ? "complete" : "active" });
|
|
120
|
+
if (!opts.noTask && !opts.complete) {
|
|
121
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "low", depends: [], demo: "", sequence: 1 });
|
|
122
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Task", status: "active" });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
writeFileSync(
|
|
126
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
127
|
+
[
|
|
128
|
+
"# M001: Milestone",
|
|
129
|
+
"",
|
|
130
|
+
"**Vision:** Fixture milestone",
|
|
131
|
+
"",
|
|
132
|
+
"## Slices",
|
|
133
|
+
"",
|
|
134
|
+
"- [ ] **S01: Slice** `risk:low` `depends:[]`",
|
|
135
|
+
"",
|
|
136
|
+
].join("\n"),
|
|
137
|
+
);
|
|
138
|
+
if (!opts.noTask && !opts.complete) {
|
|
139
|
+
writeFileSync(
|
|
140
|
+
join(sliceDir, "S01-PLAN.md"),
|
|
141
|
+
[
|
|
142
|
+
"# S01: Slice",
|
|
143
|
+
"",
|
|
144
|
+
"**Goal:** Fixture goal",
|
|
145
|
+
"**Demo:** Fixture demo",
|
|
146
|
+
"",
|
|
147
|
+
"## Must-Haves",
|
|
148
|
+
"",
|
|
149
|
+
"- Everything works",
|
|
150
|
+
"",
|
|
151
|
+
"## Tasks",
|
|
152
|
+
"",
|
|
153
|
+
"- [ ] **T01: Task** `est:1h`",
|
|
154
|
+
"",
|
|
155
|
+
].join("\n"),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
acquireSessionLock(base);
|
|
160
|
+
|
|
161
|
+
const session = new AutoSession();
|
|
162
|
+
session.basePath = base;
|
|
163
|
+
session.originalBasePath = base;
|
|
164
|
+
session.currentMilestoneId = "M001";
|
|
165
|
+
session.resourceVersionOnStart = null;
|
|
166
|
+
|
|
167
|
+
const ctx: OrchestratorContext = {
|
|
168
|
+
ctx: { model: {}, modelRegistry: { getAll: () => [] }, ui: { notify() {} } } as never,
|
|
169
|
+
pi: { getActiveTools: () => [] } as never,
|
|
170
|
+
dispatchBasePath: base,
|
|
171
|
+
runtimeBasePath: base,
|
|
172
|
+
session,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const ruleName = opts.ruleName ?? "fixture-dispatch";
|
|
176
|
+
const decide = opts.dispatch ?? (() => DEFAULT_DISPATCH);
|
|
177
|
+
const rule: UnifiedRule = {
|
|
178
|
+
name: ruleName,
|
|
179
|
+
when: "dispatch",
|
|
180
|
+
evaluation: "first-match",
|
|
181
|
+
where: async () => decide(),
|
|
182
|
+
then: (r: unknown) => r,
|
|
183
|
+
};
|
|
184
|
+
setRegistry(new RuleRegistry([rule]));
|
|
185
|
+
|
|
186
|
+
const orchestrator = createAutoOrchestrator(ctx);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
base,
|
|
190
|
+
session,
|
|
191
|
+
ctx,
|
|
192
|
+
orchestrator,
|
|
193
|
+
journalNames() {
|
|
194
|
+
return queryJournal(base)
|
|
195
|
+
.map((e) => (e.data as Record<string, unknown> | undefined)?.name)
|
|
196
|
+
.filter((n): n is string => typeof n === "string");
|
|
197
|
+
},
|
|
198
|
+
cleanup() {
|
|
199
|
+
resetRegistry();
|
|
200
|
+
try { releaseSessionLock(base); } catch { /* */ }
|
|
201
|
+
try { closeDatabase(); } catch { /* */ }
|
|
202
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
203
|
+
},
|
|
204
|
+
};
|
|
24
205
|
}
|
|
25
206
|
|
|
26
207
|
function makeState(): GSDState {
|
|
@@ -38,1002 +219,771 @@ function makeState(): GSDState {
|
|
|
38
219
|
};
|
|
39
220
|
}
|
|
40
221
|
|
|
41
|
-
|
|
42
|
-
const calls: string[] = [];
|
|
43
|
-
const stateSnapshot = makeState();
|
|
222
|
+
const SESSION_CONTEXT: AutoSessionContext = { basePath: "/tmp/project", trigger: "manual" };
|
|
44
223
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
calls.push("state.reconcile");
|
|
49
|
-
return { ok: true, stateSnapshot };
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
dispatch: {
|
|
53
|
-
async decideNextUnit(input) {
|
|
54
|
-
calls.push("dispatch.decide");
|
|
55
|
-
assert.equal(input.stateSnapshot, stateSnapshot);
|
|
56
|
-
return { unitType: "execute-task", unitId: "T01", reason: "ready", preconditions: [] };
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
toolContract: {
|
|
60
|
-
async compileUnitToolContract() {
|
|
61
|
-
calls.push("tool.compile");
|
|
62
|
-
return { ok: true };
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
recovery: {
|
|
66
|
-
async classifyAndRecover() {
|
|
67
|
-
calls.push("recovery.classify");
|
|
68
|
-
return { action: "stop", reason: "fatal" };
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
worktree: {
|
|
72
|
-
async prepareForUnit() {
|
|
73
|
-
calls.push("worktree.prepare");
|
|
74
|
-
return { ok: true };
|
|
75
|
-
},
|
|
76
|
-
async syncAfterUnit() { calls.push("worktree.sync"); },
|
|
77
|
-
async cleanupOnStop() { calls.push("worktree.cleanup"); },
|
|
78
|
-
},
|
|
79
|
-
health: {
|
|
80
|
-
checkResourcesStale() {
|
|
81
|
-
calls.push("health.stale");
|
|
82
|
-
return null;
|
|
83
|
-
},
|
|
84
|
-
async preAdvanceGate() {
|
|
85
|
-
calls.push("health.pre");
|
|
86
|
-
return { kind: "pass" };
|
|
87
|
-
},
|
|
88
|
-
async postAdvanceRecord() { calls.push("health.post"); },
|
|
89
|
-
},
|
|
90
|
-
runtime: {
|
|
91
|
-
async ensureLockOwnership() { calls.push("runtime.lock"); },
|
|
92
|
-
async journalTransition(event) { calls.push(`journal:${event.name}`); },
|
|
93
|
-
},
|
|
94
|
-
notifications: {
|
|
95
|
-
async notifyLifecycle(event) { calls.push(`notify:${event.name}`); },
|
|
96
|
-
},
|
|
97
|
-
uokGate: {
|
|
98
|
-
async emit(input) { calls.push(`gate:${input.gateId}:${input.outcome}`); },
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
return { deps: { ...deps, ...overrides }, calls };
|
|
103
|
-
}
|
|
224
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
225
|
+
// Lifecycle: start / resume / stop
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
227
|
|
|
105
|
-
test("start() enters running phase without dispatching", async () => {
|
|
106
|
-
const
|
|
107
|
-
|
|
228
|
+
test("start() enters running phase without dispatching", async (t) => {
|
|
229
|
+
const f = makeFixture();
|
|
230
|
+
t.after(() => f.cleanup());
|
|
108
231
|
|
|
109
|
-
const result = await orchestrator.start(
|
|
232
|
+
const result = await f.orchestrator.start(SESSION_CONTEXT);
|
|
110
233
|
|
|
111
234
|
assert.equal(result.kind, "started");
|
|
112
|
-
const status = orchestrator.getStatus();
|
|
235
|
+
const status = f.orchestrator.getStatus();
|
|
113
236
|
assert.equal(status.phase, "running");
|
|
114
237
|
assert.equal(status.activeUnit, undefined);
|
|
115
|
-
assert.ok(
|
|
116
|
-
assert.ok(!
|
|
238
|
+
assert.ok(f.journalNames().includes("start"));
|
|
239
|
+
assert.ok(!f.journalNames().includes("advance"));
|
|
117
240
|
});
|
|
118
241
|
|
|
119
|
-
test("
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
checkResourcesStale: () => null,
|
|
123
|
-
async preAdvanceGate() { return { kind: "fail", reason: "doctor-block" }; },
|
|
124
|
-
async postAdvanceRecord() {},
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
242
|
+
test("resume() enters running phase without dispatching", async (t) => {
|
|
243
|
+
const f = makeFixture();
|
|
244
|
+
t.after(() => f.cleanup());
|
|
128
245
|
|
|
129
|
-
const result = await orchestrator.
|
|
246
|
+
const result = await f.orchestrator.resume();
|
|
130
247
|
|
|
131
|
-
|
|
132
|
-
assert.equal(
|
|
133
|
-
assert.
|
|
134
|
-
assert.ok(calls.includes("gate:pre-dispatch-health-gate:manual-attention"));
|
|
248
|
+
assert.equal(result.kind, "resumed");
|
|
249
|
+
assert.equal(f.orchestrator.getStatus().phase, "running");
|
|
250
|
+
assert.ok(!f.journalNames().includes("advance"));
|
|
135
251
|
});
|
|
136
252
|
|
|
137
|
-
test("
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
checkResourcesStale: () => null,
|
|
141
|
-
async preAdvanceGate() {
|
|
142
|
-
return { kind: "fail", reason: "Could not verify git conflict state", action: "stop" };
|
|
143
|
-
},
|
|
144
|
-
async postAdvanceRecord() {},
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
253
|
+
test("transitionCount increases across lifecycle transitions", async (t) => {
|
|
254
|
+
const f = makeFixture();
|
|
255
|
+
t.after(() => f.cleanup());
|
|
148
256
|
|
|
149
|
-
const
|
|
257
|
+
const before = f.orchestrator.getStatus().transitionCount;
|
|
258
|
+
await f.orchestrator.start(SESSION_CONTEXT);
|
|
259
|
+
const afterStart = f.orchestrator.getStatus().transitionCount;
|
|
260
|
+
await f.orchestrator.stop("done");
|
|
261
|
+
const afterStop = f.orchestrator.getStatus().transitionCount;
|
|
150
262
|
|
|
151
|
-
|
|
152
|
-
assert.
|
|
263
|
+
assert.ok(afterStart > before);
|
|
264
|
+
assert.ok(afterStop > afterStart);
|
|
153
265
|
});
|
|
154
266
|
|
|
155
|
-
test("
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
checkResourcesStale: () => "resources changed since session start",
|
|
159
|
-
async preAdvanceGate() { return { kind: "pass" }; },
|
|
160
|
-
async postAdvanceRecord() {},
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
267
|
+
test("stop() transitions to stopped and journals stop", async (t) => {
|
|
268
|
+
const f = makeFixture();
|
|
269
|
+
t.after(() => f.cleanup());
|
|
164
270
|
|
|
165
|
-
const result = await orchestrator.
|
|
271
|
+
const result = await f.orchestrator.stop("user-request");
|
|
166
272
|
|
|
167
|
-
|
|
168
|
-
assert.equal(
|
|
169
|
-
assert.
|
|
170
|
-
assert.ok(calls.includes("gate:resource-version-guard:fail"));
|
|
171
|
-
assert.ok(!calls.includes("health.pre"));
|
|
172
|
-
assert.ok(!calls.includes("state.reconcile"));
|
|
273
|
+
assert.equal(result.kind, "stopped");
|
|
274
|
+
assert.equal(f.orchestrator.getStatus().phase, "stopped");
|
|
275
|
+
assert.ok(f.journalNames().includes("stop"));
|
|
173
276
|
});
|
|
174
277
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
staleMsg: string | null;
|
|
179
|
-
gateResult: Awaited<ReturnType<AutoOrchestratorDeps["health"]["preAdvanceGate"]>>;
|
|
180
|
-
expectedKind: "advanced" | "blocked";
|
|
181
|
-
expectedAction?: "pause" | "stop";
|
|
182
|
-
expectedReason?: string;
|
|
183
|
-
expectedGates: string[];
|
|
184
|
-
};
|
|
185
|
-
const scenarios: Scenario[] = [
|
|
186
|
-
{
|
|
187
|
-
name: "pass",
|
|
188
|
-
staleMsg: null,
|
|
189
|
-
gateResult: { kind: "pass" },
|
|
190
|
-
expectedKind: "advanced",
|
|
191
|
-
expectedGates: [
|
|
192
|
-
"resource-version-guard:policy:pass:none:resource version guard passed:",
|
|
193
|
-
"pre-dispatch-health-gate:execution:pass:none:pre-dispatch health gate passed:",
|
|
194
|
-
],
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
name: "resource-stale",
|
|
198
|
-
staleMsg: "resources changed since session start",
|
|
199
|
-
gateResult: { kind: "pass" },
|
|
200
|
-
expectedKind: "blocked",
|
|
201
|
-
expectedAction: "pause",
|
|
202
|
-
expectedReason: "resources changed since session start",
|
|
203
|
-
expectedGates: [
|
|
204
|
-
"resource-version-guard:policy:fail:policy:resource version guard blocked dispatch:resources changed since session start",
|
|
205
|
-
],
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: "health-gate-fail",
|
|
209
|
-
staleMsg: null,
|
|
210
|
-
gateResult: { kind: "fail", reason: "doctor-block" },
|
|
211
|
-
expectedKind: "blocked",
|
|
212
|
-
expectedAction: "pause",
|
|
213
|
-
expectedReason: "doctor-block",
|
|
214
|
-
expectedGates: [
|
|
215
|
-
"resource-version-guard:policy:pass:none:resource version guard passed:",
|
|
216
|
-
"pre-dispatch-health-gate:execution:manual-attention:manual-attention:pre-dispatch health gate blocked dispatch:doctor-block",
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
];
|
|
220
|
-
|
|
221
|
-
for (const scenario of scenarios) {
|
|
222
|
-
const gateEvents: string[] = [];
|
|
223
|
-
const { deps } = makeDeps({
|
|
224
|
-
health: {
|
|
225
|
-
checkResourcesStale: () => scenario.staleMsg,
|
|
226
|
-
async preAdvanceGate() { return scenario.gateResult; },
|
|
227
|
-
async postAdvanceRecord() {},
|
|
228
|
-
},
|
|
229
|
-
uokGate: {
|
|
230
|
-
async emit(input) {
|
|
231
|
-
gateEvents.push(
|
|
232
|
-
`${input.gateId}:${input.gateType}:${input.outcome}:${input.failureClass}:${input.rationale}:${input.findings ?? ""}`,
|
|
233
|
-
);
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
238
|
-
const result = await orchestrator.advance();
|
|
239
|
-
|
|
240
|
-
assert.equal(result.kind, scenario.expectedKind, `${scenario.name} result kind`);
|
|
241
|
-
if (scenario.expectedKind === "blocked") {
|
|
242
|
-
assertBlockedResult(result);
|
|
243
|
-
assert.equal(result.action, scenario.expectedAction, `${scenario.name} blocked action`);
|
|
244
|
-
assert.equal(result.reason, scenario.expectedReason, `${scenario.name} blocked reason`);
|
|
245
|
-
}
|
|
246
|
-
assert.deepEqual(gateEvents, scenario.expectedGates, `${scenario.name} gate parity`);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
278
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
// advance(): happy path + ADR-015 invariant sequence
|
|
280
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
281
|
|
|
250
|
-
test("advance()
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
checkResourcesStale: () => null,
|
|
254
|
-
async preAdvanceGate() { return { kind: "threw", error: new Error("boom") }; },
|
|
255
|
-
async postAdvanceRecord() {},
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
282
|
+
test("advance() dispatches the resolved unit and journals advance", async (t) => {
|
|
283
|
+
const f = makeFixture();
|
|
284
|
+
t.after(() => f.cleanup());
|
|
259
285
|
|
|
260
|
-
const result = await orchestrator.advance();
|
|
286
|
+
const result = await f.orchestrator.advance();
|
|
261
287
|
|
|
262
288
|
assert.equal(result.kind, "advanced");
|
|
263
|
-
|
|
264
|
-
assert.
|
|
265
|
-
assert.
|
|
289
|
+
if (result.kind !== "advanced") return;
|
|
290
|
+
assert.deepEqual(result.unit, { unitType: "execute-task", unitId: "M001/S01/T01" });
|
|
291
|
+
assert.equal(f.orchestrator.getStatus().phase, "running");
|
|
292
|
+
// Journal records the advance AFTER the invariant gates (lock, health,
|
|
293
|
+
// reconcile, dispatch, tool-contract, worktree) — i.e. no advance-blocked.
|
|
294
|
+
const names = f.journalNames();
|
|
295
|
+
assert.ok(names.includes("advance"));
|
|
296
|
+
assert.ok(!names.includes("advance-blocked"));
|
|
266
297
|
});
|
|
267
298
|
|
|
268
|
-
test("advance()
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
health: {
|
|
272
|
-
checkResourcesStale: () => null,
|
|
273
|
-
async preAdvanceGate() { return { kind: "pass", fixesApplied: ["fix-a", "fix-b"] }; },
|
|
274
|
-
async postAdvanceRecord() {},
|
|
275
|
-
},
|
|
276
|
-
uokGate: {
|
|
277
|
-
async emit(input) {
|
|
278
|
-
if (input.gateId === "pre-dispatch-health-gate" && input.outcome === "pass") {
|
|
279
|
-
observed = input.findings ?? "";
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
299
|
+
test("advance() sets active unit and is reflected in status", async (t) => {
|
|
300
|
+
const f = makeFixture();
|
|
301
|
+
t.after(() => f.cleanup());
|
|
285
302
|
|
|
286
|
-
await orchestrator.advance();
|
|
303
|
+
await f.orchestrator.advance();
|
|
287
304
|
|
|
288
|
-
assert.
|
|
305
|
+
assert.deepEqual(f.orchestrator.getStatus().activeUnit, {
|
|
306
|
+
unitType: "execute-task",
|
|
307
|
+
unitId: "M001/S01/T01",
|
|
308
|
+
});
|
|
289
309
|
});
|
|
290
310
|
|
|
291
|
-
test("advance()
|
|
292
|
-
const
|
|
293
|
-
|
|
311
|
+
test("advance() blocks source dispatch when an earlier slice is incomplete", async (t) => {
|
|
312
|
+
const f = makeFixture({
|
|
313
|
+
dispatch: () => ({
|
|
314
|
+
action: "dispatch",
|
|
315
|
+
unitType: "execute-task",
|
|
316
|
+
unitId: "M001/S02/T01",
|
|
317
|
+
prompt: "fixture-prompt",
|
|
318
|
+
}),
|
|
319
|
+
});
|
|
320
|
+
t.after(() => f.cleanup());
|
|
321
|
+
|
|
322
|
+
insertSlice({
|
|
323
|
+
id: "S02",
|
|
324
|
+
milestoneId: "M001",
|
|
325
|
+
title: "Second slice",
|
|
326
|
+
status: "active",
|
|
327
|
+
risk: "low",
|
|
328
|
+
depends: [],
|
|
329
|
+
demo: "",
|
|
330
|
+
sequence: 2,
|
|
331
|
+
});
|
|
332
|
+
insertTask({
|
|
333
|
+
id: "T01",
|
|
334
|
+
sliceId: "S02",
|
|
335
|
+
milestoneId: "M001",
|
|
336
|
+
title: "Second task",
|
|
337
|
+
status: "active",
|
|
338
|
+
});
|
|
294
339
|
|
|
295
|
-
const result = await orchestrator.advance();
|
|
340
|
+
const result = await f.orchestrator.advance();
|
|
296
341
|
|
|
297
|
-
assert.equal(result.kind, "
|
|
298
|
-
|
|
299
|
-
assert.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"gate:pre-dispatch-health-gate:pass",
|
|
305
|
-
"state.reconcile",
|
|
306
|
-
"dispatch.decide",
|
|
307
|
-
"tool.compile",
|
|
308
|
-
"worktree.prepare",
|
|
309
|
-
"journal:advance",
|
|
310
|
-
"worktree.sync",
|
|
311
|
-
"health.post",
|
|
312
|
-
]);
|
|
342
|
+
assert.equal(result.kind, "blocked");
|
|
343
|
+
if (result.kind !== "blocked") return;
|
|
344
|
+
assert.equal(result.action, "stop");
|
|
345
|
+
assert.match(result.reason, /earlier slice M001\/S01 is not complete/);
|
|
346
|
+
assert.equal(f.session.pendingOrchestrationDispatch, null);
|
|
347
|
+
assert.deepEqual(f.orchestrator.getStatus().activeUnit, undefined);
|
|
348
|
+
assert.ok(f.journalNames().includes("advance-blocked"));
|
|
313
349
|
});
|
|
314
350
|
|
|
315
|
-
test("
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
async reconcileBeforeDispatch() {
|
|
319
|
-
calls.push("state.reconcile");
|
|
320
|
-
return { ok: false, reason: "state drift blocked", stateSnapshot: makeState() };
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
351
|
+
test("getStatus() returns defensive copy of activeUnit", async (t) => {
|
|
352
|
+
const f = makeFixture();
|
|
353
|
+
t.after(() => f.cleanup());
|
|
325
354
|
|
|
326
|
-
|
|
355
|
+
await f.orchestrator.advance();
|
|
356
|
+
const snap1 = f.orchestrator.getStatus();
|
|
357
|
+
if (snap1.activeUnit) snap1.activeUnit.unitId = "MUTATED";
|
|
358
|
+
const snap2 = f.orchestrator.getStatus();
|
|
327
359
|
|
|
328
|
-
|
|
329
|
-
assert.equal(result.reason, "state drift blocked");
|
|
330
|
-
assert.equal(result.action, "pause");
|
|
331
|
-
assert.ok(!calls.includes("dispatch.decide"));
|
|
332
|
-
assert.ok(calls.includes("journal:advance-blocked"));
|
|
360
|
+
assert.equal(snap2.activeUnit?.unitId, "M001/S01/T01");
|
|
333
361
|
});
|
|
334
362
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
363
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
364
|
+
// Dispatch passthrough decisions (skip / blocked / no-remaining-units)
|
|
365
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
test("advance() keeps running when dispatch intentionally skips a phase", async (t) => {
|
|
368
|
+
const f = makeFixture({
|
|
369
|
+
dispatch: () => ({ action: "skip", matchedRule: "evaluating-gates skipped after marking gates omitted" }),
|
|
343
370
|
});
|
|
344
|
-
|
|
371
|
+
t.after(() => f.cleanup());
|
|
345
372
|
|
|
346
|
-
const result = await orchestrator.advance();
|
|
373
|
+
const result = await f.orchestrator.advance();
|
|
347
374
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
assert.equal(result.
|
|
351
|
-
assert.
|
|
352
|
-
|
|
353
|
-
assert.ok(
|
|
375
|
+
assert.equal(result.kind, "skipped");
|
|
376
|
+
if (result.kind !== "skipped") return;
|
|
377
|
+
assert.equal(result.reason, "evaluating-gates skipped after marking gates omitted");
|
|
378
|
+
assert.equal(f.orchestrator.getStatus().phase, "running");
|
|
379
|
+
const names = f.journalNames();
|
|
380
|
+
assert.ok(names.includes("advance-skipped"));
|
|
381
|
+
assert.ok(!names.includes("advance-stopped"));
|
|
354
382
|
});
|
|
355
383
|
|
|
356
|
-
test("advance()
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
calls.push("worktree.prepare");
|
|
361
|
-
return { ok: false, reason: "worktree invalid" };
|
|
362
|
-
},
|
|
363
|
-
async syncAfterUnit() { calls.push("worktree.sync"); },
|
|
364
|
-
async cleanupOnStop() { calls.push("worktree.cleanup"); },
|
|
365
|
-
},
|
|
384
|
+
test("advance() surfaces dispatch blocker reason instead of generic no remaining units", async (t) => {
|
|
385
|
+
const reason = "Milestone M001 validation verdict is needs-remediation but all slices are complete.";
|
|
386
|
+
const f = makeFixture({
|
|
387
|
+
dispatch: () => ({ action: "stop", reason, level: "warning" }),
|
|
366
388
|
});
|
|
367
|
-
|
|
389
|
+
t.after(() => f.cleanup());
|
|
368
390
|
|
|
369
|
-
const result = await orchestrator.advance();
|
|
391
|
+
const result = await f.orchestrator.advance();
|
|
370
392
|
|
|
371
|
-
|
|
372
|
-
|
|
393
|
+
assert.equal(result.kind, "blocked");
|
|
394
|
+
if (result.kind !== "blocked") return;
|
|
395
|
+
assert.equal(result.reason, reason);
|
|
373
396
|
assert.equal(result.action, "pause");
|
|
374
|
-
|
|
375
|
-
assert.ok(
|
|
376
|
-
assert.ok(
|
|
397
|
+
const names = f.journalNames();
|
|
398
|
+
assert.ok(names.includes("advance-blocked"));
|
|
399
|
+
assert.ok(!names.includes("advance-stopped"));
|
|
377
400
|
});
|
|
378
401
|
|
|
379
|
-
test("advance()
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
async prepareForUnit() {
|
|
383
|
-
calls.push("worktree.prepare");
|
|
384
|
-
return { ok: true, reason: "isolation-not-worktree" };
|
|
385
|
-
},
|
|
386
|
-
async syncAfterUnit() { calls.push("worktree.sync"); },
|
|
387
|
-
async cleanupOnStop() { calls.push("worktree.cleanup"); },
|
|
388
|
-
},
|
|
402
|
+
test("advance() stop level=error blocks with action stop", async (t) => {
|
|
403
|
+
const f = makeFixture({
|
|
404
|
+
dispatch: () => ({ action: "stop", reason: "hard blocker", level: "error" }),
|
|
389
405
|
});
|
|
390
|
-
|
|
406
|
+
t.after(() => f.cleanup());
|
|
391
407
|
|
|
392
|
-
const result = await orchestrator.advance();
|
|
408
|
+
const result = await f.orchestrator.advance();
|
|
393
409
|
|
|
394
|
-
assert.equal(result.kind, "
|
|
395
|
-
|
|
396
|
-
assert.
|
|
410
|
+
assert.equal(result.kind, "blocked");
|
|
411
|
+
if (result.kind !== "blocked") return;
|
|
412
|
+
assert.equal(result.action, "stop");
|
|
397
413
|
});
|
|
398
414
|
|
|
399
|
-
test("advance()
|
|
400
|
-
const {
|
|
401
|
-
|
|
402
|
-
async decideNextUnit() { return null; },
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
415
|
+
test("advance() reports completion when complete state has no next unit", async (t) => {
|
|
416
|
+
const f = makeFixture({ complete: true, noTask: true });
|
|
417
|
+
t.after(() => f.cleanup());
|
|
406
418
|
|
|
407
|
-
const result = await orchestrator.advance();
|
|
419
|
+
const result = await f.orchestrator.advance();
|
|
408
420
|
|
|
409
421
|
assert.equal(result.kind, "stopped");
|
|
410
|
-
|
|
422
|
+
if (result.kind !== "stopped") return;
|
|
423
|
+
assert.equal(result.reason, "All milestones complete");
|
|
424
|
+
assert.equal(result.terminalOutcome?.code, "all-complete");
|
|
425
|
+
assert.equal(f.orchestrator.getStatus().phase, "stopped");
|
|
411
426
|
});
|
|
412
427
|
|
|
413
|
-
test("advance()
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
},
|
|
427
|
-
dispatch: {
|
|
428
|
-
async decideNextUnit() { return null; },
|
|
429
|
-
},
|
|
428
|
+
test("advance() blocks all-complete stop when completed milestone is still unmerged in a worktree", async (t) => {
|
|
429
|
+
const f = makeFixture({ complete: true, noTask: true });
|
|
430
|
+
t.after(() => f.cleanup());
|
|
431
|
+
|
|
432
|
+
insertSlice({
|
|
433
|
+
id: "S01",
|
|
434
|
+
milestoneId: "M001",
|
|
435
|
+
title: "Slice",
|
|
436
|
+
status: "complete",
|
|
437
|
+
risk: "low",
|
|
438
|
+
depends: [],
|
|
439
|
+
demo: "",
|
|
440
|
+
sequence: 1,
|
|
430
441
|
});
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
test("advance() keeps running when dispatch intentionally skips a phase", async () => {
|
|
440
|
-
const { deps, calls } = makeDeps({
|
|
441
|
-
dispatch: {
|
|
442
|
-
async decideNextUnit() {
|
|
443
|
-
return { kind: "skipped", reason: "evaluating-gates skipped after marking gates omitted" };
|
|
444
|
-
},
|
|
445
|
-
},
|
|
442
|
+
insertAssessment({
|
|
443
|
+
path: "milestones/M001/M001-VALIDATION.md",
|
|
444
|
+
milestoneId: "M001",
|
|
445
|
+
status: "pass",
|
|
446
|
+
scope: "milestone-validation",
|
|
447
|
+
fullContent: "verdict: pass",
|
|
446
448
|
});
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
assert.equal(result.reason, "evaluating-gates skipped after marking gates omitted");
|
|
454
|
-
assert.equal(orchestrator.getStatus().phase, "running");
|
|
455
|
-
assert.ok(calls.includes("journal:advance-skipped"));
|
|
456
|
-
assert.ok(!calls.includes("journal:advance-stopped"));
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test("advance() surfaces dispatch blocker reason instead of generic no remaining units", async () => {
|
|
460
|
-
const { deps, calls } = makeDeps({
|
|
461
|
-
dispatch: {
|
|
462
|
-
async decideNextUnit() {
|
|
463
|
-
return {
|
|
464
|
-
kind: "blocked",
|
|
465
|
-
reason: "Milestone M001 validation verdict is needs-remediation but all slices are complete.",
|
|
466
|
-
action: "pause",
|
|
467
|
-
};
|
|
468
|
-
},
|
|
469
|
-
},
|
|
449
|
+
insertGateRow({
|
|
450
|
+
milestoneId: "M001",
|
|
451
|
+
sliceId: "S01",
|
|
452
|
+
gateId: "Q3",
|
|
453
|
+
scope: "slice",
|
|
454
|
+
status: "pending",
|
|
470
455
|
});
|
|
471
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
472
456
|
|
|
473
|
-
const
|
|
457
|
+
const worktreePath = join(f.base, ".gsd", "worktrees", "M001");
|
|
458
|
+
mkdirSync(join(f.base, ".gsd", "worktrees"), { recursive: true });
|
|
459
|
+
execFileSync("git", ["worktree", "add", "-b", "milestone/M001", worktreePath], { cwd: f.base, stdio: "ignore" });
|
|
460
|
+
mkdirSync(join(worktreePath, ".gsd", "milestones", "M001"), { recursive: true });
|
|
461
|
+
writeFileSync(join(worktreePath, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\n");
|
|
462
|
+
f.session.basePath = worktreePath;
|
|
463
|
+
f.session.originalBasePath = f.base;
|
|
464
|
+
f.session.currentMilestoneId = "M001";
|
|
465
|
+
f.session.milestoneMergedInPhases = false;
|
|
466
|
+
|
|
467
|
+
const result = await f.orchestrator.advance();
|
|
474
468
|
|
|
475
469
|
assert.equal(result.kind, "blocked");
|
|
476
470
|
if (result.kind !== "blocked") return;
|
|
477
|
-
assert.equal(result.reason, "Milestone M001 validation verdict is needs-remediation but all slices are complete.");
|
|
478
471
|
assert.equal(result.action, "pause");
|
|
479
|
-
assert.
|
|
480
|
-
assert.
|
|
472
|
+
assert.equal(result.terminalOutcome?.code, "settlement-blocked");
|
|
473
|
+
assert.match(result.reason, /worktree branch has not been merged to main/);
|
|
474
|
+
assert.doesNotMatch(result.reason, /quality gate Q3 is still pending/);
|
|
475
|
+
assert.equal(f.orchestrator.getStatus().phase, "paused");
|
|
476
|
+
assert.equal(f.session.milestoneSettlement?.ok, false);
|
|
477
|
+
const names = f.journalNames();
|
|
478
|
+
assert.ok(names.includes("advance-blocked"));
|
|
479
|
+
assert.ok(!names.includes("advance-stopped"));
|
|
481
480
|
});
|
|
482
481
|
|
|
483
|
-
test("
|
|
484
|
-
|
|
485
|
-
|
|
482
|
+
test("advance() stopped clears previous activeUnit and resets idempotent lock", async (t) => {
|
|
483
|
+
// First advance dispatches; then we make the milestone resolve to no unit by
|
|
484
|
+
// closing it on disk + DB and re-deriving. Simpler: drive a fixture that
|
|
485
|
+
// dispatches once, finalize externally, then the next decision is complete.
|
|
486
|
+
let dispatchOnce = true;
|
|
487
|
+
const f = makeFixture({
|
|
488
|
+
dispatch: () => {
|
|
489
|
+
if (dispatchOnce) {
|
|
490
|
+
dispatchOnce = false;
|
|
491
|
+
return DEFAULT_DISPATCH;
|
|
492
|
+
}
|
|
493
|
+
// After the first advance, signal completion via a benign skip → still
|
|
494
|
+
// exercises the running/active-unit transition. For the stopped path we
|
|
495
|
+
// rely on the complete-state test above.
|
|
496
|
+
return { action: "skip", matchedRule: "done" };
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
t.after(() => f.cleanup());
|
|
486
500
|
|
|
487
|
-
const
|
|
501
|
+
const first = await f.orchestrator.advance();
|
|
502
|
+
assert.equal(first.kind, "advanced");
|
|
488
503
|
|
|
489
|
-
|
|
490
|
-
assert.equal(
|
|
491
|
-
|
|
492
|
-
assert.
|
|
504
|
+
const second = await f.orchestrator.advance();
|
|
505
|
+
assert.equal(second.kind, "skipped");
|
|
506
|
+
// skip clears activeUnit
|
|
507
|
+
assert.equal(f.orchestrator.getStatus().activeUnit, undefined);
|
|
493
508
|
});
|
|
494
509
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
async classifyAndRecover() { return { action: "escalate", reason: "needs manual" }; },
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
510
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
511
|
+
// Idempotency + finalized guard + stuck-loop ring (issues #5786 / #5787 / #415)
|
|
512
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
test("advance() is idempotent for the same active unit", async (t) => {
|
|
515
|
+
const f = makeFixture();
|
|
516
|
+
t.after(() => f.cleanup());
|
|
506
517
|
|
|
507
|
-
const
|
|
518
|
+
const first = await f.orchestrator.advance();
|
|
519
|
+
const second = await f.orchestrator.advance();
|
|
508
520
|
|
|
509
|
-
assert.equal(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
521
|
+
assert.equal(first.kind, "advanced");
|
|
522
|
+
if (first.kind === "advanced") {
|
|
523
|
+
assert.deepEqual(first.unit, { unitType: "execute-task", unitId: "M001/S01/T01" });
|
|
524
|
+
}
|
|
525
|
+
assert.equal(second.kind, "skipped");
|
|
526
|
+
if (second.kind !== "skipped") return;
|
|
527
|
+
assert.equal(second.reason, "idempotent advance: unit already active");
|
|
513
528
|
});
|
|
514
529
|
|
|
515
|
-
test("
|
|
516
|
-
const
|
|
517
|
-
|
|
530
|
+
test("idempotency skip fires with its own reason before saturation", async (t) => {
|
|
531
|
+
const f = makeFixture();
|
|
532
|
+
t.after(() => f.cleanup());
|
|
518
533
|
|
|
519
|
-
const first = await orchestrator.advance();
|
|
520
|
-
const second = await orchestrator.advance();
|
|
534
|
+
const first = await f.orchestrator.advance();
|
|
535
|
+
const second = await f.orchestrator.advance();
|
|
521
536
|
|
|
522
537
|
assert.equal(first.kind, "advanced");
|
|
523
|
-
assert.
|
|
524
|
-
|
|
538
|
+
assert.equal(second.kind, "skipped");
|
|
539
|
+
if (second.kind !== "skipped") return;
|
|
525
540
|
assert.equal(second.reason, "idempotent advance: unit already active");
|
|
526
|
-
assert.equal(second.action, "pause");
|
|
527
|
-
|
|
528
|
-
const prepareCalls = calls.filter((c) => c === "worktree.prepare").length;
|
|
529
|
-
assert.equal(prepareCalls, 1);
|
|
530
541
|
});
|
|
531
542
|
|
|
532
|
-
test("completeActiveUnit clears in-flight idempotency and stops stale same-unit advance", async () => {
|
|
533
|
-
const
|
|
534
|
-
|
|
543
|
+
test("completeActiveUnit clears in-flight idempotency and stops stale same-unit advance", async (t) => {
|
|
544
|
+
const f = makeFixture();
|
|
545
|
+
t.after(() => f.cleanup());
|
|
535
546
|
|
|
536
|
-
const first = await orchestrator.advance();
|
|
547
|
+
const first = await f.orchestrator.advance();
|
|
537
548
|
assert.equal(first.kind, "advanced");
|
|
538
549
|
if (first.kind !== "advanced") throw new Error("expected first advance");
|
|
539
550
|
|
|
540
|
-
await orchestrator.completeActiveUnit(first.unit);
|
|
541
|
-
const second = await orchestrator.advance();
|
|
551
|
+
await f.orchestrator.completeActiveUnit(first.unit);
|
|
552
|
+
const second = await f.orchestrator.advance();
|
|
542
553
|
|
|
543
|
-
assert.equal(orchestrator.getStatus().activeUnit, undefined);
|
|
554
|
+
assert.equal(f.orchestrator.getStatus().activeUnit, undefined);
|
|
544
555
|
assert.equal(second.kind, "blocked");
|
|
545
556
|
if (second.kind !== "blocked") throw new Error("expected stale same-unit block");
|
|
546
557
|
assert.equal(second.action, "stop");
|
|
547
|
-
assert.equal(second.reason, "state did not advance after finalized execute-task T01");
|
|
548
|
-
assert.ok(
|
|
549
|
-
const prepareCalls = calls.filter((c) => c === "worktree.prepare").length;
|
|
550
|
-
assert.equal(prepareCalls, 1, "stale same-unit advance must not prepare or redispatch");
|
|
558
|
+
assert.equal(second.reason, "state did not advance after finalized execute-task M001/S01/T01");
|
|
559
|
+
assert.ok(f.journalNames().includes("unit-finalized"));
|
|
551
560
|
});
|
|
552
561
|
|
|
553
|
-
test("
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
562
|
+
test("#442: finalized-repeat recovers (skipped) when the unit's artifact already exists on disk", async (t) => {
|
|
563
|
+
// plan-milestone's expected artifact is the ROADMAP, which the fixture
|
|
564
|
+
// already writes — so verifyExpectedArtifact returns true. This is the legacy
|
|
565
|
+
// stuck-recovery scenario (unit completed on disk, DB row stale): instead of
|
|
566
|
+
// the finalized-repeat HARD-STOP, #442 verify-and-recover should refresh +
|
|
567
|
+
// skip so the loop can progress. plan-milestone is deliberately NOT one of
|
|
568
|
+
// the DB-refreshing unit types, so the recovery stays side-effect-light.
|
|
569
|
+
const f = makeFixture({
|
|
570
|
+
dispatch: () => ({ action: "dispatch", unitType: "plan-milestone", unitId: "M001", prompt: "p" }),
|
|
561
571
|
});
|
|
562
|
-
|
|
572
|
+
t.after(() => f.cleanup());
|
|
563
573
|
|
|
564
|
-
const first = await orchestrator.advance();
|
|
574
|
+
const first = await f.orchestrator.advance();
|
|
575
|
+
if (first.kind !== "advanced") {
|
|
576
|
+
throw new Error(`expected advanced, got ${first.kind}: ${(first as { reason?: string }).reason ?? ""}`);
|
|
577
|
+
}
|
|
578
|
+
await f.orchestrator.completeActiveUnit(first.unit);
|
|
579
|
+
|
|
580
|
+
const second = await f.orchestrator.advance();
|
|
581
|
+
assert.equal(second.kind, "skipped", "should recover via artifact verification, not hard-stop");
|
|
582
|
+
if (second.kind !== "skipped") throw new Error("expected skipped recovery");
|
|
583
|
+
assert.match(second.reason, /stuck-recovery/);
|
|
584
|
+
assert.ok(f.journalNames().includes("advance-skipped"));
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test("completeActiveUnit allows a different next unit to advance", async (t) => {
|
|
588
|
+
let nextTaskId = "M001/S01/T01";
|
|
589
|
+
const f = makeFixture({
|
|
590
|
+
dispatch: () => ({ action: "dispatch", unitType: "execute-task", unitId: nextTaskId, prompt: "p" }),
|
|
591
|
+
});
|
|
592
|
+
t.after(() => f.cleanup());
|
|
593
|
+
|
|
594
|
+
const first = await f.orchestrator.advance();
|
|
565
595
|
assert.equal(first.kind, "advanced");
|
|
566
596
|
if (first.kind !== "advanced") throw new Error("expected first advance");
|
|
567
597
|
|
|
568
|
-
await orchestrator.completeActiveUnit(first.unit);
|
|
569
|
-
nextTaskId = "T02";
|
|
570
|
-
const second = await orchestrator.advance();
|
|
598
|
+
await f.orchestrator.completeActiveUnit(first.unit);
|
|
599
|
+
nextTaskId = "M001/S01/T02";
|
|
600
|
+
const second = await f.orchestrator.advance();
|
|
571
601
|
|
|
572
602
|
assert.equal(second.kind, "advanced");
|
|
573
603
|
if (second.kind !== "advanced") throw new Error("expected second advance");
|
|
574
|
-
assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "T02" });
|
|
604
|
+
assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "M001/S01/T02" });
|
|
575
605
|
});
|
|
576
606
|
|
|
577
|
-
test("completeActiveUnit guard survives an intervening advance and blocks X→Y→X re-dispatch", async () => {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const { deps } = makeDeps({
|
|
582
|
-
dispatch: {
|
|
583
|
-
async decideNextUnit() {
|
|
584
|
-
return { unitType: "execute-task", unitId: nextTaskId, reason: "ready", preconditions: [] };
|
|
585
|
-
},
|
|
586
|
-
},
|
|
607
|
+
test("completeActiveUnit guard survives an intervening advance and blocks X→Y→X re-dispatch (#415)", async (t) => {
|
|
608
|
+
let nextTaskId = "M001/S01/T01";
|
|
609
|
+
const f = makeFixture({
|
|
610
|
+
dispatch: () => ({ action: "dispatch", unitType: "execute-task", unitId: nextTaskId, prompt: "p" }),
|
|
587
611
|
});
|
|
588
|
-
|
|
612
|
+
t.after(() => f.cleanup());
|
|
589
613
|
|
|
590
|
-
|
|
591
|
-
const first = await orchestrator.advance();
|
|
614
|
+
const first = await f.orchestrator.advance();
|
|
592
615
|
assert.equal(first.kind, "advanced");
|
|
593
616
|
if (first.kind !== "advanced") throw new Error("expected first advance");
|
|
594
617
|
|
|
595
|
-
|
|
596
|
-
await orchestrator.completeActiveUnit(first.unit);
|
|
618
|
+
await f.orchestrator.completeActiveUnit(first.unit);
|
|
597
619
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const second = await orchestrator.advance();
|
|
620
|
+
nextTaskId = "M001/S01/T02";
|
|
621
|
+
const second = await f.orchestrator.advance();
|
|
601
622
|
assert.equal(second.kind, "advanced");
|
|
602
623
|
if (second.kind !== "advanced") throw new Error("expected second advance (T02)");
|
|
603
|
-
assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "T02" });
|
|
624
|
+
assert.deepEqual(second.unit, { unitType: "execute-task", unitId: "M001/S01/T02" });
|
|
604
625
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const third = await orchestrator.advance();
|
|
626
|
+
nextTaskId = "M001/S01/T01";
|
|
627
|
+
const third = await f.orchestrator.advance();
|
|
608
628
|
assert.equal(third.kind, "blocked");
|
|
609
629
|
if (third.kind !== "blocked") throw new Error("expected X→Y→X re-dispatch to be blocked");
|
|
610
630
|
assert.equal(third.action, "stop");
|
|
611
|
-
assert.equal(third.reason, "state did not advance after finalized execute-task T01");
|
|
631
|
+
assert.equal(third.reason, "state did not advance after finalized execute-task M001/S01/T01");
|
|
612
632
|
});
|
|
613
633
|
|
|
614
|
-
test("retryActiveUnit clears in-flight idempotency without marking the unit finalized", async () => {
|
|
615
|
-
const
|
|
616
|
-
|
|
634
|
+
test("retryActiveUnit clears in-flight idempotency without marking the unit finalized", async (t) => {
|
|
635
|
+
const f = makeFixture();
|
|
636
|
+
t.after(() => f.cleanup());
|
|
617
637
|
|
|
618
|
-
const first = await orchestrator.advance();
|
|
638
|
+
const first = await f.orchestrator.advance();
|
|
619
639
|
assert.equal(first.kind, "advanced");
|
|
620
640
|
if (first.kind !== "advanced") throw new Error("expected first advance");
|
|
621
641
|
|
|
622
|
-
await orchestrator.retryActiveUnit(first.unit);
|
|
623
|
-
const second = await orchestrator.advance();
|
|
642
|
+
await f.orchestrator.retryActiveUnit(first.unit);
|
|
643
|
+
const second = await f.orchestrator.advance();
|
|
624
644
|
|
|
625
645
|
assert.equal(second.kind, "advanced");
|
|
626
646
|
if (second.kind !== "advanced") throw new Error("expected retry advance");
|
|
627
647
|
assert.deepEqual(second.unit, first.unit);
|
|
628
|
-
assert.ok(
|
|
629
|
-
const prepareCalls = calls.filter((c) => c === "worktree.prepare").length;
|
|
630
|
-
assert.equal(prepareCalls, 2, "retry should intentionally redispatch the same unit");
|
|
648
|
+
assert.ok(f.journalNames().includes("unit-retry"));
|
|
631
649
|
});
|
|
632
650
|
|
|
633
|
-
test("retryActiveUnit clears finalized same-unit guard for post-hook retries", async () => {
|
|
634
|
-
const
|
|
635
|
-
|
|
651
|
+
test("retryActiveUnit clears finalized same-unit guard for post-hook retries", async (t) => {
|
|
652
|
+
const f = makeFixture();
|
|
653
|
+
t.after(() => f.cleanup());
|
|
636
654
|
|
|
637
|
-
const first = await orchestrator.advance();
|
|
655
|
+
const first = await f.orchestrator.advance();
|
|
638
656
|
assert.equal(first.kind, "advanced");
|
|
639
657
|
if (first.kind !== "advanced") throw new Error("expected first advance");
|
|
640
658
|
|
|
641
|
-
await orchestrator.completeActiveUnit(first.unit);
|
|
642
|
-
await orchestrator.retryActiveUnit(first.unit);
|
|
643
|
-
const second = await orchestrator.advance();
|
|
659
|
+
await f.orchestrator.completeActiveUnit(first.unit);
|
|
660
|
+
await f.orchestrator.retryActiveUnit(first.unit);
|
|
661
|
+
const second = await f.orchestrator.advance();
|
|
644
662
|
|
|
645
663
|
assert.equal(second.kind, "advanced");
|
|
646
664
|
if (second.kind !== "advanced") throw new Error("expected retry advance");
|
|
647
665
|
assert.deepEqual(second.unit, first.unit);
|
|
648
|
-
|
|
649
|
-
assert.ok(
|
|
650
|
-
|
|
651
|
-
assert.equal(prepareCalls, 2, "post-hook retry should redispatch the finalized unit");
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
test("resume() re-enters running phase", async () => {
|
|
655
|
-
const { deps } = makeDeps();
|
|
656
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
657
|
-
|
|
658
|
-
const result = await orchestrator.resume();
|
|
659
|
-
|
|
660
|
-
assert.equal(result.kind, "resumed");
|
|
661
|
-
assert.equal(orchestrator.getStatus().phase, "running");
|
|
666
|
+
const names = f.journalNames();
|
|
667
|
+
assert.ok(names.includes("unit-finalized"));
|
|
668
|
+
assert.ok(names.includes("unit-retry"));
|
|
662
669
|
});
|
|
663
670
|
|
|
664
|
-
test("resume() clears idempotent lock and allows re-advance", async () => {
|
|
665
|
-
const
|
|
666
|
-
|
|
671
|
+
test("resume() clears idempotent lock and allows re-advance", async (t) => {
|
|
672
|
+
const f = makeFixture();
|
|
673
|
+
t.after(() => f.cleanup());
|
|
667
674
|
|
|
668
|
-
const first = await orchestrator.advance();
|
|
669
|
-
const
|
|
670
|
-
const resumed = await orchestrator.resume();
|
|
671
|
-
const next = await orchestrator.advance();
|
|
675
|
+
const first = await f.orchestrator.advance();
|
|
676
|
+
const idempotent = await f.orchestrator.advance();
|
|
677
|
+
const resumed = await f.orchestrator.resume();
|
|
678
|
+
const next = await f.orchestrator.advance();
|
|
672
679
|
|
|
673
680
|
assert.equal(first.kind, "advanced");
|
|
674
|
-
assert.equal(
|
|
681
|
+
assert.equal(idempotent.kind, "skipped");
|
|
675
682
|
assert.equal(resumed.kind, "resumed");
|
|
676
683
|
assert.equal(next.kind, "advanced");
|
|
677
684
|
});
|
|
678
685
|
|
|
679
|
-
test("
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const before = orchestrator.getStatus().transitionCount;
|
|
684
|
-
await orchestrator.start({ basePath: "/tmp/project", trigger: "manual" });
|
|
685
|
-
const afterStart = orchestrator.getStatus().transitionCount;
|
|
686
|
-
await orchestrator.stop("done");
|
|
687
|
-
const afterStop = orchestrator.getStatus().transitionCount;
|
|
688
|
-
|
|
689
|
-
assert.ok(afterStart > before);
|
|
690
|
-
assert.ok(afterStop > afterStart);
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
test("stop() clears idempotent unit lock so advance can run again", async () => {
|
|
694
|
-
const { deps } = makeDeps();
|
|
695
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
696
|
-
|
|
697
|
-
const first = await orchestrator.advance();
|
|
698
|
-
const blocked = await orchestrator.advance();
|
|
699
|
-
const stopped = await orchestrator.stop("reset");
|
|
700
|
-
const second = await orchestrator.advance();
|
|
686
|
+
test("start() clears prior idempotent lock", async (t) => {
|
|
687
|
+
const f = makeFixture();
|
|
688
|
+
t.after(() => f.cleanup());
|
|
701
689
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
});
|
|
690
|
+
await f.orchestrator.advance();
|
|
691
|
+
const idempotent = await f.orchestrator.advance();
|
|
692
|
+
const restarted = await f.orchestrator.start(SESSION_CONTEXT);
|
|
693
|
+
const next = await f.orchestrator.advance();
|
|
707
694
|
|
|
708
|
-
|
|
709
|
-
let first = true;
|
|
710
|
-
const { deps } = makeDeps({
|
|
711
|
-
dispatch: {
|
|
712
|
-
async decideNextUnit() {
|
|
713
|
-
if (first) {
|
|
714
|
-
first = false;
|
|
715
|
-
return { unitType: "execute-task", unitId: "T01", reason: "ready", preconditions: [] };
|
|
716
|
-
}
|
|
717
|
-
return null;
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
});
|
|
721
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
722
|
-
|
|
723
|
-
await orchestrator.advance();
|
|
724
|
-
const stopped = await orchestrator.advance();
|
|
725
|
-
|
|
726
|
-
assert.equal(stopped.kind, "stopped");
|
|
727
|
-
assert.equal(orchestrator.getStatus().activeUnit, undefined);
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
test("recovery stop clears activeUnit", async () => {
|
|
731
|
-
const { deps, calls } = makeDeps({
|
|
732
|
-
runtime: {
|
|
733
|
-
async ensureLockOwnership() { throw new Error("boom"); },
|
|
734
|
-
async journalTransition(event) { calls.push(`journal:${event.name}`); },
|
|
735
|
-
},
|
|
736
|
-
recovery: {
|
|
737
|
-
async classifyAndRecover() { return { action: "stop", reason: "fatal" }; },
|
|
738
|
-
},
|
|
739
|
-
});
|
|
740
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
741
|
-
|
|
742
|
-
const result = await orchestrator.advance();
|
|
743
|
-
|
|
744
|
-
assert.equal(result.kind, "stopped");
|
|
745
|
-
assert.equal(orchestrator.getStatus().activeUnit, undefined);
|
|
746
|
-
assert.ok(calls.includes("journal:advance-stopped"));
|
|
747
|
-
assert.ok(calls.includes("notify:stopped"));
|
|
748
|
-
assert.ok(!calls.includes("notify:error"));
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
test("recovery retry maps to paused result", async () => {
|
|
752
|
-
const { deps, calls } = makeDeps({
|
|
753
|
-
runtime: {
|
|
754
|
-
async ensureLockOwnership() { throw new Error("boom"); },
|
|
755
|
-
async journalTransition(event) { calls.push(`journal:${event.name}`); },
|
|
756
|
-
},
|
|
757
|
-
recovery: {
|
|
758
|
-
async classifyAndRecover() { return { action: "retry", reason: "transient" }; },
|
|
759
|
-
},
|
|
760
|
-
});
|
|
761
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
762
|
-
|
|
763
|
-
const result = await orchestrator.advance();
|
|
764
|
-
|
|
765
|
-
assert.equal(result.kind, "paused");
|
|
766
|
-
assert.equal(result.reason, "transient");
|
|
767
|
-
assert.equal(orchestrator.getStatus().phase, "paused");
|
|
768
|
-
assert.ok(calls.includes("journal:advance-paused"));
|
|
769
|
-
assert.ok(calls.includes("notify:pause"));
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
test("getStatus() returns defensive copy of activeUnit", async () => {
|
|
773
|
-
const { deps } = makeDeps();
|
|
774
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
775
|
-
|
|
776
|
-
await orchestrator.advance();
|
|
777
|
-
const snap1 = orchestrator.getStatus();
|
|
778
|
-
if (snap1.activeUnit) snap1.activeUnit.unitId = "MUTATED";
|
|
779
|
-
const snap2 = orchestrator.getStatus();
|
|
780
|
-
|
|
781
|
-
assert.equal(snap2.activeUnit?.unitId, "T01");
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
test("start() clears prior idempotent lock", async () => {
|
|
785
|
-
const { deps } = makeDeps();
|
|
786
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
787
|
-
|
|
788
|
-
await orchestrator.advance();
|
|
789
|
-
const blocked = await orchestrator.advance();
|
|
790
|
-
const restarted = await orchestrator.start({ basePath: "/tmp/project", trigger: "manual" });
|
|
791
|
-
const next = await orchestrator.advance();
|
|
792
|
-
|
|
793
|
-
assert.equal(blocked.kind, "blocked");
|
|
695
|
+
assert.equal(idempotent.kind, "skipped");
|
|
794
696
|
assert.equal(restarted.kind, "started");
|
|
795
697
|
assert.equal(next.kind, "advanced");
|
|
796
698
|
});
|
|
797
699
|
|
|
798
|
-
test("
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
async ensureLockOwnership() { throw new Error("boom"); },
|
|
802
|
-
async journalTransition(event) { calls.push(`journal:${event.name}`); },
|
|
803
|
-
},
|
|
804
|
-
recovery: {
|
|
805
|
-
async classifyAndRecover() { return { action: "escalate", reason: "needs manual" }; },
|
|
806
|
-
},
|
|
807
|
-
});
|
|
808
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
809
|
-
|
|
810
|
-
await orchestrator.advance();
|
|
811
|
-
|
|
812
|
-
assert.ok(calls.includes("notify:error"));
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
test("blocked path journals advance-blocked", async () => {
|
|
816
|
-
const { deps, calls } = makeDeps();
|
|
817
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
818
|
-
|
|
819
|
-
await orchestrator.advance();
|
|
820
|
-
await orchestrator.advance();
|
|
821
|
-
|
|
822
|
-
assert.ok(calls.includes("journal:advance-blocked"));
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test("health post hook runs on blocked result", async () => {
|
|
826
|
-
const { deps, calls } = makeDeps();
|
|
827
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
700
|
+
test("stop() clears idempotent unit lock so advance can run again", async (t) => {
|
|
701
|
+
const f = makeFixture();
|
|
702
|
+
t.after(() => f.cleanup());
|
|
828
703
|
|
|
829
|
-
await orchestrator.advance();
|
|
830
|
-
await orchestrator.advance();
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
test("start() emits start notification", async () => {
|
|
836
|
-
const { deps, calls } = makeDeps();
|
|
837
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
838
|
-
|
|
839
|
-
await orchestrator.start({ basePath: "/tmp/project", trigger: "manual" });
|
|
840
|
-
|
|
841
|
-
assert.ok(calls.includes("notify:start"));
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
test("resume() emits resume notification", async () => {
|
|
845
|
-
const { deps, calls } = makeDeps();
|
|
846
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
847
|
-
|
|
848
|
-
await orchestrator.resume();
|
|
849
|
-
|
|
850
|
-
assert.ok(calls.includes("notify:resume"));
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
test("stopped with no remaining units clears idempotent lock for next advance", async () => {
|
|
854
|
-
let callCount = 0;
|
|
855
|
-
const { deps } = makeDeps({
|
|
856
|
-
dispatch: {
|
|
857
|
-
async decideNextUnit() {
|
|
858
|
-
callCount += 1;
|
|
859
|
-
if (callCount === 2) return null;
|
|
860
|
-
return { unitType: "execute-task", unitId: "T01", reason: "ready", preconditions: [] };
|
|
861
|
-
},
|
|
862
|
-
},
|
|
863
|
-
});
|
|
864
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
865
|
-
|
|
866
|
-
const first = await orchestrator.advance();
|
|
867
|
-
const stopped = await orchestrator.advance();
|
|
868
|
-
const after = await orchestrator.advance();
|
|
704
|
+
const first = await f.orchestrator.advance();
|
|
705
|
+
const idempotent = await f.orchestrator.advance();
|
|
706
|
+
const stopped = await f.orchestrator.stop("reset");
|
|
707
|
+
const second = await f.orchestrator.advance();
|
|
869
708
|
|
|
870
709
|
assert.equal(first.kind, "advanced");
|
|
710
|
+
assert.equal(idempotent.kind, "skipped");
|
|
871
711
|
assert.equal(stopped.kind, "stopped");
|
|
872
|
-
assert.equal(
|
|
712
|
+
assert.equal(second.kind, "advanced");
|
|
873
713
|
});
|
|
874
714
|
|
|
875
|
-
test("
|
|
876
|
-
const
|
|
877
|
-
|
|
715
|
+
test("idempotent path journals advance-skipped and records a health snapshot", async (t) => {
|
|
716
|
+
const f = makeFixture();
|
|
717
|
+
t.after(() => f.cleanup());
|
|
878
718
|
|
|
879
|
-
|
|
719
|
+
await f.orchestrator.advance();
|
|
720
|
+
await f.orchestrator.advance();
|
|
880
721
|
|
|
881
|
-
assert.
|
|
882
|
-
assert.equal(orchestrator.getStatus().phase, "stopped");
|
|
883
|
-
assert.ok(calls.includes("worktree.cleanup"));
|
|
884
|
-
assert.ok(calls.includes("journal:stop"));
|
|
885
|
-
assert.ok(calls.includes("notify:stop"));
|
|
722
|
+
assert.ok(f.journalNames().includes("advance-skipped"));
|
|
886
723
|
});
|
|
887
724
|
|
|
888
|
-
//
|
|
889
|
-
// Stuck-loop ring buffer (issue #5787)
|
|
890
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
891
|
-
|
|
892
|
-
test("STUCK_WINDOW_SIZE matches the legacy auto/phases.ts constant", () => {
|
|
893
|
-
assert.equal(STUCK_WINDOW_SIZE, 6);
|
|
894
|
-
});
|
|
725
|
+
// ─── Stuck-loop ring buffer (issue #5787) ──────────────────────────────────
|
|
895
726
|
|
|
896
|
-
test("stuck-loop: empty ring on a freshly constructed orchestrator advances normally", async () => {
|
|
897
|
-
const
|
|
898
|
-
|
|
727
|
+
test("stuck-loop: empty ring on a freshly constructed orchestrator advances normally", async (t) => {
|
|
728
|
+
const f = makeFixture();
|
|
729
|
+
t.after(() => f.cleanup());
|
|
899
730
|
|
|
900
|
-
const result = await orchestrator.advance();
|
|
731
|
+
const result = await f.orchestrator.advance();
|
|
901
732
|
|
|
902
733
|
assert.equal(result.kind, "advanced");
|
|
903
734
|
});
|
|
904
735
|
|
|
905
|
-
test("stuck-loop: partial fill of mixed units does not block", async () => {
|
|
906
|
-
// Alternate A/B for STUCK_WINDOW_SIZE rounds. No single key saturates the
|
|
907
|
-
// window, so neither idempotency nor stuck-loop should fire.
|
|
736
|
+
test("stuck-loop: partial fill of mixed units does not block", async (t) => {
|
|
908
737
|
let i = 0;
|
|
909
|
-
const sequence = ["A", "B", "A", "B", "A", "B"];
|
|
910
|
-
const
|
|
911
|
-
dispatch: {
|
|
912
|
-
async decideNextUnit() {
|
|
913
|
-
const id = sequence[i++ % sequence.length];
|
|
914
|
-
return { unitType: "execute-task", unitId: id, reason: "ready", preconditions: [] };
|
|
915
|
-
},
|
|
916
|
-
},
|
|
738
|
+
const sequence = ["M001/S01/A", "M001/S01/B", "M001/S01/A", "M001/S01/B", "M001/S01/A", "M001/S01/B"];
|
|
739
|
+
const f = makeFixture({
|
|
740
|
+
dispatch: () => ({ action: "dispatch", unitType: "execute-task", unitId: sequence[i++ % sequence.length], prompt: "p" }),
|
|
917
741
|
});
|
|
918
|
-
|
|
742
|
+
t.after(() => f.cleanup());
|
|
919
743
|
|
|
920
744
|
for (let round = 0; round < STUCK_WINDOW_SIZE; round++) {
|
|
921
|
-
const result = await orchestrator.advance();
|
|
745
|
+
const result = await f.orchestrator.advance();
|
|
922
746
|
assert.equal(result.kind, "advanced", `round ${round} should advance, got ${result.kind}`);
|
|
923
747
|
}
|
|
924
748
|
});
|
|
925
749
|
|
|
926
|
-
test("stuck-loop: ring saturated with same unit blocks with action 'stop' and stuck-loop reason", async () => {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
// The STUCK_WINDOW_SIZE'th call sees a saturated ring and returns stuck-loop.
|
|
930
|
-
const { deps } = makeDeps();
|
|
931
|
-
const orchestrator = createAutoOrchestrator(deps);
|
|
750
|
+
test("stuck-loop: ring saturated with same unit blocks with action 'stop' and stuck-loop reason", async (t) => {
|
|
751
|
+
const f = makeFixture();
|
|
752
|
+
t.after(() => f.cleanup());
|
|
932
753
|
|
|
933
|
-
const results: Awaited<ReturnType<typeof orchestrator.advance>>[] = [];
|
|
754
|
+
const results: Awaited<ReturnType<typeof f.orchestrator.advance>>[] = [];
|
|
934
755
|
for (let i = 0; i < STUCK_WINDOW_SIZE; i++) {
|
|
935
|
-
results.push(await orchestrator.advance());
|
|
756
|
+
results.push(await f.orchestrator.advance());
|
|
936
757
|
}
|
|
937
758
|
|
|
938
759
|
// First call advances.
|
|
939
760
|
assert.equal(results[0].kind, "advanced");
|
|
940
761
|
|
|
941
|
-
// Intermediate calls are
|
|
762
|
+
// Intermediate calls are skipped by idempotency (not stuck-loop yet).
|
|
942
763
|
for (let i = 1; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
943
764
|
const r = results[i];
|
|
944
|
-
assert.equal(r.kind, "
|
|
945
|
-
if (r.kind !== "
|
|
765
|
+
assert.equal(r.kind, "skipped", `round ${i} should be skipped`);
|
|
766
|
+
if (r.kind !== "skipped") return;
|
|
946
767
|
assert.equal(r.reason, "idempotent advance: unit already active");
|
|
947
|
-
assert.equal(r.action, "pause");
|
|
948
768
|
}
|
|
949
769
|
|
|
950
|
-
// The final call (ring now holds STUCK_WINDOW_SIZE copies) returns stuck-loop
|
|
770
|
+
// The final call (ring now holds STUCK_WINDOW_SIZE copies) returns stuck-loop
|
|
771
|
+
// with the detect-stuck rule verdict in the reason.
|
|
951
772
|
const last = results[STUCK_WINDOW_SIZE - 1];
|
|
952
773
|
assert.equal(last.kind, "blocked");
|
|
953
774
|
if (last.kind !== "blocked") return;
|
|
954
775
|
assert.equal(last.action, "stop");
|
|
955
|
-
assert.
|
|
776
|
+
assert.ok(
|
|
777
|
+
last.reason.startsWith("stuck-loop: execute-task:M001/S01/T01 derived"),
|
|
778
|
+
`expected detect-stuck verdict reason, got: ${last.reason}`,
|
|
779
|
+
);
|
|
956
780
|
});
|
|
957
781
|
|
|
958
|
-
test("stuck-loop:
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
782
|
+
test("stuck-loop: start() resets the ring so a fresh saturation cycle is required", async (t) => {
|
|
783
|
+
const f = makeFixture();
|
|
784
|
+
t.after(() => f.cleanup());
|
|
785
|
+
|
|
786
|
+
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
787
|
+
await f.orchestrator.advance();
|
|
788
|
+
}
|
|
963
789
|
|
|
964
|
-
const
|
|
965
|
-
|
|
790
|
+
const restarted = await f.orchestrator.start(SESSION_CONTEXT);
|
|
791
|
+
assert.equal(restarted.kind, "started");
|
|
966
792
|
|
|
967
|
-
|
|
968
|
-
assert.equal(
|
|
969
|
-
assert.equal(second.reason, "idempotent advance: unit already active");
|
|
970
|
-
assert.equal(second.action, "pause");
|
|
793
|
+
const next = await f.orchestrator.advance();
|
|
794
|
+
assert.equal(next.kind, "advanced");
|
|
971
795
|
});
|
|
972
796
|
|
|
973
|
-
test("stuck-loop:
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
|
|
977
|
-
const
|
|
797
|
+
test("stuck-loop: resume() preserves ring so detection accumulates across pause/resume", async (t) => {
|
|
798
|
+
// Regression for #572: resume() must NOT reset dispatchKeyWindow. Before the
|
|
799
|
+
// fix, a pause/resume cycle cleared the window, letting a stuck loop silently
|
|
800
|
+
// re-accumulate STUCK_WINDOW_SIZE dispatches before being detected again.
|
|
801
|
+
const f = makeFixture();
|
|
802
|
+
t.after(() => f.cleanup());
|
|
978
803
|
|
|
979
804
|
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
980
|
-
await orchestrator.advance();
|
|
805
|
+
await f.orchestrator.advance();
|
|
981
806
|
}
|
|
982
807
|
|
|
983
|
-
const
|
|
984
|
-
assert.equal(
|
|
808
|
+
const resumed = await f.orchestrator.resume();
|
|
809
|
+
assert.equal(resumed.kind, "resumed");
|
|
985
810
|
|
|
986
|
-
//
|
|
987
|
-
//
|
|
988
|
-
const next = await orchestrator.advance();
|
|
989
|
-
assert.equal(next.kind, "
|
|
811
|
+
// The ring is preserved, so the next advance pushes it to STUCK_WINDOW_SIZE
|
|
812
|
+
// and triggers stuck-loop detection — not a fresh dispatch.
|
|
813
|
+
const next = await f.orchestrator.advance();
|
|
814
|
+
assert.equal(next.kind, "blocked");
|
|
815
|
+
if (next.kind !== "blocked") return;
|
|
816
|
+
assert.equal(next.action, "stop");
|
|
817
|
+
assert.ok(next.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${next.reason}`);
|
|
990
818
|
});
|
|
991
819
|
|
|
992
|
-
test("stuck-loop:
|
|
993
|
-
|
|
994
|
-
|
|
820
|
+
test("stuck-loop: stop('pause') preserves ring across the stop/resume cycle", async (t) => {
|
|
821
|
+
// Regression for #572: stop("pause") must behave the same as resume() —
|
|
822
|
+
// the window must survive so detection accumulates across pause/resume pairs.
|
|
823
|
+
const f = makeFixture();
|
|
824
|
+
t.after(() => f.cleanup());
|
|
995
825
|
|
|
996
826
|
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
997
|
-
await orchestrator.advance();
|
|
827
|
+
await f.orchestrator.advance();
|
|
998
828
|
}
|
|
999
829
|
|
|
1000
|
-
const
|
|
830
|
+
const stopped = await f.orchestrator.stop("pause");
|
|
831
|
+
assert.equal(stopped.kind, "stopped");
|
|
832
|
+
|
|
833
|
+
const resumed = await f.orchestrator.resume();
|
|
1001
834
|
assert.equal(resumed.kind, "resumed");
|
|
1002
835
|
|
|
1003
|
-
const next = await orchestrator.advance();
|
|
1004
|
-
assert.equal(next.kind, "
|
|
836
|
+
const next = await f.orchestrator.advance();
|
|
837
|
+
assert.equal(next.kind, "blocked");
|
|
838
|
+
if (next.kind !== "blocked") return;
|
|
839
|
+
assert.equal(next.action, "stop");
|
|
840
|
+
assert.ok(next.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${next.reason}`);
|
|
1005
841
|
});
|
|
1006
842
|
|
|
1007
|
-
test("stuck-loop: stop() resets the ring", async () => {
|
|
1008
|
-
const
|
|
1009
|
-
|
|
843
|
+
test("stuck-loop: stop('user-request') resets the ring (hard stop)", async (t) => {
|
|
844
|
+
const f = makeFixture();
|
|
845
|
+
t.after(() => f.cleanup());
|
|
1010
846
|
|
|
1011
847
|
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
1012
|
-
await orchestrator.advance();
|
|
848
|
+
await f.orchestrator.advance();
|
|
1013
849
|
}
|
|
1014
850
|
|
|
1015
|
-
const stopped = await orchestrator.stop("user-request");
|
|
851
|
+
const stopped = await f.orchestrator.stop("user-request");
|
|
1016
852
|
assert.equal(stopped.kind, "stopped");
|
|
1017
853
|
|
|
1018
|
-
//
|
|
1019
|
-
const next = await orchestrator.advance();
|
|
854
|
+
// Hard stop clears the ring, so the next advance dispatches fresh.
|
|
855
|
+
const next = await f.orchestrator.advance();
|
|
1020
856
|
assert.equal(next.kind, "advanced");
|
|
1021
857
|
});
|
|
1022
858
|
|
|
1023
|
-
test("stuck-loop:
|
|
1024
|
-
const
|
|
1025
|
-
|
|
859
|
+
test("stuck-loop #482 regression: start() rehydrates the window from the dispatch ledger so cross-session re-dispatch loops are detected", async (t) => {
|
|
860
|
+
const f = makeFixture();
|
|
861
|
+
t.after(() => f.cleanup());
|
|
862
|
+
|
|
863
|
+
// Simulate a PRIOR session: the dispatch ledger recorded the same unit
|
|
864
|
+
// being re-dispatched repeatedly without progress. The orchestrator under test is a
|
|
865
|
+
// fresh instance (as it would be after a session restart) — before the
|
|
866
|
+
// Dispatch History module, start() reset the window to [] and the loop
|
|
867
|
+
// would silently re-dispatch the unit forever (#482: 146 re-dispatches).
|
|
868
|
+
const worker = registerAutoWorker({ projectRootRealpath: normalizeRealPath(f.base) });
|
|
869
|
+
const lease = claimMilestoneLease(worker, "M001");
|
|
870
|
+
assert.equal(lease.ok, true);
|
|
871
|
+
if (!lease.ok) return;
|
|
872
|
+
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
873
|
+
const claim = recordDispatchClaim({
|
|
874
|
+
traceId: `prior-session-${i}`,
|
|
875
|
+
workerId: worker,
|
|
876
|
+
milestoneLeaseToken: lease.token,
|
|
877
|
+
milestoneId: "M001",
|
|
878
|
+
unitType: "execute-task",
|
|
879
|
+
unitId: "M001/S01/T01",
|
|
880
|
+
});
|
|
881
|
+
assert.equal(claim.ok, true);
|
|
882
|
+
if (!claim.ok) return;
|
|
883
|
+
markFailed(claim.dispatchId, { errorSummary: "" });
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const started = await f.orchestrator.start(SESSION_CONTEXT);
|
|
887
|
+
assert.equal(started.kind, "started");
|
|
888
|
+
|
|
889
|
+
// The very next decision for the same unit must trip the stuck verdict
|
|
890
|
+
// instead of advancing.
|
|
891
|
+
const result = await f.orchestrator.advance();
|
|
892
|
+
assert.equal(result.kind, "blocked");
|
|
893
|
+
if (result.kind !== "blocked") return;
|
|
894
|
+
assert.equal(result.action, "stop");
|
|
895
|
+
assert.ok(result.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${result.reason}`);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("stuck-loop #482: resume() with an empty window rehydrates from the dispatch ledger", async (t) => {
|
|
899
|
+
const f = makeFixture();
|
|
900
|
+
t.after(() => f.cleanup());
|
|
901
|
+
|
|
902
|
+
const worker = registerAutoWorker({ projectRootRealpath: normalizeRealPath(f.base) });
|
|
903
|
+
const lease = claimMilestoneLease(worker, "M001");
|
|
904
|
+
assert.equal(lease.ok, true);
|
|
905
|
+
if (!lease.ok) return;
|
|
906
|
+
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
907
|
+
const claim = recordDispatchClaim({
|
|
908
|
+
traceId: `prior-session-resume-${i}`,
|
|
909
|
+
workerId: worker,
|
|
910
|
+
milestoneLeaseToken: lease.token,
|
|
911
|
+
milestoneId: "M001",
|
|
912
|
+
unitType: "execute-task",
|
|
913
|
+
unitId: "M001/S01/T01",
|
|
914
|
+
});
|
|
915
|
+
assert.equal(claim.ok, true);
|
|
916
|
+
if (!claim.ok) return;
|
|
917
|
+
markFailed(claim.dispatchId, { errorSummary: "" });
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Fresh orchestrator resuming a prior session: window starts empty, so
|
|
921
|
+
// resume() must rehydrate (while in-process resume keeps the live window —
|
|
922
|
+
// see the #572 preservation tests above).
|
|
923
|
+
const resumed = await f.orchestrator.resume();
|
|
924
|
+
assert.equal(resumed.kind, "resumed");
|
|
925
|
+
|
|
926
|
+
const result = await f.orchestrator.advance();
|
|
927
|
+
assert.equal(result.kind, "blocked");
|
|
928
|
+
if (result.kind !== "blocked") return;
|
|
929
|
+
assert.equal(result.action, "stop");
|
|
930
|
+
assert.ok(result.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${result.reason}`);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
test("stuck-loop: journal records the stuck-loop reason on advance-blocked", async (t) => {
|
|
934
|
+
const f = makeFixture();
|
|
935
|
+
t.after(() => f.cleanup());
|
|
1026
936
|
|
|
1027
937
|
for (let i = 0; i < STUCK_WINDOW_SIZE; i++) {
|
|
1028
|
-
await orchestrator.advance();
|
|
938
|
+
await f.orchestrator.advance();
|
|
1029
939
|
}
|
|
1030
940
|
|
|
1031
|
-
|
|
941
|
+
const stuckEntry = queryJournal(f.base).find(
|
|
942
|
+
(e) => {
|
|
943
|
+
const reason = (e.data as Record<string, unknown> | undefined)?.reason;
|
|
944
|
+
return typeof reason === "string" && reason.startsWith("stuck-loop:");
|
|
945
|
+
},
|
|
946
|
+
);
|
|
947
|
+
assert.ok(stuckEntry, "journal must record an advance-blocked entry with the stuck-loop reason");
|
|
948
|
+
assert.ok(f.journalNames().includes("advance-blocked"));
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
952
|
+
// Recovery path: a lock held by another process throws inside advance() and is
|
|
953
|
+
// routed through the REAL classifyFailure → result mapping + notifications.
|
|
954
|
+
// We force the throw by acquiring the lock under a different PID (writing a
|
|
955
|
+
// foreign-PID lockfile is not portable, so we drive the deterministic-stop
|
|
956
|
+
// classification via a fixture whose runtimeBasePath has no valid lock).
|
|
957
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
958
|
+
|
|
959
|
+
test("advance() routes a lost-lock error through recovery and journals an outcome", async (t) => {
|
|
960
|
+
const f = makeFixture();
|
|
961
|
+
t.after(() => f.cleanup());
|
|
962
|
+
|
|
963
|
+
// Release the lock so ensureLockOwnership() sees missing-metadata and throws,
|
|
964
|
+
// exercising the catch → classifyAndRecover → result-mapping branch.
|
|
965
|
+
releaseSessionLock(f.base);
|
|
966
|
+
// Remove the lockfile artifact so getSessionLockStatus returns !valid.
|
|
967
|
+
try { rmSync(join(f.base, ".gsd", "auto.lock"), { force: true }); } catch { /* */ }
|
|
968
|
+
try { rmSync(join(f.base, ".gsd.lock"), { recursive: true, force: true }); } catch { /* */ }
|
|
969
|
+
|
|
970
|
+
const result = await f.orchestrator.advance();
|
|
971
|
+
|
|
972
|
+
// classifyFailure maps a generic Error to a recovery action; the orchestrator
|
|
973
|
+
// surfaces it as paused/stopped/error and journals the corresponding event.
|
|
974
|
+
assert.ok(["paused", "stopped", "error"].includes(result.kind), `unexpected kind ${result.kind}`);
|
|
975
|
+
const names = f.journalNames();
|
|
976
|
+
assert.ok(
|
|
977
|
+
names.includes("advance-paused") || names.includes("advance-stopped") || names.includes("advance-error"),
|
|
978
|
+
"recovery must journal an advance-paused/stopped/error event",
|
|
979
|
+
);
|
|
1032
980
|
});
|
|
1033
981
|
|
|
1034
|
-
//
|
|
982
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
983
|
+
// closeout regression: live-base resolver after worktree cleanup
|
|
984
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1035
985
|
|
|
1036
|
-
test("
|
|
986
|
+
test("live orchestrator base resolver prefers live project root after worktree cleanup", (t) => {
|
|
1037
987
|
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-orch-root-"));
|
|
1038
988
|
const staleWorktreeRoot = join(projectRoot, ".gsd", "worktrees", "M002");
|
|
1039
989
|
mkdirSync(join(staleWorktreeRoot, ".bg-shell"), { recursive: true });
|
|
@@ -1050,7 +1000,7 @@ test("wired orchestrator base resolver prefers live project root after worktree
|
|
|
1050
1000
|
);
|
|
1051
1001
|
});
|
|
1052
1002
|
|
|
1053
|
-
test("
|
|
1003
|
+
test("live orchestrator base resolver keeps a captured active git worktree", (t) => {
|
|
1054
1004
|
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-orch-worktree-"));
|
|
1055
1005
|
const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M003");
|
|
1056
1006
|
mkdirSync(worktreeRoot, { recursive: true });
|
|
@@ -1066,14 +1016,14 @@ test("wired orchestrator base resolver keeps a captured active git worktree", (t
|
|
|
1066
1016
|
);
|
|
1067
1017
|
});
|
|
1068
1018
|
|
|
1069
|
-
//
|
|
1019
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1020
|
+
// Dispatch-decision parity (#5789) — formerly the createWiredDispatchAdapter
|
|
1021
|
+
// tests. These exercise the exported pure decideOrchestratorDispatch helper.
|
|
1022
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1070
1023
|
|
|
1071
|
-
test("
|
|
1024
|
+
test("decideOrchestratorDispatch forwards session-derived dispatch inputs identically to runDispatch", async () => {
|
|
1072
1025
|
const stateSnapshot = makeState();
|
|
1073
1026
|
|
|
1074
|
-
// Install a capturing registry so we observe the DispatchContext both code paths
|
|
1075
|
-
// build, and force a deterministic dispatch action so the parity assertion is
|
|
1076
|
-
// about *inputs*, not rule evaluation.
|
|
1077
1027
|
const captured: DispatchContext[] = [];
|
|
1078
1028
|
const captureRule: UnifiedRule = {
|
|
1079
1029
|
name: "test-capture",
|
|
@@ -1093,7 +1043,6 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
1093
1043
|
setRegistry(new RuleRegistry([captureRule]));
|
|
1094
1044
|
|
|
1095
1045
|
try {
|
|
1096
|
-
// Mock ExtensionContext + ExtensionAPI with the surface the wired adapter touches.
|
|
1097
1046
|
const fakeModelRegistry = {
|
|
1098
1047
|
getAll: () => [],
|
|
1099
1048
|
getProviderAuthMode: (_provider: string) => "apiKey" as const,
|
|
@@ -1105,30 +1054,28 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
1105
1054
|
contextWindow: 200_000,
|
|
1106
1055
|
},
|
|
1107
1056
|
modelRegistry: fakeModelRegistry,
|
|
1108
|
-
} as
|
|
1057
|
+
} as never;
|
|
1109
1058
|
const pi = {
|
|
1110
1059
|
getActiveTools: () => ["read_file", "write_file"],
|
|
1111
|
-
} as
|
|
1060
|
+
} as never;
|
|
1112
1061
|
const basePath = "/tmp/parity-fixture";
|
|
1113
1062
|
|
|
1114
|
-
// Path A —
|
|
1115
|
-
const
|
|
1116
|
-
const adapterResult = await adapter.decideNextUnit({ stateSnapshot });
|
|
1063
|
+
// Path A — the orchestrator's pure dispatch decision.
|
|
1064
|
+
const adapterResult = await decideOrchestratorDispatch(ctx, pi, basePath, undefined, { stateSnapshot });
|
|
1117
1065
|
|
|
1118
1066
|
// Path B — direct resolveDispatch call mirroring phases.ts:runDispatch.
|
|
1119
|
-
|
|
1120
|
-
const
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
? ctx.modelRegistry.getProviderAuthMode(provider)
|
|
1067
|
+
const prefs = undefined;
|
|
1068
|
+
const provider = (ctx as { model?: { provider?: string } }).model?.provider;
|
|
1069
|
+
const authMode = provider && typeof fakeModelRegistry.getProviderAuthMode === "function"
|
|
1070
|
+
? fakeModelRegistry.getProviderAuthMode(provider)
|
|
1124
1071
|
: undefined;
|
|
1125
|
-
const activeTools =
|
|
1072
|
+
const activeTools = ["read_file", "write_file"];
|
|
1126
1073
|
const structuredQuestionsAvailable: "true" | "false" =
|
|
1127
1074
|
prefs !== undefined && (prefs as { planning_depth?: string }).planning_depth === "deep"
|
|
1128
1075
|
? "false"
|
|
1129
1076
|
: supportsStructuredQuestions(activeTools, {
|
|
1130
1077
|
authMode,
|
|
1131
|
-
baseUrl: ctx.model?.baseUrl,
|
|
1078
|
+
baseUrl: (ctx as { model?: { baseUrl?: string } }).model?.baseUrl,
|
|
1132
1079
|
})
|
|
1133
1080
|
? "true"
|
|
1134
1081
|
: "false";
|
|
@@ -1140,17 +1087,15 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
1140
1087
|
state: stateSnapshot,
|
|
1141
1088
|
prefs,
|
|
1142
1089
|
structuredQuestionsAvailable,
|
|
1143
|
-
sessionContextWindow:
|
|
1144
|
-
sessionProvider:
|
|
1145
|
-
modelRegistry:
|
|
1090
|
+
sessionContextWindow: 200_000,
|
|
1091
|
+
sessionProvider: "anthropic",
|
|
1092
|
+
modelRegistry: fakeModelRegistry,
|
|
1146
1093
|
};
|
|
1147
1094
|
const directAction = await resolveDispatch(builtDirectCtx);
|
|
1148
1095
|
|
|
1149
|
-
// Two contexts captured: one per resolveDispatch call.
|
|
1150
1096
|
assert.equal(captured.length, 2, "expected two captured dispatch contexts");
|
|
1151
1097
|
const [adapterCtx, directCtx] = captured;
|
|
1152
1098
|
|
|
1153
|
-
// Parity assertion: session-derived fields are identical.
|
|
1154
1099
|
assert.equal(adapterCtx.structuredQuestionsAvailable, directCtx.structuredQuestionsAvailable);
|
|
1155
1100
|
assert.equal(adapterCtx.sessionContextWindow, directCtx.sessionContextWindow);
|
|
1156
1101
|
assert.equal(adapterCtx.sessionProvider, directCtx.sessionProvider);
|
|
@@ -1159,7 +1104,6 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
1159
1104
|
assert.equal(adapterCtx.mid, directCtx.mid);
|
|
1160
1105
|
assert.equal(adapterCtx.midTitle, directCtx.midTitle);
|
|
1161
1106
|
|
|
1162
|
-
// Dispatch action equality: both flows reach the same dispatch decision.
|
|
1163
1107
|
if (!adapterResult || !("unitType" in adapterResult)) {
|
|
1164
1108
|
assert.fail("expected adapter result to be a dispatch decision");
|
|
1165
1109
|
}
|
|
@@ -1177,7 +1121,7 @@ test("wired DispatchAdapter forwards session-derived dispatch inputs identically
|
|
|
1177
1121
|
}
|
|
1178
1122
|
});
|
|
1179
1123
|
|
|
1180
|
-
test("
|
|
1124
|
+
test("decideOrchestratorDispatch prefers caller-supplied dispatch inputs over ctx-derived values", async () => {
|
|
1181
1125
|
const stateSnapshot = makeState();
|
|
1182
1126
|
const captured: DispatchContext[] = [];
|
|
1183
1127
|
const captureRule: UnifiedRule = {
|
|
@@ -1213,14 +1157,11 @@ test("wired DispatchAdapter prefers caller-supplied dispatch inputs over ctx-der
|
|
|
1213
1157
|
contextWindow: 200_000,
|
|
1214
1158
|
},
|
|
1215
1159
|
modelRegistry: ctxModelRegistry,
|
|
1216
|
-
} as
|
|
1217
|
-
const pi = {
|
|
1218
|
-
|
|
1219
|
-
} as any;
|
|
1220
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/parity-fixture");
|
|
1221
|
-
const session = { basePath: "/tmp/session-fixture" } as any;
|
|
1160
|
+
} as never;
|
|
1161
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1162
|
+
const session = { basePath: "/tmp/session-fixture" } as never;
|
|
1222
1163
|
|
|
1223
|
-
const result = await
|
|
1164
|
+
const result = await decideOrchestratorDispatch(ctx, pi, "/tmp/parity-fixture", undefined, {
|
|
1224
1165
|
stateSnapshot,
|
|
1225
1166
|
session,
|
|
1226
1167
|
structuredQuestionsAvailable: "true",
|
|
@@ -1242,7 +1183,7 @@ test("wired DispatchAdapter prefers caller-supplied dispatch inputs over ctx-der
|
|
|
1242
1183
|
}
|
|
1243
1184
|
});
|
|
1244
1185
|
|
|
1245
|
-
test("
|
|
1186
|
+
test("decideOrchestratorDispatch forwards constructor session when advance input omits session", async () => {
|
|
1246
1187
|
const stateSnapshot = makeState();
|
|
1247
1188
|
const captured: DispatchContext[] = [];
|
|
1248
1189
|
const captureRule: UnifiedRule = {
|
|
@@ -1263,16 +1204,15 @@ test("wired DispatchAdapter forwards constructor session when advance input omit
|
|
|
1263
1204
|
setRegistry(new RuleRegistry([captureRule]));
|
|
1264
1205
|
|
|
1265
1206
|
try {
|
|
1266
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1267
|
-
const pi = { getActiveTools: () => [] } as
|
|
1207
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1208
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1268
1209
|
const session = {
|
|
1269
1210
|
basePath: "/tmp/worktree-fixture",
|
|
1270
1211
|
originalBasePath: "/tmp/project-fixture",
|
|
1271
1212
|
currentMilestoneId: "M001",
|
|
1272
|
-
} as
|
|
1273
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/project-fixture", session);
|
|
1213
|
+
} as never;
|
|
1274
1214
|
|
|
1275
|
-
const result = await
|
|
1215
|
+
const result = await decideOrchestratorDispatch(ctx, pi, "/tmp/project-fixture", session, { stateSnapshot });
|
|
1276
1216
|
|
|
1277
1217
|
assert.ok(result);
|
|
1278
1218
|
assert.equal(captured.length, 1, "expected one captured dispatch context");
|
|
@@ -1283,7 +1223,104 @@ test("wired DispatchAdapter forwards constructor session when advance input omit
|
|
|
1283
1223
|
}
|
|
1284
1224
|
});
|
|
1285
1225
|
|
|
1286
|
-
test("
|
|
1226
|
+
test("decideOrchestratorDispatch evaluates deep pre-planning rules without an active milestone", async (t) => {
|
|
1227
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-no-active-"));
|
|
1228
|
+
t.after(() => {
|
|
1229
|
+
resetRegistry();
|
|
1230
|
+
rmSync(base, { recursive: true, force: true });
|
|
1231
|
+
});
|
|
1232
|
+
resetRegistry();
|
|
1233
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1234
|
+
writeFileSync(
|
|
1235
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
1236
|
+
[
|
|
1237
|
+
"---",
|
|
1238
|
+
"planning_depth: deep",
|
|
1239
|
+
"workflow_prefs_captured: true",
|
|
1240
|
+
"---",
|
|
1241
|
+
"",
|
|
1242
|
+
].join("\n"),
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
const stateSnapshot: GSDState = {
|
|
1246
|
+
...makeState(),
|
|
1247
|
+
activeMilestone: null,
|
|
1248
|
+
phase: "pre-planning",
|
|
1249
|
+
nextAction: "All remaining milestones are parked (M027). Run /gsd unpark M027 or create a new milestone.",
|
|
1250
|
+
registry: [{ id: "M027", title: "Parked", status: "parked" }],
|
|
1251
|
+
};
|
|
1252
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1253
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1254
|
+
const session = {
|
|
1255
|
+
basePath: base,
|
|
1256
|
+
originalBasePath: base,
|
|
1257
|
+
currentMilestoneId: "M027",
|
|
1258
|
+
} as never;
|
|
1259
|
+
|
|
1260
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1261
|
+
|
|
1262
|
+
assert.ok(result && "unitType" in result, `expected project-level dispatch, got ${JSON.stringify(result)}`);
|
|
1263
|
+
assert.equal(result.unitType, "discuss-project");
|
|
1264
|
+
assert.equal(result.unitId, "PROJECT");
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
test("decideOrchestratorDispatch does not replay milestone-scoped verification retry when no milestone is active", async (t) => {
|
|
1268
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-no-active-retry-"));
|
|
1269
|
+
t.after(() => {
|
|
1270
|
+
resetRegistry();
|
|
1271
|
+
rmSync(base, { recursive: true, force: true });
|
|
1272
|
+
});
|
|
1273
|
+
resetRegistry();
|
|
1274
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1275
|
+
writeFileSync(
|
|
1276
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
1277
|
+
[
|
|
1278
|
+
"---",
|
|
1279
|
+
"planning_depth: deep",
|
|
1280
|
+
"workflow_prefs_captured: true",
|
|
1281
|
+
"---",
|
|
1282
|
+
"",
|
|
1283
|
+
].join("\n"),
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
const stateSnapshot: GSDState = {
|
|
1287
|
+
...makeState(),
|
|
1288
|
+
activeMilestone: null,
|
|
1289
|
+
phase: "pre-planning",
|
|
1290
|
+
nextAction: "All remaining milestones are parked (M027). Run /gsd unpark M027 or create a new milestone.",
|
|
1291
|
+
registry: [{ id: "M027", title: "Parked", status: "parked" }],
|
|
1292
|
+
};
|
|
1293
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1294
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1295
|
+
const stalePendingRetry = {
|
|
1296
|
+
unitType: "execute-task",
|
|
1297
|
+
unitId: "M027.S1.T1",
|
|
1298
|
+
prompt: "stale retry prompt",
|
|
1299
|
+
pauseAfterUatDispatch: false,
|
|
1300
|
+
state: stateSnapshot,
|
|
1301
|
+
mid: "M027",
|
|
1302
|
+
midTitle: "Parked",
|
|
1303
|
+
};
|
|
1304
|
+
const session = {
|
|
1305
|
+
basePath: base,
|
|
1306
|
+
originalBasePath: base,
|
|
1307
|
+
currentMilestoneId: "M027",
|
|
1308
|
+
pendingVerificationRetryDispatch: stalePendingRetry,
|
|
1309
|
+
} as never;
|
|
1310
|
+
|
|
1311
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1312
|
+
|
|
1313
|
+
assert.ok(result && "unitType" in result, `expected project-level dispatch, got ${JSON.stringify(result)}`);
|
|
1314
|
+
assert.equal(result.unitType, "discuss-project");
|
|
1315
|
+
assert.equal(result.unitId, "PROJECT");
|
|
1316
|
+
// The stale retry must be preserved for a future tick, not consumed by this
|
|
1317
|
+
// no-active-milestone path (mirrors pre-#712-fix behavior where !active
|
|
1318
|
+
// returned null before touching the retry).
|
|
1319
|
+
const sess = session as unknown as { pendingVerificationRetryDispatch: unknown };
|
|
1320
|
+
assert.equal(sess.pendingVerificationRetryDispatch, stalePendingRetry);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
test("decideOrchestratorDispatch adopts next active milestone after the session milestone is closed", async (t) => {
|
|
1287
1324
|
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-milestone-adopt-"));
|
|
1288
1325
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
1289
1326
|
|
|
@@ -1314,28 +1351,27 @@ test("wired DispatchAdapter adopts next active milestone after the session miles
|
|
|
1314
1351
|
setRegistry(new RuleRegistry([captureRule]));
|
|
1315
1352
|
|
|
1316
1353
|
try {
|
|
1317
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1318
|
-
const pi = { getActiveTools: () => [] } as
|
|
1354
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1355
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1319
1356
|
const session = {
|
|
1320
1357
|
basePath: base,
|
|
1321
1358
|
originalBasePath: base,
|
|
1322
1359
|
currentMilestoneId: "M001",
|
|
1323
|
-
} as
|
|
1324
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, base, session);
|
|
1360
|
+
} as never;
|
|
1325
1361
|
|
|
1326
|
-
const result = await
|
|
1362
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1327
1363
|
|
|
1328
1364
|
assert.ok(result);
|
|
1329
|
-
if (!("unitType" in result)) assert.fail(`expected dispatch decision, got ${JSON.stringify(result)}`);
|
|
1365
|
+
if (!result || !("unitType" in result)) assert.fail(`expected dispatch decision, got ${JSON.stringify(result)}`);
|
|
1330
1366
|
assert.equal(result.unitId, "M002/S01/T01");
|
|
1331
|
-
assert.equal(session.currentMilestoneId, "M002");
|
|
1367
|
+
assert.equal((session as { currentMilestoneId: string }).currentMilestoneId, "M002");
|
|
1332
1368
|
assert.equal(captured[0]?.session?.currentMilestoneId, "M002");
|
|
1333
1369
|
} finally {
|
|
1334
1370
|
resetRegistry();
|
|
1335
1371
|
}
|
|
1336
1372
|
});
|
|
1337
1373
|
|
|
1338
|
-
test("
|
|
1374
|
+
test("decideOrchestratorDispatch keeps blocking stale milestone worktree scope", async (t) => {
|
|
1339
1375
|
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-worktree-block-"));
|
|
1340
1376
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
1341
1377
|
|
|
@@ -1349,16 +1385,15 @@ test("wired DispatchAdapter keeps blocking stale milestone worktree scope", asyn
|
|
|
1349
1385
|
};
|
|
1350
1386
|
const worktreePath = join(base, ".gsd", "worktrees", "M001");
|
|
1351
1387
|
mkdirSync(worktreePath, { recursive: true });
|
|
1352
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1353
|
-
const pi = { getActiveTools: () => [] } as
|
|
1388
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1389
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1354
1390
|
const session = {
|
|
1355
1391
|
basePath: worktreePath,
|
|
1356
1392
|
originalBasePath: base,
|
|
1357
1393
|
currentMilestoneId: "M001",
|
|
1358
|
-
} as
|
|
1359
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, base, session);
|
|
1394
|
+
} as never;
|
|
1360
1395
|
|
|
1361
|
-
const result = await
|
|
1396
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1362
1397
|
|
|
1363
1398
|
assert.deepEqual(result, {
|
|
1364
1399
|
kind: "blocked",
|
|
@@ -1366,13 +1401,13 @@ test("wired DispatchAdapter keeps blocking stale milestone worktree scope", asyn
|
|
|
1366
1401
|
'Dispatch milestone mismatch: context mid "M002" does not match session.currentMilestoneId "M001". The active worktree/session and derived project state disagree; recover, park, or discard the stranded milestone before continuing.',
|
|
1367
1402
|
action: "pause",
|
|
1368
1403
|
});
|
|
1369
|
-
assert.equal(session.currentMilestoneId, "M001");
|
|
1404
|
+
assert.equal((session as { currentMilestoneId: string }).currentMilestoneId, "M001");
|
|
1370
1405
|
});
|
|
1371
1406
|
|
|
1372
|
-
test("
|
|
1407
|
+
test("decideOrchestratorDispatch replays pending verification retry dispatch", async () => {
|
|
1373
1408
|
const stateSnapshot = makeState();
|
|
1374
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1375
|
-
const pi = { getActiveTools: () => [] } as
|
|
1409
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1410
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1376
1411
|
const session = {
|
|
1377
1412
|
basePath: "/tmp/worktree-fixture",
|
|
1378
1413
|
pendingOrchestrationDispatch: null,
|
|
@@ -1385,22 +1420,25 @@ test("wired DispatchAdapter replays pending verification retry dispatch", async
|
|
|
1385
1420
|
mid: "M004",
|
|
1386
1421
|
midTitle: "Milestone 4",
|
|
1387
1422
|
},
|
|
1388
|
-
} as
|
|
1389
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/project-fixture", session);
|
|
1423
|
+
} as never;
|
|
1390
1424
|
|
|
1391
|
-
const result = await
|
|
1425
|
+
const result = await decideOrchestratorDispatch(ctx, pi, "/tmp/project-fixture", session, { stateSnapshot });
|
|
1392
1426
|
|
|
1393
1427
|
assert.ok(result);
|
|
1394
|
-
if (!("unitType" in result)) assert.fail("expected dispatch decision");
|
|
1428
|
+
if (!result || !("unitType" in result)) assert.fail("expected dispatch decision");
|
|
1395
1429
|
assert.equal(result.unitType, "complete-slice");
|
|
1396
1430
|
assert.equal(result.unitId, "M004/S01");
|
|
1397
1431
|
assert.equal(result.reason, "verification-retry");
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1432
|
+
const sess = session as {
|
|
1433
|
+
pendingVerificationRetryDispatch: unknown;
|
|
1434
|
+
pendingOrchestrationDispatch: { prompt?: string; state?: unknown } | null;
|
|
1435
|
+
};
|
|
1436
|
+
assert.equal(sess.pendingVerificationRetryDispatch, null);
|
|
1437
|
+
assert.equal(sess.pendingOrchestrationDispatch?.prompt, "repair slice closeout");
|
|
1438
|
+
assert.equal(sess.pendingOrchestrationDispatch?.state, stateSnapshot);
|
|
1401
1439
|
});
|
|
1402
1440
|
|
|
1403
|
-
test("
|
|
1441
|
+
test("decideOrchestratorDispatch clears verification retry state when skipping an already closed retry dispatch", async () => {
|
|
1404
1442
|
const stateSnapshot = makeState();
|
|
1405
1443
|
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-closed-retry-"));
|
|
1406
1444
|
|
|
@@ -1425,8 +1463,8 @@ test("wired DispatchAdapter clears verification retry state when skipping an alr
|
|
|
1425
1463
|
};
|
|
1426
1464
|
setRegistry(new RuleRegistry([retryRule]));
|
|
1427
1465
|
|
|
1428
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1429
|
-
const pi = { getActiveTools: () => [] } as
|
|
1466
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1467
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1430
1468
|
const session = {
|
|
1431
1469
|
basePath: base,
|
|
1432
1470
|
pendingOrchestrationDispatch: { stale: true },
|
|
@@ -1435,17 +1473,17 @@ test("wired DispatchAdapter clears verification retry state when skipping an alr
|
|
|
1435
1473
|
failureContext: "artifact missing",
|
|
1436
1474
|
attempt: 1,
|
|
1437
1475
|
},
|
|
1438
|
-
} as
|
|
1439
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, base, session);
|
|
1476
|
+
} as never;
|
|
1440
1477
|
|
|
1441
|
-
const result = await
|
|
1478
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1442
1479
|
|
|
1443
1480
|
assert.deepEqual(result, {
|
|
1444
1481
|
kind: "skipped",
|
|
1445
1482
|
reason: "execute-task M001/S01/T01 is already complete",
|
|
1446
1483
|
});
|
|
1447
|
-
|
|
1448
|
-
assert.equal(
|
|
1484
|
+
const sess = session as { pendingVerificationRetry: unknown; pendingOrchestrationDispatch: unknown };
|
|
1485
|
+
assert.equal(sess.pendingVerificationRetry, null);
|
|
1486
|
+
assert.equal(sess.pendingOrchestrationDispatch, null);
|
|
1449
1487
|
} finally {
|
|
1450
1488
|
resetRegistry();
|
|
1451
1489
|
closeDatabase();
|
|
@@ -1453,7 +1491,7 @@ test("wired DispatchAdapter clears verification retry state when skipping an alr
|
|
|
1453
1491
|
}
|
|
1454
1492
|
});
|
|
1455
1493
|
|
|
1456
|
-
test("
|
|
1494
|
+
test("decideOrchestratorDispatch preserves stop reason as a blocked decision", async () => {
|
|
1457
1495
|
const stateSnapshot = makeState();
|
|
1458
1496
|
const stopRule: UnifiedRule = {
|
|
1459
1497
|
name: "test-stop",
|
|
@@ -1469,11 +1507,10 @@ test("wired DispatchAdapter preserves stop reason as a blocked decision", async
|
|
|
1469
1507
|
setRegistry(new RuleRegistry([stopRule]));
|
|
1470
1508
|
|
|
1471
1509
|
try {
|
|
1472
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1473
|
-
const pi = { getActiveTools: () => [] } as
|
|
1474
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/parity-fixture");
|
|
1510
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1511
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1475
1512
|
|
|
1476
|
-
const result = await
|
|
1513
|
+
const result = await decideOrchestratorDispatch(ctx, pi, "/tmp/parity-fixture", undefined, { stateSnapshot });
|
|
1477
1514
|
|
|
1478
1515
|
assert.deepEqual(result, {
|
|
1479
1516
|
kind: "blocked",
|
|
@@ -1485,7 +1522,7 @@ test("wired DispatchAdapter preserves stop reason as a blocked decision", async
|
|
|
1485
1522
|
}
|
|
1486
1523
|
});
|
|
1487
1524
|
|
|
1488
|
-
test("
|
|
1525
|
+
test("decideOrchestratorDispatch preserves dispatch skip instead of collapsing it to no remaining units", async () => {
|
|
1489
1526
|
const stateSnapshot = makeState();
|
|
1490
1527
|
const skipRule: UnifiedRule = {
|
|
1491
1528
|
name: "test-skip-gate",
|
|
@@ -1500,11 +1537,10 @@ test("wired DispatchAdapter preserves dispatch skip instead of collapsing it to
|
|
|
1500
1537
|
setRegistry(new RuleRegistry([skipRule]));
|
|
1501
1538
|
|
|
1502
1539
|
try {
|
|
1503
|
-
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as
|
|
1504
|
-
const pi = { getActiveTools: () => [] } as
|
|
1505
|
-
const adapter = createWiredDispatchAdapter(ctx, pi, "/tmp/parity-fixture");
|
|
1540
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1541
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1506
1542
|
|
|
1507
|
-
const result = await
|
|
1543
|
+
const result = await decideOrchestratorDispatch(ctx, pi, "/tmp/parity-fixture", undefined, { stateSnapshot });
|
|
1508
1544
|
|
|
1509
1545
|
assert.deepEqual(result, {
|
|
1510
1546
|
kind: "skipped",
|