claude-issue-solver 1.27.1 → 1.28.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
@@ -112,6 +112,10 @@ cis
112
112
  # Solve a specific issue directly
113
113
  claude-issue 42
114
114
 
115
+ # Auto mode: solve → review → fix until approved (max 3 iterations)
116
+ claude-issue --auto # Interactive selection with auto mode
117
+ claude-issue 42 --auto # Solve specific issue with auto mode
118
+
115
119
  # List open issues
116
120
  claude-issue list
117
121
  claude-issue ls
@@ -1 +1,3 @@
1
- export declare function selectCommand(): Promise<void>;
1
+ export declare function selectCommand(options?: {
2
+ auto?: boolean;
3
+ }): Promise<void>;
@@ -9,7 +9,7 @@ const inquirer_1 = __importDefault(require("inquirer"));
9
9
  const github_1 = require("../utils/github");
10
10
  const git_1 = require("../utils/git");
11
11
  const solve_1 = require("./solve");
12
- async function selectCommand() {
12
+ async function selectCommand(options = {}) {
13
13
  const projectName = (0, git_1.getProjectName)();
14
14
  console.log(chalk_1.default.bold(`\nOpen issues for ${projectName}:\n`));
15
15
  const issues = (0, github_1.listIssues)();
@@ -42,9 +42,9 @@ async function selectCommand() {
42
42
  console.log(chalk_1.default.dim('No issues selected.'));
43
43
  return;
44
44
  }
45
- console.log(chalk_1.default.cyan(`\nStarting ${issueNumbers.length} issue(s)...\n`));
45
+ console.log(chalk_1.default.cyan(`\nStarting ${issueNumbers.length} issue(s)${options.auto ? ' in auto mode' : ''}...\n`));
46
46
  for (const issueNumber of issueNumbers) {
47
- await (0, solve_1.solveCommand)(issueNumber);
47
+ await (0, solve_1.solveCommand)(issueNumber, { auto: options.auto });
48
48
  console.log();
49
49
  }
50
50
  }
@@ -1 +1,3 @@
1
- export declare function solveCommand(issueNumber: number): Promise<void>;
1
+ export declare function solveCommand(issueNumber: number, options?: {
2
+ auto?: boolean;
3
+ }): Promise<void>;
@@ -45,7 +45,8 @@ const child_process_1 = require("child_process");
45
45
  const github_1 = require("../utils/github");
46
46
  const git_1 = require("../utils/git");
47
47
  const helpers_1 = require("../utils/helpers");
48
- async function solveCommand(issueNumber) {
48
+ const config_1 = require("./config");
49
+ async function solveCommand(issueNumber, options = {}) {
49
50
  const spinner = (0, ora_1.default)(`Fetching issue #${issueNumber}...`).start();
50
51
  const issue = (0, github_1.getIssue)(issueNumber);
51
52
  if (!issue) {
@@ -122,6 +123,9 @@ Instructions:
122
123
  // Write prompt to a file to avoid shell escaping issues with backticks, <>, etc.
123
124
  const promptFile = path.join(worktreePath, '.claude-prompt.txt');
124
125
  fs.writeFileSync(promptFile, prompt);
126
+ // Get bot token for auto mode
127
+ const botToken = (0, config_1.getBotToken)();
128
+ const autoMode = options.auto || false;
125
129
  // Create runner script
126
130
  const runnerScript = path.join(worktreePath, '.claude-runner.sh');
127
131
  const runnerContent = `#!/bin/bash
@@ -133,11 +137,16 @@ echo -ne "\\033]0;Issue #${issueNumber}: ${issue.title.replace(/"/g, '\\"').slic
133
137
  echo "🤖 Claude Code - Issue #${issueNumber}: ${issue.title}"
134
138
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
135
139
  echo ""
136
- echo "When Claude commits, a PR will be created automatically."
140
+ ${autoMode ? 'echo "🔄 AUTO MODE: Will solve → review → fix until approved (max 3 iterations)"' : 'echo "When Claude commits, a PR will be created automatically."'}
137
141
  echo "The terminal stays open for follow-up changes."
138
142
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
139
143
  echo ""
140
144
 
145
+ ${botToken ? `# Bot token for reviews
146
+ export BOT_TOKEN="${botToken}"
147
+ export GH_TOKEN="${botToken}"
148
+ ` : ''}
149
+
141
150
  # Function to create PR
142
151
  create_pr() {
143
152
  COMMITS=$(git log origin/${baseBranch}..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ')
@@ -182,6 +191,7 @@ $COMMIT_LIST
182
191
  # Update terminal title with PR info
183
192
  PR_NUM=$(echo "$PR_URL" | grep -oE '[0-9]+$')
184
193
  echo -ne "\\033]0;Issue #${issueNumber} → PR #\$PR_NUM\\007"
194
+ echo "$PR_NUM"
185
195
  fi
186
196
  else
187
197
  # PR exists, just push new commits
@@ -191,16 +201,27 @@ $COMMIT_LIST
191
201
  echo "📤 Pushed new commits to PR #$EXISTING_PR"
192
202
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
193
203
  echo ""
204
+ echo "$EXISTING_PR"
194
205
  fi
195
206
  fi
196
207
  }
197
208
 
209
+ # Function to get PR number
210
+ get_pr_number() {
211
+ gh pr list --head "${branchName}" --json number --jq '.[0].number' 2>/dev/null
212
+ }
213
+
214
+ # Function to get PR review status
215
+ get_review_status() {
216
+ ${botToken ? 'GH_TOKEN="${BOT_TOKEN}"' : ''} gh pr view "$1" --json reviewDecision --jq '.reviewDecision' 2>/dev/null
217
+ }
218
+
198
219
  # Watch for new commits in background and create PR
199
220
  LAST_COMMIT=""
200
221
  while true; do
201
222
  CURRENT_COMMIT=$(git rev-parse HEAD 2>/dev/null)
202
223
  if [ "$CURRENT_COMMIT" != "$LAST_COMMIT" ] && [ -n "$LAST_COMMIT" ]; then
203
- create_pr
224
+ create_pr > /dev/null
204
225
  fi
205
226
  LAST_COMMIT="$CURRENT_COMMIT"
206
227
  sleep 2
@@ -217,7 +238,135 @@ rm -f '${promptFile}'
217
238
  kill $WATCHER_PID 2>/dev/null
218
239
 
219
240
  # Final PR check after Claude exits
220
- create_pr
241
+ create_pr > /dev/null
242
+
243
+ ${autoMode ? `
244
+ # AUTO MODE: Review loop
245
+ MAX_ITERATIONS=3
246
+ ITERATION=0
247
+
248
+ while [ $ITERATION -lt $MAX_ITERATIONS ]; do
249
+ ITERATION=$((ITERATION + 1))
250
+
251
+ # Wait for PR to be created
252
+ sleep 2
253
+ PR_NUM=$(get_pr_number)
254
+
255
+ if [ -z "$PR_NUM" ]; then
256
+ echo ""
257
+ echo "⚠️ No PR found, skipping auto-review"
258
+ break
259
+ fi
260
+
261
+ echo ""
262
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
263
+ echo "🔍 AUTO-REVIEW: Iteration $ITERATION of $MAX_ITERATIONS"
264
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
265
+ echo ""
266
+
267
+ # Get PR diff for review
268
+ PR_DIFF=$(gh pr diff $PR_NUM 2>/dev/null | head -500)
269
+
270
+ # Create review prompt
271
+ REVIEW_PROMPT="You are reviewing PR #$PR_NUM for issue #${issueNumber}: ${issue.title.replace(/"/g, '\\"')}
272
+
273
+ ## Issue Description
274
+ ${issue.body.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/\`/g, '\\`')}
275
+
276
+ ## Your Task
277
+ Review the code changes in this PR. Look for:
278
+ 1. Bugs and logic errors
279
+ 2. Security vulnerabilities
280
+ 3. Missing error handling
281
+ 4. Code quality issues
282
+ 5. Performance problems
283
+
284
+ ## How to Leave Feedback
285
+ ${botToken ? `Use GH_TOKEN=\\$BOT_TOKEN prefix for all gh commands.
286
+
287
+ If the code looks good:
288
+ \\\`\\\`\\\`bash
289
+ GH_TOKEN=\\$BOT_TOKEN gh pr review $PR_NUM --approve --body \\"LGTM! Code looks good.\\"
290
+ \\\`\\\`\\\`
291
+
292
+ If changes are needed:
293
+ \\\`\\\`\\\`bash
294
+ GH_TOKEN=\\$BOT_TOKEN gh pr review $PR_NUM --request-changes --body \\"<your feedback>\\"
295
+ \\\`\\\`\\\`
296
+ ` : `If the code looks good:
297
+ \\\`\\\`\\\`bash
298
+ gh pr review $PR_NUM --approve --body \\"LGTM! Code looks good.\\"
299
+ \\\`\\\`\\\`
300
+
301
+ If changes are needed:
302
+ \\\`\\\`\\\`bash
303
+ gh pr review $PR_NUM --request-changes --body \\"<your feedback>\\"
304
+ \\\`\\\`\\\`
305
+ `}
306
+
307
+ ## PR Diff (first 500 lines)
308
+ \\\`\\\`\\\`diff
309
+ $PR_DIFF
310
+ \\\`\\\`\\\`
311
+
312
+ Review the code and either approve or request changes."
313
+
314
+ # Run Claude for review
315
+ claude --dangerously-skip-permissions "$REVIEW_PROMPT"
316
+
317
+ # Check review status
318
+ sleep 2
319
+ REVIEW_STATUS=$(get_review_status $PR_NUM)
320
+
321
+ echo ""
322
+ echo "📊 Review status: $REVIEW_STATUS"
323
+
324
+ if [ "$REVIEW_STATUS" = "APPROVED" ]; then
325
+ echo ""
326
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
327
+ echo "✅ PR APPROVED! Ready to merge."
328
+ echo " Run: cis merge"
329
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
330
+ break
331
+ elif [ "$REVIEW_STATUS" = "CHANGES_REQUESTED" ]; then
332
+ if [ $ITERATION -lt $MAX_ITERATIONS ]; then
333
+ echo ""
334
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
335
+ echo "🔧 Changes requested. Claude will fix them..."
336
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
337
+ echo ""
338
+
339
+ # Get the review comments
340
+ REVIEW_COMMENTS=$(${botToken ? 'GH_TOKEN="${BOT_TOKEN}"' : ''} gh pr view $PR_NUM --json reviews --jq '.reviews[-1].body' 2>/dev/null)
341
+
342
+ FIX_PROMPT="The code review requested changes. Please fix them:
343
+
344
+ ## Review Feedback
345
+ $REVIEW_COMMENTS
346
+
347
+ Please address the feedback above, make the necessary changes, and commit them."
348
+
349
+ # Run Claude to fix
350
+ claude --dangerously-skip-permissions "$FIX_PROMPT"
351
+
352
+ # Push changes
353
+ git push origin "${branchName}" 2>/dev/null
354
+ sleep 2
355
+ else
356
+ echo ""
357
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
358
+ echo "⚠️ Max iterations reached. Manual review needed."
359
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
360
+ fi
361
+ else
362
+ echo ""
363
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
364
+ echo "ℹ️ Review status: $REVIEW_STATUS"
365
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
366
+ break
367
+ fi
368
+ done
369
+ ` : ''}
221
370
 
222
371
  echo ""
223
372
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
package/dist/index.js CHANGED
@@ -52,17 +52,18 @@ program.hook('preAction', (thisCommand) => {
52
52
  // Default command - interactive selection
53
53
  program
54
54
  .argument('[issue]', 'Issue number to solve')
55
- .action(async (issue) => {
55
+ .option('--auto', 'Auto mode: solve → review → fix until approved (max 3 iterations)')
56
+ .action(async (issue, options) => {
56
57
  if (issue) {
57
58
  const issueNumber = parseInt(issue, 10);
58
59
  if (isNaN(issueNumber)) {
59
60
  console.log(chalk_1.default.red(`❌ Invalid issue number: ${issue}`));
60
61
  process.exit(1);
61
62
  }
62
- await (0, solve_1.solveCommand)(issueNumber);
63
+ await (0, solve_1.solveCommand)(issueNumber, { auto: options.auto });
63
64
  }
64
65
  else {
65
- await (0, select_1.selectCommand)();
66
+ await (0, select_1.selectCommand)({ auto: options.auto });
66
67
  }
67
68
  });
68
69
  // List command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-issue-solver",
3
- "version": "1.27.1",
3
+ "version": "1.28.0",
4
4
  "description": "Automatically solve GitHub issues using Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "bin": {