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
|
@@ -19,6 +19,11 @@ import type {
|
|
|
19
19
|
import { deriveState } from "./state.js";
|
|
20
20
|
import { parseUnitId } from "./unit-id.js";
|
|
21
21
|
import type { GSDState } from "./types.js";
|
|
22
|
+
import {
|
|
23
|
+
assessInterruptedSession,
|
|
24
|
+
readPausedSessionMetadata,
|
|
25
|
+
type InterruptedSessionAssessment,
|
|
26
|
+
} from "./interrupted-session.js";
|
|
22
27
|
import { getManifestStatus } from "./files.js";
|
|
23
28
|
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
24
29
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
@@ -46,6 +51,7 @@ import {
|
|
|
46
51
|
clearLock,
|
|
47
52
|
readCrashLock,
|
|
48
53
|
isLockProcessAlive,
|
|
54
|
+
formatCrashInfo,
|
|
49
55
|
} from "./crash-recovery.js";
|
|
50
56
|
import {
|
|
51
57
|
acquireSessionLock,
|
|
@@ -118,6 +124,7 @@ import {
|
|
|
118
124
|
formatTokenCount,
|
|
119
125
|
} from "./metrics.js";
|
|
120
126
|
import { setLogBasePath, logWarning, logError } from "./workflow-logger.js";
|
|
127
|
+
import { homedir } from "node:os";
|
|
121
128
|
import { join } from "node:path";
|
|
122
129
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
123
130
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
@@ -920,6 +927,8 @@ export async function pauseAuto(
|
|
|
920
927
|
stepMode: s.stepMode,
|
|
921
928
|
pausedAt: new Date().toISOString(),
|
|
922
929
|
sessionFile: s.pausedSessionFile,
|
|
930
|
+
unitType: s.currentUnit?.type ?? undefined,
|
|
931
|
+
unitId: s.currentUnit?.id ?? undefined,
|
|
923
932
|
activeEngineId: s.activeEngineId,
|
|
924
933
|
activeRunDir: s.activeRunDir,
|
|
925
934
|
autoStartTime: s.autoStartTime,
|
|
@@ -1141,7 +1150,10 @@ export async function startAuto(
|
|
|
1141
1150
|
pi: ExtensionAPI,
|
|
1142
1151
|
base: string,
|
|
1143
1152
|
verboseMode: boolean,
|
|
1144
|
-
options?: {
|
|
1153
|
+
options?: {
|
|
1154
|
+
step?: boolean;
|
|
1155
|
+
interrupted?: InterruptedSessionAssessment;
|
|
1156
|
+
},
|
|
1145
1157
|
): Promise<void> {
|
|
1146
1158
|
if (s.active) {
|
|
1147
1159
|
debugLog("startAuto", { phase: "already-active", skipping: true });
|
|
@@ -1149,41 +1161,60 @@ export async function startAuto(
|
|
|
1149
1161
|
}
|
|
1150
1162
|
|
|
1151
1163
|
const requestedStepMode = options?.step ?? false;
|
|
1164
|
+
const interruptedAssessment = options?.interrupted ?? null;
|
|
1152
1165
|
|
|
1153
1166
|
// Escape stale worktree cwd from a previous milestone (#608).
|
|
1154
1167
|
base = escapeStaleWorktree(base);
|
|
1155
1168
|
|
|
1169
|
+
const freshStartAssessment = interruptedAssessment
|
|
1170
|
+
?? await assessInterruptedSession(base);
|
|
1171
|
+
|
|
1172
|
+
if (freshStartAssessment.classification === "running") {
|
|
1173
|
+
const pid = freshStartAssessment.lock?.pid;
|
|
1174
|
+
ctx.ui.notify(
|
|
1175
|
+
pid
|
|
1176
|
+
? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.`
|
|
1177
|
+
: "Another auto-mode session appears to be running.",
|
|
1178
|
+
"error",
|
|
1179
|
+
);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1156
1183
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
1157
1184
|
// Check persisted paused-session first (#1383) — survives /exit.
|
|
1158
1185
|
if (!s.paused) {
|
|
1159
1186
|
try {
|
|
1187
|
+
const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
|
|
1160
1188
|
const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
|
|
1161
|
-
if (
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1189
|
+
if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
|
|
1190
|
+
// Custom workflow resume — restore engine state
|
|
1191
|
+
s.activeEngineId = meta.activeEngineId;
|
|
1192
|
+
s.activeRunDir = meta.activeRunDir ?? null;
|
|
1193
|
+
s.originalBasePath = meta.originalBasePath || base;
|
|
1194
|
+
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
1195
|
+
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1196
|
+
s.paused = true;
|
|
1197
|
+
try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
|
|
1198
|
+
ctx.ui.notify(
|
|
1199
|
+
`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`,
|
|
1200
|
+
"info",
|
|
1201
|
+
);
|
|
1202
|
+
} else if (meta?.milestoneId) {
|
|
1203
|
+
const shouldResumePausedSession =
|
|
1204
|
+
freshStartAssessment.classification === "recoverable"
|
|
1205
|
+
&& (
|
|
1206
|
+
freshStartAssessment.hasResumableDiskState
|
|
1207
|
+
|| !!freshStartAssessment.recoveryPrompt
|
|
1208
|
+
|| !!freshStartAssessment.lock
|
|
1177
1209
|
);
|
|
1178
|
-
|
|
1210
|
+
if (shouldResumePausedSession) {
|
|
1179
1211
|
// Validate the milestone still exists and isn't already complete (#1664).
|
|
1180
1212
|
const mDir = resolveMilestonePath(base, meta.milestoneId);
|
|
1181
1213
|
const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
|
|
1182
1214
|
if (!mDir || summaryFile) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1215
|
+
try { unlinkSync(pausedPath); } catch (err) {
|
|
1216
|
+
logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1217
|
+
}
|
|
1187
1218
|
ctx.ui.notify(
|
|
1188
1219
|
`Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`,
|
|
1189
1220
|
"info",
|
|
@@ -1192,22 +1223,54 @@ export async function startAuto(
|
|
|
1192
1223
|
s.currentMilestoneId = meta.milestoneId;
|
|
1193
1224
|
s.originalBasePath = meta.originalBasePath || base;
|
|
1194
1225
|
s.stepMode = meta.stepMode ?? requestedStepMode;
|
|
1226
|
+
s.pausedSessionFile = meta.sessionFile ?? null;
|
|
1227
|
+
s.pausedUnitType = meta.unitType ?? null;
|
|
1228
|
+
s.pausedUnitId = meta.unitId ?? null;
|
|
1195
1229
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1196
1230
|
s.paused = true;
|
|
1197
|
-
|
|
1198
|
-
// If lock fails, the file must survive for retry.
|
|
1199
|
-
s.pausedSessionFile = pausedPath;
|
|
1231
|
+
try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
|
|
1200
1232
|
ctx.ui.notify(
|
|
1201
|
-
`Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`,
|
|
1233
|
+
`Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`,
|
|
1202
1234
|
"info",
|
|
1203
1235
|
);
|
|
1204
1236
|
}
|
|
1237
|
+
} else if (existsSync(pausedPath)) {
|
|
1238
|
+
try { unlinkSync(pausedPath); } catch (e) { logWarning("session", `stale pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" }); }
|
|
1205
1239
|
}
|
|
1206
1240
|
}
|
|
1207
1241
|
} catch (err) {
|
|
1208
1242
|
// Malformed or missing — proceed with fresh bootstrap
|
|
1209
1243
|
logWarning("session", `paused-session restore failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1210
1244
|
}
|
|
1245
|
+
// Guard against zero/missing autoStartTime after resume (#3585)
|
|
1246
|
+
if (!s.autoStartTime || s.autoStartTime <= 0) s.autoStartTime = Date.now();
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (!s.paused) {
|
|
1250
|
+
s.stepMode = requestedStepMode;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (freshStartAssessment.lock) {
|
|
1254
|
+
clearLock(base);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (!s.paused) {
|
|
1258
|
+
s.pendingCrashRecovery =
|
|
1259
|
+
freshStartAssessment.classification === "recoverable"
|
|
1260
|
+
? freshStartAssessment.recoveryPrompt
|
|
1261
|
+
: null;
|
|
1262
|
+
|
|
1263
|
+
if (freshStartAssessment.classification === "recoverable" && freshStartAssessment.lock) {
|
|
1264
|
+
const info = formatCrashInfo(freshStartAssessment.lock);
|
|
1265
|
+
if (freshStartAssessment.recoveryToolCallCount > 0) {
|
|
1266
|
+
ctx.ui.notify(
|
|
1267
|
+
`${info}\nRecovered ${freshStartAssessment.recoveryToolCallCount} tool calls from crashed session. Resuming with full context.`,
|
|
1268
|
+
"warning",
|
|
1269
|
+
);
|
|
1270
|
+
} else if (freshStartAssessment.hasResumableDiskState) {
|
|
1271
|
+
ctx.ui.notify(`${info}\nResuming from disk state.`, "warning");
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1211
1274
|
}
|
|
1212
1275
|
|
|
1213
1276
|
if (s.paused) {
|
|
@@ -1232,26 +1295,19 @@ export async function startAuto(
|
|
|
1232
1295
|
s.active = true;
|
|
1233
1296
|
s.verbose = verboseMode;
|
|
1234
1297
|
s.stepMode = requestedStepMode;
|
|
1235
|
-
|
|
1236
|
-
// when resuming from a provider-error pause. The resume callback receives
|
|
1237
|
-
// an ExtensionContext (from the agent_end hook) which lacks newSession —
|
|
1238
|
-
// using it would crash runUnit with "newSession is not a function".
|
|
1239
|
-
// Only override if the new ctx actually has newSession (user-initiated resume).
|
|
1240
|
-
if ("newSession" in ctx && typeof (ctx as any).newSession === "function") {
|
|
1241
|
-
s.cmdCtx = ctx;
|
|
1242
|
-
} else if (!s.cmdCtx) {
|
|
1243
|
-
// No saved cmdCtx — this shouldn't happen, but handle gracefully
|
|
1244
|
-
s.cmdCtx = ctx as ExtensionCommandContext;
|
|
1245
|
-
}
|
|
1246
|
-
// else: keep existing s.cmdCtx which has the real newSession
|
|
1298
|
+
s.cmdCtx = ctx;
|
|
1247
1299
|
s.basePath = base;
|
|
1248
|
-
setLogBasePath(base);
|
|
1249
|
-
if (!s.autoStartTime || s.autoStartTime <= 0) s.autoStartTime = Date.now();
|
|
1250
1300
|
s.unitDispatchCount.clear();
|
|
1251
1301
|
s.unitLifetimeDispatches.clear();
|
|
1252
1302
|
if (!getLedger()) initMetrics(base);
|
|
1253
1303
|
if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
|
|
1254
1304
|
|
|
1305
|
+
// Re-register health level notification callback lost across process restart
|
|
1306
|
+
setLevelChangeCallback((_from, to, summary) => {
|
|
1307
|
+
const level = to === "red" ? "error" : to === "yellow" ? "warning" : "info";
|
|
1308
|
+
ctx.ui.notify(summary, level as "info" | "warning" | "error");
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1255
1311
|
// ── Auto-worktree: re-enter worktree on resume ──
|
|
1256
1312
|
if (
|
|
1257
1313
|
s.currentMilestoneId &&
|
|
@@ -1275,6 +1331,11 @@ export async function startAuto(
|
|
|
1275
1331
|
"info",
|
|
1276
1332
|
);
|
|
1277
1333
|
restoreHookState(s.basePath);
|
|
1334
|
+
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
1335
|
+
// bundled extension updates before resume-time verification/state logic runs.
|
|
1336
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
|
|
1337
|
+
const { initResources } = await import("../../../" + "resource-loader.js");
|
|
1338
|
+
initResources(agentDir);
|
|
1278
1339
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1279
1340
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1280
1341
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -1305,8 +1366,8 @@ export async function startAuto(
|
|
|
1305
1366
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
1306
1367
|
const recovery = synthesizeCrashRecovery(
|
|
1307
1368
|
s.basePath,
|
|
1308
|
-
s.currentUnit?.type ?? "unknown",
|
|
1309
|
-
s.currentUnit?.id ?? "unknown",
|
|
1369
|
+
s.currentUnit?.type ?? s.pausedUnitType ?? "unknown",
|
|
1370
|
+
s.currentUnit?.id ?? s.pausedUnitId ?? "unknown",
|
|
1310
1371
|
s.pausedSessionFile ?? undefined,
|
|
1311
1372
|
activityDir,
|
|
1312
1373
|
);
|
|
@@ -1354,6 +1415,7 @@ export async function startAuto(
|
|
|
1354
1415
|
verboseMode,
|
|
1355
1416
|
requestedStepMode,
|
|
1356
1417
|
bootstrapDeps,
|
|
1418
|
+
freshStartAssessment,
|
|
1357
1419
|
);
|
|
1358
1420
|
if (!ready) return;
|
|
1359
1421
|
|
|
@@ -1467,27 +1529,6 @@ function ensurePreconditions(
|
|
|
1467
1529
|
}
|
|
1468
1530
|
}
|
|
1469
1531
|
|
|
1470
|
-
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
1471
|
-
|
|
1472
|
-
/** Build recovery context from module state for recoverTimedOutUnit */
|
|
1473
|
-
function buildRecoveryContext(): import("./auto-timeout-recovery.js").RecoveryContext {
|
|
1474
|
-
return {
|
|
1475
|
-
basePath: s.basePath,
|
|
1476
|
-
verbose: s.verbose,
|
|
1477
|
-
currentUnitStartedAt: s.currentUnit?.startedAt ?? Date.now(),
|
|
1478
|
-
unitRecoveryCount: s.unitRecoveryCount,
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
/**
|
|
1483
|
-
* Test-only: expose skip-loop state for unit tests.
|
|
1484
|
-
* Not part of the public API.
|
|
1485
|
-
*/
|
|
1486
|
-
|
|
1487
|
-
/**
|
|
1488
|
-
* Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
|
|
1489
|
-
* Used for manual hook triggers via /gsd run-hook.
|
|
1490
|
-
*/
|
|
1491
1532
|
export async function dispatchHookUnit(
|
|
1492
1533
|
ctx: ExtensionContext,
|
|
1493
1534
|
pi: ExtensionAPI,
|
|
@@ -168,7 +168,7 @@ export async function buildBeforeAgentStartResult(
|
|
|
168
168
|
const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
|
|
169
169
|
|
|
170
170
|
// Re-inject forensics context on follow-up turns (#2941)
|
|
171
|
-
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd()) : null;
|
|
171
|
+
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd(), event.prompt) : null;
|
|
172
172
|
|
|
173
173
|
const worktreeBlock = buildWorktreeContextBlock();
|
|
174
174
|
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
@@ -481,7 +481,7 @@ function oneLine(text: string): string {
|
|
|
481
481
|
* Check for an active forensics session and return the prompt content
|
|
482
482
|
* so it can be re-injected on follow-up turns.
|
|
483
483
|
*/
|
|
484
|
-
function buildForensicsContextInjection(basePath: string): string | null {
|
|
484
|
+
export function buildForensicsContextInjection(basePath: string, prompt: string): string | null {
|
|
485
485
|
const marker = readForensicsMarker(basePath);
|
|
486
486
|
if (!marker) return null;
|
|
487
487
|
|
|
@@ -492,6 +492,12 @@ function buildForensicsContextInjection(basePath: string): string | null {
|
|
|
492
492
|
return null;
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
|
|
496
|
+
if (trimmed && !RESUME_INTENT_PATTERNS.test(trimmed)) {
|
|
497
|
+
clearForensicsMarker(basePath);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
495
501
|
return marker.promptContent;
|
|
496
502
|
}
|
|
497
503
|
|
|
@@ -70,7 +70,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
70
70
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
71
71
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
72
72
|
{ cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
|
|
73
|
-
{ cmd: "mcp", desc: "MCP server status and
|
|
73
|
+
{ cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
|
|
74
74
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
75
75
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
76
76
|
{ cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
|
|
@@ -201,6 +201,7 @@ const NESTED_COMPLETIONS: CompletionMap = {
|
|
|
201
201
|
mcp: [
|
|
202
202
|
{ cmd: "status", desc: "Show all MCP server statuses (default)" },
|
|
203
203
|
{ cmd: "check", desc: "Detailed status for a specific server" },
|
|
204
|
+
{ cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
|
|
204
205
|
],
|
|
205
206
|
doctor: [
|
|
206
207
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
@@ -60,7 +60,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
60
60
|
" /gsd hooks Show post-unit hook configuration",
|
|
61
61
|
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
62
62
|
" /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
|
|
63
|
-
" /gsd mcp MCP server status and connectivity [status|check <server
|
|
63
|
+
" /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
|
|
64
64
|
"",
|
|
65
65
|
"MAINTENANCE",
|
|
66
66
|
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
@@ -7,12 +7,15 @@
|
|
|
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
|
|
|
12
13
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
14
|
|
|
14
15
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
+
import { join, resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
|
|
16
19
|
|
|
17
20
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
18
21
|
|
|
@@ -28,6 +31,28 @@ export interface McpServerDetail extends McpServerStatus {
|
|
|
28
31
|
tools: string[];
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export function formatMcpInitResult(
|
|
35
|
+
status: "created" | "updated" | "unchanged",
|
|
36
|
+
configPath: string,
|
|
37
|
+
targetPath: string,
|
|
38
|
+
): string {
|
|
39
|
+
const summary =
|
|
40
|
+
status === "created"
|
|
41
|
+
? "Created project MCP config."
|
|
42
|
+
: status === "updated"
|
|
43
|
+
? "Updated project MCP config."
|
|
44
|
+
: "Project MCP config is already up to date.";
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
summary,
|
|
48
|
+
"",
|
|
49
|
+
`Project: ${targetPath}`,
|
|
50
|
+
`Config: ${configPath}`,
|
|
51
|
+
"",
|
|
52
|
+
"Claude Code can now load the GSD workflow MCP server from this folder.",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// ─── Config reader (standalone — does not import mcp-client internals) ──────
|
|
32
57
|
|
|
33
58
|
interface McpServerRawConfig {
|
|
@@ -94,6 +119,7 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
|
|
|
94
119
|
"No MCP servers configured.",
|
|
95
120
|
"",
|
|
96
121
|
"Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
|
|
122
|
+
"Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
|
|
97
123
|
"See: https://modelcontextprotocol.io/quickstart",
|
|
98
124
|
].join("\n");
|
|
99
125
|
}
|
|
@@ -153,12 +179,31 @@ export async function handleMcpStatus(
|
|
|
153
179
|
args: string,
|
|
154
180
|
ctx: ExtensionCommandContext,
|
|
155
181
|
): Promise<void> {
|
|
156
|
-
const trimmed = args.trim()
|
|
182
|
+
const trimmed = args.trim();
|
|
183
|
+
const lowered = trimmed.toLowerCase();
|
|
157
184
|
const configs = readMcpConfigs();
|
|
158
185
|
|
|
186
|
+
// /gsd mcp init [dir]
|
|
187
|
+
if (!lowered || lowered === "status") {
|
|
188
|
+
// handled below
|
|
189
|
+
} else if (lowered === "init" || lowered.startsWith("init ")) {
|
|
190
|
+
const rawPath = trimmed.slice("init".length).trim();
|
|
191
|
+
const targetPath = resolve(rawPath || ".");
|
|
192
|
+
try {
|
|
193
|
+
const result = ensureProjectWorkflowMcpConfig(targetPath);
|
|
194
|
+
ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
|
|
195
|
+
} catch (err) {
|
|
196
|
+
ctx.ui.notify(
|
|
197
|
+
`Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
198
|
+
"error",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
159
204
|
// /gsd mcp check <server>
|
|
160
|
-
if (
|
|
161
|
-
const serverName =
|
|
205
|
+
if (lowered.startsWith("check ")) {
|
|
206
|
+
const serverName = trimmed.slice("check ".length).trim();
|
|
162
207
|
const config = configs.find((c) => c.name === serverName);
|
|
163
208
|
if (!config) {
|
|
164
209
|
const available = configs.map((c) => c.name).join(", ") || "(none)";
|
|
@@ -202,7 +247,7 @@ export async function handleMcpStatus(
|
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
// /gsd mcp or /gsd mcp status
|
|
205
|
-
if (!
|
|
250
|
+
if (!lowered || lowered === "status") {
|
|
206
251
|
// Build status for each server
|
|
207
252
|
const statuses: McpServerStatus[] = [];
|
|
208
253
|
|
|
@@ -239,9 +284,10 @@ export async function handleMcpStatus(
|
|
|
239
284
|
|
|
240
285
|
// Unknown subcommand
|
|
241
286
|
ctx.ui.notify(
|
|
242
|
-
"Usage: /gsd mcp [status|check <server
|
|
287
|
+
"Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
|
|
243
288
|
" status Show all MCP server statuses (default)\n" +
|
|
244
|
-
" check <server> Detailed status for a specific server"
|
|
289
|
+
" check <server> Detailed status for a specific server\n" +
|
|
290
|
+
" init [dir] Write .mcp.json for the local GSD workflow MCP server",
|
|
245
291
|
"warning",
|
|
246
292
|
);
|
|
247
293
|
}
|
|
@@ -10,7 +10,7 @@ import { deriveState, isMilestoneComplete } from "./state.js";
|
|
|
10
10
|
import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
|
|
11
11
|
import { abortAndReset } from "./git-self-heal.js";
|
|
12
12
|
import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
|
|
13
|
-
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
13
|
+
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
14
14
|
import { getAllWorktreeHealth } from "./worktree-health.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
16
|
|
|
@@ -386,19 +386,19 @@ export async function checkGitHealth(
|
|
|
386
386
|
code: "stale_uncommitted_changes",
|
|
387
387
|
scope: "project",
|
|
388
388
|
unitId: "project",
|
|
389
|
-
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting
|
|
389
|
+
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting tracked files.`,
|
|
390
390
|
fixable: true,
|
|
391
391
|
});
|
|
392
392
|
|
|
393
393
|
if (shouldFix("stale_uncommitted_changes")) {
|
|
394
394
|
try {
|
|
395
|
-
|
|
395
|
+
nativeAddTracked(basePath);
|
|
396
396
|
const commitMsg = `gsd snapshot: uncommitted changes after ${mins}m inactivity`;
|
|
397
397
|
const result = nativeCommit(basePath, commitMsg);
|
|
398
398
|
if (result) {
|
|
399
399
|
fixesApplied.push(`created gsd snapshot after ${mins}m of uncommitted changes`);
|
|
400
400
|
} else {
|
|
401
|
-
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging
|
|
401
|
+
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging tracked files");
|
|
402
402
|
}
|
|
403
403
|
} catch {
|
|
404
404
|
fixesApplied.push("failed to create gsd snapshot commit");
|
|
@@ -21,8 +21,8 @@ import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.j
|
|
|
21
21
|
import { abortAndReset } from "./git-self-heal.js";
|
|
22
22
|
import { rebuildState } from "./doctor.js";
|
|
23
23
|
import { deriveState } from "./state.js";
|
|
24
|
-
import {
|
|
25
|
-
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
24
|
+
import { resolveMilestoneIntegrationBranch } from "./git-service.js";
|
|
25
|
+
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
26
26
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
27
27
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
28
28
|
|
|
@@ -312,7 +312,7 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
312
312
|
if (minutesSinceCommit >= thresholdMinutes) {
|
|
313
313
|
const mins = Math.floor(minutesSinceCommit);
|
|
314
314
|
try {
|
|
315
|
-
|
|
315
|
+
nativeAddTracked(basePath);
|
|
316
316
|
const commitMsg = `gsd snapshot: pre-dispatch, uncommitted changes after ${mins}m inactivity`;
|
|
317
317
|
const result = nativeCommit(basePath, commitMsg);
|
|
318
318
|
if (result) {
|
|
@@ -8,6 +8,7 @@ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSl
|
|
|
8
8
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
9
9
|
import { invalidateAllCaches } from "./cache.js";
|
|
10
10
|
import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
|
|
11
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
11
12
|
|
|
12
13
|
import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
|
|
13
14
|
import { GLOBAL_STATE_CODES } from "./doctor-types.js";
|
|
@@ -474,15 +475,16 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
474
475
|
if (!roadmapContent) continue;
|
|
475
476
|
|
|
476
477
|
// Normalize slices: prefer DB, fall back to parser
|
|
477
|
-
type NormSlice = RoadmapSliceEntry & { pending?: boolean };
|
|
478
|
+
type NormSlice = RoadmapSliceEntry & { pending?: boolean; skipped?: boolean };
|
|
478
479
|
let slices: NormSlice[];
|
|
479
480
|
if (isDbAvailable()) {
|
|
480
481
|
const dbSlices = getMilestoneSlices(milestoneId);
|
|
481
482
|
slices = dbSlices.map(s => ({
|
|
482
483
|
id: s.id,
|
|
483
484
|
title: s.title,
|
|
484
|
-
done: s.status
|
|
485
|
+
done: isClosedStatus(s.status),
|
|
485
486
|
pending: s.status === "pending",
|
|
487
|
+
skipped: s.status === "skipped",
|
|
486
488
|
risk: (s.risk || "medium") as RoadmapSliceEntry["risk"],
|
|
487
489
|
depends: s.depends,
|
|
488
490
|
demo: s.demo,
|
|
@@ -578,8 +580,9 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
578
580
|
const slicePath = resolveSlicePath(basePath, milestoneId, slice.id);
|
|
579
581
|
if (!slicePath) {
|
|
580
582
|
// Pending slices haven't been planned yet — directories are created
|
|
581
|
-
// lazily by ensurePreconditions() at dispatch time.
|
|
582
|
-
|
|
583
|
+
// lazily by ensurePreconditions() at dispatch time. Skipped slices are
|
|
584
|
+
// intentionally allowed to remain summary-less and directory-less.
|
|
585
|
+
if (slice.pending || slice.skipped) continue;
|
|
583
586
|
const expectedPath = relSlicePath(basePath, milestoneId, slice.id);
|
|
584
587
|
issues.push({
|
|
585
588
|
severity: slice.done ? "warning" : "error",
|
|
@@ -603,7 +606,8 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
603
606
|
const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
|
|
604
607
|
if (!tasksDir) {
|
|
605
608
|
// Pending slices haven't been planned yet — tasks/ is created on demand.
|
|
606
|
-
|
|
609
|
+
// Skipped slices may legitimately never create tasks/.
|
|
610
|
+
if (slice.pending || slice.skipped) continue;
|
|
607
611
|
issues.push({
|
|
608
612
|
severity: slice.done ? "warning" : "error",
|
|
609
613
|
code: "missing_tasks_dir",
|
|
@@ -16,7 +16,12 @@ import { buildSkillActivationBlock } from "./auto-prompts.js";
|
|
|
16
16
|
import { deriveState } from "./state.js";
|
|
17
17
|
import { invalidateAllCaches } from "./cache.js";
|
|
18
18
|
import { startAuto } from "./auto.js";
|
|
19
|
-
import {
|
|
19
|
+
import { clearLock } from "./crash-recovery.js";
|
|
20
|
+
import {
|
|
21
|
+
assessInterruptedSession,
|
|
22
|
+
formatInterruptedSessionRunningMessage,
|
|
23
|
+
formatInterruptedSessionSummary,
|
|
24
|
+
} from "./interrupted-session.js";
|
|
20
25
|
import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
21
26
|
import { resolveExpectedArtifactPath } from "./auto.js";
|
|
22
27
|
import {
|
|
@@ -215,17 +220,9 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
215
220
|
|
|
216
221
|
// Gate 4: Discussion manifest process verification (multi-milestone only)
|
|
217
222
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
218
|
-
//
|
|
219
|
-
//
|
|
223
|
+
// When it exists, validate it before auto-starting. Project history alone is
|
|
224
|
+
// not a reliable signal for the current discussion mode.
|
|
220
225
|
const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
|
|
221
|
-
const requiresManifest = projectIds.length > 1 || findMilestoneIds(basePath).length > 1;
|
|
222
|
-
if (requiresManifest && !existsSync(manifestPath)) {
|
|
223
|
-
ctx.ui.notify(
|
|
224
|
-
"Multi-milestone discussion manifest is missing. Auto-start will remain paused until the manifest is written.",
|
|
225
|
-
"warning",
|
|
226
|
-
);
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
226
|
if (existsSync(manifestPath)) {
|
|
230
227
|
try {
|
|
231
228
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -1322,36 +1319,45 @@ export async function showSmartEntry(
|
|
|
1322
1319
|
// ── Self-heal stale runtime records from crashed auto-mode sessions ──
|
|
1323
1320
|
selfHealRuntimeRecords(basePath, ctx);
|
|
1324
1321
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
clearLock(basePath);
|
|
1322
|
+
const interrupted = await assessInterruptedSession(basePath);
|
|
1323
|
+
if (interrupted.classification === "running") {
|
|
1324
|
+
ctx.ui.notify(formatInterruptedSessionRunningMessage(interrupted), "error");
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1331
1327
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
if (!isBootstrapCrash) {
|
|
1340
|
-
const resume = await showNextAction(ctx, {
|
|
1341
|
-
title: "GSD — Interrupted Session Detected",
|
|
1342
|
-
summary: [formatCrashInfo(crashLock)],
|
|
1343
|
-
actions: [
|
|
1344
|
-
{ id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true },
|
|
1345
|
-
{ id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
|
|
1346
|
-
],
|
|
1347
|
-
});
|
|
1348
|
-
if (resume === "resume") {
|
|
1349
|
-
await startAuto(ctx, pi, basePath, false);
|
|
1350
|
-
return;
|
|
1328
|
+
if (interrupted.classification === "stale") {
|
|
1329
|
+
clearLock(basePath);
|
|
1330
|
+
if (interrupted.pausedSession) {
|
|
1331
|
+
try {
|
|
1332
|
+
unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"));
|
|
1333
|
+
} catch (e) {
|
|
1334
|
+
logWarning("guided", `stale pause file cleanup failed: ${(e as Error).message}`, { file: "guided-flow.ts" });
|
|
1351
1335
|
}
|
|
1352
1336
|
}
|
|
1337
|
+
} else if (interrupted.classification === "recoverable") {
|
|
1338
|
+
if (interrupted.lock) clearLock(basePath);
|
|
1339
|
+
const resumeLabel = interrupted.pausedSession?.stepMode
|
|
1340
|
+
? "Resume with /gsd next"
|
|
1341
|
+
: "Resume with /gsd auto";
|
|
1342
|
+
const resume = await showNextAction(ctx, {
|
|
1343
|
+
title: "GSD — Interrupted Session Detected",
|
|
1344
|
+
summary: formatInterruptedSessionSummary(interrupted),
|
|
1345
|
+
actions: [
|
|
1346
|
+
{ id: "resume", label: resumeLabel, description: "Pick up where it left off", recommended: true },
|
|
1347
|
+
{ id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
|
|
1348
|
+
],
|
|
1349
|
+
});
|
|
1350
|
+
if (resume === "resume") {
|
|
1351
|
+
await startAuto(ctx, pi, basePath, false, {
|
|
1352
|
+
interrupted,
|
|
1353
|
+
step: interrupted.pausedSession?.stepMode ?? false,
|
|
1354
|
+
});
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1353
1357
|
}
|
|
1354
1358
|
|
|
1359
|
+
// Always derive from the project root — the assessment may have derived
|
|
1360
|
+
// state from a worktree path that was cleaned up in the stale branch above.
|
|
1355
1361
|
const state = await deriveState(basePath);
|
|
1356
1362
|
|
|
1357
1363
|
// Rebuild STATE.md from derived state before any dispatch (#3475).
|