claude-issue-solver 1.25.0 โ 1.26.1
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 +4 -0
- package/dist/commands/clean.js +11 -8
- package/dist/commands/merge.d.ts +1 -0
- package/dist/commands/merge.js +282 -0
- package/dist/index.js +8 -0
- package/package.json +1 -1
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 |
|
package/dist/commands/clean.js
CHANGED
|
@@ -527,10 +527,12 @@ async function cleanMergedCommand() {
|
|
|
527
527
|
prStatus: wt.branch ? await (0, github_1.getPRForBranchAsync)(wt.branch) : null,
|
|
528
528
|
})));
|
|
529
529
|
statusSpinner.stop();
|
|
530
|
-
// Filter to
|
|
530
|
+
// Filter to merged PRs and orphaned folders
|
|
531
531
|
const mergedWorktrees = worktreesWithStatus.filter((wt) => wt.prStatus?.state === 'merged');
|
|
532
|
-
|
|
533
|
-
|
|
532
|
+
const orphanedWorktrees = worktreesWithStatus.filter((wt) => !wt.branch);
|
|
533
|
+
const toClean = [...mergedWorktrees, ...orphanedWorktrees];
|
|
534
|
+
if (toClean.length === 0) {
|
|
535
|
+
console.log(chalk_1.default.yellow('\nNo worktrees with merged PRs or orphaned folders found.'));
|
|
534
536
|
// Show what's available
|
|
535
537
|
if (worktreesWithStatus.length > 0) {
|
|
536
538
|
console.log(chalk_1.default.dim('\nExisting worktrees:'));
|
|
@@ -541,15 +543,16 @@ async function cleanMergedCommand() {
|
|
|
541
543
|
}
|
|
542
544
|
return;
|
|
543
545
|
}
|
|
544
|
-
console.log(chalk_1.default.bold(`\n๐งน Cleaning ${
|
|
545
|
-
for (const wt of
|
|
546
|
-
|
|
546
|
+
console.log(chalk_1.default.bold(`\n๐งน Cleaning ${toClean.length} worktree(s):\n`));
|
|
547
|
+
for (const wt of toClean) {
|
|
548
|
+
const status = getStatusLabel(wt);
|
|
549
|
+
console.log(` ${chalk_1.default.cyan(`#${wt.issueNumber}`)}\t${status}`);
|
|
547
550
|
if (wt.branch) {
|
|
548
551
|
console.log(chalk_1.default.dim(` \t${wt.branch}`));
|
|
549
552
|
}
|
|
550
553
|
}
|
|
551
554
|
console.log();
|
|
552
|
-
for (const wt of
|
|
555
|
+
for (const wt of toClean) {
|
|
553
556
|
const spinner = (0, ora_1.default)(`Cleaning issue #${wt.issueNumber}...`).start();
|
|
554
557
|
try {
|
|
555
558
|
// Close terminal and VS Code windows for this worktree
|
|
@@ -632,5 +635,5 @@ async function cleanMergedCommand() {
|
|
|
632
635
|
// May fail if current directory was deleted
|
|
633
636
|
}
|
|
634
637
|
console.log();
|
|
635
|
-
console.log(chalk_1.default.green(`โ
Cleaned up ${
|
|
638
|
+
console.log(chalk_1.default.green(`โ
Cleaned up ${toClean.length} worktree(s)!`));
|
|
636
639
|
}
|
|
@@ -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();
|