@link-assistant/hive-mind 0.39.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +769 -0
  4. package/package.json +58 -0
  5. package/src/agent.lib.mjs +705 -0
  6. package/src/agent.prompts.lib.mjs +196 -0
  7. package/src/buildUserMention.lib.mjs +71 -0
  8. package/src/claude-limits.lib.mjs +389 -0
  9. package/src/claude.lib.mjs +1445 -0
  10. package/src/claude.prompts.lib.mjs +203 -0
  11. package/src/codex.lib.mjs +552 -0
  12. package/src/codex.prompts.lib.mjs +194 -0
  13. package/src/config.lib.mjs +207 -0
  14. package/src/contributing-guidelines.lib.mjs +268 -0
  15. package/src/exit-handler.lib.mjs +205 -0
  16. package/src/git.lib.mjs +145 -0
  17. package/src/github-issue-creator.lib.mjs +246 -0
  18. package/src/github-linking.lib.mjs +152 -0
  19. package/src/github.batch.lib.mjs +272 -0
  20. package/src/github.graphql.lib.mjs +258 -0
  21. package/src/github.lib.mjs +1479 -0
  22. package/src/hive.config.lib.mjs +254 -0
  23. package/src/hive.mjs +1500 -0
  24. package/src/instrument.mjs +191 -0
  25. package/src/interactive-mode.lib.mjs +1000 -0
  26. package/src/lenv-reader.lib.mjs +206 -0
  27. package/src/lib.mjs +490 -0
  28. package/src/lino.lib.mjs +176 -0
  29. package/src/local-ci-checks.lib.mjs +324 -0
  30. package/src/memory-check.mjs +419 -0
  31. package/src/model-mapping.lib.mjs +145 -0
  32. package/src/model-validation.lib.mjs +278 -0
  33. package/src/opencode.lib.mjs +479 -0
  34. package/src/opencode.prompts.lib.mjs +194 -0
  35. package/src/protect-branch.mjs +159 -0
  36. package/src/review.mjs +433 -0
  37. package/src/reviewers-hive.mjs +643 -0
  38. package/src/sentry.lib.mjs +284 -0
  39. package/src/solve.auto-continue.lib.mjs +568 -0
  40. package/src/solve.auto-pr.lib.mjs +1374 -0
  41. package/src/solve.branch-errors.lib.mjs +341 -0
  42. package/src/solve.branch.lib.mjs +230 -0
  43. package/src/solve.config.lib.mjs +342 -0
  44. package/src/solve.error-handlers.lib.mjs +256 -0
  45. package/src/solve.execution.lib.mjs +291 -0
  46. package/src/solve.feedback.lib.mjs +436 -0
  47. package/src/solve.mjs +1128 -0
  48. package/src/solve.preparation.lib.mjs +210 -0
  49. package/src/solve.repo-setup.lib.mjs +114 -0
  50. package/src/solve.repository.lib.mjs +961 -0
  51. package/src/solve.results.lib.mjs +558 -0
  52. package/src/solve.session.lib.mjs +135 -0
  53. package/src/solve.validation.lib.mjs +325 -0
  54. package/src/solve.watch.lib.mjs +572 -0
  55. package/src/start-screen.mjs +324 -0
  56. package/src/task.mjs +308 -0
  57. package/src/telegram-bot.mjs +1481 -0
  58. package/src/telegram-markdown.lib.mjs +64 -0
  59. package/src/usage-limit.lib.mjs +218 -0
  60. package/src/version.lib.mjs +41 -0
  61. package/src/youtrack/solve.youtrack.lib.mjs +116 -0
  62. package/src/youtrack/youtrack-sync.mjs +219 -0
  63. package/src/youtrack/youtrack.lib.mjs +425 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Branch Protection Script
4
+ *
5
+ * Enables branch protection rules on the default branch of a GitHub repository
6
+ * to require pull requests before merging.
7
+ *
8
+ * Usage:
9
+ * ./protect-branch.mjs <owner>/<repo>
10
+ * ./protect-branch.mjs <owner>/<repo> <branch-name>
11
+ *
12
+ * Examples:
13
+ * ./protect-branch.mjs konard/my-repo
14
+ * ./protect-branch.mjs konard/my-repo main
15
+ */
16
+
17
+ // Use use-m to dynamically import modules for cross-runtime compatibility
18
+ const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
19
+
20
+ // Use command-stream for consistent $ behavior across runtimes
21
+ const { $ } = await use('command-stream');
22
+
23
+ // Parse command line arguments
24
+ const args = process.argv.slice(2);
25
+
26
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
27
+ console.log('Branch Protection Tool');
28
+ console.log('Usage: ./protect-branch.mjs <owner>/<repo> [branch-name]');
29
+ console.log('');
30
+ console.log('Examples:');
31
+ console.log(' ./protect-branch.mjs konard/my-repo');
32
+ console.log(' ./protect-branch.mjs konard/my-repo main');
33
+ process.exit(0);
34
+ }
35
+
36
+ // Parse repository argument
37
+ const repoArg = args[0];
38
+ if (!repoArg.includes('/')) {
39
+ console.error('Error: Repository must be in format owner/repo');
40
+ process.exit(1);
41
+ }
42
+
43
+ const [owner, repo] = repoArg.split('/');
44
+ let branchName = args[1];
45
+
46
+ console.log('๐Ÿ”’ Branch Protection Tool');
47
+ console.log(`๐Ÿ“ฆ Repository: ${owner}/${repo}`);
48
+
49
+ try {
50
+ // If branch name not provided, get the default branch
51
+ if (!branchName) {
52
+ console.log('๐Ÿ” Detecting default branch...');
53
+ const defaultBranchResult = await $`gh api repos/${owner}/${repo} --jq .default_branch`;
54
+
55
+ if (defaultBranchResult.code !== 0) {
56
+ console.error('Error: Failed to get repository information');
57
+ console.error(defaultBranchResult.stderr ? defaultBranchResult.stderr.toString() : 'Unknown error');
58
+ process.exit(1);
59
+ }
60
+
61
+ branchName = defaultBranchResult.stdout.toString().trim();
62
+ console.log(`โœ… Default branch: ${branchName}`);
63
+ } else {
64
+ console.log(`๐ŸŽฏ Target branch: ${branchName}`);
65
+ }
66
+
67
+ // Check if branch exists
68
+ console.log('๐Ÿ” Verifying branch exists...');
69
+ const branchCheckResult = await $`gh api repos/${owner}/${repo}/branches/${branchName} --silent`;
70
+
71
+ if (branchCheckResult.code !== 0) {
72
+ console.error(`Error: Branch '${branchName}' not found in repository`);
73
+ process.exit(1);
74
+ }
75
+ console.log('โœ… Branch verified');
76
+
77
+ // Enable branch protection with PR requirement
78
+ console.log(`๐Ÿ” Enabling branch protection for '${branchName}'...`);
79
+
80
+ // Create the protection rules JSON
81
+ const protectionRules = {
82
+ required_status_checks: null,
83
+ enforce_admins: false,
84
+ required_pull_request_reviews: {
85
+ dismiss_stale_reviews: false,
86
+ require_code_owner_reviews: false,
87
+ required_approving_review_count: 0,
88
+ require_last_push_approval: false
89
+ },
90
+ restrictions: null,
91
+ allow_force_pushes: false,
92
+ allow_deletions: false,
93
+ block_creations: false,
94
+ required_conversation_resolution: false,
95
+ lock_branch: false,
96
+ allow_fork_syncing: false
97
+ };
98
+
99
+ // Apply branch protection using GitHub API
100
+ const protectResult = await $`gh api \
101
+ --method PUT \
102
+ repos/${owner}/${repo}/branches/${branchName}/protection \
103
+ --input - << 'EOF'
104
+ ${JSON.stringify(protectionRules, null, 2)}
105
+ EOF`;
106
+
107
+ if (protectResult.code !== 0) {
108
+ // Check if it's already protected
109
+ if (protectResult.stderr && protectResult.stderr.toString().includes('Branch protection is disabled')) {
110
+ console.warn('โš ๏ธ Branch protection might require admin permissions or a paid plan');
111
+ } else {
112
+ console.error('Error: Failed to enable branch protection');
113
+ console.error(protectResult.stderr ? protectResult.stderr.toString() : 'Unknown error');
114
+ }
115
+
116
+ // Try a simpler approach for public repos
117
+ console.log('๐Ÿ”„ Trying alternative method...');
118
+
119
+ // For public repos, we can at least try to update settings
120
+ const updateResult = await $`gh api \
121
+ --method PATCH \
122
+ repos/${owner}/${repo} \
123
+ --field allow_merge_commit=true \
124
+ --field allow_squash_merge=true \
125
+ --field allow_rebase_merge=true \
126
+ --field delete_branch_on_merge=false`;
127
+
128
+ if (updateResult.code === 0) {
129
+ console.log('โœ… Repository settings updated (PR workflow encouraged)');
130
+ }
131
+ } else {
132
+ console.log('โœ… Branch protection enabled successfully!');
133
+ }
134
+
135
+ // Verify the protection status
136
+ console.log('\n๐Ÿ“Š Verifying protection status...');
137
+ const statusResult = await $`gh api repos/${owner}/${repo}/branches/${branchName}/protection --silent 2>/dev/null || echo "not-protected"`;
138
+
139
+ if (statusResult.stdout.toString().trim() === 'not-protected') {
140
+ console.log('โš ๏ธ Branch protection not fully active (may require admin rights or paid plan)');
141
+ console.log('\n๐Ÿ’ก Alternative: Configure protection manually in repository settings:');
142
+ console.log(` https://github.com/${owner}/${repo}/settings/branches`);
143
+ } else {
144
+ console.log('โœ… Branch protection is active');
145
+ console.log('\n๐ŸŽฏ Protection rules applied:');
146
+ console.log(' โ€ข Pull requests required before merging');
147
+ console.log(' โ€ข Force pushes disabled');
148
+ console.log(' โ€ข Branch deletion disabled');
149
+ }
150
+
151
+ console.log('\nโœจ Done! The branch is configured to require pull requests.');
152
+
153
+ } catch (error) {
154
+ console.error('\nโŒ Error:', error.message);
155
+ if (error.stderr) {
156
+ console.error('Details:', error.stderr.toString());
157
+ }
158
+ process.exit(1);
159
+ }
package/src/review.mjs ADDED
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Early exit paths - handle these before loading all modules to speed up testing
4
+ const earlyArgs = process.argv.slice(2);
5
+
6
+ if (earlyArgs.includes('--version')) {
7
+ const { getVersion } = await import('./version.lib.mjs');
8
+ try {
9
+ const version = await getVersion();
10
+ console.log(version);
11
+ } catch {
12
+ console.error('Error: Unable to determine version');
13
+ process.exit(1);
14
+ }
15
+ process.exit(0);
16
+ }
17
+
18
+ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
19
+ // Show help and exit
20
+ console.log('Usage: review.mjs <pr-url> [options]');
21
+ console.log('\nOptions:');
22
+ console.log(' --version Show version number');
23
+ console.log(' --help, -h Show help');
24
+ console.log(' --resume, -r Resume from a previous session ID');
25
+ console.log(' --dry-run, -n Prepare everything but do not execute Claude');
26
+ console.log(' --model, -m Model to use (opus, sonnet, or full model ID) [default: opus]');
27
+ console.log(' --focus, -f Focus areas for review [default: all]');
28
+ console.log(' --approve If review passes, approve the PR');
29
+ console.log(' --verbose, -v Enable verbose logging');
30
+ process.exit(0);
31
+ }
32
+
33
+ // Use use-m to dynamically import modules for cross-runtime compatibility
34
+ const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
35
+
36
+ // Use command-stream for consistent $ behavior across runtimes
37
+ const { $ } = await use('command-stream');
38
+
39
+ const yargs = (await use('yargs@latest')).default;
40
+ const os = (await use('os')).default;
41
+ const path = (await use('path')).default;
42
+ const fs = (await use('fs')).promises;
43
+
44
+ // Import shared functions from lib.mjs to follow DRY principle
45
+ import { log, setLogFile, getLogFile, formatAligned } from './lib.mjs';
46
+ import { reportError } from './sentry.lib.mjs';
47
+ import * as memoryCheck from './memory-check.mjs';
48
+
49
+ // Import Claude execution functions
50
+ import { executeClaudeCommand } from './claude.lib.mjs';
51
+
52
+ // Configure command line arguments - GitHub PR URL as positional argument
53
+ // Use yargs().parse(args) instead of yargs(args).argv to ensure .strict() mode works
54
+ const argv = yargs()
55
+ .usage('Usage: $0 <pr-url> [options]')
56
+ .positional('pr-url', {
57
+ type: 'string',
58
+ description: 'The GitHub pull request URL to review'
59
+ })
60
+ .option('resume', {
61
+ type: 'string',
62
+ description: 'Resume from a previous session ID (when limit was reached)',
63
+ alias: 'r'
64
+ })
65
+ .option('dry-run', {
66
+ type: 'boolean',
67
+ description: 'Prepare everything but do not execute Claude',
68
+ alias: 'n'
69
+ })
70
+ .option('model', {
71
+ type: 'string',
72
+ description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
73
+ alias: 'm',
74
+ default: 'opus',
75
+ choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101']
76
+ })
77
+ .option('focus', {
78
+ type: 'string',
79
+ description: 'Focus areas for review (security, performance, logic, style, tests)',
80
+ alias: 'f',
81
+ default: 'all'
82
+ })
83
+ .option('approve', {
84
+ type: 'boolean',
85
+ description: 'If review passes, approve the PR',
86
+ default: false
87
+ })
88
+ .option('verbose', {
89
+ type: 'boolean',
90
+ description: 'Enable verbose logging for debugging',
91
+ alias: 'v',
92
+ default: false
93
+ })
94
+ .demandCommand(1, 'The GitHub pull request URL is required')
95
+ .parserConfiguration({
96
+ 'boolean-negation': true
97
+ })
98
+ .help('h')
99
+ .alias('h', 'help')
100
+ // Use yargs built-in strict mode to reject unrecognized options
101
+ // This prevents issues like #453 and #482 where unknown options are silently ignored
102
+ .strict()
103
+ .parse(process.argv.slice(2));
104
+
105
+ const prUrl = argv['pr-url'] || argv._[0];
106
+
107
+ // Set global verbose mode for log function
108
+ global.verboseMode = argv.verbose;
109
+
110
+ // Create permanent log file immediately with timestamp
111
+ const scriptDir = path.dirname(process.argv[1]);
112
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
113
+ const logFilePath = path.join(scriptDir, `review-${timestamp}.log`);
114
+ setLogFile(logFilePath);
115
+
116
+ // Create the log file immediately
117
+ await fs.writeFile(logFilePath, `# Review.mjs Log - ${new Date().toISOString()}\n\n`);
118
+ await log(`๐Ÿ“ Log file: ${logFilePath}`);
119
+ await log(' (All output will be logged here)\n');
120
+
121
+ // Validate GitHub PR URL format
122
+ if (!prUrl.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/)) {
123
+ await log('Error: Please provide a valid GitHub pull request URL (e.g., https://github.com/owner/repo/pull/123)', { level: 'error' });
124
+ process.exit(1);
125
+ }
126
+
127
+ const claudePath = process.env.CLAUDE_PATH || 'claude';
128
+
129
+ // Extract repository and PR number from URL
130
+ const urlParts = prUrl.split('/');
131
+ const owner = urlParts[3];
132
+ const repo = urlParts[4];
133
+ const prNumber = urlParts[6];
134
+
135
+ // Create or find temporary directory for cloning the repository
136
+ let tempDir;
137
+ let isResuming = argv.resume;
138
+
139
+ if (isResuming) {
140
+ // When resuming, try to find existing directory or create a new one
141
+ const scriptDir = path.dirname(process.argv[1]);
142
+ const sessionLogPattern = path.join(scriptDir, `${argv.resume}.log`);
143
+
144
+ try {
145
+ // Check if session log exists to verify session is valid
146
+ await fs.access(sessionLogPattern);
147
+ await log(`๐Ÿ”„ Resuming session ${argv.resume} (session log found)`);
148
+
149
+ // For resumed sessions, create new temp directory since old one may be cleaned up
150
+ tempDir = path.join(os.tmpdir(), `gh-pr-reviewer-resume-${argv.resume}-${Date.now()}`);
151
+ await fs.mkdir(tempDir, { recursive: true });
152
+ await log(`Creating new temporary directory for resumed session: ${tempDir}`);
153
+ } catch (err) {
154
+ reportError(err, {
155
+ context: 'resume_session_lookup',
156
+ sessionId: argv.resume
157
+ });
158
+ await log(`Warning: Session log for ${argv.resume} not found, but continuing with resume attempt`);
159
+ tempDir = path.join(os.tmpdir(), `gh-pr-reviewer-resume-${argv.resume}-${Date.now()}`);
160
+ await fs.mkdir(tempDir, { recursive: true });
161
+ await log(`Creating temporary directory for resumed session: ${tempDir}`);
162
+ }
163
+ } else {
164
+ tempDir = path.join(os.tmpdir(), `gh-pr-reviewer-${Date.now()}`);
165
+ await fs.mkdir(tempDir, { recursive: true });
166
+ await log(`Creating temporary directory: ${tempDir}\n`);
167
+ }
168
+
169
+ let limitReached = false;
170
+
171
+ try {
172
+ // Get PR details first
173
+ await log('๐Ÿ“Š Getting pull request details...');
174
+ const prDetailsResult = await $`gh pr view ${prUrl} --json title,body,headRefName,baseRefName,author,number,state,files`;
175
+
176
+ if (prDetailsResult.code !== 0) {
177
+ await log('Error: Failed to get PR details', { level: 'error' });
178
+ await log(prDetailsResult.stderr ? prDetailsResult.stderr.toString() : 'Unknown error', { level: 'error' });
179
+ process.exit(1);
180
+ }
181
+
182
+ const prDetails = JSON.parse(prDetailsResult.stdout.toString());
183
+
184
+ await log(`\n๐Ÿ“„ Pull Request: #${prDetails.number} - ${prDetails.title}`);
185
+ await log(`๐Ÿ‘ค Author: ${prDetails.author.login}`);
186
+ await log(`๐ŸŒฟ Branch: ${prDetails.headRefName} โ†’ ${prDetails.baseRefName}`);
187
+ await log(`๐Ÿ“Š State: ${prDetails.state}`);
188
+ await log(`๐Ÿ“ Files changed: ${prDetails.files.length}`);
189
+
190
+ // Clone the repository using gh tool with authentication
191
+ await log(`\nCloning repository ${owner}/${repo} using gh tool...\n`);
192
+ const cloneResult = await $`gh repo clone ${owner}/${repo} ${tempDir}`;
193
+
194
+ // Verify clone was successful
195
+ if (cloneResult.code !== 0) {
196
+ await log('Error: Failed to clone repository', { level: 'error' });
197
+ await log(cloneResult.stderr ? cloneResult.stderr.toString() : 'Unknown error', { level: 'error' });
198
+ process.exit(1);
199
+ }
200
+
201
+ await log(`โœ… Repository cloned successfully to ${tempDir}\n`);
202
+
203
+ // Set up git authentication using gh
204
+ const authSetupResult = await $`cd ${tempDir} && gh auth setup-git 2>&1`;
205
+ if (authSetupResult.code !== 0) {
206
+ await log('Note: gh auth setup-git had issues, continuing anyway\n');
207
+ }
208
+
209
+ // Fetch and checkout the PR branch
210
+ await log(`๐Ÿ”€ Fetching and checking out PR branch: ${prDetails.headRefName}`);
211
+ const fetchResult = await $`cd ${tempDir} && gh pr checkout ${prNumber}`;
212
+
213
+ if (fetchResult.code !== 0) {
214
+ await log('Error: Failed to checkout PR branch', { level: 'error' });
215
+ await log(fetchResult.stderr ? fetchResult.stderr.toString() : 'Unknown error', { level: 'error' });
216
+ process.exit(1);
217
+ }
218
+
219
+ await log('โœ… Successfully checked out PR branch\n');
220
+
221
+ // Get the diff for the PR
222
+ await log('๐Ÿ“ Getting PR diff...');
223
+ const diffResult = await $`gh pr diff ${prUrl}`;
224
+
225
+ if (diffResult.code !== 0) {
226
+ await log('Error: Failed to get PR diff', { level: 'error' });
227
+ await log(diffResult.stderr ? diffResult.stderr.toString() : 'Unknown error', { level: 'error' });
228
+ process.exit(1);
229
+ }
230
+
231
+ const prDiff = diffResult.stdout.toString();
232
+ await log(`โœ… Got PR diff (${prDiff.length} characters)\n`);
233
+
234
+ // Save diff to a file for reference
235
+ const diffFile = path.join(tempDir, 'pr-diff.patch');
236
+ await fs.writeFile(diffFile, prDiff);
237
+ await log(`๐Ÿ“„ Diff saved to: ${diffFile}\n`);
238
+
239
+ const prompt = `Pull request to review: ${prUrl}
240
+ PR Number: ${prNumber}
241
+ Repository: ${owner}/${repo}
242
+ Working directory: ${tempDir}
243
+ Diff file: ${diffFile}
244
+ Focus areas: ${argv.focus}
245
+ Auto-approve if passes: ${argv.approve}
246
+
247
+ Review this pull request thoroughly.`;
248
+
249
+ const systemPrompt = `You are an expert code reviewer for pull requests.
250
+
251
+ 0. General guidelines.
252
+ - When you execute commands, always save their logs to files for easy reading if the output gets large.
253
+ - When running commands, do not set a timeout yourself โ€” let them run as long as needed.
254
+ - When a code or log file has more than 2500 lines, read it in chunks of 2500 lines.
255
+ - When reviewing, be thorough but constructive.
256
+ - When suggesting improvements, provide specific code examples.
257
+
258
+ 1. Initial analysis.
259
+ - When you start, read the PR description using gh pr view ${prUrl}.
260
+ - When you need the diff, read it from ${diffFile} or use gh pr diff ${prNumber}.
261
+ - When you need file context, explore files in ${tempDir}.
262
+ - When you check tests, run them if possible using existing test commands.
263
+ - When you review commits, use gh pr view ${prNumber} --json commits.
264
+
265
+ 2. Review focus areas.
266
+ ${argv.focus === 'all' ? '- Review all aspects: logic, security, performance, style, tests, documentation.' : `- Focus specifically on: ${argv.focus}`}
267
+ - When reviewing logic, check for edge cases and error handling.
268
+ - When reviewing security, look for vulnerabilities and unsafe patterns.
269
+ - When reviewing performance, identify bottlenecks and inefficiencies.
270
+ - When reviewing style, ensure consistency with project conventions.
271
+ - When reviewing tests, verify coverage and test quality.
272
+
273
+ 3. Providing feedback.
274
+ - When you find issues, create review comments using gh pr review ${prNumber} --comment.
275
+ - When suggesting changes, use gh pr review ${prNumber} --comment with specific line references.
276
+ - When code needs changes, provide suggestions with exact code snippets.
277
+ - When adding line comments, use the format: path/to/file.ext:LINE_NUMBER
278
+ - When creating suggestions, use GitHub's suggestion format in comments:
279
+ \`\`\`suggestion
280
+ improved code here
281
+ \`\`\`
282
+
283
+ 4. Review submission.
284
+ - When review is complete, submit it using gh pr review ${prNumber} --${argv.approve ? 'approve' : 'comment'} --body "review summary".
285
+ - When requesting changes, use gh pr review ${prNumber} --request-changes --body "summary of required changes".
286
+ - When approving, only do so if code meets all quality standards.
287
+ - When commenting, be specific about line numbers and files.
288
+
289
+ 5. Line-specific comments.
290
+ - When adding comments to specific lines, use gh api to post review comments.
291
+ - When referencing lines, use the commit SHA from the PR.
292
+ - When suggesting code changes, include the suggestion block format.
293
+ - Example for line comment:
294
+ gh api repos/${owner}/${repo}/pulls/${prNumber}/comments \\
295
+ --method POST \\
296
+ --field path="file.js" \\
297
+ --field line=42 \\
298
+ --field body="Comment text with suggestion" \\
299
+ --field commit_id="SHA"
300
+
301
+ 6. Best practices.
302
+ - When reviewing, check for breaking changes.
303
+ - When examining dependencies, verify versions and security.
304
+ - When looking at tests, ensure they actually test the changes.
305
+ - When reviewing documentation, verify it matches the code.
306
+ - When finding issues, prioritize them by severity.
307
+ - When suggesting improvements, explain why they're beneficial.`;
308
+
309
+ // Properly escape prompts for shell usage - escape quotes and preserve newlines
310
+ const escapedPrompt = prompt.replace(/"/g, '\\"').replace(/\$/g, '\\$');
311
+ const escapedSystemPrompt = systemPrompt.replace(/"/g, '\\"').replace(/\$/g, '\\$');
312
+
313
+ // Execute claude command from the cloned repository directory
314
+ await log(`\n๐Ÿค– Executing Claude (${argv.model.toUpperCase()}) for PR review...`);
315
+
316
+ // If dry-run, exit here
317
+ if (argv.dryRun) {
318
+ await log('โœ… Command preparation complete');
319
+ await log(`๐Ÿ“‚ Repository cloned to: ${tempDir}`);
320
+ await log(`๐Ÿ”€ PR branch checked out: ${prDetails.headRefName}`);
321
+ process.exit(0);
322
+ }
323
+
324
+ // Change to the temporary directory before executing
325
+ process.chdir(tempDir);
326
+
327
+ // Use the shared executeClaudeCommand function from claude.lib.mjs
328
+ // This ensures consistent NDJSON parsing, error handling, and retry logic across all commands
329
+ const result = await executeClaudeCommand({
330
+ tempDir,
331
+ branchName: prDetails.headRefName,
332
+ prompt,
333
+ systemPrompt,
334
+ escapedPrompt,
335
+ escapedSystemPrompt,
336
+ argv,
337
+ log,
338
+ setLogFile,
339
+ getLogFile,
340
+ formatAligned,
341
+ getResourceSnapshot: memoryCheck.getResourceSnapshot,
342
+ forkedRepo: null,
343
+ feedbackLines: [],
344
+ claudePath,
345
+ $
346
+ });
347
+
348
+ const { success: commandSuccess, sessionId, limitReached: limitReachedResult } = result;
349
+ limitReached = limitReachedResult;
350
+
351
+ // Handle command failure
352
+ if (!commandSuccess) {
353
+ await log('\nโŒ Command execution failed. Check the log file for details.');
354
+ await log(`๐Ÿ“ Log file: ${getLogFile()}`);
355
+ process.exit(1);
356
+ }
357
+
358
+ // Show summary of session and log file
359
+ await log('\n=== Review Summary ===');
360
+
361
+ if (sessionId) {
362
+ await log(`โœ… Session ID: ${sessionId}`);
363
+ await log(`โœ… Complete log file: ${getLogFile()}`);
364
+
365
+ if (limitReached) {
366
+ await log('\nโฐ LIMIT REACHED DETECTED!');
367
+ await log('\n๐Ÿ”„ To resume when limit resets, use:\n');
368
+ await log(`./review.mjs "${prUrl}" --resume ${sessionId}`);
369
+ await log('\n This will continue from where it left off with full context.\n');
370
+ } else {
371
+ // Check if review was submitted
372
+ await log('\n๐Ÿ” Checking for submitted review...');
373
+
374
+ try {
375
+ // Get reviews for the PR
376
+ const reviewsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews --jq '.[] | select(.user.login == "'$(gh api user --jq .login)'") | {state, submitted_at}'`;
377
+
378
+ if (reviewsResult.code === 0 && reviewsResult.stdout.toString().trim()) {
379
+ await log(`โœ… Review has been submitted to PR #${prNumber}`);
380
+ await log(`๐Ÿ“ View at: ${prUrl}`);
381
+ } else {
382
+ await log('โ„น๏ธ Review may be pending or saved as draft');
383
+ }
384
+ } catch (error) {
385
+ reportError(error, {
386
+ context: 'verify_review_status',
387
+ prNumber,
388
+ level: 'warning'
389
+ });
390
+ await log('โš ๏ธ Could not verify review status');
391
+ }
392
+
393
+ // Show command to resume session in interactive mode
394
+ await log('\n๐Ÿ’ก To continue this session in Claude Code interactive mode:\n');
395
+ await log(` (cd ${tempDir} && claude --resume ${sessionId})`);
396
+ await log('');
397
+ }
398
+ } else {
399
+ await log('โŒ No session ID extracted');
400
+ await log(`๐Ÿ“ Log file available: ${getLogFile()}`);
401
+ }
402
+
403
+ await log('\nโœจ Review process complete. Check the PR for review comments.');
404
+ await log(`๐Ÿ“ Pull Request: ${prUrl}`);
405
+
406
+ } catch (error) {
407
+ reportError(error, {
408
+ context: 'review_execution',
409
+ prUrl: prUrl
410
+ });
411
+ await log('Error executing review:', error.message, { level: 'error' });
412
+ process.exit(1);
413
+ } finally {
414
+ // Clean up temporary directory (but not when resuming or when limit reached)
415
+ if (!argv.resume && !limitReached) {
416
+ try {
417
+ process.stdout.write('\n๐Ÿงน Cleaning up...');
418
+ await fs.rm(tempDir, { recursive: true, force: true });
419
+ await log(' โœ…');
420
+ } catch (cleanupError) {
421
+ reportError(cleanupError, {
422
+ context: 'cleanup_temp_dir',
423
+ level: 'warning',
424
+ tempDir
425
+ });
426
+ await log(' โš ๏ธ (failed)');
427
+ }
428
+ } else if (argv.resume) {
429
+ await log(`\n๐Ÿ“ Keeping directory for resumed session: ${tempDir}`);
430
+ } else if (limitReached) {
431
+ await log(`\n๐Ÿ“ Keeping directory for future resume: ${tempDir}`);
432
+ }
433
+ }