claude-issue-solver 1.21.0 โ 1.23.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 +6 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +486 -0
- package/dist/index.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ Starting 2 issue(s)...
|
|
|
59
59
|
- โจ **Create and solve** - Create new issues and start solving them immediately
|
|
60
60
|
- ๐ฟ **Worktree isolation** - Each issue gets its own worktree, work on multiple issues in parallel
|
|
61
61
|
- ๐ค **Real-time PR creation** - Automatically creates/updates PR as Claude commits changes
|
|
62
|
+
- ๐ **AI code review** - Review PRs with Claude, posts suggestions you can commit directly on GitHub
|
|
62
63
|
- ๐งน **Smart cleanup** - Auto-clean merged PRs, close VS Code/terminal windows on macOS
|
|
63
64
|
- ๐ **Monorepo support** - Recursively copies all `.env*` files, symlinks `node_modules`
|
|
64
65
|
- ๐ป **Cross-platform terminals** - iTerm2, Terminal.app (macOS), gnome-terminal, xterm, konsole (Linux)
|
|
@@ -127,6 +128,10 @@ claude-issue new "Fix crash" -l bug -l priority
|
|
|
127
128
|
# Create PR for a solved issue (if you skipped it earlier)
|
|
128
129
|
claude-issue pr 42
|
|
129
130
|
|
|
131
|
+
# Review PRs with AI (posts suggestions you can commit on GitHub)
|
|
132
|
+
claude-issue review # Interactive: select PRs to review in parallel
|
|
133
|
+
claude-issue review 42 # Review specific issue's PR
|
|
134
|
+
|
|
130
135
|
# Clean up worktree and branch
|
|
131
136
|
claude-issue clean 42 # Clean specific issue
|
|
132
137
|
claude-issue clean # Interactive selection
|
|
@@ -151,6 +156,7 @@ claude-issue --help
|
|
|
151
156
|
| `claude-issue list` | `ls` | List open issues |
|
|
152
157
|
| `claude-issue show <number>` | - | Show full issue details |
|
|
153
158
|
| `claude-issue pr <number>` | - | Create PR for solved issue |
|
|
159
|
+
| `claude-issue review [number]` | - | Review PRs with AI suggestions |
|
|
154
160
|
| `claude-issue clean [number]` | `rm` | Remove worktree and branch |
|
|
155
161
|
| `claude-issue go [number]` | - | Navigate to worktree |
|
|
156
162
|
| `claude-issue init` | - | Setup wizard for requirements |
|
|
@@ -0,0 +1,486 @@
|
|
|
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.reviewCommand = reviewCommand;
|
|
40
|
+
exports.selectReviewCommand = selectReviewCommand;
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const ora_1 = __importDefault(require("ora"));
|
|
43
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const github_1 = require("../utils/github");
|
|
48
|
+
const git_1 = require("../utils/git");
|
|
49
|
+
const helpers_1 = require("../utils/helpers");
|
|
50
|
+
async function reviewCommand(issueNumber) {
|
|
51
|
+
const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
|
|
52
|
+
const issue = (0, github_1.getIssue)(issueNumber);
|
|
53
|
+
if (!issue) {
|
|
54
|
+
spinner.fail(`Could not find issue #${issueNumber}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
spinner.succeed(`Found issue #${issueNumber}`);
|
|
58
|
+
const projectRoot = (0, git_1.getProjectRoot)();
|
|
59
|
+
const projectName = (0, git_1.getProjectName)();
|
|
60
|
+
const baseBranch = (0, git_1.getDefaultBranch)();
|
|
61
|
+
const branchSlug = (0, helpers_1.slugify)(issue.title);
|
|
62
|
+
const branchName = `issue-${issueNumber}-${branchSlug}`;
|
|
63
|
+
const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
|
|
64
|
+
// Check if there's a PR for this issue
|
|
65
|
+
const prCheckSpinner = (0, ora_1.default)('Checking for PR...').start();
|
|
66
|
+
let prNumber = null;
|
|
67
|
+
try {
|
|
68
|
+
const prOutput = (0, child_process_1.execSync)(`gh pr list --head "${branchName}" --json number --jq '.[0].number'`, {
|
|
69
|
+
cwd: projectRoot,
|
|
70
|
+
encoding: 'utf-8',
|
|
71
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
|
+
}).trim();
|
|
73
|
+
if (prOutput) {
|
|
74
|
+
prNumber = prOutput;
|
|
75
|
+
prCheckSpinner.succeed(`Found PR #${prNumber}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
prCheckSpinner.fail('No PR found for this issue');
|
|
79
|
+
console.log(chalk_1.default.yellow('\nA PR must exist before you can review it.'));
|
|
80
|
+
console.log(chalk_1.default.dim(`First solve the issue: claude-issue ${issueNumber}`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
prCheckSpinner.fail('Could not check for PR');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk_1.default.bold(`๐ Reviewing: ${issue.title}`));
|
|
90
|
+
console.log(chalk_1.default.dim(`๐ PR: https://github.com/${getRepoName(projectRoot)}/pull/${prNumber}`));
|
|
91
|
+
console.log();
|
|
92
|
+
// Fetch latest
|
|
93
|
+
const fetchSpinner = (0, ora_1.default)(`Fetching latest changes...`).start();
|
|
94
|
+
try {
|
|
95
|
+
(0, child_process_1.execSync)(`git fetch origin ${branchName} --quiet`, { cwd: projectRoot, stdio: 'pipe' });
|
|
96
|
+
fetchSpinner.succeed('Fetched latest changes');
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
fetchSpinner.warn('Could not fetch branch');
|
|
100
|
+
}
|
|
101
|
+
// Check if worktree already exists
|
|
102
|
+
if (fs.existsSync(worktreePath)) {
|
|
103
|
+
console.log(chalk_1.default.yellow(`\n๐ฟ Using existing worktree at: ${worktreePath}`));
|
|
104
|
+
// Pull latest changes
|
|
105
|
+
try {
|
|
106
|
+
(0, child_process_1.execSync)('git pull --quiet', { cwd: worktreePath, stdio: 'pipe' });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Ignore pull errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const worktreeSpinner = (0, ora_1.default)(`Creating worktree for review...`).start();
|
|
114
|
+
try {
|
|
115
|
+
if ((0, git_1.branchExists)(branchName)) {
|
|
116
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
|
|
117
|
+
cwd: projectRoot,
|
|
118
|
+
stdio: 'pipe',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Branch should exist if PR exists, but handle edge case
|
|
123
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" "origin/${branchName}"`, {
|
|
124
|
+
cwd: projectRoot,
|
|
125
|
+
stdio: 'pipe',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
worktreeSpinner.succeed(`Created worktree at: ${worktreePath}`);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
worktreeSpinner.fail('Failed to create worktree');
|
|
132
|
+
console.error(error);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
// Copy env files and symlink node_modules
|
|
136
|
+
const setupSpinner = (0, ora_1.default)('Setting up worktree...').start();
|
|
137
|
+
(0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
|
|
138
|
+
(0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
|
|
139
|
+
setupSpinner.succeed('Worktree setup complete');
|
|
140
|
+
}
|
|
141
|
+
// Get the diff for context
|
|
142
|
+
let diffContent = '';
|
|
143
|
+
try {
|
|
144
|
+
diffContent = (0, child_process_1.execSync)(`gh pr diff ${prNumber}`, {
|
|
145
|
+
cwd: projectRoot,
|
|
146
|
+
encoding: 'utf-8',
|
|
147
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
148
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large diffs
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
console.log(chalk_1.default.yellow('Could not fetch PR diff, Claude will review the files directly.'));
|
|
153
|
+
}
|
|
154
|
+
// Build the review prompt
|
|
155
|
+
const prompt = `You are reviewing PR #${prNumber} for issue #${issueNumber}: ${issue.title}
|
|
156
|
+
|
|
157
|
+
## Issue Description
|
|
158
|
+
${issue.body}
|
|
159
|
+
|
|
160
|
+
## Your Task
|
|
161
|
+
Review the code changes in this PR. Look for:
|
|
162
|
+
1. Bugs and logic errors
|
|
163
|
+
2. Security vulnerabilities
|
|
164
|
+
3. Missing error handling
|
|
165
|
+
4. Code quality issues
|
|
166
|
+
5. Missing tests
|
|
167
|
+
6. Performance problems
|
|
168
|
+
|
|
169
|
+
## How to Leave Feedback
|
|
170
|
+
Use the gh CLI to post review comments with suggestions. For each issue you find:
|
|
171
|
+
|
|
172
|
+
\`\`\`bash
|
|
173
|
+
gh pr review ${prNumber} --comment --body "**File: path/to/file.ts**
|
|
174
|
+
|
|
175
|
+
Description of the issue...
|
|
176
|
+
|
|
177
|
+
\\\`\\\`\\\`suggestion
|
|
178
|
+
// Your suggested fix here
|
|
179
|
+
\\\`\\\`\\\`
|
|
180
|
+
"
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
The \`suggestion\` code block will create a "Commit suggestion" button on GitHub.
|
|
184
|
+
|
|
185
|
+
For a final review summary, use:
|
|
186
|
+
\`\`\`bash
|
|
187
|
+
gh pr review ${prNumber} --comment --body "## Review Summary
|
|
188
|
+
|
|
189
|
+
- Issue 1: ...
|
|
190
|
+
- Issue 2: ...
|
|
191
|
+
|
|
192
|
+
Overall: [APPROVE/REQUEST_CHANGES/COMMENT]"
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
Or to approve/request changes formally:
|
|
196
|
+
\`\`\`bash
|
|
197
|
+
gh pr review ${prNumber} --approve --body "LGTM! Code looks good."
|
|
198
|
+
gh pr review ${prNumber} --request-changes --body "Please address the issues above."
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
## PR Diff
|
|
202
|
+
${diffContent ? `\n\`\`\`diff\n${diffContent.slice(0, 50000)}\n\`\`\`\n` : 'Run `gh pr diff ' + prNumber + '` to see the changes.'}
|
|
203
|
+
|
|
204
|
+
Start by examining the diff and the changed files, then provide your review.`;
|
|
205
|
+
// Write prompt to a file
|
|
206
|
+
const promptFile = path.join(worktreePath, '.claude-review-prompt.txt');
|
|
207
|
+
fs.writeFileSync(promptFile, prompt);
|
|
208
|
+
// Create runner script for review
|
|
209
|
+
const runnerScript = path.join(worktreePath, '.claude-review-runner.sh');
|
|
210
|
+
const runnerContent = `#!/bin/bash
|
|
211
|
+
cd "${worktreePath}"
|
|
212
|
+
|
|
213
|
+
# Set terminal title
|
|
214
|
+
echo -ne "\\033]0;Review PR #${prNumber}: ${issue.title.replace(/"/g, '\\"').slice(0, 50)}\\007"
|
|
215
|
+
|
|
216
|
+
echo "๐ Claude Code Review - PR #${prNumber}"
|
|
217
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
218
|
+
echo ""
|
|
219
|
+
echo "Issue #${issueNumber}: ${issue.title.replace(/"/g, '\\"')}"
|
|
220
|
+
echo ""
|
|
221
|
+
echo "Claude will review the PR and post suggestions."
|
|
222
|
+
echo "You can commit suggestions directly on GitHub."
|
|
223
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
224
|
+
echo ""
|
|
225
|
+
|
|
226
|
+
# Run Claude interactively
|
|
227
|
+
claude --dangerously-skip-permissions "$(cat '${promptFile}')"
|
|
228
|
+
|
|
229
|
+
# Clean up prompt file
|
|
230
|
+
rm -f '${promptFile}'
|
|
231
|
+
|
|
232
|
+
echo ""
|
|
233
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
234
|
+
echo "Review session ended."
|
|
235
|
+
echo ""
|
|
236
|
+
echo "View PR: gh pr view ${prNumber} --web"
|
|
237
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
238
|
+
echo ""
|
|
239
|
+
|
|
240
|
+
# Keep terminal open
|
|
241
|
+
exec bash
|
|
242
|
+
`;
|
|
243
|
+
fs.writeFileSync(runnerScript, runnerContent, { mode: 0o755 });
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(chalk_1.default.cyan('๐ Opening new terminal for code review...'));
|
|
246
|
+
console.log();
|
|
247
|
+
(0, helpers_1.openInNewTerminal)(`'${runnerScript}'`);
|
|
248
|
+
console.log(chalk_1.default.green(`โ
Review session started for PR #${prNumber}`));
|
|
249
|
+
console.log(chalk_1.default.dim(` Claude is reviewing in a new terminal window.`));
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(chalk_1.default.dim(` View PR: gh pr view ${prNumber} --web`));
|
|
252
|
+
console.log(chalk_1.default.dim(` To clean up later: claude-issue clean ${issueNumber}`));
|
|
253
|
+
}
|
|
254
|
+
function getRepoName(projectRoot) {
|
|
255
|
+
try {
|
|
256
|
+
const output = (0, child_process_1.execSync)('gh repo view --json nameWithOwner --jq .nameWithOwner', {
|
|
257
|
+
cwd: projectRoot,
|
|
258
|
+
encoding: 'utf-8',
|
|
259
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
260
|
+
});
|
|
261
|
+
return output.trim();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return '';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function getOpenPRs(projectRoot) {
|
|
268
|
+
try {
|
|
269
|
+
const output = (0, child_process_1.execSync)('gh pr list --state open --json number,title,headRefName --limit 50', {
|
|
270
|
+
cwd: projectRoot,
|
|
271
|
+
encoding: 'utf-8',
|
|
272
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
273
|
+
});
|
|
274
|
+
const prs = JSON.parse(output);
|
|
275
|
+
return prs.map((pr) => {
|
|
276
|
+
// Try to extract issue number from branch name (issue-42-slug)
|
|
277
|
+
const match = pr.headRefName.match(/^issue-(\d+)-/);
|
|
278
|
+
return {
|
|
279
|
+
...pr,
|
|
280
|
+
issueNumber: match ? parseInt(match[1], 10) : null,
|
|
281
|
+
};
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function selectReviewCommand() {
|
|
289
|
+
const projectRoot = (0, git_1.getProjectRoot)();
|
|
290
|
+
const projectName = (0, git_1.getProjectName)();
|
|
291
|
+
console.log(chalk_1.default.bold(`\nOpen PRs for ${projectName}:\n`));
|
|
292
|
+
const spinner = (0, ora_1.default)('Fetching open PRs...').start();
|
|
293
|
+
const prs = getOpenPRs(projectRoot);
|
|
294
|
+
spinner.stop();
|
|
295
|
+
if (prs.length === 0) {
|
|
296
|
+
console.log(chalk_1.default.yellow('No open PRs found.'));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Build choices for checkbox prompt
|
|
300
|
+
const choices = prs.map((pr) => {
|
|
301
|
+
const issueTag = pr.issueNumber ? chalk_1.default.dim(` (issue #${pr.issueNumber})`) : '';
|
|
302
|
+
return {
|
|
303
|
+
name: `#${pr.number}\t${pr.title}${issueTag}`,
|
|
304
|
+
value: pr,
|
|
305
|
+
checked: false,
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
const { selected } = await inquirer_1.default.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: 'checkbox',
|
|
311
|
+
name: 'selected',
|
|
312
|
+
message: 'Select PRs to review (space to toggle, enter to confirm):',
|
|
313
|
+
choices,
|
|
314
|
+
},
|
|
315
|
+
]);
|
|
316
|
+
if (selected.length === 0) {
|
|
317
|
+
console.log(chalk_1.default.dim('No PRs selected.'));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
console.log();
|
|
321
|
+
console.log(chalk_1.default.cyan(`๐ Starting ${selected.length} review session(s) in parallel...`));
|
|
322
|
+
console.log();
|
|
323
|
+
// Launch reviews in parallel
|
|
324
|
+
for (const pr of selected) {
|
|
325
|
+
await launchReviewForPR(pr, projectRoot, projectName);
|
|
326
|
+
}
|
|
327
|
+
console.log();
|
|
328
|
+
console.log(chalk_1.default.green(`โ
Started ${selected.length} review session(s)!`));
|
|
329
|
+
console.log(chalk_1.default.dim(' Each review is running in its own terminal window.'));
|
|
330
|
+
}
|
|
331
|
+
async function launchReviewForPR(pr, projectRoot, projectName) {
|
|
332
|
+
const baseBranch = (0, git_1.getDefaultBranch)();
|
|
333
|
+
const branchName = pr.headRefName;
|
|
334
|
+
const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
|
|
335
|
+
// Fetch latest
|
|
336
|
+
try {
|
|
337
|
+
(0, child_process_1.execSync)(`git fetch origin ${branchName} --quiet`, { cwd: projectRoot, stdio: 'pipe' });
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Ignore fetch errors
|
|
341
|
+
}
|
|
342
|
+
// Check if worktree already exists
|
|
343
|
+
if (!fs.existsSync(worktreePath)) {
|
|
344
|
+
try {
|
|
345
|
+
if ((0, git_1.branchExists)(branchName)) {
|
|
346
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
|
|
347
|
+
cwd: projectRoot,
|
|
348
|
+
stdio: 'pipe',
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
(0, child_process_1.execSync)(`git worktree add "${worktreePath}" "origin/${branchName}"`, {
|
|
353
|
+
cwd: projectRoot,
|
|
354
|
+
stdio: 'pipe',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// Copy env files and symlink node_modules
|
|
358
|
+
(0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
|
|
359
|
+
(0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.log(chalk_1.default.yellow(`โ ๏ธ Could not create worktree for PR #${pr.number}`));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
// Pull latest changes
|
|
368
|
+
try {
|
|
369
|
+
(0, child_process_1.execSync)('git pull --quiet', { cwd: worktreePath, stdio: 'pipe' });
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// Ignore pull errors
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Get the diff for context
|
|
376
|
+
let diffContent = '';
|
|
377
|
+
try {
|
|
378
|
+
diffContent = (0, child_process_1.execSync)(`gh pr diff ${pr.number}`, {
|
|
379
|
+
cwd: projectRoot,
|
|
380
|
+
encoding: 'utf-8',
|
|
381
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
382
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// Ignore diff errors
|
|
387
|
+
}
|
|
388
|
+
// Get issue body if we have an issue number
|
|
389
|
+
let issueBody = '';
|
|
390
|
+
if (pr.issueNumber) {
|
|
391
|
+
const issue = (0, github_1.getIssue)(pr.issueNumber);
|
|
392
|
+
if (issue) {
|
|
393
|
+
issueBody = issue.body;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Build the review prompt
|
|
397
|
+
const prompt = `You are reviewing PR #${pr.number}: ${pr.title}
|
|
398
|
+
${pr.issueNumber ? `\n## Related Issue #${pr.issueNumber}\n${issueBody}\n` : ''}
|
|
399
|
+
## Your Task
|
|
400
|
+
Review the code changes in this PR. Look for:
|
|
401
|
+
1. Bugs and logic errors
|
|
402
|
+
2. Security vulnerabilities
|
|
403
|
+
3. Missing error handling
|
|
404
|
+
4. Code quality issues
|
|
405
|
+
5. Missing tests
|
|
406
|
+
6. Performance problems
|
|
407
|
+
|
|
408
|
+
## How to Leave Feedback
|
|
409
|
+
Use the gh CLI to post review comments with suggestions. For each issue you find:
|
|
410
|
+
|
|
411
|
+
\`\`\`bash
|
|
412
|
+
gh pr review ${pr.number} --comment --body "**File: path/to/file.ts**
|
|
413
|
+
|
|
414
|
+
Description of the issue...
|
|
415
|
+
|
|
416
|
+
\\\`\\\`\\\`suggestion
|
|
417
|
+
// Your suggested fix here
|
|
418
|
+
\\\`\\\`\\\`
|
|
419
|
+
"
|
|
420
|
+
\`\`\`
|
|
421
|
+
|
|
422
|
+
The \`suggestion\` code block will create a "Commit suggestion" button on GitHub.
|
|
423
|
+
|
|
424
|
+
For a final review summary, use:
|
|
425
|
+
\`\`\`bash
|
|
426
|
+
gh pr review ${pr.number} --comment --body "## Review Summary
|
|
427
|
+
|
|
428
|
+
- Issue 1: ...
|
|
429
|
+
- Issue 2: ...
|
|
430
|
+
|
|
431
|
+
Overall: [APPROVE/REQUEST_CHANGES/COMMENT]"
|
|
432
|
+
\`\`\`
|
|
433
|
+
|
|
434
|
+
Or to approve/request changes formally:
|
|
435
|
+
\`\`\`bash
|
|
436
|
+
gh pr review ${pr.number} --approve --body "LGTM! Code looks good."
|
|
437
|
+
gh pr review ${pr.number} --request-changes --body "Please address the issues above."
|
|
438
|
+
\`\`\`
|
|
439
|
+
|
|
440
|
+
## PR Diff
|
|
441
|
+
${diffContent ? `\n\`\`\`diff\n${diffContent.slice(0, 50000)}\n\`\`\`\n` : 'Run `gh pr diff ' + pr.number + '` to see the changes.'}
|
|
442
|
+
|
|
443
|
+
Start by examining the diff and the changed files, then provide your review.`;
|
|
444
|
+
// Write prompt to a file
|
|
445
|
+
const promptFile = path.join(worktreePath, '.claude-review-prompt.txt');
|
|
446
|
+
fs.writeFileSync(promptFile, prompt);
|
|
447
|
+
// Create runner script for review
|
|
448
|
+
const runnerScript = path.join(worktreePath, '.claude-review-runner.sh');
|
|
449
|
+
const escapedTitle = pr.title.replace(/"/g, '\\"').slice(0, 50);
|
|
450
|
+
const runnerContent = `#!/bin/bash
|
|
451
|
+
cd "${worktreePath}"
|
|
452
|
+
|
|
453
|
+
# Set terminal title
|
|
454
|
+
echo -ne "\\033]0;Review PR #${pr.number}: ${escapedTitle}\\007"
|
|
455
|
+
|
|
456
|
+
echo "๐ Claude Code Review - PR #${pr.number}"
|
|
457
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
458
|
+
echo ""
|
|
459
|
+
echo "${escapedTitle}"
|
|
460
|
+
echo ""
|
|
461
|
+
echo "Claude will review the PR and post suggestions."
|
|
462
|
+
echo "You can commit suggestions directly on GitHub."
|
|
463
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
464
|
+
echo ""
|
|
465
|
+
|
|
466
|
+
# Run Claude interactively
|
|
467
|
+
claude --dangerously-skip-permissions "$(cat '${promptFile}')"
|
|
468
|
+
|
|
469
|
+
# Clean up prompt file
|
|
470
|
+
rm -f '${promptFile}'
|
|
471
|
+
|
|
472
|
+
echo ""
|
|
473
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
474
|
+
echo "Review session ended."
|
|
475
|
+
echo ""
|
|
476
|
+
echo "View PR: gh pr view ${pr.number} --web"
|
|
477
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
478
|
+
echo ""
|
|
479
|
+
|
|
480
|
+
# Keep terminal open
|
|
481
|
+
exec bash
|
|
482
|
+
`;
|
|
483
|
+
fs.writeFileSync(runnerScript, runnerContent, { mode: 0o755 });
|
|
484
|
+
console.log(chalk_1.default.dim(` Starting review for PR #${pr.number}: ${pr.title.slice(0, 50)}...`));
|
|
485
|
+
(0, helpers_1.openInNewTerminal)(`'${runnerScript}'`);
|
|
486
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const go_1 = require("./commands/go");
|
|
|
17
17
|
const new_1 = require("./commands/new");
|
|
18
18
|
const init_1 = require("./commands/init");
|
|
19
19
|
const show_1 = require("./commands/show");
|
|
20
|
+
const review_1 = require("./commands/review");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
22
|
const packageJson = require('../package.json');
|
|
22
23
|
const program = new commander_1.Command();
|
|
@@ -150,4 +151,21 @@ program
|
|
|
150
151
|
.action(async () => {
|
|
151
152
|
await (0, init_1.initCommand)();
|
|
152
153
|
});
|
|
154
|
+
// Review command - AI code review for PRs
|
|
155
|
+
program
|
|
156
|
+
.command('review [issue]')
|
|
157
|
+
.description('Review PRs with Claude and post suggestions')
|
|
158
|
+
.action(async (issue) => {
|
|
159
|
+
if (issue) {
|
|
160
|
+
const issueNumber = parseInt(issue, 10);
|
|
161
|
+
if (isNaN(issueNumber)) {
|
|
162
|
+
console.log(chalk_1.default.red(`โ Invalid issue number: ${issue}`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
await (0, review_1.reviewCommand)(issueNumber);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
await (0, review_1.selectReviewCommand)();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
153
171
|
program.parse();
|