gsd-pi 0.1.0

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 (128) hide show
  1. package/README.md +341 -0
  2. package/dist/app-paths.d.ts +4 -0
  3. package/dist/app-paths.js +6 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +35 -0
  6. package/dist/loader.d.ts +2 -0
  7. package/dist/loader.js +69 -0
  8. package/dist/modes/interactive/theme/dark.json +85 -0
  9. package/dist/modes/interactive/theme/light.json +84 -0
  10. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  11. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  12. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  13. package/dist/modes/interactive/theme/theme.js +949 -0
  14. package/dist/modes/interactive/theme/theme.js.map +1 -0
  15. package/dist/resource-loader.d.ts +22 -0
  16. package/dist/resource-loader.js +48 -0
  17. package/dist/wizard.d.ts +20 -0
  18. package/dist/wizard.js +132 -0
  19. package/package.json +39 -0
  20. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  21. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  22. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  23. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  24. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  25. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  26. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  27. package/pkg/package.json +8 -0
  28. package/scripts/postinstall.js +10 -0
  29. package/src/resources/AGENTS.md +204 -0
  30. package/src/resources/GSD-WORKFLOW.md +661 -0
  31. package/src/resources/agents/researcher.md +29 -0
  32. package/src/resources/agents/scout.md +56 -0
  33. package/src/resources/agents/worker.md +31 -0
  34. package/src/resources/extensions/ask-user-questions.ts +200 -0
  35. package/src/resources/extensions/bg-shell/index.ts +2554 -0
  36. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  37. package/src/resources/extensions/browser-tools/core.js +1057 -0
  38. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  39. package/src/resources/extensions/browser-tools/package.json +20 -0
  40. package/src/resources/extensions/context7/index.ts +428 -0
  41. package/src/resources/extensions/context7/package.json +11 -0
  42. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  43. package/src/resources/extensions/gsd/activity-log.ts +48 -0
  44. package/src/resources/extensions/gsd/auto.ts +2032 -0
  45. package/src/resources/extensions/gsd/commands.ts +292 -0
  46. package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
  47. package/src/resources/extensions/gsd/dashboard-overlay.ts +516 -0
  48. package/src/resources/extensions/gsd/docs/preferences-reference.md +103 -0
  49. package/src/resources/extensions/gsd/doctor.ts +683 -0
  50. package/src/resources/extensions/gsd/files.ts +730 -0
  51. package/src/resources/extensions/gsd/gitignore.ts +104 -0
  52. package/src/resources/extensions/gsd/guided-flow.ts +800 -0
  53. package/src/resources/extensions/gsd/index.ts +418 -0
  54. package/src/resources/extensions/gsd/metrics.ts +372 -0
  55. package/src/resources/extensions/gsd/observability-validator.ts +408 -0
  56. package/src/resources/extensions/gsd/package.json +11 -0
  57. package/src/resources/extensions/gsd/paths.ts +308 -0
  58. package/src/resources/extensions/gsd/preferences.ts +600 -0
  59. package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
  60. package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  61. package/src/resources/extensions/gsd/prompts/complete-slice.md +27 -0
  62. package/src/resources/extensions/gsd/prompts/discuss.md +151 -0
  63. package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  64. package/src/resources/extensions/gsd/prompts/execute-task.md +64 -0
  65. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
  66. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
  67. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
  68. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
  69. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
  70. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
  71. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
  72. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  73. package/src/resources/extensions/gsd/prompts/plan-milestone.md +47 -0
  74. package/src/resources/extensions/gsd/prompts/plan-slice.md +63 -0
  75. package/src/resources/extensions/gsd/prompts/queue.md +85 -0
  76. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  77. package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  78. package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  79. package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
  80. package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
  81. package/src/resources/extensions/gsd/prompts/system.md +220 -0
  82. package/src/resources/extensions/gsd/session-forensics.ts +487 -0
  83. package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
  84. package/src/resources/extensions/gsd/state.ts +439 -0
  85. package/src/resources/extensions/gsd/templates/context.md +76 -0
  86. package/src/resources/extensions/gsd/templates/decisions.md +8 -0
  87. package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  88. package/src/resources/extensions/gsd/templates/plan.md +133 -0
  89. package/src/resources/extensions/gsd/templates/preferences.md +15 -0
  90. package/src/resources/extensions/gsd/templates/project.md +31 -0
  91. package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
  92. package/src/resources/extensions/gsd/templates/requirements.md +81 -0
  93. package/src/resources/extensions/gsd/templates/research.md +46 -0
  94. package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
  95. package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
  96. package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
  97. package/src/resources/extensions/gsd/templates/state.md +19 -0
  98. package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
  99. package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
  100. package/src/resources/extensions/gsd/templates/uat.md +54 -0
  101. package/src/resources/extensions/gsd/types.ts +159 -0
  102. package/src/resources/extensions/gsd/unit-runtime.ts +162 -0
  103. package/src/resources/extensions/gsd/workspace-index.ts +203 -0
  104. package/src/resources/extensions/gsd/worktree.ts +182 -0
  105. package/src/resources/extensions/plan-mode/README.md +65 -0
  106. package/src/resources/extensions/plan-mode/index.ts +521 -0
  107. package/src/resources/extensions/plan-mode/utils.ts +168 -0
  108. package/src/resources/extensions/search-the-web/cache.ts +70 -0
  109. package/src/resources/extensions/search-the-web/format.ts +134 -0
  110. package/src/resources/extensions/search-the-web/http.ts +147 -0
  111. package/src/resources/extensions/search-the-web/index.ts +46 -0
  112. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +374 -0
  113. package/src/resources/extensions/search-the-web/tool-search.ts +424 -0
  114. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  115. package/src/resources/extensions/shared/interview-ui.ts +613 -0
  116. package/src/resources/extensions/shared/next-action-ui.ts +197 -0
  117. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  118. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  119. package/src/resources/extensions/shared/ui.ts +400 -0
  120. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  121. package/src/resources/extensions/slash-commands/audit.ts +88 -0
  122. package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
  123. package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  124. package/src/resources/extensions/slash-commands/gsd-run.ts +34 -0
  125. package/src/resources/extensions/slash-commands/index.ts +12 -0
  126. package/src/resources/extensions/subagent/agents.ts +126 -0
  127. package/src/resources/extensions/subagent/index.ts +1021 -0
  128. package/src/resources/extensions/worktree/index.ts +420 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Worktree Extension — /wt
3
+ *
4
+ * Manage git worktrees for parallel extension development.
5
+ * Each worktree gets its own branch, isolated from main.
6
+ *
7
+ * Commands:
8
+ * /wt new <name> — create worktree + branch, switch agent cwd there
9
+ * /wt ls — list all worktrees with status
10
+ * /wt switch <name>— switch agent cwd to a worktree (or main)
11
+ * /wt merge <name> — squash merge into main, delete worktree + branch
12
+ * /wt rm <name> — delete worktree + branch (discard changes)
13
+ * /wt status — show current worktree and git status summary
14
+ */
15
+
16
+ import { execSync } from "node:child_process";
17
+ import * as path from "node:path";
18
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
19
+
20
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
21
+
22
+ const REPO_ROOT = "/Users/lexchristopherson/.pi";
23
+ const WORKTREES_DIR = path.join(REPO_ROOT, "worktrees");
24
+
25
+ function run(cmd: string, cwd = REPO_ROOT): string {
26
+ try {
27
+ return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
28
+ } catch (e: unknown) {
29
+ const err = e as { stderr?: string; message?: string };
30
+ throw new Error(err.stderr?.trim() || err.message || String(e));
31
+ }
32
+ }
33
+
34
+ interface WorktreeInfo {
35
+ name: string;
36
+ path: string;
37
+ branch: string;
38
+ isMain: boolean;
39
+ commitCount: number;
40
+ dirty: boolean;
41
+ }
42
+
43
+ function listWorktrees(): WorktreeInfo[] {
44
+ const raw = run("git worktree list --porcelain");
45
+ const entries = raw.split("\n\n").filter(Boolean);
46
+
47
+ return entries.map((entry) => {
48
+ const lines = entry.split("\n");
49
+ const wtPath = lines.find((l) => l.startsWith("worktree "))?.replace("worktree ", "") ?? "";
50
+ const branch = lines.find((l) => l.startsWith("branch "))?.replace("branch refs/heads/", "") ?? "(detached)";
51
+ const isMain = wtPath === REPO_ROOT;
52
+
53
+ let commitCount = 0;
54
+ let dirty = false;
55
+
56
+ if (!isMain) {
57
+ try {
58
+ const count = run(`git rev-list --count main..${branch}`, wtPath);
59
+ commitCount = parseInt(count, 10) || 0;
60
+ const status = run("git status --porcelain", wtPath);
61
+ dirty = status.length > 0;
62
+ } catch {
63
+ // branch might not exist yet
64
+ }
65
+ }
66
+
67
+ const name = isMain ? "main" : path.basename(wtPath);
68
+ return { name, path: wtPath, branch, isMain, commitCount, dirty };
69
+ });
70
+ }
71
+
72
+ function getCurrentWorktreeName(cwd: string): string {
73
+ if (cwd === REPO_ROOT) return "main";
74
+ const worktrees = listWorktrees();
75
+ const match = worktrees.find((w) => w.path === cwd);
76
+ return match?.name ?? "main";
77
+ }
78
+
79
+ function formatWorktreeList(worktrees: WorktreeInfo[], currentPath: string): string[] {
80
+ return worktrees.map((w) => {
81
+ const isCurrent = w.path === currentPath;
82
+ const marker = isCurrent ? "→ " : " ";
83
+ const dirty = w.dirty ? " *" : "";
84
+ const commits = w.isMain ? "" : ` (${w.commitCount} commit${w.commitCount !== 1 ? "s" : ""} ahead)`;
85
+ return `${marker}${w.name}${dirty}${commits} [${w.branch}]`;
86
+ });
87
+ }
88
+
89
+ function updateStatus(ctx: ExtensionContext, currentWorktree: string): void {
90
+ if (currentWorktree === "main") {
91
+ ctx.ui.setStatus("worktree", undefined);
92
+ } else {
93
+ ctx.ui.setStatus("worktree", ctx.ui.theme.fg("accent", `⑂ ${currentWorktree}`));
94
+ }
95
+ }
96
+
97
+ function getWorktreeCdCommand(wtPath: string): string {
98
+ return `cd ${JSON.stringify(wtPath)}`;
99
+ }
100
+
101
+ function getWorktreeExtensionLaunchCommand(wtPath: string): string {
102
+ return `pi -e ${JSON.stringify(wtPath)}`;
103
+ }
104
+
105
+ function formatWorktreeHandoff(wtPath: string): string[] {
106
+ return [
107
+ "Human terminal handoff:",
108
+ ` ${getWorktreeCdCommand(wtPath)}`,
109
+ "",
110
+ "To run pi against the worktree copy instead of main:",
111
+ ` ${getWorktreeExtensionLaunchCommand(wtPath)}`,
112
+ ];
113
+ }
114
+
115
+ // ─── Extension ────────────────────────────────────────────────────────────────
116
+
117
+ export default function worktreeExtension(pi: ExtensionAPI): void {
118
+ // Track current cwd — starts at repo root
119
+ let currentCwd = REPO_ROOT;
120
+
121
+ // Inject cwd into every bash call so the agent operates in the right worktree
122
+ pi.on("tool_call", async (event) => {
123
+ if (event.toolName !== "bash" || currentCwd === REPO_ROOT) return;
124
+ // Prepend cd so all bash commands run in the active worktree
125
+ const cmd = event.input.command as string;
126
+ if (!cmd.startsWith(`cd ${currentCwd}`)) {
127
+ return {
128
+ input: {
129
+ ...event.input,
130
+ command: `cd ${currentCwd} && ${cmd}`,
131
+ },
132
+ };
133
+ }
134
+ });
135
+
136
+ // Restore cwd on session resume
137
+ pi.on("session_start", async (_event, ctx) => {
138
+ const entries = ctx.sessionManager.getEntries();
139
+ const last = [...entries]
140
+ .reverse()
141
+ .find(
142
+ (e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "worktree-cwd",
143
+ ) as { data?: { cwd: string } } | undefined;
144
+ if (last?.data?.cwd) {
145
+ currentCwd = last.data?.cwd;
146
+ }
147
+ updateStatus(ctx, getCurrentWorktreeName(currentCwd));
148
+ });
149
+
150
+ // ── /wt command ─────────────────────────────────────────────────────────────
151
+
152
+ pi.registerCommand("wt", {
153
+ description: "Manage git worktrees: /wt new|ls|switch|merge|rm|status [name]",
154
+
155
+ getArgumentCompletions: (prefix: string) => {
156
+ const subcommands = ["new", "ls", "switch", "merge", "rm", "status"];
157
+ const parts = prefix.trim().split(/\s+/);
158
+
159
+ if (parts.length <= 1) {
160
+ return subcommands
161
+ .filter((s) => s.startsWith(parts[0] ?? ""))
162
+ .map((s) => ({ value: s, label: s }));
163
+ }
164
+
165
+ const sub = parts[0];
166
+ if (["switch", "merge", "rm"].includes(sub)) {
167
+ try {
168
+ const worktrees = listWorktrees();
169
+ const namePrefix = parts[1] ?? "";
170
+ return worktrees
171
+ .filter((w) => !w.isMain && w.name.startsWith(namePrefix))
172
+ .map((w) => ({ value: `${sub} ${w.name}`, label: w.name }));
173
+ } catch {
174
+ return [];
175
+ }
176
+ }
177
+
178
+ return [];
179
+ },
180
+
181
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
182
+ const parts = args.trim().split(/\s+/);
183
+ const sub = parts[0];
184
+ const name = parts[1];
185
+
186
+ await ctx.waitForIdle();
187
+
188
+ // ── ls ────────────────────────────────────────────────────────────────
189
+ if (!sub || sub === "ls") {
190
+ try {
191
+ const worktrees = listWorktrees();
192
+ const lines = formatWorktreeList(worktrees, currentCwd);
193
+ const header = `Worktrees (${worktrees.length}):`;
194
+ ctx.ui.notify([header, "", ...lines].join("\n"), "info");
195
+ } catch (e) {
196
+ ctx.ui.notify(`Error: ${(e as Error).message}`, "error");
197
+ }
198
+ return;
199
+ }
200
+
201
+ // ── status ────────────────────────────────────────────────────────────
202
+ if (sub === "status") {
203
+ try {
204
+ const wtName = getCurrentWorktreeName(currentCwd);
205
+ const status = run("git status --short", currentCwd);
206
+ const branch = run("git branch --show-current", currentCwd);
207
+ const lines = [
208
+ `Current worktree: ${wtName}`,
209
+ `Branch: ${branch}`,
210
+ `Path: ${currentCwd}`,
211
+ "",
212
+ ...(wtName === "main" ? [] : [...formatWorktreeHandoff(currentCwd), ""]),
213
+ status || "(clean)",
214
+ ];
215
+ ctx.ui.notify(lines.join("\n"), "info");
216
+ } catch (e) {
217
+ ctx.ui.notify(`Error: ${(e as Error).message}`, "error");
218
+ }
219
+ return;
220
+ }
221
+
222
+ // ── new ───────────────────────────────────────────────────────────────
223
+ if (sub === "new") {
224
+ let wtName = name;
225
+ if (!wtName) {
226
+ wtName = (await ctx.ui.input("Worktree name:", "my-experiment")) ?? undefined;
227
+ if (!wtName) return;
228
+ }
229
+ wtName = wtName.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
230
+
231
+ const wtPath = path.join(WORKTREES_DIR, wtName);
232
+ try {
233
+ // Check if already exists
234
+ const existing = listWorktrees();
235
+ if (existing.some((w) => w.name === wtName)) {
236
+ ctx.ui.notify(`Worktree '${wtName}' already exists. Use /wt switch ${wtName}`, "error");
237
+ return;
238
+ }
239
+
240
+ // Create from current HEAD of main
241
+ run(`git worktree add ${wtPath} -b ${wtName}`, REPO_ROOT);
242
+
243
+ // Switch agent cwd
244
+ currentCwd = wtPath;
245
+ pi.appendEntry("worktree-cwd", { cwd: currentCwd });
246
+ updateStatus(ctx, wtName);
247
+
248
+ ctx.ui.notify(
249
+ [
250
+ `✓ Created worktree '${wtName}'`,
251
+ ` Branch: ${wtName}`,
252
+ ` Path: ${wtPath}`,
253
+ ` Agent is now working in this worktree.`,
254
+ "",
255
+ ...formatWorktreeHandoff(wtPath),
256
+ "",
257
+ ` Use /wt merge ${wtName} when done.`,
258
+ ].join("\n"),
259
+ "info",
260
+ );
261
+ } catch (e) {
262
+ ctx.ui.notify(`Failed to create worktree: ${(e as Error).message}`, "error");
263
+ }
264
+ return;
265
+ }
266
+
267
+ // ── switch ────────────────────────────────────────────────────────────
268
+ if (sub === "switch") {
269
+ if (!name) {
270
+ ctx.ui.notify("Usage: /wt switch <name> (use 'main' to go back)", "error");
271
+ return;
272
+ }
273
+
274
+ if (name === "main") {
275
+ currentCwd = REPO_ROOT;
276
+ pi.appendEntry("worktree-cwd", { cwd: currentCwd });
277
+ updateStatus(ctx, "main");
278
+ ctx.ui.notify("Switched to main repo.", "info");
279
+ return;
280
+ }
281
+
282
+ const worktrees = listWorktrees();
283
+ const target = worktrees.find((w) => w.name === name);
284
+ if (!target) {
285
+ ctx.ui.notify(`No worktree named '${name}'. Use /wt ls to see all.`, "error");
286
+ return;
287
+ }
288
+
289
+ currentCwd = target.path;
290
+ pi.appendEntry("worktree-cwd", { cwd: currentCwd });
291
+ updateStatus(ctx, name);
292
+ ctx.ui.notify(
293
+ [
294
+ `Switched to worktree '${name}' (${target.branch})`,
295
+ ` Path: ${target.path}`,
296
+ "",
297
+ ...formatWorktreeHandoff(target.path),
298
+ ].join("\n"),
299
+ "info",
300
+ );
301
+ return;
302
+ }
303
+
304
+ // ── merge ─────────────────────────────────────────────────────────────
305
+ if (sub === "merge") {
306
+ const wtName = name ?? getCurrentWorktreeName(currentCwd);
307
+ if (wtName === "main") {
308
+ ctx.ui.notify("Can't merge main into itself.", "error");
309
+ return;
310
+ }
311
+
312
+ const worktrees = listWorktrees();
313
+ const target = worktrees.find((w) => w.name === wtName);
314
+ if (!target) {
315
+ ctx.ui.notify(`No worktree named '${wtName}'.`, "error");
316
+ return;
317
+ }
318
+
319
+ // Check for uncommitted changes
320
+ const dirty = run("git status --porcelain", target.path);
321
+ if (dirty) {
322
+ const ok = await ctx.ui.confirm(
323
+ `'${wtName}' has uncommitted changes`,
324
+ "These will be lost on merge. Continue?",
325
+ );
326
+ if (!ok) return;
327
+ }
328
+
329
+ // Get commit log for the editor prefill
330
+ let commitInfo = "";
331
+ try {
332
+ const count = run(`git rev-list --count main..${wtName}`, REPO_ROOT);
333
+ const log = run(`git log --oneline main..${wtName}`, REPO_ROOT);
334
+ commitInfo = `\n\n# Squashing ${count} commit(s):\n# ${log.split("\n").join("\n# ")}`;
335
+ } catch {}
336
+
337
+ // Ask for squash commit message
338
+ const msg = await ctx.ui.editor(
339
+ `Squash merge '${wtName}' → main\n(Edit the commit message)`,
340
+ `feat(${wtName}): ${commitInfo}`,
341
+ );
342
+ if (!msg?.trim()) {
343
+ ctx.ui.notify("Merge cancelled — no commit message.", "info");
344
+ return;
345
+ }
346
+
347
+ try {
348
+ // If currently in this worktree, switch to main first
349
+ if (currentCwd === target.path) {
350
+ currentCwd = REPO_ROOT;
351
+ pi.appendEntry("worktree-cwd", { cwd: currentCwd });
352
+ updateStatus(ctx, "main");
353
+ }
354
+
355
+ // Squash merge
356
+ run(`git merge --squash ${wtName}`, REPO_ROOT);
357
+ run(`git commit -m ${JSON.stringify(msg.trim())}`, REPO_ROOT);
358
+
359
+ // Clean up worktree and branch
360
+ run(`git worktree remove --force ${target.path}`, REPO_ROOT);
361
+ run(`git branch -D ${wtName}`, REPO_ROOT);
362
+
363
+ ctx.ui.notify(
364
+ [`✓ Squash merged '${wtName}' → main`, ` Branch and worktree deleted.`, ` Now on: main`].join("\n"),
365
+ "info",
366
+ );
367
+ } catch (e) {
368
+ ctx.ui.notify(`Merge failed: ${(e as Error).message}`, "error");
369
+ }
370
+ return;
371
+ }
372
+
373
+ // ── rm ────────────────────────────────────────────────────────────────
374
+ if (sub === "rm") {
375
+ const wtName = name ?? getCurrentWorktreeName(currentCwd);
376
+ if (wtName === "main") {
377
+ ctx.ui.notify("Can't remove main.", "error");
378
+ return;
379
+ }
380
+
381
+ const worktrees = listWorktrees();
382
+ const target = worktrees.find((w) => w.name === wtName);
383
+ if (!target) {
384
+ ctx.ui.notify(`No worktree named '${wtName}'.`, "error");
385
+ return;
386
+ }
387
+
388
+ let warn = `Delete worktree '${wtName}' and branch '${target.branch}'?`;
389
+ if (target.commitCount > 0) {
390
+ warn += `\n\n⚠️ ${target.commitCount} commit${target.commitCount !== 1 ? "s" : ""} will be lost.`;
391
+ }
392
+
393
+ const ok = await ctx.ui.confirm(warn, "This cannot be undone.");
394
+ if (!ok) return;
395
+
396
+ try {
397
+ if (currentCwd === target.path) {
398
+ currentCwd = REPO_ROOT;
399
+ pi.appendEntry("worktree-cwd", { cwd: currentCwd });
400
+ updateStatus(ctx, "main");
401
+ }
402
+
403
+ run(`git worktree remove --force ${target.path}`, REPO_ROOT);
404
+ run(`git branch -D ${target.branch}`, REPO_ROOT);
405
+
406
+ ctx.ui.notify(`✓ Deleted worktree '${wtName}' and branch '${target.branch}'.`, "info");
407
+ } catch (e) {
408
+ ctx.ui.notify(`Failed: ${(e as Error).message}`, "error");
409
+ }
410
+ return;
411
+ }
412
+
413
+ // ── fallthrough ───────────────────────────────────────────────────────
414
+ ctx.ui.notify(
415
+ ["Usage: /wt <subcommand> [name]", "", " new [name] create worktree + branch, switch there", " ls list all worktrees", " switch <name> switch agent cwd (use 'main' to go back)", " merge [name] squash merge → main, delete worktree + branch", " rm [name] discard worktree + branch", " status show current worktree git status"].join("\n"),
416
+ "info",
417
+ );
418
+ },
419
+ });
420
+ }