claude-issue-solver 1.22.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 CHANGED
@@ -128,8 +128,9 @@ claude-issue new "Fix crash" -l bug -l priority
128
128
  # Create PR for a solved issue (if you skipped it earlier)
129
129
  claude-issue pr 42
130
130
 
131
- # Review a PR with AI (posts suggestions you can commit on GitHub)
132
- claude-issue review 42
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
133
134
 
134
135
  # Clean up worktree and branch
135
136
  claude-issue clean 42 # Clean specific issue
@@ -155,7 +156,7 @@ claude-issue --help
155
156
  | `claude-issue list` | `ls` | List open issues |
156
157
  | `claude-issue show <number>` | - | Show full issue details |
157
158
  | `claude-issue pr <number>` | - | Create PR for solved issue |
158
- | `claude-issue review <number>` | - | Review PR with AI suggestions |
159
+ | `claude-issue review [number]` | - | Review PRs with AI suggestions |
159
160
  | `claude-issue clean [number]` | `rm` | Remove worktree and branch |
160
161
  | `claude-issue go [number]` | - | Navigate to worktree |
161
162
  | `claude-issue init` | - | Setup wizard for requirements |
@@ -1 +1,2 @@
1
1
  export declare function reviewCommand(issueNumber: number): Promise<void>;
2
+ export declare function selectReviewCommand(): Promise<void>;
@@ -37,8 +37,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.reviewCommand = reviewCommand;
40
+ exports.selectReviewCommand = selectReviewCommand;
40
41
  const chalk_1 = __importDefault(require("chalk"));
41
42
  const ora_1 = __importDefault(require("ora"));
43
+ const inquirer_1 = __importDefault(require("inquirer"));
42
44
  const fs = __importStar(require("fs"));
43
45
  const path = __importStar(require("path"));
44
46
  const child_process_1 = require("child_process");
@@ -262,3 +264,223 @@ function getRepoName(projectRoot) {
262
264
  return '';
263
265
  }
264
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
@@ -153,14 +153,19 @@ program
153
153
  });
154
154
  // Review command - AI code review for PRs
155
155
  program
156
- .command('review <issue>')
157
- .description('Review a PR with Claude and post suggestions')
156
+ .command('review [issue]')
157
+ .description('Review PRs with Claude and post suggestions')
158
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);
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)();
163
169
  }
164
- await (0, review_1.reviewCommand)(issueNumber);
165
170
  });
166
171
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {