claude-issue-solver 1.31.0 → 1.33.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.
@@ -1,2 +1,6 @@
1
- export declare function reviewCommand(issueNumber: number): Promise<void>;
2
- export declare function selectReviewCommand(): Promise<void>;
1
+ export declare function reviewCommand(issueNumber: number, options?: {
2
+ merge?: boolean;
3
+ }): Promise<void>;
4
+ export declare function selectReviewCommand(options?: {
5
+ merge?: boolean;
6
+ }): Promise<void>;
@@ -43,12 +43,13 @@ const ora_1 = __importDefault(require("ora"));
43
43
  const inquirer_1 = __importDefault(require("inquirer"));
44
44
  const fs = __importStar(require("fs"));
45
45
  const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
46
47
  const child_process_1 = require("child_process");
47
48
  const github_1 = require("../utils/github");
48
49
  const git_1 = require("../utils/git");
49
50
  const helpers_1 = require("../utils/helpers");
50
51
  const config_1 = require("./config");
51
- async function reviewCommand(issueNumber) {
52
+ async function reviewCommand(issueNumber, options = {}) {
52
53
  const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
53
54
  const issue = (0, github_1.getIssue)(issueNumber);
54
55
  if (!issue) {
@@ -57,22 +58,21 @@ async function reviewCommand(issueNumber) {
57
58
  }
58
59
  spinner.succeed(`Found issue #${issueNumber}`);
59
60
  const projectRoot = (0, git_1.getProjectRoot)();
60
- const projectName = (0, git_1.getProjectName)();
61
- const baseBranch = (0, git_1.getDefaultBranch)();
62
- const branchSlug = (0, helpers_1.slugify)(issue.title);
63
- const branchName = `issue-${issueNumber}-${branchSlug}`;
64
- const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
65
- // Check if there's a PR for this issue
61
+ // Check if there's a PR for this issue - search by issue number in all PRs
66
62
  const prCheckSpinner = (0, ora_1.default)('Checking for PR...').start();
67
63
  let prNumber = null;
64
+ let branchName = null;
68
65
  try {
69
- const prOutput = (0, child_process_1.execSync)(`gh pr list --head "${branchName}" --json number --jq '.[0].number'`, {
66
+ // Search for PRs that mention the issue number in their branch name
67
+ const prOutput = (0, child_process_1.execSync)(`gh pr list --state open --json number,headRefName --jq '.[] | select(.headRefName | test("issue-${issueNumber}-")) | "\\(.number) \\(.headRefName)"'`, {
70
68
  cwd: projectRoot,
71
69
  encoding: 'utf-8',
72
70
  stdio: ['pipe', 'pipe', 'pipe'],
73
71
  }).trim();
74
72
  if (prOutput) {
75
- prNumber = prOutput;
73
+ const [num, branch] = prOutput.split(' ');
74
+ prNumber = num;
75
+ branchName = branch;
76
76
  prCheckSpinner.succeed(`Found PR #${prNumber}`);
77
77
  }
78
78
  else {
@@ -90,55 +90,6 @@ async function reviewCommand(issueNumber) {
90
90
  console.log(chalk_1.default.bold(`šŸ“Œ Reviewing: ${issue.title}`));
91
91
  console.log(chalk_1.default.dim(`šŸ”— PR: https://github.com/${getRepoName(projectRoot)}/pull/${prNumber}`));
92
92
  console.log();
93
- // Fetch latest
94
- const fetchSpinner = (0, ora_1.default)(`Fetching latest changes...`).start();
95
- try {
96
- (0, child_process_1.execSync)(`git fetch origin ${branchName} --quiet`, { cwd: projectRoot, stdio: 'pipe' });
97
- fetchSpinner.succeed('Fetched latest changes');
98
- }
99
- catch {
100
- fetchSpinner.warn('Could not fetch branch');
101
- }
102
- // Check if worktree already exists
103
- if (fs.existsSync(worktreePath)) {
104
- console.log(chalk_1.default.yellow(`\n🌿 Using existing worktree at: ${worktreePath}`));
105
- // Pull latest changes
106
- try {
107
- (0, child_process_1.execSync)('git pull --quiet', { cwd: worktreePath, stdio: 'pipe' });
108
- }
109
- catch {
110
- // Ignore pull errors
111
- }
112
- }
113
- else {
114
- const worktreeSpinner = (0, ora_1.default)(`Creating worktree for review...`).start();
115
- try {
116
- if ((0, git_1.branchExists)(branchName)) {
117
- (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
118
- cwd: projectRoot,
119
- stdio: 'pipe',
120
- });
121
- }
122
- else {
123
- // Branch should exist if PR exists, but handle edge case
124
- (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "origin/${branchName}"`, {
125
- cwd: projectRoot,
126
- stdio: 'pipe',
127
- });
128
- }
129
- worktreeSpinner.succeed(`Created worktree at: ${worktreePath}`);
130
- }
131
- catch (error) {
132
- worktreeSpinner.fail('Failed to create worktree');
133
- console.error(error);
134
- process.exit(1);
135
- }
136
- // Copy env files and symlink node_modules
137
- const setupSpinner = (0, ora_1.default)('Setting up worktree...').start();
138
- (0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
139
- (0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
140
- setupSpinner.succeed('Worktree setup complete');
141
- }
142
93
  // Get the diff for context
143
94
  let diffContent = '';
144
95
  try {
@@ -247,8 +198,9 @@ The \`suggestion\` code block creates a "Commit suggestion" button on GitHub.
247
198
  ${diffContent ? `\n\`\`\`diff\n${diffContent.slice(0, 50000)}\n\`\`\`\n` : 'Run `gh pr diff ' + prNumber + '` to see the changes.'}
248
199
 
249
200
  Start by examining the diff and the changed files, then provide your review.`;
250
- // Write prompt to a file
251
- const promptFile = path.join(worktreePath, '.claude-review-prompt.txt');
201
+ // Write prompt and runner script to temp directory
202
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cis-review-'));
203
+ const promptFile = path.join(tempDir, '.claude-review-prompt.txt');
252
204
  fs.writeFileSync(promptFile, prompt);
253
205
  // Bot token already fetched above
254
206
  const botTokenEnv = botToken ? `export BOT_TOKEN="${botToken}"\nexport GH_TOKEN="${botToken}"` : '# No bot token configured';
@@ -256,9 +208,9 @@ Start by examining the diff and the changed files, then provide your review.`;
256
208
  ? 'Using bot token for reviews (can approve/request changes)'
257
209
  : 'No bot token - using your account (may have limitations on own PRs)';
258
210
  // Create runner script for review
259
- const runnerScript = path.join(worktreePath, '.claude-review-runner.sh');
211
+ const runnerScript = path.join(tempDir, '.claude-review-runner.sh');
260
212
  const runnerContent = `#!/bin/bash
261
- cd "${worktreePath}"
213
+ cd "${projectRoot}"
262
214
 
263
215
  # Set bot token if configured
264
216
  ${botTokenEnv}
@@ -275,14 +227,15 @@ echo "${botNote}"
275
227
  echo ""
276
228
  echo "Claude will review the PR and post suggestions."
277
229
  echo "You can commit suggestions directly on GitHub."
230
+ ${options.merge ? 'echo "\\nšŸ”„ Auto-merge enabled: will merge if approved."' : ''}
278
231
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
279
232
  echo ""
280
233
 
281
234
  # Run Claude interactively
282
235
  claude --dangerously-skip-permissions "$(cat '${promptFile}')"
283
236
 
284
- # Clean up prompt file
285
- rm -f '${promptFile}'
237
+ # Clean up temp files
238
+ rm -rf '${tempDir}'
286
239
 
287
240
  echo ""
288
241
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -295,30 +248,15 @@ REVIEW_STATUS=$(gh pr view ${prNumber} --json reviewDecision --jq '.reviewDecisi
295
248
  if [ "$REVIEW_STATUS" = "APPROVED" ]; then
296
249
  echo "āœ… PR #${prNumber} is approved!"
297
250
  echo ""
298
- read -p "Would you like to merge and clean up? [y/N] " -n 1 -r
299
- echo ""
300
- if [[ \\$REPLY =~ ^[Yy]$ ]]; then
251
+ ${options.merge ? ` echo "šŸ“¤ Auto-merging PR #${prNumber}..."
252
+ if gh pr merge ${prNumber} --squash --delete-branch; then
301
253
  echo ""
302
- echo "šŸ“¤ Merging PR #${prNumber}..."
303
- if gh pr merge ${prNumber} --squash --delete-branch; then
304
- echo ""
305
- echo "āœ… PR merged successfully!"
306
- echo ""
307
- echo "Cleaning up worktree..."
308
- cd "${projectRoot}"
309
- git worktree remove "${worktreePath}" --force 2>/dev/null || rm -rf "${worktreePath}"
310
- git worktree prune 2>/dev/null
311
- git branch -D "${branchName}" 2>/dev/null
312
- echo "āœ… Cleanup complete!"
313
- else
314
- echo ""
315
- echo "āš ļø Merge failed. You can try manually: gh pr merge ${prNumber} --squash"
316
- fi
254
+ echo "āœ… PR merged successfully!"
317
255
  else
318
256
  echo ""
319
- echo "View PR: gh pr view ${prNumber} --web"
320
- echo "To merge later: cis merge"
321
- fi
257
+ echo "āš ļø Merge failed. You can try manually: gh pr merge ${prNumber} --squash"
258
+ fi` : ` echo "View PR: gh pr view ${prNumber} --web"
259
+ echo "To merge: cis merge"`}
322
260
  else
323
261
  echo "View PR: gh pr view ${prNumber} --web"
324
262
  fi
@@ -337,7 +275,6 @@ exec bash
337
275
  console.log(chalk_1.default.dim(` Claude is reviewing in a new terminal window.`));
338
276
  console.log();
339
277
  console.log(chalk_1.default.dim(` View PR: gh pr view ${prNumber} --web`));
340
- console.log(chalk_1.default.dim(` To clean up later: claude-issue clean ${issueNumber}`));
341
278
  }
342
279
  function getRepoName(projectRoot) {
343
280
  try {
@@ -374,7 +311,7 @@ function getOpenPRs(projectRoot) {
374
311
  return [];
375
312
  }
376
313
  }
377
- async function selectReviewCommand() {
314
+ async function selectReviewCommand(options = {}) {
378
315
  const projectRoot = (0, git_1.getProjectRoot)();
379
316
  const projectName = (0, git_1.getProjectName)();
380
317
  console.log(chalk_1.default.bold(`\nOpen PRs for ${projectName}:\n`));
@@ -425,56 +362,13 @@ async function selectReviewCommand() {
425
362
  console.log();
426
363
  // Launch reviews in parallel
427
364
  for (const pr of selected) {
428
- await launchReviewForPR(pr, projectRoot, projectName);
365
+ await launchReviewForPR(pr, projectRoot, projectName, options);
429
366
  }
430
367
  console.log();
431
368
  console.log(chalk_1.default.green(`āœ… Started ${selected.length} review session(s)!`));
432
369
  console.log(chalk_1.default.dim(' Each review is running in its own terminal window.'));
433
370
  }
434
- async function launchReviewForPR(pr, projectRoot, projectName) {
435
- const baseBranch = (0, git_1.getDefaultBranch)();
436
- const branchName = pr.headRefName;
437
- const worktreePath = path.join(path.dirname(projectRoot), `${projectName}-${branchName}`);
438
- // Fetch latest
439
- try {
440
- (0, child_process_1.execSync)(`git fetch origin ${branchName} --quiet`, { cwd: projectRoot, stdio: 'pipe' });
441
- }
442
- catch {
443
- // Ignore fetch errors
444
- }
445
- // Check if worktree already exists
446
- if (!fs.existsSync(worktreePath)) {
447
- try {
448
- if ((0, git_1.branchExists)(branchName)) {
449
- (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "${branchName}"`, {
450
- cwd: projectRoot,
451
- stdio: 'pipe',
452
- });
453
- }
454
- else {
455
- (0, child_process_1.execSync)(`git worktree add "${worktreePath}" "origin/${branchName}"`, {
456
- cwd: projectRoot,
457
- stdio: 'pipe',
458
- });
459
- }
460
- // Copy env files and symlink node_modules
461
- (0, helpers_1.copyEnvFiles)(projectRoot, worktreePath);
462
- (0, helpers_1.symlinkNodeModules)(projectRoot, worktreePath);
463
- }
464
- catch (error) {
465
- console.log(chalk_1.default.yellow(`āš ļø Could not create worktree for PR #${pr.number}`));
466
- return;
467
- }
468
- }
469
- else {
470
- // Pull latest changes
471
- try {
472
- (0, child_process_1.execSync)('git pull --quiet', { cwd: worktreePath, stdio: 'pipe' });
473
- }
474
- catch {
475
- // Ignore pull errors
476
- }
477
- }
371
+ async function launchReviewForPR(pr, projectRoot, _projectName, options = {}) {
478
372
  // Get the diff for context
479
373
  let diffContent = '';
480
374
  try {
@@ -587,18 +481,19 @@ The \`suggestion\` code block creates a "Commit suggestion" button on GitHub.
587
481
  ${diffContent ? `\n\`\`\`diff\n${diffContent.slice(0, 50000)}\n\`\`\`\n` : 'Run `gh pr diff ' + pr.number + '` to see the changes.'}
588
482
 
589
483
  Start by examining the diff and the changed files, then provide your review.`;
590
- // Write prompt to a file
591
- const promptFile = path.join(worktreePath, '.claude-review-prompt.txt');
484
+ // Write prompt and runner script to temp directory
485
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cis-review-'));
486
+ const promptFile = path.join(tempDir, '.claude-review-prompt.txt');
592
487
  fs.writeFileSync(promptFile, prompt);
593
488
  const botTokenEnv = botToken ? `export BOT_TOKEN="${botToken}"\nexport GH_TOKEN="${botToken}"` : '# No bot token configured';
594
489
  const botNote = botToken
595
490
  ? 'Using bot token for reviews (can approve/request changes)'
596
491
  : 'No bot token - using your account (may have limitations on own PRs)';
597
492
  // Create runner script for review
598
- const runnerScript = path.join(worktreePath, '.claude-review-runner.sh');
493
+ const runnerScript = path.join(tempDir, '.claude-review-runner.sh');
599
494
  const escapedTitle = pr.title.replace(/"/g, '\\"').slice(0, 50);
600
495
  const runnerContent = `#!/bin/bash
601
- cd "${worktreePath}"
496
+ cd "${projectRoot}"
602
497
 
603
498
  # Set bot token if configured
604
499
  ${botTokenEnv}
@@ -615,14 +510,15 @@ echo "${botNote}"
615
510
  echo ""
616
511
  echo "Claude will review the PR and post suggestions."
617
512
  echo "You can commit suggestions directly on GitHub."
513
+ ${options.merge ? 'echo "\\nšŸ”„ Auto-merge enabled: will merge if approved."' : ''}
618
514
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
619
515
  echo ""
620
516
 
621
517
  # Run Claude interactively
622
518
  claude --dangerously-skip-permissions "$(cat '${promptFile}')"
623
519
 
624
- # Clean up prompt file
625
- rm -f '${promptFile}'
520
+ # Clean up temp files
521
+ rm -rf '${tempDir}'
626
522
 
627
523
  echo ""
628
524
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -635,30 +531,15 @@ REVIEW_STATUS=$(gh pr view ${pr.number} --json reviewDecision --jq '.reviewDecis
635
531
  if [ "$REVIEW_STATUS" = "APPROVED" ]; then
636
532
  echo "āœ… PR #${pr.number} is approved!"
637
533
  echo ""
638
- read -p "Would you like to merge and clean up? [y/N] " -n 1 -r
639
- echo ""
640
- if [[ \\$REPLY =~ ^[Yy]$ ]]; then
534
+ ${options.merge ? ` echo "šŸ“¤ Auto-merging PR #${pr.number}..."
535
+ if gh pr merge ${pr.number} --squash --delete-branch; then
641
536
  echo ""
642
- echo "šŸ“¤ Merging PR #${pr.number}..."
643
- if gh pr merge ${pr.number} --squash --delete-branch; then
644
- echo ""
645
- echo "āœ… PR merged successfully!"
646
- echo ""
647
- echo "Cleaning up worktree..."
648
- cd "${projectRoot}"
649
- git worktree remove "${worktreePath}" --force 2>/dev/null || rm -rf "${worktreePath}"
650
- git worktree prune 2>/dev/null
651
- git branch -D "${branchName}" 2>/dev/null
652
- echo "āœ… Cleanup complete!"
653
- else
654
- echo ""
655
- echo "āš ļø Merge failed. You can try manually: gh pr merge ${pr.number} --squash"
656
- fi
537
+ echo "āœ… PR merged successfully!"
657
538
  else
658
539
  echo ""
659
- echo "View PR: gh pr view ${pr.number} --web"
660
- echo "To merge later: cis merge"
661
- fi
540
+ echo "āš ļø Merge failed. You can try manually: gh pr merge ${pr.number} --squash"
541
+ fi` : ` echo "View PR: gh pr view ${pr.number} --web"
542
+ echo "To merge: cis merge"`}
662
543
  else
663
544
  echo "View PR: gh pr view ${pr.number} --web"
664
545
  fi
package/dist/index.js CHANGED
@@ -157,17 +157,18 @@ program
157
157
  program
158
158
  .command('review [issue]')
159
159
  .description('Review PRs with Claude and post suggestions')
160
- .action(async (issue) => {
160
+ .option('-m, --merge', 'Automatically merge PR if approved')
161
+ .action(async (issue, options) => {
161
162
  if (issue) {
162
163
  const issueNumber = parseInt(issue, 10);
163
164
  if (isNaN(issueNumber)) {
164
165
  console.log(chalk_1.default.red(`āŒ Invalid issue number: ${issue}`));
165
166
  process.exit(1);
166
167
  }
167
- await (0, review_1.reviewCommand)(issueNumber);
168
+ await (0, review_1.reviewCommand)(issueNumber, { merge: options.merge });
168
169
  }
169
170
  else {
170
- await (0, review_1.selectReviewCommand)();
171
+ await (0, review_1.selectReviewCommand)({ merge: options.merge });
171
172
  }
172
173
  });
173
174
  // Config command - manage settings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.31.0",
3
+ "version": "1.33.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {