claude-issue-solver 1.6.4 → 1.6.6

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.
@@ -124,30 +124,57 @@ function getIssueWorktrees() {
124
124
  const projectName = (0, git_1.getProjectName)();
125
125
  const parentDir = path.dirname(projectRoot);
126
126
  const worktrees = [];
127
+ const foundPaths = new Set();
127
128
  // Get all worktrees from git
128
129
  const output = (0, git_1.exec)('git worktree list --porcelain', projectRoot);
129
- if (!output)
130
- return worktrees;
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 ', '');
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
+ }
137
151
  }
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
- });
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
+ }
148
172
  }
149
173
  }
150
174
  }
175
+ catch {
176
+ // Ignore errors reading parent directory
177
+ }
151
178
  return worktrees;
152
179
  }
153
180
  async function cleanAllCommand() {
@@ -159,7 +186,7 @@ async function cleanAllCommand() {
159
186
  }
160
187
  console.log(chalk_1.default.bold('\n🧹 Found issue worktrees:\n'));
161
188
  for (const wt of worktrees) {
162
- 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)')}`);
163
190
  console.log(chalk_1.default.dim(` \t${wt.path}`));
164
191
  console.log();
165
192
  }
@@ -188,35 +215,54 @@ async function cleanAllCommand() {
188
215
  catch {
189
216
  // Ignore errors closing windows
190
217
  }
191
- // Remove worktree
218
+ // Remove worktree/folder
219
+ const isOrphaned = !wt.branch;
192
220
  if (fs.existsSync(wt.path)) {
193
- try {
194
- (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
195
- cwd: projectRoot,
196
- stdio: 'pipe',
197
- });
221
+ // Try git worktree remove first (only if not orphaned)
222
+ if (!isOrphaned) {
223
+ try {
224
+ (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
225
+ cwd: projectRoot,
226
+ stdio: 'pipe',
227
+ });
228
+ }
229
+ catch {
230
+ // Ignore - we'll force delete below if needed
231
+ }
198
232
  }
199
- catch {
200
- // If git worktree remove fails, try removing directory manually with rm -rf
201
- // This handles locked files better than fs.rmSync
233
+ // If folder still exists, force delete it
234
+ if (fs.existsSync(wt.path)) {
202
235
  try {
203
- (0, child_process_1.execSync)(`rm -rf "${wt.path}"`, { stdio: 'pipe' });
236
+ (0, child_process_1.execSync)(`/bin/rm -rf "${wt.path}"`, { stdio: 'pipe' });
204
237
  }
205
238
  catch {
206
- fs.rmSync(wt.path, { recursive: true, force: true });
239
+ try {
240
+ fs.rmSync(wt.path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
241
+ }
242
+ catch {
243
+ // Ignore - will check at end
244
+ }
207
245
  }
246
+ }
247
+ // Prune git worktrees
248
+ try {
208
249
  (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
209
250
  }
251
+ catch {
252
+ // Ignore
253
+ }
210
254
  }
211
- // Delete branch
212
- try {
213
- (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
214
- cwd: projectRoot,
215
- stdio: 'pipe',
216
- });
217
- }
218
- catch {
219
- // Branch may already be deleted
255
+ // Delete branch (if we have one)
256
+ if (wt.branch) {
257
+ try {
258
+ (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
259
+ cwd: projectRoot,
260
+ stdio: 'pipe',
261
+ });
262
+ }
263
+ catch {
264
+ // Branch may already be deleted
265
+ }
220
266
  }
221
267
  spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
222
268
  }
@@ -232,7 +278,9 @@ async function cleanAllCommand() {
232
278
  async function cleanCommand(issueNumber) {
233
279
  const projectRoot = (0, git_1.getProjectRoot)();
234
280
  const projectName = (0, git_1.getProjectName)();
281
+ const parentDir = path.dirname(projectRoot);
235
282
  // Find the worktree for this issue number (don't need to fetch from GitHub)
283
+ // This now also includes orphaned folders
236
284
  const worktrees = getIssueWorktrees();
237
285
  const worktree = worktrees.find((wt) => wt.issueNumber === String(issueNumber));
238
286
  if (!worktree) {
@@ -242,7 +290,7 @@ async function cleanCommand(issueNumber) {
242
290
  const branches = output.split('\n').map((b) => b.trim().replace('* ', ''));
243
291
  const matchingBranch = branches.find((b) => b.startsWith(branchPattern));
244
292
  if (!matchingBranch) {
245
- console.log(chalk_1.default.red(`\nāŒ No worktree or branch found for issue #${issueNumber}`));
293
+ console.log(chalk_1.default.red(`\nāŒ No worktree, folder, or branch found for issue #${issueNumber}`));
246
294
  return;
247
295
  }
248
296
  // Found a branch but no worktree - just delete the branch
@@ -270,16 +318,22 @@ async function cleanCommand(issueNumber) {
270
318
  }
271
319
  const branchName = worktree.branch;
272
320
  const worktreePath = worktree.path;
321
+ const isOrphaned = !branchName;
273
322
  console.log();
274
323
  console.log(chalk_1.default.bold(`🧹 Cleaning up issue #${issueNumber}`));
275
- console.log(chalk_1.default.dim(` Branch: ${branchName}`));
276
- console.log(chalk_1.default.dim(` Worktree: ${worktreePath}`));
324
+ if (isOrphaned) {
325
+ console.log(chalk_1.default.yellow(` (Orphaned folder - no git worktree reference)`));
326
+ }
327
+ else {
328
+ console.log(chalk_1.default.dim(` Branch: ${branchName}`));
329
+ }
330
+ console.log(chalk_1.default.dim(` Folder: ${worktreePath}`));
277
331
  console.log();
278
332
  const { confirm } = await inquirer_1.default.prompt([
279
333
  {
280
334
  type: 'confirm',
281
335
  name: 'confirm',
282
- message: 'Remove worktree and delete branch?',
336
+ message: isOrphaned ? 'Remove orphaned folder?' : 'Remove worktree and delete branch?',
283
337
  default: false,
284
338
  },
285
339
  ]);
@@ -298,46 +352,66 @@ async function cleanCommand(issueNumber) {
298
352
  catch {
299
353
  windowSpinner.warn('Could not close some windows');
300
354
  }
301
- // Remove worktree
355
+ // Remove worktree/folder
302
356
  if (fs.existsSync(worktreePath)) {
303
357
  const worktreeSpinner = (0, ora_1.default)('Removing worktree...').start();
304
- try {
305
- (0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, {
306
- cwd: projectRoot,
307
- stdio: 'pipe',
308
- });
309
- worktreeSpinner.succeed('Worktree removed');
358
+ // Try git worktree remove first (only if not orphaned)
359
+ if (!isOrphaned) {
360
+ try {
361
+ (0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, {
362
+ cwd: projectRoot,
363
+ stdio: 'pipe',
364
+ });
365
+ }
366
+ catch {
367
+ // Ignore - we'll force delete below if needed
368
+ }
310
369
  }
311
- catch {
312
- // If git worktree remove fails, try removing directory manually with rm -rf
370
+ // If folder still exists, force delete it
371
+ if (fs.existsSync(worktreePath)) {
313
372
  try {
314
- (0, child_process_1.execSync)(`rm -rf "${worktreePath}"`, { stdio: 'pipe' });
315
- (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
316
- worktreeSpinner.succeed('Worktree removed (manually)');
373
+ // Use rm -rf with full path
374
+ (0, child_process_1.execSync)(`/bin/rm -rf "${worktreePath}"`, { stdio: 'pipe' });
317
375
  }
318
376
  catch {
377
+ // Try Node's rmSync as fallback
319
378
  try {
320
- fs.rmSync(worktreePath, { recursive: true, force: true });
321
- (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
322
- worktreeSpinner.succeed('Worktree removed (manually)');
379
+ fs.rmSync(worktreePath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
323
380
  }
324
381
  catch {
325
- worktreeSpinner.warn('Could not remove worktree directory');
382
+ // Last resort - try with sudo hint
383
+ worktreeSpinner.warn(`Could not remove directory. Try manually: rm -rf "${worktreePath}"`);
326
384
  }
327
385
  }
328
386
  }
387
+ // Prune git worktrees
388
+ try {
389
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
390
+ }
391
+ catch {
392
+ // Ignore
393
+ }
394
+ // Check final result
395
+ if (fs.existsSync(worktreePath)) {
396
+ worktreeSpinner.warn(`Could not fully remove directory: ${worktreePath}`);
397
+ }
398
+ else {
399
+ worktreeSpinner.succeed(isOrphaned ? 'Folder removed' : 'Worktree removed');
400
+ }
329
401
  }
330
- // Delete branch
331
- const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
332
- try {
333
- (0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
334
- cwd: projectRoot,
335
- stdio: 'pipe',
336
- });
337
- branchSpinner.succeed('Branch deleted');
338
- }
339
- catch {
340
- branchSpinner.warn('Could not delete branch (may already be deleted)');
402
+ // Delete branch (if we have one)
403
+ if (branchName) {
404
+ const branchSpinner = (0, ora_1.default)('Deleting branch...').start();
405
+ try {
406
+ (0, child_process_1.execSync)(`git branch -D "${branchName}"`, {
407
+ cwd: projectRoot,
408
+ stdio: 'pipe',
409
+ });
410
+ branchSpinner.succeed('Branch deleted');
411
+ }
412
+ catch {
413
+ branchSpinner.warn('Could not delete branch (may already be deleted)');
414
+ }
341
415
  }
342
416
  console.log();
343
417
  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.4",
3
+ "version": "1.6.6",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {