claude-issue-solver 1.42.0 → 1.43.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.
@@ -3,6 +3,16 @@ export declare function checkRequirements(): {
3
3
  ok: boolean;
4
4
  missing: string[];
5
5
  };
6
+ /**
7
+ * Generate AppleScript for opening a script in iTerm2.
8
+ * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
9
+ */
10
+ export declare function generateITermOpenScript(script: string): string;
11
+ /**
12
+ * Generate AppleScript for opening a script in Terminal.app.
13
+ * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
14
+ */
15
+ export declare function generateTerminalOpenScript(script: string): string;
6
16
  export declare function openInNewTerminal(script: string): void;
7
17
  export declare function copyEnvFiles(from: string, to: string): void;
8
18
  export declare function symlinkNodeModules(from: string, to: string): void;
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.slugify = slugify;
37
37
  exports.checkRequirements = checkRequirements;
38
+ exports.generateITermOpenScript = generateITermOpenScript;
39
+ exports.generateTerminalOpenScript = generateTerminalOpenScript;
38
40
  exports.openInNewTerminal = openInNewTerminal;
39
41
  exports.copyEnvFiles = copyEnvFiles;
40
42
  exports.symlinkNodeModules = symlinkNodeModules;
@@ -71,25 +73,45 @@ function checkRequirements() {
71
73
  }
72
74
  return { ok: missing.length === 0, missing };
73
75
  }
74
- function openInNewTerminal(script) {
75
- const platform = os.platform();
76
- if (platform === 'darwin') {
77
- // macOS - try iTerm2 first, then Terminal
78
- const iTermScript = `
76
+ /**
77
+ * Generate AppleScript for opening a script in iTerm2.
78
+ * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
79
+ */
80
+ function generateITermOpenScript(script) {
81
+ const escapedScript = script.replace(/"/g, '\\"');
82
+ const bashCommand = `/bin/bash "${escapedScript}"`;
83
+ return `
79
84
  tell application "iTerm"
80
85
  activate
81
86
  set newWindow to (create window with default profile)
82
87
  tell current session of newWindow
83
- write text "${script.replace(/"/g, '\\"')}"
88
+ write text "n"
89
+ delay 0.3
90
+ write text "${bashCommand.replace(/"/g, '\\"')}"
84
91
  end tell
85
92
  end tell
86
93
  `;
87
- const terminalScript = `
94
+ }
95
+ /**
96
+ * Generate AppleScript for opening a script in Terminal.app.
97
+ * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
98
+ */
99
+ function generateTerminalOpenScript(script) {
100
+ const escapedScript = script.replace(/"/g, '\\"');
101
+ const bashCommand = `/bin/bash "${escapedScript}"`;
102
+ return `
88
103
  tell application "Terminal"
89
104
  activate
90
- do script "${script.replace(/"/g, '\\"')}"
105
+ do script "n; ${bashCommand.replace(/"/g, '\\"')}"
91
106
  end tell
92
107
  `;
108
+ }
109
+ function openInNewTerminal(script) {
110
+ const platform = os.platform();
111
+ if (platform === 'darwin') {
112
+ // macOS - try iTerm2 first, then Terminal
113
+ const iTermScript = generateITermOpenScript(script);
114
+ const terminalScript = generateTerminalOpenScript(script);
93
115
  try {
94
116
  // Check if iTerm is installed
95
117
  if (fs.existsSync('/Applications/iTerm.app')) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const helpers_1 = require("./helpers");
5
+ (0, vitest_1.describe)('helpers utilities', () => {
6
+ (0, vitest_1.describe)('slugify', () => {
7
+ (0, vitest_1.it)('should convert text to lowercase slug', () => {
8
+ (0, vitest_1.expect)((0, helpers_1.slugify)('Hello World')).toBe('hello-world');
9
+ });
10
+ (0, vitest_1.it)('should remove brackets prefix', () => {
11
+ (0, vitest_1.expect)((0, helpers_1.slugify)('[Bug] Fix login issue')).toBe('fix-login-issue');
12
+ (0, vitest_1.expect)((0, helpers_1.slugify)('[FAQ] How to reset password')).toBe('how-to-reset-password');
13
+ });
14
+ (0, vitest_1.it)('should remove special characters', () => {
15
+ (0, vitest_1.expect)((0, helpers_1.slugify)('Fix: issue #123!')).toBe('fix-issue-123');
16
+ });
17
+ (0, vitest_1.it)('should remove duplicate consecutive words', () => {
18
+ (0, vitest_1.expect)((0, helpers_1.slugify)('[FAQ] FAQ question')).toBe('faq-question');
19
+ });
20
+ (0, vitest_1.it)('should limit slug to 30 characters', () => {
21
+ const longTitle = 'This is a very long issue title that should be truncated';
22
+ (0, vitest_1.expect)((0, helpers_1.slugify)(longTitle).length).toBeLessThanOrEqual(30);
23
+ });
24
+ });
25
+ (0, vitest_1.describe)('generateITermOpenScript', () => {
26
+ (0, vitest_1.it)('should generate valid AppleScript for iTerm', () => {
27
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/script.sh');
28
+ (0, vitest_1.expect)(script).toContain('tell application "iTerm"');
29
+ (0, vitest_1.expect)(script).toContain('create window with default profile');
30
+ (0, vitest_1.expect)(script).toContain('write text');
31
+ });
32
+ (0, vitest_1.it)('should send "n" to dismiss oh-my-zsh update prompts', () => {
33
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/script.sh');
34
+ (0, vitest_1.expect)(script).toContain('write text "n"');
35
+ });
36
+ (0, vitest_1.it)('should include delay after dismissing prompt', () => {
37
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/script.sh');
38
+ (0, vitest_1.expect)(script).toContain('delay 0.3');
39
+ });
40
+ (0, vitest_1.it)('should use /bin/bash to run the script', () => {
41
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/script.sh');
42
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
43
+ (0, vitest_1.expect)(script).toContain('/path/to/script.sh');
44
+ });
45
+ (0, vitest_1.it)('should handle double quotes in script path without breaking AppleScript', () => {
46
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/"quoted"/script.sh');
47
+ // Should still contain the path and be valid AppleScript structure
48
+ (0, vitest_1.expect)(script).toContain('tell application "iTerm"');
49
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
50
+ (0, vitest_1.expect)(script).toContain('quoted');
51
+ });
52
+ (0, vitest_1.it)('should handle paths with spaces', () => {
53
+ const script = (0, helpers_1.generateITermOpenScript)('/path/with spaces/script.sh');
54
+ (0, vitest_1.expect)(script).toContain('/path/with spaces/script.sh');
55
+ });
56
+ });
57
+ (0, vitest_1.describe)('generateTerminalOpenScript', () => {
58
+ (0, vitest_1.it)('should generate valid AppleScript for Terminal.app', () => {
59
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/to/script.sh');
60
+ (0, vitest_1.expect)(script).toContain('tell application "Terminal"');
61
+ (0, vitest_1.expect)(script).toContain('do script');
62
+ });
63
+ (0, vitest_1.it)('should send "n" to dismiss oh-my-zsh update prompts', () => {
64
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/to/script.sh');
65
+ (0, vitest_1.expect)(script).toContain('n;');
66
+ });
67
+ (0, vitest_1.it)('should use /bin/bash to run the script', () => {
68
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/to/script.sh');
69
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
70
+ (0, vitest_1.expect)(script).toContain('/path/to/script.sh');
71
+ });
72
+ (0, vitest_1.it)('should handle double quotes in script path without breaking AppleScript', () => {
73
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/to/"quoted"/script.sh');
74
+ // Should still contain the path and be valid AppleScript structure
75
+ (0, vitest_1.expect)(script).toContain('tell application "Terminal"');
76
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
77
+ (0, vitest_1.expect)(script).toContain('quoted');
78
+ });
79
+ (0, vitest_1.it)('should handle paths with spaces', () => {
80
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/with spaces/script.sh');
81
+ (0, vitest_1.expect)(script).toContain('/path/with spaces/script.sh');
82
+ });
83
+ });
84
+ (0, vitest_1.describe)('oh-my-zsh bypass behavior', () => {
85
+ (0, vitest_1.it)('iTerm script should have prompt dismissal before command execution', () => {
86
+ const script = (0, helpers_1.generateITermOpenScript)('/test/script.sh');
87
+ const lines = script.split('\n');
88
+ // Find the indices of the relevant lines
89
+ const dismissIndex = lines.findIndex(line => line.includes('write text "n"'));
90
+ const commandIndex = lines.findIndex(line => line.includes('/bin/bash'));
91
+ // Dismissal should come before command
92
+ (0, vitest_1.expect)(dismissIndex).toBeGreaterThan(-1);
93
+ (0, vitest_1.expect)(commandIndex).toBeGreaterThan(-1);
94
+ (0, vitest_1.expect)(dismissIndex).toBeLessThan(commandIndex);
95
+ });
96
+ (0, vitest_1.it)('Terminal script should combine dismiss and command in single do script', () => {
97
+ const script = (0, helpers_1.generateTerminalOpenScript)('/test/script.sh');
98
+ // Should have "n; /bin/bash..." in a single do script call
99
+ (0, vitest_1.expect)(script).toMatch(/do script "n;.*\/bin\/bash/);
100
+ });
101
+ });
102
+ });
@@ -10,6 +10,7 @@ export declare function getSearchPatterns(options: CloseTerminalOptions): string
10
10
  /**
11
11
  * Generate AppleScript to close iTerm2 windows/tabs matching patterns
12
12
  * Uses a two-pass approach: first collect IDs, then close them
13
+ * Matches both window names AND session working directories for reliability
13
14
  */
14
15
  export declare function generateITermCloseScript(patterns: string[]): string;
15
16
  /**
@@ -24,6 +25,11 @@ export declare function generateVSCodeCloseScript(patterns: string[]): string;
24
25
  * Execute AppleScript and handle errors gracefully
25
26
  */
26
27
  export declare function executeAppleScript(script: string, timeout?: number): boolean;
28
+ /**
29
+ * Find and terminate shell processes running in the given directory
30
+ * This is a fallback approach when AppleScript matching fails
31
+ */
32
+ export declare function killProcessesInDirectory(dirPath: string): boolean;
27
33
  /**
28
34
  * Close terminal windows and VS Code windows associated with a worktree
29
35
  * Returns an object indicating which applications had windows closed
@@ -32,4 +38,5 @@ export declare function closeWindowsForWorktree(options: CloseTerminalOptions):
32
38
  iTerm: boolean;
33
39
  terminal: boolean;
34
40
  vscode: boolean;
41
+ processes: boolean;
35
42
  };
@@ -38,6 +38,7 @@ exports.generateITermCloseScript = generateITermCloseScript;
38
38
  exports.generateTerminalCloseScript = generateTerminalCloseScript;
39
39
  exports.generateVSCodeCloseScript = generateVSCodeCloseScript;
40
40
  exports.executeAppleScript = executeAppleScript;
41
+ exports.killProcessesInDirectory = killProcessesInDirectory;
41
42
  exports.closeWindowsForWorktree = closeWindowsForWorktree;
42
43
  const child_process_1 = require("child_process");
43
44
  const os = __importStar(require("os"));
@@ -48,6 +49,10 @@ function getSearchPatterns(options) {
48
49
  const { folderPath, issueNumber, prNumber } = options;
49
50
  const folderName = folderPath.split('/').pop() || '';
50
51
  const patterns = [];
52
+ // Full folder path (most reliable for working directory matching)
53
+ if (folderPath) {
54
+ patterns.push(folderPath);
55
+ }
51
56
  // Folder name pattern (e.g., "project-issue-38-slug")
52
57
  if (folderName) {
53
58
  patterns.push(folderName);
@@ -66,6 +71,7 @@ function getSearchPatterns(options) {
66
71
  /**
67
72
  * Generate AppleScript to close iTerm2 windows/tabs matching patterns
68
73
  * Uses a two-pass approach: first collect IDs, then close them
74
+ * Matches both window names AND session working directories for reliability
69
75
  */
70
76
  function generateITermCloseScript(patterns) {
71
77
  // Escape patterns for AppleScript string comparison
@@ -83,13 +89,20 @@ tell application "iTerm"
83
89
  ${escapedPatterns.map((p) => `if windowName contains "${p}" then set end of windowsToClose to windowId`).join('\n ')}
84
90
  end try
85
91
 
86
- -- Check tabs and sessions
92
+ -- Check tabs and sessions - match by name OR working directory path
87
93
  repeat with t in tabs of w
88
94
  repeat with s in sessions of t
89
95
  try
90
96
  set sessionName to name of s
97
+ set sessionPath to ""
98
+ try
99
+ set sessionPath to path of s
100
+ end try
91
101
  set sessionId to unique id of s
102
+ -- Match by session name
92
103
  ${escapedPatterns.map((p) => `if sessionName contains "${p}" then set end of sessionsToClose to {windowId, sessionId}`).join('\n ')}
104
+ -- Match by working directory path (more reliable)
105
+ ${escapedPatterns.map((p) => `if sessionPath contains "${p}" then set end of sessionsToClose to {windowId, sessionId}`).join('\n ')}
93
106
  end try
94
107
  end repeat
95
108
  end repeat
@@ -214,13 +227,60 @@ function executeAppleScript(script, timeout = 5000) {
214
227
  return false;
215
228
  }
216
229
  }
230
+ /**
231
+ * Find and terminate shell processes running in the given directory
232
+ * This is a fallback approach when AppleScript matching fails
233
+ */
234
+ function killProcessesInDirectory(dirPath) {
235
+ if (os.platform() !== 'darwin' && os.platform() !== 'linux') {
236
+ return false;
237
+ }
238
+ try {
239
+ // Use lsof to find processes with their current working directory in the target path
240
+ // +D flag finds processes with files open in the directory (including cwd)
241
+ const output = (0, child_process_1.execSync)(`lsof +D "${dirPath}" 2>/dev/null || true`, {
242
+ encoding: 'utf8',
243
+ timeout: 5000,
244
+ });
245
+ if (!output.trim()) {
246
+ return false;
247
+ }
248
+ // Parse lsof output to get PIDs of shell processes
249
+ const lines = output.split('\n').slice(1); // Skip header
250
+ const pids = new Set();
251
+ for (const line of lines) {
252
+ const parts = line.split(/\s+/);
253
+ if (parts.length >= 2) {
254
+ const command = parts[0].toLowerCase();
255
+ const pid = parts[1];
256
+ // Only kill shell processes and their children, not system processes
257
+ if (['bash', 'zsh', 'sh', 'fish', 'claude', 'node'].some(s => command.includes(s))) {
258
+ pids.add(pid);
259
+ }
260
+ }
261
+ }
262
+ // Send SIGTERM to each process
263
+ for (const pid of pids) {
264
+ try {
265
+ (0, child_process_1.execSync)(`kill -TERM ${pid} 2>/dev/null || true`, { stdio: 'pipe' });
266
+ }
267
+ catch {
268
+ // Process may have already exited
269
+ }
270
+ }
271
+ return pids.size > 0;
272
+ }
273
+ catch {
274
+ return false;
275
+ }
276
+ }
217
277
  /**
218
278
  * Close terminal windows and VS Code windows associated with a worktree
219
279
  * Returns an object indicating which applications had windows closed
220
280
  */
221
281
  function closeWindowsForWorktree(options) {
222
282
  if (os.platform() !== 'darwin') {
223
- return { iTerm: false, terminal: false, vscode: false };
283
+ return { iTerm: false, terminal: false, vscode: false, processes: false };
224
284
  }
225
285
  const patterns = getSearchPatterns(options);
226
286
  // Try to close iTerm2 windows/sessions
@@ -232,9 +292,12 @@ function closeWindowsForWorktree(options) {
232
292
  // Try to close VS Code windows
233
293
  const vscodeScript = generateVSCodeCloseScript(patterns);
234
294
  const vscodeResult = executeAppleScript(vscodeScript, 10000);
295
+ // Fallback: kill processes running in the worktree directory
296
+ const processResult = killProcessesInDirectory(options.folderPath);
235
297
  return {
236
298
  iTerm: iTermResult,
237
299
  terminal: terminalResult,
238
300
  vscode: vscodeResult,
301
+ processes: processResult,
239
302
  };
240
303
  }
@@ -10,10 +10,21 @@ const terminal_1 = require("./terminal");
10
10
  issueNumber: '42',
11
11
  };
12
12
  const patterns = (0, terminal_1.getSearchPatterns)(options);
13
+ // Should include full path for working directory matching
14
+ (0, vitest_1.expect)(patterns).toContain('/Users/test/project-issue-42-fix-bug');
13
15
  (0, vitest_1.expect)(patterns).toContain('project-issue-42-fix-bug');
14
16
  (0, vitest_1.expect)(patterns).toContain('Issue #42');
15
17
  (0, vitest_1.expect)(patterns).toContain('issue-42-');
16
18
  });
19
+ (0, vitest_1.it)('should include full folder path for working directory matching', () => {
20
+ const options = {
21
+ folderPath: '/Users/dev/myproject-issue-123-feature',
22
+ issueNumber: '123',
23
+ };
24
+ const patterns = (0, terminal_1.getSearchPatterns)(options);
25
+ // Full path should be first for best matching
26
+ (0, vitest_1.expect)(patterns[0]).toBe('/Users/dev/myproject-issue-123-feature');
27
+ });
17
28
  (0, vitest_1.it)('should include PR patterns when prNumber is provided', () => {
18
29
  const options = {
19
30
  folderPath: '/Users/test/project-issue-42-fix-bug',
@@ -68,6 +79,14 @@ const terminal_1 = require("./terminal");
68
79
  // Third pass closes windows
69
80
  (0, vitest_1.expect)(script).toContain('Third pass: close entire windows');
70
81
  });
82
+ (0, vitest_1.it)('should match by session working directory path', () => {
83
+ const patterns = ['/Users/test/worktree'];
84
+ const script = (0, terminal_1.generateITermCloseScript)(patterns);
85
+ // Should include path matching
86
+ (0, vitest_1.expect)(script).toContain('set sessionPath to');
87
+ (0, vitest_1.expect)(script).toContain('path of s');
88
+ (0, vitest_1.expect)(script).toContain('sessionPath contains');
89
+ });
71
90
  });
72
91
  (0, vitest_1.describe)('generateTerminalCloseScript', () => {
73
92
  (0, vitest_1.it)('should generate valid AppleScript for Terminal.app', () => {
@@ -116,9 +135,11 @@ const terminal_1 = require("./terminal");
116
135
  (0, vitest_1.expect)(result).toHaveProperty('iTerm');
117
136
  (0, vitest_1.expect)(result).toHaveProperty('terminal');
118
137
  (0, vitest_1.expect)(result).toHaveProperty('vscode');
138
+ (0, vitest_1.expect)(result).toHaveProperty('processes');
119
139
  (0, vitest_1.expect)(typeof result.iTerm).toBe('boolean');
120
140
  (0, vitest_1.expect)(typeof result.terminal).toBe('boolean');
121
141
  (0, vitest_1.expect)(typeof result.vscode).toBe('boolean');
142
+ (0, vitest_1.expect)(typeof result.processes).toBe('boolean');
122
143
  });
123
144
  (0, vitest_1.it)('should handle prNumber parameter', () => {
124
145
  const result = (0, terminal_1.closeWindowsForWorktree)({
@@ -129,6 +150,7 @@ const terminal_1 = require("./terminal");
129
150
  (0, vitest_1.expect)(result).toHaveProperty('iTerm');
130
151
  (0, vitest_1.expect)(result).toHaveProperty('terminal');
131
152
  (0, vitest_1.expect)(result).toHaveProperty('vscode');
153
+ (0, vitest_1.expect)(result).toHaveProperty('processes');
132
154
  });
133
155
  });
134
156
  (0, vitest_1.describe)('pattern matching edge cases', () => {
@@ -175,7 +197,8 @@ const terminal_1 = require("./terminal");
175
197
  const script = (0, terminal_1.generateITermCloseScript)(patterns);
176
198
  // Script should be syntactically valid (quotes should be escaped)
177
199
  (0, vitest_1.expect)(script).toContain('test\\"pattern');
178
- (0, vitest_1.expect)(script).not.toContain('""'); // No empty quotes from bad escaping
200
+ // Should not have broken string like contains "" (empty pattern)
201
+ (0, vitest_1.expect)(script).not.toMatch(/contains ""/);
179
202
  });
180
203
  (0, vitest_1.it)('should not have nested quotes issues in Terminal script', () => {
181
204
  const patterns = ['test"pattern'];
@@ -188,4 +211,14 @@ const terminal_1 = require("./terminal");
188
211
  (0, vitest_1.expect)(script).toContain('test\\"pattern');
189
212
  });
190
213
  });
214
+ (0, vitest_1.describe)('killProcessesInDirectory', () => {
215
+ (0, vitest_1.it)('should return false for non-existent directory', () => {
216
+ const result = (0, terminal_1.killProcessesInDirectory)('/nonexistent/path/that/does/not/exist');
217
+ (0, vitest_1.expect)(result).toBe(false);
218
+ });
219
+ (0, vitest_1.it)('should return boolean result', () => {
220
+ const result = (0, terminal_1.killProcessesInDirectory)('/tmp');
221
+ (0, vitest_1.expect)(typeof result).toBe('boolean');
222
+ });
223
+ });
191
224
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.42.0",
3
+ "version": "1.43.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {