claude-issue-solver 1.40.1 → 1.41.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/dist/commands/clean.js +29 -122
- package/dist/utils/github.d.ts +1 -0
- package/dist/utils/github.js +4 -2
- package/dist/utils/terminal.d.ts +35 -0
- package/dist/utils/terminal.js +240 -0
- package/dist/utils/terminal.test.d.ts +1 -0
- package/dist/utils/terminal.test.js +191 -0
- package/package.json +5 -2
package/dist/commands/clean.js
CHANGED
|
@@ -44,110 +44,10 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
44
44
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
45
45
|
const fs = __importStar(require("fs"));
|
|
46
46
|
const path = __importStar(require("path"));
|
|
47
|
-
const os = __importStar(require("os"));
|
|
48
47
|
const child_process_1 = require("child_process");
|
|
49
48
|
const github_1 = require("../utils/github");
|
|
50
49
|
const git_1 = require("../utils/git");
|
|
51
|
-
|
|
52
|
-
if (os.platform() !== 'darwin')
|
|
53
|
-
return;
|
|
54
|
-
const folderName = path.basename(folderPath);
|
|
55
|
-
const issuePattern = `Issue #${issueNumber}`;
|
|
56
|
-
const reviewPattern = prNumber ? `Review PR #${prNumber}` : `issue-${issueNumber}-`;
|
|
57
|
-
// Try to close iTerm2 tabs/windows with this path, issue number, or review PR
|
|
58
|
-
try {
|
|
59
|
-
(0, child_process_1.execSync)(`osascript -e '
|
|
60
|
-
tell application "iTerm"
|
|
61
|
-
repeat with w in windows
|
|
62
|
-
try
|
|
63
|
-
set windowName to name of w
|
|
64
|
-
if windowName contains "${folderName}" or windowName contains "${issuePattern}" or windowName contains "${reviewPattern}" then
|
|
65
|
-
close w
|
|
66
|
-
end if
|
|
67
|
-
end try
|
|
68
|
-
repeat with t in tabs of w
|
|
69
|
-
repeat with s in sessions of t
|
|
70
|
-
try
|
|
71
|
-
set sessionName to name of s
|
|
72
|
-
if sessionName contains "${folderName}" or sessionName contains "${issuePattern}" or sessionName contains "${reviewPattern}" then
|
|
73
|
-
close s
|
|
74
|
-
end if
|
|
75
|
-
end try
|
|
76
|
-
end repeat
|
|
77
|
-
end repeat
|
|
78
|
-
end repeat
|
|
79
|
-
end tell
|
|
80
|
-
'`, { stdio: 'pipe' });
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// iTerm not running or no matching sessions
|
|
84
|
-
}
|
|
85
|
-
// Try to close Terminal.app windows with this path, issue number, or review PR
|
|
86
|
-
try {
|
|
87
|
-
(0, child_process_1.execSync)(`osascript -e '
|
|
88
|
-
tell application "Terminal"
|
|
89
|
-
repeat with w in windows
|
|
90
|
-
set windowName to name of w
|
|
91
|
-
if windowName contains "${folderName}" or windowName contains "${issuePattern}" or windowName contains "${reviewPattern}" then
|
|
92
|
-
close w
|
|
93
|
-
end if
|
|
94
|
-
end repeat
|
|
95
|
-
end tell
|
|
96
|
-
'`, { stdio: 'pipe' });
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Terminal not running or no matching windows
|
|
100
|
-
}
|
|
101
|
-
// Try to close VS Code windows with this path
|
|
102
|
-
// Method 1: AppleScript to close windows matching the folder name
|
|
103
|
-
try {
|
|
104
|
-
(0, child_process_1.execSync)(`osascript -e '
|
|
105
|
-
tell application "System Events"
|
|
106
|
-
if exists process "Code" then
|
|
107
|
-
tell process "Code"
|
|
108
|
-
set windowList to every window
|
|
109
|
-
repeat with w in windowList
|
|
110
|
-
try
|
|
111
|
-
set windowName to name of w
|
|
112
|
-
if windowName contains "${folderName}" then
|
|
113
|
-
perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
|
|
114
|
-
delay 0.2
|
|
115
|
-
end if
|
|
116
|
-
end try
|
|
117
|
-
end repeat
|
|
118
|
-
end tell
|
|
119
|
-
end if
|
|
120
|
-
end tell
|
|
121
|
-
'`, { stdio: 'pipe', timeout: 5000 });
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// VS Code not running or no matching windows
|
|
125
|
-
}
|
|
126
|
-
// Method 2: Also try matching the issue number in window title
|
|
127
|
-
try {
|
|
128
|
-
(0, child_process_1.execSync)(`osascript -e '
|
|
129
|
-
tell application "System Events"
|
|
130
|
-
if exists process "Code" then
|
|
131
|
-
tell process "Code"
|
|
132
|
-
set windowList to every window
|
|
133
|
-
repeat with w in windowList
|
|
134
|
-
try
|
|
135
|
-
set windowName to name of w
|
|
136
|
-
if windowName contains "${issuePattern}" then
|
|
137
|
-
perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
|
|
138
|
-
delay 0.2
|
|
139
|
-
end if
|
|
140
|
-
end try
|
|
141
|
-
end repeat
|
|
142
|
-
end tell
|
|
143
|
-
end if
|
|
144
|
-
end tell
|
|
145
|
-
'`, { stdio: 'pipe', timeout: 5000 });
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
// VS Code not running or no matching windows
|
|
149
|
-
}
|
|
150
|
-
}
|
|
50
|
+
const terminal_1 = require("../utils/terminal");
|
|
151
51
|
function getStatusLabel(wt) {
|
|
152
52
|
if (!wt.branch) {
|
|
153
53
|
return chalk_1.default.yellow('(orphaned folder)');
|
|
@@ -283,14 +183,16 @@ async function cleanAllCommand() {
|
|
|
283
183
|
const spinner = (0, ora_1.default)(`Cleaning issue #${wt.issueNumber}...`).start();
|
|
284
184
|
try {
|
|
285
185
|
// Close terminal and VS Code windows for this worktree
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
186
|
+
// Get PR number for this worktree if available
|
|
187
|
+
const wtStatus = worktreesWithStatus.find((w) => w.issueNumber === wt.issueNumber);
|
|
188
|
+
const prNum = wtStatus?.prStatus?.number?.toString();
|
|
189
|
+
(0, terminal_1.closeWindowsForWorktree)({
|
|
190
|
+
folderPath: wt.path,
|
|
191
|
+
issueNumber: wt.issueNumber,
|
|
192
|
+
prNumber: prNum,
|
|
193
|
+
});
|
|
194
|
+
// Give windows time to close before removing folder
|
|
195
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
294
196
|
// Remove worktree/folder
|
|
295
197
|
const isOrphaned = !wt.branch;
|
|
296
198
|
// Try git worktree remove first (only if not orphaned)
|
|
@@ -440,14 +342,19 @@ async function cleanCommand(issueNumber) {
|
|
|
440
342
|
}
|
|
441
343
|
// Close terminal and VS Code windows for this worktree
|
|
442
344
|
const windowSpinner = (0, ora_1.default)('Closing terminal and VS Code windows...').start();
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
345
|
+
const prNum = prStatus?.number?.toString();
|
|
346
|
+
const closeResult = (0, terminal_1.closeWindowsForWorktree)({
|
|
347
|
+
folderPath: worktreePath,
|
|
348
|
+
issueNumber: String(issueNumber),
|
|
349
|
+
prNumber: prNum,
|
|
350
|
+
});
|
|
351
|
+
// Give windows time to close before removing folder
|
|
352
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
353
|
+
if (closeResult.iTerm || closeResult.terminal || closeResult.vscode) {
|
|
447
354
|
windowSpinner.succeed('Windows closed');
|
|
448
355
|
}
|
|
449
|
-
|
|
450
|
-
windowSpinner.
|
|
356
|
+
else {
|
|
357
|
+
windowSpinner.info('No matching windows found');
|
|
451
358
|
}
|
|
452
359
|
// Remove worktree/folder
|
|
453
360
|
if (fs.existsSync(worktreePath)) {
|
|
@@ -565,13 +472,13 @@ async function cleanMergedCommand() {
|
|
|
565
472
|
const spinner = (0, ora_1.default)(`Cleaning issue #${wt.issueNumber}...`).start();
|
|
566
473
|
try {
|
|
567
474
|
// Close terminal and VS Code windows for this worktree
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
475
|
+
const prNum = wt.prStatus?.number?.toString();
|
|
476
|
+
(0, terminal_1.closeWindowsForWorktree)({
|
|
477
|
+
folderPath: wt.path,
|
|
478
|
+
issueNumber: wt.issueNumber,
|
|
479
|
+
prNumber: prNum,
|
|
480
|
+
});
|
|
481
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
575
482
|
// Remove worktree/folder
|
|
576
483
|
const isOrphaned = !wt.branch;
|
|
577
484
|
// Try git worktree remove first (only if not orphaned)
|
package/dist/utils/github.d.ts
CHANGED
package/dist/utils/github.js
CHANGED
|
@@ -96,11 +96,12 @@ async function getIssueStatusAsync(issueNumber) {
|
|
|
96
96
|
}
|
|
97
97
|
function getPRForBranch(branch) {
|
|
98
98
|
try {
|
|
99
|
-
const output = (0, child_process_1.execSync)(`gh pr list --head "${branch}" --state all --json state,url --limit 1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
99
|
+
const output = (0, child_process_1.execSync)(`gh pr list --head "${branch}" --state all --json number,state,url --limit 1`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
100
100
|
const data = JSON.parse(output);
|
|
101
101
|
if (data.length === 0)
|
|
102
102
|
return null;
|
|
103
103
|
return {
|
|
104
|
+
number: data[0].number,
|
|
104
105
|
state: data[0].state.toLowerCase(),
|
|
105
106
|
url: data[0].url,
|
|
106
107
|
};
|
|
@@ -111,11 +112,12 @@ function getPRForBranch(branch) {
|
|
|
111
112
|
}
|
|
112
113
|
async function getPRForBranchAsync(branch) {
|
|
113
114
|
try {
|
|
114
|
-
const { stdout } = await execAsync(`gh pr list --head "${branch}" --state all --json state,url --limit 1`);
|
|
115
|
+
const { stdout } = await execAsync(`gh pr list --head "${branch}" --state all --json number,state,url --limit 1`);
|
|
115
116
|
const data = JSON.parse(stdout);
|
|
116
117
|
if (data.length === 0)
|
|
117
118
|
return null;
|
|
118
119
|
return {
|
|
120
|
+
number: data[0].number,
|
|
119
121
|
state: data[0].state.toLowerCase(),
|
|
120
122
|
url: data[0].url,
|
|
121
123
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface CloseTerminalOptions {
|
|
2
|
+
folderPath: string;
|
|
3
|
+
issueNumber: string;
|
|
4
|
+
prNumber?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Generates search patterns for matching terminal windows/sessions
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSearchPatterns(options: CloseTerminalOptions): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Generate AppleScript to close iTerm2 windows/tabs matching patterns
|
|
12
|
+
* Uses a two-pass approach: first collect IDs, then close them
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateITermCloseScript(patterns: string[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate AppleScript to close Terminal.app windows matching patterns
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateTerminalCloseScript(patterns: string[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generate AppleScript to close VS Code windows matching patterns
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateVSCodeCloseScript(patterns: string[]): string;
|
|
23
|
+
/**
|
|
24
|
+
* Execute AppleScript and handle errors gracefully
|
|
25
|
+
*/
|
|
26
|
+
export declare function executeAppleScript(script: string, timeout?: number): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Close terminal windows and VS Code windows associated with a worktree
|
|
29
|
+
* Returns an object indicating which applications had windows closed
|
|
30
|
+
*/
|
|
31
|
+
export declare function closeWindowsForWorktree(options: CloseTerminalOptions): {
|
|
32
|
+
iTerm: boolean;
|
|
33
|
+
terminal: boolean;
|
|
34
|
+
vscode: boolean;
|
|
35
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getSearchPatterns = getSearchPatterns;
|
|
37
|
+
exports.generateITermCloseScript = generateITermCloseScript;
|
|
38
|
+
exports.generateTerminalCloseScript = generateTerminalCloseScript;
|
|
39
|
+
exports.generateVSCodeCloseScript = generateVSCodeCloseScript;
|
|
40
|
+
exports.executeAppleScript = executeAppleScript;
|
|
41
|
+
exports.closeWindowsForWorktree = closeWindowsForWorktree;
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
/**
|
|
45
|
+
* Generates search patterns for matching terminal windows/sessions
|
|
46
|
+
*/
|
|
47
|
+
function getSearchPatterns(options) {
|
|
48
|
+
const { folderPath, issueNumber, prNumber } = options;
|
|
49
|
+
const folderName = folderPath.split('/').pop() || '';
|
|
50
|
+
const patterns = [];
|
|
51
|
+
// Folder name pattern (e.g., "project-issue-38-slug")
|
|
52
|
+
if (folderName) {
|
|
53
|
+
patterns.push(folderName);
|
|
54
|
+
}
|
|
55
|
+
// Issue pattern (e.g., "Issue #38")
|
|
56
|
+
patterns.push(`Issue #${issueNumber}`);
|
|
57
|
+
// Branch pattern (e.g., "issue-38-")
|
|
58
|
+
patterns.push(`issue-${issueNumber}-`);
|
|
59
|
+
// PR pattern if provided
|
|
60
|
+
if (prNumber) {
|
|
61
|
+
patterns.push(`PR #${prNumber}`);
|
|
62
|
+
patterns.push(`Review PR #${prNumber}`);
|
|
63
|
+
}
|
|
64
|
+
return patterns;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate AppleScript to close iTerm2 windows/tabs matching patterns
|
|
68
|
+
* Uses a two-pass approach: first collect IDs, then close them
|
|
69
|
+
*/
|
|
70
|
+
function generateITermCloseScript(patterns) {
|
|
71
|
+
// Escape patterns for AppleScript string comparison
|
|
72
|
+
const escapedPatterns = patterns.map((p) => p.replace(/"/g, '\\"'));
|
|
73
|
+
return `
|
|
74
|
+
tell application "iTerm"
|
|
75
|
+
set windowsToClose to {}
|
|
76
|
+
set sessionsToClose to {}
|
|
77
|
+
|
|
78
|
+
-- First pass: collect windows and sessions to close
|
|
79
|
+
repeat with w in windows
|
|
80
|
+
try
|
|
81
|
+
set windowName to name of w
|
|
82
|
+
set windowId to id of w
|
|
83
|
+
${escapedPatterns.map((p) => `if windowName contains "${p}" then set end of windowsToClose to windowId`).join('\n ')}
|
|
84
|
+
end try
|
|
85
|
+
|
|
86
|
+
-- Check tabs and sessions
|
|
87
|
+
repeat with t in tabs of w
|
|
88
|
+
repeat with s in sessions of t
|
|
89
|
+
try
|
|
90
|
+
set sessionName to name of s
|
|
91
|
+
set sessionId to unique id of s
|
|
92
|
+
${escapedPatterns.map((p) => `if sessionName contains "${p}" then set end of sessionsToClose to {windowId, sessionId}`).join('\n ')}
|
|
93
|
+
end try
|
|
94
|
+
end repeat
|
|
95
|
+
end repeat
|
|
96
|
+
end repeat
|
|
97
|
+
|
|
98
|
+
-- Second pass: close sessions first (from windows not being fully closed)
|
|
99
|
+
repeat with sessionInfo in sessionsToClose
|
|
100
|
+
try
|
|
101
|
+
set targetWindowId to item 1 of sessionInfo
|
|
102
|
+
set targetSessionId to item 2 of sessionInfo
|
|
103
|
+
-- Only close session if its window isn't being closed entirely
|
|
104
|
+
if windowsToClose does not contain targetWindowId then
|
|
105
|
+
repeat with w in windows
|
|
106
|
+
if id of w is targetWindowId then
|
|
107
|
+
repeat with t in tabs of w
|
|
108
|
+
repeat with s in sessions of t
|
|
109
|
+
if unique id of s is targetSessionId then
|
|
110
|
+
close s
|
|
111
|
+
end if
|
|
112
|
+
end repeat
|
|
113
|
+
end repeat
|
|
114
|
+
end if
|
|
115
|
+
end repeat
|
|
116
|
+
end if
|
|
117
|
+
end try
|
|
118
|
+
end repeat
|
|
119
|
+
|
|
120
|
+
-- Third pass: close entire windows
|
|
121
|
+
repeat with targetWindowId in windowsToClose
|
|
122
|
+
try
|
|
123
|
+
repeat with w in windows
|
|
124
|
+
if id of w is targetWindowId then
|
|
125
|
+
close w
|
|
126
|
+
exit repeat
|
|
127
|
+
end if
|
|
128
|
+
end repeat
|
|
129
|
+
end try
|
|
130
|
+
end repeat
|
|
131
|
+
end tell
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Generate AppleScript to close Terminal.app windows matching patterns
|
|
136
|
+
*/
|
|
137
|
+
function generateTerminalCloseScript(patterns) {
|
|
138
|
+
const escapedPatterns = patterns.map((p) => p.replace(/"/g, '\\"'));
|
|
139
|
+
const conditions = escapedPatterns.map((p) => `windowName contains "${p}"`).join(' or ');
|
|
140
|
+
return `
|
|
141
|
+
tell application "Terminal"
|
|
142
|
+
set windowsToClose to {}
|
|
143
|
+
|
|
144
|
+
-- First pass: collect window IDs
|
|
145
|
+
repeat with w in windows
|
|
146
|
+
try
|
|
147
|
+
set windowName to name of w
|
|
148
|
+
if ${conditions} then
|
|
149
|
+
set end of windowsToClose to id of w
|
|
150
|
+
end if
|
|
151
|
+
end try
|
|
152
|
+
end repeat
|
|
153
|
+
|
|
154
|
+
-- Second pass: close windows by ID
|
|
155
|
+
repeat with targetId in windowsToClose
|
|
156
|
+
try
|
|
157
|
+
repeat with w in windows
|
|
158
|
+
if id of w is targetId then
|
|
159
|
+
close w
|
|
160
|
+
exit repeat
|
|
161
|
+
end if
|
|
162
|
+
end repeat
|
|
163
|
+
end try
|
|
164
|
+
end repeat
|
|
165
|
+
end tell
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Generate AppleScript to close VS Code windows matching patterns
|
|
170
|
+
*/
|
|
171
|
+
function generateVSCodeCloseScript(patterns) {
|
|
172
|
+
const escapedPatterns = patterns.map((p) => p.replace(/"/g, '\\"'));
|
|
173
|
+
const conditions = escapedPatterns.map((p) => `windowName contains "${p}"`).join(' or ');
|
|
174
|
+
return `
|
|
175
|
+
tell application "System Events"
|
|
176
|
+
if exists process "Code" then
|
|
177
|
+
tell process "Code"
|
|
178
|
+
set windowsToClose to {}
|
|
179
|
+
|
|
180
|
+
-- First pass: collect windows to close
|
|
181
|
+
repeat with w in windows
|
|
182
|
+
try
|
|
183
|
+
set windowName to name of w
|
|
184
|
+
if ${conditions} then
|
|
185
|
+
set end of windowsToClose to w
|
|
186
|
+
end if
|
|
187
|
+
end try
|
|
188
|
+
end repeat
|
|
189
|
+
|
|
190
|
+
-- Second pass: close collected windows
|
|
191
|
+
repeat with targetWindow in windowsToClose
|
|
192
|
+
try
|
|
193
|
+
perform action "AXPress" of (first button of targetWindow whose subrole is "AXCloseButton")
|
|
194
|
+
delay 0.2
|
|
195
|
+
end try
|
|
196
|
+
end repeat
|
|
197
|
+
end tell
|
|
198
|
+
end if
|
|
199
|
+
end tell
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Execute AppleScript and handle errors gracefully
|
|
204
|
+
*/
|
|
205
|
+
function executeAppleScript(script, timeout = 5000) {
|
|
206
|
+
try {
|
|
207
|
+
(0, child_process_1.execSync)(`osascript -e '${script.replace(/'/g, "'\"'\"'")}'`, {
|
|
208
|
+
stdio: 'pipe',
|
|
209
|
+
timeout,
|
|
210
|
+
});
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Close terminal windows and VS Code windows associated with a worktree
|
|
219
|
+
* Returns an object indicating which applications had windows closed
|
|
220
|
+
*/
|
|
221
|
+
function closeWindowsForWorktree(options) {
|
|
222
|
+
if (os.platform() !== 'darwin') {
|
|
223
|
+
return { iTerm: false, terminal: false, vscode: false };
|
|
224
|
+
}
|
|
225
|
+
const patterns = getSearchPatterns(options);
|
|
226
|
+
// Try to close iTerm2 windows/sessions
|
|
227
|
+
const iTermScript = generateITermCloseScript(patterns);
|
|
228
|
+
const iTermResult = executeAppleScript(iTermScript);
|
|
229
|
+
// Try to close Terminal.app windows
|
|
230
|
+
const terminalScript = generateTerminalCloseScript(patterns);
|
|
231
|
+
const terminalResult = executeAppleScript(terminalScript);
|
|
232
|
+
// Try to close VS Code windows
|
|
233
|
+
const vscodeScript = generateVSCodeCloseScript(patterns);
|
|
234
|
+
const vscodeResult = executeAppleScript(vscodeScript, 10000);
|
|
235
|
+
return {
|
|
236
|
+
iTerm: iTermResult,
|
|
237
|
+
terminal: terminalResult,
|
|
238
|
+
vscode: vscodeResult,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const terminal_1 = require("./terminal");
|
|
5
|
+
(0, vitest_1.describe)('terminal utilities', () => {
|
|
6
|
+
(0, vitest_1.describe)('getSearchPatterns', () => {
|
|
7
|
+
(0, vitest_1.it)('should generate patterns for folder path and issue number', () => {
|
|
8
|
+
const options = {
|
|
9
|
+
folderPath: '/Users/test/project-issue-42-fix-bug',
|
|
10
|
+
issueNumber: '42',
|
|
11
|
+
};
|
|
12
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
13
|
+
(0, vitest_1.expect)(patterns).toContain('project-issue-42-fix-bug');
|
|
14
|
+
(0, vitest_1.expect)(patterns).toContain('Issue #42');
|
|
15
|
+
(0, vitest_1.expect)(patterns).toContain('issue-42-');
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)('should include PR patterns when prNumber is provided', () => {
|
|
18
|
+
const options = {
|
|
19
|
+
folderPath: '/Users/test/project-issue-42-fix-bug',
|
|
20
|
+
issueNumber: '42',
|
|
21
|
+
prNumber: '123',
|
|
22
|
+
};
|
|
23
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
24
|
+
(0, vitest_1.expect)(patterns).toContain('PR #123');
|
|
25
|
+
(0, vitest_1.expect)(patterns).toContain('Review PR #123');
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('should not include PR patterns when prNumber is not provided', () => {
|
|
28
|
+
const options = {
|
|
29
|
+
folderPath: '/Users/test/project-issue-42-fix-bug',
|
|
30
|
+
issueNumber: '42',
|
|
31
|
+
};
|
|
32
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
33
|
+
(0, vitest_1.expect)(patterns).not.toContain('PR #');
|
|
34
|
+
(0, vitest_1.expect)(patterns).not.toContain('Review PR #');
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)('should handle paths with only folder name', () => {
|
|
37
|
+
const options = {
|
|
38
|
+
folderPath: 'my-project-issue-1-test',
|
|
39
|
+
issueNumber: '1',
|
|
40
|
+
};
|
|
41
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
42
|
+
(0, vitest_1.expect)(patterns).toContain('my-project-issue-1-test');
|
|
43
|
+
(0, vitest_1.expect)(patterns).toContain('Issue #1');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.describe)('generateITermCloseScript', () => {
|
|
47
|
+
(0, vitest_1.it)('should generate valid AppleScript with patterns', () => {
|
|
48
|
+
const patterns = ['project-issue-42', 'Issue #42'];
|
|
49
|
+
const script = (0, terminal_1.generateITermCloseScript)(patterns);
|
|
50
|
+
(0, vitest_1.expect)(script).toContain('tell application "iTerm"');
|
|
51
|
+
(0, vitest_1.expect)(script).toContain('project-issue-42');
|
|
52
|
+
(0, vitest_1.expect)(script).toContain('Issue #42');
|
|
53
|
+
(0, vitest_1.expect)(script).toContain('windowsToClose');
|
|
54
|
+
(0, vitest_1.expect)(script).toContain('sessionsToClose');
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.it)('should escape double quotes in patterns', () => {
|
|
57
|
+
const patterns = ['Issue "quoted"'];
|
|
58
|
+
const script = (0, terminal_1.generateITermCloseScript)(patterns);
|
|
59
|
+
(0, vitest_1.expect)(script).toContain('Issue \\"quoted\\"');
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.it)('should use two-pass approach for closing', () => {
|
|
62
|
+
const patterns = ['test'];
|
|
63
|
+
const script = (0, terminal_1.generateITermCloseScript)(patterns);
|
|
64
|
+
// First pass collects
|
|
65
|
+
(0, vitest_1.expect)(script).toContain('First pass: collect');
|
|
66
|
+
// Second pass closes sessions
|
|
67
|
+
(0, vitest_1.expect)(script).toContain('Second pass: close sessions');
|
|
68
|
+
// Third pass closes windows
|
|
69
|
+
(0, vitest_1.expect)(script).toContain('Third pass: close entire windows');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.describe)('generateTerminalCloseScript', () => {
|
|
73
|
+
(0, vitest_1.it)('should generate valid AppleScript for Terminal.app', () => {
|
|
74
|
+
const patterns = ['project-issue-42', 'Issue #42'];
|
|
75
|
+
const script = (0, terminal_1.generateTerminalCloseScript)(patterns);
|
|
76
|
+
(0, vitest_1.expect)(script).toContain('tell application "Terminal"');
|
|
77
|
+
(0, vitest_1.expect)(script).toContain('project-issue-42');
|
|
78
|
+
(0, vitest_1.expect)(script).toContain('Issue #42');
|
|
79
|
+
(0, vitest_1.expect)(script).toContain('windowsToClose');
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)('should create OR conditions for multiple patterns', () => {
|
|
82
|
+
const patterns = ['pattern1', 'pattern2'];
|
|
83
|
+
const script = (0, terminal_1.generateTerminalCloseScript)(patterns);
|
|
84
|
+
(0, vitest_1.expect)(script).toContain('windowName contains "pattern1" or windowName contains "pattern2"');
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.it)('should use two-pass approach to avoid iteration issues', () => {
|
|
87
|
+
const patterns = ['test'];
|
|
88
|
+
const script = (0, terminal_1.generateTerminalCloseScript)(patterns);
|
|
89
|
+
(0, vitest_1.expect)(script).toContain('First pass: collect window IDs');
|
|
90
|
+
(0, vitest_1.expect)(script).toContain('Second pass: close windows by ID');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.describe)('generateVSCodeCloseScript', () => {
|
|
94
|
+
(0, vitest_1.it)('should generate valid AppleScript for VS Code', () => {
|
|
95
|
+
const patterns = ['project-issue-42'];
|
|
96
|
+
const script = (0, terminal_1.generateVSCodeCloseScript)(patterns);
|
|
97
|
+
(0, vitest_1.expect)(script).toContain('tell application "System Events"');
|
|
98
|
+
(0, vitest_1.expect)(script).toContain('process "Code"');
|
|
99
|
+
(0, vitest_1.expect)(script).toContain('AXCloseButton');
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)('should handle multiple patterns', () => {
|
|
102
|
+
const patterns = ['folder-name', 'Issue #42'];
|
|
103
|
+
const script = (0, terminal_1.generateVSCodeCloseScript)(patterns);
|
|
104
|
+
(0, vitest_1.expect)(script).toContain('folder-name');
|
|
105
|
+
(0, vitest_1.expect)(script).toContain('Issue #42');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.describe)('closeWindowsForWorktree', () => {
|
|
109
|
+
(0, vitest_1.it)('should return object with boolean results', () => {
|
|
110
|
+
// This test verifies the function returns the expected shape
|
|
111
|
+
// The actual behavior depends on the platform and running applications
|
|
112
|
+
const result = (0, terminal_1.closeWindowsForWorktree)({
|
|
113
|
+
folderPath: '/test/path/project-issue-999-nonexistent',
|
|
114
|
+
issueNumber: '999',
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.expect)(result).toHaveProperty('iTerm');
|
|
117
|
+
(0, vitest_1.expect)(result).toHaveProperty('terminal');
|
|
118
|
+
(0, vitest_1.expect)(result).toHaveProperty('vscode');
|
|
119
|
+
(0, vitest_1.expect)(typeof result.iTerm).toBe('boolean');
|
|
120
|
+
(0, vitest_1.expect)(typeof result.terminal).toBe('boolean');
|
|
121
|
+
(0, vitest_1.expect)(typeof result.vscode).toBe('boolean');
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.it)('should handle prNumber parameter', () => {
|
|
124
|
+
const result = (0, terminal_1.closeWindowsForWorktree)({
|
|
125
|
+
folderPath: '/test/path/project-issue-999-nonexistent',
|
|
126
|
+
issueNumber: '999',
|
|
127
|
+
prNumber: '888',
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.expect)(result).toHaveProperty('iTerm');
|
|
130
|
+
(0, vitest_1.expect)(result).toHaveProperty('terminal');
|
|
131
|
+
(0, vitest_1.expect)(result).toHaveProperty('vscode');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
(0, vitest_1.describe)('pattern matching edge cases', () => {
|
|
135
|
+
(0, vitest_1.it)('should handle issue numbers with leading zeros', () => {
|
|
136
|
+
const options = {
|
|
137
|
+
folderPath: '/path/project-issue-007-bond',
|
|
138
|
+
issueNumber: '007',
|
|
139
|
+
};
|
|
140
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
141
|
+
(0, vitest_1.expect)(patterns).toContain('Issue #007');
|
|
142
|
+
(0, vitest_1.expect)(patterns).toContain('issue-007-');
|
|
143
|
+
});
|
|
144
|
+
(0, vitest_1.it)('should handle long issue titles in folder names', () => {
|
|
145
|
+
const longSlug = 'this-is-a-very-long-issue-title';
|
|
146
|
+
const options = {
|
|
147
|
+
folderPath: `/path/project-issue-42-${longSlug}`,
|
|
148
|
+
issueNumber: '42',
|
|
149
|
+
};
|
|
150
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
151
|
+
(0, vitest_1.expect)(patterns).toContain(`project-issue-42-${longSlug}`);
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('should handle empty folder path gracefully', () => {
|
|
154
|
+
const options = {
|
|
155
|
+
folderPath: '',
|
|
156
|
+
issueNumber: '42',
|
|
157
|
+
};
|
|
158
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
159
|
+
// Should still have issue-related patterns even with empty path
|
|
160
|
+
(0, vitest_1.expect)(patterns).toContain('Issue #42');
|
|
161
|
+
(0, vitest_1.expect)(patterns).toContain('issue-42-');
|
|
162
|
+
});
|
|
163
|
+
(0, vitest_1.it)('should handle paths with special characters', () => {
|
|
164
|
+
const options = {
|
|
165
|
+
folderPath: '/Users/user name/project-issue-42-fix',
|
|
166
|
+
issueNumber: '42',
|
|
167
|
+
};
|
|
168
|
+
const patterns = (0, terminal_1.getSearchPatterns)(options);
|
|
169
|
+
(0, vitest_1.expect)(patterns).toContain('project-issue-42-fix');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
(0, vitest_1.describe)('AppleScript generation robustness', () => {
|
|
173
|
+
(0, vitest_1.it)('should not have nested quotes issues in iTerm script', () => {
|
|
174
|
+
const patterns = ['test"pattern', "another'pattern"];
|
|
175
|
+
const script = (0, terminal_1.generateITermCloseScript)(patterns);
|
|
176
|
+
// Script should be syntactically valid (quotes should be escaped)
|
|
177
|
+
(0, vitest_1.expect)(script).toContain('test\\"pattern');
|
|
178
|
+
(0, vitest_1.expect)(script).not.toContain('""'); // No empty quotes from bad escaping
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.it)('should not have nested quotes issues in Terminal script', () => {
|
|
181
|
+
const patterns = ['test"pattern'];
|
|
182
|
+
const script = (0, terminal_1.generateTerminalCloseScript)(patterns);
|
|
183
|
+
(0, vitest_1.expect)(script).toContain('test\\"pattern');
|
|
184
|
+
});
|
|
185
|
+
(0, vitest_1.it)('should not have nested quotes issues in VSCode script', () => {
|
|
186
|
+
const patterns = ['test"pattern'];
|
|
187
|
+
const script = (0, terminal_1.generateVSCodeCloseScript)(patterns);
|
|
188
|
+
(0, vitest_1.expect)(script).toContain('test\\"pattern');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-issue-solver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"description": "Automatically solve GitHub issues using Claude Code",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"dev": "ts-node src/index.ts",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:run": "vitest run",
|
|
13
15
|
"prepublishOnly": "npm run build",
|
|
14
16
|
"release": "npx ts-node scripts/release.ts"
|
|
15
17
|
},
|
|
@@ -37,7 +39,8 @@
|
|
|
37
39
|
"@types/inquirer": "^8.2.10",
|
|
38
40
|
"@types/node": "^20.10.0",
|
|
39
41
|
"ts-node": "^10.9.2",
|
|
40
|
-
"typescript": "^5.3.0"
|
|
42
|
+
"typescript": "^5.3.0",
|
|
43
|
+
"vitest": "^4.0.17"
|
|
41
44
|
},
|
|
42
45
|
"engines": {
|
|
43
46
|
"node": ">=18.0.0"
|