@akiojin/gwt 4.7.0 → 4.9.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 (69) hide show
  1. package/README.ja.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/claude.js +1 -1
  4. package/dist/claude.js.map +1 -1
  5. package/dist/cli/ui/components/App.js +1 -1
  6. package/dist/cli/ui/components/App.js.map +1 -1
  7. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  8. package/dist/cli/ui/components/screens/BranchListScreen.js +8 -7
  9. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  10. package/dist/cli/ui/components/screens/LogDatePickerScreen.js +1 -1
  11. package/dist/cli/ui/components/screens/LogDatePickerScreen.js.map +1 -1
  12. package/dist/cli/ui/components/screens/LogDetailScreen.js +1 -1
  13. package/dist/cli/ui/components/screens/LogDetailScreen.js.map +1 -1
  14. package/dist/cli/ui/components/screens/LogListScreen.js +1 -1
  15. package/dist/cli/ui/components/screens/LogListScreen.js.map +1 -1
  16. package/dist/cli/ui/utils/branchFormatter.d.ts +5 -0
  17. package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
  18. package/dist/cli/ui/utils/branchFormatter.js +18 -5
  19. package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
  20. package/dist/codex.d.ts.map +1 -1
  21. package/dist/codex.js +0 -1
  22. package/dist/codex.js.map +1 -1
  23. package/dist/config/index.d.ts.map +1 -1
  24. package/dist/config/index.js +3 -7
  25. package/dist/config/index.js.map +1 -1
  26. package/dist/config/profiles.d.ts +2 -2
  27. package/dist/config/profiles.d.ts.map +1 -1
  28. package/dist/config/profiles.js +4 -7
  29. package/dist/config/profiles.js.map +1 -1
  30. package/dist/config/tools.d.ts +1 -1
  31. package/dist/config/tools.d.ts.map +1 -1
  32. package/dist/config/tools.js +3 -43
  33. package/dist/config/tools.js.map +1 -1
  34. package/dist/gemini.d.ts.map +1 -1
  35. package/dist/gemini.js +1 -2
  36. package/dist/gemini.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +106 -90
  39. package/dist/index.js.map +1 -1
  40. package/dist/utils/command.d.ts +11 -0
  41. package/dist/utils/command.d.ts.map +1 -1
  42. package/dist/utils/command.js +33 -0
  43. package/dist/utils/command.js.map +1 -1
  44. package/dist/web/client/src/pages/ConfigPage.js +1 -1
  45. package/dist/web/client/src/pages/ConfigPage.js.map +1 -1
  46. package/package.json +2 -2
  47. package/src/claude.ts +1 -1
  48. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +1 -1
  49. package/src/cli/ui/__tests__/components/App.test.tsx +65 -3
  50. package/src/cli/ui/__tests__/components/screens/LogDetailScreen.test.tsx +1 -1
  51. package/src/cli/ui/__tests__/components/screens/LogListScreen.test.tsx +1 -1
  52. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +83 -22
  53. package/src/cli/ui/__tests__/integration/navigation.test.tsx +57 -37
  54. package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +105 -0
  55. package/src/cli/ui/components/App.tsx +1 -1
  56. package/src/cli/ui/components/screens/BranchListScreen.tsx +9 -7
  57. package/src/cli/ui/components/screens/LogDatePickerScreen.tsx +1 -1
  58. package/src/cli/ui/components/screens/LogDetailScreen.tsx +1 -1
  59. package/src/cli/ui/components/screens/LogListScreen.tsx +1 -1
  60. package/src/cli/ui/utils/branchFormatter.ts +19 -5
  61. package/src/codex.ts +0 -1
  62. package/src/config/index.ts +3 -7
  63. package/src/config/profiles.ts +4 -7
  64. package/src/config/tools.ts +3 -56
  65. package/src/gemini.ts +1 -2
  66. package/src/index.ts +148 -133
  67. package/src/utils/command.ts +37 -0
  68. package/src/web/client/src/pages/ConfigPage.tsx +2 -2
  69. package/src/index.ts.backup +0 -1543
@@ -1,1543 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import path from "node:path";
4
- import { confirm } from "@inquirer/prompts";
5
- import {
6
- isGitRepository,
7
- getAllBranches,
8
- createBranch,
9
- branchExists,
10
- getRepositoryRoot,
11
- deleteBranch,
12
- deleteRemoteBranch,
13
- hasUncommittedChanges,
14
- showStatus,
15
- stashChanges,
16
- discardAllChanges,
17
- commitChanges,
18
- fetchAllRemotes,
19
- pushBranchToRemote,
20
- isInWorktree,
21
- GitError,
22
- getCurrentVersion,
23
- calculateNewVersion,
24
- executeNpmVersionInWorktree,
25
- getCurrentBranchName,
26
- } from "./git.js";
27
- import {
28
- listAdditionalWorktrees,
29
- worktreeExists,
30
- generateWorktreePath,
31
- createWorktree,
32
- removeWorktree,
33
- getMergedPRWorktrees,
34
- WorktreeError,
35
- } from "./worktree.js";
36
- import {
37
- launchClaudeCode,
38
- isClaudeCodeAvailable,
39
- ClaudeError,
40
- } from "./claude.js";
41
- import { launchCodexCLI, isCodexAvailable, CodexError } from "./codex.js";
42
- import {
43
- selectFromTable,
44
- selectBaseBranch,
45
- confirmWorktreeCreation,
46
- confirmSkipPermissions,
47
- selectWorktreeForManagement,
48
- selectWorktreeAction,
49
- confirmWorktreeRemoval,
50
- confirmBranchRemoval,
51
- selectChangesAction,
52
- inputCommitMessage,
53
- confirmDiscardChanges,
54
- confirmContinue,
55
- selectCleanupTargets,
56
- confirmCleanup,
57
- confirmRemoteBranchDeletion,
58
- confirmPushUnpushedCommits,
59
- confirmProceedWithoutPush,
60
- selectSession,
61
- selectClaudeExecutionMode,
62
- selectVersionBumpType,
63
- selectReleaseAction,
64
- } from "./ui/legacy/prompts.js";
65
- import {
66
- displayBranchTable,
67
- printError,
68
- printSuccess,
69
- printInfo,
70
- printWarning,
71
- printExit,
72
- printStatistics,
73
- displayCleanupTargets,
74
- displayCleanupResults,
75
- } from "./ui/legacy/display.js";
76
- import { createBranchTable } from "./ui/legacy/table.js";
77
- import chalk from "chalk";
78
- import { isGitHubCLIAvailable, checkGitHubAuth } from "./github.js";
79
- import { CleanupTarget } from "./ui/types.js";
80
- import { AppError, setupExitHandlers, handleUserCancel } from "./utils.js";
81
- import { BranchInfo, WorktreeConfig } from "./ui/types.js";
82
- import { WorktreeInfo } from "./worktree.js";
83
- import {
84
- loadSession,
85
- saveSession,
86
- SessionData,
87
- getAllSessions,
88
- } from "./config/index.js";
89
-
90
- function showHelp(): void {
91
- console.log(`
92
- Worktree Manager
93
-
94
- Usage: claude-worktree [options]
95
-
96
- Options:
97
- -c Continue from the last session (automatically open the last used worktree)
98
- -r, --resume Resume a session - interactively select from available sessions
99
- --tool <name> Select AI tool to launch in worktree (claude|codex)
100
- -h, --help Show this help message
101
-
102
- Description:
103
- Interactive Git worktree manager with AI tool selection (Claude Code / Codex CLI) and graphical branch selection.
104
-
105
- Without options: Opens the interactive menu to select branches and manage worktrees.
106
- With -c option: Automatically continues from where you left off in the last session.
107
- With -r option: Shows a list of recent sessions to choose from and resume.
108
-
109
- Pass-through:
110
- Use "--" to pass additional args directly to the selected tool.
111
- Examples:
112
- claude-worktree --tool claude -- -r
113
- claude-worktree --tool codex -- resume --last
114
- `);
115
- }
116
-
117
- /**
118
- * Main function for Ink.js UI (new UI)
119
- */
120
- async function mainInkUI(): Promise<void> {
121
- const { render } = await import('ink');
122
- const React = await import('react');
123
- const { App } = await import('./ui/components/App.js');
124
-
125
- // Check if current directory is a Git repository
126
- if (!(await isGitRepository())) {
127
- printError("Current directory is not a Git repository.");
128
- process.exit(1);
129
- }
130
-
131
- let selectedBranch: string | undefined;
132
-
133
- const { unmount, waitUntilExit } = render(
134
- React.createElement(App, {
135
- onExit: (branch?: string) => {
136
- selectedBranch = branch;
137
- },
138
- })
139
- );
140
-
141
- // Wait for user to exit
142
- await waitUntilExit();
143
- unmount();
144
-
145
- // If a branch was selected, handle it
146
- if (selectedBranch) {
147
- printInfo(`Selected branch: ${selectedBranch}`);
148
- // TODO: Implement branch handling logic
149
- // For now, just exit
150
- }
151
- }
152
-
153
- export async function main(): Promise<void> {
154
- try {
155
- // Check for Ink UI feature flag (default: true, set USE_INK_UI=false to use legacy UI)
156
- const useInkUI = process.env.USE_INK_UI !== 'false';
157
-
158
- if (useInkUI) {
159
- await mainInkUI();
160
- return;
161
- }
162
-
163
- // Legacy UI (original implementation)
164
- // Parse command line arguments
165
- const args = process.argv.slice(2);
166
- const continueLastSession = args.includes("-c");
167
- const resumeSession = args.includes("-r") || args.includes("--resume");
168
- const showHelpFlag = args.includes("-h") || args.includes("--help");
169
- // Parse --tool value (supports --tool codex or --tool=codex)
170
- const toolIndex = args.findIndex(
171
- (a) =>
172
- a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
173
- );
174
- let cliToolArg: "claude" | "codex" | undefined;
175
- if (toolIndex !== -1) {
176
- const token: string | undefined = args[toolIndex];
177
- if (typeof token === "string" && token.includes("=")) {
178
- const val = token.split("=")[1];
179
- if (val === "claude" || val === "codex") cliToolArg = val;
180
- else {
181
- printError(`Unknown tool: ${val}. Use --tool claude|codex`);
182
- return; // Exit early on invalid tool arg
183
- }
184
- } else if (
185
- typeof args[toolIndex + 1] === "string" &&
186
- (args[toolIndex + 1] === "claude" || args[toolIndex + 1] === "codex")
187
- ) {
188
- cliToolArg = args[toolIndex + 1] as "claude" | "codex";
189
- } else if (typeof args[toolIndex + 1] === "string") {
190
- printError(
191
- `Unknown tool: ${args[toolIndex + 1]}. Use --tool claude|codex`,
192
- );
193
- return;
194
- }
195
- }
196
-
197
- // Collect pass-through args after "--"
198
- const dashDashIndex = args.findIndex((a) => a === "--");
199
- const passThroughArgs: string[] =
200
- dashDashIndex !== -1 ? args.slice(dashDashIndex + 1) : [];
201
-
202
- // Show help if requested
203
- if (showHelpFlag) {
204
- showHelp();
205
- return;
206
- }
207
-
208
- // Setup graceful exit handlers
209
- setupExitHandlers();
210
-
211
- // Check if current directory is a Git repository
212
- if (!(await isGitRepository())) {
213
- printError("Current directory is not a Git repository.");
214
- process.exit(1);
215
- }
216
-
217
- // Check if running from a worktree directory
218
- if (await isInWorktree()) {
219
- printWarning("Running from a worktree directory is not recommended.");
220
- printInfo(
221
- "Please run this command from the main repository root to avoid path issues.",
222
- );
223
- printInfo(
224
- "You can continue, but some operations may not work correctly.",
225
- );
226
- const shouldContinue = await confirmContinue(
227
- "Do you want to continue anyway?",
228
- );
229
- if (!shouldContinue) {
230
- process.exit(0);
231
- }
232
- }
233
-
234
- // Check tool availability for selection later
235
- const [claudeAvailable, codexAvailable] = await Promise.all([
236
- isClaudeCodeAvailable().catch(() => false),
237
- isCodexAvailable().catch(() => false),
238
- ]);
239
- if (!claudeAvailable) {
240
- printWarning(
241
- "Claude Code CLI not found. Make sure it's installed and in your PATH.",
242
- );
243
- printInfo("You can install it from: https://claude.ai/code");
244
- }
245
- if (!codexAvailable) {
246
- printInfo(
247
- "Codex CLI not detected. You can still select it, but bunx may prompt to install.",
248
- );
249
- }
250
-
251
- // Get repository root
252
- const repoRoot = await getRepositoryRoot();
253
-
254
- // Handle continue last session option
255
- if (continueLastSession) {
256
- const sessionData = await loadSession(repoRoot);
257
- if (sessionData && sessionData.lastWorktreePath) {
258
- printInfo(
259
- `Continuing last session: ${sessionData.lastBranch} (${sessionData.lastWorktreePath})`,
260
- );
261
-
262
- // Check if worktree still exists
263
- if (await worktreeExists(sessionData.lastBranch!)) {
264
- // Select AI tool
265
- const { selectAITool } = await import("./ui/legacy/prompts.js");
266
- let selectedTool: "claude" | "codex" | null = null;
267
-
268
- // Parse tool arg
269
- const argv = process.argv.slice(2);
270
- const idx = argv.findIndex(
271
- (a) =>
272
- a === "--tool" ||
273
- (typeof a === "string" && a.startsWith("--tool=")),
274
- );
275
- let argTool: "claude" | "codex" | undefined;
276
- if (idx !== -1) {
277
- const tok: string | undefined = argv[idx];
278
- if (typeof tok === "string" && tok.includes("=")) {
279
- const val = tok.split("=")[1];
280
- if (val === "claude" || val === "codex") argTool = val;
281
- } else if (
282
- typeof argv[idx + 1] === "string" &&
283
- (argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
284
- ) {
285
- argTool = argv[idx + 1] as "claude" | "codex";
286
- }
287
- }
288
-
289
- const [localClaudeAvail, localCodexAvail] = await Promise.all([
290
- isClaudeCodeAvailable().catch(() => false),
291
- isCodexAvailable().catch(() => false),
292
- ]);
293
-
294
- if (argTool) {
295
- selectedTool = argTool;
296
- if (selectedTool === "claude" && !localClaudeAvail) {
297
- selectedTool = null;
298
- printWarning("Requested tool is not available.");
299
- } else if (selectedTool === "codex" && !localCodexAvail) {
300
- selectedTool = null;
301
- printWarning("Codex CLI was not detected.");
302
- }
303
- }
304
-
305
- if (!selectedTool) {
306
- selectedTool = await selectAITool({
307
- claudeAvailable: localClaudeAvail,
308
- codexAvailable: localCodexAvail,
309
- });
310
- }
311
-
312
- if (!selectedTool) {
313
- printInfo("No AI tool selected.");
314
- return;
315
- }
316
-
317
- const executionConfig = await selectClaudeExecutionMode(
318
- selectedTool === "claude" ? "Claude Code" : "Codex CLI",
319
- );
320
- if (!executionConfig) return;
321
- const { mode, skipPermissions } = executionConfig;
322
-
323
- try {
324
- if (selectedTool === "claude") {
325
- await launchClaudeCode(sessionData.lastWorktreePath, {
326
- mode,
327
- skipPermissions,
328
- });
329
- await handlePostClaudeChanges(sessionData.lastWorktreePath);
330
- } else {
331
- await launchCodexCLI(sessionData.lastWorktreePath, {
332
- mode,
333
- bypassApprovals: skipPermissions,
334
- });
335
- }
336
- } catch (error) {
337
- if (error instanceof ClaudeError || error instanceof CodexError) {
338
- printError(error.message);
339
- } else {
340
- printError(
341
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
342
- );
343
- }
344
- await confirmContinue("Press enter to continue...");
345
- }
346
-
347
- return; // Exit after continuing session
348
- } else {
349
- printWarning(
350
- `Last session worktree no longer exists: ${sessionData.lastWorktreePath}`,
351
- );
352
- printInfo("Falling back to normal flow...");
353
- }
354
- } else {
355
- printInfo("No previous session found. Starting normally...");
356
- }
357
- }
358
-
359
- // Handle resume session option
360
- if (resumeSession) {
361
- const allSessions = await getAllSessions();
362
- if (allSessions.length === 0) {
363
- printInfo("No previous sessions found. Starting normally...");
364
- } else {
365
- const selectedSession = await selectSession(allSessions);
366
- if (selectedSession && selectedSession.lastWorktreePath) {
367
- printInfo(
368
- `Resuming session: ${selectedSession.lastBranch} (${selectedSession.lastWorktreePath})`,
369
- );
370
-
371
- // Check if worktree still exists
372
- if (await worktreeExists(selectedSession.lastBranch!)) {
373
- // Select AI tool
374
- const { selectAITool } = await import("./ui/legacy/prompts.js");
375
- let selectedTool: "claude" | "codex" | null = null;
376
-
377
- // Parse tool arg
378
- const argv = process.argv.slice(2);
379
- const idx = argv.findIndex(
380
- (a) =>
381
- a === "--tool" ||
382
- (typeof a === "string" && a.startsWith("--tool=")),
383
- );
384
- let argTool: "claude" | "codex" | undefined;
385
- if (idx !== -1) {
386
- const tok: string | undefined = argv[idx];
387
- if (typeof tok === "string" && tok.includes("=")) {
388
- const val = tok.split("=")[1];
389
- if (val === "claude" || val === "codex") argTool = val;
390
- } else if (
391
- typeof argv[idx + 1] === "string" &&
392
- (argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
393
- ) {
394
- argTool = argv[idx + 1] as "claude" | "codex";
395
- }
396
- }
397
-
398
- const [localClaudeAvail, localCodexAvail] = await Promise.all([
399
- isClaudeCodeAvailable().catch(() => false),
400
- isCodexAvailable().catch(() => false),
401
- ]);
402
-
403
- if (argTool) {
404
- selectedTool = argTool;
405
- if (selectedTool === "claude" && !localClaudeAvail) {
406
- selectedTool = null;
407
- printWarning("Requested tool is not available.");
408
- } else if (selectedTool === "codex" && !localCodexAvail) {
409
- selectedTool = null;
410
- printWarning("Codex CLI was not detected.");
411
- }
412
- }
413
-
414
- if (!selectedTool) {
415
- selectedTool = await selectAITool({
416
- claudeAvailable: localClaudeAvail,
417
- codexAvailable: localCodexAvail,
418
- });
419
- }
420
-
421
- if (!selectedTool) {
422
- printInfo("No AI tool selected.");
423
- return;
424
- }
425
-
426
- const executionConfig = await selectClaudeExecutionMode(
427
- selectedTool === "claude" ? "Claude Code" : "Codex CLI",
428
- );
429
- if (!executionConfig) return;
430
- const { mode, skipPermissions } = executionConfig;
431
-
432
- try {
433
- if (selectedTool === "claude") {
434
- await launchClaudeCode(selectedSession.lastWorktreePath, {
435
- mode,
436
- skipPermissions,
437
- });
438
- await handlePostClaudeChanges(selectedSession.lastWorktreePath);
439
- } else {
440
- await launchCodexCLI(selectedSession.lastWorktreePath, {
441
- mode,
442
- bypassApprovals: skipPermissions,
443
- });
444
- }
445
- } catch (error) {
446
- if (error instanceof ClaudeError || error instanceof CodexError) {
447
- printError(error.message);
448
- } else {
449
- printError(
450
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
451
- );
452
- }
453
- await confirmContinue("Press enter to continue...");
454
- }
455
-
456
- return; // Exit after resuming session
457
- } else {
458
- printWarning(
459
- `Selected session worktree no longer exists: ${selectedSession.lastWorktreePath}`,
460
- );
461
- printInfo("Falling back to normal flow...");
462
- }
463
- } else {
464
- printInfo("No session selected. Starting normally...");
465
- }
466
- }
467
- }
468
-
469
- // Main application loop
470
- while (true) {
471
- try {
472
- // Get current repository state
473
- const [branches, worktrees] = await Promise.all([
474
- getAllBranches(),
475
- listAdditionalWorktrees(),
476
- ]);
477
-
478
- // Create and display table
479
- const choices = await createBranchTable(branches, worktrees);
480
-
481
- // Get user selection with statistics
482
- const selection = await selectFromTable(choices, {
483
- branches,
484
- worktrees,
485
- });
486
-
487
- // Handle selection
488
- const shouldContinue = await handleSelection(
489
- selection,
490
- branches,
491
- worktrees,
492
- repoRoot,
493
- );
494
-
495
- if (!shouldContinue) {
496
- break;
497
- }
498
- } catch (error) {
499
- handleUserCancel(error);
500
- }
501
- }
502
- } catch (error) {
503
- if (error instanceof GitError) {
504
- printError(`Git error: ${error.message}`);
505
- } else if (error instanceof WorktreeError) {
506
- printError(`Worktree error: ${error.message}`);
507
- } else if (error instanceof ClaudeError) {
508
- printError(`Claude Code error: ${error.message}`);
509
- if (error.cause && error.cause instanceof Error) {
510
- printError(`Cause: ${error.cause.message}`);
511
- }
512
- } else if (error instanceof AppError) {
513
- printError(`Application error: ${error.message}`);
514
- } else {
515
- printError(
516
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
517
- );
518
- }
519
-
520
- process.exit(1);
521
- }
522
- }
523
-
524
- async function handleSelection(
525
- selection: string,
526
- branches: BranchInfo[],
527
- worktrees: WorktreeInfo[],
528
- repoRoot: string,
529
- ): Promise<boolean> {
530
- switch (selection) {
531
- case "__exit__":
532
- printExit();
533
- return false;
534
-
535
- case "__create_new__":
536
- return await handleCreateNewBranch(branches, repoRoot);
537
-
538
- case "__manage_worktrees__":
539
- return await handleManageWorktrees(worktrees);
540
-
541
- case "__cleanup_prs__":
542
- return await handleCleanupMergedPRs();
543
-
544
- default:
545
- // Handle branch selection
546
- return await handleBranchSelection(selection, repoRoot);
547
- }
548
- }
549
-
550
- async function handleBranchSelection(
551
- branchName: string,
552
- repoRoot: string,
553
- ): Promise<boolean> {
554
- try {
555
- // Collect pass-through args from process argv
556
- const argvAll = process.argv.slice(2);
557
- const ddIndex = argvAll.findIndex((a) => a === "--");
558
- const passThroughArgs = ddIndex !== -1 ? argvAll.slice(ddIndex + 1) : [];
559
-
560
- // Check if this is a remote branch
561
- const isRemoteBranch = branchName.startsWith("origin/");
562
- let localBranchName = branchName;
563
- let targetBranch = branchName;
564
-
565
- if (isRemoteBranch) {
566
- // Extract local branch name from remote branch
567
- localBranchName = branchName.replace(/^origin\//, "");
568
- targetBranch = localBranchName;
569
- }
570
-
571
- // Check if worktree exists (using local branch name)
572
- let worktreePath = await worktreeExists(targetBranch);
573
-
574
- if (worktreePath) {
575
- printInfo(`Opening existing worktree: ${worktreePath}`);
576
- } else {
577
- // Create new worktree
578
- worktreePath = await generateWorktreePath(repoRoot, targetBranch);
579
-
580
- // Check for path conflict
581
- const {
582
- checkWorktreePathConflict,
583
- generateAlternativeWorktreePath,
584
- removeWorktree: removeConflictingWorktree,
585
- } = await import("./worktree.js");
586
- const pathConflict = await checkWorktreePathConflict(worktreePath);
587
-
588
- if (pathConflict && pathConflict.branch !== targetBranch) {
589
- // Path conflict detected - let user choose how to proceed
590
- const { selectWorktreePathConflictResolution } = await import(
591
- "./ui/legacy/prompts.js"
592
- );
593
- const resolution = await selectWorktreePathConflictResolution(
594
- targetBranch,
595
- worktreePath,
596
- pathConflict.branch,
597
- );
598
-
599
- if (resolution === "cancel") {
600
- printInfo("Operation cancelled.");
601
- return true;
602
- } else if (resolution === "remove-and-create") {
603
- printInfo(
604
- `Removing existing worktree for "${pathConflict.branch}"...`,
605
- );
606
- await removeConflictingWorktree(worktreePath, true);
607
- printSuccess("Existing worktree removed.");
608
- } else if (resolution === "use-different-path") {
609
- worktreePath = await generateAlternativeWorktreePath(worktreePath);
610
- printInfo(`Using alternative path: ${worktreePath}`);
611
- }
612
- }
613
-
614
- if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
615
- printInfo("Operation cancelled.");
616
- return true; // Continue to main menu
617
- }
618
-
619
- let isNewBranch = false;
620
- let baseBranch = targetBranch;
621
-
622
- if (isRemoteBranch) {
623
- // Check if local branch exists
624
- const localExists = await branchExists(localBranchName);
625
- if (!localExists) {
626
- // Need to create new local branch from remote
627
- isNewBranch = true;
628
- baseBranch = branchName; // Use full remote branch name as base
629
- }
630
- }
631
-
632
- const worktreeConfig: WorktreeConfig = {
633
- branchName: targetBranch,
634
- worktreePath,
635
- repoRoot,
636
- isNewBranch,
637
- baseBranch,
638
- };
639
-
640
- printInfo(`Creating worktree for "${targetBranch}"...`);
641
- await createWorktree(worktreeConfig);
642
- printSuccess(`Worktree created at: ${worktreePath}`);
643
- }
644
-
645
- // Select and launch AI tool
646
- const { selectAITool } = await import("./ui/legacy/prompts.js");
647
- let selectedTool: "claude" | "codex" | null = null;
648
- // Re-parse tool arg for this flow as well
649
- const argv = process.argv.slice(2);
650
- const idx = argv.findIndex(
651
- (a) =>
652
- a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
653
- );
654
- let argTool: "claude" | "codex" | undefined;
655
- if (idx !== -1) {
656
- const tok: string | undefined = argv[idx];
657
- if (typeof tok === "string" && tok.includes("=")) {
658
- const val = tok.split("=")[1];
659
- if (val === "claude" || val === "codex") argTool = val;
660
- } else if (
661
- typeof argv[idx + 1] === "string" &&
662
- (argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
663
- ) {
664
- argTool = argv[idx + 1] as "claude" | "codex";
665
- }
666
- }
667
- const [localClaudeAvail, localCodexAvail] = await Promise.all([
668
- isClaudeCodeAvailable().catch(() => false),
669
- isCodexAvailable().catch(() => false),
670
- ]);
671
- if (argTool) {
672
- selectedTool = argTool;
673
- if (selectedTool === "claude" && !localClaudeAvail) {
674
- selectedTool = null;
675
- printWarning("Requested tool is not available.");
676
- } else if (selectedTool === "codex" && !localCodexAvail) {
677
- selectedTool = null;
678
- printWarning(
679
- "Codex CLI was not detected. Install Bun and Codex before selecting it.",
680
- );
681
- }
682
- }
683
- if (!selectedTool) {
684
- selectedTool = await selectAITool({
685
- claudeAvailable: localClaudeAvail,
686
- codexAvailable: localCodexAvail,
687
- });
688
- }
689
- if (!selectedTool) {
690
- printInfo("No AI tool selected. Returning to menu.");
691
- return true;
692
- }
693
- if (!localClaudeAvail && !localCodexAvail) {
694
- printError(
695
- "No AI tools are available in PATH (Claude Code or Codex CLI).",
696
- );
697
- await confirmContinue("Press enter to continue...");
698
- return true;
699
- }
700
- if (selectedTool === "claude" && !localClaudeAvail) {
701
- printError(
702
- "Claude Code CLI not found in PATH. Install it before selecting this option.",
703
- );
704
- await confirmContinue("Press enter to continue...");
705
- return true;
706
- }
707
- if (selectedTool === "codex" && !localCodexAvail) {
708
- printError(
709
- "Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
710
- );
711
- await confirmContinue("Press enter to continue...");
712
- return true;
713
- }
714
-
715
- const executionConfig = await selectClaudeExecutionMode(
716
- selectedTool === "claude" ? "Claude Code" : "Codex CLI",
717
- );
718
- if (!executionConfig) return true;
719
- const { mode, skipPermissions } = executionConfig;
720
-
721
- try {
722
- // Save session data before launching
723
- const sessionData: SessionData = {
724
- lastWorktreePath: worktreePath,
725
- lastBranch: targetBranch,
726
- timestamp: Date.now(),
727
- repositoryRoot: repoRoot,
728
- };
729
- await saveSession(sessionData);
730
-
731
- if (selectedTool === "claude") {
732
- await launchClaudeCode(worktreePath, {
733
- mode,
734
- skipPermissions,
735
- extraArgs: passThroughArgs,
736
- });
737
- await handlePostClaudeChanges(worktreePath);
738
- } else {
739
- await launchCodexCLI(worktreePath, {
740
- mode,
741
- extraArgs: passThroughArgs,
742
- bypassApprovals: skipPermissions,
743
- });
744
- }
745
- } catch (error) {
746
- if (error instanceof ClaudeError || error instanceof CodexError) {
747
- printError(error.message);
748
- } else {
749
- printError(
750
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
751
- );
752
- }
753
- await confirmContinue("Press enter to continue...");
754
- }
755
-
756
- // After handling changes, return to main menu
757
- return true;
758
- } catch (error) {
759
- printError(
760
- `Failed to handle branch selection: ${error instanceof Error ? error.message : String(error)}`,
761
- );
762
- await confirmContinue("Press enter to continue...");
763
- return true;
764
- }
765
- }
766
-
767
- async function handleCreateNewBranch(
768
- branches: BranchInfo[],
769
- repoRoot: string,
770
- ): Promise<boolean> {
771
- try {
772
- // まず、ブランチタイプのみを選択
773
- const { selectBranchType } = await import("./ui/legacy/prompts.js");
774
- const branchType = await selectBranchType();
775
-
776
- let targetBranch: string;
777
- let baseBranch: string;
778
-
779
- // リリースブランチの場合は特別な処理
780
- if (branchType === "release") {
781
- // Git flowではリリースブランチはdevelopから分岐するが、
782
- // developブランチがない場合はmainブランチから分岐
783
- const developBranch = branches.find(
784
- (b) => b.type === "local" && (b.name === "develop" || b.name === "dev"),
785
- );
786
-
787
- if (developBranch) {
788
- baseBranch = developBranch.name;
789
- printInfo(`Creating release branch from ${baseBranch} (Git Flow)`);
790
- } else {
791
- // developがない場合はmain/masterから分岐
792
- const mainBranch = branches.find(
793
- (b) =>
794
- b.type === "local" && (b.name === "main" || b.name === "master"),
795
- );
796
-
797
- if (!mainBranch) {
798
- printError("No develop, main, or master branch found.");
799
- return true;
800
- }
801
-
802
- baseBranch = mainBranch.name;
803
- printWarning(
804
- `No develop branch found. Creating release branch from ${baseBranch}`,
805
- );
806
- }
807
-
808
- // 現在のバージョンを取得
809
- const currentVersion = await getCurrentVersion(repoRoot);
810
-
811
- // バージョンバンプタイプを選択
812
- const versionBump = await selectVersionBumpType(currentVersion);
813
-
814
- // 新しいバージョンを計算
815
- const newVersion = calculateNewVersion(currentVersion, versionBump);
816
-
817
- // リリースブランチ名を生成
818
- targetBranch = `release/${newVersion}`;
819
- printInfo(`Release branch will be: ${targetBranch}`);
820
- } else {
821
- // 通常のブランチの場合
822
- const { inputBranchName } = await import("./ui/legacy/prompts.js");
823
- const taskName = await inputBranchName(branchType);
824
- targetBranch = `${branchType}/${taskName}`;
825
- baseBranch = await selectBaseBranch(branches);
826
- }
827
-
828
- // Check if branch already exists
829
- if (await branchExists(targetBranch)) {
830
- printError(`Branch "${targetBranch}" already exists.`);
831
- if (await confirmContinue("Return to main menu?")) {
832
- return true;
833
- }
834
- return false;
835
- }
836
-
837
- printInfo(`Creating new branch "${targetBranch}" from "${baseBranch}"`);
838
-
839
- // Create worktree path
840
- const worktreePath = await generateWorktreePath(repoRoot, targetBranch);
841
-
842
- if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
843
- printInfo("Operation cancelled.");
844
- return true;
845
- }
846
-
847
- // Create worktree configuration
848
- const worktreeConfig: WorktreeConfig = {
849
- branchName: targetBranch,
850
- worktreePath,
851
- repoRoot,
852
- isNewBranch: true,
853
- baseBranch,
854
- };
855
-
856
- // Create worktree
857
- printInfo(`Creating worktree for "${targetBranch}"...`);
858
- await createWorktree(worktreeConfig);
859
- printSuccess(`Worktree created at: ${worktreePath}`);
860
-
861
- // リリースブランチの場合、worktree作成後にnpm versionを実行
862
- if (branchType === "release") {
863
- printInfo("Updating version in release branch...");
864
- try {
865
- const newVersion = targetBranch.replace("release/", "");
866
- await executeNpmVersionInWorktree(worktreePath, newVersion);
867
- printSuccess(`Version updated to ${newVersion} in release branch`);
868
- } catch (error) {
869
- printError(
870
- `Failed to update version: ${error instanceof Error ? error.message : String(error)}`,
871
- );
872
- // エラーが発生してもworktreeは作成済みなので続行
873
- }
874
- }
875
-
876
- // Select and launch AI tool
877
- const { selectAITool } = await import("./ui/legacy/prompts.js");
878
- let selectedTool: "claude" | "codex" | null = null;
879
-
880
- // Parse tool arg
881
- const argv = process.argv.slice(2);
882
- const idx = argv.findIndex(
883
- (a) =>
884
- a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
885
- );
886
- let argTool: "claude" | "codex" | undefined;
887
- if (idx !== -1) {
888
- const tok: string | undefined = argv[idx];
889
- if (typeof tok === "string" && tok.includes("=")) {
890
- const val = tok.split("=")[1];
891
- if (val === "claude" || val === "codex") argTool = val;
892
- } else if (
893
- typeof argv[idx + 1] === "string" &&
894
- (argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
895
- ) {
896
- argTool = argv[idx + 1] as "claude" | "codex";
897
- }
898
- }
899
-
900
- const [localClaudeAvail, localCodexAvail] = await Promise.all([
901
- isClaudeCodeAvailable().catch(() => false),
902
- isCodexAvailable().catch(() => false),
903
- ]);
904
-
905
- if (argTool) {
906
- selectedTool = argTool;
907
- if (selectedTool === "claude" && !localClaudeAvail) {
908
- selectedTool = null;
909
- printWarning("Requested tool is not available.");
910
- } else if (selectedTool === "codex" && !localCodexAvail) {
911
- selectedTool = null;
912
- printWarning(
913
- "Codex CLI was not detected. Install Bun and Codex before selecting it.",
914
- );
915
- }
916
- }
917
-
918
- if (!selectedTool) {
919
- selectedTool = await selectAITool({
920
- claudeAvailable: localClaudeAvail,
921
- codexAvailable: localCodexAvail,
922
- });
923
- }
924
-
925
- if (!selectedTool) {
926
- printInfo("No AI tool selected. Returning to menu.");
927
- return true;
928
- }
929
-
930
- if (!localClaudeAvail && !localCodexAvail) {
931
- printError(
932
- "No AI tools are available in PATH (Claude Code or Codex CLI).",
933
- );
934
- await confirmContinue("Press enter to continue...");
935
- return true;
936
- }
937
-
938
- if (selectedTool === "claude" && !localClaudeAvail) {
939
- printError(
940
- "Claude Code CLI not found in PATH. Install it before selecting this option.",
941
- );
942
- await confirmContinue("Press enter to continue...");
943
- return true;
944
- }
945
-
946
- if (selectedTool === "codex" && !localCodexAvail) {
947
- printError(
948
- "Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
949
- );
950
- await confirmContinue("Press enter to continue...");
951
- return true;
952
- }
953
-
954
- const executionConfig = await selectClaudeExecutionMode(
955
- selectedTool === "claude" ? "Claude Code" : "Codex CLI",
956
- );
957
- if (!executionConfig) return true;
958
- const { mode, skipPermissions } = executionConfig;
959
-
960
- try {
961
- // Save session data before launching
962
- const sessionData: SessionData = {
963
- lastWorktreePath: worktreePath,
964
- lastBranch: targetBranch,
965
- timestamp: Date.now(),
966
- repositoryRoot: repoRoot,
967
- };
968
- await saveSession(sessionData);
969
-
970
- if (selectedTool === "claude") {
971
- await launchClaudeCode(worktreePath, { mode, skipPermissions });
972
- await handlePostClaudeChanges(worktreePath);
973
- } else {
974
- await launchCodexCLI(worktreePath, {
975
- mode,
976
- bypassApprovals: skipPermissions,
977
- });
978
- }
979
- } catch (error) {
980
- if (error instanceof ClaudeError || error instanceof CodexError) {
981
- printError(error.message);
982
- } else {
983
- printError(
984
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
985
- );
986
- }
987
- await confirmContinue("Press enter to continue...");
988
- }
989
-
990
- return true;
991
- } catch (error) {
992
- printError(
993
- `Failed to create new branch: ${error instanceof Error ? error.message : String(error)}`,
994
- );
995
- await confirmContinue("Press enter to continue...");
996
- return true;
997
- }
998
- }
999
-
1000
- async function handleManageWorktrees(
1001
- worktrees: WorktreeInfo[],
1002
- ): Promise<boolean> {
1003
- try {
1004
- if (worktrees.length === 0) {
1005
- printInfo("No worktrees found.");
1006
- if (await confirmContinue("Return to main menu?")) {
1007
- return true;
1008
- }
1009
- return false;
1010
- }
1011
-
1012
- while (true) {
1013
- const worktreeChoices = worktrees.map((w) => ({
1014
- branch: w.branch,
1015
- path: w.path,
1016
- }));
1017
- const selectedWorktree =
1018
- await selectWorktreeForManagement(worktreeChoices);
1019
-
1020
- if (selectedWorktree === "back") {
1021
- return true; // Return to main menu
1022
- }
1023
-
1024
- const worktree = worktrees.find((w) => w.branch === selectedWorktree);
1025
- if (!worktree) {
1026
- printError("Worktree not found.");
1027
- continue;
1028
- }
1029
-
1030
- const action = await selectWorktreeAction();
1031
-
1032
- switch (action) {
1033
- case "open":
1034
- // Check if worktree is accessible
1035
- if (worktree.isAccessible === false) {
1036
- printError("Cannot open inaccessible worktree");
1037
- printInfo(`Path: ${worktree.path}`);
1038
- printInfo(
1039
- "This worktree was created in a different environment and is not accessible here.",
1040
- );
1041
- await confirmContinue("Press enter to continue...");
1042
- break;
1043
- }
1044
-
1045
- // Select AI tool (reuse selection logic)
1046
- const { selectAITool } = await import("./ui/legacy/prompts.js");
1047
- const [localClaudeAvail, localCodexAvail] = await Promise.all([
1048
- isClaudeCodeAvailable().catch(() => false),
1049
- isCodexAvailable().catch(() => false),
1050
- ]);
1051
- if (!localClaudeAvail && !localCodexAvail) {
1052
- printError(
1053
- "No AI tools are available in PATH (Claude Code or Codex CLI).",
1054
- );
1055
- await confirmContinue("Press enter to continue...");
1056
- return true;
1057
- }
1058
-
1059
- const selectedTool = await selectAITool({
1060
- claudeAvailable: localClaudeAvail,
1061
- codexAvailable: localCodexAvail,
1062
- });
1063
- if (!selectedTool) {
1064
- printInfo("No AI tool selected. Returning to menu.");
1065
- return true;
1066
- }
1067
- if (selectedTool === "claude" && !localClaudeAvail) {
1068
- printError(
1069
- "Claude Code CLI not found in PATH. Install it before selecting this option.",
1070
- );
1071
- await confirmContinue("Press enter to continue...");
1072
- return true;
1073
- }
1074
- if (selectedTool === "codex" && !localCodexAvail) {
1075
- printError(
1076
- "Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
1077
- );
1078
- await confirmContinue("Press enter to continue...");
1079
- return true;
1080
- }
1081
-
1082
- // Ask execution mode
1083
- const execCfg = await selectClaudeExecutionMode(
1084
- selectedTool === "claude" ? "Claude Code" : "Codex CLI",
1085
- );
1086
- if (!execCfg) return true;
1087
- const { mode, skipPermissions } = execCfg;
1088
-
1089
- try {
1090
- // Save session data before launching
1091
- const sessionData: SessionData = {
1092
- lastWorktreePath: worktree.path,
1093
- lastBranch: worktree.branch,
1094
- timestamp: Date.now(),
1095
- repositoryRoot: await getRepositoryRoot(),
1096
- };
1097
- await saveSession(sessionData);
1098
-
1099
- if (selectedTool === "claude") {
1100
- await launchClaudeCode(worktree.path, { mode, skipPermissions });
1101
- } else {
1102
- await launchCodexCLI(worktree.path, {
1103
- mode,
1104
- bypassApprovals: skipPermissions,
1105
- });
1106
- }
1107
- } catch (error) {
1108
- if (error instanceof ClaudeError || error instanceof CodexError) {
1109
- printError(error.message);
1110
- } else {
1111
- printError(
1112
- `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
1113
- );
1114
- }
1115
- await confirmContinue("Press enter to continue...");
1116
- }
1117
- return true; // Return to main menu after opening
1118
-
1119
- case "remove":
1120
- if (worktree.isAccessible === false) {
1121
- // Special handling for inaccessible worktrees
1122
- const shouldRemove = await confirm({
1123
- message:
1124
- "This worktree is inaccessible. Do you want to remove it from Git's records?",
1125
- default: false,
1126
- });
1127
- if (shouldRemove) {
1128
- await removeWorktree(worktree.path, true); // Force removal
1129
- printSuccess(`Worktree record removed: ${worktree.path}`);
1130
- // Update worktrees list
1131
- const index = worktrees.indexOf(worktree);
1132
- worktrees.splice(index, 1);
1133
- }
1134
- } else {
1135
- if (await confirmWorktreeRemoval(worktree.path)) {
1136
- await removeWorktree(worktree.path);
1137
- printSuccess(`Worktree removed: ${worktree.path}`);
1138
- // Update worktrees list
1139
- const index = worktrees.indexOf(worktree);
1140
- worktrees.splice(index, 1);
1141
- }
1142
- }
1143
- break;
1144
-
1145
- case "remove-branch":
1146
- if (worktree.isAccessible === false) {
1147
- // Special handling for inaccessible worktrees
1148
- const shouldRemove = await confirm({
1149
- message:
1150
- "This worktree is inaccessible. Do you want to remove it from Git's records and delete the branch?",
1151
- default: false,
1152
- });
1153
- if (shouldRemove) {
1154
- await removeWorktree(worktree.path, true); // Force removal
1155
- printSuccess(`Worktree record removed: ${worktree.path}`);
1156
-
1157
- if (await confirmBranchRemoval(worktree.branch)) {
1158
- await deleteBranch(worktree.branch, true); // Force delete
1159
- printSuccess(`Branch deleted: ${worktree.branch}`);
1160
- }
1161
-
1162
- // Update worktrees list
1163
- const index = worktrees.indexOf(worktree);
1164
- worktrees.splice(index, 1);
1165
- }
1166
- } else {
1167
- if (await confirmWorktreeRemoval(worktree.path)) {
1168
- await removeWorktree(worktree.path);
1169
- printSuccess(`Worktree removed: ${worktree.path}`);
1170
-
1171
- if (await confirmBranchRemoval(worktree.branch)) {
1172
- await deleteBranch(worktree.branch, true); // Force delete
1173
- printSuccess(`Branch deleted: ${worktree.branch}`);
1174
- }
1175
-
1176
- // Update worktrees list
1177
- const index = worktrees.indexOf(worktree);
1178
- worktrees.splice(index, 1);
1179
- }
1180
- }
1181
- break;
1182
-
1183
- case "back":
1184
- continue; // Continue worktree management loop
1185
- }
1186
- }
1187
- } catch (error) {
1188
- printError(
1189
- `Failed to manage worktrees: ${error instanceof Error ? error.message : String(error)}`,
1190
- );
1191
- await confirmContinue("Press enter to continue...");
1192
- return true;
1193
- }
1194
- }
1195
-
1196
- async function handleCleanupMergedPRs(): Promise<boolean> {
1197
- try {
1198
- // Check if GitHub CLI is available
1199
- if (!(await isGitHubCLIAvailable())) {
1200
- printError(
1201
- "GitHub CLI is not installed. Please install it to use this feature.",
1202
- );
1203
- printInfo("Install GitHub CLI: https://cli.github.com/");
1204
- return true;
1205
- }
1206
-
1207
- // Check if authenticated
1208
- if (!(await checkGitHubAuth())) {
1209
- return true;
1210
- }
1211
-
1212
- printInfo("Fetching latest changes from remote...");
1213
- await fetchAllRemotes();
1214
-
1215
- printInfo("Checking for merged pull requests...");
1216
- const cleanupTargets = await getMergedPRWorktrees();
1217
-
1218
- if (cleanupTargets.length === 0) {
1219
- console.log(
1220
- chalk.green(
1221
- "✨ すべてクリーンです!クリーンアップが必要なworktreeはありません。",
1222
- ),
1223
- );
1224
- await confirmContinue("Press enter to continue...");
1225
- return true;
1226
- }
1227
-
1228
- // Display targets
1229
- displayCleanupTargets(cleanupTargets);
1230
-
1231
- // Select targets to clean up
1232
- const selectedTargets = await selectCleanupTargets(cleanupTargets);
1233
-
1234
- // Check if user pressed q to go back
1235
- if (selectedTargets === null) {
1236
- console.log(chalk.yellow("🔙 前の画面に戻ります。"));
1237
- return true;
1238
- }
1239
-
1240
- if (selectedTargets.length === 0) {
1241
- console.log(chalk.yellow("🚫 クリーンアップをキャンセルしました。"));
1242
- return true;
1243
- }
1244
-
1245
- // Confirm cleanup
1246
- if (!(await confirmCleanup(selectedTargets))) {
1247
- console.log(chalk.yellow("🚫 クリーンアップをキャンセルしました。"));
1248
- return true;
1249
- }
1250
-
1251
- // Check if there are branches with unpushed commits and ask about pushing
1252
- const shouldPushUnpushed =
1253
- await confirmPushUnpushedCommits(selectedTargets);
1254
-
1255
- // Ask about remote branch deletion
1256
- const deleteRemoteBranches =
1257
- await confirmRemoteBranchDeletion(selectedTargets);
1258
-
1259
- // Perform cleanup
1260
- const results: Array<{
1261
- target: CleanupTarget;
1262
- success: boolean;
1263
- error?: string;
1264
- }> = [];
1265
-
1266
- for (const target of selectedTargets) {
1267
- try {
1268
- // Push unpushed commits if requested and needed (only for worktree targets)
1269
- if (
1270
- shouldPushUnpushed &&
1271
- target.hasUnpushedCommits &&
1272
- target.cleanupType === "worktree-and-branch"
1273
- ) {
1274
- printInfo(`Pushing unpushed commits in branch: ${target.branch}`);
1275
- try {
1276
- await pushBranchToRemote(target.worktreePath!, target.branch);
1277
- printSuccess(
1278
- `Successfully pushed changes for branch: ${target.branch}`,
1279
- );
1280
- } catch (error) {
1281
- printWarning(
1282
- `Failed to push branch ${target.branch}: ${error instanceof Error ? error.message : String(error)}`,
1283
- );
1284
-
1285
- // Ask user if they want to proceed without pushing
1286
- if (!(await confirmProceedWithoutPush(target.branch))) {
1287
- printInfo(`Skipping deletion of branch: ${target.branch}`);
1288
- continue; // Skip this target
1289
- }
1290
- }
1291
- }
1292
-
1293
- // Handle different cleanup types
1294
- if (target.cleanupType === "worktree-and-branch") {
1295
- printInfo(`Removing worktree: ${target.worktreePath}`);
1296
- await removeWorktree(target.worktreePath!, true); // Force remove
1297
-
1298
- printInfo(`Deleting local branch: ${target.branch}`);
1299
- await deleteBranch(target.branch, true); // Force delete
1300
- } else if (target.cleanupType === "branch-only") {
1301
- printInfo(`Deleting local branch: ${target.branch}`);
1302
- await deleteBranch(target.branch, true); // Force delete
1303
- }
1304
-
1305
- if (deleteRemoteBranches && target.hasRemoteBranch) {
1306
- printInfo(`Deleting remote branch: origin/${target.branch}`);
1307
- try {
1308
- await deleteRemoteBranch(target.branch);
1309
- printSuccess(
1310
- `Successfully deleted remote branch: origin/${target.branch}`,
1311
- );
1312
- } catch (error) {
1313
- // リモートブランチの削除に失敗してもローカルの削除は成功として扱う
1314
- printWarning(
1315
- `Failed to delete remote branch: ${error instanceof Error ? error.message : String(error)}`,
1316
- );
1317
- }
1318
- }
1319
-
1320
- results.push({ target, success: true });
1321
- } catch (error) {
1322
- results.push({
1323
- target,
1324
- success: false,
1325
- error: error instanceof Error ? error.message : String(error),
1326
- });
1327
- }
1328
- }
1329
-
1330
- // Display results
1331
- displayCleanupResults(results);
1332
-
1333
- return true;
1334
- } catch (error) {
1335
- printError(
1336
- `Failed to cleanup merged PRs: ${error instanceof Error ? error.message : String(error)}`,
1337
- );
1338
- await confirmContinue("Press enter to continue...");
1339
- return true;
1340
- }
1341
- }
1342
-
1343
- async function handlePostClaudeChanges(worktreePath: string): Promise<void> {
1344
- try {
1345
- // 正確なブランチ名を取得
1346
- const branchName = await getCurrentBranchName(worktreePath);
1347
- const isReleaseBranch = branchName.startsWith("release/");
1348
-
1349
- // リリースブランチの場合は特別な処理
1350
- if (isReleaseBranch) {
1351
- // 変更がある場合は自動的にコミット
1352
- if (await hasUncommittedChanges(worktreePath)) {
1353
- const version = branchName.replace("release/", "");
1354
- const commitMessage = `chore: prepare release ${version}`;
1355
- printInfo(`Committing release changes: ${commitMessage}`);
1356
- await commitChanges(worktreePath, commitMessage);
1357
- printSuccess("Release changes committed successfully!");
1358
- }
1359
-
1360
- // リリースアクションを選択
1361
- const action = await selectReleaseAction();
1362
-
1363
- switch (action) {
1364
- case "complete":
1365
- try {
1366
- await pushBranchToRemote(worktreePath, branchName);
1367
- printSuccess(`Pushed release branch: ${branchName}`);
1368
-
1369
- // GitHub CLIが利用可能か確認
1370
- if (await isGitHubCLIAvailable()) {
1371
- const version = branchName.replace("release/", "");
1372
- printInfo("\nCreating pull request...");
1373
-
1374
- try {
1375
- const { execa } = await import("execa");
1376
- const prTitle = `Release v${version}`;
1377
- const prBody = `## Release v${version}\n\nThis PR contains the release preparation for version ${version}.\n\n### Release Checklist\n- [ ] Review changes\n- [ ] Update changelog if needed\n- [ ] Merge to main\n- [ ] Create tag v${version}\n- [ ] Merge back to develop`;
1378
-
1379
- const { stdout } = await execa(
1380
- "gh",
1381
- [
1382
- "pr",
1383
- "create",
1384
- "--base",
1385
- "main",
1386
- "--head",
1387
- branchName,
1388
- "--title",
1389
- prTitle,
1390
- "--body",
1391
- prBody,
1392
- ],
1393
- { cwd: worktreePath },
1394
- );
1395
-
1396
- printSuccess("Pull request created successfully!");
1397
- printInfo(stdout);
1398
-
1399
- // リリースブランチのworktreeとローカルブランチを削除
1400
- printInfo("\nCleaning up release worktree and local branch...");
1401
- try {
1402
- await removeWorktree(worktreePath, true);
1403
- printSuccess("Release worktree removed successfully.");
1404
-
1405
- // ローカルブランチも削除(リモートブランチは残す)
1406
- await deleteBranch(branchName, true);
1407
- printSuccess(
1408
- `Local branch ${branchName} deleted successfully.`,
1409
- );
1410
-
1411
- printInfo(
1412
- "\nRelease process initiated. The PR is ready for review.",
1413
- );
1414
- printInfo("Remote branch is preserved for the PR.");
1415
- } catch (error) {
1416
- printWarning(
1417
- "Failed to clean up. Please remove worktree/branch manually.",
1418
- );
1419
- }
1420
- } catch (error) {
1421
- printWarning(
1422
- "Failed to create PR automatically. Please create it manually.",
1423
- );
1424
- printInfo("\nGit Flow Release Process:");
1425
- printInfo("1. Create a PR to main branch");
1426
- printInfo("2. After merge, create a tag on main branch");
1427
- printInfo("3. Merge back to develop branch");
1428
- }
1429
- } else {
1430
- printInfo(
1431
- "\nGitHub CLI not found. Please create the PR manually:",
1432
- );
1433
- printInfo("1. Create a PR to main branch");
1434
- printInfo("2. After merge, create a tag on main branch");
1435
- printInfo("3. Merge back to develop branch");
1436
- }
1437
- } catch (error) {
1438
- printError(
1439
- `Failed to push: ${error instanceof Error ? error.message : String(error)}`,
1440
- );
1441
- }
1442
- break;
1443
-
1444
- case "continue":
1445
- printInfo(
1446
- "Release branch saved. You can continue working on it later.",
1447
- );
1448
- break;
1449
-
1450
- case "nothing":
1451
- // Just exit
1452
- break;
1453
- }
1454
- return;
1455
- }
1456
-
1457
- // 通常のブランチの場合は従来の処理
1458
- // Check if there are uncommitted changes
1459
- if (!(await hasUncommittedChanges(worktreePath))) {
1460
- return;
1461
- }
1462
-
1463
- while (true) {
1464
- const action = await selectChangesAction();
1465
-
1466
- switch (action) {
1467
- case "status":
1468
- const status = await showStatus(worktreePath);
1469
- console.log("\n" + status + "\n");
1470
- await confirmContinue("Press enter to continue...");
1471
- break;
1472
-
1473
- case "commit":
1474
- const commitMessage = await inputCommitMessage();
1475
- await commitChanges(worktreePath, commitMessage);
1476
- printSuccess("Changes committed successfully!");
1477
-
1478
- // リリースブランチの場合は、リリースアクションを選択
1479
- if (isReleaseBranch) {
1480
- const action = await selectReleaseAction();
1481
-
1482
- switch (action) {
1483
- case "complete":
1484
- try {
1485
- await pushBranchToRemote(worktreePath, branchName);
1486
- printSuccess(`Pushed release branch: ${branchName}`);
1487
- printInfo("\nGit Flow Release Process:");
1488
- printInfo("1. Create a PR to main branch");
1489
- printInfo("2. After merge, create a tag on main branch");
1490
- printInfo("3. Merge back to develop branch");
1491
- printInfo("\nUse GitHub/GitLab to create the PR.");
1492
- } catch (error) {
1493
- printError(
1494
- `Failed to push: ${error instanceof Error ? error.message : String(error)}`,
1495
- );
1496
- }
1497
- break;
1498
-
1499
- case "continue":
1500
- printInfo(
1501
- "Release branch saved with your commits. You can continue working on it later.",
1502
- );
1503
- break;
1504
-
1505
- case "nothing":
1506
- // Just exit
1507
- break;
1508
- }
1509
- }
1510
- return;
1511
-
1512
- case "stash":
1513
- await stashChanges(worktreePath, "Stashed by Worktree Manager");
1514
- printSuccess("Changes stashed successfully!");
1515
- return;
1516
-
1517
- case "discard":
1518
- if (await confirmDiscardChanges()) {
1519
- await discardAllChanges(worktreePath);
1520
- printSuccess("All changes discarded.");
1521
- return;
1522
- }
1523
- break;
1524
-
1525
- case "continue":
1526
- return;
1527
- }
1528
- }
1529
- } catch (error) {
1530
- printError(
1531
- `Failed to handle changes: ${error instanceof Error ? error.message : String(error)}`,
1532
- );
1533
- await confirmContinue("Press enter to continue...");
1534
- }
1535
- }
1536
-
1537
- // Run the application if this module is executed directly
1538
- if (import.meta.url === `file://${process.argv[1]}`) {
1539
- main().catch((error) => {
1540
- console.error("Fatal error:", error);
1541
- process.exit(1);
1542
- });
1543
- }