@akiojin/gwt 2.2.0 → 2.3.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 (149) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/cli/ui/components/App.d.ts +4 -4
  4. package/dist/cli/ui/components/App.d.ts.map +1 -1
  5. package/dist/cli/ui/components/App.js +144 -105
  6. package/dist/cli/ui/components/App.js.map +1 -1
  7. package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
  8. package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
  9. package/dist/cli/ui/components/common/Confirm.js +7 -7
  10. package/dist/cli/ui/components/common/Confirm.js.map +1 -1
  11. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
  12. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
  13. package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
  14. package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
  15. package/dist/cli/ui/components/common/Input.d.ts +2 -2
  16. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  17. package/dist/cli/ui/components/common/Input.js +4 -4
  18. package/dist/cli/ui/components/common/Input.js.map +1 -1
  19. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
  20. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
  21. package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
  22. package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
  23. package/dist/cli/ui/components/common/Select.d.ts +1 -1
  24. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  25. package/dist/cli/ui/components/common/Select.js +11 -12
  26. package/dist/cli/ui/components/common/Select.js.map +1 -1
  27. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +2 -2
  28. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  29. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
  30. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  31. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
  32. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
  34. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
  36. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
  38. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
  40. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
  42. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
  44. package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
  45. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
  46. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
  47. package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
  48. package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
  49. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  50. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
  51. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  52. package/dist/cli/ui/types.d.ts.map +1 -1
  53. package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
  54. package/dist/client/index.html +1 -1
  55. package/dist/config/builtin-tools.d.ts +10 -2
  56. package/dist/config/builtin-tools.d.ts.map +1 -1
  57. package/dist/config/builtin-tools.js +40 -4
  58. package/dist/config/builtin-tools.js.map +1 -1
  59. package/dist/config/index.d.ts.map +1 -1
  60. package/dist/config/index.js.map +1 -1
  61. package/dist/config/tools.d.ts.map +1 -1
  62. package/dist/config/tools.js +4 -3
  63. package/dist/config/tools.js.map +1 -1
  64. package/dist/gemini.d.ts +12 -0
  65. package/dist/gemini.d.ts.map +1 -0
  66. package/dist/gemini.js +154 -0
  67. package/dist/gemini.js.map +1 -0
  68. package/dist/git.d.ts.map +1 -1
  69. package/dist/git.js.map +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +30 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/qwen.d.ts +12 -0
  74. package/dist/qwen.d.ts.map +1 -0
  75. package/dist/qwen.js +154 -0
  76. package/dist/qwen.js.map +1 -0
  77. package/dist/services/git.service.d.ts.map +1 -1
  78. package/dist/services/git.service.js.map +1 -1
  79. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  80. package/dist/web/client/src/components/BranchGraph.js +1 -1
  81. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  82. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  83. package/dist/web/client/src/components/EnvEditor.js +7 -4
  84. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  85. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  86. package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
  87. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  88. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  89. package/dist/web/client/src/pages/BranchListPage.js +10 -4
  90. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  91. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  92. package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
  93. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  94. package/package.json +2 -1
  95. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
  96. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
  97. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
  98. package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
  99. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
  100. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
  101. package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
  102. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +40 -34
  103. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
  104. package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
  105. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
  106. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
  107. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
  108. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
  109. package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
  110. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
  111. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
  112. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
  113. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
  114. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
  115. package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
  116. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
  117. package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
  118. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
  119. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
  120. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
  121. package/src/cli/ui/components/App.tsx +247 -150
  122. package/src/cli/ui/components/common/Confirm.tsx +13 -9
  123. package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
  124. package/src/cli/ui/components/common/Input.tsx +12 -4
  125. package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
  126. package/src/cli/ui/components/common/Select.tsx +28 -17
  127. package/src/cli/ui/components/parts/Header.test.tsx +5 -15
  128. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +19 -13
  129. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
  130. package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
  131. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
  132. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
  133. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
  134. package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
  135. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
  136. package/src/cli/ui/types.ts +8 -1
  137. package/src/config/builtin-tools.ts +42 -4
  138. package/src/config/index.ts +2 -12
  139. package/src/config/tools.ts +16 -6
  140. package/src/gemini.ts +202 -0
  141. package/src/git.ts +2 -1
  142. package/src/index.ts +30 -0
  143. package/src/qwen.ts +208 -0
  144. package/src/services/git.service.ts +2 -1
  145. package/src/web/client/src/components/BranchGraph.tsx +3 -2
  146. package/src/web/client/src/components/EnvEditor.tsx +44 -11
  147. package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
  148. package/src/web/client/src/pages/BranchListPage.tsx +37 -13
  149. package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
@@ -1,22 +1,28 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { useApp } from 'ink';
3
- import { ErrorBoundary } from './common/ErrorBoundary.js';
4
- import { BranchListScreen } from './screens/BranchListScreen.js';
5
- import { WorktreeManagerScreen } from './screens/WorktreeManagerScreen.js';
6
- import { BranchCreatorScreen } from './screens/BranchCreatorScreen.js';
7
- import { BranchActionSelectorScreen } from '../screens/BranchActionSelectorScreen.js';
8
- import { AIToolSelectorScreen } from './screens/AIToolSelectorScreen.js';
9
- import { SessionSelectorScreen } from './screens/SessionSelectorScreen.js';
10
- import { ExecutionModeSelectorScreen } from './screens/ExecutionModeSelectorScreen.js';
11
- import type { AITool } from './screens/AIToolSelectorScreen.js';
12
- import type { ExecutionMode } from './screens/ExecutionModeSelectorScreen.js';
13
- import type { WorktreeItem } from './screens/WorktreeManagerScreen.js';
14
- import { useGitData } from '../hooks/useGitData.js';
15
- import { useScreenState } from '../hooks/useScreenState.js';
16
- import { formatBranchItems } from '../utils/branchFormatter.js';
17
- import { calculateStatistics } from '../utils/statisticsCalculator.js';
18
- import type { BranchInfo, BranchItem, SelectedBranchState } from '../types.js';
19
- import { getRepositoryRoot, deleteBranch } from '../../../git.js';
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from "react";
8
+ import { useApp } from "ink";
9
+ import { ErrorBoundary } from "./common/ErrorBoundary.js";
10
+ import { BranchListScreen } from "./screens/BranchListScreen.js";
11
+ import { WorktreeManagerScreen } from "./screens/WorktreeManagerScreen.js";
12
+ import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
13
+ import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
14
+ import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
15
+ import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
16
+ import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
17
+ import type { AITool } from "./screens/AIToolSelectorScreen.js";
18
+ import type { ExecutionMode } from "./screens/ExecutionModeSelectorScreen.js";
19
+ import type { WorktreeItem } from "./screens/WorktreeManagerScreen.js";
20
+ import { useGitData } from "../hooks/useGitData.js";
21
+ import { useScreenState } from "../hooks/useScreenState.js";
22
+ import { formatBranchItems } from "../utils/branchFormatter.js";
23
+ import { calculateStatistics } from "../utils/statisticsCalculator.js";
24
+ import type { BranchInfo, BranchItem, SelectedBranchState } from "../types.js";
25
+ import { getRepositoryRoot, deleteBranch } from "../../../git.js";
20
26
  import {
21
27
  createWorktree,
22
28
  generateWorktreePath,
@@ -24,30 +30,30 @@ import {
24
30
  isProtectedBranchName,
25
31
  removeWorktree,
26
32
  switchToProtectedBranch,
27
- } from '../../../worktree.js';
28
- import { getPackageVersion } from '../../../utils.js';
33
+ } from "../../../worktree.js";
34
+ import { getPackageVersion } from "../../../utils.js";
29
35
  import {
30
36
  resolveBaseBranchLabel,
31
37
  resolveBaseBranchRef,
32
- } from '../utils/baseBranch.js';
38
+ } from "../utils/baseBranch.js";
33
39
 
34
- const SPINNER_FRAMES = ['', '', '', '', '', '', '', ''];
40
+ const SPINNER_FRAMES = ["", "", "", "", "", "", "", ""];
35
41
  const COMPLETION_HOLD_DURATION_MS = 3000;
36
42
  const PROTECTED_BRANCH_WARNING =
37
- 'Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.';
43
+ "Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.";
38
44
 
39
45
  const getSpinnerFrame = (index: number): string => {
40
46
  const frame = SPINNER_FRAMES[index];
41
- if (typeof frame === 'string') {
47
+ if (typeof frame === "string") {
42
48
  return frame;
43
49
  }
44
- return SPINNER_FRAMES[0] ?? '';
50
+ return SPINNER_FRAMES[0] ?? "";
45
51
  };
46
52
 
47
53
  export interface SelectionResult {
48
54
  branch: string; // Local branch name (without remote prefix)
49
55
  displayName: string; // Name that was selected in the UI (may include remote prefix)
50
- branchType: 'local' | 'remote';
56
+ branchType: "local" | "remote";
51
57
  remoteBranch?: string; // Full remote ref when branchType === 'remote'
52
58
  tool: AITool;
53
59
  mode: ExecutionMode;
@@ -69,24 +75,37 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
69
75
  // 起動ディレクトリの取得
70
76
  const workingDirectory = process.cwd();
71
77
 
72
- const { branches, worktrees, loading, error, refresh, lastUpdated } = useGitData({
73
- enableAutoRefresh: false, // Manual refresh with 'r' key
74
- });
78
+ const { branches, worktrees, loading, error, refresh, lastUpdated } =
79
+ useGitData({
80
+ enableAutoRefresh: false, // Manual refresh with 'r' key
81
+ });
75
82
  const { currentScreen, navigateTo, goBack } = useScreenState();
76
83
 
77
84
  // Version state
78
85
  const [version, setVersion] = useState<string | null>(null);
79
86
 
80
87
  // Selection state (for branch → tool → mode flow)
81
- const [selectedBranch, setSelectedBranch] = useState<SelectedBranchState | null>(null);
82
- const [creationSourceBranch, setCreationSourceBranch] = useState<SelectedBranchState | null>(null);
88
+ const [selectedBranch, setSelectedBranch] =
89
+ useState<SelectedBranchState | null>(null);
90
+ const [creationSourceBranch, setCreationSourceBranch] =
91
+ useState<SelectedBranchState | null>(null);
83
92
  const [selectedTool, setSelectedTool] = useState<AITool | null>(null);
84
93
 
85
94
  // PR cleanup feedback
86
- const [cleanupIndicators, setCleanupIndicators] = useState<Record<string, { icon: string; color?: 'cyan' | 'green' | 'yellow' | 'red' }>>({});
87
- const [cleanupProcessingBranch, setCleanupProcessingBranch] = useState<string | null>(null);
95
+ const [cleanupIndicators, setCleanupIndicators] = useState<
96
+ Record<
97
+ string,
98
+ { icon: string; color?: "cyan" | "green" | "yellow" | "red" }
99
+ >
100
+ >({});
101
+ const [cleanupProcessingBranch, setCleanupProcessingBranch] = useState<
102
+ string | null
103
+ >(null);
88
104
  const [cleanupInputLocked, setCleanupInputLocked] = useState(false);
89
- const [cleanupFooterMessage, setCleanupFooterMessage] = useState<{ text: string; color?: 'cyan' | 'green' | 'yellow' | 'red' } | null>(null);
105
+ const [cleanupFooterMessage, setCleanupFooterMessage] = useState<{
106
+ text: string;
107
+ color?: "cyan" | "green" | "yellow" | "red";
108
+ } | null>(null);
90
109
  const [hiddenBranches, setHiddenBranches] = useState<string[]>([]);
91
110
  const spinnerFrameIndexRef = useRef(0);
92
111
  const [spinnerFrameIndex, setSpinnerFrameIndex] = useState(0);
@@ -107,7 +126,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
107
126
  }
108
127
 
109
128
  const interval = setInterval(() => {
110
- spinnerFrameIndexRef.current = (spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
129
+ spinnerFrameIndexRef.current =
130
+ (spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
111
131
  setSpinnerFrameIndex(spinnerFrameIndexRef.current);
112
132
  }, 120);
113
133
 
@@ -128,20 +148,23 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
128
148
  if (cleanupProcessingBranch) {
129
149
  setCleanupIndicators((prev) => {
130
150
  const current = prev[cleanupProcessingBranch];
131
- if (current && current.icon === frame && current.color === 'cyan') {
151
+ if (current && current.icon === frame && current.color === "cyan") {
132
152
  return prev;
133
153
  }
134
154
 
135
- const next: Record<string, { icon: string; color?: 'cyan' | 'green' | 'yellow' | 'red' }> = {
155
+ const next: Record<
156
+ string,
157
+ { icon: string; color?: "cyan" | "green" | "yellow" | "red" }
158
+ > = {
136
159
  ...prev,
137
- [cleanupProcessingBranch]: { icon: frame, color: 'cyan' },
160
+ [cleanupProcessingBranch]: { icon: frame, color: "cyan" },
138
161
  };
139
162
 
140
163
  return next;
141
164
  });
142
165
  }
143
166
 
144
- setCleanupFooterMessage({ text: `Processing... ${frame}`, color: 'cyan' });
167
+ setCleanupFooterMessage({ text: `Processing... ${frame}`, color: "cyan" });
145
168
  }, [cleanupInputLocked, cleanupProcessingBranch, spinnerFrameIndex]);
146
169
 
147
170
  useEffect(() => {
@@ -157,28 +180,34 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
157
180
  }
158
181
  }, [branches, hiddenBranches]);
159
182
 
160
- useEffect(() => () => {
161
- if (completionTimerRef.current) {
162
- clearTimeout(completionTimerRef.current);
163
- completionTimerRef.current = null;
164
- }
165
- }, []);
183
+ useEffect(
184
+ () => () => {
185
+ if (completionTimerRef.current) {
186
+ clearTimeout(completionTimerRef.current);
187
+ completionTimerRef.current = null;
188
+ }
189
+ },
190
+ [],
191
+ );
166
192
 
167
193
  const visibleBranches = useMemo(
168
194
  () => branches.filter((branch) => !hiddenBranches.includes(branch.name)),
169
- [branches, hiddenBranches]
195
+ [branches, hiddenBranches],
170
196
  );
171
197
 
172
198
  // Helper function to create content-based hash for branches
173
199
  const branchHash = useMemo(
174
- () => visibleBranches.map((b) => `${b.name}-${b.type}-${b.isCurrent}`).join(','),
175
- [visibleBranches]
200
+ () =>
201
+ visibleBranches
202
+ .map((b) => `${b.name}-${b.type}-${b.isCurrent}`)
203
+ .join(","),
204
+ [visibleBranches],
176
205
  );
177
206
 
178
207
  // Helper function to create content-based hash for worktrees
179
208
  const worktreeHash = useMemo(
180
- () => worktrees.map((w) => `${w.branch}-${w.path}`).join(','),
181
- [worktrees]
209
+ () => worktrees.map((w) => `${w.branch}-${w.path}`).join(","),
210
+ [worktrees],
182
211
  );
183
212
 
184
213
  // Format branches to BranchItems (memoized for performance with content-based dependencies)
@@ -197,76 +226,89 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
197
226
  }, [branchHash, worktreeHash, visibleBranches, worktrees]);
198
227
 
199
228
  // Calculate statistics (memoized for performance)
200
- const stats = useMemo(() => calculateStatistics(visibleBranches), [visibleBranches]);
229
+ const stats = useMemo(
230
+ () => calculateStatistics(visibleBranches),
231
+ [visibleBranches],
232
+ );
201
233
 
202
234
  // Format worktrees to WorktreeItems
203
235
  const worktreeItems: WorktreeItem[] = useMemo(
204
236
  () =>
205
- worktrees.map((wt): WorktreeItem => ({
206
- branch: wt.branch,
207
- path: wt.path,
208
- isAccessible: wt.isAccessible ?? true,
209
- })),
210
- [worktrees]
237
+ worktrees.map(
238
+ (wt): WorktreeItem => ({
239
+ branch: wt.branch,
240
+ path: wt.path,
241
+ isAccessible: wt.isAccessible ?? true,
242
+ }),
243
+ ),
244
+ [worktrees],
211
245
  );
212
246
 
213
247
  const resolveBaseBranch = useCallback(() => {
214
248
  const localMain = branches.find(
215
249
  (branch) =>
216
- branch.type === 'local' && (branch.name === 'main' || branch.name === 'master')
250
+ branch.type === "local" &&
251
+ (branch.name === "main" || branch.name === "master"),
217
252
  );
218
253
  if (localMain) {
219
254
  return localMain.name;
220
255
  }
221
256
 
222
257
  const develop = branches.find(
223
- (branch) => branch.type === 'local' && (branch.name === 'develop' || branch.name === 'dev')
258
+ (branch) =>
259
+ branch.type === "local" &&
260
+ (branch.name === "develop" || branch.name === "dev"),
224
261
  );
225
262
  if (develop) {
226
263
  return develop.name;
227
264
  }
228
265
 
229
- return 'main';
266
+ return "main";
230
267
  }, [branches]);
231
268
 
232
269
  const baseBranchLabel = useMemo(
233
- () => resolveBaseBranchLabel(creationSourceBranch, selectedBranch, resolveBaseBranch),
234
- [creationSourceBranch, resolveBaseBranch, selectedBranch]
270
+ () =>
271
+ resolveBaseBranchLabel(
272
+ creationSourceBranch,
273
+ selectedBranch,
274
+ resolveBaseBranch,
275
+ ),
276
+ [creationSourceBranch, resolveBaseBranch, selectedBranch],
235
277
  );
236
278
 
237
279
  // Handle branch selection
238
280
  const toLocalBranchName = useCallback((remoteName: string) => {
239
- const segments = remoteName.split('/');
281
+ const segments = remoteName.split("/");
240
282
  if (segments.length <= 1) {
241
283
  return remoteName;
242
284
  }
243
- return segments.slice(1).join('/');
285
+ return segments.slice(1).join("/");
244
286
  }, []);
245
287
 
246
288
  const inferBranchCategory = useCallback(
247
- (branchName: string): BranchInfo['branchType'] => {
289
+ (branchName: string): BranchInfo["branchType"] => {
248
290
  const matched = branches.find((branch) => branch.name === branchName);
249
291
  if (matched) {
250
292
  return matched.branchType;
251
293
  }
252
- if (branchName === 'main' || branchName === 'master') {
253
- return 'main';
294
+ if (branchName === "main" || branchName === "master") {
295
+ return "main";
254
296
  }
255
- if (branchName === 'develop' || branchName === 'dev') {
256
- return 'develop';
297
+ if (branchName === "develop" || branchName === "dev") {
298
+ return "develop";
257
299
  }
258
- if (branchName.startsWith('feature/')) {
259
- return 'feature';
300
+ if (branchName.startsWith("feature/")) {
301
+ return "feature";
260
302
  }
261
- if (branchName.startsWith('hotfix/')) {
262
- return 'hotfix';
303
+ if (branchName.startsWith("hotfix/")) {
304
+ return "hotfix";
263
305
  }
264
- if (branchName.startsWith('release/')) {
265
- return 'release';
306
+ if (branchName.startsWith("release/")) {
307
+ return "release";
266
308
  }
267
- return 'other';
309
+ return "other";
268
310
  },
269
- [branches]
311
+ [branches],
270
312
  );
271
313
 
272
314
  const isProtectedSelection = useCallback(
@@ -277,12 +319,14 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
277
319
  return (
278
320
  isProtectedBranchName(branch.name) ||
279
321
  isProtectedBranchName(branch.displayName) ||
280
- (branch.remoteBranch ? isProtectedBranchName(branch.remoteBranch) : false) ||
281
- branch.branchCategory === 'main' ||
282
- branch.branchCategory === 'develop'
322
+ (branch.remoteBranch
323
+ ? isProtectedBranchName(branch.remoteBranch)
324
+ : false) ||
325
+ branch.branchCategory === "main" ||
326
+ branch.branchCategory === "develop"
283
327
  );
284
328
  },
285
- [isProtectedBranchName]
329
+ [isProtectedBranchName],
286
330
  );
287
331
 
288
332
  const protectedBranchInfo = useMemo(() => {
@@ -302,18 +346,18 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
302
346
  const handleSelect = useCallback(
303
347
  (item: BranchItem) => {
304
348
  const selection: SelectedBranchState =
305
- item.type === 'remote'
349
+ item.type === "remote"
306
350
  ? {
307
351
  name: toLocalBranchName(item.name),
308
352
  displayName: item.name,
309
- branchType: 'remote',
353
+ branchType: "remote",
310
354
  branchCategory: item.branchType,
311
355
  remoteBranch: item.name,
312
356
  }
313
357
  : {
314
358
  name: item.name,
315
359
  displayName: item.name,
316
- branchType: 'local',
360
+ branchType: "local",
317
361
  branchCategory: item.branchType,
318
362
  };
319
363
 
@@ -326,15 +370,22 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
326
370
  if (protectedSelected) {
327
371
  setCleanupFooterMessage({
328
372
  text: PROTECTED_BRANCH_WARNING,
329
- color: 'yellow',
373
+ color: "yellow",
330
374
  });
331
375
  } else {
332
376
  setCleanupFooterMessage(null);
333
377
  }
334
378
 
335
- navigateTo('branch-action-selector');
379
+ navigateTo("branch-action-selector");
336
380
  },
337
- [isProtectedSelection, navigateTo, setCleanupFooterMessage, setCreationSourceBranch, setSelectedTool, toLocalBranchName]
381
+ [
382
+ isProtectedSelection,
383
+ navigateTo,
384
+ setCleanupFooterMessage,
385
+ setCreationSourceBranch,
386
+ setSelectedTool,
387
+ toLocalBranchName,
388
+ ],
338
389
  );
339
390
 
340
391
  // Handle navigation
@@ -342,7 +393,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
342
393
  (screen: string) => {
343
394
  navigateTo(screen as any);
344
395
  },
345
- [navigateTo]
396
+ [navigateTo],
346
397
  );
347
398
 
348
399
  const handleWorktreeSelect = useCallback(
@@ -350,15 +401,20 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
350
401
  setSelectedBranch({
351
402
  name: worktree.branch,
352
403
  displayName: worktree.branch,
353
- branchType: 'local',
404
+ branchType: "local",
354
405
  branchCategory: inferBranchCategory(worktree.branch),
355
406
  });
356
407
  setSelectedTool(null);
357
408
  setCreationSourceBranch(null);
358
409
  setCleanupFooterMessage(null);
359
- navigateTo('ai-tool-selector');
410
+ navigateTo("ai-tool-selector");
360
411
  },
361
- [inferBranchCategory, navigateTo, setCleanupFooterMessage, setCreationSourceBranch]
412
+ [
413
+ inferBranchCategory,
414
+ navigateTo,
415
+ setCleanupFooterMessage,
416
+ setCreationSourceBranch,
417
+ ],
362
418
  );
363
419
 
364
420
  // Handle branch action selection
@@ -370,13 +426,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
370
426
  try {
371
427
  setCleanupFooterMessage({
372
428
  text: `Preparing root branch '${selectedBranch.displayName ?? selectedBranch.name}'...`,
373
- color: 'cyan',
429
+ color: "cyan",
374
430
  });
375
431
  const repoRoot = await getRepositoryRoot();
376
432
  const remoteRef =
377
433
  selectedBranch.remoteBranch ??
378
- (selectedBranch.branchType === 'remote'
379
- ? selectedBranch.displayName ?? selectedBranch.name
434
+ (selectedBranch.branchType === "remote"
435
+ ? (selectedBranch.displayName ?? selectedBranch.name)
380
436
  : null);
381
437
 
382
438
  const result = await switchToProtectedBranch({
@@ -386,45 +442,44 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
386
442
  });
387
443
 
388
444
  let successMessage = `'${selectedBranch.displayName ?? selectedBranch.name}' will use the repository root.`;
389
- if (result === 'remote') {
445
+ if (result === "remote") {
390
446
  successMessage = `Created a local tracking branch for '${selectedBranch.displayName ?? selectedBranch.name}' and switched to the protected branch.`;
391
- } else if (result === 'local') {
447
+ } else if (result === "local") {
392
448
  successMessage = `Checked out '${selectedBranch.displayName ?? selectedBranch.name}' in the repository root.`;
393
449
  }
394
450
 
395
451
  setCleanupFooterMessage({
396
452
  text: successMessage,
397
- color: 'green',
453
+ color: "green",
398
454
  });
399
455
  refresh();
400
- navigateTo('ai-tool-selector');
456
+ navigateTo("ai-tool-selector");
401
457
  } catch (error) {
402
- const message =
403
- error instanceof Error ? error.message : String(error);
458
+ const message = error instanceof Error ? error.message : String(error);
404
459
  setCleanupFooterMessage({
405
460
  text: `Failed to switch root branch: ${message}`,
406
- color: 'red',
461
+ color: "red",
407
462
  });
408
- console.error('Failed to switch protected branch:', error);
463
+ console.error("Failed to switch protected branch:", error);
409
464
  }
410
- }, [
411
- navigateTo,
412
- refresh,
413
- selectedBranch,
414
- setCleanupFooterMessage,
415
- ]);
465
+ }, [navigateTo, refresh, selectedBranch, setCleanupFooterMessage]);
416
466
 
417
467
  const handleUseExistingBranch = useCallback(() => {
418
468
  if (selectedBranch && isProtectedSelection(selectedBranch)) {
419
469
  void handleProtectedBranchSwitch();
420
470
  return;
421
471
  }
422
- navigateTo('ai-tool-selector');
423
- }, [handleProtectedBranchSwitch, isProtectedSelection, navigateTo, selectedBranch]);
472
+ navigateTo("ai-tool-selector");
473
+ }, [
474
+ handleProtectedBranchSwitch,
475
+ isProtectedSelection,
476
+ navigateTo,
477
+ selectedBranch,
478
+ ]);
424
479
 
425
480
  const handleCreateNewBranch = useCallback(() => {
426
481
  setCreationSourceBranch(selectedBranch);
427
- navigateTo('branch-creator');
482
+ navigateTo("branch-creator");
428
483
  }, [navigateTo, selectedBranch]);
429
484
 
430
485
  // Handle quit
@@ -459,16 +514,16 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
459
514
  setSelectedBranch({
460
515
  name: branchName,
461
516
  displayName: branchName,
462
- branchType: 'local',
517
+ branchType: "local",
463
518
  branchCategory: inferBranchCategory(branchName),
464
519
  });
465
520
  setSelectedTool(null);
466
521
  setCleanupFooterMessage(null);
467
522
 
468
- navigateTo('ai-tool-selector');
523
+ navigateTo("ai-tool-selector");
469
524
  } catch (error) {
470
525
  // On error, go back to branch list
471
- console.error('Failed to create branch:', error);
526
+ console.error("Failed to create branch:", error);
472
527
  goBack();
473
528
  refresh();
474
529
  }
@@ -482,7 +537,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
482
537
  creationSourceBranch,
483
538
  inferBranchCategory,
484
539
  setCleanupFooterMessage,
485
- ]
540
+ ],
486
541
  );
487
542
 
488
543
  const handleCleanupCommand = useCallback(async () => {
@@ -516,7 +571,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
516
571
  setCleanupInputLocked(true);
517
572
  setCleanupIndicators({});
518
573
  const initialFrame = getSpinnerFrame(0);
519
- setCleanupFooterMessage({ text: `Processing... ${initialFrame}`, color: 'cyan' });
574
+ setCleanupFooterMessage({
575
+ text: `Processing... ${initialFrame}`,
576
+ color: "cyan",
577
+ });
520
578
  setCleanupProcessingBranch(null);
521
579
  spinnerFrameIndexRef.current = 0;
522
580
  setSpinnerFrameIndex(0);
@@ -527,7 +585,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
527
585
  } catch (error) {
528
586
  const message = error instanceof Error ? error.message : String(error);
529
587
  setCleanupIndicators({});
530
- setCleanupFooterMessage({ text: `❌ ${message}`, color: 'red' });
588
+ setCleanupFooterMessage({ text: `❌ ${message}`, color: "red" });
531
589
  setCleanupInputLocked(false);
532
590
  completionTimerRef.current = setTimeout(() => {
533
591
  setCleanupFooterMessage(null);
@@ -538,7 +596,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
538
596
 
539
597
  if (targets.length === 0) {
540
598
  setCleanupIndicators({});
541
- setCleanupFooterMessage({ text: '✅ Nothing to clean up.', color: 'green' });
599
+ setCleanupFooterMessage({
600
+ text: "✅ Nothing to clean up.",
601
+ color: "green",
602
+ });
542
603
  setCleanupInputLocked(false);
543
604
  completionTimerRef.current = setTimeout(() => {
544
605
  setCleanupFooterMessage(null);
@@ -548,11 +609,21 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
548
609
  }
549
610
 
550
611
  // Reset hidden branches that may already be gone
551
- setHiddenBranches((prev) => prev.filter((name) => targets.find((t) => t.branch === name) === undefined));
612
+ setHiddenBranches((prev) =>
613
+ prev.filter(
614
+ (name) => targets.find((t) => t.branch === name) === undefined,
615
+ ),
616
+ );
552
617
 
553
- const initialIndicators = targets.reduce<Record<string, { icon: string; color?: 'cyan' | 'green' | 'yellow' | 'red' }>>((acc, target, index) => {
554
- const icon = index === 0 ? getSpinnerFrame(0) : '⏳';
555
- const color: 'cyan' | 'green' | 'yellow' | 'red' = index === 0 ? 'cyan' : 'yellow';
618
+ const initialIndicators = targets.reduce<
619
+ Record<
620
+ string,
621
+ { icon: string; color?: "cyan" | "green" | "yellow" | "red" }
622
+ >
623
+ >((acc, target, index) => {
624
+ const icon = index === 0 ? getSpinnerFrame(0) : "⏳";
625
+ const color: "cyan" | "green" | "yellow" | "red" =
626
+ index === 0 ? "cyan" : "yellow";
556
627
  acc[target.branch] = { icon, color };
557
628
  return acc;
558
629
  }, {});
@@ -562,7 +633,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
562
633
  setCleanupProcessingBranch(firstTarget ? firstTarget.branch : null);
563
634
  spinnerFrameIndexRef.current = 0;
564
635
  setSpinnerFrameIndex(0);
565
- setCleanupFooterMessage({ text: `Processing... ${getSpinnerFrame(0)}`, color: 'cyan' });
636
+ setCleanupFooterMessage({
637
+ text: `Processing... ${getSpinnerFrame(0)}`,
638
+ color: "cyan",
639
+ });
566
640
 
567
641
  for (let index = 0; index < targets.length; index += 1) {
568
642
  const currentTarget = targets[index];
@@ -577,11 +651,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
577
651
 
578
652
  setCleanupIndicators((prev) => {
579
653
  const updated = { ...prev };
580
- updated[target.branch] = { icon: getSpinnerFrame(0), color: 'cyan' };
654
+ updated[target.branch] = { icon: getSpinnerFrame(0), color: "cyan" };
581
655
  for (const pending of targets.slice(index + 1)) {
582
656
  const current = updated[pending.branch];
583
- if (!current || current.icon !== '') {
584
- updated[pending.branch] = { icon: '', color: 'yellow' };
657
+ if (!current || current.icon !== "") {
658
+ updated[pending.branch] = { icon: "", color: "yellow" };
585
659
  }
586
660
  }
587
661
  return updated;
@@ -590,19 +664,23 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
590
664
  const shouldSkip =
591
665
  target.hasUncommittedChanges ||
592
666
  target.hasUnpushedCommits ||
593
- (target.cleanupType === 'worktree-and-branch' && (!target.worktreePath || target.isAccessible === false));
667
+ (target.cleanupType === "worktree-and-branch" &&
668
+ (!target.worktreePath || target.isAccessible === false));
594
669
 
595
670
  if (shouldSkip) {
596
671
  setCleanupIndicators((prev) => ({
597
672
  ...prev,
598
- [target.branch]: { icon: '⏭️', color: 'yellow' },
673
+ [target.branch]: { icon: "⏭️", color: "yellow" },
599
674
  }));
600
675
  setCleanupProcessingBranch(null);
601
676
  continue;
602
677
  }
603
678
 
604
679
  try {
605
- if (target.cleanupType === 'worktree-and-branch' && target.worktreePath) {
680
+ if (
681
+ target.cleanupType === "worktree-and-branch" &&
682
+ target.worktreePath
683
+ ) {
606
684
  await removeWorktree(target.worktreePath, true);
607
685
  }
608
686
 
@@ -610,13 +688,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
610
688
  succeededBranches.push(target.branch);
611
689
  setCleanupIndicators((prev) => ({
612
690
  ...prev,
613
- [target.branch]: { icon: '', color: 'green' },
691
+ [target.branch]: { icon: "", color: "green" },
614
692
  }));
615
693
  } catch {
616
- const icon = '';
694
+ const icon = "";
617
695
  setCleanupIndicators((prev) => ({
618
696
  ...prev,
619
- [target.branch]: { icon, color: 'red' },
697
+ [target.branch]: { icon, color: "red" },
620
698
  }));
621
699
  }
622
700
 
@@ -625,23 +703,32 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
625
703
 
626
704
  setCleanupProcessingBranch(null);
627
705
  setCleanupInputLocked(false);
628
- setCleanupFooterMessage({ text: 'Cleanup completed. Finalizing...', color: 'green' });
706
+ setCleanupFooterMessage({
707
+ text: "Cleanup completed. Finalizing...",
708
+ color: "green",
709
+ });
629
710
 
630
711
  const holdDuration =
631
- typeof process !== 'undefined' && process.env?.NODE_ENV === 'test'
712
+ typeof process !== "undefined" && process.env?.NODE_ENV === "test"
632
713
  ? 0
633
714
  : COMPLETION_HOLD_DURATION_MS;
634
715
 
635
716
  completionTimerRef.current = setTimeout(resetAfterWait, holdDuration);
636
- }, [cleanupInputLocked, deleteBranch, getMergedPRWorktrees, refresh, removeWorktree]);
717
+ }, [
718
+ cleanupInputLocked,
719
+ deleteBranch,
720
+ getMergedPRWorktrees,
721
+ refresh,
722
+ removeWorktree,
723
+ ]);
637
724
 
638
725
  // Handle AI tool selection
639
726
  const handleToolSelect = useCallback(
640
727
  (tool: AITool) => {
641
728
  setSelectedTool(tool);
642
- navigateTo('execution-mode-selector');
729
+ navigateTo("execution-mode-selector");
643
730
  },
644
- [navigateTo]
731
+ [navigateTo],
645
732
  );
646
733
 
647
734
  // Handle session selection
@@ -651,7 +738,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
651
738
  // For now, just go back to branch list
652
739
  goBack();
653
740
  },
654
- [goBack]
741
+ [goBack],
655
742
  );
656
743
 
657
744
  // Handle execution mode and skipPermissions selection
@@ -675,13 +762,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
675
762
  exit();
676
763
  }
677
764
  },
678
- [selectedBranch, selectedTool, onExit, exit]
765
+ [selectedBranch, selectedTool, onExit, exit],
679
766
  );
680
767
 
681
768
  // Render screen based on currentScreen
682
769
  const renderScreen = () => {
683
770
  switch (currentScreen) {
684
- case 'branch-list':
771
+ case "branch-list":
685
772
  return (
686
773
  <BranchListScreen
687
774
  branches={branchItems}
@@ -705,7 +792,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
705
792
  />
706
793
  );
707
794
 
708
- case 'worktree-manager':
795
+ case "worktree-manager":
709
796
  return (
710
797
  <WorktreeManagerScreen
711
798
  worktrees={worktreeItems}
@@ -715,7 +802,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
715
802
  />
716
803
  );
717
804
 
718
- case 'branch-creator':
805
+ case "branch-creator":
719
806
  return (
720
807
  <BranchCreatorScreen
721
808
  onBack={goBack}
@@ -725,10 +812,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
725
812
  />
726
813
  );
727
814
 
728
- case 'branch-action-selector': {
815
+ case "branch-action-selector": {
729
816
  const isProtected = Boolean(protectedBranchInfo);
730
817
  const baseProps = {
731
- selectedBranch: selectedBranch?.displayName ?? '',
818
+ selectedBranch: selectedBranch?.displayName ?? "",
732
819
  onUseExisting: handleUseExistingBranch,
733
820
  onCreateNew: handleCreateNewBranch,
734
821
  onBack: goBack,
@@ -750,10 +837,16 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
750
837
  return <BranchActionSelectorScreen {...baseProps} />;
751
838
  }
752
839
 
753
- case 'ai-tool-selector':
754
- return <AIToolSelectorScreen onBack={goBack} onSelect={handleToolSelect} version={version} />;
840
+ case "ai-tool-selector":
841
+ return (
842
+ <AIToolSelectorScreen
843
+ onBack={goBack}
844
+ onSelect={handleToolSelect}
845
+ version={version}
846
+ />
847
+ );
755
848
 
756
- case 'session-selector':
849
+ case "session-selector":
757
850
  // TODO: Implement session data fetching
758
851
  return (
759
852
  <SessionSelectorScreen
@@ -764,9 +857,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
764
857
  />
765
858
  );
766
859
 
767
- case 'execution-mode-selector':
860
+ case "execution-mode-selector":
768
861
  return (
769
- <ExecutionModeSelectorScreen onBack={goBack} onSelect={handleModeSelect} version={version} />
862
+ <ExecutionModeSelectorScreen
863
+ onBack={goBack}
864
+ onSelect={handleModeSelect}
865
+ version={version}
866
+ />
770
867
  );
771
868
 
772
869
  default: