gsd-pi 2.32.0-dev.d792ba5 → 2.32.0-dev.f3d5d53

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.
Files changed (50) hide show
  1. package/dist/resource-loader.js +13 -3
  2. package/dist/resources/extensions/gsd/auto-prompts.ts +46 -44
  3. package/dist/resources/extensions/gsd/auto-start.ts +6 -5
  4. package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
  5. package/dist/resources/extensions/gsd/auto-verification.ts +2 -1
  6. package/dist/resources/extensions/gsd/auto-worktree.ts +5 -4
  7. package/dist/resources/extensions/gsd/auto.ts +24 -23
  8. package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
  9. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
  10. package/dist/resources/extensions/gsd/error-utils.ts +6 -0
  11. package/dist/resources/extensions/gsd/export.ts +2 -1
  12. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  13. package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
  14. package/dist/resources/extensions/gsd/index.ts +6 -5
  15. package/dist/resources/extensions/gsd/key-manager.ts +2 -1
  16. package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  17. package/dist/resources/extensions/gsd/migrate-external.ts +3 -2
  18. package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
  19. package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
  20. package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
  21. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  22. package/dist/resources/extensions/gsd/quick.ts +2 -1
  23. package/dist/resources/extensions/gsd/session-lock.ts +12 -1
  24. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  25. package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
  26. package/package.json +1 -1
  27. package/src/resources/extensions/gsd/auto-prompts.ts +46 -44
  28. package/src/resources/extensions/gsd/auto-start.ts +6 -5
  29. package/src/resources/extensions/gsd/auto-timers.ts +3 -2
  30. package/src/resources/extensions/gsd/auto-verification.ts +2 -1
  31. package/src/resources/extensions/gsd/auto-worktree.ts +5 -4
  32. package/src/resources/extensions/gsd/auto.ts +24 -23
  33. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  34. package/src/resources/extensions/gsd/commands-workflow-templates.ts +2 -1
  35. package/src/resources/extensions/gsd/error-utils.ts +6 -0
  36. package/src/resources/extensions/gsd/export.ts +2 -1
  37. package/src/resources/extensions/gsd/git-service.ts +3 -2
  38. package/src/resources/extensions/gsd/guided-flow.ts +3 -2
  39. package/src/resources/extensions/gsd/index.ts +6 -5
  40. package/src/resources/extensions/gsd/key-manager.ts +2 -1
  41. package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  42. package/src/resources/extensions/gsd/migrate-external.ts +3 -2
  43. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  44. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
  45. package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
  46. package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  47. package/src/resources/extensions/gsd/quick.ts +2 -1
  48. package/src/resources/extensions/gsd/session-lock.ts +12 -1
  49. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  50. package/src/resources/extensions/gsd/worktree-command.ts +8 -7
@@ -189,6 +189,7 @@ import {
189
189
  NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
190
190
  } from "./auto/session.js";
191
191
  import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
192
+ import { getErrorMessage } from "./error-utils.js";
192
193
 
193
194
  // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
194
195
  // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
@@ -428,7 +429,7 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
428
429
  try {
429
430
  await dispatchNextUnit(ctx, pi);
430
431
  } catch (retryErr) {
431
- const message = retryErr instanceof Error ? retryErr.message : String(retryErr);
432
+ const message = getErrorMessage(retryErr);
432
433
  await stopAuto(ctx, pi, `Dispatch gap recovery failed: ${message}`);
433
434
  return;
434
435
  }
@@ -458,14 +459,14 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
458
459
  // ── Auto-worktree: exit worktree and reset s.basePath on stop ──
459
460
  if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
460
461
  try {
461
- try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
462
+ try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: getErrorMessage(e) }); }
462
463
  teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
463
464
  s.basePath = s.originalBasePath;
464
465
  s.gitService = createGitService(s.basePath);
465
466
  ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
466
467
  } catch (err) {
467
468
  ctx?.ui.notify(
468
- `Auto-worktree teardown failed: ${err instanceof Error ? err.message : String(err)}`,
469
+ `Auto-worktree teardown failed: ${getErrorMessage(err)}`,
469
470
  "warning",
470
471
  );
471
472
  }
@@ -476,7 +477,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
476
477
  try {
477
478
  const { closeDatabase } = await import("./gsd-db.js");
478
479
  closeDatabase();
479
- } catch (e) { debugLog("db-close-failed", { error: e instanceof Error ? e.message : String(e) }); }
480
+ } catch (e) { debugLog("db-close-failed", { error: getErrorMessage(e) }); }
480
481
  }
481
482
 
482
483
  if (s.originalBasePath) {
@@ -496,7 +497,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
496
497
  }
497
498
 
498
499
  if (s.basePath) {
499
- try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
500
+ try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: getErrorMessage(e) }); }
500
501
  }
501
502
 
502
503
  if (isDebugEnabled()) {
@@ -635,7 +636,7 @@ export async function startAuto(
635
636
  }
636
637
  } catch (err) {
637
638
  ctx.ui.notify(
638
- `Auto-worktree re-entry failed: ${err instanceof Error ? err.message : String(err)}. Continuing at current path.`,
639
+ `Auto-worktree re-entry failed: ${getErrorMessage(err)}. Continuing at current path.`,
639
640
  "warning",
640
641
  );
641
642
  }
@@ -647,13 +648,13 @@ export async function startAuto(
647
648
  ctx.ui.setFooter(hideFooter);
648
649
  ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
649
650
  restoreHookState(s.basePath);
650
- try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
651
+ try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: getErrorMessage(e) }); }
651
652
  try {
652
653
  const report = await runGSDDoctor(s.basePath, { fix: true });
653
654
  if (report.fixesApplied.length > 0) {
654
655
  ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
655
656
  }
656
- } catch (e) { debugLog("resume-doctor-failed", { error: e instanceof Error ? e.message : String(e) }); }
657
+ } catch (e) { debugLog("resume-doctor-failed", { error: getErrorMessage(e) }); }
657
658
  await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
658
659
  invalidateAllCaches();
659
660
 
@@ -700,7 +701,7 @@ export async function startAuto(
700
701
  }
701
702
  } catch (err) {
702
703
  ctx.ui.notify(
703
- `Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
704
+ `Secrets check error: ${getErrorMessage(err)}. Continuing without secrets.`,
704
705
  "warning",
705
706
  );
706
707
  }
@@ -807,7 +808,7 @@ export async function handleAgentEnd(
807
808
  try {
808
809
  await dispatchNextUnit(ctx, pi);
809
810
  } catch (dispatchErr) {
810
- const message = dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr);
811
+ const message = getErrorMessage(dispatchErr);
811
812
  ctx.ui.notify(
812
813
  `Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
813
814
  "error",
@@ -838,7 +839,7 @@ export async function handleAgentEnd(
838
839
  clearDispatchGapWatchdog();
839
840
  setImmediate(() => {
840
841
  handleAgentEnd(ctx, pi).catch((err) => {
841
- const msg = err instanceof Error ? err.message : String(err);
842
+ const msg = getErrorMessage(err);
842
843
  ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
843
844
  pauseAuto(ctx, pi).catch(() => {});
844
845
  });
@@ -1086,7 +1087,7 @@ async function dispatchNextUnit(
1086
1087
  );
1087
1088
  } catch (err) {
1088
1089
  ctx.ui.notify(
1089
- `Report generation failed: ${err instanceof Error ? err.message : String(err)}`,
1090
+ `Report generation failed: ${getErrorMessage(err)}`,
1090
1091
  "warning",
1091
1092
  );
1092
1093
  }
@@ -1102,7 +1103,7 @@ async function dispatchNextUnit(
1102
1103
  atomicWriteSync(file, JSON.stringify([]));
1103
1104
  }
1104
1105
  s.completedKeySet.clear();
1105
- } catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
1106
+ } catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
1106
1107
 
1107
1108
  // ── Worktree lifecycle on milestone transition (#616) ──
1108
1109
  if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
@@ -1121,7 +1122,7 @@ async function dispatchNextUnit(
1121
1122
  }
1122
1123
  } catch (err) {
1123
1124
  ctx.ui.notify(
1124
- `Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
1125
+ `Milestone merge failed during transition: ${getErrorMessage(err)}`,
1125
1126
  "warning",
1126
1127
  );
1127
1128
  if (s.originalBasePath) {
@@ -1146,7 +1147,7 @@ async function dispatchNextUnit(
1146
1147
  ctx.ui.notify(`Created auto-worktree for ${mid} at ${wtPath}`, "info");
1147
1148
  } catch (err) {
1148
1149
  ctx.ui.notify(
1149
- `Auto-worktree creation for ${mid} failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
1150
+ `Auto-worktree creation for ${mid} failed: ${getErrorMessage(err)}. Continuing in project root.`,
1150
1151
  "warning",
1151
1152
  );
1152
1153
  }
@@ -1190,7 +1191,7 @@ async function dispatchNextUnit(
1190
1191
  }
1191
1192
  } catch (err) {
1192
1193
  ctx.ui.notify(
1193
- `Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
1194
+ `Milestone merge failed: ${getErrorMessage(err)}`,
1194
1195
  "warning",
1195
1196
  );
1196
1197
  if (s.originalBasePath) {
@@ -1216,7 +1217,7 @@ async function dispatchNextUnit(
1216
1217
  }
1217
1218
  } catch (err) {
1218
1219
  ctx.ui.notify(
1219
- `Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
1220
+ `Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
1220
1221
  "warning",
1221
1222
  );
1222
1223
  }
@@ -1276,7 +1277,7 @@ async function dispatchNextUnit(
1276
1277
  atomicWriteSync(file, JSON.stringify([]));
1277
1278
  }
1278
1279
  s.completedKeySet.clear();
1279
- } catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
1280
+ } catch (e) { debugLog("completed-keys-reset-failed", { error: getErrorMessage(e) }); }
1280
1281
  // ── Milestone merge ──
1281
1282
  if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
1282
1283
  try {
@@ -1292,7 +1293,7 @@ async function dispatchNextUnit(
1292
1293
  );
1293
1294
  } catch (err) {
1294
1295
  ctx.ui.notify(
1295
- `Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
1296
+ `Milestone merge failed: ${getErrorMessage(err)}`,
1296
1297
  "warning",
1297
1298
  );
1298
1299
  if (s.originalBasePath) {
@@ -1318,7 +1319,7 @@ async function dispatchNextUnit(
1318
1319
  }
1319
1320
  } catch (err) {
1320
1321
  ctx.ui.notify(
1321
- `Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
1322
+ `Milestone merge failed (branch mode): ${getErrorMessage(err)}`,
1322
1323
  "warning",
1323
1324
  );
1324
1325
  }
@@ -1417,7 +1418,7 @@ async function dispatchNextUnit(
1417
1418
  }
1418
1419
  } catch (err) {
1419
1420
  ctx.ui.notify(
1420
- `Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`,
1421
+ `Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`,
1421
1422
  "warning",
1422
1423
  );
1423
1424
  }
@@ -1628,7 +1629,7 @@ async function dispatchNextUnit(
1628
1629
  );
1629
1630
  result = await Promise.race([sessionPromise, timeoutPromise]);
1630
1631
  } catch (sessionErr) {
1631
- const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
1632
+ const msg = getErrorMessage(sessionErr);
1632
1633
  ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
1633
1634
  throw new Error(`newSession() failed: ${msg}`);
1634
1635
  }
@@ -1704,7 +1705,7 @@ async function dispatchNextUnit(
1704
1705
  const { reorderForCaching } = await import("./prompt-ordering.js");
1705
1706
  finalPrompt = reorderForCaching(finalPrompt);
1706
1707
  } catch (reorderErr) {
1707
- const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
1708
+ const msg = getErrorMessage(reorderErr);
1708
1709
  process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
1709
1710
  }
1710
1711
 
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
8
+ import { getErrorMessage } from "./error-utils.js";
8
9
 
9
10
  export interface InspectData {
10
11
  schemaVersion: number | null;
@@ -84,7 +85,7 @@ export async function handleInspect(ctx: ExtensionCommandContext): Promise<void>
84
85
 
85
86
  ctx.ui.notify(formatInspectOutput(data), "info");
86
87
  } catch (err) {
87
- process.stderr.write(`gsd-db: /gsd inspect failed: ${err instanceof Error ? err.message : String(err)}\n`);
88
+ process.stderr.write(`gsd-db: /gsd inspect failed: ${getErrorMessage(err)}\n`);
88
89
  ctx.ui.notify("Failed to inspect GSD database. Check stderr for details.", "error");
89
90
  }
90
91
  }
@@ -21,6 +21,7 @@ import { loadPrompt } from "./prompt-loader.js";
21
21
  import { gsdRoot } from "./paths.js";
22
22
  import { createGitService, runGit } from "./git-service.js";
23
23
  import { isAutoActive, isAutoPaused } from "./auto.js";
24
+ import { getErrorMessage } from "./error-utils.js";
24
25
 
25
26
  // ─── Helpers ─────────────────────────────────────────────────────────────────
26
27
 
@@ -439,7 +440,7 @@ export async function handleStart(
439
440
  branchCreated = true;
440
441
  }
441
442
  } catch (err) {
442
- const message = err instanceof Error ? err.message : String(err);
443
+ const message = getErrorMessage(err);
443
444
  ctx.ui.notify(
444
445
  `Could not create branch ${branchName}: ${message}. Working on current branch.`,
445
446
  "warning",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Extract a human-readable message from an unknown caught value.
3
+ */
4
+ export function getErrorMessage(err: unknown): string {
5
+ return err instanceof Error ? err.message : String(err);
6
+ }
@@ -12,6 +12,7 @@ import {
12
12
  import type { UnitMetrics } from "./metrics.js";
13
13
  import { gsdRoot } from "./paths.js";
14
14
  import { formatDuration, fileLink } from "../shared/mod.js";
15
+ import { getErrorMessage } from "./error-utils.js";
15
16
 
16
17
  /**
17
18
  * Open a file in the user's default browser.
@@ -226,7 +227,7 @@ export async function handleExport(args: string, ctx: ExtensionCommandContext, b
226
227
  }
227
228
  } catch (err) {
228
229
  ctx.ui.notify(
229
- `HTML export failed: ${err instanceof Error ? err.message : String(err)}`,
230
+ `HTML export failed: ${getErrorMessage(err)}`,
230
231
  "error",
231
232
  );
232
233
  }
@@ -33,6 +33,7 @@ import {
33
33
  nativeAddPaths,
34
34
  } from "./native-git-bridge.js";
35
35
  import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
36
+ import { getErrorMessage } from "./error-utils.js";
36
37
 
37
38
  // ─── Types ─────────────────────────────────────────────────────────────────
38
39
 
@@ -281,7 +282,7 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
281
282
  }).trim();
282
283
  } catch (error) {
283
284
  if (options.allowFailure) return "";
284
- const message = error instanceof Error ? error.message : String(error);
285
+ const message = getErrorMessage(error);
285
286
  throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${filterGitSvnNoise(message)}`);
286
287
  }
287
288
  }
@@ -533,7 +534,7 @@ export class GitServiceImpl {
533
534
  execSync(command, { cwd: this.basePath, stdio: "pipe", encoding: "utf-8" });
534
535
  return { passed: true, skipped: false, command };
535
536
  } catch (err) {
536
- const msg = err instanceof Error ? err.message : String(err);
537
+ const msg = getErrorMessage(err);
537
538
  return { passed: false, skipped: false, command, error: msg };
538
539
  }
539
540
  }
@@ -44,6 +44,7 @@ export {
44
44
  showQueue, handleQueueReorder, showQueueAdd,
45
45
  buildExistingMilestonesContext,
46
46
  } from "./guided-flow-queue.js";
47
+ import { getErrorMessage } from "./error-utils.js";
47
48
 
48
49
  // ─── Commit Instruction Helpers ──────────────────────────────────────────────
49
50
 
@@ -158,9 +159,9 @@ export function checkAutoStartAfterDiscuss(): boolean {
158
159
 
159
160
  pendingAutoStart = null;
160
161
  startAuto(ctx, pi, basePath, false, { step }).catch((err) => {
161
- ctx.ui.notify(`Auto-start failed: ${err instanceof Error ? err.message : String(err)}`, "error");
162
+ ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
162
163
  if (process.env.GSD_DEBUG) console.error('[gsd] auto start error:', err);
163
- debugLog("auto-start-failed", { error: err instanceof Error ? err.message : String(err) });
164
+ debugLog("auto-start-failed", { error: getErrorMessage(err) });
164
165
  });
165
166
  return true;
166
167
  }
@@ -64,6 +64,7 @@ import { pauseAutoForProviderError, classifyProviderError } from "./provider-err
64
64
  import { toPosixPath } from "../shared/mod.js";
65
65
  import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
66
66
  import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
67
+ import { getErrorMessage } from "./error-utils.js";
67
68
 
68
69
  /**
69
70
  * Ensure the GSD database is available, auto-initializing if needed.
@@ -374,7 +375,7 @@ export default function (pi: ExtensionAPI) {
374
375
  details: { operation: "save_decision", id },
375
376
  };
376
377
  } catch (err) {
377
- const msg = err instanceof Error ? err.message : String(err);
378
+ const msg = getErrorMessage(err);
378
379
  process.stderr.write(`gsd-db: gsd_save_decision tool failed: ${msg}\n`);
379
380
  return {
380
381
  content: [{ type: "text" as const, text: `Error saving decision: ${msg}` }],
@@ -445,7 +446,7 @@ export default function (pi: ExtensionAPI) {
445
446
  details: { operation: "update_requirement", id: params.id },
446
447
  };
447
448
  } catch (err) {
448
- const msg = err instanceof Error ? err.message : String(err);
449
+ const msg = getErrorMessage(err);
449
450
  process.stderr.write(`gsd-db: gsd_update_requirement tool failed: ${msg}\n`);
450
451
  return {
451
452
  content: [{ type: "text" as const, text: `Error updating requirement: ${msg}` }],
@@ -525,7 +526,7 @@ export default function (pi: ExtensionAPI) {
525
526
  details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type },
526
527
  };
527
528
  } catch (err) {
528
- const msg = err instanceof Error ? err.message : String(err);
529
+ const msg = getErrorMessage(err);
529
530
  process.stderr.write(`gsd-db: gsd_save_summary tool failed: ${msg}\n`);
530
531
  return {
531
532
  content: [{ type: "text" as const, text: `Error saving artifact: ${msg}` }],
@@ -574,7 +575,7 @@ export default function (pi: ExtensionAPI) {
574
575
  details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, reservedCount: reservedMilestoneIds.size, uniqueEnabled },
575
576
  };
576
577
  } catch (err) {
577
- const msg = err instanceof Error ? err.message : String(err);
578
+ const msg = getErrorMessage(err);
578
579
  return {
579
580
  content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
580
581
  isError: true,
@@ -993,7 +994,7 @@ export default function (pi: ExtensionAPI) {
993
994
  } catch (err) {
994
995
  // Safety net: if handleAgentEnd throws despite its internal try-catch,
995
996
  // ensure auto-mode stops gracefully instead of silently stalling (#381).
996
- const message = err instanceof Error ? err.message : String(err);
997
+ const message = getErrorMessage(err);
997
998
  ctx.ui.notify(
998
999
  `Auto-mode error in agent_end handler: ${message}. Stopping auto-mode.`,
999
1000
  "error",
@@ -16,6 +16,7 @@ import { getEnvApiKey } from "@gsd/pi-ai";
16
16
  import { existsSync, statSync, chmodSync } from "node:fs";
17
17
  import { join, dirname } from "node:path";
18
18
  import { mkdirSync } from "node:fs";
19
+ import { getErrorMessage } from "./error-utils.js";
19
20
 
20
21
  // ─── Provider Registry ─────────────────────────────────────────────────────────
21
22
 
@@ -552,7 +553,7 @@ export async function testProviderKey(
552
553
  return { provider, status: "error", message: `HTTP ${res.status}`, latencyMs };
553
554
  } catch (err) {
554
555
  const latencyMs = Date.now() - start;
555
- const msg = err instanceof Error ? err.message : String(err);
556
+ const msg = getErrorMessage(err);
556
557
  if (msg.includes("timeout") || msg.includes("AbortError")) {
557
558
  return { provider, status: "error", message: "timeout (15s)", latencyMs };
558
559
  }
@@ -16,6 +16,7 @@
16
16
 
17
17
  import * as fs from 'node:fs';
18
18
  import * as path from 'node:path';
19
+ import { getErrorMessage } from "./error-utils.js";
19
20
 
20
21
  // ============================================================================
21
22
  // Type Definitions
@@ -194,7 +195,7 @@ export function parseMarketplaceJson(repoRoot: string):
194
195
  } catch (err) {
195
196
  return {
196
197
  success: false,
197
- error: `Failed to read marketplace.json: ${err instanceof Error ? err.message : String(err)}`
198
+ error: `Failed to read marketplace.json: ${getErrorMessage(err)}`
198
199
  };
199
200
  }
200
201
 
@@ -204,7 +205,7 @@ export function parseMarketplaceJson(repoRoot: string):
204
205
  } catch (err) {
205
206
  return {
206
207
  success: false,
207
- error: `Failed to parse marketplace.json: ${err instanceof Error ? err.message : String(err)}`
208
+ error: `Failed to parse marketplace.json: ${getErrorMessage(err)}`
208
209
  };
209
210
  }
210
211
 
@@ -293,7 +294,7 @@ export function inspectPlugin(
293
294
  }
294
295
  } catch (err) {
295
296
  // Fall back to marketplace inline or derived
296
- result.error = `Failed to parse plugin.json: ${err instanceof Error ? err.message : String(err)}`;
297
+ result.error = `Failed to parse plugin.json: ${getErrorMessage(err)}`;
297
298
  }
298
299
  }
299
300
 
@@ -9,6 +9,7 @@
9
9
  import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { externalGsdRoot } from "./repo-identity.js";
12
+ import { getErrorMessage } from "./error-utils.js";
12
13
 
13
14
  export interface MigrationResult {
14
15
  migrated: boolean;
@@ -47,7 +48,7 @@ export function migrateToExternalState(basePath: string): MigrationResult {
47
48
  return { migrated: false, error: ".gsd exists but is not a directory or symlink" };
48
49
  }
49
50
  } catch (err) {
50
- return { migrated: false, error: `Cannot stat .gsd: ${err instanceof Error ? err.message : String(err)}` };
51
+ return { migrated: false, error: `Cannot stat .gsd: ${getErrorMessage(err)}` };
51
52
  }
52
53
 
53
54
  const externalPath = externalGsdRoot(basePath);
@@ -114,7 +115,7 @@ export function migrateToExternalState(basePath: string): MigrationResult {
114
115
 
115
116
  return {
116
117
  migrated: false,
117
- error: `Migration failed: ${err instanceof Error ? err.message : String(err)}`,
118
+ error: `Migration failed: ${getErrorMessage(err)}`,
118
119
  };
119
120
  }
120
121
  }
@@ -9,6 +9,7 @@ import { randomInt } from "node:crypto";
9
9
  import { readdirSync, existsSync } from "node:fs";
10
10
  import { milestonesDir } from "./paths.js";
11
11
  import { loadQueueOrder, sortByQueueOrder } from "./queue-order.js";
12
+ import { getErrorMessage } from "./error-utils.js";
12
13
 
13
14
  // ─── Regex ──────────────────────────────────────────────────────────────────
14
15
 
@@ -88,7 +89,7 @@ export function findMilestoneIds(basePath: string): string[] {
88
89
  } catch (err) {
89
90
  // Log why milestone scanning failed — silent [] here causes infinite loops (#456)
90
91
  if (existsSync(dir)) {
91
- console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${err instanceof Error ? err.message : String(err)}`);
92
+ console.error(`[gsd] findMilestoneIds: .gsd/milestones/ exists but readdirSync failed — ${getErrorMessage(err)}`);
92
93
  }
93
94
  return [];
94
95
  }
@@ -10,6 +10,7 @@ import { existsSync, readFileSync, unlinkSync, rmSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { GSDError, GSD_GIT_ERROR } from "./errors.js";
12
12
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
13
+ import { getErrorMessage } from "./error-utils.js";
13
14
 
14
15
  // Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
15
16
  // caller explicitly opts into the native helper.
@@ -716,7 +717,7 @@ export function nativeCommit(
716
717
  try {
717
718
  return native.gitCommit(basePath, message, options?.allowEmpty);
718
719
  } catch (e) {
719
- const msg = e instanceof Error ? e.message : String(e);
720
+ const msg = getErrorMessage(e);
720
721
  if (msg.includes("nothing to commit")) return null;
721
722
  throw e;
722
723
  }
@@ -11,6 +11,7 @@ import { mergeMilestoneToMain } from "./auto-worktree.js";
11
11
  import { MergeConflictError } from "./git-service.js";
12
12
  import { removeSessionStatus } from "./session-status-io.js";
13
13
  import type { WorkerInfo } from "./parallel-orchestrator.js";
14
+ import { getErrorMessage } from "./error-utils.js";
14
15
 
15
16
  // ─── Types ─────────────────────────────────────────────────────────────────
16
17
 
@@ -99,7 +100,7 @@ export async function mergeCompletedMilestone(
99
100
  return {
100
101
  milestoneId,
101
102
  success: false,
102
- error: err instanceof Error ? err.message : String(err),
103
+ error: getErrorMessage(err),
103
104
  };
104
105
  }
105
106
  }
@@ -38,6 +38,7 @@ import {
38
38
  analyzeParallelEligibility,
39
39
  type ParallelCandidates,
40
40
  } from "./parallel-eligibility.js";
41
+ import { getErrorMessage } from "./error-utils.js";
41
42
 
42
43
  // ─── Types ─────────────────────────────────────────────────────────────────
43
44
 
@@ -363,7 +364,7 @@ export async function startParallel(
363
364
 
364
365
  started.push(mid);
365
366
  } catch (err) {
366
- const message = err instanceof Error ? err.message : String(err);
367
+ const message = getErrorMessage(err);
367
368
  errors.push({ mid, error: message });
368
369
  }
369
370
  }
@@ -15,6 +15,7 @@ import { join } from "node:path";
15
15
  import { loadPrompt } from "./prompt-loader.js";
16
16
  import { gsdRoot } from "./paths.js";
17
17
  import { createGitService, runGit } from "./git-service.js";
18
+ import { getErrorMessage } from "./error-utils.js";
18
19
 
19
20
  // ─── Quick Task Helpers ───────────────────────────────────────────────────────
20
21
 
@@ -122,7 +123,7 @@ export async function handleQuick(
122
123
  }
123
124
  } catch (err) {
124
125
  // Branch creation failed — continue on current branch
125
- const message = err instanceof Error ? err.message : String(err);
126
+ const message = getErrorMessage(err);
126
127
  ctx.ui.notify(`Could not create branch ${branchName}: ${message}. Working on current branch.`, "warning");
127
128
  }
128
129
  }
@@ -154,12 +154,23 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
154
154
  // Retry acquisition after cleanup
155
155
  const release = lockfile.lockSync(gsdDir, {
156
156
  realpath: false,
157
- stale: 300_000,
157
+ stale: 1_800_000, // 30 minutes — match primary lock settings
158
158
  update: 10_000,
159
+ onCompromised: () => {
160
+ _lockCompromised = true;
161
+ },
159
162
  });
160
163
  _releaseFunction = release;
161
164
  _lockedPath = basePath;
162
165
  _lockPid = process.pid;
166
+
167
+ // Safety net for retry path too
168
+ const retryLockDir = join(gsdDir + ".lock");
169
+ process.once("exit", () => {
170
+ try { if (_releaseFunction) { _releaseFunction(); _releaseFunction = null; } } catch {}
171
+ try { if (existsSync(retryLockDir)) rmSync(retryLockDir, { recursive: true, force: true }); } catch {}
172
+ });
173
+
163
174
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
164
175
  return { acquired: true };
165
176
  } catch {
@@ -91,7 +91,7 @@ test("compression: buildPlanMilestonePrompt minimal drops project/requirements/d
91
91
  // The plan-milestone builder should gate root file inlining on inlineLevel
92
92
  assert.ok(
93
93
  promptsSrc.includes('inlineLevel !== "minimal"') &&
94
- promptsSrc.includes('inlineGsdRootFile(base, "project.md"'),
94
+ promptsSrc.includes("inlineProjectFromDb(base)"),
95
95
  "plan-milestone should conditionally include project.md based on level",
96
96
  );
97
97
  });
@@ -34,6 +34,7 @@ import type { FileLineStat } from "./worktree-manager.js";
34
34
  import { existsSync, realpathSync, readdirSync, rmSync, unlinkSync } from "node:fs";
35
35
  import { nativeMergeAbort } from "./native-git-bridge.js";
36
36
  import { join, sep } from "node:path";
37
+ import { getErrorMessage } from "./error-utils.js";
37
38
 
38
39
  /**
39
40
  * Tracks the original project root so we can switch back.
@@ -370,7 +371,7 @@ async function handleCreate(
370
371
  "info",
371
372
  );
372
373
  } catch (error) {
373
- const msg = error instanceof Error ? error.message : String(error);
374
+ const msg = getErrorMessage(error);
374
375
  ctx.ui.notify(`Failed to create worktree: ${msg}`, "error");
375
376
  }
376
377
  }
@@ -418,7 +419,7 @@ async function handleSwitch(
418
419
  "info",
419
420
  );
420
421
  } catch (error) {
421
- const msg = error instanceof Error ? error.message : String(error);
422
+ const msg = getErrorMessage(error);
422
423
  ctx.ui.notify(`Failed to switch to worktree: ${msg}`, "error");
423
424
  }
424
425
  }
@@ -528,7 +529,7 @@ async function handleList(
528
529
 
529
530
  ctx.ui.notify(lines.join("\n"), "info");
530
531
  } catch (error) {
531
- const msg = error instanceof Error ? error.message : String(error);
532
+ const msg = getErrorMessage(error);
532
533
  ctx.ui.notify(`Failed to list worktrees: ${msg}`, "error");
533
534
  }
534
535
  }
@@ -646,7 +647,7 @@ async function handleMerge(
646
647
  );
647
648
  return;
648
649
  } catch (mergeErr) {
649
- const mergeMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
650
+ const mergeMsg = getErrorMessage(mergeErr);
650
651
  const isConflict = /conflict/i.test(mergeMsg);
651
652
 
652
653
  if (isConflict) {
@@ -703,7 +704,7 @@ async function handleMerge(
703
704
  "info",
704
705
  );
705
706
  } catch (error) {
706
- const msg = error instanceof Error ? error.message : String(error);
707
+ const msg = getErrorMessage(error);
707
708
  ctx.ui.notify(`Failed to start merge: ${msg}`, "error");
708
709
  }
709
710
  }
@@ -746,7 +747,7 @@ async function handleRemove(
746
747
 
747
748
  ctx.ui.notify(`${CLR.ok("✓")} Worktree ${CLR.name(name)} removed ${CLR.muted("(branch deleted)")}.`, "info");
748
749
  } catch (error) {
749
- const msg = error instanceof Error ? error.message : String(error);
750
+ const msg = getErrorMessage(error);
750
751
  ctx.ui.notify(`Failed to remove worktree: ${msg}`, "error");
751
752
  }
752
753
  }
@@ -800,7 +801,7 @@ async function handleRemoveAll(
800
801
  if (failed.length > 0) lines.push(`${CLR.warn("✗")} Failed: ${failed.map(n => CLR.name(n)).join(", ")}`);
801
802
  ctx.ui.notify(lines.join("\n"), failed.length > 0 ? "warning" : "info");
802
803
  } catch (error) {
803
- const msg = error instanceof Error ? error.message : String(error);
804
+ const msg = getErrorMessage(error);
804
805
  ctx.ui.notify(`Failed to remove worktrees: ${msg}`, "error");
805
806
  }
806
807
  }