@opengsd/gsd-pi 1.0.2-dev.5961fbf → 1.0.2-dev.5f7864c
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 +63 -12
- package/dist/onboarding.js +22 -3
- package/dist/resource-loader.d.ts +2 -0
- package/dist/resource-loader.js +18 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/get-secrets-from-user.js +16 -16
- package/dist/resources/extensions/google-cli/index.js +30 -0
- package/dist/resources/extensions/google-cli/models.js +55 -0
- package/dist/resources/extensions/google-cli/package.json +11 -0
- package/dist/resources/extensions/google-cli/readiness.js +12 -0
- package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
- package/dist/resources/extensions/gsd/commands-usage.js +105 -1
- package/dist/resources/extensions/gsd/config-overlay.js +20 -14
- package/dist/resources/extensions/gsd/context-overlay.js +22 -16
- package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
- package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/key-manager.js +45 -13
- package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
- package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
- package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
- package/dist/resources/extensions/gsd/vision-ask.js +22 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/extensions/shared/confirm-ui.js +9 -6
- package/dist/resources/extensions/shared/dialog-frame.js +42 -0
- package/dist/resources/extensions/shared/interview-ui.js +42 -30
- package/dist/resources/extensions/shared/next-action-ui.js +6 -6
- package/dist/resources/shared/package-manager-detection.js +36 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +7 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +13 -13
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +57 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +64 -28
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/detect-existing.js +17 -3
- package/scripts/install/npm-global.js +103 -33
- package/scripts/install.js +1 -0
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/get-secrets-from-user.ts +17 -16
- package/src/resources/extensions/google-cli/index.ts +34 -0
- package/src/resources/extensions/google-cli/models.ts +57 -0
- package/src/resources/extensions/google-cli/package.json +11 -0
- package/src/resources/extensions/google-cli/readiness.ts +15 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
- package/src/resources/extensions/gsd/commands-usage.ts +110 -5
- package/src/resources/extensions/gsd/config-overlay.ts +19 -16
- package/src/resources/extensions/gsd/context-overlay.ts +24 -19
- package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
- package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/key-manager.ts +57 -14
- package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
- package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
- package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
- package/src/resources/extensions/gsd/vision-ask.ts +28 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/extensions/shared/confirm-ui.ts +8 -12
- package/src/resources/extensions/shared/dialog-frame.ts +71 -0
- package/src/resources/extensions/shared/interview-ui.ts +43 -42
- package/src/resources/extensions/shared/next-action-ui.ts +6 -6
- package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
- package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
- package/src/resources/shared/package-manager-detection.ts +39 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_ssgManifest.js +0 -0
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
nativeBranchDelete,
|
|
49
49
|
nativeWorktreeRemove,
|
|
50
50
|
nativeCommitCountBetween,
|
|
51
|
+
nativeHasChanges,
|
|
51
52
|
} from "./native-git-bridge.js";
|
|
52
53
|
import { GitServiceImpl } from "./git-service.js";
|
|
53
54
|
import {
|
|
@@ -242,24 +243,126 @@ export function resolveSurvivorRecoveryIsolationMode(
|
|
|
242
243
|
return isolationMode;
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
export type StrandedWorkRecoveryMode = "worktree" | "branch";
|
|
247
|
+
|
|
248
|
+
export type OrphanAuditActionKind =
|
|
249
|
+
| "in-progress-stranded-work"
|
|
250
|
+
| "complete-merged-branch"
|
|
251
|
+
| "complete-merged-worktree"
|
|
252
|
+
| "complete-unmerged-branch"
|
|
253
|
+
| "complete-branchless-worktree";
|
|
254
|
+
|
|
255
|
+
export interface OrphanAuditAction {
|
|
256
|
+
kind: OrphanAuditActionKind;
|
|
257
|
+
milestoneId: string;
|
|
258
|
+
message: string;
|
|
259
|
+
severity: "info" | "warning";
|
|
260
|
+
branch?: string;
|
|
261
|
+
commitsAhead?: number;
|
|
262
|
+
dirtyWorktree?: boolean;
|
|
263
|
+
worktreeDirExists?: boolean;
|
|
264
|
+
recoveryMode?: StrandedWorkRecoveryMode;
|
|
265
|
+
blocksAuto: boolean;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface OrphanAuditResult {
|
|
269
|
+
recovered: string[];
|
|
270
|
+
warnings: string[];
|
|
271
|
+
actions: OrphanAuditAction[];
|
|
272
|
+
blockingStrandedWork: OrphanAuditAction | null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function isBlockingStrandedWorkAction(action: OrphanAuditAction): boolean {
|
|
276
|
+
return action.kind === "in-progress-stranded-work" && action.blocksAuto;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function detectWorktreeEvidence(
|
|
280
|
+
basePath: string,
|
|
281
|
+
milestoneId: string,
|
|
282
|
+
hasChanges: typeof nativeHasChanges,
|
|
283
|
+
): { path: string | null; dirExists: boolean; dirty: boolean } {
|
|
284
|
+
const wtDir = getWorktreeDir(basePath, milestoneId);
|
|
285
|
+
const wtPath = getAutoWorktreePath(basePath, milestoneId);
|
|
286
|
+
let dirty = false;
|
|
287
|
+
if (wtPath) {
|
|
288
|
+
try {
|
|
289
|
+
dirty = hasChanges(wtPath);
|
|
290
|
+
} catch {
|
|
291
|
+
dirty = false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
path: wtPath,
|
|
296
|
+
dirExists: existsSync(wtDir),
|
|
297
|
+
dirty,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function strandedWorkMessage(args: {
|
|
302
|
+
milestoneId: string;
|
|
303
|
+
branch?: string;
|
|
304
|
+
commitsAhead: number;
|
|
305
|
+
mainBranch: string;
|
|
306
|
+
dirtyWorktree: boolean;
|
|
307
|
+
worktreeDirExists: boolean;
|
|
308
|
+
recoveryMode: StrandedWorkRecoveryMode;
|
|
309
|
+
}): string {
|
|
310
|
+
const evidence: string[] = [];
|
|
311
|
+
if (args.branch && args.commitsAhead > 0) {
|
|
312
|
+
evidence.push(
|
|
313
|
+
`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (args.dirtyWorktree) {
|
|
317
|
+
evidence.push("the worktree has uncommitted changes");
|
|
318
|
+
}
|
|
319
|
+
if (evidence.length === 0) {
|
|
320
|
+
evidence.push("physical git evidence exists");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const wtSuffix = args.worktreeDirExists
|
|
324
|
+
? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
|
|
325
|
+
: "";
|
|
326
|
+
const recovery = args.recoveryMode === "worktree"
|
|
327
|
+
? "Recovering will adopt the existing worktree."
|
|
328
|
+
: "Recovering will adopt the milestone branch.";
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
`Stranded work for in-progress milestone ${args.milestoneId}: ${evidence.join("; ")}.` +
|
|
332
|
+
wtSuffix +
|
|
333
|
+
` ${recovery} Park or discard explicitly if abandoning.`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
245
337
|
export function auditOrphanedMilestoneBranches(
|
|
246
338
|
basePath: string,
|
|
247
|
-
|
|
339
|
+
_isolationMode: "worktree" | "branch" | "none",
|
|
248
340
|
gitDeps: {
|
|
249
341
|
branchList?: typeof nativeBranchList;
|
|
250
342
|
branchExists?: typeof nativeBranchExists;
|
|
343
|
+
hasChanges?: typeof nativeHasChanges;
|
|
251
344
|
} = {},
|
|
252
|
-
):
|
|
345
|
+
): OrphanAuditResult {
|
|
253
346
|
const recovered: string[] = [];
|
|
254
347
|
const warnings: string[] = [];
|
|
348
|
+
const actions: OrphanAuditAction[] = [];
|
|
255
349
|
const branchList = gitDeps.branchList ?? nativeBranchList;
|
|
256
350
|
const branchExists = gitDeps.branchExists ?? nativeBranchExists;
|
|
351
|
+
const hasChanges = gitDeps.hasChanges ?? nativeHasChanges;
|
|
257
352
|
|
|
258
|
-
|
|
259
|
-
|
|
353
|
+
const pushAction = (action: OrphanAuditAction): void => {
|
|
354
|
+
actions.push(action);
|
|
355
|
+
if (action.severity === "info") {
|
|
356
|
+
recovered.push(action.message);
|
|
357
|
+
} else {
|
|
358
|
+
warnings.push(action.message);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
260
361
|
|
|
261
362
|
// Skip if DB not available — can't determine completion status
|
|
262
|
-
if (!isDbAvailable())
|
|
363
|
+
if (!isDbAvailable()) {
|
|
364
|
+
return { recovered, warnings, actions, blockingStrandedWork: null };
|
|
365
|
+
}
|
|
263
366
|
|
|
264
367
|
let milestoneBranches: string[];
|
|
265
368
|
let milestoneBranchListAvailable = true;
|
|
@@ -295,6 +398,7 @@ export function auditOrphanedMilestoneBranches(
|
|
|
295
398
|
if (!milestone) continue;
|
|
296
399
|
|
|
297
400
|
const isMerged = mergedBranches.has(branch);
|
|
401
|
+
const worktreeEvidence = detectWorktreeEvidence(basePath, milestoneId, hasChanges);
|
|
298
402
|
|
|
299
403
|
// #4762 — in-progress milestone branch with unmerged commits ahead of
|
|
300
404
|
// main. This is the pre-completion orphan case: auto-mode exited without
|
|
@@ -307,33 +411,45 @@ export function auditOrphanedMilestoneBranches(
|
|
|
307
411
|
// Parked/other closed statuses go through the legacy complete/unmerged
|
|
308
412
|
// path below where appropriate.
|
|
309
413
|
if (!isClosedStatus(milestone.status)) {
|
|
310
|
-
if (isMerged) continue; // nothing to recover
|
|
311
414
|
let commitsAhead = 0;
|
|
312
415
|
try {
|
|
313
416
|
commitsAhead = nativeCommitCountBetween(basePath, mainBranch, branch);
|
|
314
417
|
} catch {
|
|
315
|
-
|
|
316
|
-
continue;
|
|
418
|
+
commitsAhead = 0;
|
|
317
419
|
}
|
|
318
|
-
if (commitsAhead === 0) continue;
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
420
|
+
if ((isMerged || commitsAhead === 0) && !worktreeEvidence.dirty) continue;
|
|
421
|
+
|
|
422
|
+
const recoveryMode: StrandedWorkRecoveryMode = worktreeEvidence.path
|
|
423
|
+
? "worktree"
|
|
424
|
+
: "branch";
|
|
425
|
+
const message = strandedWorkMessage({
|
|
426
|
+
milestoneId,
|
|
427
|
+
branch,
|
|
428
|
+
commitsAhead,
|
|
429
|
+
mainBranch,
|
|
430
|
+
dirtyWorktree: worktreeEvidence.dirty,
|
|
431
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
432
|
+
recoveryMode,
|
|
433
|
+
});
|
|
434
|
+
pushAction({
|
|
435
|
+
kind: "in-progress-stranded-work",
|
|
436
|
+
milestoneId,
|
|
437
|
+
branch,
|
|
438
|
+
commitsAhead,
|
|
439
|
+
dirtyWorktree: worktreeEvidence.dirty,
|
|
440
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
441
|
+
recoveryMode,
|
|
442
|
+
message,
|
|
443
|
+
severity: "warning",
|
|
444
|
+
blocksAuto: true,
|
|
445
|
+
});
|
|
330
446
|
|
|
331
447
|
// #4764 telemetry
|
|
332
448
|
try {
|
|
333
449
|
emitWorktreeOrphaned(basePath, milestoneId, {
|
|
334
450
|
reason: "in-progress-unmerged",
|
|
335
451
|
commitsAhead,
|
|
336
|
-
worktreeDirExists:
|
|
452
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
337
453
|
});
|
|
338
454
|
} catch (err) {
|
|
339
455
|
logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -351,7 +467,14 @@ export function auditOrphanedMilestoneBranches(
|
|
|
351
467
|
// Branch is merged — safe to delete branch and clean up worktree dir
|
|
352
468
|
try {
|
|
353
469
|
nativeBranchDelete(basePath, branch, true);
|
|
354
|
-
|
|
470
|
+
pushAction({
|
|
471
|
+
kind: "complete-merged-branch",
|
|
472
|
+
milestoneId,
|
|
473
|
+
branch,
|
|
474
|
+
message: `Deleted merged branch ${branch} for completed milestone ${milestoneId}.`,
|
|
475
|
+
severity: "info",
|
|
476
|
+
blocksAuto: false,
|
|
477
|
+
});
|
|
355
478
|
} catch (err) {
|
|
356
479
|
warnings.push(`Failed to delete merged branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
357
480
|
}
|
|
@@ -374,7 +497,15 @@ export function auditOrphanedMilestoneBranches(
|
|
|
374
497
|
if (isInsideWorktreesDir(basePath, wtDir)) {
|
|
375
498
|
try {
|
|
376
499
|
rmSync(wtDir, { recursive: true, force: true });
|
|
377
|
-
|
|
500
|
+
pushAction({
|
|
501
|
+
kind: "complete-merged-worktree",
|
|
502
|
+
milestoneId,
|
|
503
|
+
branch,
|
|
504
|
+
worktreeDirExists: true,
|
|
505
|
+
message: `Removed orphaned worktree directory for ${milestoneId}.`,
|
|
506
|
+
severity: "info",
|
|
507
|
+
blocksAuto: false,
|
|
508
|
+
});
|
|
378
509
|
} catch (err2) {
|
|
379
510
|
warnings.push(`Failed to remove worktree directory for ${milestoneId}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
380
511
|
}
|
|
@@ -382,15 +513,30 @@ export function auditOrphanedMilestoneBranches(
|
|
|
382
513
|
warnings.push(`Orphaned worktree directory for ${milestoneId} is outside .gsd/worktrees/ — skipping removal for safety.`);
|
|
383
514
|
}
|
|
384
515
|
} else {
|
|
385
|
-
|
|
516
|
+
pushAction({
|
|
517
|
+
kind: "complete-merged-worktree",
|
|
518
|
+
milestoneId,
|
|
519
|
+
branch,
|
|
520
|
+
worktreeDirExists: true,
|
|
521
|
+
message: `Removed orphaned worktree directory for ${milestoneId}.`,
|
|
522
|
+
severity: "info",
|
|
523
|
+
blocksAuto: false,
|
|
524
|
+
});
|
|
386
525
|
}
|
|
387
526
|
}
|
|
388
527
|
} else {
|
|
389
528
|
// Branch is NOT merged — preserve for safety, warn the user
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
529
|
+
pushAction({
|
|
530
|
+
kind: "complete-unmerged-branch",
|
|
531
|
+
milestoneId,
|
|
532
|
+
branch,
|
|
533
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
534
|
+
message:
|
|
535
|
+
`Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
|
|
536
|
+
`This may contain unmerged work. Merge manually or run \`/gsd doctor fix\` to resolve.`,
|
|
537
|
+
severity: "warning",
|
|
538
|
+
blocksAuto: false,
|
|
539
|
+
});
|
|
394
540
|
|
|
395
541
|
// #4764 telemetry
|
|
396
542
|
try {
|
|
@@ -428,6 +574,41 @@ export function auditOrphanedMilestoneBranches(
|
|
|
428
574
|
completedMilestones = [];
|
|
429
575
|
}
|
|
430
576
|
for (const m of completedMilestones) {
|
|
577
|
+
if (!isClosedStatus(m.status)) {
|
|
578
|
+
if (seenMilestoneIds.has(m.id)) continue;
|
|
579
|
+
const worktreeEvidence = detectWorktreeEvidence(basePath, m.id, hasChanges);
|
|
580
|
+
if (!worktreeEvidence.dirty) continue;
|
|
581
|
+
const message = strandedWorkMessage({
|
|
582
|
+
milestoneId: m.id,
|
|
583
|
+
commitsAhead: 0,
|
|
584
|
+
mainBranch,
|
|
585
|
+
dirtyWorktree: true,
|
|
586
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
587
|
+
recoveryMode: "worktree",
|
|
588
|
+
});
|
|
589
|
+
pushAction({
|
|
590
|
+
kind: "in-progress-stranded-work",
|
|
591
|
+
milestoneId: m.id,
|
|
592
|
+
commitsAhead: 0,
|
|
593
|
+
dirtyWorktree: true,
|
|
594
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
595
|
+
recoveryMode: "worktree",
|
|
596
|
+
message,
|
|
597
|
+
severity: "warning",
|
|
598
|
+
blocksAuto: true,
|
|
599
|
+
});
|
|
600
|
+
try {
|
|
601
|
+
emitWorktreeOrphaned(basePath, m.id, {
|
|
602
|
+
reason: "in-progress-unmerged",
|
|
603
|
+
commitsAhead: 0,
|
|
604
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
605
|
+
});
|
|
606
|
+
} catch (err) {
|
|
607
|
+
logWarning("engine", `worktree-orphaned telemetry failed for ${m.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
608
|
+
}
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
|
|
431
612
|
if (m.status !== "complete") continue;
|
|
432
613
|
if (seenMilestoneIds.has(m.id)) continue; // already processed in the branch loop
|
|
433
614
|
if (!milestoneBranchListAvailable) {
|
|
@@ -461,18 +642,37 @@ export function auditOrphanedMilestoneBranches(
|
|
|
461
642
|
if (existsSync(wtDir)) {
|
|
462
643
|
try {
|
|
463
644
|
rmSync(wtDir, { recursive: true, force: true });
|
|
464
|
-
|
|
645
|
+
pushAction({
|
|
646
|
+
kind: "complete-branchless-worktree",
|
|
647
|
+
milestoneId: m.id,
|
|
648
|
+
worktreeDirExists: true,
|
|
649
|
+
message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
|
|
650
|
+
severity: "info",
|
|
651
|
+
blocksAuto: false,
|
|
652
|
+
});
|
|
465
653
|
} catch (err) {
|
|
466
654
|
warnings.push(
|
|
467
655
|
`Failed to remove orphaned worktree directory for ${m.id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
468
656
|
);
|
|
469
657
|
}
|
|
470
658
|
} else {
|
|
471
|
-
|
|
659
|
+
pushAction({
|
|
660
|
+
kind: "complete-branchless-worktree",
|
|
661
|
+
milestoneId: m.id,
|
|
662
|
+
worktreeDirExists: true,
|
|
663
|
+
message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
|
|
664
|
+
severity: "info",
|
|
665
|
+
blocksAuto: false,
|
|
666
|
+
});
|
|
472
667
|
}
|
|
473
668
|
}
|
|
474
669
|
|
|
475
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
recovered,
|
|
672
|
+
warnings,
|
|
673
|
+
actions,
|
|
674
|
+
blockingStrandedWork: actions.find(isBlockingStrandedWorkAction) ?? null,
|
|
675
|
+
};
|
|
476
676
|
}
|
|
477
677
|
|
|
478
678
|
/**
|
|
@@ -894,17 +1094,27 @@ export async function bootstrapAutoSession(
|
|
|
894
1094
|
// was lost due to session ending between completion and teardown.
|
|
895
1095
|
// Must run after DB open and before worktree entry.
|
|
896
1096
|
let orphanAuditRecovered = false;
|
|
1097
|
+
let strandedRecoveryActions: OrphanAuditAction[] = [];
|
|
1098
|
+
let strandedRecoveryAction: OrphanAuditAction | null = null;
|
|
897
1099
|
try {
|
|
898
1100
|
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
1101
|
+
strandedRecoveryActions = auditResult.actions.filter(isBlockingStrandedWorkAction);
|
|
1102
|
+
strandedRecoveryAction = strandedRecoveryActions[0] ?? null;
|
|
899
1103
|
for (const msg of auditResult.recovered) {
|
|
900
1104
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
901
1105
|
}
|
|
902
1106
|
for (const msg of auditResult.warnings) {
|
|
903
|
-
|
|
1107
|
+
const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
|
|
1108
|
+
ctx.ui.notify(`${prefix}${msg}`, "warning");
|
|
904
1109
|
}
|
|
905
1110
|
if (auditResult.recovered.length > 0) {
|
|
906
1111
|
orphanAuditRecovered = true;
|
|
907
|
-
debugLog("orphan-audit", {
|
|
1112
|
+
debugLog("orphan-audit", {
|
|
1113
|
+
recovered: auditResult.recovered,
|
|
1114
|
+
warnings: auditResult.warnings,
|
|
1115
|
+
strandedRecoveryAction,
|
|
1116
|
+
strandedRecoveryActions,
|
|
1117
|
+
});
|
|
908
1118
|
}
|
|
909
1119
|
} catch (err) {
|
|
910
1120
|
// Non-fatal — the audit is defensive, never block bootstrap
|
|
@@ -946,6 +1156,46 @@ export async function bootstrapAutoSession(
|
|
|
946
1156
|
|
|
947
1157
|
let state = await deriveState(base);
|
|
948
1158
|
|
|
1159
|
+
// Stale worktree state recovery (#654)
|
|
1160
|
+
if (
|
|
1161
|
+
state.activeMilestone &&
|
|
1162
|
+
shouldUseWorktreeIsolation(base) &&
|
|
1163
|
+
!detectWorktreeName(base)
|
|
1164
|
+
) {
|
|
1165
|
+
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
1166
|
+
if (wtPath) {
|
|
1167
|
+
state = await deriveState(wtPath);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const blockingStrandedRecoveryAction = state.activeMilestone
|
|
1172
|
+
? strandedRecoveryActions.find(
|
|
1173
|
+
(action) => action.milestoneId !== state.activeMilestone?.id,
|
|
1174
|
+
) ?? strandedRecoveryAction
|
|
1175
|
+
: strandedRecoveryAction;
|
|
1176
|
+
|
|
1177
|
+
if (blockingStrandedRecoveryAction) {
|
|
1178
|
+
if (!state.activeMilestone) {
|
|
1179
|
+
ctx.ui.notify(
|
|
1180
|
+
`Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode, but that milestone is not active in project state. Park or discard it explicitly before continuing.`,
|
|
1181
|
+
"error",
|
|
1182
|
+
);
|
|
1183
|
+
return releaseLockAndReturn();
|
|
1184
|
+
}
|
|
1185
|
+
if (state.activeMilestone.id !== blockingStrandedRecoveryAction.milestoneId) {
|
|
1186
|
+
ctx.ui.notify(
|
|
1187
|
+
`Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode before ${state.activeMilestone.id}. Recover, park, or discard ${blockingStrandedRecoveryAction.milestoneId} explicitly before continuing.`,
|
|
1188
|
+
"error",
|
|
1189
|
+
);
|
|
1190
|
+
return releaseLockAndReturn();
|
|
1191
|
+
}
|
|
1192
|
+
strandedRecoveryAction = blockingStrandedRecoveryAction;
|
|
1193
|
+
ctx.ui.notify(
|
|
1194
|
+
`Recovering stranded work for ${strandedRecoveryAction.milestoneId} before dispatching new units.`,
|
|
1195
|
+
"info",
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
949
1199
|
if (
|
|
950
1200
|
process.env.GSD_HEADLESS === "1" &&
|
|
951
1201
|
orphanAuditRecovered &&
|
|
@@ -959,18 +1209,6 @@ export async function bootstrapAutoSession(
|
|
|
959
1209
|
return releaseLockAndReturn();
|
|
960
1210
|
}
|
|
961
1211
|
|
|
962
|
-
// Stale worktree state recovery (#654)
|
|
963
|
-
if (
|
|
964
|
-
state.activeMilestone &&
|
|
965
|
-
shouldUseWorktreeIsolation(base) &&
|
|
966
|
-
!detectWorktreeName(base)
|
|
967
|
-
) {
|
|
968
|
-
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
969
|
-
if (wtPath) {
|
|
970
|
-
state = await deriveState(wtPath);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
1212
|
// Milestone branch recovery (#601, #2358)
|
|
975
1213
|
// Detect survivor milestone branches in both pre-planning and complete phases.
|
|
976
1214
|
// In phase=complete, the milestone artifacts exist but finalization (merge,
|
|
@@ -1005,7 +1243,10 @@ export async function bootstrapAutoSession(
|
|
|
1005
1243
|
// The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
|
|
1006
1244
|
// Route to the interactive discussion handler instead of falling through to
|
|
1007
1245
|
// auto-mode, which would immediately stop with "needs discussion".
|
|
1008
|
-
if (
|
|
1246
|
+
if (
|
|
1247
|
+
!strandedRecoveryAction &&
|
|
1248
|
+
decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss"
|
|
1249
|
+
) {
|
|
1009
1250
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
1010
1251
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
1011
1252
|
|
|
@@ -1101,7 +1342,7 @@ export async function bootstrapAutoSession(
|
|
|
1101
1342
|
{ hasSurvivorBranch },
|
|
1102
1343
|
);
|
|
1103
1344
|
|
|
1104
|
-
if (deepProjectStagePending) {
|
|
1345
|
+
if (deepProjectStagePending && !strandedRecoveryAction) {
|
|
1105
1346
|
// Deep project-level setup runs before the first milestone exists. Let
|
|
1106
1347
|
// the auto loop dispatch workflow-preferences / project / requirements
|
|
1107
1348
|
// units instead of recursing back through showSmartEntry while this
|
|
@@ -1109,7 +1350,7 @@ export async function bootstrapAutoSession(
|
|
|
1109
1350
|
s.currentMilestoneId = null;
|
|
1110
1351
|
}
|
|
1111
1352
|
|
|
1112
|
-
if (!hasSurvivorBranch && !deepProjectStagePending) {
|
|
1353
|
+
if (!hasSurvivorBranch && !deepProjectStagePending && !strandedRecoveryAction) {
|
|
1113
1354
|
// No active work — start a new milestone via discuss flow
|
|
1114
1355
|
if (!state.activeMilestone || state.phase === "complete") {
|
|
1115
1356
|
// Guard against recursive dialog loop (#1348):
|
|
@@ -1185,7 +1426,7 @@ export async function bootstrapAutoSession(
|
|
|
1185
1426
|
}
|
|
1186
1427
|
|
|
1187
1428
|
// Unreachable safety check
|
|
1188
|
-
if (!state.activeMilestone && !deepProjectStagePending) {
|
|
1429
|
+
if (!state.activeMilestone && !deepProjectStagePending && !strandedRecoveryAction) {
|
|
1189
1430
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
1190
1431
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
1191
1432
|
return releaseLockAndReturn();
|
|
@@ -1222,7 +1463,9 @@ export async function bootstrapAutoSession(
|
|
|
1222
1463
|
s.resourceVersionOnStart = readResourceVersion();
|
|
1223
1464
|
s.pendingQuickTasks = [];
|
|
1224
1465
|
s.currentUnit = null;
|
|
1225
|
-
s.currentMilestoneId ??=
|
|
1466
|
+
s.currentMilestoneId ??=
|
|
1467
|
+
strandedRecoveryAction?.milestoneId ??
|
|
1468
|
+
(deepProjectStagePending ? null : state.activeMilestone?.id ?? null);
|
|
1226
1469
|
s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
|
|
1227
1470
|
s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
|
|
1228
1471
|
s.originalThinkingLevel = startThinkingSnapshot ?? null;
|
|
@@ -1232,7 +1475,7 @@ export async function bootstrapAutoSession(
|
|
|
1232
1475
|
|
|
1233
1476
|
// Capture integration branch
|
|
1234
1477
|
if (s.currentMilestoneId) {
|
|
1235
|
-
if (getIsolationMode(base) !== "none") {
|
|
1478
|
+
if (getIsolationMode(base) !== "none" || strandedRecoveryAction) {
|
|
1236
1479
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
1237
1480
|
}
|
|
1238
1481
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -1243,7 +1486,7 @@ export async function bootstrapAutoSession(
|
|
|
1243
1486
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
1244
1487
|
const isolationMode = getIsolationMode(base);
|
|
1245
1488
|
const isRepo = nativeIsRepo(base);
|
|
1246
|
-
if (isolationMode === "none" && isRepo) {
|
|
1489
|
+
if (isolationMode === "none" && isRepo && !strandedRecoveryAction) {
|
|
1247
1490
|
try {
|
|
1248
1491
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
1249
1492
|
const integrationBranch = nativeDetectMainBranch(base);
|
|
@@ -1282,13 +1525,21 @@ export async function bootstrapAutoSession(
|
|
|
1282
1525
|
|
|
1283
1526
|
if (
|
|
1284
1527
|
s.currentMilestoneId &&
|
|
1285
|
-
getIsolationMode(base) !== "none" &&
|
|
1528
|
+
(getIsolationMode(base) !== "none" || strandedRecoveryAction?.recoveryMode) &&
|
|
1286
1529
|
!detectWorktreeName(base) &&
|
|
1287
1530
|
!isUnderGsdWorktrees(base)
|
|
1288
1531
|
) {
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1532
|
+
const lifecycle = buildLifecycle();
|
|
1533
|
+
const enterResult = strandedRecoveryAction?.recoveryMode
|
|
1534
|
+
? lifecycle.adoptStrandedMilestone(
|
|
1535
|
+
s.currentMilestoneId,
|
|
1536
|
+
base,
|
|
1537
|
+
{ notify: ctx.ui.notify.bind(ctx.ui) },
|
|
1538
|
+
{ mode: strandedRecoveryAction.recoveryMode },
|
|
1539
|
+
)
|
|
1540
|
+
: lifecycle.enterMilestone(s.currentMilestoneId, {
|
|
1541
|
+
notify: ctx.ui.notify.bind(ctx.ui),
|
|
1542
|
+
});
|
|
1292
1543
|
if (!enterResult.ok) {
|
|
1293
1544
|
s.active = false;
|
|
1294
1545
|
if (enterResult.reason === "lease-conflict") {
|
|
@@ -715,8 +715,9 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
715
715
|
promptSnippet: "Complete a GSD task (DB write + summary render + checkbox toggle)",
|
|
716
716
|
promptGuidelines: [
|
|
717
717
|
"Use gsd_task_complete (or gsd_complete_task) when a task is finished and needs to be recorded.",
|
|
718
|
-
"
|
|
719
|
-
"
|
|
718
|
+
"Include verification whenever possible. If verification is omitted, the executor derives it from verificationEvidence when possible.",
|
|
719
|
+
"verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
|
|
720
|
+
"The tool validates required fields and returns an error message if verification cannot be derived.",
|
|
720
721
|
"On success, returns the summaryPath where the SUMMARY.md was written.",
|
|
721
722
|
"Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
|
|
722
723
|
],
|
|
@@ -727,7 +728,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
727
728
|
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
728
729
|
oneLiner: Type.String({ description: "One-line summary of what was accomplished" }),
|
|
729
730
|
narrative: Type.String({ description: "Detailed narrative of what happened during the task" }),
|
|
730
|
-
verification: Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed" }),
|
|
731
|
+
verification: Type.Optional(Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed. If omitted, derived from verificationEvidence when possible." })),
|
|
731
732
|
// ── Enrichment metadata (optional — defaults to empty) ────────────
|
|
732
733
|
deviations: Type.Optional(Type.String({ description: "Deviations from the task plan, or 'None.'" })),
|
|
733
734
|
knownIssues: Type.Optional(Type.String({ description: "Known issues discovered but not fixed, or 'None.'" })),
|
|
@@ -507,6 +507,21 @@ function initSessionNotifications(ctx: ExtensionContext): void {
|
|
|
507
507
|
initNotificationWidget(ctx);
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
async function prepareWorkflowMcpForHookContext(
|
|
511
|
+
ctx: ExtensionContext,
|
|
512
|
+
basePath: string,
|
|
513
|
+
): Promise<void> {
|
|
514
|
+
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
515
|
+
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
516
|
+
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
517
|
+
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
518
|
+
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
519
|
+
if (isInAutoWorktree(basePath)) return;
|
|
520
|
+
|
|
521
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
522
|
+
prepareWorkflowMcpForProject(ctx, basePath);
|
|
523
|
+
}
|
|
524
|
+
|
|
510
525
|
export function registerHooks(
|
|
511
526
|
pi: ExtensionAPI,
|
|
512
527
|
ecosystemHandlers: GSDEcosystemBeforeAgentStartHandler[],
|
|
@@ -532,12 +547,7 @@ export function registerHooks(
|
|
|
532
547
|
await syncServiceTierStatus(ctx);
|
|
533
548
|
await applyDisabledModelProviderPolicy(ctx);
|
|
534
549
|
await applyCompactionThresholdOverride(ctx);
|
|
535
|
-
|
|
536
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
537
|
-
if (!isInAutoWorktree(basePath)) {
|
|
538
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
539
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
540
|
-
}
|
|
550
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
541
551
|
|
|
542
552
|
// Apply show_token_cost preference (#1515)
|
|
543
553
|
try {
|
|
@@ -563,15 +573,7 @@ export function registerHooks(
|
|
|
563
573
|
await syncServiceTierStatus(ctx);
|
|
564
574
|
await applyDisabledModelProviderPolicy(ctx);
|
|
565
575
|
await applyCompactionThresholdOverride(ctx);
|
|
566
|
-
|
|
567
|
-
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
568
|
-
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
569
|
-
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
570
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
571
|
-
if (!isInAutoWorktree(basePath)) {
|
|
572
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
573
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
574
|
-
}
|
|
576
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
575
577
|
await loadToolApiKeysForSession();
|
|
576
578
|
if (!isAutoActive()) {
|
|
577
579
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
@@ -608,6 +610,11 @@ export function registerHooks(
|
|
|
608
610
|
}
|
|
609
611
|
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
610
612
|
|
|
613
|
+
// session_start can fire before the active provider has settled. By
|
|
614
|
+
// before_agent_start, Claude Code CLI sessions should get the same
|
|
615
|
+
// project MCP config that /gsd mcp init would write.
|
|
616
|
+
await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
|
|
617
|
+
|
|
611
618
|
// GSD's own context injection (existing behavior — unchanged).
|
|
612
619
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
613
620
|
const gsdResult = await buildBeforeAgentStartResult(event, ctx);
|
|
@@ -205,7 +205,12 @@ export function getCloseoutManualResolveBlocker(basePath: string): string | null
|
|
|
205
205
|
return `Unmerged paths remain in ${basePath}: ${conflictProbe.unmerged.slice(0, 5).join(", ")}`;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
let status: string;
|
|
209
|
+
try {
|
|
210
|
+
status = runGit(basePath, ["status", "--porcelain"]);
|
|
211
|
+
} catch {
|
|
212
|
+
return `Could not inspect git status in ${basePath}.`;
|
|
213
|
+
}
|
|
209
214
|
if (status) {
|
|
210
215
|
return `Working tree still has uncommitted changes in ${basePath}. Commit, stash, or run /gsd closeout retry first.`;
|
|
211
216
|
}
|
|
@@ -208,7 +208,15 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
208
208
|
|
|
209
209
|
if (trimmed === "") {
|
|
210
210
|
if (!(await guardRemoteSession(ctx, pi))) return true;
|
|
211
|
-
|
|
211
|
+
const basePath = projectRoot();
|
|
212
|
+
const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
|
|
213
|
+
const { gsdRoot } = await import("../../paths.js");
|
|
214
|
+
if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
|
|
215
|
+
const { showSmartEntry } = await import("../../guided-flow.js");
|
|
216
|
+
await showSmartEntry(ctx, pi, basePath, { step: true });
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (await hasUnresolvedCloseoutBlocker(ctx, basePath)) return true;
|
|
212
220
|
const { showGsdHome } = await import("../../gsd-command-home.js");
|
|
213
221
|
await showGsdHome(ctx, pi, projectRoot());
|
|
214
222
|
return true;
|
|
@@ -26,6 +26,7 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
|
|
|
26
26
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
27
27
|
import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
|
|
28
28
|
import { loadPrompt } from "./prompt-loader.js";
|
|
29
|
+
import { isPnpmInstall } from "../../shared/package-manager-detection.js";
|
|
29
30
|
import {
|
|
30
31
|
buildDoctorHealIssuePayload,
|
|
31
32
|
buildDoctorHealSummary,
|
|
@@ -57,6 +58,7 @@ function isBunInstall(argv1: string | undefined = process.argv[1]): boolean {
|
|
|
57
58
|
|
|
58
59
|
function resolveInstallCommand(pkg: string): string {
|
|
59
60
|
if (isBunInstall()) return `bun add -g ${pkg}`;
|
|
61
|
+
if (isPnpmInstall()) return `pnpm add -g ${pkg}`;
|
|
60
62
|
return `npm install -g ${pkg}`;
|
|
61
63
|
}
|
|
62
64
|
|