gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +178 -108
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-start.js +197 -6
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +79 -24
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +238 -28
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +66 -32
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
- 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 +15 -15
- 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 +15 -15
- 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 +3 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +9 -2
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.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 +2 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
- package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
- package/packages/pi-agent-core/dist/token-audit.js +221 -0
- package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
- package/packages/pi-agent-core/dist/types.d.ts +9 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-agent-core/src/agent.ts +8 -0
- package/packages/pi-agent-core/src/index.ts +2 -0
- package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
- package/packages/pi-agent-core/src/token-audit.ts +287 -0
- package/packages/pi-agent-core/src/types.ts +14 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +41 -7
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +45 -7
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
- package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
- package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +251 -172
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
- package/src/resources/extensions/gsd/auto/session.ts +8 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-start.ts +230 -9
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
- package/src/resources/extensions/gsd/auto.ts +87 -19
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +283 -28
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +84 -39
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
- package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
- package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
- package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
|
@@ -23,7 +23,7 @@ import type { CmuxLogLevel } from "../../shared/cmux-events.js";
|
|
|
23
23
|
import type { JournalEntry } from "../journal.js";
|
|
24
24
|
import type { MergeReconcileResult } from "../auto-recovery.js";
|
|
25
25
|
import type { UokTurnObserver } from "../uok/contracts.js";
|
|
26
|
-
import type { PreflightResult } from "../clean-root-preflight.js";
|
|
26
|
+
import type { PostflightResult, PreflightResult } from "../clean-root-preflight.js";
|
|
27
27
|
|
|
28
28
|
type PauseAutoFn = (
|
|
29
29
|
ctx?: ExtensionContext,
|
|
@@ -141,7 +141,7 @@ export interface LoopDeps {
|
|
|
141
141
|
milestoneId: string,
|
|
142
142
|
stashMarker: string | undefined,
|
|
143
143
|
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
144
|
-
) =>
|
|
144
|
+
) => PostflightResult;
|
|
145
145
|
|
|
146
146
|
// Budget/context/secrets
|
|
147
147
|
getLedger: () => unknown;
|
|
@@ -50,13 +50,14 @@ import {
|
|
|
50
50
|
} from "../workflow-logger.js";
|
|
51
51
|
import { gsdRoot } from "../paths.js";
|
|
52
52
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
53
|
-
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
53
|
+
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
|
|
54
54
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
55
55
|
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
56
56
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
57
57
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
58
|
-
import { isDbAvailable, getMilestoneSlices
|
|
58
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
59
59
|
import type { MinimalModelRegistry } from "../context-budget.js";
|
|
60
|
+
import type { PostflightResult, PreflightResult } from "../clean-root-preflight.js";
|
|
60
61
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
61
62
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
62
63
|
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
@@ -76,10 +77,15 @@ function isSamePathLocal(a: string, b: string): boolean {
|
|
|
76
77
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
export function shouldDegradeEmptyWorktreeToProjectRoot(
|
|
81
|
+
worktreeClassification: ReturnType<typeof classifyProject>,
|
|
82
|
+
projectRootClassification: ReturnType<typeof classifyProject>,
|
|
83
|
+
): boolean {
|
|
84
|
+
return (
|
|
85
|
+
worktreeClassification.kind === "greenfield" &&
|
|
86
|
+
projectRootClassification.kind !== "greenfield" &&
|
|
87
|
+
projectRootClassification.kind !== "invalid-repo"
|
|
88
|
+
);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
@@ -215,6 +221,117 @@ async function closeoutAndStop(
|
|
|
215
221
|
await deps.stopAuto(ctx, pi, reason);
|
|
216
222
|
}
|
|
217
223
|
|
|
224
|
+
async function stopOnPostflightRecoveryNeeded(
|
|
225
|
+
ic: IterationContext,
|
|
226
|
+
result: PostflightResult,
|
|
227
|
+
milestoneId: string,
|
|
228
|
+
): Promise<{ action: "break"; reason: string } | null> {
|
|
229
|
+
if (!result.needsManualRecovery) return null;
|
|
230
|
+
const { ctx, pi, deps } = ic;
|
|
231
|
+
const reason = `Post-merge stash restore failed for milestone ${milestoneId}`;
|
|
232
|
+
ctx.ui.notify(
|
|
233
|
+
`${reason}. Resolve the working tree before resuming auto-mode. ${result.message}`,
|
|
234
|
+
"error",
|
|
235
|
+
);
|
|
236
|
+
await deps.stopAuto(ctx, pi, reason);
|
|
237
|
+
return { action: "break", reason: "postflight-stash-restore-failed" };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function restorePreflightStashOrStop(
|
|
241
|
+
ic: IterationContext,
|
|
242
|
+
preflight: PreflightResult,
|
|
243
|
+
milestoneId: string,
|
|
244
|
+
): Promise<{ action: "break"; reason: string } | null> {
|
|
245
|
+
if (!preflight.stashPushed) return null;
|
|
246
|
+
const { ctx, s, deps } = ic;
|
|
247
|
+
const result = deps.postflightPopStash(
|
|
248
|
+
s.originalBasePath || s.basePath,
|
|
249
|
+
milestoneId,
|
|
250
|
+
preflight.stashMarker,
|
|
251
|
+
ctx.ui.notify.bind(ctx.ui),
|
|
252
|
+
);
|
|
253
|
+
return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Run a milestone merge surrounded by preflight stash + always-on postflight
|
|
258
|
+
* pop. The previous code popped the stash only after a successful merge, which
|
|
259
|
+
* leaked `gsd-preflight-stash:M00x:*` entries whenever `mergeAndExit` threw —
|
|
260
|
+
* leaving the user's pre-merge working tree silently stashed away after a
|
|
261
|
+
* merge-conflict or other merge error. This helper restores the stash on
|
|
262
|
+
* every exit path, then surfaces the merge or stash failure (in priority
|
|
263
|
+
* order) as the loop's stop reason.
|
|
264
|
+
*
|
|
265
|
+
* Returns a `break` action when auto-mode must stop, or `null` when the merge
|
|
266
|
+
* succeeded and the stash (if any) was restored cleanly.
|
|
267
|
+
*/
|
|
268
|
+
export async function _runMilestoneMergeWithStashRestore(
|
|
269
|
+
ic: IterationContext,
|
|
270
|
+
milestoneId: string,
|
|
271
|
+
): Promise<{ action: "break"; reason: string } | null> {
|
|
272
|
+
const { ctx, pi, s, deps } = ic;
|
|
273
|
+
|
|
274
|
+
const preflight = deps.preflightCleanRoot(
|
|
275
|
+
s.originalBasePath || s.basePath,
|
|
276
|
+
milestoneId,
|
|
277
|
+
ctx.ui.notify.bind(ctx.ui),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
let mergeError: unknown = null;
|
|
281
|
+
try {
|
|
282
|
+
deps.resolver.mergeAndExit(milestoneId, ctx.ui);
|
|
283
|
+
s.milestoneMergedInPhases = true;
|
|
284
|
+
} catch (mergeErr) {
|
|
285
|
+
mergeError = mergeErr;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Always attempt to restore the stashed working tree, even on merge error.
|
|
289
|
+
// postflightPopStash itself does not throw; failures surface via the
|
|
290
|
+
// PostflightResult.needsManualRecovery flag.
|
|
291
|
+
let stashResult: PostflightResult | null = null;
|
|
292
|
+
if (preflight.stashPushed) {
|
|
293
|
+
stashResult = deps.postflightPopStash(
|
|
294
|
+
s.originalBasePath || s.basePath,
|
|
295
|
+
milestoneId,
|
|
296
|
+
preflight.stashMarker,
|
|
297
|
+
ctx.ui.notify.bind(ctx.ui),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Merge failure takes priority over stash recovery — the merge is the
|
|
302
|
+
// authoritative gate. If the stash also needed manual recovery, the user
|
|
303
|
+
// already saw the postflightPopStash notify above.
|
|
304
|
+
if (mergeError) {
|
|
305
|
+
if (mergeError instanceof MergeConflictError) {
|
|
306
|
+
ctx.ui.notify(
|
|
307
|
+
`Merge conflict: ${mergeError.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`,
|
|
308
|
+
"error",
|
|
309
|
+
);
|
|
310
|
+
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${milestoneId}`);
|
|
311
|
+
return { action: "break", reason: "merge-conflict" };
|
|
312
|
+
}
|
|
313
|
+
logError("engine", "Milestone merge failed with non-conflict error", {
|
|
314
|
+
milestone: milestoneId,
|
|
315
|
+
error: String(mergeError),
|
|
316
|
+
});
|
|
317
|
+
ctx.ui.notify(
|
|
318
|
+
`Merge failed: ${mergeError instanceof Error ? mergeError.message : String(mergeError)}. Resolve and run /gsd auto to resume.`,
|
|
319
|
+
"error",
|
|
320
|
+
);
|
|
321
|
+
await deps.stopAuto(
|
|
322
|
+
ctx,
|
|
323
|
+
pi,
|
|
324
|
+
`Merge error on milestone ${milestoneId}: ${String(mergeError)}`,
|
|
325
|
+
);
|
|
326
|
+
return { action: "break", reason: "merge-failed" };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (stashResult) {
|
|
330
|
+
return stopOnPostflightRecoveryNeeded(ic, stashResult, milestoneId);
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
218
335
|
async function emitCancelledUnitEnd(
|
|
219
336
|
ic: IterationContext,
|
|
220
337
|
unitType: string,
|
|
@@ -651,42 +768,11 @@ export async function runPreDispatch(
|
|
|
651
768
|
loopState.recentUnits.length = 0;
|
|
652
769
|
loopState.stuckRecoveryAttempts = 0;
|
|
653
770
|
|
|
654
|
-
// Worktree lifecycle on milestone transition — merge current, enter next
|
|
655
|
-
// #2909: preflight
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
660
|
-
);
|
|
661
|
-
try {
|
|
662
|
-
deps.resolver.mergeAndExit(s.currentMilestoneId!, ctx.ui);
|
|
663
|
-
} catch (mergeErr) {
|
|
664
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
665
|
-
// Real code conflicts — stop the loop instead of retrying forever (#2330)
|
|
666
|
-
ctx.ui.notify(
|
|
667
|
-
`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`,
|
|
668
|
-
"error",
|
|
669
|
-
);
|
|
670
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
671
|
-
return { action: "break", reason: "merge-conflict" };
|
|
672
|
-
}
|
|
673
|
-
// Non-conflict merge errors — stop auto to avoid advancing with unmerged work
|
|
674
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId!, error: String(mergeErr) });
|
|
675
|
-
ctx.ui.notify(
|
|
676
|
-
`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`,
|
|
677
|
-
"error",
|
|
678
|
-
);
|
|
679
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
680
|
-
return { action: "break", reason: "merge-failed" };
|
|
681
|
-
}
|
|
682
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
683
|
-
if (preflightTransition.stashPushed) {
|
|
684
|
-
deps.postflightPopStash(
|
|
685
|
-
s.originalBasePath || s.basePath,
|
|
686
|
-
s.currentMilestoneId!,
|
|
687
|
-
preflightTransition.stashMarker,
|
|
688
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
689
|
-
);
|
|
771
|
+
// Worktree lifecycle on milestone transition — merge current, enter next.
|
|
772
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
773
|
+
{
|
|
774
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId!);
|
|
775
|
+
if (stop) return stop;
|
|
690
776
|
}
|
|
691
777
|
|
|
692
778
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
@@ -764,45 +850,11 @@ export async function runPreDispatch(
|
|
|
764
850
|
m.status !== "complete" && m.status !== "parked",
|
|
765
851
|
);
|
|
766
852
|
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
767
|
-
// All milestones complete — merge milestone branch before stopping
|
|
853
|
+
// All milestones complete — merge milestone branch before stopping.
|
|
768
854
|
if (s.currentMilestoneId) {
|
|
769
|
-
// #2909: preflight
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
s.currentMilestoneId,
|
|
773
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
774
|
-
);
|
|
775
|
-
try {
|
|
776
|
-
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
777
|
-
// Prevent stopAuto from attempting the same merge (#2645)
|
|
778
|
-
s.milestoneMergedInPhases = true;
|
|
779
|
-
} catch (mergeErr) {
|
|
780
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
781
|
-
ctx.ui.notify(
|
|
782
|
-
`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`,
|
|
783
|
-
"error",
|
|
784
|
-
);
|
|
785
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
786
|
-
return { action: "break", reason: "merge-conflict" };
|
|
787
|
-
}
|
|
788
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId!, error: String(mergeErr) });
|
|
789
|
-
ctx.ui.notify(
|
|
790
|
-
`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`,
|
|
791
|
-
"error",
|
|
792
|
-
);
|
|
793
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
794
|
-
return { action: "break", reason: "merge-failed" };
|
|
795
|
-
}
|
|
796
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
797
|
-
if (preflightAllComplete.stashPushed) {
|
|
798
|
-
deps.postflightPopStash(
|
|
799
|
-
s.originalBasePath || s.basePath,
|
|
800
|
-
s.currentMilestoneId,
|
|
801
|
-
preflightAllComplete.stashMarker,
|
|
802
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
|
|
855
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
856
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
|
|
857
|
+
if (stop) return stop;
|
|
806
858
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
807
859
|
}
|
|
808
860
|
deps.sendDesktopNotification(
|
|
@@ -893,45 +945,11 @@ export async function runPreDispatch(
|
|
|
893
945
|
|
|
894
946
|
// Terminal: complete
|
|
895
947
|
if (state.phase === "complete") {
|
|
896
|
-
// Milestone merge on complete (before closeout so branch state is clean)
|
|
948
|
+
// Milestone merge on complete (before closeout so branch state is clean).
|
|
897
949
|
if (s.currentMilestoneId) {
|
|
898
|
-
// #2909: preflight
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
s.currentMilestoneId,
|
|
902
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
903
|
-
);
|
|
904
|
-
try {
|
|
905
|
-
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
906
|
-
// Prevent stopAuto from attempting the same merge (#2645)
|
|
907
|
-
s.milestoneMergedInPhases = true;
|
|
908
|
-
} catch (mergeErr) {
|
|
909
|
-
if (mergeErr instanceof MergeConflictError) {
|
|
910
|
-
ctx.ui.notify(
|
|
911
|
-
`Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /gsd auto to resume.`,
|
|
912
|
-
"error",
|
|
913
|
-
);
|
|
914
|
-
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
915
|
-
return { action: "break", reason: "merge-conflict" };
|
|
916
|
-
}
|
|
917
|
-
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId!, error: String(mergeErr) });
|
|
918
|
-
ctx.ui.notify(
|
|
919
|
-
`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`,
|
|
920
|
-
"error",
|
|
921
|
-
);
|
|
922
|
-
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
923
|
-
return { action: "break", reason: "merge-failed" };
|
|
924
|
-
}
|
|
925
|
-
// #2909: postflight — restore stashed changes after successful merge
|
|
926
|
-
if (preflightComplete.stashPushed) {
|
|
927
|
-
deps.postflightPopStash(
|
|
928
|
-
s.originalBasePath || s.basePath,
|
|
929
|
-
s.currentMilestoneId,
|
|
930
|
-
preflightComplete.stashMarker,
|
|
931
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
|
|
950
|
+
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
951
|
+
const stop = await _runMilestoneMergeWithStashRestore(ic, s.currentMilestoneId);
|
|
952
|
+
if (stop) return stop;
|
|
935
953
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
936
954
|
}
|
|
937
955
|
deps.sendDesktopNotification(
|
|
@@ -1054,6 +1072,65 @@ export async function runDispatch(
|
|
|
1054
1072
|
let prompt = dispatchResult.prompt;
|
|
1055
1073
|
const pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
|
1056
1074
|
|
|
1075
|
+
// Resolve hooks and prior-slice gating before health/stuck accounting so
|
|
1076
|
+
// those checks run against the final dispatch unit.
|
|
1077
|
+
const preDispatchResult = deps.runPreDispatchHooks(
|
|
1078
|
+
unitType,
|
|
1079
|
+
unitId,
|
|
1080
|
+
prompt,
|
|
1081
|
+
s.basePath,
|
|
1082
|
+
);
|
|
1083
|
+
if (preDispatchResult.firedHooks.length > 0) {
|
|
1084
|
+
ctx.ui.notify(
|
|
1085
|
+
`Pre-dispatch hook${preDispatchResult.firedHooks.length > 1 ? "s" : ""}: ${preDispatchResult.firedHooks.join(", ")}`,
|
|
1086
|
+
"info",
|
|
1087
|
+
);
|
|
1088
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "pre-dispatch-hook", data: { firedHooks: preDispatchResult.firedHooks, action: preDispatchResult.action } });
|
|
1089
|
+
}
|
|
1090
|
+
if (preDispatchResult.action === "skip") {
|
|
1091
|
+
ctx.ui.notify(
|
|
1092
|
+
`Skipping ${unitType} ${unitId} (pre-dispatch hook).`,
|
|
1093
|
+
"info",
|
|
1094
|
+
);
|
|
1095
|
+
await new Promise((r) => setImmediate(r));
|
|
1096
|
+
return { action: "continue" };
|
|
1097
|
+
}
|
|
1098
|
+
if (preDispatchResult.action === "replace") {
|
|
1099
|
+
prompt = preDispatchResult.prompt ?? prompt;
|
|
1100
|
+
if (preDispatchResult.unitType) unitType = preDispatchResult.unitType;
|
|
1101
|
+
} else if (preDispatchResult.prompt) {
|
|
1102
|
+
prompt = preDispatchResult.prompt;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const guardBasePath = _resolveDispatchGuardBasePath(s);
|
|
1106
|
+
const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(
|
|
1107
|
+
guardBasePath,
|
|
1108
|
+
deps.getMainBranch(guardBasePath),
|
|
1109
|
+
unitType,
|
|
1110
|
+
unitId,
|
|
1111
|
+
);
|
|
1112
|
+
if (priorSliceBlocker) {
|
|
1113
|
+
await deps.stopAuto(ctx, pi, priorSliceBlocker);
|
|
1114
|
+
debugLog("autoLoop", { phase: "exit", reason: "prior-slice-blocker" });
|
|
1115
|
+
return { action: "break", reason: "prior-slice-blocker" };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Execute-task needs a real writable checkout. The same health check also
|
|
1119
|
+
// exists in runUnitPhase, but the stuck-window detector runs before that
|
|
1120
|
+
// phase. Check here too so repeated derivations of a broken worktree stop
|
|
1121
|
+
// with the actionable worktree error instead of the generic stuck-loop error.
|
|
1122
|
+
if (s.basePath && unitType === "execute-task") {
|
|
1123
|
+
const gitMarker = join(s.basePath, ".git");
|
|
1124
|
+
const hasGit = deps.existsSync(gitMarker);
|
|
1125
|
+
if (!hasGit) {
|
|
1126
|
+
const msg = `Worktree health check failed: ${s.basePath} has no .git — refusing to dispatch ${unitType} ${unitId}`;
|
|
1127
|
+
debugLog("autoLoop", { phase: "dispatch-worktree-health-fail", basePath: s.basePath, hasGit });
|
|
1128
|
+
ctx.ui.notify(msg, "error");
|
|
1129
|
+
await deps.stopAuto(ctx, pi, msg);
|
|
1130
|
+
return { action: "break", reason: "worktree-invalid" };
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1057
1134
|
// ── Sliding-window stuck detection with graduated recovery ──
|
|
1058
1135
|
const derivedKey = `${unitType}/${unitId}`;
|
|
1059
1136
|
|
|
@@ -1100,17 +1177,24 @@ export async function runDispatch(
|
|
|
1100
1177
|
level: 1,
|
|
1101
1178
|
action: "artifact-found",
|
|
1102
1179
|
});
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
"info",
|
|
1106
|
-
);
|
|
1107
|
-
if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
|
|
1180
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
1181
|
+
if (!recoveryDb.ok) {
|
|
1108
1182
|
ctx.ui.notify(
|
|
1109
|
-
|
|
1183
|
+
recoveryDb.fatal
|
|
1184
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
1185
|
+
: `${recoveryDb.message} Keeping stuck state for retry.`,
|
|
1110
1186
|
"warning",
|
|
1111
1187
|
);
|
|
1188
|
+
if (recoveryDb.fatal) {
|
|
1189
|
+
await deps.pauseAuto(ctx, pi);
|
|
1190
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
1191
|
+
}
|
|
1112
1192
|
return { action: "continue" };
|
|
1113
1193
|
}
|
|
1194
|
+
ctx.ui.notify(
|
|
1195
|
+
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`,
|
|
1196
|
+
"info",
|
|
1197
|
+
);
|
|
1114
1198
|
deps.invalidateAllCaches();
|
|
1115
1199
|
loopState.recentUnits.length = 0;
|
|
1116
1200
|
loopState.stuckRecoveryAttempts = 0;
|
|
@@ -1135,19 +1219,26 @@ export async function runDispatch(
|
|
|
1135
1219
|
level: 2,
|
|
1136
1220
|
action: "artifact-found",
|
|
1137
1221
|
});
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1222
|
+
const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId);
|
|
1223
|
+
if (recoveryDb.ok) {
|
|
1224
|
+
ctx.ui.notify(
|
|
1225
|
+
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`,
|
|
1226
|
+
"info",
|
|
1227
|
+
);
|
|
1143
1228
|
loopState.recentUnits.length = 0;
|
|
1144
1229
|
loopState.stuckRecoveryAttempts = 0;
|
|
1145
1230
|
return { action: "continue" };
|
|
1146
1231
|
}
|
|
1147
1232
|
ctx.ui.notify(
|
|
1148
|
-
|
|
1233
|
+
recoveryDb.fatal
|
|
1234
|
+
? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
|
|
1235
|
+
: `${recoveryDb.message} Stopping for manual recovery.`,
|
|
1149
1236
|
"warning",
|
|
1150
1237
|
);
|
|
1238
|
+
if (recoveryDb.fatal) {
|
|
1239
|
+
await deps.pauseAuto(ctx, pi);
|
|
1240
|
+
return { action: "break", reason: recoveryDb.reason };
|
|
1241
|
+
}
|
|
1151
1242
|
}
|
|
1152
1243
|
debugLog("autoLoop", {
|
|
1153
1244
|
phase: "stuck-detected",
|
|
@@ -1181,48 +1272,6 @@ export async function runDispatch(
|
|
|
1181
1272
|
}
|
|
1182
1273
|
}
|
|
1183
1274
|
|
|
1184
|
-
// Pre-dispatch hooks
|
|
1185
|
-
const preDispatchResult = deps.runPreDispatchHooks(
|
|
1186
|
-
unitType,
|
|
1187
|
-
unitId,
|
|
1188
|
-
prompt,
|
|
1189
|
-
s.basePath,
|
|
1190
|
-
);
|
|
1191
|
-
if (preDispatchResult.firedHooks.length > 0) {
|
|
1192
|
-
ctx.ui.notify(
|
|
1193
|
-
`Pre-dispatch hook${preDispatchResult.firedHooks.length > 1 ? "s" : ""}: ${preDispatchResult.firedHooks.join(", ")}`,
|
|
1194
|
-
"info",
|
|
1195
|
-
);
|
|
1196
|
-
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "pre-dispatch-hook", data: { firedHooks: preDispatchResult.firedHooks, action: preDispatchResult.action } });
|
|
1197
|
-
}
|
|
1198
|
-
if (preDispatchResult.action === "skip") {
|
|
1199
|
-
ctx.ui.notify(
|
|
1200
|
-
`Skipping ${unitType} ${unitId} (pre-dispatch hook).`,
|
|
1201
|
-
"info",
|
|
1202
|
-
);
|
|
1203
|
-
await new Promise((r) => setImmediate(r));
|
|
1204
|
-
return { action: "continue" };
|
|
1205
|
-
}
|
|
1206
|
-
if (preDispatchResult.action === "replace") {
|
|
1207
|
-
prompt = preDispatchResult.prompt ?? prompt;
|
|
1208
|
-
if (preDispatchResult.unitType) unitType = preDispatchResult.unitType;
|
|
1209
|
-
} else if (preDispatchResult.prompt) {
|
|
1210
|
-
prompt = preDispatchResult.prompt;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
const guardBasePath = _resolveDispatchGuardBasePath(s);
|
|
1214
|
-
const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(
|
|
1215
|
-
guardBasePath,
|
|
1216
|
-
deps.getMainBranch(guardBasePath),
|
|
1217
|
-
unitType,
|
|
1218
|
-
unitId,
|
|
1219
|
-
);
|
|
1220
|
-
if (priorSliceBlocker) {
|
|
1221
|
-
await deps.stopAuto(ctx, pi, priorSliceBlocker);
|
|
1222
|
-
debugLog("autoLoop", { phase: "exit", reason: "prior-slice-blocker" });
|
|
1223
|
-
return { action: "break", reason: "prior-slice-blocker" };
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
1275
|
return {
|
|
1227
1276
|
action: "next",
|
|
1228
1277
|
data: {
|
|
@@ -1515,6 +1564,29 @@ export async function runUnitPhase(
|
|
|
1515
1564
|
return { action: "break", reason: "worktree-invalid" };
|
|
1516
1565
|
}
|
|
1517
1566
|
} else if (projectClassification.kind === "greenfield") {
|
|
1567
|
+
const projectRoot = s.canonicalProjectRoot;
|
|
1568
|
+
if (!isSamePathLocal(s.basePath, projectRoot)) {
|
|
1569
|
+
const projectRootClassification = classifyProject(projectRoot);
|
|
1570
|
+
if (shouldDegradeEmptyWorktreeToProjectRoot(projectClassification, projectRootClassification)) {
|
|
1571
|
+
debugLog("runUnitPhase", {
|
|
1572
|
+
phase: "worktree-health-degrade-to-project-root",
|
|
1573
|
+
worktreePath: s.basePath,
|
|
1574
|
+
projectRoot,
|
|
1575
|
+
worktreeClassification: projectClassification,
|
|
1576
|
+
projectRootClassification,
|
|
1577
|
+
});
|
|
1578
|
+
ctx.ui.notify(
|
|
1579
|
+
`Warning: ${s.basePath} has no project content, but ${projectRoot} does. Continuing in project root because the milestone worktree cannot represent untracked project files.`,
|
|
1580
|
+
"warning",
|
|
1581
|
+
);
|
|
1582
|
+
s.basePath = projectRoot;
|
|
1583
|
+
s.isolationDegraded = true;
|
|
1584
|
+
projectClassification = projectRootClassification;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (projectClassification.kind === "greenfield") {
|
|
1518
1590
|
debugLog("runUnitPhase", { phase: "worktree-health-greenfield", basePath: s.basePath, classification: projectClassification });
|
|
1519
1591
|
ctx.ui.notify(`Warning: ${s.basePath} has no project content yet — proceeding as greenfield project`, "warning");
|
|
1520
1592
|
} else if (projectClassification.kind === "untyped-existing") {
|
|
@@ -2265,6 +2337,13 @@ export async function runFinalize(
|
|
|
2265
2337
|
|
|
2266
2338
|
// Both pre and post verification completed without timeout — reset counter
|
|
2267
2339
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
2340
|
+
if (preUnitSnapshot) {
|
|
2341
|
+
writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
|
|
2342
|
+
phase: "finalized",
|
|
2343
|
+
lastProgressAt: Date.now(),
|
|
2344
|
+
lastProgressKind: "finalize-success",
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2268
2347
|
s.currentUnit = null;
|
|
2269
2348
|
clearCurrentPhase();
|
|
2270
2349
|
|
|
@@ -22,6 +22,7 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
|
|
|
22
22
|
|
|
23
23
|
let _currentResolve: ((result: UnitResult) => void) | null = null;
|
|
24
24
|
let _sessionSwitchInFlight = false;
|
|
25
|
+
let _pendingSwitchCancellation: { errorContext?: ErrorContext } | null = null;
|
|
25
26
|
|
|
26
27
|
// ─── Setters (needed for cross-module mutation) ─────────────────────────────
|
|
27
28
|
|
|
@@ -37,6 +38,12 @@ export function _clearCurrentResolve(): void {
|
|
|
37
38
|
_currentResolve = null;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export function _consumePendingSwitchCancellation(): { errorContext?: ErrorContext } | null {
|
|
42
|
+
const pending = _pendingSwitchCancellation;
|
|
43
|
+
_pendingSwitchCancellation = null;
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
// ─── resolveAgentEnd ─────────────────────────────────────────────────────────
|
|
41
48
|
|
|
42
49
|
/**
|
|
@@ -96,7 +103,7 @@ export function bumpAndResolveSynthetic(reason: string): void {
|
|
|
96
103
|
* blocks to ensure the autoLoop is never stuck awaiting a promise that
|
|
97
104
|
* will never resolve. Safe to call when no resolver is pending (no-op).
|
|
98
105
|
*/
|
|
99
|
-
export function resolveAgentEndCancelled(errorContext?: ErrorContext):
|
|
106
|
+
export function resolveAgentEndCancelled(errorContext?: ErrorContext): boolean {
|
|
100
107
|
if (_currentResolve) {
|
|
101
108
|
// Cancellation supersedes the in-flight turn the same way timeout
|
|
102
109
|
// recovery does — bump the turn epoch so any lingering writes from the
|
|
@@ -107,8 +114,22 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
|
|
|
107
114
|
debugLog("resolveAgentEndCancelled", { status: "resolving-cancelled" });
|
|
108
115
|
const r = _currentResolve;
|
|
109
116
|
_currentResolve = null;
|
|
117
|
+
_pendingSwitchCancellation = null;
|
|
110
118
|
r({ status: "cancelled", ...(errorContext ? { errorContext } : {}) });
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (_sessionSwitchInFlight) {
|
|
123
|
+
bumpTurnGeneration(
|
|
124
|
+
`cancelled-during-switch:${errorContext?.category ?? "unknown"}`,
|
|
125
|
+
);
|
|
126
|
+
_pendingSwitchCancellation = errorContext ? { errorContext } : {};
|
|
127
|
+
debugLog("resolveAgentEndCancelled", { status: "queued-during-switch" });
|
|
128
|
+
return false;
|
|
111
129
|
}
|
|
130
|
+
|
|
131
|
+
debugLog("resolveAgentEndCancelled", { status: "no-pending-resolve" });
|
|
132
|
+
return false;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
@@ -120,6 +141,7 @@ export function resolveAgentEndCancelled(errorContext?: ErrorContext): void {
|
|
|
120
141
|
export function _resetPendingResolve(): void {
|
|
121
142
|
_currentResolve = null;
|
|
122
143
|
_sessionSwitchInFlight = false;
|
|
144
|
+
_pendingSwitchCancellation = null;
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
export function _hasPendingResolveForTest(): boolean {
|