claude-issue-solver 1.7.1 โ†’ 1.8.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.
@@ -1,2 +1,3 @@
1
1
  export declare function cleanAllCommand(): Promise<void>;
2
2
  export declare function cleanCommand(issueNumber: number): Promise<void>;
3
+ export declare function cleanMergedCommand(): Promise<void>;
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.cleanAllCommand = cleanAllCommand;
40
40
  exports.cleanCommand = cleanCommand;
41
+ exports.cleanMergedCommand = cleanMergedCommand;
41
42
  const chalk_1 = __importDefault(require("chalk"));
42
43
  const ora_1 = __importDefault(require("ora"));
43
44
  const inquirer_1 = __importDefault(require("inquirer"));
@@ -89,16 +90,31 @@ function closeWindowsWithPath(folderPath, issueNumber) {
89
90
  // Terminal not running or no matching windows
90
91
  }
91
92
  // Try to close VS Code windows with this path
93
+ // Method 1: AppleScript to close windows matching the folder name
92
94
  try {
93
- // Use VS Code CLI to close the folder if it's open
94
- (0, child_process_1.execSync)(`code --folder-uri "file://${folderPath}" --command "workbench.action.closeWindow"`, {
95
- stdio: 'pipe',
96
- timeout: 3000
97
- });
95
+ (0, child_process_1.execSync)(`osascript -e '
96
+ tell application "System Events"
97
+ if exists process "Code" then
98
+ tell process "Code"
99
+ set windowList to every window
100
+ repeat with w in windowList
101
+ try
102
+ set windowName to name of w
103
+ if windowName contains "${folderName}" then
104
+ perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
105
+ delay 0.2
106
+ end if
107
+ end try
108
+ end repeat
109
+ end tell
110
+ end if
111
+ end tell
112
+ '`, { stdio: 'pipe', timeout: 5000 });
98
113
  }
99
114
  catch {
100
- // VS Code CLI method failed, try AppleScript
115
+ // VS Code not running or no matching windows
101
116
  }
117
+ // Method 2: Also try matching the issue number in window title
102
118
  try {
103
119
  (0, child_process_1.execSync)(`osascript -e '
104
120
  tell application "System Events"
@@ -106,15 +122,18 @@ function closeWindowsWithPath(folderPath, issueNumber) {
106
122
  tell process "Code"
107
123
  set windowList to every window
108
124
  repeat with w in windowList
109
- set windowName to name of w
110
- if windowName contains "${folderName}" then
111
- perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
112
- end if
125
+ try
126
+ set windowName to name of w
127
+ if windowName contains "${issuePattern}" then
128
+ perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
129
+ delay 0.2
130
+ end if
131
+ end try
113
132
  end repeat
114
133
  end tell
115
134
  end if
116
135
  end tell
117
- '`, { stdio: 'pipe' });
136
+ '`, { stdio: 'pipe', timeout: 5000 });
118
137
  }
119
138
  catch {
120
139
  // VS Code not running or no matching windows
@@ -254,40 +273,46 @@ async function cleanAllCommand() {
254
273
  }
255
274
  // Remove worktree/folder
256
275
  const isOrphaned = !wt.branch;
276
+ // Try git worktree remove first (only if not orphaned)
277
+ if (!isOrphaned && fs.existsSync(wt.path)) {
278
+ try {
279
+ (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
280
+ cwd: projectRoot,
281
+ stdio: 'pipe',
282
+ });
283
+ }
284
+ catch {
285
+ // Ignore - we'll force delete below
286
+ }
287
+ }
288
+ // Always try to force delete the folder
257
289
  if (fs.existsSync(wt.path)) {
258
- // Try git worktree remove first (only if not orphaned)
259
- if (!isOrphaned) {
260
- try {
261
- (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
262
- cwd: projectRoot,
263
- stdio: 'pipe',
264
- });
265
- }
266
- catch {
267
- // Ignore - we'll force delete below if needed
268
- }
290
+ // Try multiple deletion methods
291
+ try {
292
+ (0, child_process_1.execSync)(`/bin/rm -rf "${wt.path}"`, { stdio: 'pipe', timeout: 10000 });
269
293
  }
270
- // If folder still exists, force delete it
271
- if (fs.existsSync(wt.path)) {
294
+ catch {
295
+ // Fallback 1: try with shell
272
296
  try {
273
- (0, child_process_1.execSync)(`/bin/rm -rf "${wt.path}"`, { stdio: 'pipe' });
297
+ (0, child_process_1.execSync)(`rm -rf "${wt.path}"`, { shell: '/bin/bash', stdio: 'pipe', timeout: 10000 });
274
298
  }
275
299
  catch {
300
+ // Fallback 2: Node.js rmSync
276
301
  try {
277
- fs.rmSync(wt.path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
302
+ fs.rmSync(wt.path, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
278
303
  }
279
304
  catch {
280
- // Ignore - will check at end
305
+ // Will report failure below
281
306
  }
282
307
  }
283
308
  }
284
- // Prune git worktrees
285
- try {
286
- (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
287
- }
288
- catch {
289
- // Ignore
290
- }
309
+ }
310
+ // Prune git worktrees
311
+ try {
312
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
313
+ }
314
+ catch {
315
+ // Ignore
291
316
  }
292
317
  // Delete branch (if we have one)
293
318
  if (wt.branch) {
@@ -301,7 +326,13 @@ async function cleanAllCommand() {
301
326
  // Branch may already be deleted
302
327
  }
303
328
  }
304
- spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
329
+ // Check if cleanup was successful
330
+ if (fs.existsSync(wt.path)) {
331
+ spinner.warn(`Cleaned issue #${wt.issueNumber} (folder may remain: ${wt.path})`);
332
+ }
333
+ else {
334
+ spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
335
+ }
305
336
  }
306
337
  catch (error) {
307
338
  spinner.fail(`Failed to clean issue #${wt.issueNumber}: ${error}`);
@@ -462,3 +493,120 @@ async function cleanCommand(issueNumber) {
462
493
  console.log();
463
494
  console.log(chalk_1.default.green('โœ… Cleanup complete!'));
464
495
  }
496
+ async function cleanMergedCommand() {
497
+ const projectRoot = (0, git_1.getProjectRoot)();
498
+ const worktrees = getIssueWorktrees();
499
+ if (worktrees.length === 0) {
500
+ console.log(chalk_1.default.yellow('\nNo issue worktrees found.'));
501
+ return;
502
+ }
503
+ // Fetch status for all worktrees
504
+ const statusSpinner = (0, ora_1.default)('Fetching PR status...').start();
505
+ const worktreesWithStatus = worktrees.map((wt) => ({
506
+ ...wt,
507
+ issueStatus: (0, github_1.getIssueStatus)(parseInt(wt.issueNumber, 10)),
508
+ prStatus: wt.branch ? (0, github_1.getPRForBranch)(wt.branch) : null,
509
+ }));
510
+ statusSpinner.stop();
511
+ // Filter to only merged PRs
512
+ const mergedWorktrees = worktreesWithStatus.filter((wt) => wt.prStatus?.state === 'merged');
513
+ if (mergedWorktrees.length === 0) {
514
+ console.log(chalk_1.default.yellow('\nNo worktrees with merged PRs found.'));
515
+ // Show what's available
516
+ if (worktreesWithStatus.length > 0) {
517
+ console.log(chalk_1.default.dim('\nExisting worktrees:'));
518
+ for (const wt of worktreesWithStatus) {
519
+ const status = getStatusLabel(wt);
520
+ console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${status}`);
521
+ }
522
+ }
523
+ return;
524
+ }
525
+ console.log(chalk_1.default.bold(`\n๐Ÿงน Cleaning ${mergedWorktrees.length} worktree(s) with merged PRs:\n`));
526
+ for (const wt of mergedWorktrees) {
527
+ console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${chalk_1.default.green('โœ“ PR merged')}`);
528
+ if (wt.branch) {
529
+ console.log(chalk_1.default.dim(` \t${wt.branch}`));
530
+ }
531
+ }
532
+ console.log();
533
+ for (const wt of mergedWorktrees) {
534
+ const spinner = (0, ora_1.default)(`Cleaning issue #${wt.issueNumber}...`).start();
535
+ try {
536
+ // Close terminal and VS Code windows for this worktree
537
+ try {
538
+ closeWindowsWithPath(wt.path, wt.issueNumber);
539
+ await new Promise((resolve) => setTimeout(resolve, 500));
540
+ }
541
+ catch {
542
+ // Ignore errors closing windows
543
+ }
544
+ // Remove worktree/folder
545
+ const isOrphaned = !wt.branch;
546
+ // Try git worktree remove first (only if not orphaned)
547
+ if (!isOrphaned && fs.existsSync(wt.path)) {
548
+ try {
549
+ (0, child_process_1.execSync)(`git worktree remove "${wt.path}" --force`, {
550
+ cwd: projectRoot,
551
+ stdio: 'pipe',
552
+ });
553
+ }
554
+ catch {
555
+ // Ignore - we'll force delete below
556
+ }
557
+ }
558
+ // Always try to force delete the folder
559
+ if (fs.existsSync(wt.path)) {
560
+ try {
561
+ (0, child_process_1.execSync)(`/bin/rm -rf "${wt.path}"`, { stdio: 'pipe', timeout: 10000 });
562
+ }
563
+ catch {
564
+ try {
565
+ (0, child_process_1.execSync)(`rm -rf "${wt.path}"`, { shell: '/bin/bash', stdio: 'pipe', timeout: 10000 });
566
+ }
567
+ catch {
568
+ try {
569
+ fs.rmSync(wt.path, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
570
+ }
571
+ catch {
572
+ // Will report failure below
573
+ }
574
+ }
575
+ }
576
+ }
577
+ // Prune git worktrees
578
+ try {
579
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
580
+ }
581
+ catch {
582
+ // Ignore
583
+ }
584
+ // Delete branch (if we have one)
585
+ if (wt.branch) {
586
+ try {
587
+ (0, child_process_1.execSync)(`git branch -D "${wt.branch}"`, {
588
+ cwd: projectRoot,
589
+ stdio: 'pipe',
590
+ });
591
+ }
592
+ catch {
593
+ // Branch may already be deleted
594
+ }
595
+ }
596
+ // Check if cleanup was successful
597
+ if (fs.existsSync(wt.path)) {
598
+ spinner.warn(`Cleaned issue #${wt.issueNumber} (folder may remain: ${wt.path})`);
599
+ }
600
+ else {
601
+ spinner.succeed(`Cleaned issue #${wt.issueNumber}`);
602
+ }
603
+ }
604
+ catch (error) {
605
+ spinner.fail(`Failed to clean issue #${wt.issueNumber}: ${error}`);
606
+ }
607
+ }
608
+ // Prune stale worktrees
609
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
610
+ console.log();
611
+ console.log(chalk_1.default.green(`โœ… Cleaned up ${mergedWorktrees.length} merged worktree(s)!`));
612
+ }
package/dist/index.js CHANGED
@@ -76,9 +76,13 @@ program
76
76
  .command('clean [issue]')
77
77
  .alias('rm')
78
78
  .option('-a, --all', 'Clean all issue worktrees')
79
- .description('Remove worktree and branch for an issue (or all with --all)')
79
+ .option('-m, --merged', 'Clean only worktrees with merged PRs (no confirmation)')
80
+ .description('Remove worktree and branch for an issue (or all with --all, or merged with --merged)')
80
81
  .action(async (issue, options) => {
81
- if (options.all) {
82
+ if (options.merged) {
83
+ await (0, clean_1.cleanMergedCommand)();
84
+ }
85
+ else if (options.all) {
82
86
  await (0, clean_1.cleanAllCommand)();
83
87
  }
84
88
  else if (issue) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {