gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0
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/loader.js +0 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +55 -6
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +45 -52
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
- package/dist/resources/extensions/gsd/auto.js +159 -2
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/gsd-db.js +34 -1
- package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
- package/dist/resources/extensions/shared/interview-ui.js +15 -4
- 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 +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-ff639266d978f2a0.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/package.json +2 -2
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
- package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
- package/src/resources/extensions/gsd/auto/phases.ts +82 -8
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -50
- package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
- package/src/resources/extensions/gsd/auto.ts +167 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
- package/src/resources/extensions/gsd/gsd-db.ts +35 -1
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
- package/src/resources/extensions/shared/interview-ui.ts +18 -5
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
- package/dist/web/standalone/.next/static/chunks/app/page-fab3ebb85b006001.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
- /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { GSDState } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface AutoSessionContext {
|
|
4
|
+
basePath: string;
|
|
5
|
+
trigger: "guided-flow" | "resume" | "auto-loop" | "manual";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AutoStatus {
|
|
9
|
+
phase: "idle" | "running" | "paused" | "stopped" | "error";
|
|
10
|
+
activeUnit?: {
|
|
11
|
+
unitType: string;
|
|
12
|
+
unitId: string;
|
|
13
|
+
};
|
|
14
|
+
lastTransitionAt?: number;
|
|
15
|
+
transitionCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AutoAdvanceResult {
|
|
19
|
+
kind: "advanced" | "blocked" | "paused" | "stopped" | "error";
|
|
20
|
+
reason?: string;
|
|
21
|
+
stateSnapshot?: GSDState;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AutoOrchestrationModule {
|
|
25
|
+
start(sessionContext: AutoSessionContext): Promise<AutoAdvanceResult>;
|
|
26
|
+
advance(): Promise<AutoAdvanceResult>;
|
|
27
|
+
resume(): Promise<AutoAdvanceResult>;
|
|
28
|
+
stop(reason: string): Promise<AutoAdvanceResult>;
|
|
29
|
+
getStatus(): AutoStatus;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DispatchAdapter {
|
|
33
|
+
decideNextUnit(): Promise<{
|
|
34
|
+
unitType: string;
|
|
35
|
+
unitId: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
preconditions: string[];
|
|
38
|
+
} | null>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RecoveryAdapter {
|
|
42
|
+
classifyAndRecover(input: {
|
|
43
|
+
error: unknown;
|
|
44
|
+
unitType?: string;
|
|
45
|
+
unitId?: string;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
action: "retry" | "escalate" | "stop";
|
|
48
|
+
reason: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorktreeAdapter {
|
|
53
|
+
prepareForUnit(unitType: string, unitId: string): Promise<void>;
|
|
54
|
+
syncAfterUnit(unitType: string, unitId: string): Promise<void>;
|
|
55
|
+
cleanupOnStop(reason: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface HealthAdapter {
|
|
59
|
+
preAdvanceGate(): Promise<{ allow: boolean; reason?: string }>;
|
|
60
|
+
postAdvanceRecord(result: AutoAdvanceResult): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RuntimePersistenceAdapter {
|
|
64
|
+
ensureLockOwnership(): Promise<void>;
|
|
65
|
+
journalTransition(event: {
|
|
66
|
+
name: string;
|
|
67
|
+
reason?: string;
|
|
68
|
+
unitType?: string;
|
|
69
|
+
unitId?: string;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface NotificationAdapter {
|
|
74
|
+
notifyLifecycle(event: {
|
|
75
|
+
name: string;
|
|
76
|
+
detail?: string;
|
|
77
|
+
}): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AutoOrchestratorDeps {
|
|
81
|
+
dispatch: DispatchAdapter;
|
|
82
|
+
recovery: RecoveryAdapter;
|
|
83
|
+
worktree: WorktreeAdapter;
|
|
84
|
+
health: HealthAdapter;
|
|
85
|
+
runtime: RuntimePersistenceAdapter;
|
|
86
|
+
notifications: NotificationAdapter;
|
|
87
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
|
|
9
9
|
import type { AutoSession } from "./session.js";
|
|
10
|
+
import type { ErrorContext } from "./types.js";
|
|
10
11
|
import type { GSDPreferences } from "../preferences.js";
|
|
11
12
|
import type { GSDState } from "../types.js";
|
|
12
13
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
@@ -24,6 +25,12 @@ import type { MergeReconcileResult } from "../auto-recovery.js";
|
|
|
24
25
|
import type { UokTurnObserver } from "../uok/contracts.js";
|
|
25
26
|
import type { PreflightResult } from "../clean-root-preflight.js";
|
|
26
27
|
|
|
28
|
+
type PauseAutoFn = (
|
|
29
|
+
ctx?: ExtensionContext,
|
|
30
|
+
pi?: ExtensionAPI,
|
|
31
|
+
errorContext?: ErrorContext,
|
|
32
|
+
) => Promise<void>;
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Dependencies injected by the caller (auto.ts startAuto) so autoLoop
|
|
29
36
|
* can access private functions from auto.ts without exporting them.
|
|
@@ -39,7 +46,7 @@ export interface LoopDeps {
|
|
|
39
46
|
pi?: ExtensionAPI,
|
|
40
47
|
reason?: string,
|
|
41
48
|
) => Promise<void>;
|
|
42
|
-
pauseAuto:
|
|
49
|
+
pauseAuto: PauseAutoFn;
|
|
43
50
|
clearUnitTimeout: () => void;
|
|
44
51
|
updateProgressWidget: (
|
|
45
52
|
ctx: ExtensionContext,
|
|
@@ -245,7 +252,7 @@ export interface LoopDeps {
|
|
|
245
252
|
prefs: GSDPreferences | undefined;
|
|
246
253
|
buildSnapshotOpts: () => CloseoutOptions & Record<string, unknown>;
|
|
247
254
|
buildRecoveryContext: () => unknown;
|
|
248
|
-
pauseAuto:
|
|
255
|
+
pauseAuto: PauseAutoFn;
|
|
249
256
|
}) => void;
|
|
250
257
|
|
|
251
258
|
// Prompt helpers
|
|
@@ -271,7 +278,7 @@ export interface LoopDeps {
|
|
|
271
278
|
) => Promise<"dispatched" | "continue" | "retry">;
|
|
272
279
|
runPostUnitVerification: (
|
|
273
280
|
vctx: VerificationContext,
|
|
274
|
-
pauseAuto:
|
|
281
|
+
pauseAuto: PauseAutoFn,
|
|
275
282
|
) => Promise<VerificationResult>;
|
|
276
283
|
postUnitPostVerification: (
|
|
277
284
|
pctx: PostUnitContext,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { AutoAdvanceResult, AutoOrchestrationModule, AutoOrchestratorDeps, AutoSessionContext, AutoStatus } from "./contracts.js";
|
|
2
|
+
|
|
3
|
+
function now(): number {
|
|
4
|
+
return Date.now();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
8
|
+
private status: AutoStatus = {
|
|
9
|
+
phase: "idle",
|
|
10
|
+
transitionCount: 0,
|
|
11
|
+
};
|
|
12
|
+
private readonly deps: AutoOrchestratorDeps;
|
|
13
|
+
private lastAdvanceKey: string | null = null;
|
|
14
|
+
|
|
15
|
+
public constructor(deps: AutoOrchestratorDeps) {
|
|
16
|
+
this.deps = deps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
|
|
20
|
+
this.lastAdvanceKey = null;
|
|
21
|
+
this.status.phase = "running";
|
|
22
|
+
this.bumpTransition();
|
|
23
|
+
await this.deps.runtime.journalTransition({ name: "start" });
|
|
24
|
+
await this.deps.notifications.notifyLifecycle({ name: "start" });
|
|
25
|
+
return this.advance();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async advance(): Promise<AutoAdvanceResult> {
|
|
29
|
+
try {
|
|
30
|
+
await this.deps.runtime.ensureLockOwnership();
|
|
31
|
+
const gate = await this.deps.health.preAdvanceGate();
|
|
32
|
+
if (!gate.allow) {
|
|
33
|
+
const blocked: AutoAdvanceResult = { kind: "blocked", reason: gate.reason ?? "health gate blocked" };
|
|
34
|
+
await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
35
|
+
await this.deps.health.postAdvanceRecord(blocked);
|
|
36
|
+
return blocked;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const decision = await this.deps.dispatch.decideNextUnit();
|
|
40
|
+
if (!decision) {
|
|
41
|
+
const stopped: AutoAdvanceResult = { kind: "stopped", reason: "no remaining units" };
|
|
42
|
+
this.status.phase = "stopped";
|
|
43
|
+
this.status.activeUnit = undefined;
|
|
44
|
+
this.lastAdvanceKey = null;
|
|
45
|
+
this.bumpTransition();
|
|
46
|
+
await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
|
|
47
|
+
await this.deps.health.postAdvanceRecord(stopped);
|
|
48
|
+
return stopped;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const nextKey = `${decision.unitType}:${decision.unitId}`;
|
|
52
|
+
if (this.lastAdvanceKey === nextKey) {
|
|
53
|
+
const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active" };
|
|
54
|
+
await this.deps.runtime.journalTransition({
|
|
55
|
+
name: "advance-blocked",
|
|
56
|
+
reason: blocked.reason,
|
|
57
|
+
unitType: decision.unitType,
|
|
58
|
+
unitId: decision.unitId,
|
|
59
|
+
});
|
|
60
|
+
await this.deps.health.postAdvanceRecord(blocked);
|
|
61
|
+
return blocked;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.status.activeUnit = { unitType: decision.unitType, unitId: decision.unitId };
|
|
65
|
+
this.status.phase = "running";
|
|
66
|
+
this.lastAdvanceKey = nextKey;
|
|
67
|
+
this.bumpTransition();
|
|
68
|
+
|
|
69
|
+
await this.deps.runtime.journalTransition({
|
|
70
|
+
name: "advance",
|
|
71
|
+
reason: decision.reason,
|
|
72
|
+
unitType: decision.unitType,
|
|
73
|
+
unitId: decision.unitId,
|
|
74
|
+
});
|
|
75
|
+
await this.deps.worktree.prepareForUnit(decision.unitType, decision.unitId);
|
|
76
|
+
await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
|
|
77
|
+
|
|
78
|
+
const advanced: AutoAdvanceResult = { kind: "advanced" };
|
|
79
|
+
await this.deps.health.postAdvanceRecord(advanced);
|
|
80
|
+
return advanced;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const recovery = await this.deps.recovery.classifyAndRecover({
|
|
83
|
+
error,
|
|
84
|
+
unitType: this.status.activeUnit?.unitType,
|
|
85
|
+
unitId: this.status.activeUnit?.unitId,
|
|
86
|
+
});
|
|
87
|
+
const result: AutoAdvanceResult = recovery.action === "retry"
|
|
88
|
+
? { kind: "paused", reason: recovery.reason }
|
|
89
|
+
: recovery.action === "escalate"
|
|
90
|
+
? { kind: "error", reason: recovery.reason }
|
|
91
|
+
: { kind: "stopped", reason: recovery.reason };
|
|
92
|
+
|
|
93
|
+
if (result.kind === "paused") {
|
|
94
|
+
this.status.phase = "paused";
|
|
95
|
+
} else if (result.kind === "stopped") {
|
|
96
|
+
this.status.phase = "stopped";
|
|
97
|
+
} else {
|
|
98
|
+
this.status.phase = "error";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (result.kind === "stopped") {
|
|
102
|
+
this.lastAdvanceKey = null;
|
|
103
|
+
this.status.activeUnit = undefined;
|
|
104
|
+
}
|
|
105
|
+
this.bumpTransition();
|
|
106
|
+
|
|
107
|
+
const journalName = result.kind === "paused"
|
|
108
|
+
? "advance-paused"
|
|
109
|
+
: result.kind === "stopped"
|
|
110
|
+
? "advance-stopped"
|
|
111
|
+
: "advance-error";
|
|
112
|
+
await this.deps.runtime.journalTransition({ name: journalName, reason: recovery.reason });
|
|
113
|
+
|
|
114
|
+
if (result.kind === "paused") {
|
|
115
|
+
await this.deps.notifications.notifyLifecycle({ name: "pause", detail: recovery.reason });
|
|
116
|
+
} else if (result.kind === "stopped") {
|
|
117
|
+
await this.deps.notifications.notifyLifecycle({ name: "stopped", detail: recovery.reason });
|
|
118
|
+
} else if (result.kind === "error") {
|
|
119
|
+
await this.deps.notifications.notifyLifecycle({ name: "error", detail: recovery.reason });
|
|
120
|
+
}
|
|
121
|
+
await this.deps.health.postAdvanceRecord(result);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public async resume(): Promise<AutoAdvanceResult> {
|
|
127
|
+
this.lastAdvanceKey = null;
|
|
128
|
+
this.status.phase = "running";
|
|
129
|
+
this.bumpTransition();
|
|
130
|
+
await this.deps.runtime.journalTransition({ name: "resume" });
|
|
131
|
+
await this.deps.notifications.notifyLifecycle({ name: "resume" });
|
|
132
|
+
return this.advance();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async stop(reason: string): Promise<AutoAdvanceResult> {
|
|
136
|
+
if (this.status.phase === "stopped") {
|
|
137
|
+
return { kind: "stopped", reason };
|
|
138
|
+
}
|
|
139
|
+
await this.deps.worktree.cleanupOnStop(reason);
|
|
140
|
+
this.status.phase = "stopped";
|
|
141
|
+
this.status.activeUnit = undefined;
|
|
142
|
+
this.lastAdvanceKey = null;
|
|
143
|
+
this.bumpTransition();
|
|
144
|
+
await this.deps.runtime.journalTransition({ name: "stop", reason });
|
|
145
|
+
await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
|
|
146
|
+
return { kind: "stopped", reason };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public getStatus(): AutoStatus {
|
|
150
|
+
return { ...this.status, activeUnit: this.status.activeUnit ? { ...this.status.activeUnit } : undefined };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private bumpTransition(): void {
|
|
154
|
+
this.status.transitionCount += 1;
|
|
155
|
+
this.status.lastTransitionAt = now();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createAutoOrchestrator(deps: AutoOrchestratorDeps): AutoOrchestrationModule {
|
|
160
|
+
return new AutoOrchestrator(deps);
|
|
161
|
+
}
|
|
@@ -55,7 +55,7 @@ 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 } from "../gsd-db.js";
|
|
58
|
+
import { isDbAvailable, getMilestoneSlices, refreshOpenDatabaseFromDisk } from "../gsd-db.js";
|
|
59
59
|
import type { MinimalModelRegistry } from "../context-budget.js";
|
|
60
60
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
61
61
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
@@ -76,6 +76,12 @@ function isSamePathLocal(a: string, b: string): boolean {
|
|
|
76
76
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function refreshPlanSliceRecoveryDbIfNeeded(unitType: string): boolean {
|
|
80
|
+
if (unitType !== "plan-slice") return true;
|
|
81
|
+
if (!isDbAvailable()) return true;
|
|
82
|
+
return refreshOpenDatabaseFromDisk();
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
80
86
|
|
|
81
87
|
let consecutiveSessionTimeouts = 0;
|
|
@@ -232,6 +238,33 @@ async function emitCancelledUnitEnd(
|
|
|
232
238
|
});
|
|
233
239
|
}
|
|
234
240
|
|
|
241
|
+
export function _buildCancelledUnitStopReason(
|
|
242
|
+
unitType: string,
|
|
243
|
+
unitId: string,
|
|
244
|
+
errorContext?: { message: string; category: string },
|
|
245
|
+
): {
|
|
246
|
+
notifyMessage: string;
|
|
247
|
+
stopReason: string;
|
|
248
|
+
loopReason: "session-failed" | "unit-aborted";
|
|
249
|
+
} {
|
|
250
|
+
const cancellationMessage = errorContext?.message ?? "unknown";
|
|
251
|
+
const isSessionCreationFailure = errorContext?.category === "session-failed";
|
|
252
|
+
|
|
253
|
+
if (isSessionCreationFailure) {
|
|
254
|
+
return {
|
|
255
|
+
notifyMessage: `Session creation failed for ${unitType} ${unitId}: ${cancellationMessage}. Stopping auto-mode.`,
|
|
256
|
+
stopReason: `Session creation failed: ${cancellationMessage}`,
|
|
257
|
+
loopReason: "session-failed",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
notifyMessage: `Unit ${unitType} ${unitId} aborted after dispatch: ${cancellationMessage}. Stopping auto-mode.`,
|
|
263
|
+
stopReason: `Unit aborted: ${cancellationMessage}`,
|
|
264
|
+
loopReason: "unit-aborted",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
235
268
|
async function failClosedOnFinalizeTimeout(
|
|
236
269
|
ic: IterationContext,
|
|
237
270
|
iterData: IterationData,
|
|
@@ -994,7 +1027,10 @@ export async function runDispatch(
|
|
|
994
1027
|
// See: https://github.com/gsd-build/gsd-2/issues/2474
|
|
995
1028
|
if (dispatchResult.level === "warning") {
|
|
996
1029
|
ctx.ui.notify(dispatchResult.reason, "warning");
|
|
997
|
-
await deps.pauseAuto(ctx, pi
|
|
1030
|
+
await deps.pauseAuto(ctx, pi, {
|
|
1031
|
+
message: dispatchResult.reason,
|
|
1032
|
+
category: "unknown",
|
|
1033
|
+
});
|
|
998
1034
|
} else {
|
|
999
1035
|
await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
|
|
1000
1036
|
}
|
|
@@ -1065,7 +1101,16 @@ export async function runDispatch(
|
|
|
1065
1101
|
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`,
|
|
1066
1102
|
"info",
|
|
1067
1103
|
);
|
|
1104
|
+
if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
|
|
1105
|
+
ctx.ui.notify(
|
|
1106
|
+
`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Keeping stuck state for retry.`,
|
|
1107
|
+
"warning",
|
|
1108
|
+
);
|
|
1109
|
+
return { action: "continue" };
|
|
1110
|
+
}
|
|
1068
1111
|
deps.invalidateAllCaches();
|
|
1112
|
+
loopState.recentUnits.length = 0;
|
|
1113
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1069
1114
|
return { action: "continue" };
|
|
1070
1115
|
}
|
|
1071
1116
|
ctx.ui.notify(
|
|
@@ -1075,6 +1120,32 @@ export async function runDispatch(
|
|
|
1075
1120
|
deps.invalidateAllCaches();
|
|
1076
1121
|
} else {
|
|
1077
1122
|
// Level 2: hard stop — genuinely stuck
|
|
1123
|
+
deps.invalidateAllCaches();
|
|
1124
|
+
const artifactExists = verifyExpectedArtifact(
|
|
1125
|
+
unitType,
|
|
1126
|
+
unitId,
|
|
1127
|
+
s.basePath,
|
|
1128
|
+
);
|
|
1129
|
+
if (artifactExists && unitType !== "complete-milestone") {
|
|
1130
|
+
debugLog("autoLoop", {
|
|
1131
|
+
phase: "stuck-recovery",
|
|
1132
|
+
level: 2,
|
|
1133
|
+
action: "artifact-found",
|
|
1134
|
+
});
|
|
1135
|
+
ctx.ui.notify(
|
|
1136
|
+
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`,
|
|
1137
|
+
"info",
|
|
1138
|
+
);
|
|
1139
|
+
if (refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
|
|
1140
|
+
loopState.recentUnits.length = 0;
|
|
1141
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1142
|
+
return { action: "continue" };
|
|
1143
|
+
}
|
|
1144
|
+
ctx.ui.notify(
|
|
1145
|
+
`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Stopping for manual recovery.`,
|
|
1146
|
+
"warning",
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1078
1149
|
debugLog("autoLoop", {
|
|
1079
1150
|
phase: "stuck-detected",
|
|
1080
1151
|
unitType,
|
|
@@ -1871,13 +1942,16 @@ export async function runUnitPhase(
|
|
|
1871
1942
|
}
|
|
1872
1943
|
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1873
1944
|
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1945
|
+
|
|
1946
|
+
const cancelledStop = _buildCancelledUnitStopReason(
|
|
1947
|
+
unitType,
|
|
1948
|
+
unitId,
|
|
1949
|
+
unitResult.errorContext,
|
|
1877
1950
|
);
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1951
|
+
ctx.ui.notify(cancelledStop.notifyMessage, "warning");
|
|
1952
|
+
await deps.stopAuto(ctx, pi, cancelledStop.stopReason);
|
|
1953
|
+
debugLog("autoLoop", { phase: "exit", reason: cancelledStop.loopReason });
|
|
1954
|
+
return { action: "break", reason: cancelledStop.loopReason };
|
|
1881
1955
|
}
|
|
1882
1956
|
|
|
1883
1957
|
// ── Immediate unit closeout (metrics, activity log, memory) ────────
|
|
@@ -21,6 +21,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
|
|
|
21
21
|
import type { GitServiceImpl } from "../git-service.js";
|
|
22
22
|
import type { CaptureEntry } from "../captures.js";
|
|
23
23
|
import type { BudgetAlertLevel } from "../auto-budget.js";
|
|
24
|
+
import type { AutoOrchestrationModule } from "./contracts.js";
|
|
24
25
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
25
26
|
import { normalizeRealPath } from "../paths.js";
|
|
26
27
|
import type { MilestoneScope } from "../workspace.js";
|
|
@@ -229,6 +230,9 @@ export class AutoSession {
|
|
|
229
230
|
/** Cleanup function returned by startCommandPolling(); null when not running. */
|
|
230
231
|
commandPollingCleanup: (() => void) | null = null;
|
|
231
232
|
|
|
233
|
+
// ── Orchestration seam ───────────────────────────────────────────────────
|
|
234
|
+
orchestration: AutoOrchestrationModule | null = null;
|
|
235
|
+
|
|
232
236
|
// ── Loop promise state ──────────────────────────────────────────────────
|
|
233
237
|
// Per-unit resolve function and session-switch guard live at module level
|
|
234
238
|
// in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
|
|
@@ -354,10 +358,14 @@ export class AutoSession {
|
|
|
354
358
|
// Remote command polling — cleanup must be called before reset (auto.ts stopAuto)
|
|
355
359
|
this.commandPollingCleanup = null;
|
|
356
360
|
|
|
361
|
+
// Orchestration seam
|
|
362
|
+
this.orchestration = null;
|
|
363
|
+
|
|
357
364
|
// Loop promise state lives in auto-loop.ts module scope
|
|
358
365
|
}
|
|
359
366
|
|
|
360
367
|
toJSON(): Record<string, unknown> {
|
|
368
|
+
const orchestrationStatus = this.orchestration?.getStatus();
|
|
361
369
|
return {
|
|
362
370
|
active: this.active,
|
|
363
371
|
paused: this.paused,
|
|
@@ -367,6 +375,9 @@ export class AutoSession {
|
|
|
367
375
|
activeRunDir: this.activeRunDir,
|
|
368
376
|
currentMilestoneId: this.currentMilestoneId,
|
|
369
377
|
currentUnit: this.currentUnit,
|
|
378
|
+
orchestrationPhase: orchestrationStatus?.phase,
|
|
379
|
+
orchestrationTransitionCount: orchestrationStatus?.transitionCount,
|
|
380
|
+
orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
|
|
370
381
|
unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
|
|
371
382
|
};
|
|
372
383
|
}
|
|
@@ -14,7 +14,7 @@ import { appendEvent } from "./workflow-events.js";
|
|
|
14
14
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
15
15
|
import { clearParseCache } from "./files.js";
|
|
16
16
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
17
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone } from "./gsd-db.js";
|
|
17
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
18
18
|
import { isValidationTerminal } from "./state.js";
|
|
19
19
|
import { getErrorMessage } from "./error-utils.js";
|
|
20
20
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -588,65 +588,32 @@ export function verifyExpectedArtifact(
|
|
|
588
588
|
}
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
// plan-slice
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
// unit gets skipped — but deriveState still returns phase:"planning" because the
|
|
595
|
-
// plan has no tasks, creating an infinite skip loop (#699).
|
|
596
|
-
if (unitType === "plan-slice") {
|
|
597
|
-
const planContent = readFileSync(absPath, "utf-8");
|
|
598
|
-
// Accept checkbox-style (- [x] **T01: ...) or heading-style (### T01 -- / ### T01: / ### T01 —)
|
|
599
|
-
const hasCheckboxTask = /^- \[[xX ]\] \*\*T\d+:/m.test(planContent);
|
|
600
|
-
const hasHeadingTask = /^#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
601
|
-
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
602
|
-
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// execute-task: DB status is authoritative. Fall back to checked-checkbox
|
|
608
|
-
// detection when the DB is unavailable (unmigrated projects), or when the
|
|
609
|
-
// disk artifacts already reflect completion but the DB replay is one beat
|
|
610
|
-
// behind the completion write.
|
|
611
|
-
if (unitType === "execute-task") {
|
|
612
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
613
|
-
if (mid && sid && tid) {
|
|
614
|
-
const dbTask = getTask(mid, sid, tid);
|
|
615
|
-
if (dbTask) {
|
|
616
|
-
if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
} else if (!isDbAvailable()) {
|
|
620
|
-
// LEGACY: Pre-migration fallback for projects without DB.
|
|
621
|
-
// Require a CHECKED checkbox — a bare heading or unchecked checkbox
|
|
622
|
-
// does not prove gsd_complete_task ran. Summary file on disk alone
|
|
623
|
-
// is not sufficient evidence (could be a rogue write) (#3607).
|
|
624
|
-
if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) return false;
|
|
625
|
-
} else {
|
|
626
|
-
// DB available but task row not found — completion tool never ran (#3607)
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// plan-slice must also produce individual task plan files for every task listed
|
|
633
|
-
// in the slice plan. Without this check, a plan-slice that wrote S{sid}-PLAN.md
|
|
634
|
-
// but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
|
|
635
|
-
// to dispatch with a missing task plan (see issue #739).
|
|
591
|
+
// plan-slice verification is DB-primary. The slice plan is a projection, so
|
|
592
|
+
// DB task rows prove the slice was planned even if the rendered markdown no
|
|
593
|
+
// longer uses legacy checkbox/heading syntax.
|
|
636
594
|
if (unitType === "plan-slice") {
|
|
637
595
|
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
638
596
|
if (mid && sid) {
|
|
639
597
|
try {
|
|
640
|
-
// DB primary path — get task IDs to verify task plan files exist
|
|
641
598
|
let taskIds: string[] | null = null;
|
|
642
599
|
if (isDbAvailable()) {
|
|
643
|
-
const
|
|
644
|
-
if (
|
|
600
|
+
const refreshed = refreshOpenDatabaseFromDisk();
|
|
601
|
+
if (refreshed) {
|
|
602
|
+
const tasks = getSliceTasks(mid, sid);
|
|
603
|
+
if (tasks.length > 0) taskIds = tasks.map(t => t.id);
|
|
604
|
+
}
|
|
645
605
|
}
|
|
646
606
|
|
|
647
607
|
if (!taskIds) {
|
|
648
|
-
// LEGACY: DB unavailable or no tasks in DB
|
|
608
|
+
// LEGACY: DB unavailable or no tasks in DB. Require actual task
|
|
609
|
+
// entries so an empty scaffold cannot advance the pipeline (#699).
|
|
649
610
|
const planContent = readFileSync(absPath, "utf-8");
|
|
611
|
+
const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+:/m.test(planContent);
|
|
612
|
+
const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
613
|
+
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
614
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
650
617
|
const plan = parseLegacyPlan(planContent);
|
|
651
618
|
if (plan.tasks.length > 0) taskIds = plan.tasks.map((t: { id: string }) => t.id);
|
|
652
619
|
}
|
|
@@ -672,6 +639,31 @@ export function verifyExpectedArtifact(
|
|
|
672
639
|
}
|
|
673
640
|
}
|
|
674
641
|
|
|
642
|
+
// execute-task: DB status is authoritative. Fall back to checked-checkbox
|
|
643
|
+
// detection when the DB is unavailable (unmigrated projects), or when the
|
|
644
|
+
// disk artifacts already reflect completion but the DB replay is one beat
|
|
645
|
+
// behind the completion write.
|
|
646
|
+
if (unitType === "execute-task") {
|
|
647
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
648
|
+
if (mid && sid && tid) {
|
|
649
|
+
const dbTask = getTask(mid, sid, tid);
|
|
650
|
+
if (dbTask) {
|
|
651
|
+
if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
} else if (!isDbAvailable()) {
|
|
655
|
+
// LEGACY: Pre-migration fallback for projects without DB.
|
|
656
|
+
// Require a CHECKED checkbox — a bare heading or unchecked checkbox
|
|
657
|
+
// does not prove gsd_complete_task ran. Summary file on disk alone
|
|
658
|
+
// is not sufficient evidence (could be a rogue write) (#3607).
|
|
659
|
+
if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) return false;
|
|
660
|
+
} else {
|
|
661
|
+
// DB available but task row not found — completion tool never ran (#3607)
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
675
667
|
// complete-slice: DB status is authoritative for whether the slice is done.
|
|
676
668
|
// Fall back to file-based check (roadmap [x]) when DB is unavailable.
|
|
677
669
|
if (unitType === "complete-slice") {
|
|
@@ -16,14 +16,21 @@ export type AutoRuntimeSnapshot = {
|
|
|
16
16
|
paused: boolean;
|
|
17
17
|
currentUnit: CurrentUnit | null;
|
|
18
18
|
basePath: string;
|
|
19
|
+
orchestrationPhase?: "idle" | "running" | "paused" | "stopped" | "error";
|
|
20
|
+
orchestrationTransitionCount?: number;
|
|
21
|
+
orchestrationLastTransitionAt?: number;
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
export function getAutoRuntimeSnapshot(): AutoRuntimeSnapshot {
|
|
25
|
+
const orchestrationStatus = autoSession.orchestration?.getStatus();
|
|
22
26
|
return {
|
|
23
27
|
active: autoSession.active,
|
|
24
28
|
paused: autoSession.paused,
|
|
25
29
|
currentUnit: autoSession.currentUnit ? { ...autoSession.currentUnit } : null,
|
|
26
30
|
basePath: autoSession.basePath,
|
|
31
|
+
orchestrationPhase: orchestrationStatus?.phase,
|
|
32
|
+
orchestrationTransitionCount: orchestrationStatus?.transitionCount,
|
|
33
|
+
orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
|