claude-issue-solver 1.25.0 → 1.26.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.
package/README.md CHANGED
@@ -132,6 +132,9 @@ claude-issue pr 42
132
132
  claude-issue review # Interactive: select PRs to review in parallel
133
133
  claude-issue review 42 # Review specific issue's PR
134
134
 
135
+ # Merge approved PRs and clean up worktrees
136
+ claude-issue merge # Interactive: select PRs to merge
137
+
135
138
  # Clean up worktree and branch
136
139
  claude-issue clean 42 # Clean specific issue
137
140
  claude-issue clean # Interactive selection
@@ -157,6 +160,7 @@ claude-issue --help
157
160
  | `claude-issue show <number>` | - | Show full issue details |
158
161
  | `claude-issue pr <number>` | - | Create PR for solved issue |
159
162
  | `claude-issue review [number]` | - | Review PRs with AI suggestions |
163
+ | `claude-issue merge` | - | Merge approved PRs and clean up |
160
164
  | `claude-issue config` | - | Manage settings (bot token) |
161
165
  | `claude-issue clean [number]` | `rm` | Remove worktree and branch |
162
166
  | `claude-issue go [number]` | - | Navigate to worktree |
@@ -0,0 +1 @@
1
+ export declare function mergeCommand(): Promise<void>;
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.mergeCommand = mergeCommand;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const ora_1 = __importDefault(require("ora"));
42
+ const inquirer_1 = __importDefault(require("inquirer"));
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const child_process_1 = require("child_process");
47
+ const git_1 = require("../utils/git");
48
+ function closeWindowsWithPath(folderPath, issueNumber) {
49
+ if (os.platform() !== 'darwin')
50
+ return;
51
+ const folderName = path.basename(folderPath);
52
+ const issuePattern = `Issue #${issueNumber}`;
53
+ // Try to close iTerm2 tabs/windows
54
+ try {
55
+ (0, child_process_1.execSync)(`osascript -e '
56
+ tell application "iTerm"
57
+ repeat with w in windows
58
+ repeat with t in tabs of w
59
+ repeat with s in sessions of t
60
+ set sessionName to name of s
61
+ if sessionName contains "${folderName}" or sessionName contains "${issuePattern}" then
62
+ close s
63
+ end if
64
+ end repeat
65
+ end repeat
66
+ end repeat
67
+ end tell
68
+ '`, { stdio: 'pipe' });
69
+ }
70
+ catch {
71
+ // iTerm not running
72
+ }
73
+ // Try to close Terminal.app windows
74
+ try {
75
+ (0, child_process_1.execSync)(`osascript -e '
76
+ tell application "Terminal"
77
+ repeat with w in windows
78
+ set windowName to name of w
79
+ if windowName contains "${folderName}" or windowName contains "${issuePattern}" then
80
+ close w
81
+ end if
82
+ end repeat
83
+ end tell
84
+ '`, { stdio: 'pipe' });
85
+ }
86
+ catch {
87
+ // Terminal not running
88
+ }
89
+ // Try to close VS Code windows
90
+ try {
91
+ (0, child_process_1.execSync)(`osascript -e '
92
+ tell application "System Events"
93
+ if exists process "Code" then
94
+ tell process "Code"
95
+ set windowList to every window
96
+ repeat with w in windowList
97
+ try
98
+ set windowName to name of w
99
+ if windowName contains "${folderName}" then
100
+ perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")
101
+ delay 0.2
102
+ end if
103
+ end try
104
+ end repeat
105
+ end tell
106
+ end if
107
+ end tell
108
+ '`, { stdio: 'pipe', timeout: 5000 });
109
+ }
110
+ catch {
111
+ // VS Code not running
112
+ }
113
+ }
114
+ function getOpenPRs(projectRoot) {
115
+ try {
116
+ const output = (0, child_process_1.execSync)('gh pr list --state open --json number,title,headRefName,reviewDecision,mergeable --limit 50', {
117
+ cwd: projectRoot,
118
+ encoding: 'utf-8',
119
+ stdio: ['pipe', 'pipe', 'pipe'],
120
+ });
121
+ const prs = JSON.parse(output);
122
+ return prs.map((pr) => {
123
+ const match = pr.headRefName.match(/^issue-(\d+)-/);
124
+ return {
125
+ ...pr,
126
+ issueNumber: match ? parseInt(match[1], 10) : null,
127
+ };
128
+ });
129
+ }
130
+ catch {
131
+ return [];
132
+ }
133
+ }
134
+ function cleanupWorktree(projectRoot, branchName, issueNumber) {
135
+ const projectName = (0, git_1.getProjectName)();
136
+ const parentDir = path.dirname(projectRoot);
137
+ const worktreePath = path.join(parentDir, `${projectName}-${branchName}`);
138
+ // Close windows
139
+ if (issueNumber) {
140
+ try {
141
+ closeWindowsWithPath(worktreePath, issueNumber);
142
+ }
143
+ catch {
144
+ // Ignore
145
+ }
146
+ }
147
+ // Remove worktree
148
+ if (fs.existsSync(worktreePath)) {
149
+ try {
150
+ (0, child_process_1.execSync)(`git worktree remove "${worktreePath}" --force`, {
151
+ cwd: projectRoot,
152
+ stdio: 'pipe',
153
+ });
154
+ }
155
+ catch {
156
+ // Try force delete
157
+ try {
158
+ (0, child_process_1.execSync)(`/bin/rm -rf "${worktreePath}"`, { stdio: 'pipe', timeout: 10000 });
159
+ }
160
+ catch {
161
+ // Ignore
162
+ }
163
+ }
164
+ }
165
+ // Prune worktrees
166
+ try {
167
+ (0, child_process_1.execSync)('git worktree prune', { cwd: projectRoot, stdio: 'pipe' });
168
+ }
169
+ catch {
170
+ // Ignore
171
+ }
172
+ // Delete local branch
173
+ try {
174
+ (0, child_process_1.execSync)(`git branch -D "${branchName}"`, { cwd: projectRoot, stdio: 'pipe' });
175
+ }
176
+ catch {
177
+ // Branch may not exist locally
178
+ }
179
+ }
180
+ async function mergeCommand() {
181
+ const projectRoot = (0, git_1.getProjectRoot)();
182
+ const projectName = (0, git_1.getProjectName)();
183
+ console.log(chalk_1.default.bold(`\nOpen PRs for ${projectName}:\n`));
184
+ const spinner = (0, ora_1.default)('Fetching open PRs...').start();
185
+ const prs = getOpenPRs(projectRoot);
186
+ spinner.stop();
187
+ if (prs.length === 0) {
188
+ console.log(chalk_1.default.yellow('No open PRs found.'));
189
+ return;
190
+ }
191
+ // Build choices with status
192
+ const choices = prs.map((pr) => {
193
+ const issueTag = pr.issueNumber ? chalk_1.default.dim(` (issue #${pr.issueNumber})`) : '';
194
+ let statusTag = '';
195
+ let canMerge = false;
196
+ switch (pr.reviewDecision) {
197
+ case 'APPROVED':
198
+ statusTag = chalk_1.default.green(' ✓ Approved');
199
+ canMerge = pr.mergeable === 'MERGEABLE';
200
+ break;
201
+ case 'CHANGES_REQUESTED':
202
+ statusTag = chalk_1.default.red(' ✗ Changes requested');
203
+ break;
204
+ case 'REVIEW_REQUIRED':
205
+ statusTag = chalk_1.default.yellow(' ○ Review required');
206
+ break;
207
+ default:
208
+ statusTag = chalk_1.default.dim(' ○ No reviews');
209
+ }
210
+ if (pr.mergeable === 'CONFLICTING') {
211
+ statusTag += chalk_1.default.red(' ⚠ Conflicts');
212
+ }
213
+ return {
214
+ name: `#${pr.number}\t${pr.title}${issueTag}${statusTag}`,
215
+ value: pr,
216
+ checked: canMerge, // Pre-select approved & mergeable PRs
217
+ };
218
+ });
219
+ const { selected } = await inquirer_1.default.prompt([
220
+ {
221
+ type: 'checkbox',
222
+ name: 'selected',
223
+ message: 'Select PRs to merge and clean up (space to toggle, enter to confirm):',
224
+ choices,
225
+ },
226
+ ]);
227
+ if (selected.length === 0) {
228
+ console.log(chalk_1.default.dim('No PRs selected.'));
229
+ return;
230
+ }
231
+ console.log();
232
+ // Confirm merge
233
+ const { confirm } = await inquirer_1.default.prompt([
234
+ {
235
+ type: 'confirm',
236
+ name: 'confirm',
237
+ message: `Merge ${selected.length} PR(s) and clean up worktrees?`,
238
+ default: true,
239
+ },
240
+ ]);
241
+ if (!confirm) {
242
+ console.log(chalk_1.default.dim('Cancelled.'));
243
+ return;
244
+ }
245
+ console.log();
246
+ let merged = 0;
247
+ let failed = 0;
248
+ for (const pr of selected) {
249
+ const prSpinner = (0, ora_1.default)(`Merging PR #${pr.number}...`).start();
250
+ try {
251
+ // Merge the PR
252
+ (0, child_process_1.execSync)(`gh pr merge ${pr.number} --squash --delete-branch`, {
253
+ cwd: projectRoot,
254
+ encoding: 'utf-8',
255
+ stdio: ['pipe', 'pipe', 'pipe'],
256
+ });
257
+ prSpinner.succeed(`Merged PR #${pr.number}: ${pr.title.slice(0, 50)}`);
258
+ // Clean up worktree
259
+ const cleanSpinner = (0, ora_1.default)(` Cleaning up worktree...`).start();
260
+ try {
261
+ cleanupWorktree(projectRoot, pr.headRefName, pr.issueNumber?.toString() || null);
262
+ cleanSpinner.succeed(` Cleaned up worktree`);
263
+ }
264
+ catch {
265
+ cleanSpinner.warn(` Could not clean worktree (may not exist)`);
266
+ }
267
+ merged++;
268
+ }
269
+ catch (error) {
270
+ const errorMsg = error.stderr?.toString() || error.message || 'Unknown error';
271
+ prSpinner.fail(`Failed to merge PR #${pr.number}: ${errorMsg.split('\n')[0]}`);
272
+ failed++;
273
+ }
274
+ }
275
+ console.log();
276
+ if (merged > 0) {
277
+ console.log(chalk_1.default.green(`✅ Merged ${merged} PR(s)!`));
278
+ }
279
+ if (failed > 0) {
280
+ console.log(chalk_1.default.yellow(`⚠️ ${failed} PR(s) could not be merged.`));
281
+ }
282
+ }
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ const init_1 = require("./commands/init");
19
19
  const show_1 = require("./commands/show");
20
20
  const review_1 = require("./commands/review");
21
21
  const config_1 = require("./commands/config");
22
+ const merge_1 = require("./commands/merge");
22
23
  // eslint-disable-next-line @typescript-eslint/no-var-requires
23
24
  const packageJson = require('../package.json');
24
25
  const program = new commander_1.Command();
@@ -176,4 +177,11 @@ program
176
177
  .action(async (action, value) => {
177
178
  await (0, config_1.configCommand)(action, value);
178
179
  });
180
+ // Merge command - merge PRs and clean up
181
+ program
182
+ .command('merge')
183
+ .description('Merge approved PRs and clean up worktrees')
184
+ .action(async () => {
185
+ await (0, merge_1.mergeCommand)();
186
+ });
179
187
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {