claude-issue-solver 1.6.3 → 1.6.5

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.
@@ -46,11 +46,12 @@ const path = __importStar(require("path"));
46
46
  const os = __importStar(require("os"));
47
47
  const child_process_1 = require("child_process");
48
48
  const git_1 = require("../utils/git");
49
- function closeWindowsWithPath(folderPath) {
49
+ function closeWindowsWithPath(folderPath, issueNumber) {
50
50
  if (os.platform() !== 'darwin')
51
51
  return;
52
52
  const folderName = path.basename(folderPath);
53
- // Try to close iTerm2 tabs/windows with this path
53
+ const issuePattern = `Issue #${issueNumber}`;
54
+ // Try to close iTerm2 tabs/windows with this path or issue number
54
55
  try {
55
56
  (0, child_process_1.execSync)(`osascript -e '
56
57
  tell application "iTerm"
@@ -58,7 +59,7 @@ function closeWindowsWithPath(folderPath) {
58
59
  repeat with t in tabs of w
59
60
  repeat with s in sessions of t
60
61
  set sessionName to name of s
61
- if sessionName contains "${folderName}" then
62
+ if sessionName contains "${folderName}" or sessionName contains "${issuePattern}" then
62
63
  close s
63
64
  end if
64
65
  end repeat
@@ -70,12 +71,13 @@ function closeWindowsWithPath(folderPath) {
70
71
  catch {
71
72
  // iTerm not running or no matching sessions
72
73
  }
73
- // Try to close Terminal.app windows with this path
74
+ // Try to close Terminal.app windows with this path or issue number
74
75
  try {
75
76
  (0, child_process_1.execSync)(`osascript -e '
76
77
  tell application "Terminal"
77
78
  repeat with w in windows
78
- if name of w contains "${folderName}" then
79
+ set windowName to name of w
80
+ if windowName contains "${folderName}" or windowName contains "${issuePattern}" then
79
81
  close w
80
82
  end if
81
83
  end repeat
@@ -86,6 +88,16 @@ function closeWindowsWithPath(folderPath) {
86
88
  // Terminal not running or no matching windows
87
89
  }
88
90
  // Try to close VS Code windows with this path
91
+ try {
92
+ // Use VS Code CLI to close the folder if it's open
93
+ (0, child_process_1.execSync)(`code --folder-uri "file://${folderPath}" --command "workbench.action.closeWindow"`, {
94
+ stdio: 'pipe',
95
+ timeout: 3000
96
+ });
97
+ }
98
+ catch {
99
+ // VS Code CLI method failed, try AppleScript
100
+ }
89
101
  try {
90
102
  (0, child_process_1.execSync)(`osascript -e '
91
103
  tell application "System Events"
@@ -112,30 +124,57 @@ function getIssueWorktrees() {
112
124
  const projectName = (0, git_1.getProjectName)();
113
125
  const parentDir = path.dirname(projectRoot);
114
126
  const worktrees = [];
127
+ const foundPaths = new Set();
115
128
  // Get all worktrees from git
116
129
  const output = (0, git_1.exec)('git worktree list --porcelain', projectRoot);
117
- if (!output)
118
- return worktrees;
119
- const lines = output.split('\n');
120
- let currentPath = '';
121
- let currentBranch = '';
122
- for (const line of lines) {
123
- if (line.startsWith('worktree ')) {
124
- currentPath = line.replace('worktree ', '');
130
+ if (output) {
131
+ const lines = output.split('\n');
132
+ let currentPath = '';
133
+ let currentBranch = '';
134
+ for (const line of lines) {
135
+ if (line.startsWith('worktree ')) {
136
+ currentPath = line.replace('worktree ', '');
137
+ }
138
+ else if (line.startsWith('branch refs/heads/')) {
139
+ currentBranch = line.replace('branch refs/heads/', '');
140
+ // Check if this is an issue branch
141
+ const match = currentBranch.match(/^issue-(\d+)-/);
142
+ if (match && currentPath.includes(`${projectName}-issue-`)) {
143
+ worktrees.push({
144
+ path: currentPath,
145
+ branch: currentBranch,
146
+ issueNumber: match[1],
147
+ });
148
+ foundPaths.add(currentPath);
149
+ }
150
+ }
125
151
  }
126
- else if (line.startsWith('branch refs/heads/')) {
127
- currentBranch = line.replace('branch refs/heads/', '');
128
- // Check if this is an issue branch
129
- const match = currentBranch.match(/^issue-(\d+)-/);
130
- if (match && currentPath.includes(`${projectName}-issue-`)) {
131
- worktrees.push({
132
- path: currentPath,
133
- branch: currentBranch,
134
- issueNumber: match[1],
135
- });
152
+ }
153
+ // Also check for orphaned folders (folders that exist but aren't in git worktree list)
154
+ // This can happen when git worktree remove fails but the folder remains
155
+ try {
156
+ const folderPattern = `${projectName}-issue-`;
157
+ const entries = fs.readdirSync(parentDir, { withFileTypes: true });
158
+ for (const entry of entries) {
159
+ if (entry.isDirectory() && entry.name.startsWith(folderPattern)) {
160
+ const folderPath = path.join(parentDir, entry.name);
161
+ if (!foundPaths.has(folderPath)) {
162
+ // Extract issue number from folder name (e.g., "project-issue-38-slug")
163
+ const match = entry.name.match(new RegExp(`${projectName}-issue-(\\d+)-`));
164
+ if (match) {
165
+ worktrees.push({
166
+ path: folderPath,
167
+ branch: '', // No branch known for orphaned folders
168
+ issueNumber: match[1],
169
+ });
170
+ }
171
+ }
136
172
  }
137
173
  }
138
174
  }
175
+ catch {
176
+ // Ignore errors reading parent directory
177
+ }
139
178
  return worktrees;
140
179
  }
141
180
  async function cleanAllCommand() {
@@ -147,7 +186,7 @@ async function cleanAllCommand() {
147
186
  }
148
187
  console.log(chalk_1.default.bold('\n🧹 Found issue worktrees:\n'));
149
188
  for (const wt of worktrees) {
150
- console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${wt.branch}`);
189
+ console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${wt.branch || chalk_1.default.yellow('(orphaned folder)')}`);
151
190
  console.log(chalk_1.default.dim(` \t${wt.path}`));
152
191
  console.log();
153
192
  }
@@ -169,7 +208,9 @@ async function cleanAllCommand() {
169
208
  try {
170
209
  // Close terminal and VS Code windows for this worktree
171
210
  try {
172
- closeWindowsWithPath(wt.path);
211
+ closeWindowsWithPath(wt.path, wt.issueNumber);
212
+ // Give windows time to close before removing folder
213
+ await new Promise((resolve) => setTimeout(resolve, 500));
173
214
  }
174
215
  catch {
175
216
  // Ignore errors closing windows
@@ -183,20 +224,28 @@ async function cleanAllCommand() {
183
224
  });
184
225
  }
185
226
  catch {
186
- // If git worktree remove fails, try removing directory manually
187
- fs.rmSync(wt.path, { recursive: true, force: true });
227
+ // If git worktree remove fails, try removing directory manually with rm -rf
228
+ // This handles locked files better than fs.rmSync
229
+ try {
230
+ (0, child_process_1.execSync)(`rm -rf "${wt.path}"`, { stdio: 'pipe' });
231
+ }
232
+ catch {
233
+ fs.rmSync(wt.path, { recursive: true, force: true });
234
+ }
188
235
  (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
189
236
  }
190
237
  }
191
- // Delete branch
192
- try {
193
- (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
194
- cwd: projectRoot,
195
- stdio: 'pipe',
196
- });
197
- }
198
- catch {
199
- // Branch may already be deleted
238
+ // Delete branch (if we have one)
239
+ if (wt.branch) {
240
+ try {
241
+ (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
242
+ cwd: projectRoot,
243
+ stdio: 'pipe',
244
+ });
245
+ }
246
+ catch {
247
+ // Branch may already be deleted
248
+ }
200
249
  }
201
250
  spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
202
251
  }
@@ -212,7 +261,9 @@ async function cleanAllCommand() {
212
261
  async function cleanCommand(issueNumber) {
213
262
  const projectRoot = (0, git_1.getProjectRoot)();
214
263
  const projectName = (0, git_1.getProjectName)();
264
+ const parentDir = path.dirname(projectRoot);
215
265
  // Find the worktree for this issue number (don't need to fetch from GitHub)
266
+ // This now also includes orphaned folders
216
267
  const worktrees = getIssueWorktrees();
217
268
  const worktree = worktrees.find((wt) => wt.issueNumber === String(issueNumber));
218
269
  if (!worktree) {
@@ -222,7 +273,7 @@ async function cleanCommand(issueNumber) {
222
273
  const branches = output.split('\n').map((b) => b.trim().replace('* ', ''));
223
274
  const matchingBranch = branches.find((b) => b.startsWith(branchPattern));
224
275
  if (!matchingBranch) {
225
- console.log(chalk_1.default.red(`\nāŒ No worktree or branch found for issue #${issueNumber}`));
276
+ console.log(chalk_1.default.red(`\nāŒ No worktree, folder, or branch found for issue #${issueNumber}`));
226
277
  return;
227
278
  }
228
279
  // Found a branch but no worktree - just delete the branch
@@ -250,16 +301,22 @@ async function cleanCommand(issueNumber) {
250
301
  }
251
302
  const branchName = worktree.branch;
252
303
  const worktreePath = worktree.path;
304
+ const isOrphaned = !branchName;
253
305
  console.log();
254
306
  console.log(chalk_1.default.bold(`🧹 Cleaning up issue #${issueNumber}`));
255
- console.log(chalk_1.default.dim(` Branch: ${branchName}`));
256
- console.log(chalk_1.default.dim(` Worktree: ${worktreePath}`));
307
+ if (isOrphaned) {
308
+ console.log(chalk_1.default.yellow(` (Orphaned folder - no git worktree reference)`));
309
+ }
310
+ else {
311
+ console.log(chalk_1.default.dim(` Branch: ${branchName}`));
312
+ }
313
+ console.log(chalk_1.default.dim(` Folder: ${worktreePath}`));
257
314
  console.log();
258
315
  const { confirm } = await inquirer_1.default.prompt([
259
316
  {
260
317
  type: 'confirm',
261
318
  name: 'confirm',
262
- message: 'Remove worktree and delete branch?',
319
+ message: isOrphaned ? 'Remove orphaned folder?' : 'Remove worktree and delete branch?',
263
320
  default: false,
264
321
  },
265
322
  ]);
@@ -268,11 +325,15 @@ async function cleanCommand(issueNumber) {
268
325
  return;
269
326
  }
270
327
  // Close terminal and VS Code windows for this worktree
328
+ const windowSpinner = (0, ora_1.default)('Closing terminal and VS Code windows...').start();
271
329
  try {
272
- closeWindowsWithPath(worktreePath);
330
+ closeWindowsWithPath(worktreePath, String(issueNumber));
331
+ // Give windows time to close before removing folder
332
+ await new Promise((resolve) => setTimeout(resolve, 500));
333
+ windowSpinner.succeed('Windows closed');
273
334
  }
274
335
  catch {
275
- // Ignore errors closing windows
336
+ windowSpinner.warn('Could not close some windows');
276
337
  }
277
338
  // Remove worktree
278
339
  if (fs.existsSync(worktreePath)) {
@@ -285,28 +346,37 @@ async function cleanCommand(issueNumber) {
285
346
  worktreeSpinner.succeed('Worktree removed');
286
347
  }
287
348
  catch {
288
- // If git worktree remove fails, try removing directory manually
349
+ // If git worktree remove fails, try removing directory manually with rm -rf
289
350
  try {
290
- fs.rmSync(worktreePath, { recursive: true, force: true });
351
+ (0, child_process_1.execSync)(`rm -rf "${worktreePath}"`, { stdio: 'pipe' });
291
352
  (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
292
353
  worktreeSpinner.succeed('Worktree removed (manually)');
293
354
  }
294
355
  catch {
295
- worktreeSpinner.warn('Could not remove worktree directory');
356
+ try {
357
+ fs.rmSync(worktreePath, { recursive: true, force: true });
358
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
359
+ worktreeSpinner.succeed('Worktree removed (manually)');
360
+ }
361
+ catch {
362
+ worktreeSpinner.warn('Could not remove worktree directory');
363
+ }
296
364
  }
297
365
  }
298
366
  }
299
- // Delete branch
300
- const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
301
- try {
302
- (0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
303
- cwd: projectRoot,
304
- stdio: 'pipe',
305
- });
306
- branchSpinner.succeed('Branch deleted');
307
- }
308
- catch {
309
- branchSpinner.warn('Could not delete branch (may already be deleted)');
367
+ // Delete branch (if we have one)
368
+ if (branchName) {
369
+ const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
370
+ try {
371
+ (0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
372
+ cwd: projectRoot,
373
+ stdio: 'pipe',
374
+ });
375
+ branchSpinner.succeed('Branch deleted');
376
+ }
377
+ catch {
378
+ branchSpinner.warn('Could not delete branch (may already be deleted)');
379
+ }
310
380
  }
311
381
  console.log();
312
382
  console.log(chalk_1.default.green('āœ… Cleanup complete!'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {