@akiojin/gwt 4.1.1 → 4.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.
- package/README.md +28 -3
- package/dist/claude.d.ts +4 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +13 -1
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +68 -68
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.d.ts +3 -1
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
- package/dist/cli/ui/components/common/Select.js +13 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +6 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/client/assets/index-ChHC-Puh.css +1 -0
- package/dist/client/assets/index-PqK9jkug.js +78 -0
- package/dist/client/index.html +2 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -0
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +10 -1
- package/dist/config/tools.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -4
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +15 -0
- package/dist/launcher.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts.map +1 -1
- package/dist/services/aiToolResolver.js +55 -8
- package/dist/services/aiToolResolver.js.map +1 -1
- package/dist/services/customToolResolver.d.ts.map +1 -1
- package/dist/services/customToolResolver.js +22 -17
- package/dist/services/customToolResolver.js.map +1 -1
- package/dist/utils/prompt.d.ts +12 -0
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js +60 -10
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/webui.js +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts +5 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +35 -108
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts +15 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js +57 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.js +103 -0
- package/dist/web/client/src/components/graph/BranchNode.js.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.js +109 -0
- package/dist/web/client/src/components/graph/ClusterNode.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts +17 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js +94 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts +13 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js +113 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts +67 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.js +175 -0
- package/dist/web/client/src/components/graph/graphUtils.js.map +1 -0
- package/dist/web/client/src/components/graph/index.d.ts +10 -0
- package/dist/web/client/src/components/graph/index.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/index.js +10 -0
- package/dist/web/client/src/components/graph/index.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +2 -1
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/vite.config.js +1 -1
- package/dist/web/server/env/importer.d.ts.map +1 -1
- package/dist/web/server/env/importer.js +4 -0
- package/dist/web/server/env/importer.js.map +1 -1
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +9 -0
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +24 -1
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +7 -0
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/tray.d.ts +1 -1
- package/dist/web/server/tray.d.ts.map +1 -1
- package/dist/web/server/tray.js +52 -34
- package/dist/web/server/tray.js.map +1 -1
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +4 -0
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +6 -3
- package/src/claude.ts +15 -1
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +2 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +142 -8
- package/src/cli/ui/__tests__/components/App.test.tsx +4 -3
- package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +1 -0
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +45 -0
- package/src/cli/ui/components/App.tsx +91 -81
- package/src/cli/ui/components/common/Select.tsx +14 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
- package/src/cli/ui/types.ts +1 -1
- package/src/config/builtin-tools.ts +3 -0
- package/src/config/tools.ts +24 -1
- package/src/index.ts +50 -3
- package/src/launcher.ts +26 -0
- package/src/services/aiToolResolver.ts +75 -9
- package/src/services/customToolResolver.ts +32 -17
- package/src/utils/__tests__/prompt.test.ts +72 -35
- package/src/utils/prompt.ts +79 -10
- package/src/utils/webui.ts +1 -1
- package/src/web/client/src/components/BranchGraph.tsx +51 -208
- package/src/web/client/src/components/graph/BranchDetailPanel.tsx +152 -0
- package/src/web/client/src/components/graph/BranchNode.tsx +200 -0
- package/src/web/client/src/components/graph/ClusterNode.tsx +211 -0
- package/src/web/client/src/components/graph/SynapticCanvas.tsx +171 -0
- package/src/web/client/src/components/graph/SynapticEdge.tsx +311 -0
- package/src/web/client/src/components/graph/graphUtils.ts +265 -0
- package/src/web/client/src/components/graph/index.ts +10 -0
- package/src/web/client/src/index.css +314 -29
- package/src/web/client/src/lib/websocket.ts +2 -1
- package/src/web/client/vite.config.ts +1 -1
- package/src/web/server/env/importer.ts +5 -0
- package/src/web/server/index.ts +10 -0
- package/src/web/server/pty/manager.ts +43 -1
- package/src/web/server/routes/sessions.ts +15 -0
- package/src/web/server/tray.ts +62 -46
- package/src/web/server/websocket/handler.ts +13 -0
- package/dist/client/assets/index-DsDNCy5f.css +0 -1
- package/dist/client/assets/index-v8smkNOL.js +0 -72
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
AITool,
|
|
33
33
|
BranchInfo,
|
|
34
34
|
BranchItem,
|
|
35
|
+
CleanupTarget,
|
|
35
36
|
InferenceLevel,
|
|
36
37
|
SelectedBranchState,
|
|
37
38
|
} from "../types.js";
|
|
@@ -40,7 +41,6 @@ import { loadSession } from "../../../config/index.js";
|
|
|
40
41
|
import {
|
|
41
42
|
createWorktree,
|
|
42
43
|
generateWorktreePath,
|
|
43
|
-
getMergedPRWorktrees,
|
|
44
44
|
isProtectedBranchName,
|
|
45
45
|
removeWorktree,
|
|
46
46
|
switchToProtectedBranch,
|
|
@@ -166,7 +166,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
166
166
|
} | null>(null);
|
|
167
167
|
const [hiddenBranches, setHiddenBranches] = useState<string[]>([]);
|
|
168
168
|
const [selectedBranches, setSelectedBranches] = useState<string[]>([]);
|
|
169
|
-
const [safeBranches, setSafeBranches] = useState<Set<string>>(new Set());
|
|
170
169
|
const spinnerFrameIndexRef = useRef(0);
|
|
171
170
|
const [spinnerFrameIndex, setSpinnerFrameIndex] = useState(0);
|
|
172
171
|
const completionTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -258,29 +257,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
258
257
|
);
|
|
259
258
|
}, [branches, hiddenBranches]);
|
|
260
259
|
|
|
261
|
-
// Precompute safe-to-clean branches using cleanup candidate logic
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
let cancelled = false;
|
|
264
|
-
(async () => {
|
|
265
|
-
try {
|
|
266
|
-
const targets = await getMergedPRWorktrees();
|
|
267
|
-
if (cancelled) return;
|
|
268
|
-
const safe = new Set(
|
|
269
|
-
targets
|
|
270
|
-
.filter((t) => !t.hasUncommittedChanges && !t.hasUnpushedCommits)
|
|
271
|
-
.map((t) => t.branch),
|
|
272
|
-
);
|
|
273
|
-
setSafeBranches(safe);
|
|
274
|
-
} catch {
|
|
275
|
-
if (cancelled) return;
|
|
276
|
-
setSafeBranches(new Set());
|
|
277
|
-
}
|
|
278
|
-
})();
|
|
279
|
-
return () => {
|
|
280
|
-
cancelled = true;
|
|
281
|
-
};
|
|
282
|
-
}, [branches, worktrees]);
|
|
283
|
-
|
|
284
260
|
// Load quick start options for selected branch (latest per tool)
|
|
285
261
|
useEffect(() => {
|
|
286
262
|
if (!selectedBranch) {
|
|
@@ -520,16 +496,15 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
520
496
|
});
|
|
521
497
|
}
|
|
522
498
|
const baseItems = formatBranchItems(visibleBranches, worktreeMap);
|
|
523
|
-
return baseItems.map((item) =>
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
);
|
|
499
|
+
return baseItems.map((item) => {
|
|
500
|
+
const hasUncommitted = item.worktree?.hasUncommittedChanges ?? false;
|
|
501
|
+
const hasUnpushed = Boolean(item.hasUnpushedCommits);
|
|
502
|
+
const isMerged = Boolean(item.mergedPR);
|
|
503
|
+
const safeToCleanup =
|
|
504
|
+
item.type === "local" && isMerged && !hasUncommitted && !hasUnpushed;
|
|
505
|
+
return { ...item, safeToCleanup };
|
|
506
|
+
});
|
|
507
|
+
}, [branchHash, worktreeHash, visibleBranches, worktrees]);
|
|
533
508
|
|
|
534
509
|
// Calculate statistics (memoized for performance)
|
|
535
510
|
const stats = useMemo(
|
|
@@ -622,17 +597,31 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
622
597
|
[isProtectedBranchName],
|
|
623
598
|
);
|
|
624
599
|
|
|
625
|
-
const toggleBranchSelection = useCallback(
|
|
626
|
-
|
|
627
|
-
const
|
|
628
|
-
if (
|
|
629
|
-
|
|
630
|
-
} else {
|
|
631
|
-
set.add(branchName);
|
|
600
|
+
const toggleBranchSelection = useCallback(
|
|
601
|
+
(branchName: string) => {
|
|
602
|
+
const branch = branches.find((b) => b.name === branchName);
|
|
603
|
+
if (!branch || branch.type === "remote") {
|
|
604
|
+
return;
|
|
632
605
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
606
|
+
if (
|
|
607
|
+
isProtectedBranchName(branch.name) ||
|
|
608
|
+
branch.branchType === "main" ||
|
|
609
|
+
branch.branchType === "develop"
|
|
610
|
+
) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
setSelectedBranches((prev) => {
|
|
614
|
+
const set = new Set(prev);
|
|
615
|
+
if (set.has(branchName)) {
|
|
616
|
+
set.delete(branchName);
|
|
617
|
+
} else {
|
|
618
|
+
set.add(branchName);
|
|
619
|
+
}
|
|
620
|
+
return Array.from(set);
|
|
621
|
+
});
|
|
622
|
+
},
|
|
623
|
+
[branches, isProtectedBranchName],
|
|
624
|
+
);
|
|
636
625
|
|
|
637
626
|
const protectedBranchInfo = useMemo(() => {
|
|
638
627
|
if (!selectedBranch) {
|
|
@@ -846,6 +835,20 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
846
835
|
completionTimerRef.current = null;
|
|
847
836
|
}
|
|
848
837
|
|
|
838
|
+
if (selectedBranches.length === 0) {
|
|
839
|
+
setCleanupIndicators({});
|
|
840
|
+
setCleanupFooterMessage({
|
|
841
|
+
text: "クリーンアップ対象が選択されていません",
|
|
842
|
+
color: "yellow",
|
|
843
|
+
});
|
|
844
|
+
setCleanupInputLocked(false);
|
|
845
|
+
completionTimerRef.current = setTimeout(() => {
|
|
846
|
+
setCleanupFooterMessage(null);
|
|
847
|
+
completionTimerRef.current = null;
|
|
848
|
+
}, COMPLETION_HOLD_DURATION_MS);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
849
852
|
const succeededBranches: string[] = [];
|
|
850
853
|
|
|
851
854
|
const resetAfterWait = () => {
|
|
@@ -878,26 +881,50 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
878
881
|
spinnerFrameIndexRef.current = 0;
|
|
879
882
|
setSpinnerFrameIndex(0);
|
|
880
883
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
884
|
+
const branchMap = new Map(branches.map((branch) => [branch.name, branch]));
|
|
885
|
+
const worktreeMap = new Map(
|
|
886
|
+
worktrees.map((worktree) => [worktree.branch, worktree]),
|
|
887
|
+
);
|
|
888
|
+
const targets = selectedBranches.reduce<CleanupTarget[]>((acc, name) => {
|
|
889
|
+
const branch = branchMap.get(name);
|
|
890
|
+
if (!branch || branch.type === "remote") {
|
|
891
|
+
return acc;
|
|
892
|
+
}
|
|
893
|
+
if (
|
|
894
|
+
isProtectedBranchName(branch.name) ||
|
|
895
|
+
branch.branchType === "main" ||
|
|
896
|
+
branch.branchType === "develop"
|
|
897
|
+
) {
|
|
898
|
+
return acc;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const worktree = worktreeMap.get(branch.name);
|
|
902
|
+
const hasRemoteBranch =
|
|
903
|
+
typeof branch.hasRemoteCounterpart === "boolean"
|
|
904
|
+
? branch.hasRemoteCounterpart
|
|
905
|
+
: undefined;
|
|
906
|
+
const isAccessible =
|
|
907
|
+
typeof worktree?.isAccessible === "boolean"
|
|
908
|
+
? worktree.isAccessible
|
|
909
|
+
: undefined;
|
|
910
|
+
acc.push({
|
|
911
|
+
branch: branch.name,
|
|
912
|
+
pullRequest: null,
|
|
913
|
+
worktreePath: worktree?.path ?? null,
|
|
914
|
+
cleanupType: worktree ? "worktree-and-branch" : "branch-only",
|
|
915
|
+
hasUncommittedChanges: worktree?.hasUncommittedChanges ?? false,
|
|
916
|
+
hasUnpushedCommits: Boolean(branch.hasUnpushedCommits),
|
|
917
|
+
...(hasRemoteBranch !== undefined ? { hasRemoteBranch } : {}),
|
|
918
|
+
...(isAccessible !== undefined ? { isAccessible } : {}),
|
|
919
|
+
});
|
|
920
|
+
return acc;
|
|
921
|
+
}, []);
|
|
895
922
|
|
|
896
923
|
if (targets.length === 0) {
|
|
897
924
|
setCleanupIndicators({});
|
|
898
925
|
setCleanupFooterMessage({
|
|
899
|
-
text: "
|
|
900
|
-
color: "
|
|
926
|
+
text: "⚠️ No cleanup candidates among selected branches.",
|
|
927
|
+
color: "yellow",
|
|
901
928
|
});
|
|
902
929
|
setCleanupInputLocked(false);
|
|
903
930
|
completionTimerRef.current = setTimeout(() => {
|
|
@@ -907,24 +934,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
907
934
|
return;
|
|
908
935
|
}
|
|
909
936
|
|
|
910
|
-
// Manual selection: restrict targets when選択がある
|
|
911
|
-
if (selectedBranchSet.size > 0) {
|
|
912
|
-
targets = targets.filter((t) => selectedBranchSet.has(t.branch));
|
|
913
|
-
if (targets.length === 0) {
|
|
914
|
-
setCleanupIndicators({});
|
|
915
|
-
setCleanupFooterMessage({
|
|
916
|
-
text: "⚠️ No cleanup candidates among selected branches.",
|
|
917
|
-
color: "yellow",
|
|
918
|
-
});
|
|
919
|
-
setCleanupInputLocked(false);
|
|
920
|
-
completionTimerRef.current = setTimeout(() => {
|
|
921
|
-
setCleanupFooterMessage(null);
|
|
922
|
-
completionTimerRef.current = null;
|
|
923
|
-
}, COMPLETION_HOLD_DURATION_MS);
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
937
|
// Reset hidden branches that may already be gone
|
|
929
938
|
setHiddenBranches((prev) =>
|
|
930
939
|
prev.filter(
|
|
@@ -1037,11 +1046,12 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
1037
1046
|
completionTimerRef.current = setTimeout(resetAfterWait, holdDuration);
|
|
1038
1047
|
}, [
|
|
1039
1048
|
cleanupInputLocked,
|
|
1049
|
+
branches,
|
|
1040
1050
|
deleteBranch,
|
|
1041
|
-
getMergedPRWorktrees,
|
|
1042
1051
|
refresh,
|
|
1043
1052
|
removeWorktree,
|
|
1044
|
-
|
|
1053
|
+
selectedBranches,
|
|
1054
|
+
worktrees,
|
|
1045
1055
|
]);
|
|
1046
1056
|
|
|
1047
1057
|
// Handle AI tool selection
|
|
@@ -28,6 +28,8 @@ export interface SelectProps<T extends SelectItem = SelectItem> {
|
|
|
28
28
|
// Optional controlled component props for cursor position
|
|
29
29
|
selectedIndex?: number;
|
|
30
30
|
onSelectedIndexChange?: (index: number) => void;
|
|
31
|
+
onSpace?: (item: T) => void;
|
|
32
|
+
onEscape?: () => void;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
@@ -47,7 +49,9 @@ function arePropsEqual<T extends SelectItem = SelectItem>(
|
|
|
47
49
|
prevProps.onSelect !== nextProps.onSelect ||
|
|
48
50
|
prevProps.onSelectedIndexChange !== nextProps.onSelectedIndexChange ||
|
|
49
51
|
prevProps.renderIndicator !== nextProps.renderIndicator ||
|
|
50
|
-
prevProps.renderItem !== nextProps.renderItem
|
|
52
|
+
prevProps.renderItem !== nextProps.renderItem ||
|
|
53
|
+
prevProps.onSpace !== nextProps.onSpace ||
|
|
54
|
+
prevProps.onEscape !== nextProps.onEscape
|
|
51
55
|
) {
|
|
52
56
|
return false;
|
|
53
57
|
}
|
|
@@ -93,6 +97,8 @@ const SelectComponent = <T extends SelectItem = SelectItem>({
|
|
|
93
97
|
renderItem,
|
|
94
98
|
selectedIndex: externalSelectedIndex,
|
|
95
99
|
onSelectedIndexChange,
|
|
100
|
+
onSpace,
|
|
101
|
+
onEscape,
|
|
96
102
|
}: SelectProps<T>) => {
|
|
97
103
|
// Support both controlled and uncontrolled modes
|
|
98
104
|
const [internalSelectedIndex, setInternalSelectedIndex] =
|
|
@@ -177,6 +183,13 @@ const SelectComponent = <T extends SelectItem = SelectItem>({
|
|
|
177
183
|
if (selectedItem && !disabled) {
|
|
178
184
|
onSelect(selectedItem);
|
|
179
185
|
}
|
|
186
|
+
} else if (input === ' ' && onSpace) {
|
|
187
|
+
const selectedItem = items[selectedIndex];
|
|
188
|
+
if (selectedItem && !disabled) {
|
|
189
|
+
onSpace(selectedItem);
|
|
190
|
+
}
|
|
191
|
+
} else if (key.escape && onEscape) {
|
|
192
|
+
onEscape();
|
|
180
193
|
}
|
|
181
194
|
// All other keys are ignored and will propagate to parent components
|
|
182
195
|
});
|
|
@@ -407,7 +407,12 @@ export function BranchListScreen({
|
|
|
407
407
|
const indicatorPrefix = indicatorIcon ? `${indicatorIcon} ` : "";
|
|
408
408
|
|
|
409
409
|
const isChecked = selectedSet.has(item.name);
|
|
410
|
-
const
|
|
410
|
+
const isWarning = Boolean(item.hasUnpushedCommits) || !item.mergedPR;
|
|
411
|
+
const selectionIcon = isChecked
|
|
412
|
+
? isWarning
|
|
413
|
+
? chalk.red("[*]")
|
|
414
|
+
: "[*]"
|
|
415
|
+
: "[ ]";
|
|
411
416
|
let worktreeIcon = chalk.gray("⚪");
|
|
412
417
|
if (item.worktreeStatus === "active") {
|
|
413
418
|
worktreeIcon = chalk.green("🟢");
|
package/src/cli/ui/types.ts
CHANGED
|
@@ -231,7 +231,7 @@ export interface BranchItem extends BranchInfo {
|
|
|
231
231
|
syncStatus?: SyncStatus;
|
|
232
232
|
syncInfo?: string | undefined;
|
|
233
233
|
remoteName?: string | undefined;
|
|
234
|
-
//
|
|
234
|
+
// クリーンアップ判定で「未コミット/未プッシュなし かつマージ済み」と評価された場合に true
|
|
235
235
|
safeToCleanup?: boolean;
|
|
236
236
|
}
|
|
237
237
|
|
package/src/config/tools.ts
CHANGED
|
@@ -21,8 +21,11 @@ import type {
|
|
|
21
21
|
AIToolConfig,
|
|
22
22
|
} from "../types/tools.js";
|
|
23
23
|
import { BUILTIN_TOOLS } from "./builtin-tools.js";
|
|
24
|
+
import { createLogger } from "../logging/logger.js";
|
|
24
25
|
import { resolveProfileEnv } from "./profiles.js";
|
|
25
26
|
|
|
27
|
+
const logger = createLogger({ category: "config" });
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
30
|
* ツール設定ファイルのパス
|
|
28
31
|
* 環境変数の優先順位: GWT_HOME > CLAUDE_WORKTREE_HOME (後方互換性) > ホームディレクトリ
|
|
@@ -64,6 +67,10 @@ async function migrateLegacyConfig(): Promise<void> {
|
|
|
64
67
|
// レガシーディレクトリを新しいディレクトリにコピー
|
|
65
68
|
await mkdir(path.dirname(CONFIG_DIR), { recursive: true });
|
|
66
69
|
await cp(LEGACY_CONFIG_DIR, CONFIG_DIR, { recursive: true });
|
|
70
|
+
logger.info(
|
|
71
|
+
{ from: LEGACY_CONFIG_DIR, to: CONFIG_DIR },
|
|
72
|
+
"Legacy config migrated",
|
|
73
|
+
);
|
|
67
74
|
console.log(
|
|
68
75
|
`✅ Migrated configuration from ${LEGACY_CONFIG_DIR} to ${CONFIG_DIR}`,
|
|
69
76
|
);
|
|
@@ -101,6 +108,11 @@ export async function loadToolsConfig(): Promise<ToolsConfig> {
|
|
|
101
108
|
// 検証
|
|
102
109
|
validateToolsConfig(config);
|
|
103
110
|
|
|
111
|
+
logger.debug(
|
|
112
|
+
{ path: TOOLS_CONFIG_PATH, toolCount: config.customTools.length },
|
|
113
|
+
"Tools config loaded",
|
|
114
|
+
);
|
|
115
|
+
|
|
104
116
|
return {
|
|
105
117
|
...config,
|
|
106
118
|
env: config.env ?? {},
|
|
@@ -108,11 +120,19 @@ export async function loadToolsConfig(): Promise<ToolsConfig> {
|
|
|
108
120
|
} catch (error) {
|
|
109
121
|
// ファイルが存在しない場合は空配列を返す
|
|
110
122
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
123
|
+
logger.debug(
|
|
124
|
+
{ path: TOOLS_CONFIG_PATH },
|
|
125
|
+
"Tools config not found, using defaults",
|
|
126
|
+
);
|
|
111
127
|
return { ...DEFAULT_CONFIG };
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
// JSON構文エラーの場合
|
|
115
131
|
if (error instanceof SyntaxError) {
|
|
132
|
+
logger.error(
|
|
133
|
+
{ path: TOOLS_CONFIG_PATH, error: error.message },
|
|
134
|
+
"Tools config parse error",
|
|
135
|
+
);
|
|
116
136
|
throw new Error(
|
|
117
137
|
`Failed to parse tools.json: ${error.message}\n` +
|
|
118
138
|
`Please check the JSON syntax in ${TOOLS_CONFIG_PATH}`,
|
|
@@ -283,12 +303,15 @@ export async function getToolById(
|
|
|
283
303
|
// ビルトインツールから検索
|
|
284
304
|
const builtinTool = BUILTIN_TOOLS.find((t) => t.id === id);
|
|
285
305
|
if (builtinTool) {
|
|
306
|
+
logger.debug({ id, found: true, isBuiltin: true }, "Tool lookup");
|
|
286
307
|
return builtinTool;
|
|
287
308
|
}
|
|
288
309
|
|
|
289
310
|
// カスタムツールから検索
|
|
290
311
|
const config = await loadToolsConfig();
|
|
291
|
-
|
|
312
|
+
const customTool = config.customTools.find((t) => t.id === id);
|
|
313
|
+
logger.debug({ id, found: !!customTool, isBuiltin: false }, "Tool lookup");
|
|
314
|
+
return customTool;
|
|
292
315
|
}
|
|
293
316
|
|
|
294
317
|
/**
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
fetchAllRemotes,
|
|
8
8
|
pullFastForward,
|
|
9
9
|
getBranchDivergenceStatuses,
|
|
10
|
+
hasUncommittedChanges,
|
|
11
|
+
hasUnpushedCommits,
|
|
12
|
+
getUncommittedChangesCount,
|
|
13
|
+
getUnpushedCommitsCount,
|
|
14
|
+
pushBranchToRemote,
|
|
10
15
|
GitError,
|
|
11
16
|
} from "./git.js";
|
|
12
17
|
import { launchClaudeCode } from "./claude.js";
|
|
@@ -50,11 +55,12 @@ import {
|
|
|
50
55
|
DependencyInstallError,
|
|
51
56
|
type DependencyInstallResult,
|
|
52
57
|
} from "./services/dependency-installer.js";
|
|
53
|
-
import { waitForEnter } from "./utils/prompt.js";
|
|
58
|
+
import { confirmYesNo, waitForEnter } from "./utils/prompt.js";
|
|
54
59
|
|
|
55
60
|
const ERROR_PROMPT = chalk.yellow(
|
|
56
61
|
"Review the error details, then press Enter to continue.",
|
|
57
62
|
);
|
|
63
|
+
const POST_SESSION_DELAY_MS = 3000;
|
|
58
64
|
|
|
59
65
|
// Category: cli
|
|
60
66
|
const appLogger = createLogger({ category: "cli" });
|
|
@@ -203,7 +209,7 @@ Worktree Manager
|
|
|
203
209
|
Usage: gwt [command] [options]
|
|
204
210
|
|
|
205
211
|
Commands:
|
|
206
|
-
serve Start Web UI server (http://localhost:
|
|
212
|
+
serve Start Web UI server (http://localhost:3001)
|
|
207
213
|
|
|
208
214
|
Options:
|
|
209
215
|
-h, --help Show this help message
|
|
@@ -602,6 +608,7 @@ export async function handleAIToolWorkflow(
|
|
|
602
608
|
envOverrides?: Record<string, string>;
|
|
603
609
|
model?: string;
|
|
604
610
|
sessionId?: string | null;
|
|
611
|
+
chrome?: boolean;
|
|
605
612
|
} = {
|
|
606
613
|
mode:
|
|
607
614
|
mode === "resume"
|
|
@@ -612,6 +619,7 @@ export async function handleAIToolWorkflow(
|
|
|
612
619
|
skipPermissions,
|
|
613
620
|
envOverrides: sharedEnv,
|
|
614
621
|
sessionId: resumeSessionId,
|
|
622
|
+
chrome: true,
|
|
615
623
|
};
|
|
616
624
|
if (normalizedModel) {
|
|
617
625
|
launchOptions.model = normalizedModel;
|
|
@@ -757,8 +765,47 @@ export async function handleAIToolWorkflow(
|
|
|
757
765
|
lastSessionId: finalSessionId,
|
|
758
766
|
});
|
|
759
767
|
|
|
768
|
+
try {
|
|
769
|
+
const [hasUncommitted, hasUnpushed] = await Promise.all([
|
|
770
|
+
hasUncommittedChanges(worktreePath),
|
|
771
|
+
hasUnpushedCommits(worktreePath, branch),
|
|
772
|
+
]);
|
|
773
|
+
|
|
774
|
+
if (hasUncommitted) {
|
|
775
|
+
const uncommittedCount = await getUncommittedChangesCount(worktreePath);
|
|
776
|
+
const countLabel =
|
|
777
|
+
uncommittedCount > 0 ? ` (${uncommittedCount}件)` : "";
|
|
778
|
+
printWarning(`未コミットの変更があります${countLabel}。`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (hasUnpushed) {
|
|
782
|
+
const unpushedCount = await getUnpushedCommitsCount(
|
|
783
|
+
worktreePath,
|
|
784
|
+
branch,
|
|
785
|
+
);
|
|
786
|
+
const countLabel = unpushedCount > 0 ? ` (${unpushedCount}件)` : "";
|
|
787
|
+
const shouldPush = await confirmYesNo(
|
|
788
|
+
`未プッシュのコミットがあります${countLabel}。プッシュしますか?`,
|
|
789
|
+
{ defaultValue: false },
|
|
790
|
+
);
|
|
791
|
+
if (shouldPush) {
|
|
792
|
+
printInfo(`Pushing origin/${branch}...`);
|
|
793
|
+
try {
|
|
794
|
+
await pushBranchToRemote(worktreePath, branch);
|
|
795
|
+
printInfo(`Push completed for ${branch}.`);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
const details =
|
|
798
|
+
error instanceof Error ? error.message : String(error);
|
|
799
|
+
printWarning(`Push failed for ${branch}: ${details}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} catch (error) {
|
|
804
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
805
|
+
printWarning(`Failed to check git status after session: ${details}`);
|
|
806
|
+
}
|
|
760
807
|
// Small buffer before returning to branch list to avoid abrupt screen swap
|
|
761
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
808
|
+
await new Promise((resolve) => setTimeout(resolve, POST_SESSION_DELAY_MS));
|
|
762
809
|
printInfo("Session completed successfully. Returning to main menu...");
|
|
763
810
|
return;
|
|
764
811
|
} catch (error) {
|
package/src/launcher.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import { execa } from "execa";
|
|
9
9
|
import type { CustomAITool, LaunchOptions } from "./types/tools.js";
|
|
10
|
+
import { createLogger } from "./logging/logger.js";
|
|
11
|
+
|
|
12
|
+
const logger = createLogger({ category: "launcher" });
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* コマンド名をPATH環境変数から解決
|
|
@@ -28,16 +31,22 @@ export async function resolveCommand(commandName: string): Promise<string> {
|
|
|
28
31
|
const resolvedPath = (result.stdout.split("\n")[0] ?? "").trim();
|
|
29
32
|
|
|
30
33
|
if (!resolvedPath) {
|
|
34
|
+
logger.error({ commandName }, "Command not found in PATH");
|
|
31
35
|
throw new Error(
|
|
32
36
|
`Command "${commandName}" not found in PATH.\n` +
|
|
33
37
|
`Please ensure the command is installed and available in your PATH environment variable.`,
|
|
34
38
|
);
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
logger.debug({ commandName, resolvedPath }, "Command resolved");
|
|
37
42
|
return resolvedPath;
|
|
38
43
|
} catch (error) {
|
|
39
44
|
// which/whereコマンド自体が失敗した場合
|
|
40
45
|
if (error instanceof Error) {
|
|
46
|
+
logger.error(
|
|
47
|
+
{ commandName, error: error.message },
|
|
48
|
+
"Command resolution failed",
|
|
49
|
+
);
|
|
41
50
|
throw new Error(
|
|
42
51
|
`Failed to resolve command "${commandName}".\n` +
|
|
43
52
|
`Error: ${error.message}\n` +
|
|
@@ -78,6 +87,10 @@ function buildArgs(tool: CustomAITool, options: LaunchOptions): string[] {
|
|
|
78
87
|
args.push(...options.extraArgs);
|
|
79
88
|
}
|
|
80
89
|
|
|
90
|
+
logger.debug(
|
|
91
|
+
{ toolId: tool.id, mode: options.mode ?? "normal", argsCount: args.length },
|
|
92
|
+
"Args built",
|
|
93
|
+
);
|
|
81
94
|
return args;
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -110,10 +123,21 @@ export async function launchCustomAITool(
|
|
|
110
123
|
env,
|
|
111
124
|
};
|
|
112
125
|
|
|
126
|
+
logger.info(
|
|
127
|
+
{
|
|
128
|
+
toolId: tool.id,
|
|
129
|
+
toolType: tool.type,
|
|
130
|
+
command: tool.command,
|
|
131
|
+
mode: options.mode ?? "normal",
|
|
132
|
+
},
|
|
133
|
+
"Launching custom AI tool",
|
|
134
|
+
);
|
|
135
|
+
|
|
113
136
|
switch (tool.type) {
|
|
114
137
|
case "path": {
|
|
115
138
|
// 絶対パスで直接実行
|
|
116
139
|
await execa(tool.command, args, execaOptions);
|
|
140
|
+
logger.info({ toolId: tool.id }, "Custom AI tool completed (path)");
|
|
117
141
|
break;
|
|
118
142
|
}
|
|
119
143
|
|
|
@@ -121,6 +145,7 @@ export async function launchCustomAITool(
|
|
|
121
145
|
// bunx経由でパッケージ実行
|
|
122
146
|
// bunx [package] [args...]
|
|
123
147
|
await execa("bunx", [tool.command, ...args], execaOptions);
|
|
148
|
+
logger.info({ toolId: tool.id }, "Custom AI tool completed (bunx)");
|
|
124
149
|
break;
|
|
125
150
|
}
|
|
126
151
|
|
|
@@ -128,6 +153,7 @@ export async function launchCustomAITool(
|
|
|
128
153
|
// PATH解決 → 実行
|
|
129
154
|
const resolvedPath = await resolveCommand(tool.command);
|
|
130
155
|
await execa(resolvedPath, args, execaOptions);
|
|
156
|
+
logger.info({ toolId: tool.id }, "Custom AI tool completed (command)");
|
|
131
157
|
break;
|
|
132
158
|
}
|
|
133
159
|
|