gsd-pi 2.67.0-dev.5399650 → 2.67.0-dev.6fc2289
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 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +16 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
- package/dist/resources/extensions/gsd/auto.js +94 -59
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/doctor.js +8 -4
- package/dist/resources/extensions/gsd/guided-flow.js +40 -31
- package/dist/resources/extensions/gsd/init-wizard.js +15 -12
- package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- 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_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 +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 +2 -2
- 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 +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +10 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.ts +13 -2
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
- package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
- package/packages/pi-coding-agent/src/core/index.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +23 -55
- package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
- package/src/resources/extensions/gsd/auto.ts +104 -63
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/doctor.ts +9 -5
- package/src/resources/extensions/gsd/guided-flow.ts +42 -36
- package/src/resources/extensions/gsd/init-wizard.ts +17 -11
- package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +178 -17
- package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { deriveState } from "./state.js";
|
|
13
13
|
import { parseUnitId } from "./unit-id.js";
|
|
14
|
+
import { assessInterruptedSession, readPausedSessionMetadata, } from "./interrupted-session.js";
|
|
14
15
|
import { getManifestStatus } from "./files.js";
|
|
15
16
|
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
16
17
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
@@ -18,7 +19,7 @@ import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveDir, milest
|
|
|
18
19
|
import { invalidateAllCaches } from "./cache.js";
|
|
19
20
|
import { clearActivityLogState } from "./activity-log.js";
|
|
20
21
|
import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
|
|
21
|
-
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, } from "./crash-recovery.js";
|
|
22
|
+
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, } from "./crash-recovery.js";
|
|
22
23
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
23
24
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
24
25
|
import { sendDesktopNotification } from "./notifications.js";
|
|
@@ -34,7 +35,8 @@ import { clearSkillSnapshot } from "./skill-discovery.js";
|
|
|
34
35
|
import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.js";
|
|
35
36
|
import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
|
|
36
37
|
import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
|
|
37
|
-
import {
|
|
38
|
+
import { logWarning } from "./workflow-logger.js";
|
|
39
|
+
import { homedir } from "node:os";
|
|
38
40
|
import { join } from "node:path";
|
|
39
41
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
40
42
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
@@ -665,6 +667,8 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
665
667
|
stepMode: s.stepMode,
|
|
666
668
|
pausedAt: new Date().toISOString(),
|
|
667
669
|
sessionFile: s.pausedSessionFile,
|
|
670
|
+
unitType: s.currentUnit?.type ?? undefined,
|
|
671
|
+
unitId: s.currentUnit?.id ?? undefined,
|
|
668
672
|
activeEngineId: s.activeEngineId,
|
|
669
673
|
activeRunDir: s.activeRunDir,
|
|
670
674
|
autoStartTime: s.autoStartTime,
|
|
@@ -853,38 +857,54 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
853
857
|
return;
|
|
854
858
|
}
|
|
855
859
|
const requestedStepMode = options?.step ?? false;
|
|
860
|
+
const interruptedAssessment = options?.interrupted ?? null;
|
|
856
861
|
// Escape stale worktree cwd from a previous milestone (#608).
|
|
857
862
|
base = escapeStaleWorktree(base);
|
|
863
|
+
const freshStartAssessment = interruptedAssessment
|
|
864
|
+
?? await assessInterruptedSession(base);
|
|
865
|
+
if (freshStartAssessment.classification === "running") {
|
|
866
|
+
const pid = freshStartAssessment.lock?.pid;
|
|
867
|
+
ctx.ui.notify(pid
|
|
868
|
+
? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.`
|
|
869
|
+
: "Another auto-mode session appears to be running.", "error");
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
858
872
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
859
873
|
// Check persisted paused-session first (#1383) — survives /exit.
|
|
860
874
|
if (!s.paused) {
|
|
861
875
|
try {
|
|
876
|
+
const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
|
|
862
877
|
const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
// Don't delete pause file yet — defer until lock is acquired.
|
|
874
|
-
// If lock fails, the file must survive for retry.
|
|
875
|
-
s.pausedSessionFile = pausedPath;
|
|
876
|
-
ctx.ui.notify(`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`, "info");
|
|
878
|
+
if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
|
|
879
|
+
// Custom workflow resume — restore engine state
|
|
880
|
+
s.activeEngineId = meta.activeEngineId;
|
|
881
|
+
s.activeRunDir = meta.activeRunDir ?? null;
|
|
882
|
+
s.originalBasePath = meta.originalBasePath || base;
|
|
883
|
+
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
884
|
+
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
885
|
+
s.paused = true;
|
|
886
|
+
try {
|
|
887
|
+
unlinkSync(pausedPath);
|
|
877
888
|
}
|
|
878
|
-
|
|
889
|
+
catch (e) {
|
|
890
|
+
logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
891
|
+
}
|
|
892
|
+
ctx.ui.notify(`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`, "info");
|
|
893
|
+
}
|
|
894
|
+
else if (meta?.milestoneId) {
|
|
895
|
+
const shouldResumePausedSession = freshStartAssessment.classification === "recoverable"
|
|
896
|
+
&& (freshStartAssessment.hasResumableDiskState
|
|
897
|
+
|| !!freshStartAssessment.recoveryPrompt
|
|
898
|
+
|| !!freshStartAssessment.lock);
|
|
899
|
+
if (shouldResumePausedSession) {
|
|
879
900
|
// Validate the milestone still exists and isn't already complete (#1664).
|
|
880
901
|
const mDir = resolveMilestonePath(base, meta.milestoneId);
|
|
881
902
|
const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
|
|
882
903
|
if (!mDir || summaryFile) {
|
|
883
|
-
// Stale milestone — clean up and fall through to fresh bootstrap
|
|
884
904
|
try {
|
|
885
905
|
unlinkSync(pausedPath);
|
|
886
906
|
}
|
|
887
|
-
catch (err) {
|
|
907
|
+
catch (err) {
|
|
888
908
|
logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
889
909
|
}
|
|
890
910
|
ctx.ui.notify(`Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`, "info");
|
|
@@ -893,12 +913,26 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
893
913
|
s.currentMilestoneId = meta.milestoneId;
|
|
894
914
|
s.originalBasePath = meta.originalBasePath || base;
|
|
895
915
|
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
916
|
+
s.pausedSessionFile = meta.sessionFile ?? null;
|
|
917
|
+
s.pausedUnitType = meta.unitType ?? null;
|
|
918
|
+
s.pausedUnitId = meta.unitId ?? null;
|
|
896
919
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
897
920
|
s.paused = true;
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
921
|
+
try {
|
|
922
|
+
unlinkSync(pausedPath);
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
926
|
+
}
|
|
927
|
+
ctx.ui.notify(`Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`, "info");
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
else if (existsSync(pausedPath)) {
|
|
931
|
+
try {
|
|
932
|
+
unlinkSync(pausedPath);
|
|
933
|
+
}
|
|
934
|
+
catch (e) {
|
|
935
|
+
logWarning("session", `stale pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
|
|
902
936
|
}
|
|
903
937
|
}
|
|
904
938
|
}
|
|
@@ -907,6 +941,30 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
907
941
|
// Malformed or missing — proceed with fresh bootstrap
|
|
908
942
|
logWarning("session", `paused-session restore failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
909
943
|
}
|
|
944
|
+
// Guard against zero/missing autoStartTime after resume (#3585)
|
|
945
|
+
if (!s.autoStartTime || s.autoStartTime <= 0)
|
|
946
|
+
s.autoStartTime = Date.now();
|
|
947
|
+
}
|
|
948
|
+
if (!s.paused) {
|
|
949
|
+
s.stepMode = requestedStepMode;
|
|
950
|
+
}
|
|
951
|
+
if (freshStartAssessment.lock) {
|
|
952
|
+
clearLock(base);
|
|
953
|
+
}
|
|
954
|
+
if (!s.paused) {
|
|
955
|
+
s.pendingCrashRecovery =
|
|
956
|
+
freshStartAssessment.classification === "recoverable"
|
|
957
|
+
? freshStartAssessment.recoveryPrompt
|
|
958
|
+
: null;
|
|
959
|
+
if (freshStartAssessment.classification === "recoverable" && freshStartAssessment.lock) {
|
|
960
|
+
const info = formatCrashInfo(freshStartAssessment.lock);
|
|
961
|
+
if (freshStartAssessment.recoveryToolCallCount > 0) {
|
|
962
|
+
ctx.ui.notify(`${info}\nRecovered ${freshStartAssessment.recoveryToolCallCount} tool calls from crashed session. Resuming with full context.`, "warning");
|
|
963
|
+
}
|
|
964
|
+
else if (freshStartAssessment.hasResumableDiskState) {
|
|
965
|
+
ctx.ui.notify(`${info}\nResuming from disk state.`, "warning");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
910
968
|
}
|
|
911
969
|
if (s.paused) {
|
|
912
970
|
const resumeLock = acquireSessionLock(base);
|
|
@@ -931,29 +989,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
931
989
|
s.active = true;
|
|
932
990
|
s.verbose = verboseMode;
|
|
933
991
|
s.stepMode = requestedStepMode;
|
|
934
|
-
|
|
935
|
-
// when resuming from a provider-error pause. The resume callback receives
|
|
936
|
-
// an ExtensionContext (from the agent_end hook) which lacks newSession —
|
|
937
|
-
// using it would crash runUnit with "newSession is not a function".
|
|
938
|
-
// Only override if the new ctx actually has newSession (user-initiated resume).
|
|
939
|
-
if ("newSession" in ctx && typeof ctx.newSession === "function") {
|
|
940
|
-
s.cmdCtx = ctx;
|
|
941
|
-
}
|
|
942
|
-
else if (!s.cmdCtx) {
|
|
943
|
-
// No saved cmdCtx — this shouldn't happen, but handle gracefully
|
|
944
|
-
s.cmdCtx = ctx;
|
|
945
|
-
}
|
|
946
|
-
// else: keep existing s.cmdCtx which has the real newSession
|
|
992
|
+
s.cmdCtx = ctx;
|
|
947
993
|
s.basePath = base;
|
|
948
|
-
setLogBasePath(base);
|
|
949
|
-
if (!s.autoStartTime || s.autoStartTime <= 0)
|
|
950
|
-
s.autoStartTime = Date.now();
|
|
951
994
|
s.unitDispatchCount.clear();
|
|
952
995
|
s.unitLifetimeDispatches.clear();
|
|
953
996
|
if (!getLedger())
|
|
954
997
|
initMetrics(base);
|
|
955
998
|
if (s.currentMilestoneId)
|
|
956
999
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
1000
|
+
// Re-register health level notification callback lost across process restart
|
|
1001
|
+
setLevelChangeCallback((_from, to, summary) => {
|
|
1002
|
+
const level = to === "red" ? "error" : to === "yellow" ? "warning" : "info";
|
|
1003
|
+
ctx.ui.notify(summary, level);
|
|
1004
|
+
});
|
|
957
1005
|
// ── Auto-worktree: re-enter worktree on resume ──
|
|
958
1006
|
if (s.currentMilestoneId &&
|
|
959
1007
|
shouldUseWorktreeIsolation() &&
|
|
@@ -970,6 +1018,11 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
970
1018
|
ctx.ui.setFooter(hideFooter);
|
|
971
1019
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
972
1020
|
restoreHookState(s.basePath);
|
|
1021
|
+
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
1022
|
+
// bundled extension updates before resume-time verification/state logic runs.
|
|
1023
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
|
|
1024
|
+
const { initResources } = await import("../../../" + "resource-loader.js");
|
|
1025
|
+
initResources(agentDir);
|
|
973
1026
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
974
1027
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
975
1028
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -996,7 +1049,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
996
1049
|
invalidateAllCaches();
|
|
997
1050
|
if (s.pausedSessionFile) {
|
|
998
1051
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
999
|
-
const recovery = synthesizeCrashRecovery(s.basePath, s.currentUnit?.type ?? "unknown", s.currentUnit?.id ?? "unknown", s.pausedSessionFile ?? undefined, activityDir);
|
|
1052
|
+
const recovery = synthesizeCrashRecovery(s.basePath, s.currentUnit?.type ?? s.pausedUnitType ?? "unknown", s.currentUnit?.id ?? s.pausedUnitId ?? "unknown", s.pausedSessionFile ?? undefined, activityDir);
|
|
1000
1053
|
if (recovery && recovery.trace.toolCallCount > 0) {
|
|
1001
1054
|
s.pendingCrashRecovery = recovery.prompt;
|
|
1002
1055
|
ctx.ui.notify(`Recovered ${recovery.trace.toolCallCount} tool calls from paused session. Resuming with context.`, "info");
|
|
@@ -1018,7 +1071,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1018
1071
|
lockBase,
|
|
1019
1072
|
buildResolver,
|
|
1020
1073
|
};
|
|
1021
|
-
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
|
|
1074
|
+
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1022
1075
|
if (!ready)
|
|
1023
1076
|
return;
|
|
1024
1077
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
@@ -1101,24 +1154,6 @@ function ensurePreconditions(unitType, unitId, base, state) {
|
|
|
1101
1154
|
}
|
|
1102
1155
|
}
|
|
1103
1156
|
}
|
|
1104
|
-
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
1105
|
-
/** Build recovery context from module state for recoverTimedOutUnit */
|
|
1106
|
-
function buildRecoveryContext() {
|
|
1107
|
-
return {
|
|
1108
|
-
basePath: s.basePath,
|
|
1109
|
-
verbose: s.verbose,
|
|
1110
|
-
currentUnitStartedAt: s.currentUnit?.startedAt ?? Date.now(),
|
|
1111
|
-
unitRecoveryCount: s.unitRecoveryCount,
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Test-only: expose skip-loop state for unit tests.
|
|
1116
|
-
* Not part of the public API.
|
|
1117
|
-
*/
|
|
1118
|
-
/**
|
|
1119
|
-
* Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
|
|
1120
|
-
* Used for manual hook triggers via /gsd run-hook.
|
|
1121
|
-
*/
|
|
1122
1157
|
export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, triggerUnitId, hookPrompt, hookModel, targetBasePath) {
|
|
1123
1158
|
if (!s.active) {
|
|
1124
1159
|
s.active = true;
|
|
@@ -141,7 +141,7 @@ export async function buildBeforeAgentStartResult(event, ctx) {
|
|
|
141
141
|
warnDeprecatedAgentInstructions();
|
|
142
142
|
const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
|
|
143
143
|
// Re-inject forensics context on follow-up turns (#2941)
|
|
144
|
-
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd()) : null;
|
|
144
|
+
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd(), event.prompt) : null;
|
|
145
145
|
const worktreeBlock = buildWorktreeContextBlock();
|
|
146
146
|
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
147
147
|
stopContextTimer({
|
|
@@ -425,7 +425,7 @@ function oneLine(text) {
|
|
|
425
425
|
* Check for an active forensics session and return the prompt content
|
|
426
426
|
* so it can be re-injected on follow-up turns.
|
|
427
427
|
*/
|
|
428
|
-
function buildForensicsContextInjection(basePath) {
|
|
428
|
+
export function buildForensicsContextInjection(basePath, prompt) {
|
|
429
429
|
const marker = readForensicsMarker(basePath);
|
|
430
430
|
if (!marker)
|
|
431
431
|
return null;
|
|
@@ -435,6 +435,11 @@ function buildForensicsContextInjection(basePath) {
|
|
|
435
435
|
clearForensicsMarker(basePath);
|
|
436
436
|
return null;
|
|
437
437
|
}
|
|
438
|
+
const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
|
|
439
|
+
if (trimmed && !RESUME_INTENT_PATTERNS.test(trimmed)) {
|
|
440
|
+
clearForensicsMarker(basePath);
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
438
443
|
return marker.promptContent;
|
|
439
444
|
}
|
|
440
445
|
/**
|
|
@@ -58,7 +58,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
58
58
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
59
59
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
60
60
|
{ cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
|
|
61
|
-
{ cmd: "mcp", desc: "MCP server status and
|
|
61
|
+
{ cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
|
|
62
62
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
63
63
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
64
64
|
{ cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
|
|
@@ -188,6 +188,7 @@ const NESTED_COMPLETIONS = {
|
|
|
188
188
|
mcp: [
|
|
189
189
|
{ cmd: "status", desc: "Show all MCP server statuses (default)" },
|
|
190
190
|
{ cmd: "check", desc: "Detailed status for a specific server" },
|
|
191
|
+
{ cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
|
|
191
192
|
],
|
|
192
193
|
doctor: [
|
|
193
194
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
@@ -55,7 +55,7 @@ export function showHelp(ctx) {
|
|
|
55
55
|
" /gsd hooks Show post-unit hook configuration",
|
|
56
56
|
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
57
57
|
" /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
|
|
58
|
-
" /gsd mcp MCP server status and connectivity [status|check <server
|
|
58
|
+
" /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
|
|
59
59
|
"",
|
|
60
60
|
"MAINTENANCE",
|
|
61
61
|
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
@@ -7,9 +7,26 @@
|
|
|
7
7
|
* /gsd mcp — Overview of all servers (alias: /gsd mcp status)
|
|
8
8
|
* /gsd mcp status — Same as bare /gsd mcp
|
|
9
9
|
* /gsd mcp check <srv> — Detailed status for a specific server
|
|
10
|
+
* /gsd mcp init [dir] — Write project-local GSD workflow MCP config
|
|
10
11
|
*/
|
|
11
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
|
|
15
|
+
export function formatMcpInitResult(status, configPath, targetPath) {
|
|
16
|
+
const summary = status === "created"
|
|
17
|
+
? "Created project MCP config."
|
|
18
|
+
: status === "updated"
|
|
19
|
+
? "Updated project MCP config."
|
|
20
|
+
: "Project MCP config is already up to date.";
|
|
21
|
+
return [
|
|
22
|
+
summary,
|
|
23
|
+
"",
|
|
24
|
+
`Project: ${targetPath}`,
|
|
25
|
+
`Config: ${configPath}`,
|
|
26
|
+
"",
|
|
27
|
+
"Claude Code can now load the GSD workflow MCP server from this folder.",
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
13
30
|
function readMcpConfigs() {
|
|
14
31
|
const servers = [];
|
|
15
32
|
const seen = new Set();
|
|
@@ -61,6 +78,7 @@ export function formatMcpStatusReport(servers) {
|
|
|
61
78
|
"No MCP servers configured.",
|
|
62
79
|
"",
|
|
63
80
|
"Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
|
|
81
|
+
"Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
|
|
64
82
|
"See: https://modelcontextprotocol.io/quickstart",
|
|
65
83
|
].join("\n");
|
|
66
84
|
}
|
|
@@ -109,11 +127,28 @@ export function formatMcpServerDetail(server) {
|
|
|
109
127
|
* Handle `/gsd mcp [status|check <server>]`.
|
|
110
128
|
*/
|
|
111
129
|
export async function handleMcpStatus(args, ctx) {
|
|
112
|
-
const trimmed = args.trim()
|
|
130
|
+
const trimmed = args.trim();
|
|
131
|
+
const lowered = trimmed.toLowerCase();
|
|
113
132
|
const configs = readMcpConfigs();
|
|
133
|
+
// /gsd mcp init [dir]
|
|
134
|
+
if (!lowered || lowered === "status") {
|
|
135
|
+
// handled below
|
|
136
|
+
}
|
|
137
|
+
else if (lowered === "init" || lowered.startsWith("init ")) {
|
|
138
|
+
const rawPath = trimmed.slice("init".length).trim();
|
|
139
|
+
const targetPath = resolve(rawPath || ".");
|
|
140
|
+
try {
|
|
141
|
+
const result = ensureProjectWorkflowMcpConfig(targetPath);
|
|
142
|
+
ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
ctx.ui.notify(`Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
114
149
|
// /gsd mcp check <server>
|
|
115
|
-
if (
|
|
116
|
-
const serverName =
|
|
150
|
+
if (lowered.startsWith("check ")) {
|
|
151
|
+
const serverName = trimmed.slice("check ".length).trim();
|
|
117
152
|
const config = configs.find((c) => c.name === serverName);
|
|
118
153
|
if (!config) {
|
|
119
154
|
const available = configs.map((c) => c.name).join(", ") || "(none)";
|
|
@@ -149,7 +184,7 @@ export async function handleMcpStatus(args, ctx) {
|
|
|
149
184
|
return;
|
|
150
185
|
}
|
|
151
186
|
// /gsd mcp or /gsd mcp status
|
|
152
|
-
if (!
|
|
187
|
+
if (!lowered || lowered === "status") {
|
|
153
188
|
// Build status for each server
|
|
154
189
|
const statuses = [];
|
|
155
190
|
for (const config of configs) {
|
|
@@ -181,7 +216,8 @@ export async function handleMcpStatus(args, ctx) {
|
|
|
181
216
|
return;
|
|
182
217
|
}
|
|
183
218
|
// Unknown subcommand
|
|
184
|
-
ctx.ui.notify("Usage: /gsd mcp [status|check <server
|
|
219
|
+
ctx.ui.notify("Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
|
|
185
220
|
" status Show all MCP server statuses (default)\n" +
|
|
186
|
-
" check <server> Detailed status for a specific server"
|
|
221
|
+
" check <server> Detailed status for a specific server\n" +
|
|
222
|
+
" init [dir] Write .mcp.json for the local GSD workflow MCP server", "warning");
|
|
187
223
|
}
|
|
@@ -8,7 +8,7 @@ import { deriveState, isMilestoneComplete } from "./state.js";
|
|
|
8
8
|
import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
|
|
9
9
|
import { abortAndReset } from "./git-self-heal.js";
|
|
10
10
|
import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
|
|
11
|
-
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
11
|
+
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
12
12
|
import { getAllWorktreeHealth } from "./worktree-health.js";
|
|
13
13
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
14
14
|
/**
|
|
@@ -380,19 +380,19 @@ export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix,
|
|
|
380
380
|
code: "stale_uncommitted_changes",
|
|
381
381
|
scope: "project",
|
|
382
382
|
unitId: "project",
|
|
383
|
-
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting
|
|
383
|
+
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting tracked files.`,
|
|
384
384
|
fixable: true,
|
|
385
385
|
});
|
|
386
386
|
if (shouldFix("stale_uncommitted_changes")) {
|
|
387
387
|
try {
|
|
388
|
-
|
|
388
|
+
nativeAddTracked(basePath);
|
|
389
389
|
const commitMsg = `gsd snapshot: uncommitted changes after ${mins}m inactivity`;
|
|
390
390
|
const result = nativeCommit(basePath, commitMsg);
|
|
391
391
|
if (result) {
|
|
392
392
|
fixesApplied.push(`created gsd snapshot after ${mins}m of uncommitted changes`);
|
|
393
393
|
}
|
|
394
394
|
else {
|
|
395
|
-
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging
|
|
395
|
+
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging tracked files");
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
catch {
|
|
@@ -20,8 +20,8 @@ import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.j
|
|
|
20
20
|
import { abortAndReset } from "./git-self-heal.js";
|
|
21
21
|
import { rebuildState } from "./doctor.js";
|
|
22
22
|
import { deriveState } from "./state.js";
|
|
23
|
-
import {
|
|
24
|
-
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
23
|
+
import { resolveMilestoneIntegrationBranch } from "./git-service.js";
|
|
24
|
+
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
25
25
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
26
26
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
27
27
|
/** In-memory health history for the current auto-mode session. */
|
|
@@ -247,7 +247,7 @@ export async function preDispatchHealthGate(basePath) {
|
|
|
247
247
|
if (minutesSinceCommit >= thresholdMinutes) {
|
|
248
248
|
const mins = Math.floor(minutesSinceCommit);
|
|
249
249
|
try {
|
|
250
|
-
|
|
250
|
+
nativeAddTracked(basePath);
|
|
251
251
|
const commitMsg = `gsd snapshot: pre-dispatch, uncommitted changes after ${mins}m inactivity`;
|
|
252
252
|
const result = nativeCommit(basePath, commitMsg);
|
|
253
253
|
if (result) {
|
|
@@ -7,6 +7,7 @@ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSl
|
|
|
7
7
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
8
8
|
import { invalidateAllCaches } from "./cache.js";
|
|
9
9
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
10
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
10
11
|
import { GLOBAL_STATE_CODES } from "./doctor-types.js";
|
|
11
12
|
import { checkGitHealth, checkRuntimeHealth, checkGlobalHealth, checkEngineHealth } from "./doctor-checks.js";
|
|
12
13
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
@@ -443,8 +444,9 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
443
444
|
slices = dbSlices.map(s => ({
|
|
444
445
|
id: s.id,
|
|
445
446
|
title: s.title,
|
|
446
|
-
done: s.status
|
|
447
|
+
done: isClosedStatus(s.status),
|
|
447
448
|
pending: s.status === "pending",
|
|
449
|
+
skipped: s.status === "skipped",
|
|
448
450
|
risk: (s.risk || "medium"),
|
|
449
451
|
depends: s.depends,
|
|
450
452
|
demo: s.demo,
|
|
@@ -541,8 +543,9 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
541
543
|
const slicePath = resolveSlicePath(basePath, milestoneId, slice.id);
|
|
542
544
|
if (!slicePath) {
|
|
543
545
|
// Pending slices haven't been planned yet — directories are created
|
|
544
|
-
// lazily by ensurePreconditions() at dispatch time.
|
|
545
|
-
|
|
546
|
+
// lazily by ensurePreconditions() at dispatch time. Skipped slices are
|
|
547
|
+
// intentionally allowed to remain summary-less and directory-less.
|
|
548
|
+
if (slice.pending || slice.skipped)
|
|
546
549
|
continue;
|
|
547
550
|
const expectedPath = relSlicePath(basePath, milestoneId, slice.id);
|
|
548
551
|
issues.push({
|
|
@@ -566,7 +569,8 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
566
569
|
const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
|
|
567
570
|
if (!tasksDir) {
|
|
568
571
|
// Pending slices haven't been planned yet — tasks/ is created on demand.
|
|
569
|
-
|
|
572
|
+
// Skipped slices may legitimately never create tasks/.
|
|
573
|
+
if (slice.pending || slice.skipped)
|
|
570
574
|
continue;
|
|
571
575
|
issues.push({
|
|
572
576
|
severity: slice.done ? "warning" : "error",
|
|
@@ -14,7 +14,8 @@ import { buildSkillActivationBlock } from "./auto-prompts.js";
|
|
|
14
14
|
import { deriveState } from "./state.js";
|
|
15
15
|
import { invalidateAllCaches } from "./cache.js";
|
|
16
16
|
import { startAuto } from "./auto.js";
|
|
17
|
-
import {
|
|
17
|
+
import { clearLock } from "./crash-recovery.js";
|
|
18
|
+
import { assessInterruptedSession, formatInterruptedSessionRunningMessage, formatInterruptedSessionSummary, } from "./interrupted-session.js";
|
|
18
19
|
import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
19
20
|
import { resolveExpectedArtifactPath } from "./auto.js";
|
|
20
21
|
import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
|
|
@@ -167,14 +168,9 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
167
168
|
}
|
|
168
169
|
// Gate 4: Discussion manifest process verification (multi-milestone only)
|
|
169
170
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
170
|
-
//
|
|
171
|
-
//
|
|
171
|
+
// When it exists, validate it before auto-starting. Project history alone is
|
|
172
|
+
// not a reliable signal for the current discussion mode.
|
|
172
173
|
const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
|
|
173
|
-
const requiresManifest = projectIds.length > 1 || findMilestoneIds(basePath).length > 1;
|
|
174
|
-
if (requiresManifest && !existsSync(manifestPath)) {
|
|
175
|
-
ctx.ui.notify("Multi-milestone discussion manifest is missing. Auto-start will remain paused until the manifest is written.", "warning");
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
174
|
if (existsSync(manifestPath)) {
|
|
179
175
|
try {
|
|
180
176
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -1102,33 +1098,46 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1102
1098
|
untrackRuntimeFiles(basePath);
|
|
1103
1099
|
// ── Self-heal stale runtime records from crashed auto-mode sessions ──
|
|
1104
1100
|
selfHealRuntimeRecords(basePath, ctx);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1101
|
+
const interrupted = await assessInterruptedSession(basePath);
|
|
1102
|
+
if (interrupted.classification === "running") {
|
|
1103
|
+
ctx.ui.notify(formatInterruptedSessionRunningMessage(interrupted), "error");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (interrupted.classification === "stale") {
|
|
1110
1107
|
clearLock(basePath);
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
// real auto-mode work begins.
|
|
1115
|
-
const isBootstrapCrash = crashLock.unitType === "starting"
|
|
1116
|
-
&& crashLock.unitId === "bootstrap";
|
|
1117
|
-
if (!isBootstrapCrash) {
|
|
1118
|
-
const resume = await showNextAction(ctx, {
|
|
1119
|
-
title: "GSD — Interrupted Session Detected",
|
|
1120
|
-
summary: [formatCrashInfo(crashLock)],
|
|
1121
|
-
actions: [
|
|
1122
|
-
{ id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true },
|
|
1123
|
-
{ id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
|
|
1124
|
-
],
|
|
1125
|
-
});
|
|
1126
|
-
if (resume === "resume") {
|
|
1127
|
-
await startAuto(ctx, pi, basePath, false);
|
|
1128
|
-
return;
|
|
1108
|
+
if (interrupted.pausedSession) {
|
|
1109
|
+
try {
|
|
1110
|
+
unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"));
|
|
1129
1111
|
}
|
|
1112
|
+
catch (e) {
|
|
1113
|
+
logWarning("guided", `stale pause file cleanup failed: ${e.message}`, { file: "guided-flow.ts" });
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
else if (interrupted.classification === "recoverable") {
|
|
1118
|
+
if (interrupted.lock)
|
|
1119
|
+
clearLock(basePath);
|
|
1120
|
+
const resumeLabel = interrupted.pausedSession?.stepMode
|
|
1121
|
+
? "Resume with /gsd next"
|
|
1122
|
+
: "Resume with /gsd auto";
|
|
1123
|
+
const resume = await showNextAction(ctx, {
|
|
1124
|
+
title: "GSD — Interrupted Session Detected",
|
|
1125
|
+
summary: formatInterruptedSessionSummary(interrupted),
|
|
1126
|
+
actions: [
|
|
1127
|
+
{ id: "resume", label: resumeLabel, description: "Pick up where it left off", recommended: true },
|
|
1128
|
+
{ id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
|
|
1129
|
+
],
|
|
1130
|
+
});
|
|
1131
|
+
if (resume === "resume") {
|
|
1132
|
+
await startAuto(ctx, pi, basePath, false, {
|
|
1133
|
+
interrupted,
|
|
1134
|
+
step: interrupted.pausedSession?.stepMode ?? false,
|
|
1135
|
+
});
|
|
1136
|
+
return;
|
|
1130
1137
|
}
|
|
1131
1138
|
}
|
|
1139
|
+
// Always derive from the project root — the assessment may have derived
|
|
1140
|
+
// state from a worktree path that was cleaned up in the stale branch above.
|
|
1132
1141
|
const state = await deriveState(basePath);
|
|
1133
1142
|
// Rebuild STATE.md from derived state before any dispatch (#3475).
|
|
1134
1143
|
try {
|