claude-issue-solver 1.43.1 → 1.43.3

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.
@@ -5,11 +5,13 @@ export declare function checkRequirements(): {
5
5
  };
6
6
  /**
7
7
  * Generate AppleScript for opening a script in iTerm2.
8
+ * Changes to the script's directory first so the session path matches the worktree.
8
9
  * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
9
10
  */
10
11
  export declare function generateITermOpenScript(script: string): string;
11
12
  /**
12
13
  * Generate AppleScript for opening a script in Terminal.app.
14
+ * Changes to the script's directory first so the session path matches the worktree.
13
15
  * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
14
16
  */
15
17
  export declare function generateTerminalOpenScript(script: string): string;
@@ -75,11 +75,16 @@ function checkRequirements() {
75
75
  }
76
76
  /**
77
77
  * Generate AppleScript for opening a script in iTerm2.
78
+ * Changes to the script's directory first so the session path matches the worktree.
78
79
  * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
79
80
  */
80
81
  function generateITermOpenScript(script) {
81
82
  const escapedScript = script.replace(/"/g, '\\"');
82
- const bashCommand = `/bin/bash "${escapedScript}"`;
83
+ // Extract directory from script path to set as working directory
84
+ const scriptDir = path.dirname(script.replace(/'/g, ''));
85
+ const escapedDir = scriptDir.replace(/"/g, '\\"');
86
+ // cd to the script's directory first, so session path matches worktree
87
+ const bashCommand = `cd "${escapedDir}" && /bin/bash "${escapedScript}"`;
83
88
  return `
84
89
  tell application "iTerm"
85
90
  activate
@@ -94,11 +99,16 @@ function generateITermOpenScript(script) {
94
99
  }
95
100
  /**
96
101
  * Generate AppleScript for opening a script in Terminal.app.
102
+ * Changes to the script's directory first so the session path matches the worktree.
97
103
  * Sends 'n' first to dismiss any oh-my-zsh update prompts, then runs the script with bash.
98
104
  */
99
105
  function generateTerminalOpenScript(script) {
100
106
  const escapedScript = script.replace(/"/g, '\\"');
101
- const bashCommand = `/bin/bash "${escapedScript}"`;
107
+ // Extract directory from script path to set as working directory
108
+ const scriptDir = path.dirname(script.replace(/'/g, ''));
109
+ const escapedDir = scriptDir.replace(/"/g, '\\"');
110
+ // cd to the script's directory first, so session path matches worktree
111
+ const bashCommand = `cd "${escapedDir}" && /bin/bash "${escapedScript}"`;
102
112
  return `
103
113
  tell application "Terminal"
104
114
  activate
@@ -99,4 +99,24 @@ const helpers_1 = require("./helpers");
99
99
  (0, vitest_1.expect)(script).toMatch(/do script "n;.*\/bin\/bash/);
100
100
  });
101
101
  });
102
+ (0, vitest_1.describe)('working directory for terminal closing', () => {
103
+ (0, vitest_1.it)('iTerm script should cd to script directory before running', () => {
104
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/worktree/.claude-runner.sh');
105
+ // Should cd to the script's directory first (quotes are escaped in AppleScript)
106
+ (0, vitest_1.expect)(script).toContain('cd \\"/path/to/worktree\\"');
107
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
108
+ });
109
+ (0, vitest_1.it)('Terminal script should cd to script directory before running', () => {
110
+ const script = (0, helpers_1.generateTerminalOpenScript)('/path/to/worktree/.claude-runner.sh');
111
+ // Should cd to the script's directory first (quotes are escaped in AppleScript)
112
+ (0, vitest_1.expect)(script).toContain('cd \\"/path/to/worktree\\"');
113
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
114
+ });
115
+ (0, vitest_1.it)('should handle script paths with quotes', () => {
116
+ const script = (0, helpers_1.generateITermOpenScript)('/path/to/work"tree/.script.sh');
117
+ (0, vitest_1.expect)(script).toContain('/bin/bash');
118
+ // Path should be escaped
119
+ (0, vitest_1.expect)(script).toContain('work');
120
+ });
121
+ });
102
122
  });
@@ -25,6 +25,14 @@ export declare function generateVSCodeCloseScript(patterns: string[]): string;
25
25
  * Execute AppleScript and handle errors gracefully
26
26
  */
27
27
  export declare function executeAppleScript(script: string, timeout?: number): boolean;
28
+ /**
29
+ * Find TTYs associated with processes in the given directory
30
+ */
31
+ export declare function findTTYsInDirectory(dirPath: string): string[];
32
+ /**
33
+ * Generate AppleScript to close iTerm2 windows by TTY
34
+ */
35
+ export declare function generateITermCloseByTTYScript(ttys: string[]): string;
28
36
  /**
29
37
  * Find and terminate shell processes running in the given directory
30
38
  * This is a fallback approach when AppleScript matching fails
@@ -38,6 +38,8 @@ exports.generateITermCloseScript = generateITermCloseScript;
38
38
  exports.generateTerminalCloseScript = generateTerminalCloseScript;
39
39
  exports.generateVSCodeCloseScript = generateVSCodeCloseScript;
40
40
  exports.executeAppleScript = executeAppleScript;
41
+ exports.findTTYsInDirectory = findTTYsInDirectory;
42
+ exports.generateITermCloseByTTYScript = generateITermCloseByTTYScript;
41
43
  exports.killProcessesInDirectory = killProcessesInDirectory;
42
44
  exports.closeWindowsForWorktree = closeWindowsForWorktree;
43
45
  const child_process_1 = require("child_process");
@@ -208,6 +210,84 @@ function executeAppleScript(script, timeout = 5000) {
208
210
  return false;
209
211
  }
210
212
  }
213
+ /**
214
+ * Find TTYs associated with processes in the given directory
215
+ */
216
+ function findTTYsInDirectory(dirPath) {
217
+ if (os.platform() !== 'darwin' && os.platform() !== 'linux') {
218
+ return [];
219
+ }
220
+ try {
221
+ // Use lsof to find processes with files open in the directory
222
+ const output = (0, child_process_1.execSync)(`lsof +D "${dirPath}" 2>/dev/null || true`, {
223
+ encoding: 'utf8',
224
+ timeout: 5000,
225
+ });
226
+ if (!output.trim()) {
227
+ return [];
228
+ }
229
+ // Parse lsof output to get TTYs
230
+ const lines = output.split('\n').slice(1); // Skip header
231
+ const ttys = new Set();
232
+ for (const line of lines) {
233
+ const parts = line.split(/\s+/);
234
+ // lsof output: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
235
+ // We want to find the FD column that shows the tty
236
+ if (parts.length >= 9) {
237
+ const fd = parts[3];
238
+ const type = parts[4];
239
+ const name = parts[8];
240
+ // Look for tty file descriptors
241
+ if ((fd === '0u' || fd === '1u' || fd === '2u') && name.startsWith('/dev/ttys')) {
242
+ ttys.add(name);
243
+ }
244
+ }
245
+ }
246
+ return Array.from(ttys);
247
+ }
248
+ catch {
249
+ return [];
250
+ }
251
+ }
252
+ /**
253
+ * Generate AppleScript to close iTerm2 windows by TTY
254
+ */
255
+ function generateITermCloseByTTYScript(ttys) {
256
+ if (ttys.length === 0)
257
+ return '';
258
+ const ttyConditions = ttys.map(tty => `tty of s is "${tty}"`).join(' or ');
259
+ return `
260
+ tell application "iTerm"
261
+ set windowsToClose to {}
262
+
263
+ repeat with w in windows
264
+ set windowId to id of w
265
+ repeat with t in tabs of w
266
+ repeat with s in sessions of t
267
+ try
268
+ if ${ttyConditions} then
269
+ if windowsToClose does not contain windowId then
270
+ set end of windowsToClose to windowId
271
+ end if
272
+ end if
273
+ end try
274
+ end repeat
275
+ end repeat
276
+ end repeat
277
+
278
+ repeat with targetWindowId in windowsToClose
279
+ try
280
+ repeat with w in windows
281
+ if id of w is targetWindowId then
282
+ close w
283
+ exit repeat
284
+ end if
285
+ end repeat
286
+ end try
287
+ end repeat
288
+ end tell
289
+ `;
290
+ }
211
291
  /**
212
292
  * Find and terminate shell processes running in the given directory
213
293
  * This is a fallback approach when AppleScript matching fails
@@ -264,10 +344,21 @@ function closeWindowsForWorktree(options) {
264
344
  return { iTerm: false, terminal: false, vscode: false, processes: false };
265
345
  }
266
346
  const patterns = getSearchPatterns(options);
267
- // Try to close iTerm2 windows/sessions
268
- const iTermScript = generateITermCloseScript(patterns);
269
- const iTermResult = executeAppleScript(iTermScript);
270
- // Try to close Terminal.app windows
347
+ // First, find TTYs associated with processes in the worktree
348
+ // This is the most reliable way to identify the terminal
349
+ const ttys = findTTYsInDirectory(options.folderPath);
350
+ let iTermResult = false;
351
+ // Try to close iTerm2 by TTY first (most reliable)
352
+ if (ttys.length > 0) {
353
+ const iTermTTYScript = generateITermCloseByTTYScript(ttys);
354
+ iTermResult = executeAppleScript(iTermTTYScript);
355
+ }
356
+ // Fall back to pattern-based matching if TTY approach didn't work
357
+ if (!iTermResult) {
358
+ const iTermScript = generateITermCloseScript(patterns);
359
+ iTermResult = executeAppleScript(iTermScript);
360
+ }
361
+ // Try to close Terminal.app windows (pattern-based)
271
362
  const terminalScript = generateTerminalCloseScript(patterns);
272
363
  const terminalResult = executeAppleScript(terminalScript);
273
364
  // Try to close VS Code windows
@@ -218,4 +218,30 @@ const terminal_1 = require("./terminal");
218
218
  (0, vitest_1.expect)(typeof result).toBe('boolean');
219
219
  });
220
220
  });
221
+ (0, vitest_1.describe)('findTTYsInDirectory', () => {
222
+ (0, vitest_1.it)('should return empty array for non-existent directory', () => {
223
+ const result = (0, terminal_1.findTTYsInDirectory)('/nonexistent/path/that/does/not/exist');
224
+ (0, vitest_1.expect)(result).toEqual([]);
225
+ });
226
+ (0, vitest_1.it)('should return array', () => {
227
+ const result = (0, terminal_1.findTTYsInDirectory)('/tmp');
228
+ (0, vitest_1.expect)(Array.isArray(result)).toBe(true);
229
+ });
230
+ });
231
+ (0, vitest_1.describe)('generateITermCloseByTTYScript', () => {
232
+ (0, vitest_1.it)('should return empty string for empty TTY array', () => {
233
+ const script = (0, terminal_1.generateITermCloseByTTYScript)([]);
234
+ (0, vitest_1.expect)(script).toBe('');
235
+ });
236
+ (0, vitest_1.it)('should generate valid AppleScript for single TTY', () => {
237
+ const script = (0, terminal_1.generateITermCloseByTTYScript)(['/dev/ttys001']);
238
+ (0, vitest_1.expect)(script).toContain('tell application "iTerm"');
239
+ (0, vitest_1.expect)(script).toContain('tty of s is "/dev/ttys001"');
240
+ (0, vitest_1.expect)(script).toContain('close w');
241
+ });
242
+ (0, vitest_1.it)('should generate OR conditions for multiple TTYs', () => {
243
+ const script = (0, terminal_1.generateITermCloseByTTYScript)(['/dev/ttys001', '/dev/ttys002']);
244
+ (0, vitest_1.expect)(script).toContain('tty of s is "/dev/ttys001" or tty of s is "/dev/ttys002"');
245
+ });
246
+ });
221
247
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.43.1",
3
+ "version": "1.43.3",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {