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.
@@ -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
- function closeWindowsWithPath(folderPath, issueNumber, prNumber) {
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
- try {
287
- closeWindowsWithPath(wt.path, wt.issueNumber);
288
- // Give windows time to close before removing folder
289
- await new Promise((resolve) => setTimeout(resolve, 500));
290
- }
291
- catch {
292
- // Ignore errors closing windows
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
- try {
444
- closeWindowsWithPath(worktreePath, String(issueNumber));
445
- // Give windows time to close before removing folder
446
- await new Promise((resolve) => setTimeout(resolve, 500));
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
- catch {
450
- windowSpinner.warn('Could not close some windows');
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
- try {
569
- closeWindowsWithPath(wt.path, wt.issueNumber);
570
- await new Promise((resolve) => setTimeout(resolve, 500));
571
- }
572
- catch {
573
- // Ignore errors closing windows
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)
@@ -22,6 +22,7 @@ export interface IssueStatus {
22
22
  state: 'open' | 'closed';
23
23
  }
24
24
  export interface PRStatus {
25
+ number: number;
25
26
  state: 'open' | 'closed' | 'merged';
26
27
  url: string;
27
28
  }
@@ -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.40.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"