gsd-pi 2.31.2 → 2.32.0-dev.3d7932c

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 (138) hide show
  1. package/README.md +27 -20
  2. package/dist/cli.js +5 -5
  3. package/dist/resource-loader.js +13 -3
  4. package/dist/resources/extensions/gsd/auto-constants.ts +6 -0
  5. package/dist/resources/extensions/gsd/auto-dashboard.ts +23 -27
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
  7. package/dist/resources/extensions/gsd/auto-dispatch.ts +4 -8
  8. package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
  9. package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
  10. package/dist/resources/extensions/gsd/auto-post-unit.ts +32 -37
  11. package/dist/resources/extensions/gsd/auto-prompts.ts +84 -78
  12. package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
  13. package/dist/resources/extensions/gsd/auto-start.ts +16 -12
  14. package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
  16. package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
  17. package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
  18. package/dist/resources/extensions/gsd/auto-worktree.ts +5 -4
  19. package/dist/resources/extensions/gsd/auto.ts +82 -60
  20. package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
  21. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +5 -6
  22. package/dist/resources/extensions/gsd/commands.ts +19 -0
  23. package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
  24. package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
  25. package/dist/resources/extensions/gsd/dashboard-overlay.ts +28 -0
  26. package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
  27. package/dist/resources/extensions/gsd/doctor-environment.ts +497 -0
  28. package/dist/resources/extensions/gsd/doctor-providers.ts +343 -0
  29. package/dist/resources/extensions/gsd/doctor-types.ts +14 -1
  30. package/dist/resources/extensions/gsd/doctor.ts +6 -0
  31. package/dist/resources/extensions/gsd/error-utils.ts +6 -0
  32. package/dist/resources/extensions/gsd/export.ts +2 -1
  33. package/dist/resources/extensions/gsd/git-service.ts +12 -2
  34. package/dist/resources/extensions/gsd/guided-flow-queue.ts +1 -8
  35. package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
  36. package/dist/resources/extensions/gsd/health-widget.ts +167 -0
  37. package/dist/resources/extensions/gsd/index.ts +18 -5
  38. package/dist/resources/extensions/gsd/key-manager.ts +2 -1
  39. package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  40. package/dist/resources/extensions/gsd/metrics.ts +3 -3
  41. package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
  42. package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
  43. package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
  44. package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
  45. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  46. package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
  47. package/dist/resources/extensions/gsd/preferences-types.ts +8 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.ts +3 -10
  49. package/dist/resources/extensions/gsd/progress-score.ts +273 -0
  50. package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -42
  51. package/dist/resources/extensions/gsd/quick.ts +61 -8
  52. package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
  53. package/dist/resources/extensions/gsd/session-lock.ts +12 -1
  54. package/dist/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +127 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
  57. package/dist/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
  58. package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
  59. package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
  60. package/dist/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
  61. package/dist/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
  62. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
  63. package/dist/resources/extensions/gsd/undo.ts +5 -7
  64. package/dist/resources/extensions/gsd/unit-id.ts +14 -0
  65. package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
  66. package/dist/resources/extensions/gsd/visualizer-data.ts +60 -2
  67. package/dist/resources/extensions/gsd/visualizer-views.ts +54 -0
  68. package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
  69. package/dist/worktree-cli.d.ts +42 -6
  70. package/dist/worktree-cli.js +88 -48
  71. package/package.json +1 -1
  72. package/packages/pi-coding-agent/package.json +1 -1
  73. package/pkg/package.json +1 -1
  74. package/src/resources/extensions/gsd/auto-constants.ts +6 -0
  75. package/src/resources/extensions/gsd/auto-dashboard.ts +23 -27
  76. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
  77. package/src/resources/extensions/gsd/auto-dispatch.ts +4 -8
  78. package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
  79. package/src/resources/extensions/gsd/auto-observability.ts +2 -4
  80. package/src/resources/extensions/gsd/auto-post-unit.ts +32 -37
  81. package/src/resources/extensions/gsd/auto-prompts.ts +84 -78
  82. package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
  83. package/src/resources/extensions/gsd/auto-start.ts +16 -12
  84. package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
  85. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
  86. package/src/resources/extensions/gsd/auto-timers.ts +3 -2
  87. package/src/resources/extensions/gsd/auto-verification.ts +6 -6
  88. package/src/resources/extensions/gsd/auto-worktree.ts +5 -4
  89. package/src/resources/extensions/gsd/auto.ts +82 -60
  90. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  91. package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -6
  92. package/src/resources/extensions/gsd/commands.ts +19 -0
  93. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
  94. package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
  95. package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -0
  96. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  97. package/src/resources/extensions/gsd/doctor-environment.ts +497 -0
  98. package/src/resources/extensions/gsd/doctor-providers.ts +343 -0
  99. package/src/resources/extensions/gsd/doctor-types.ts +14 -1
  100. package/src/resources/extensions/gsd/doctor.ts +6 -0
  101. package/src/resources/extensions/gsd/error-utils.ts +6 -0
  102. package/src/resources/extensions/gsd/export.ts +2 -1
  103. package/src/resources/extensions/gsd/git-service.ts +12 -2
  104. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -8
  105. package/src/resources/extensions/gsd/guided-flow.ts +3 -2
  106. package/src/resources/extensions/gsd/health-widget.ts +167 -0
  107. package/src/resources/extensions/gsd/index.ts +18 -5
  108. package/src/resources/extensions/gsd/key-manager.ts +2 -1
  109. package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
  110. package/src/resources/extensions/gsd/metrics.ts +3 -3
  111. package/src/resources/extensions/gsd/migrate-external.ts +21 -4
  112. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  113. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
  114. package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
  115. package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
  116. package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
  117. package/src/resources/extensions/gsd/preferences-types.ts +8 -0
  118. package/src/resources/extensions/gsd/preferences-validation.ts +3 -10
  119. package/src/resources/extensions/gsd/progress-score.ts +273 -0
  120. package/src/resources/extensions/gsd/prompts/run-uat.md +1 -42
  121. package/src/resources/extensions/gsd/quick.ts +61 -8
  122. package/src/resources/extensions/gsd/repo-identity.ts +22 -1
  123. package/src/resources/extensions/gsd/session-lock.ts +12 -1
  124. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +127 -0
  125. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  126. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
  127. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
  128. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
  129. package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
  130. package/src/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
  131. package/src/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
  132. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
  133. package/src/resources/extensions/gsd/undo.ts +5 -7
  134. package/src/resources/extensions/gsd/unit-id.ts +14 -0
  135. package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
  136. package/src/resources/extensions/gsd/visualizer-data.ts +60 -2
  137. package/src/resources/extensions/gsd/visualizer-views.ts +54 -0
  138. package/src/resources/extensions/gsd/worktree-command.ts +8 -7
@@ -9,6 +9,7 @@ import { deriveState } from "./state.js";
9
9
  import { invalidateAllCaches } from "./cache.js";
10
10
  import { gsdRoot, resolveTasksDir, resolveSlicePath, buildTaskFileName } from "./paths.js";
11
11
  import { sendDesktopNotification } from "./notifications.js";
12
+ import { parseUnitId } from "./unit-id.js";
12
13
 
13
14
  /**
14
15
  * Undo the last completed unit: revert git commits, remove from completed-units,
@@ -62,11 +63,10 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
62
63
  writeFileSync(completedKeysFile, JSON.stringify(keys), "utf-8");
63
64
 
64
65
  // 3. Delete summary artifact
65
- const parts = unitId.split("/");
66
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
66
67
  let summaryRemoved = false;
67
- if (parts.length === 3) {
68
+ if (mid && sid && tid) {
68
69
  // Task-level: M001/S01/T01
69
- const [mid, sid, tid] = parts;
70
70
  const tasksDir = resolveTasksDir(basePath, mid, sid);
71
71
  if (tasksDir) {
72
72
  const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
@@ -75,9 +75,8 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
75
75
  summaryRemoved = true;
76
76
  }
77
77
  }
78
- } else if (parts.length === 2) {
78
+ } else if (mid && sid) {
79
79
  // Slice-level: M001/S01
80
- const [mid, sid] = parts;
81
80
  const slicePath = resolveSlicePath(basePath, mid, sid);
82
81
  if (slicePath) {
83
82
  // Try common summary filenames
@@ -93,8 +92,7 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
93
92
 
94
93
  // 4. Uncheck task in PLAN if execute-task
95
94
  let planUpdated = false;
96
- if (unitType === "execute-task" && parts.length === 3) {
97
- const [mid, sid, tid] = parts;
95
+ if (unitType === "execute-task" && mid && sid && tid) {
98
96
  planUpdated = uncheckTaskInPlan(basePath, mid, sid, tid);
99
97
  }
100
98
 
@@ -0,0 +1,14 @@
1
+ // GSD Extension — Unit ID Parsing
2
+ // Centralizes the milestone/slice/task decomposition of unit ID strings.
3
+
4
+ export interface ParsedUnitId {
5
+ milestone: string;
6
+ slice?: string;
7
+ task?: string;
8
+ }
9
+
10
+ /** Parse a unit ID string (e.g. "M1/S1/T1") into its milestone, slice, and task components. */
11
+ export function parseUnitId(unitId: string): ParsedUnitId {
12
+ const [milestone, slice, task] = unitId.split("/");
13
+ return { milestone: milestone!, slice, task };
14
+ }
@@ -9,6 +9,7 @@ import {
9
9
  } from "./paths.js";
10
10
  import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
11
11
  import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
12
+ import { parseUnitId } from "./unit-id.js";
12
13
 
13
14
  export type UnitRuntimePhase =
14
15
  | "dispatched"
@@ -131,7 +132,7 @@ export async function inspectExecuteTaskDurability(
131
132
  basePath: string,
132
133
  unitId: string,
133
134
  ): Promise<ExecuteTaskRecoveryStatus | null> {
134
- const [mid, sid, tid] = unitId.split("/");
135
+ const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
135
136
  if (!mid || !sid || !tid) return null;
136
137
 
137
138
  const planAbs = resolveSliceFile(basePath, mid, sid, "PLAN");
@@ -18,6 +18,9 @@ import {
18
18
  } from './metrics.js';
19
19
  import { loadAllCaptures, countPendingCaptures } from './captures.js';
20
20
  import { loadEffectiveGSDPreferences } from './preferences.js';
21
+ import { runProviderChecks, type ProviderCheckResult } from './doctor-providers.js';
22
+ import { generateSkillHealthReport } from './skill-health.js';
23
+ import { runEnvironmentChecks, type EnvironmentCheckResult } from './doctor-environment.js';
21
24
 
22
25
  import type { Phase } from './types.js';
23
26
  import type { CaptureEntry } from './captures.js';
@@ -142,6 +145,22 @@ export interface CapturesInfo {
142
145
  totalCount: number;
143
146
  }
144
147
 
148
+ export interface ProviderStatusSummary {
149
+ name: string;
150
+ label: string;
151
+ category: string;
152
+ ok: boolean;
153
+ required: boolean;
154
+ message: string;
155
+ }
156
+
157
+ export interface SkillSummaryInfo {
158
+ total: number;
159
+ warningCount: number;
160
+ criticalCount: number;
161
+ topIssue: string | null;
162
+ }
163
+
145
164
  export interface HealthInfo {
146
165
  budgetCeiling: number | undefined;
147
166
  tokenProfile: string;
@@ -152,6 +171,9 @@ export interface HealthInfo {
152
171
  toolCalls: number;
153
172
  assistantMessages: number;
154
173
  userMessages: number;
174
+ providers: ProviderStatusSummary[];
175
+ skillSummary: SkillSummaryInfo;
176
+ environmentIssues: import("./doctor-environment.js").EnvironmentCheckResult[];
155
177
  }
156
178
 
157
179
  export interface VisualizerData {
@@ -538,7 +560,7 @@ function loadKnowledge(basePath: string): KnowledgeInfo {
538
560
 
539
561
  // ─── Health Loader ────────────────────────────────────────────────────────────
540
562
 
541
- function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthInfo {
563
+ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath: string): HealthInfo {
542
564
  const prefs = loadEffectiveGSDPreferences();
543
565
  const budgetCeiling = prefs?.preferences?.budget_ceiling;
544
566
  const tokenProfile = prefs?.preferences?.token_profile ?? 'standard';
@@ -553,6 +575,39 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthI
553
575
  const tierBreakdown = aggregateByTier(units);
554
576
  const tierSavingsLine = formatTierSavings(units);
555
577
 
578
+ // Provider checks — fast (auth.json + env vars only, no network)
579
+ let providers: ProviderStatusSummary[] = [];
580
+ try {
581
+ providers = runProviderChecks().map((r: ProviderCheckResult) => ({
582
+ name: r.name,
583
+ label: r.label,
584
+ category: r.category,
585
+ ok: r.status === "ok" || r.status === "unconfigured",
586
+ required: r.required,
587
+ message: r.message,
588
+ }));
589
+ } catch { /* non-fatal */ }
590
+
591
+ // Skill health summary
592
+ let skillSummary: SkillSummaryInfo = { total: 0, warningCount: 0, criticalCount: 0, topIssue: null };
593
+ try {
594
+ const report = generateSkillHealthReport(basePath);
595
+ const warnings = report.suggestions.filter(s => s.severity === "warning");
596
+ const criticals = report.suggestions.filter(s => s.severity === "critical");
597
+ skillSummary = {
598
+ total: report.skills.length,
599
+ warningCount: warnings.length,
600
+ criticalCount: criticals.length,
601
+ topIssue: report.suggestions[0]?.message ?? null,
602
+ };
603
+ } catch { /* non-fatal */ }
604
+
605
+ // Environment issues (from doctor-environment.ts, #1221)
606
+ let environmentIssues: EnvironmentCheckResult[] = [];
607
+ try {
608
+ environmentIssues = runEnvironmentChecks(basePath).filter(r => r.status !== "ok");
609
+ } catch { /* non-fatal */ }
610
+
556
611
  return {
557
612
  budgetCeiling,
558
613
  tokenProfile,
@@ -563,6 +618,9 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthI
563
618
  toolCalls: totals?.toolCalls ?? 0,
564
619
  assistantMessages: totals?.assistantMessages ?? 0,
565
620
  userMessages: totals?.userMessages ?? 0,
621
+ providers,
622
+ skillSummary,
623
+ environmentIssues,
566
624
  };
567
625
  }
568
626
 
@@ -780,7 +838,7 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
780
838
  totalCount: allCaptures.length,
781
839
  };
782
840
 
783
- const health = loadHealth(units, totals);
841
+ const health = loadHealth(units, totals, basePath);
784
842
  const stats = buildVisualizerStats(milestones, changelog.entries);
785
843
  const discussion = loadDiscussionState(basePath, milestones);
786
844
 
@@ -1113,5 +1113,59 @@ export function renderHealthView(
1113
1113
  lines.push(` Tool calls: ${th.fg("text", String(health.toolCalls))}`);
1114
1114
  lines.push(` Messages: ${th.fg("text", String(health.assistantMessages))} sent / ${th.fg("text", String(health.userMessages))} received`);
1115
1115
 
1116
+ // Environment section — issues only (from doctor-environment.ts, #1221)
1117
+ if (health.environmentIssues?.length > 0) {
1118
+ lines.push("");
1119
+ lines.push(th.fg("accent", th.bold("Environment")));
1120
+ lines.push("");
1121
+ for (const r of health.environmentIssues) {
1122
+ const icon = r.status === "error" ? th.fg("error", "✗") : th.fg("warning", "⚠");
1123
+ lines.push(` ${icon} ${th.fg("text", r.message)}`);
1124
+ if (r.detail) lines.push(` ${th.fg("dim", r.detail)}`);
1125
+ }
1126
+ }
1127
+
1128
+ // Providers section
1129
+ if (health.providers?.length > 0) {
1130
+ lines.push("");
1131
+ lines.push(th.fg("accent", th.bold("Providers")));
1132
+ lines.push("");
1133
+ const categoryOrder = ["llm", "remote", "search", "tool"];
1134
+ const categoryLabels: Record<string, string> = { llm: "LLM", remote: "Notifications", search: "Search", tool: "Tools" };
1135
+ const grouped = new Map<string, typeof health.providers>();
1136
+ for (const p of health.providers) {
1137
+ const cat = p.category;
1138
+ if (!grouped.has(cat)) grouped.set(cat, []);
1139
+ grouped.get(cat)!.push(p);
1140
+ }
1141
+ for (const cat of categoryOrder) {
1142
+ const items = grouped.get(cat);
1143
+ if (!items || items.length === 0) continue;
1144
+ lines.push(` ${th.fg("dim", categoryLabels[cat] ?? cat)}`);
1145
+ for (const p of items) {
1146
+ const icon = p.ok ? th.fg("success", "✓") : th.fg("error", "✗");
1147
+ const msg = p.ok ? th.fg("dim", p.message) : th.fg("text", p.message);
1148
+ lines.push(` ${icon} ${msg}`);
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ // Skills section
1154
+ if (health.skillSummary?.total > 0) {
1155
+ lines.push("");
1156
+ lines.push(th.fg("accent", th.bold("Skills")));
1157
+ lines.push("");
1158
+ const { total, warningCount, criticalCount, topIssue } = health.skillSummary;
1159
+ const issueColor = criticalCount > 0 ? "error" : warningCount > 0 ? "warning" : "success";
1160
+ const issueTag = criticalCount > 0
1161
+ ? `${criticalCount} critical`
1162
+ : warningCount > 0
1163
+ ? `${warningCount} warning${warningCount > 1 ? "s" : ""}`
1164
+ : "all healthy";
1165
+ lines.push(` ${th.fg("text", String(total))} skills tracked · ${th.fg(issueColor, issueTag)}`);
1166
+ if (topIssue) lines.push(` ${th.fg("warning", "⚠")} ${th.fg("dim", topIssue)}`);
1167
+ lines.push(` ${th.fg("dim", "→ /gsd skill-health for full report")}`);
1168
+ }
1169
+
1116
1170
  return lines;
1117
1171
  }
@@ -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
  }