claude-issue-solver 1.21.0 โ†’ 1.22.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
@@ -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,9 @@ 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 a PR with AI (posts suggestions you can commit on GitHub)
132
+ claude-issue review 42
133
+
130
134
  # Clean up worktree and branch
131
135
  claude-issue clean 42 # Clean specific issue
132
136
  claude-issue clean # Interactive selection
@@ -151,6 +155,7 @@ claude-issue --help
151
155
  | `claude-issue list` | `ls` | List open issues |
152
156
  | `claude-issue show <number>` | - | Show full issue details |
153
157
  | `claude-issue pr <number>` | - | Create PR for solved issue |
158
+ | `claude-issue review <number>` | - | Review PR with AI suggestions |
154
159
  | `claude-issue clean [number]` | `rm` | Remove worktree and branch |
155
160
  | `claude-issue go [number]` | - | Navigate to worktree |
156
161
  | `claude-issue init` | - | Setup wizard for requirements |
@@ -0,0 +1 @@
1
+ export declare function reviewCommand(issueNumber: number): Promise<void>;
@@ -0,0 +1,264 @@
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
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const ora_1 = __importDefault(require("ora"));
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const child_process_1 = require("child_process");
45
+ const github_1 = require("../utils/github");
46
+ const git_1 = require("../utils/git");
47
+ const helpers_1 = require("../utils/helpers");
48
+ async function reviewCommand(issueNumber) {
49
+ const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
50
+ const issue = (0, github_1.getIssue)(issueNumber);
51
+ if (!issue) {
52
+ spinner.fail(`Could not find issue #${issueNumber}`);
53
+ process.exit(1);
54
+ }
55
+ spinner.succeed(`Found issue #${issueNumber}`);
56
+ const projectRoot = (0, git_1.getProjectRoot)();
57
+ const projectName = (0, git_1.getProjectName)();
58
+ const baseBranch = (0, git_1.getDefaultBranch)();
59
+ const branchSlug = (0, helpers_1.slugify)(issue.title);
60
+ const branchName = `issue-${issueNumber}-${branchSlug}`;
61
+ const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
62
+ // Check if there's a PR for this issue
63
+ const prCheckSpinner = (0, ora_1.default)('Checking for PR...').start();
64
+ let prNumber = null;
65
+ try {
66
+ const prOutput = (0, child_process_1.execSync)(`gh pr list --head "${branchName}" --json number --jq '.[0].number'`, {
67
+ cwd: projectRoot,
68
+ encoding: 'utf-8',
69
+ stdio: ['pipe', 'pipe', 'pipe'],
70
+ }).trim();
71
+ if (prOutput) {
72
+ prNumber = prOutput;
73
+ prCheckSpinner.succeed(`Found PR #${prNumber}`);
74
+ }
75
+ else {
76
+ prCheckSpinner.fail('No PR found for this issue');
77
+ console.log(chalk_1.default.yellow('\nA PR must exist before you can review it.'));
78
+ console.log(chalk_1.default.dim(`First solve the issue: claude-issue ${issueNumber}`));
79
+ process.exit(1);
80
+ }
81
+ }
82
+ catch {
83
+ prCheckSpinner.fail('Could not check for PR');
84
+ process.exit(1);
85
+ }
86
+ console.log();
87
+ console.log(chalk_1.default.bold(`๐Ÿ“Œ Reviewing: ${issue.title}`));
88
+ console.log(chalk_1.default.dim(`๐Ÿ”— PR: https://github.com/${getRepoName(projectRoot)}/pull/${prNumber}`));
89
+ console.log();
90
+ // Fetch latest
91
+ const fetchSpinner = (0, ora_1.default)(`Fetching latest changes...`).start();
92
+ try {
93
+ (0, child_process_1.execSync)(`git fetch origin ${branchName} --quiet`, { cwd: projectRoot, stdio: 'pipe' });
94
+ fetchSpinner.succeed('Fetched latest changes');
95
+ }
96
+ catch {
97
+ fetchSpinner.warn('Could not fetch branch');
98
+ }
99
+ // Check if worktree already exists
100
+ if (fs.existsSync(worktreePath)) {
101
+ console.log(chalk_1.default.yellow(`\n๐ŸŒฟ Using existing worktree at: ${worktreePath}`));
102
+ // Pull latest changes
103
+ try {
104
+ (0, child_process_1.execSync)('git pull --quiet', { cwd: worktreePath, stdio: 'pipe' });
105
+ }
106
+ catch {
107
+ // Ignore pull errors
108
+ }
109
+ }
110
+ else {
111
+ const worktreeSpinner = (0, ora_1.default)(`Creating worktree for review...`).start();
112
+ try {
113
+ if ((0, git_1.branchExists)(branchName)) {
114
+ (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
115
+ cwd: projectRoot,
116
+ stdio: 'pipe',
117
+ });
118
+ }
119
+ else {
120
+ // Branch should exist if PR exists, but handle edge case
121
+ (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "origin/${branchName}"`, {
122
+ cwd: projectRoot,
123
+ stdio: 'pipe',
124
+ });
125
+ }
126
+ worktreeSpinner.succeed(`Created worktree at: ${worktreePath}`);
127
+ }
128
+ catch (error) {
129
+ worktreeSpinner.fail('Failed to create worktree');
130
+ console.error(error);
131
+ process.exit(1);
132
+ }
133
+ // Copy env files and symlink node_modules
134
+ const setupSpinner = (0, ora_1.default)('Setting up worktree...').start();
135
+ (0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
136
+ (0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
137
+ setupSpinner.succeed('Worktree setup complete');
138
+ }
139
+ // Get the diff for context
140
+ let diffContent = '';
141
+ try {
142
+ diffContent = (0, child_process_1.execSync)(`gh pr diff ${prNumber}`, {
143
+ cwd: projectRoot,
144
+ encoding: 'utf-8',
145
+ stdio: ['pipe', 'pipe', 'pipe'],
146
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large diffs
147
+ });
148
+ }
149
+ catch {
150
+ console.log(chalk_1.default.yellow('Could not fetch PR diff, Claude will review the files directly.'));
151
+ }
152
+ // Build the review prompt
153
+ const prompt = `You are reviewing PR #${prNumber} for issue #${issueNumber}: ${issue.title}
154
+
155
+ ## Issue Description
156
+ ${issue.body}
157
+
158
+ ## Your Task
159
+ Review the code changes in this PR. Look for:
160
+ 1. Bugs and logic errors
161
+ 2. Security vulnerabilities
162
+ 3. Missing error handling
163
+ 4. Code quality issues
164
+ 5. Missing tests
165
+ 6. Performance problems
166
+
167
+ ## How to Leave Feedback
168
+ Use the gh CLI to post review comments with suggestions. For each issue you find:
169
+
170
+ \`\`\`bash
171
+ gh pr review ${prNumber} --comment --body "**File: path/to/file.ts**
172
+
173
+ Description of the issue...
174
+
175
+ \\\`\\\`\\\`suggestion
176
+ // Your suggested fix here
177
+ \\\`\\\`\\\`
178
+ "
179
+ \`\`\`
180
+
181
+ The \`suggestion\` code block will create a "Commit suggestion" button on GitHub.
182
+
183
+ For a final review summary, use:
184
+ \`\`\`bash
185
+ gh pr review ${prNumber} --comment --body "## Review Summary
186
+
187
+ - Issue 1: ...
188
+ - Issue 2: ...
189
+
190
+ Overall: [APPROVE/REQUEST_CHANGES/COMMENT]"
191
+ \`\`\`
192
+
193
+ Or to approve/request changes formally:
194
+ \`\`\`bash
195
+ gh pr review ${prNumber} --approve --body "LGTM! Code looks good."
196
+ gh pr review ${prNumber} --request-changes --body "Please address the issues above."
197
+ \`\`\`
198
+
199
+ ## PR Diff
200
+ ${diffContent ? `\n\`\`\`diff\n${diffContent.slice(0, 50000)}\n\`\`\`\n` : 'Run `gh pr diff ' + prNumber + '` to see the changes.'}
201
+
202
+ Start by examining the diff and the changed files, then provide your review.`;
203
+ // Write prompt to a file
204
+ const promptFile = path.join(worktreePath, '.claude-review-prompt.txt');
205
+ fs.writeFileSync(promptFile, prompt);
206
+ // Create runner script for review
207
+ const runnerScript = path.join(worktreePath, '.claude-review-runner.sh');
208
+ const runnerContent = `#!/bin/bash
209
+ cd "${worktreePath}"
210
+
211
+ # Set terminal title
212
+ echo -ne "\\033]0;Review PR #${prNumber}: ${issue.title.replace(/"/g, '\\"').slice(0, 50)}\\007"
213
+
214
+ echo "๐Ÿ” Claude Code Review - PR #${prNumber}"
215
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
216
+ echo ""
217
+ echo "Issue #${issueNumber}: ${issue.title.replace(/"/g, '\\"')}"
218
+ echo ""
219
+ echo "Claude will review the PR and post suggestions."
220
+ echo "You can commit suggestions directly on GitHub."
221
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
222
+ echo ""
223
+
224
+ # Run Claude interactively
225
+ claude --dangerously-skip-permissions "$(cat '${promptFile}')"
226
+
227
+ # Clean up prompt file
228
+ rm -f '${promptFile}'
229
+
230
+ echo ""
231
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
232
+ echo "Review session ended."
233
+ echo ""
234
+ echo "View PR: gh pr view ${prNumber} --web"
235
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
236
+ echo ""
237
+
238
+ # Keep terminal open
239
+ exec bash
240
+ `;
241
+ fs.writeFileSync(runnerScript, runnerContent, { mode: 0o755 });
242
+ console.log();
243
+ console.log(chalk_1.default.cyan('๐Ÿ” Opening new terminal for code review...'));
244
+ console.log();
245
+ (0, helpers_1.openInNewTerminal)(`'${runnerScript}'`);
246
+ console.log(chalk_1.default.green(`โœ… Review session started for PR #${prNumber}`));
247
+ console.log(chalk_1.default.dim(` Claude is reviewing in a new terminal window.`));
248
+ console.log();
249
+ console.log(chalk_1.default.dim(` View PR: gh pr view ${prNumber} --web`));
250
+ console.log(chalk_1.default.dim(` To clean up later: claude-issue clean ${issueNumber}`));
251
+ }
252
+ function getRepoName(projectRoot) {
253
+ try {
254
+ const output = (0, child_process_1.execSync)('gh repo view --json nameWithOwner --jq .nameWithOwner', {
255
+ cwd: projectRoot,
256
+ encoding: 'utf-8',
257
+ stdio: ['pipe', 'pipe', 'pipe'],
258
+ });
259
+ return output.trim();
260
+ }
261
+ catch {
262
+ return '';
263
+ }
264
+ }
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,16 @@ 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 a PR with Claude and post suggestions')
158
+ .action(async (issue) => {
159
+ const issueNumber = parseInt(issue, 10);
160
+ if (isNaN(issueNumber)) {
161
+ console.log(chalk_1.default.red(`โŒ Invalid issue number: ${issue}`));
162
+ process.exit(1);
163
+ }
164
+ await (0, review_1.reviewCommand)(issueNumber);
165
+ });
153
166
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {