@opengsd/gsd-pi 1.2.0-dev.9ad8ae33 → 1.2.0-dev.a6376d75
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-model-override.d.ts +15 -0
- package/dist/cli-model-override.js +21 -0
- package/dist/cli.js +1 -18
- package/dist/loader.js +6 -4
- package/dist/register-agent-bundles.d.ts +11 -2
- package/dist/register-agent-bundles.js +18 -4
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +3 -2
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
- package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
- package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -1
- package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
- package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
- package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
- package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +15 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
- package/dist/resources/extensions/gsd/db/queries.js +30 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
- package/dist/resources/extensions/gsd/guided-flow.js +88 -2
- package/dist/resources/extensions/gsd/health-widget.js +87 -28
- package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
- package/dist/resources/extensions/gsd/notifications.js +12 -7
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/dist/resources/extensions/gsd/skill-activation.js +3 -6
- package/dist/resources/extensions/gsd/state.js +6 -2
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
- package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
- package/dist/resources/extensions/gsd/unit-registry.js +34 -4
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
- package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
- package/dist/resources/extensions/mcp-client/manager.js +6 -1
- package/dist/runtime-checks.d.ts +10 -0
- package/dist/runtime-checks.js +27 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +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/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 +8 -8
- package/dist/web/standalone/.next/server/middleware-build-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/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +6 -4
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +8 -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 +50 -6
- 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.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +12 -3
- package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli-runner.js +137 -0
- package/packages/mcp-server/dist/cli-runner.js.map +1 -0
- package/packages/mcp-server/dist/cli.js +2 -58
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
- package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
- package/packages/mcp-server/dist/pid-registry.js +452 -0
- package/packages/mcp-server/dist/pid-registry.js.map +1 -0
- package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
- package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
- package/packages/mcp-server/dist/probe-mode.js +10 -0
- package/packages/mcp-server/dist/probe-mode.js.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
- package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +62 -43
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -5
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +43 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/index.d.ts +1 -1
- package/packages/pi-tui/dist/index.d.ts.map +1 -1
- package/packages/pi-tui/dist/index.js +1 -1
- package/packages/pi-tui/dist/index.js.map +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +54 -2
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +8 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +63 -18
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +110 -36
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +45 -17
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +7 -2
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
- package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
- package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -1
- package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
- package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
- package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
- package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
- package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
- package/src/resources/extensions/gsd/db/queries.ts +29 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
- package/src/resources/extensions/gsd/guided-flow.ts +128 -2
- package/src/resources/extensions/gsd/health-widget.ts +91 -27
- package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
- package/src/resources/extensions/gsd/notifications.ts +13 -6
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/src/resources/extensions/gsd/skill-activation.ts +3 -6
- package/src/resources/extensions/gsd/state.ts +7 -1
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
- package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
- package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
- package/src/resources/extensions/gsd/unit-registry.ts +34 -4
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
- package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
- package/src/resources/extensions/mcp-client/manager.ts +7 -1
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Auto-loop pre-dispatch phase.
|
|
3
|
+
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { existsSync, cpSync } from "node:fs";
|
|
6
|
+
import { basename } from "node:path";
|
|
7
|
+
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
8
|
+
import { resolveUokFlags } from "../uok/flags.js";
|
|
9
|
+
import {
|
|
10
|
+
ensurePlanV2Graph,
|
|
11
|
+
isEmptyPlanV2GraphResult,
|
|
12
|
+
isMissingFinalizedContextResult,
|
|
13
|
+
} from "../uok/plan-v2.js";
|
|
14
|
+
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
15
|
+
import { isSliceParallelActive, startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
16
|
+
import { reconcileBeforeSpawn } from "../state-reconciliation.js";
|
|
17
|
+
import {
|
|
18
|
+
countUnmappedActiveRequirements,
|
|
19
|
+
formatCompletePhaseNextAction,
|
|
20
|
+
} from "../requirements-backlog.js";
|
|
21
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
22
|
+
import { getIsolationMode } from "../preferences.js";
|
|
23
|
+
import { gsdRoot } from "../paths.js";
|
|
24
|
+
import { atomicWriteSync } from "../atomic-write.js";
|
|
25
|
+
import { logWarning } from "../workflow-logger.js";
|
|
26
|
+
import { debugLog } from "../debug-logger.js";
|
|
27
|
+
import {
|
|
28
|
+
persistStuckRecoveryAttempts,
|
|
29
|
+
_resolveDispatchGuardBasePath,
|
|
30
|
+
shouldRunPlanV2Gate,
|
|
31
|
+
isSamePathLocal,
|
|
32
|
+
} from "./phase-helpers.js";
|
|
33
|
+
import {
|
|
34
|
+
closeoutAndStop,
|
|
35
|
+
generateMilestoneReport,
|
|
36
|
+
_runMilestoneMergeOnceWithStashRestore,
|
|
37
|
+
shouldSkipTerminalMilestoneCloseout,
|
|
38
|
+
} from "./closeout.js";
|
|
39
|
+
import type { IterationContext, LoopState, PhaseResult, PreDispatchData } from "./types.js";
|
|
40
|
+
|
|
41
|
+
type BlockerKind = "needs-remediation-dead-end" | "other";
|
|
42
|
+
|
|
43
|
+
function classifyBlocker(blocker: string): BlockerKind {
|
|
44
|
+
const normalized = blocker.toLowerCase();
|
|
45
|
+
if (normalized.includes("needs-remediation") && normalized.includes("all slices are complete")) {
|
|
46
|
+
return "needs-remediation-dead-end";
|
|
47
|
+
}
|
|
48
|
+
return "other";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function sanitizeBlockerForUser(blocker: string): string {
|
|
52
|
+
return blocker.replaceAll("gsd_reassess_roadmap", "/gsd dispatch reassess");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Formats blocked resume guidance for users, ensuring internal tool names are
|
|
57
|
+
* never surfaced in notification text.
|
|
58
|
+
*/
|
|
59
|
+
function formatBlockedResumeMessage(blockers: string[]): string {
|
|
60
|
+
const classifiedBlockers = blockers.map((blocker) => ({
|
|
61
|
+
blocker: sanitizeBlockerForUser(blocker),
|
|
62
|
+
kind: classifyBlocker(blocker),
|
|
63
|
+
}));
|
|
64
|
+
const hasNeedsRemediationDeadEnd = classifiedBlockers.some(
|
|
65
|
+
(classifiedBlocker) => classifiedBlocker.kind === "needs-remediation-dead-end"
|
|
66
|
+
);
|
|
67
|
+
if (hasNeedsRemediationDeadEnd) {
|
|
68
|
+
return "Blocked: milestone validation requires remediation but all slices are complete. Run /gsd dispatch reassess to add remediation slices, then /gsd auto to continue.";
|
|
69
|
+
}
|
|
70
|
+
return `Blocked: ${classifiedBlockers.map((classifiedBlocker) => classifiedBlocker.blocker).join(", ")}. Fix and run /gsd auto to resume.`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Phase 1: Pre-dispatch — resource guard, health gate, state derivation,
|
|
75
|
+
* milestone transition, terminal conditions.
|
|
76
|
+
* Returns break to exit the loop, or next with PreDispatchData on success.
|
|
77
|
+
*/
|
|
78
|
+
export async function runPreDispatch(
|
|
79
|
+
ic: IterationContext,
|
|
80
|
+
loopState: LoopState,
|
|
81
|
+
): Promise<PhaseResult<PreDispatchData>> {
|
|
82
|
+
const { ctx, pi, s, deps, prefs } = ic;
|
|
83
|
+
const uokFlags = resolveUokFlags(prefs);
|
|
84
|
+
const runPreDispatchGate = async (input: {
|
|
85
|
+
gateId: string;
|
|
86
|
+
gateType: string;
|
|
87
|
+
outcome: "pass" | "fail" | "retry" | "manual-attention";
|
|
88
|
+
failureClass: "none" | "policy" | "input" | "execution" | "artifact" | "verification" | "closeout" | "git" | "timeout" | "manual-attention" | "unknown";
|
|
89
|
+
rationale: string;
|
|
90
|
+
findings?: string;
|
|
91
|
+
milestoneId?: string;
|
|
92
|
+
}): Promise<void> => {
|
|
93
|
+
if (!uokFlags.gates) return;
|
|
94
|
+
const gateRunner = new UokGateRunner();
|
|
95
|
+
gateRunner.register({
|
|
96
|
+
id: input.gateId,
|
|
97
|
+
type: input.gateType,
|
|
98
|
+
execute: async () => ({
|
|
99
|
+
outcome: input.outcome,
|
|
100
|
+
failureClass: input.failureClass,
|
|
101
|
+
rationale: input.rationale,
|
|
102
|
+
findings: input.findings ?? "",
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
await gateRunner.run(input.gateId, {
|
|
106
|
+
basePath: s.basePath,
|
|
107
|
+
traceId: `pre-dispatch:${ic.flowId}`,
|
|
108
|
+
turnId: `iter-${ic.iteration}`,
|
|
109
|
+
milestoneId: input.milestoneId ?? s.currentMilestoneId ?? undefined,
|
|
110
|
+
unitType: "pre-dispatch",
|
|
111
|
+
unitId: `iter-${ic.iteration}`,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Resource version guard
|
|
116
|
+
const staleMsg = deps.checkResourcesStale(s.resourceVersionOnStart);
|
|
117
|
+
if (staleMsg) {
|
|
118
|
+
await runPreDispatchGate({
|
|
119
|
+
gateId: "resource-version-guard",
|
|
120
|
+
gateType: "policy",
|
|
121
|
+
outcome: "fail",
|
|
122
|
+
failureClass: "policy",
|
|
123
|
+
rationale: "resource version guard blocked dispatch",
|
|
124
|
+
findings: staleMsg,
|
|
125
|
+
});
|
|
126
|
+
await deps.stopAuto(ctx, pi, staleMsg);
|
|
127
|
+
debugLog("autoLoop", { phase: "exit", reason: "resources-stale" });
|
|
128
|
+
return { action: "break", reason: "resources-stale" };
|
|
129
|
+
}
|
|
130
|
+
await runPreDispatchGate({
|
|
131
|
+
gateId: "resource-version-guard",
|
|
132
|
+
gateType: "policy",
|
|
133
|
+
outcome: "pass",
|
|
134
|
+
failureClass: "none",
|
|
135
|
+
rationale: "resource version guard passed",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
deps.invalidateAllCaches();
|
|
139
|
+
s.lastPromptCharCount = undefined;
|
|
140
|
+
s.lastBaselineCharCount = undefined;
|
|
141
|
+
|
|
142
|
+
// Pre-dispatch health gate
|
|
143
|
+
try {
|
|
144
|
+
const expectedCurrentUnit = null;
|
|
145
|
+
const healthGate = await deps.preDispatchHealthGate(s.basePath);
|
|
146
|
+
if (healthGate.fixesApplied.length > 0) {
|
|
147
|
+
ctx.ui.notify(
|
|
148
|
+
`Pre-dispatch: ${healthGate.fixesApplied.join(", ")}`,
|
|
149
|
+
"info",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (!healthGate.proceed) {
|
|
153
|
+
await runPreDispatchGate({
|
|
154
|
+
gateId: "pre-dispatch-health-gate",
|
|
155
|
+
gateType: "execution",
|
|
156
|
+
outcome: "manual-attention",
|
|
157
|
+
failureClass: "manual-attention",
|
|
158
|
+
rationale: "pre-dispatch health gate blocked dispatch",
|
|
159
|
+
findings: healthGate.reason,
|
|
160
|
+
});
|
|
161
|
+
ctx.ui.notify(
|
|
162
|
+
healthGate.reason || "Pre-dispatch health check failed — run /gsd doctor for details.",
|
|
163
|
+
"error",
|
|
164
|
+
);
|
|
165
|
+
await deps.pauseAuto(ctx, pi, undefined, { expectedCurrentUnit });
|
|
166
|
+
debugLog("autoLoop", { phase: "exit", reason: "health-gate-failed" });
|
|
167
|
+
return { action: "break", reason: "health-gate-failed" };
|
|
168
|
+
}
|
|
169
|
+
await runPreDispatchGate({
|
|
170
|
+
gateId: "pre-dispatch-health-gate",
|
|
171
|
+
gateType: "execution",
|
|
172
|
+
outcome: "pass",
|
|
173
|
+
failureClass: "none",
|
|
174
|
+
rationale: "pre-dispatch health gate passed",
|
|
175
|
+
findings: healthGate.fixesApplied.length > 0 ? healthGate.fixesApplied.join(", ") : "",
|
|
176
|
+
});
|
|
177
|
+
} catch (e) {
|
|
178
|
+
await runPreDispatchGate({
|
|
179
|
+
gateId: "pre-dispatch-health-gate",
|
|
180
|
+
gateType: "execution",
|
|
181
|
+
outcome: "manual-attention",
|
|
182
|
+
failureClass: "manual-attention",
|
|
183
|
+
rationale: "pre-dispatch health gate threw unexpectedly",
|
|
184
|
+
findings: String(e),
|
|
185
|
+
});
|
|
186
|
+
logWarning("engine", "Pre-dispatch health gate threw unexpectedly", { error: String(e) });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Sync project root artifacts into worktree
|
|
190
|
+
if (
|
|
191
|
+
s.originalBasePath &&
|
|
192
|
+
!isSamePathLocal(s.basePath, s.originalBasePath) &&
|
|
193
|
+
s.currentMilestoneId &&
|
|
194
|
+
s.scope
|
|
195
|
+
) {
|
|
196
|
+
deps.worktreeProjection.projectRootToWorktree(s.scope);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Derive state — use canonical project root so the cache key is stable
|
|
200
|
+
// across worktree↔project-root path-form alternation. See PR #5236
|
|
201
|
+
// (workspace handle infrastructure) and the Phase A pt 2 plan.
|
|
202
|
+
let state = await deps.deriveState(s.canonicalProjectRoot);
|
|
203
|
+
const { getDeepStageGate } = await import("../auto-dispatch.js");
|
|
204
|
+
const deepStageGate = getDeepStageGate(prefs, s.basePath);
|
|
205
|
+
const canRunDeepSetupGate =
|
|
206
|
+
state.phase === "pre-planning" ||
|
|
207
|
+
state.phase === "needs-discussion" ||
|
|
208
|
+
state.phase === "planning";
|
|
209
|
+
if (
|
|
210
|
+
canRunDeepSetupGate &&
|
|
211
|
+
(deepStageGate.status === "pending" || deepStageGate.status === "blocked")
|
|
212
|
+
) {
|
|
213
|
+
debugLog("autoLoop", {
|
|
214
|
+
phase: "deep-project-stage-gate",
|
|
215
|
+
stage: deepStageGate.stage,
|
|
216
|
+
status: deepStageGate.status,
|
|
217
|
+
reason: deepStageGate.reason,
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
action: "next",
|
|
221
|
+
data: {
|
|
222
|
+
state: {
|
|
223
|
+
...state,
|
|
224
|
+
phase: "pre-planning",
|
|
225
|
+
activeMilestone: null,
|
|
226
|
+
activeSlice: null,
|
|
227
|
+
activeTask: null,
|
|
228
|
+
nextAction: deepStageGate.reason,
|
|
229
|
+
},
|
|
230
|
+
mid: "PROJECT",
|
|
231
|
+
midTitle: "Project setup",
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (uokFlags.planV2 && shouldRunPlanV2Gate(state.phase)) {
|
|
237
|
+
let compiled = ensurePlanV2Graph(s.basePath, state);
|
|
238
|
+
if (isEmptyPlanV2GraphResult(compiled)) {
|
|
239
|
+
deps.invalidateAllCaches();
|
|
240
|
+
state = await deps.deriveState(s.canonicalProjectRoot);
|
|
241
|
+
compiled = shouldRunPlanV2Gate(state.phase)
|
|
242
|
+
? ensurePlanV2Graph(s.basePath, state)
|
|
243
|
+
: {
|
|
244
|
+
ok: true,
|
|
245
|
+
reason: "empty plan-v2 graph recovered by state rederive",
|
|
246
|
+
nodeCount: 0,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (!compiled.ok) {
|
|
250
|
+
const reason = compiled.reason ?? "Plan v2 compilation failed";
|
|
251
|
+
if (isMissingFinalizedContextResult(compiled)) {
|
|
252
|
+
await runPreDispatchGate({
|
|
253
|
+
gateId: "plan-v2-gate",
|
|
254
|
+
gateType: "policy",
|
|
255
|
+
outcome: "pass",
|
|
256
|
+
failureClass: "none",
|
|
257
|
+
rationale: "plan v2 missing context recovery deferred to dispatch",
|
|
258
|
+
findings: reason,
|
|
259
|
+
milestoneId: state.activeMilestone?.id ?? undefined,
|
|
260
|
+
});
|
|
261
|
+
} else {
|
|
262
|
+
await runPreDispatchGate({
|
|
263
|
+
gateId: "plan-v2-gate",
|
|
264
|
+
gateType: "policy",
|
|
265
|
+
outcome: "manual-attention",
|
|
266
|
+
failureClass: "manual-attention",
|
|
267
|
+
rationale: "plan v2 compile gate failed",
|
|
268
|
+
findings: reason,
|
|
269
|
+
milestoneId: state.activeMilestone?.id ?? undefined,
|
|
270
|
+
});
|
|
271
|
+
ctx.ui.notify(`Plan gate failed-closed: ${reason}\n\nIf this keeps happening, try: /gsd doctor heal`, "error");
|
|
272
|
+
await deps.pauseAuto(ctx, pi);
|
|
273
|
+
return { action: "break", reason: "plan-v2-gate-failed" };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (compiled.ok) {
|
|
277
|
+
await runPreDispatchGate({
|
|
278
|
+
gateId: "plan-v2-gate",
|
|
279
|
+
gateType: "policy",
|
|
280
|
+
outcome: "pass",
|
|
281
|
+
failureClass: "none",
|
|
282
|
+
rationale: "plan v2 compile gate passed",
|
|
283
|
+
milestoneId: state.activeMilestone?.id ?? undefined,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
deps.syncCmuxSidebar(prefs, state);
|
|
288
|
+
let mid = state.activeMilestone?.id;
|
|
289
|
+
let midTitle = state.activeMilestone?.title;
|
|
290
|
+
debugLog("autoLoop", {
|
|
291
|
+
phase: "state-derived",
|
|
292
|
+
iteration: ic.iteration,
|
|
293
|
+
mid,
|
|
294
|
+
statePhase: state.phase,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ── Slice-level parallelism gate (#2340) ─────────────────────────────
|
|
298
|
+
// When slice_parallel is enabled, check if multiple slices are eligible
|
|
299
|
+
// for parallel execution. If so, dispatch them in parallel and stop the
|
|
300
|
+
// sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
|
|
301
|
+
if (
|
|
302
|
+
prefs?.slice_parallel?.enabled &&
|
|
303
|
+
mid &&
|
|
304
|
+
!process.env.GSD_PARALLEL_WORKER &&
|
|
305
|
+
isDbAvailable()
|
|
306
|
+
) {
|
|
307
|
+
try {
|
|
308
|
+
const projectRoot = _resolveDispatchGuardBasePath(s);
|
|
309
|
+
if (isSliceParallelActive(projectRoot)) {
|
|
310
|
+
ctx.ui.notify("Slice-parallel: workers are still running; waiting for completion before next dispatch.", "info");
|
|
311
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
|
312
|
+
return { action: "continue" };
|
|
313
|
+
}
|
|
314
|
+
const dbSlices = getMilestoneSlices(mid);
|
|
315
|
+
if (dbSlices.length > 0) {
|
|
316
|
+
const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
|
|
317
|
+
const sliceInputs = dbSlices.map(sl => ({
|
|
318
|
+
id: sl.id,
|
|
319
|
+
done: doneIds.has(sl.id),
|
|
320
|
+
depends: sl.depends ?? [],
|
|
321
|
+
}));
|
|
322
|
+
const eligible = getEligibleSlices(sliceInputs, doneIds);
|
|
323
|
+
if (eligible.length > 1) {
|
|
324
|
+
debugLog("autoLoop", {
|
|
325
|
+
phase: "slice-parallel-dispatch",
|
|
326
|
+
iteration: ic.iteration,
|
|
327
|
+
mid,
|
|
328
|
+
eligibleSlices: eligible.map(e => e.id),
|
|
329
|
+
});
|
|
330
|
+
ctx.ui.notify(
|
|
331
|
+
`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`,
|
|
332
|
+
"info",
|
|
333
|
+
);
|
|
334
|
+
// ADR-017 #5707: reconcile before spawning so each worker doesn't
|
|
335
|
+
// independently race on the same drift. Failure aborts the spawn.
|
|
336
|
+
const spawnGate = await reconcileBeforeSpawn(projectRoot);
|
|
337
|
+
if (!spawnGate.ok) {
|
|
338
|
+
ctx.ui.notify(
|
|
339
|
+
`Slice-parallel: aborting spawn — ${spawnGate.reason}`,
|
|
340
|
+
"error",
|
|
341
|
+
);
|
|
342
|
+
return { action: "break", reason: `slice-parallel-reconciliation-failed: ${spawnGate.reason}` };
|
|
343
|
+
}
|
|
344
|
+
const result = await startSliceParallel(
|
|
345
|
+
projectRoot,
|
|
346
|
+
mid,
|
|
347
|
+
eligible,
|
|
348
|
+
{
|
|
349
|
+
maxWorkers: prefs.slice_parallel.max_workers ?? 2,
|
|
350
|
+
useExecutionGraph: uokFlags.executionGraph,
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
if (result.started.length > 0) {
|
|
354
|
+
ctx.ui.notify(
|
|
355
|
+
`Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`,
|
|
356
|
+
"info",
|
|
357
|
+
);
|
|
358
|
+
return { action: "continue" };
|
|
359
|
+
}
|
|
360
|
+
if (result.errors.length > 0) {
|
|
361
|
+
const detail = result.errors
|
|
362
|
+
.map((err) => `${err.sid}: ${err.error}`)
|
|
363
|
+
.join("; ");
|
|
364
|
+
ctx.ui.notify(
|
|
365
|
+
`Slice-parallel startup failed; falling back to sequential execution. ${detail}`,
|
|
366
|
+
"warning",
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
// Fall through to sequential if no workers started
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
debugLog("autoLoop", {
|
|
374
|
+
phase: "slice-parallel-check-error",
|
|
375
|
+
error: err instanceof Error ? err.message : String(err),
|
|
376
|
+
});
|
|
377
|
+
// Non-fatal — fall through to sequential dispatch
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ── Milestone transition ────────────────────────────────────────────
|
|
382
|
+
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
383
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
|
|
384
|
+
ctx.ui.notify(
|
|
385
|
+
`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}: ${midTitle}.`,
|
|
386
|
+
"info",
|
|
387
|
+
);
|
|
388
|
+
deps.sendDesktopNotification(
|
|
389
|
+
"GSD",
|
|
390
|
+
`Milestone ${s.currentMilestoneId} complete!`,
|
|
391
|
+
"success",
|
|
392
|
+
"milestone",
|
|
393
|
+
basename(s.originalBasePath || s.basePath),
|
|
394
|
+
);
|
|
395
|
+
deps.logCmuxEvent(
|
|
396
|
+
prefs,
|
|
397
|
+
`Milestone ${s.currentMilestoneId} complete. Advancing to ${mid}.`,
|
|
398
|
+
"success",
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const vizPrefs = prefs;
|
|
402
|
+
if (vizPrefs?.auto_visualize) {
|
|
403
|
+
ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
|
|
404
|
+
}
|
|
405
|
+
if (vizPrefs?.auto_report !== false) {
|
|
406
|
+
try {
|
|
407
|
+
await generateMilestoneReport(s, ctx, s.currentMilestoneId!);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
ctx.ui.notify(
|
|
410
|
+
`Report generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
411
|
+
"warning",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Reset dispatch counters for new milestone
|
|
417
|
+
s.unitDispatchCount.clear();
|
|
418
|
+
s.unitRecoveryCount.clear();
|
|
419
|
+
s.unitLifetimeDispatches.clear();
|
|
420
|
+
loopState.recentUnits.length = 0;
|
|
421
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
422
|
+
persistStuckRecoveryAttempts(s, loopState);
|
|
423
|
+
|
|
424
|
+
// Worktree lifecycle on milestone transition — merge current, enter next.
|
|
425
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
426
|
+
{
|
|
427
|
+
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId!);
|
|
428
|
+
if (stop) return stop;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
432
|
+
|
|
433
|
+
deps.invalidateAllCaches();
|
|
434
|
+
|
|
435
|
+
state = await deps.deriveState(s.canonicalProjectRoot);
|
|
436
|
+
mid = state.activeMilestone?.id;
|
|
437
|
+
midTitle = state.activeMilestone?.title;
|
|
438
|
+
|
|
439
|
+
if (mid) {
|
|
440
|
+
if (deps.getIsolationMode(s.basePath) !== "none") {
|
|
441
|
+
deps.captureIntegrationBranch(s.basePath, mid);
|
|
442
|
+
}
|
|
443
|
+
const enterResult = deps.lifecycle.enterMilestone(mid, ctx.ui);
|
|
444
|
+
if (!enterResult.ok) {
|
|
445
|
+
ctx.ui.notify(
|
|
446
|
+
`Milestone transition stopped: failed to enter ${mid} (${enterResult.reason}).`,
|
|
447
|
+
"error",
|
|
448
|
+
);
|
|
449
|
+
if (enterResult.reason === "lease-conflict") {
|
|
450
|
+
await deps.pauseAuto(ctx, pi);
|
|
451
|
+
}
|
|
452
|
+
return { action: "break", reason: "milestone-enter-failed" };
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
// mid is undefined — no milestone to capture integration branch for
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const pendingIds = state.registry
|
|
459
|
+
.filter(
|
|
460
|
+
(m: { status: string }) =>
|
|
461
|
+
m.status !== "complete" && m.status !== "parked",
|
|
462
|
+
)
|
|
463
|
+
.map((m: { id: string }) => m.id);
|
|
464
|
+
deps.pruneQueueOrder(s.basePath, pendingIds);
|
|
465
|
+
|
|
466
|
+
// Archive the old completed-units.json instead of wiping it (#2313).
|
|
467
|
+
try {
|
|
468
|
+
const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
|
|
469
|
+
if (existsSync(completedKeysPath) && s.currentMilestoneId) {
|
|
470
|
+
const archivePath = join(
|
|
471
|
+
gsdRoot(s.basePath),
|
|
472
|
+
`completed-units-${s.currentMilestoneId}.json`,
|
|
473
|
+
);
|
|
474
|
+
cpSync(completedKeysPath, archivePath);
|
|
475
|
+
}
|
|
476
|
+
atomicWriteSync(completedKeysPath, JSON.stringify([], null, 2));
|
|
477
|
+
} catch (e) {
|
|
478
|
+
logWarning("engine", "Failed to archive completed-units on milestone transition", { error: String(e) });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Rebuild STATE.md immediately so it reflects the new active milestone.
|
|
482
|
+
// This bypasses the 30-second throttle in the normal rebuild path —
|
|
483
|
+
// milestone transitions are rare and important enough to warrant an
|
|
484
|
+
// immediate write.
|
|
485
|
+
try {
|
|
486
|
+
await deps.rebuildState(s.basePath);
|
|
487
|
+
} catch (e) {
|
|
488
|
+
logWarning("engine", "STATE.md rebuild failed after milestone transition", { error: String(e) });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Re-project ROADMAP/PLAN markdown from the authoritative DB. Worktree DB
|
|
492
|
+
// reconciliation during merge can leave main-branch markdown stale relative
|
|
493
|
+
// to gsd.db (the 3M/3S/10T vs 3M/5S/16T drift class at /gsd startup).
|
|
494
|
+
try {
|
|
495
|
+
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
496
|
+
await rebuildMarkdownProjectionsFromDb(s.canonicalProjectRoot);
|
|
497
|
+
if (s.basePath !== s.canonicalProjectRoot) {
|
|
498
|
+
await rebuildMarkdownProjectionsFromDb(s.basePath);
|
|
499
|
+
}
|
|
500
|
+
} catch (e) {
|
|
501
|
+
logWarning("engine", "markdown projection rebuild failed after milestone transition", { error: String(e) });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (mid) {
|
|
506
|
+
s.currentMilestoneId = mid;
|
|
507
|
+
deps.setActiveMilestoneId(s.basePath, mid);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ── Terminal conditions ──────────────────────────────────────────────
|
|
511
|
+
|
|
512
|
+
if (state.phase === "complete") {
|
|
513
|
+
const closeoutSkip = await shouldSkipTerminalMilestoneCloseout(s, state, mid);
|
|
514
|
+
if (closeoutSkip.skip) {
|
|
515
|
+
debugLog("autoLoop", { phase: "complete", reason: "milestone-already-closed", milestoneId: closeoutSkip.milestoneId });
|
|
516
|
+
return { action: "break", reason: "milestone-complete" };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (!mid) {
|
|
521
|
+
if (s.currentUnit) {
|
|
522
|
+
await deps.closeoutUnit(
|
|
523
|
+
ctx,
|
|
524
|
+
s.basePath,
|
|
525
|
+
s.currentUnit.type,
|
|
526
|
+
s.currentUnit.id,
|
|
527
|
+
s.currentUnit.startedAt,
|
|
528
|
+
deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const incomplete = state.registry.filter(
|
|
533
|
+
(m: { status: string }) =>
|
|
534
|
+
m.status !== "complete" && m.status !== "parked",
|
|
535
|
+
);
|
|
536
|
+
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
537
|
+
// All milestones complete — merge milestone branch before stopping.
|
|
538
|
+
if (s.currentMilestoneId) {
|
|
539
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
540
|
+
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
541
|
+
if (stop) return stop;
|
|
542
|
+
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
543
|
+
}
|
|
544
|
+
const unmappedActive = countUnmappedActiveRequirements();
|
|
545
|
+
const completionStopReason = formatCompletePhaseNextAction(unmappedActive);
|
|
546
|
+
deps.sendDesktopNotification(
|
|
547
|
+
"GSD",
|
|
548
|
+
unmappedActive > 0 ? "All milestones complete — requirements backlog remains" : "All milestones complete!",
|
|
549
|
+
"success",
|
|
550
|
+
"milestone",
|
|
551
|
+
basename(s.originalBasePath || s.basePath),
|
|
552
|
+
);
|
|
553
|
+
deps.logCmuxEvent(
|
|
554
|
+
prefs,
|
|
555
|
+
completionStopReason,
|
|
556
|
+
"success",
|
|
557
|
+
);
|
|
558
|
+
await deps.stopAuto(ctx, pi, completionStopReason, {
|
|
559
|
+
completionWidget: {
|
|
560
|
+
milestoneId: s.currentMilestoneId,
|
|
561
|
+
milestoneTitle: midTitle,
|
|
562
|
+
allMilestonesComplete: true,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
} else if (incomplete.length === 0 && state.registry.length === 0) {
|
|
566
|
+
// Empty registry — no milestones visible, likely a path resolution bug
|
|
567
|
+
const diag = `basePath=${s.basePath}, phase=${state.phase}`;
|
|
568
|
+
ctx.ui.notify(
|
|
569
|
+
`No milestones visible in current scope. Possible path resolution issue.\n Diagnostic: ${diag}`,
|
|
570
|
+
"error",
|
|
571
|
+
);
|
|
572
|
+
await deps.stopAuto(
|
|
573
|
+
ctx,
|
|
574
|
+
pi,
|
|
575
|
+
`No milestones found — check basePath resolution`,
|
|
576
|
+
);
|
|
577
|
+
} else if (state.phase === "blocked") {
|
|
578
|
+
const blockedResumeMessage = formatBlockedResumeMessage(state.blockers);
|
|
579
|
+
// Pause instead of hard-stop so the session is resumable with `/gsd auto`.
|
|
580
|
+
// Hard-stop here was causing premature termination when slice dependencies
|
|
581
|
+
// were temporarily unresolvable (e.g. after reassessment added new slices).
|
|
582
|
+
await deps.pauseAuto(ctx, pi);
|
|
583
|
+
ctx.ui.notify(blockedResumeMessage, "warning");
|
|
584
|
+
deps.sendDesktopNotification("GSD", blockedResumeMessage, "warning", "attention", basename(s.originalBasePath || s.basePath));
|
|
585
|
+
deps.logCmuxEvent(prefs, blockedResumeMessage, "warning");
|
|
586
|
+
} else {
|
|
587
|
+
const ids = incomplete.map((m: { id: string }) => m.id).join(", ");
|
|
588
|
+
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map((m: { id: string; status: string }) => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
589
|
+
ctx.ui.notify(
|
|
590
|
+
`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`,
|
|
591
|
+
"error",
|
|
592
|
+
);
|
|
593
|
+
await deps.stopAuto(
|
|
594
|
+
ctx,
|
|
595
|
+
pi,
|
|
596
|
+
`No active milestone — ${incomplete.length} incomplete (${ids}), see diagnostic above`,
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
debugLog("autoLoop", { phase: "exit", reason: "no-active-milestone" });
|
|
600
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "no-active-milestone" } });
|
|
601
|
+
return { action: "break", reason: "no-active-milestone" };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!midTitle) {
|
|
605
|
+
midTitle = mid;
|
|
606
|
+
ctx.ui.notify(
|
|
607
|
+
`Milestone ${mid} has no title in roadmap — using ID as fallback.`,
|
|
608
|
+
"warning",
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Mid-merge safety check
|
|
613
|
+
const mergeReconcileResult = deps.reconcileMergeState(s.basePath, ctx);
|
|
614
|
+
if (mergeReconcileResult === "blocked") {
|
|
615
|
+
await deps.pauseAuto(ctx, pi);
|
|
616
|
+
debugLog("autoLoop", { phase: "exit", reason: "merge-reconciliation-blocked" });
|
|
617
|
+
return { action: "break", reason: "merge-reconciliation-blocked" };
|
|
618
|
+
}
|
|
619
|
+
if (mergeReconcileResult === "reconciled") {
|
|
620
|
+
deps.invalidateAllCaches();
|
|
621
|
+
state = await deps.deriveState(s.canonicalProjectRoot);
|
|
622
|
+
mid = state.activeMilestone?.id;
|
|
623
|
+
midTitle = state.activeMilestone?.title;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (!mid || !midTitle) {
|
|
627
|
+
const noMilestoneReason = !mid
|
|
628
|
+
? "No active milestone after merge reconciliation"
|
|
629
|
+
: `Milestone ${mid} has no title after reconciliation`;
|
|
630
|
+
await closeoutAndStop(ctx, pi, s, deps, noMilestoneReason);
|
|
631
|
+
debugLog("autoLoop", {
|
|
632
|
+
phase: "exit",
|
|
633
|
+
reason: "no-milestone-after-reconciliation",
|
|
634
|
+
});
|
|
635
|
+
return { action: "break", reason: "no-milestone-after-reconciliation" };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Terminal: complete
|
|
639
|
+
if (state.phase === "complete") {
|
|
640
|
+
// Milestone merge on complete (before closeout so branch state is clean).
|
|
641
|
+
if (s.currentMilestoneId) {
|
|
642
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
643
|
+
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
644
|
+
if (stop) return stop;
|
|
645
|
+
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
646
|
+
}
|
|
647
|
+
deps.sendDesktopNotification(
|
|
648
|
+
"GSD",
|
|
649
|
+
`Milestone ${mid} complete!`,
|
|
650
|
+
"success",
|
|
651
|
+
"milestone",
|
|
652
|
+
basename(s.originalBasePath || s.basePath),
|
|
653
|
+
);
|
|
654
|
+
deps.logCmuxEvent(
|
|
655
|
+
prefs,
|
|
656
|
+
`Milestone ${mid} complete.`,
|
|
657
|
+
"success",
|
|
658
|
+
);
|
|
659
|
+
if (s.currentUnit) {
|
|
660
|
+
await deps.closeoutUnit(
|
|
661
|
+
ctx,
|
|
662
|
+
s.basePath,
|
|
663
|
+
s.currentUnit.type,
|
|
664
|
+
s.currentUnit.id,
|
|
665
|
+
s.currentUnit.startedAt,
|
|
666
|
+
deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
|
|
667
|
+
);
|
|
668
|
+
s.clearCurrentUnit();
|
|
669
|
+
}
|
|
670
|
+
await deps.stopAuto(ctx, pi, `Milestone ${mid} complete`, {
|
|
671
|
+
completionWidget: {
|
|
672
|
+
milestoneId: mid,
|
|
673
|
+
milestoneTitle: midTitle,
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
677
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "milestone-complete", milestoneId: mid } });
|
|
678
|
+
return { action: "break", reason: "milestone-complete" };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Terminal: blocked — pause instead of hard-stop so the session is resumable.
|
|
682
|
+
if (state.phase === "blocked") {
|
|
683
|
+
const blockedResumeMessage = formatBlockedResumeMessage(state.blockers);
|
|
684
|
+
if (s.currentUnit) {
|
|
685
|
+
await deps.closeoutUnit(
|
|
686
|
+
ctx,
|
|
687
|
+
s.basePath,
|
|
688
|
+
s.currentUnit.type,
|
|
689
|
+
s.currentUnit.id,
|
|
690
|
+
s.currentUnit.startedAt,
|
|
691
|
+
deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
await deps.pauseAuto(ctx, pi);
|
|
695
|
+
ctx.ui.notify(blockedResumeMessage, "warning");
|
|
696
|
+
deps.sendDesktopNotification("GSD", blockedResumeMessage, "warning", "attention", basename(s.originalBasePath || s.basePath));
|
|
697
|
+
deps.logCmuxEvent(prefs, blockedResumeMessage, "warning");
|
|
698
|
+
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
699
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "blocked", blockers: state.blockers } });
|
|
700
|
+
return { action: "break", reason: "blocked" };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return { action: "next", data: { state, mid, midTitle } };
|
|
704
|
+
}
|