@link-assistant/hive-mind 0.46.1 ā 0.47.1
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/CHANGELOG.md +20 -15
- package/README.md +42 -8
- package/package.json +16 -3
- package/src/agent.lib.mjs +49 -70
- package/src/agent.prompts.lib.mjs +6 -20
- package/src/buildUserMention.lib.mjs +4 -17
- package/src/claude-limits.lib.mjs +15 -15
- package/src/claude.lib.mjs +617 -626
- package/src/claude.prompts.lib.mjs +7 -22
- package/src/codex.lib.mjs +39 -71
- package/src/codex.prompts.lib.mjs +6 -20
- package/src/config.lib.mjs +3 -16
- package/src/contributing-guidelines.lib.mjs +5 -18
- package/src/exit-handler.lib.mjs +4 -4
- package/src/git.lib.mjs +7 -7
- package/src/github-issue-creator.lib.mjs +17 -17
- package/src/github-linking.lib.mjs +8 -33
- package/src/github.batch.lib.mjs +20 -16
- package/src/github.graphql.lib.mjs +18 -18
- package/src/github.lib.mjs +89 -91
- package/src/hive.config.lib.mjs +50 -50
- package/src/hive.mjs +1293 -1296
- package/src/instrument.mjs +7 -11
- package/src/interactive-mode.lib.mjs +112 -138
- package/src/lenv-reader.lib.mjs +1 -6
- package/src/lib.mjs +36 -45
- package/src/lino.lib.mjs +2 -2
- package/src/local-ci-checks.lib.mjs +15 -14
- package/src/memory-check.mjs +52 -60
- package/src/model-mapping.lib.mjs +25 -32
- package/src/model-validation.lib.mjs +31 -31
- package/src/opencode.lib.mjs +37 -62
- package/src/opencode.prompts.lib.mjs +7 -21
- package/src/protect-branch.mjs +14 -15
- package/src/review.mjs +28 -27
- package/src/reviewers-hive.mjs +64 -69
- package/src/sentry.lib.mjs +13 -10
- package/src/solve.auto-continue.lib.mjs +48 -38
- package/src/solve.auto-pr.lib.mjs +111 -69
- package/src/solve.branch-errors.lib.mjs +17 -46
- package/src/solve.branch.lib.mjs +16 -23
- package/src/solve.config.lib.mjs +263 -261
- package/src/solve.error-handlers.lib.mjs +21 -79
- package/src/solve.execution.lib.mjs +10 -18
- package/src/solve.feedback.lib.mjs +25 -46
- package/src/solve.mjs +59 -60
- package/src/solve.preparation.lib.mjs +10 -36
- package/src/solve.repo-setup.lib.mjs +4 -19
- package/src/solve.repository.lib.mjs +37 -37
- package/src/solve.results.lib.mjs +32 -46
- package/src/solve.session.lib.mjs +7 -22
- package/src/solve.validation.lib.mjs +19 -17
- package/src/solve.watch.lib.mjs +20 -33
- package/src/start-screen.mjs +24 -24
- package/src/task.mjs +38 -44
- package/src/telegram-bot.mjs +125 -121
- package/src/telegram-top-command.lib.mjs +32 -48
- package/src/usage-limit.lib.mjs +9 -13
- package/src/version-info.lib.mjs +1 -1
- package/src/version.lib.mjs +1 -1
- package/src/youtrack/solve.youtrack.lib.mjs +3 -8
- package/src/youtrack/youtrack-sync.mjs +8 -14
- package/src/youtrack/youtrack.lib.mjs +26 -28
package/src/opencode.lib.mjs
CHANGED
|
@@ -19,15 +19,15 @@ import { timeouts } from './config.lib.mjs';
|
|
|
19
19
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
20
20
|
|
|
21
21
|
// Model mapping to translate aliases to full model IDs for OpenCode
|
|
22
|
-
export const mapModelToId =
|
|
22
|
+
export const mapModelToId = model => {
|
|
23
23
|
const modelMap = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
gpt4: 'openai/gpt-4',
|
|
25
|
+
gpt4o: 'openai/gpt-4o',
|
|
26
|
+
claude: 'anthropic/claude-3-5-sonnet',
|
|
27
|
+
sonnet: 'anthropic/claude-3-5-sonnet',
|
|
28
|
+
opus: 'anthropic/claude-3-opus',
|
|
29
|
+
gemini: 'google/gemini-pro',
|
|
30
|
+
grok: 'opencode/grok-code',
|
|
31
31
|
'grok-code': 'opencode/grok-code',
|
|
32
32
|
'grok-code-fast-1': 'opencode/grok-code',
|
|
33
33
|
};
|
|
@@ -108,28 +108,8 @@ export const handleOpenCodeRuntimeSwitch = async () => {
|
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
// Main function to execute OpenCode with prompts and settings
|
|
111
|
-
export const executeOpenCode = async
|
|
112
|
-
const {
|
|
113
|
-
issueUrl,
|
|
114
|
-
issueNumber,
|
|
115
|
-
prNumber,
|
|
116
|
-
prUrl,
|
|
117
|
-
branchName,
|
|
118
|
-
tempDir,
|
|
119
|
-
isContinueMode,
|
|
120
|
-
mergeStateStatus,
|
|
121
|
-
forkedRepo,
|
|
122
|
-
feedbackLines,
|
|
123
|
-
forkActionsUrl,
|
|
124
|
-
owner,
|
|
125
|
-
repo,
|
|
126
|
-
argv,
|
|
127
|
-
log,
|
|
128
|
-
formatAligned,
|
|
129
|
-
getResourceSnapshot,
|
|
130
|
-
opencodePath = 'opencode',
|
|
131
|
-
$
|
|
132
|
-
} = params;
|
|
111
|
+
export const executeOpenCode = async params => {
|
|
112
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
133
113
|
|
|
134
114
|
// Import prompt building functions from opencode.prompts.lib.mjs
|
|
135
115
|
const { buildUserPrompt, buildSystemPrompt } = await import('./opencode.prompts.lib.mjs');
|
|
@@ -149,7 +129,7 @@ export const executeOpenCode = async (params) => {
|
|
|
149
129
|
forkActionsUrl,
|
|
150
130
|
owner,
|
|
151
131
|
repo,
|
|
152
|
-
argv
|
|
132
|
+
argv,
|
|
153
133
|
});
|
|
154
134
|
|
|
155
135
|
// Build the system prompt
|
|
@@ -162,7 +142,7 @@ export const executeOpenCode = async (params) => {
|
|
|
162
142
|
tempDir,
|
|
163
143
|
isContinueMode,
|
|
164
144
|
forkedRepo,
|
|
165
|
-
argv
|
|
145
|
+
argv,
|
|
166
146
|
});
|
|
167
147
|
|
|
168
148
|
// Log prompt details in verbose mode
|
|
@@ -199,25 +179,12 @@ export const executeOpenCode = async (params) => {
|
|
|
199
179
|
forkedRepo,
|
|
200
180
|
feedbackLines,
|
|
201
181
|
opencodePath,
|
|
202
|
-
|
|
182
|
+
$,
|
|
203
183
|
});
|
|
204
184
|
};
|
|
205
185
|
|
|
206
|
-
export const executeOpenCodeCommand = async
|
|
207
|
-
const {
|
|
208
|
-
tempDir,
|
|
209
|
-
branchName,
|
|
210
|
-
prompt,
|
|
211
|
-
systemPrompt,
|
|
212
|
-
argv,
|
|
213
|
-
log,
|
|
214
|
-
formatAligned,
|
|
215
|
-
getResourceSnapshot,
|
|
216
|
-
forkedRepo,
|
|
217
|
-
feedbackLines,
|
|
218
|
-
opencodePath,
|
|
219
|
-
$
|
|
220
|
-
} = params;
|
|
186
|
+
export const executeOpenCodeCommand = async params => {
|
|
187
|
+
const { tempDir, branchName, prompt, systemPrompt, argv, log, formatAligned, getResourceSnapshot, forkedRepo, feedbackLines, opencodePath, $ } = params;
|
|
221
188
|
|
|
222
189
|
// Retry configuration
|
|
223
190
|
const maxRetries = 3;
|
|
@@ -286,12 +253,12 @@ export const executeOpenCodeCommand = async (params) => {
|
|
|
286
253
|
if (argv.resume) {
|
|
287
254
|
execCommand = $({
|
|
288
255
|
cwd: tempDir,
|
|
289
|
-
mirror: false
|
|
256
|
+
mirror: false,
|
|
290
257
|
})`cat ${promptFile} | ${opencodePath} run --format json --resume ${argv.resume} --model ${mappedModel}`;
|
|
291
258
|
} else {
|
|
292
259
|
execCommand = $({
|
|
293
260
|
cwd: tempDir,
|
|
294
|
-
mirror: false
|
|
261
|
+
mirror: false,
|
|
295
262
|
})`cat ${promptFile} | ${opencodePath} run --format json --model ${mappedModel}`;
|
|
296
263
|
}
|
|
297
264
|
|
|
@@ -340,7 +307,7 @@ export const executeOpenCodeCommand = async (params) => {
|
|
|
340
307
|
tool: 'OpenCode',
|
|
341
308
|
resetTime: limitInfo.resetTime,
|
|
342
309
|
sessionId,
|
|
343
|
-
resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null
|
|
310
|
+
resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null,
|
|
344
311
|
});
|
|
345
312
|
|
|
346
313
|
for (const line of messageLines) {
|
|
@@ -359,7 +326,7 @@ export const executeOpenCodeCommand = async (params) => {
|
|
|
359
326
|
success: false,
|
|
360
327
|
sessionId,
|
|
361
328
|
limitReached,
|
|
362
|
-
limitResetTime
|
|
329
|
+
limitResetTime,
|
|
363
330
|
};
|
|
364
331
|
}
|
|
365
332
|
|
|
@@ -369,14 +336,14 @@ export const executeOpenCodeCommand = async (params) => {
|
|
|
369
336
|
success: true,
|
|
370
337
|
sessionId,
|
|
371
338
|
limitReached,
|
|
372
|
-
limitResetTime
|
|
339
|
+
limitResetTime,
|
|
373
340
|
};
|
|
374
341
|
} catch (error) {
|
|
375
342
|
reportError(error, {
|
|
376
343
|
context: 'execute_opencode',
|
|
377
344
|
command: params.command,
|
|
378
345
|
opencodePath: params.opencodePath,
|
|
379
|
-
operation: 'run_opencode_command'
|
|
346
|
+
operation: 'run_opencode_command',
|
|
380
347
|
});
|
|
381
348
|
|
|
382
349
|
await log(`\n\nā Error executing OpenCode command: ${error.message}`, { level: 'error' });
|
|
@@ -384,7 +351,7 @@ export const executeOpenCodeCommand = async (params) => {
|
|
|
384
351
|
success: false,
|
|
385
352
|
sessionId: null,
|
|
386
353
|
limitReached: false,
|
|
387
|
-
limitResetTime: null
|
|
354
|
+
limitResetTime: null,
|
|
388
355
|
};
|
|
389
356
|
}
|
|
390
357
|
};
|
|
@@ -425,13 +392,19 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
425
392
|
if (pushResult.code === 0) {
|
|
426
393
|
await log('ā
Changes pushed successfully');
|
|
427
394
|
} else {
|
|
428
|
-
await log(`ā ļø Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
395
|
+
await log(`ā ļø Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
|
|
396
|
+
level: 'warning',
|
|
397
|
+
});
|
|
429
398
|
}
|
|
430
399
|
} else {
|
|
431
|
-
await log(`ā ļø Warning: Could not commit changes: ${commitResult.stderr?.toString().trim()}`, {
|
|
400
|
+
await log(`ā ļø Warning: Could not commit changes: ${commitResult.stderr?.toString().trim()}`, {
|
|
401
|
+
level: 'warning',
|
|
402
|
+
});
|
|
432
403
|
}
|
|
433
404
|
} else {
|
|
434
|
-
await log(`ā ļø Warning: Could not stage changes: ${addResult.stderr?.toString().trim()}`, {
|
|
405
|
+
await log(`ā ļø Warning: Could not stage changes: ${addResult.stderr?.toString().trim()}`, {
|
|
406
|
+
level: 'warning',
|
|
407
|
+
});
|
|
435
408
|
}
|
|
436
409
|
return false;
|
|
437
410
|
} else if (autoRestartEnabled) {
|
|
@@ -455,14 +428,16 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
455
428
|
return false;
|
|
456
429
|
}
|
|
457
430
|
} else {
|
|
458
|
-
await log(`ā ļø Warning: Could not check git status: ${gitStatusResult.stderr?.toString().trim()}`, {
|
|
431
|
+
await log(`ā ļø Warning: Could not check git status: ${gitStatusResult.stderr?.toString().trim()}`, {
|
|
432
|
+
level: 'warning',
|
|
433
|
+
});
|
|
459
434
|
return false;
|
|
460
435
|
}
|
|
461
436
|
} catch (gitError) {
|
|
462
437
|
reportError(gitError, {
|
|
463
438
|
context: 'check_uncommitted_changes_opencode',
|
|
464
439
|
tempDir,
|
|
465
|
-
operation: 'git_status_check'
|
|
440
|
+
operation: 'git_status_check',
|
|
466
441
|
});
|
|
467
442
|
await log(`ā ļø Warning: Error checking for uncommitted changes: ${gitError.message}`, { level: 'warning' });
|
|
468
443
|
return false;
|
|
@@ -475,5 +450,5 @@ export default {
|
|
|
475
450
|
handleOpenCodeRuntimeSwitch,
|
|
476
451
|
executeOpenCode,
|
|
477
452
|
executeOpenCodeCommand,
|
|
478
|
-
checkForUncommittedChanges
|
|
479
|
-
};
|
|
453
|
+
checkForUncommittedChanges,
|
|
454
|
+
};
|
|
@@ -8,22 +8,8 @@
|
|
|
8
8
|
* @param {Object} params - Parameters for building the user prompt
|
|
9
9
|
* @returns {string} The formatted user prompt
|
|
10
10
|
*/
|
|
11
|
-
export const buildUserPrompt =
|
|
12
|
-
const {
|
|
13
|
-
issueUrl,
|
|
14
|
-
issueNumber,
|
|
15
|
-
prNumber,
|
|
16
|
-
prUrl,
|
|
17
|
-
branchName,
|
|
18
|
-
tempDir,
|
|
19
|
-
isContinueMode,
|
|
20
|
-
forkedRepo,
|
|
21
|
-
feedbackLines,
|
|
22
|
-
forkActionsUrl,
|
|
23
|
-
owner,
|
|
24
|
-
repo,
|
|
25
|
-
argv
|
|
26
|
-
} = params;
|
|
11
|
+
export const buildUserPrompt = params => {
|
|
12
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
27
13
|
|
|
28
14
|
const promptLines = [];
|
|
29
15
|
|
|
@@ -70,7 +56,7 @@ export const buildUserPrompt = (params) => {
|
|
|
70
56
|
low: 'Think.',
|
|
71
57
|
medium: 'Think hard.',
|
|
72
58
|
high: 'Think harder.',
|
|
73
|
-
max: 'Ultrathink.'
|
|
59
|
+
max: 'Ultrathink.',
|
|
74
60
|
};
|
|
75
61
|
promptLines.push(thinkMessages[argv.think]);
|
|
76
62
|
}
|
|
@@ -87,7 +73,7 @@ export const buildUserPrompt = (params) => {
|
|
|
87
73
|
* @param {Object} params - Parameters for building the prompt
|
|
88
74
|
* @returns {string} The formatted system prompt
|
|
89
75
|
*/
|
|
90
|
-
export const buildSystemPrompt =
|
|
76
|
+
export const buildSystemPrompt = params => {
|
|
91
77
|
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
92
78
|
|
|
93
79
|
// Build thinking instruction based on --think level
|
|
@@ -97,7 +83,7 @@ export const buildSystemPrompt = (params) => {
|
|
|
97
83
|
low: 'You always think on every step.',
|
|
98
84
|
medium: 'You always think hard on every step.',
|
|
99
85
|
high: 'You always think harder on every step.',
|
|
100
|
-
max: 'You always ultrathink on every step.'
|
|
86
|
+
max: 'You always ultrathink on every step.',
|
|
101
87
|
};
|
|
102
88
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
103
89
|
}
|
|
@@ -190,5 +176,5 @@ GitHub CLI command patterns.
|
|
|
190
176
|
// Export all functions as default object too
|
|
191
177
|
export default {
|
|
192
178
|
buildUserPrompt,
|
|
193
|
-
buildSystemPrompt
|
|
194
|
-
};
|
|
179
|
+
buildSystemPrompt,
|
|
180
|
+
};
|
package/src/protect-branch.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Branch Protection Script
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Enables branch protection rules on the default branch of a GitHub repository
|
|
6
6
|
* to require pull requests before merging.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Usage:
|
|
9
9
|
* ./protect-branch.mjs <owner>/<repo>
|
|
10
10
|
* ./protect-branch.mjs <owner>/<repo> <branch-name>
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* Examples:
|
|
13
13
|
* ./protect-branch.mjs konard/my-repo
|
|
14
14
|
* ./protect-branch.mjs konard/my-repo main
|
|
@@ -51,13 +51,13 @@ try {
|
|
|
51
51
|
if (!branchName) {
|
|
52
52
|
console.log('š Detecting default branch...');
|
|
53
53
|
const defaultBranchResult = await $`gh api repos/${owner}/${repo} --jq .default_branch`;
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
if (defaultBranchResult.code !== 0) {
|
|
56
56
|
console.error('Error: Failed to get repository information');
|
|
57
57
|
console.error(defaultBranchResult.stderr ? defaultBranchResult.stderr.toString() : 'Unknown error');
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
branchName = defaultBranchResult.stdout.toString().trim();
|
|
62
62
|
console.log(`ā
Default branch: ${branchName}`);
|
|
63
63
|
} else {
|
|
@@ -67,7 +67,7 @@ try {
|
|
|
67
67
|
// Check if branch exists
|
|
68
68
|
console.log('š Verifying branch exists...');
|
|
69
69
|
const branchCheckResult = await $`gh api repos/${owner}/${repo}/branches/${branchName} --silent`;
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
if (branchCheckResult.code !== 0) {
|
|
72
72
|
console.error(`Error: Branch '${branchName}' not found in repository`);
|
|
73
73
|
process.exit(1);
|
|
@@ -76,7 +76,7 @@ try {
|
|
|
76
76
|
|
|
77
77
|
// Enable branch protection with PR requirement
|
|
78
78
|
console.log(`š Enabling branch protection for '${branchName}'...`);
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
// Create the protection rules JSON
|
|
81
81
|
const protectionRules = {
|
|
82
82
|
required_status_checks: null,
|
|
@@ -85,7 +85,7 @@ try {
|
|
|
85
85
|
dismiss_stale_reviews: false,
|
|
86
86
|
require_code_owner_reviews: false,
|
|
87
87
|
required_approving_review_count: 0,
|
|
88
|
-
require_last_push_approval: false
|
|
88
|
+
require_last_push_approval: false,
|
|
89
89
|
},
|
|
90
90
|
restrictions: null,
|
|
91
91
|
allow_force_pushes: false,
|
|
@@ -93,7 +93,7 @@ try {
|
|
|
93
93
|
block_creations: false,
|
|
94
94
|
required_conversation_resolution: false,
|
|
95
95
|
lock_branch: false,
|
|
96
|
-
allow_fork_syncing: false
|
|
96
|
+
allow_fork_syncing: false,
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
// Apply branch protection using GitHub API
|
|
@@ -112,10 +112,10 @@ EOF`;
|
|
|
112
112
|
console.error('Error: Failed to enable branch protection');
|
|
113
113
|
console.error(protectResult.stderr ? protectResult.stderr.toString() : 'Unknown error');
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
// Try a simpler approach for public repos
|
|
117
117
|
console.log('š Trying alternative method...');
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
// For public repos, we can at least try to update settings
|
|
120
120
|
const updateResult = await $`gh api \
|
|
121
121
|
--method PATCH \
|
|
@@ -124,7 +124,7 @@ EOF`;
|
|
|
124
124
|
--field allow_squash_merge=true \
|
|
125
125
|
--field allow_rebase_merge=true \
|
|
126
126
|
--field delete_branch_on_merge=false`;
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
if (updateResult.code === 0) {
|
|
129
129
|
console.log('ā
Repository settings updated (PR workflow encouraged)');
|
|
130
130
|
}
|
|
@@ -135,7 +135,7 @@ EOF`;
|
|
|
135
135
|
// Verify the protection status
|
|
136
136
|
console.log('\nš Verifying protection status...');
|
|
137
137
|
const statusResult = await $`gh api repos/${owner}/${repo}/branches/${branchName}/protection --silent 2>/dev/null || echo "not-protected"`;
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
if (statusResult.stdout.toString().trim() === 'not-protected') {
|
|
140
140
|
console.log('ā ļø Branch protection not fully active (may require admin rights or paid plan)');
|
|
141
141
|
console.log('\nš” Alternative: Configure protection manually in repository settings:');
|
|
@@ -149,11 +149,10 @@ EOF`;
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
console.log('\n⨠Done! The branch is configured to require pull requests.');
|
|
152
|
-
|
|
153
152
|
} catch (error) {
|
|
154
153
|
console.error('\nā Error:', error.message);
|
|
155
154
|
if (error.stderr) {
|
|
156
155
|
console.error('Details:', error.stderr.toString());
|
|
157
156
|
}
|
|
158
157
|
process.exit(1);
|
|
159
|
-
}
|
|
158
|
+
}
|
package/src/review.mjs
CHANGED
|
@@ -55,45 +55,45 @@ const argv = yargs()
|
|
|
55
55
|
.usage('Usage: $0 <pr-url> [options]')
|
|
56
56
|
.positional('pr-url', {
|
|
57
57
|
type: 'string',
|
|
58
|
-
description: 'The GitHub pull request URL to review'
|
|
58
|
+
description: 'The GitHub pull request URL to review',
|
|
59
59
|
})
|
|
60
60
|
.option('resume', {
|
|
61
61
|
type: 'string',
|
|
62
62
|
description: 'Resume from a previous session ID (when limit was reached)',
|
|
63
|
-
alias: 'r'
|
|
63
|
+
alias: 'r',
|
|
64
64
|
})
|
|
65
65
|
.option('dry-run', {
|
|
66
66
|
type: 'boolean',
|
|
67
67
|
description: 'Prepare everything but do not execute Claude',
|
|
68
|
-
alias: 'n'
|
|
68
|
+
alias: 'n',
|
|
69
69
|
})
|
|
70
70
|
.option('model', {
|
|
71
71
|
type: 'string',
|
|
72
72
|
description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
|
|
73
73
|
alias: 'm',
|
|
74
74
|
default: 'opus',
|
|
75
|
-
choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101']
|
|
75
|
+
choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101'],
|
|
76
76
|
})
|
|
77
77
|
.option('focus', {
|
|
78
78
|
type: 'string',
|
|
79
79
|
description: 'Focus areas for review (security, performance, logic, style, tests)',
|
|
80
80
|
alias: 'f',
|
|
81
|
-
default: 'all'
|
|
81
|
+
default: 'all',
|
|
82
82
|
})
|
|
83
83
|
.option('approve', {
|
|
84
84
|
type: 'boolean',
|
|
85
85
|
description: 'If review passes, approve the PR',
|
|
86
|
-
default: false
|
|
86
|
+
default: false,
|
|
87
87
|
})
|
|
88
88
|
.option('verbose', {
|
|
89
89
|
type: 'boolean',
|
|
90
90
|
description: 'Enable verbose logging for debugging',
|
|
91
91
|
alias: 'v',
|
|
92
|
-
default: false
|
|
92
|
+
default: false,
|
|
93
93
|
})
|
|
94
94
|
.demandCommand(1, 'The GitHub pull request URL is required')
|
|
95
95
|
.parserConfiguration({
|
|
96
|
-
'boolean-negation': true
|
|
96
|
+
'boolean-negation': true,
|
|
97
97
|
})
|
|
98
98
|
.help('h')
|
|
99
99
|
.alias('h', 'help')
|
|
@@ -120,7 +120,9 @@ await log(' (All output will be logged here)\n');
|
|
|
120
120
|
|
|
121
121
|
// Validate GitHub PR URL format
|
|
122
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)', {
|
|
123
|
+
await log('Error: Please provide a valid GitHub pull request URL (e.g., https://github.com/owner/repo/pull/123)', {
|
|
124
|
+
level: 'error',
|
|
125
|
+
});
|
|
124
126
|
process.exit(1);
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -153,7 +155,7 @@ if (isResuming) {
|
|
|
153
155
|
} catch (err) {
|
|
154
156
|
reportError(err, {
|
|
155
157
|
context: 'resume_session_lookup',
|
|
156
|
-
sessionId: argv.resume
|
|
158
|
+
sessionId: argv.resume,
|
|
157
159
|
});
|
|
158
160
|
await log(`Warning: Session log for ${argv.resume} not found, but continuing with resume attempt`);
|
|
159
161
|
tempDir = path.join(os.tmpdir(), `gh-pr-reviewer-resume-${argv.resume}-${Date.now()}`);
|
|
@@ -172,15 +174,15 @@ try {
|
|
|
172
174
|
// Get PR details first
|
|
173
175
|
await log('š Getting pull request details...');
|
|
174
176
|
const prDetailsResult = await $`gh pr view ${prUrl} --json title,body,headRefName,baseRefName,author,number,state,files`;
|
|
175
|
-
|
|
177
|
+
|
|
176
178
|
if (prDetailsResult.code !== 0) {
|
|
177
179
|
await log('Error: Failed to get PR details', { level: 'error' });
|
|
178
180
|
await log(prDetailsResult.stderr ? prDetailsResult.stderr.toString() : 'Unknown error', { level: 'error' });
|
|
179
181
|
process.exit(1);
|
|
180
182
|
}
|
|
181
|
-
|
|
183
|
+
|
|
182
184
|
const prDetails = JSON.parse(prDetailsResult.stdout.toString());
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
await log(`\nš Pull Request: #${prDetails.number} - ${prDetails.title}`);
|
|
185
187
|
await log(`š¤ Author: ${prDetails.author.login}`);
|
|
186
188
|
await log(`šæ Branch: ${prDetails.headRefName} ā ${prDetails.baseRefName}`);
|
|
@@ -190,7 +192,7 @@ try {
|
|
|
190
192
|
// Clone the repository using gh tool with authentication
|
|
191
193
|
await log(`\nCloning repository ${owner}/${repo} using gh tool...\n`);
|
|
192
194
|
const cloneResult = await $`gh repo clone ${owner}/${repo} ${tempDir}`;
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
// Verify clone was successful
|
|
195
197
|
if (cloneResult.code !== 0) {
|
|
196
198
|
await log('Error: Failed to clone repository', { level: 'error' });
|
|
@@ -209,25 +211,25 @@ try {
|
|
|
209
211
|
// Fetch and checkout the PR branch
|
|
210
212
|
await log(`š Fetching and checking out PR branch: ${prDetails.headRefName}`);
|
|
211
213
|
const fetchResult = await $`cd ${tempDir} && gh pr checkout ${prNumber}`;
|
|
212
|
-
|
|
214
|
+
|
|
213
215
|
if (fetchResult.code !== 0) {
|
|
214
216
|
await log('Error: Failed to checkout PR branch', { level: 'error' });
|
|
215
217
|
await log(fetchResult.stderr ? fetchResult.stderr.toString() : 'Unknown error', { level: 'error' });
|
|
216
218
|
process.exit(1);
|
|
217
219
|
}
|
|
218
|
-
|
|
220
|
+
|
|
219
221
|
await log('ā
Successfully checked out PR branch\n');
|
|
220
222
|
|
|
221
223
|
// Get the diff for the PR
|
|
222
224
|
await log('š Getting PR diff...');
|
|
223
225
|
const diffResult = await $`gh pr diff ${prUrl}`;
|
|
224
|
-
|
|
226
|
+
|
|
225
227
|
if (diffResult.code !== 0) {
|
|
226
228
|
await log('Error: Failed to get PR diff', { level: 'error' });
|
|
227
229
|
await log(diffResult.stderr ? diffResult.stderr.toString() : 'Unknown error', { level: 'error' });
|
|
228
230
|
process.exit(1);
|
|
229
231
|
}
|
|
230
|
-
|
|
232
|
+
|
|
231
233
|
const prDiff = diffResult.stdout.toString();
|
|
232
234
|
await log(`ā
Got PR diff (${prDiff.length} characters)\n`);
|
|
233
235
|
|
|
@@ -342,7 +344,7 @@ Review this pull request thoroughly.`;
|
|
|
342
344
|
forkedRepo: null,
|
|
343
345
|
feedbackLines: [],
|
|
344
346
|
claudePath,
|
|
345
|
-
|
|
347
|
+
$,
|
|
346
348
|
});
|
|
347
349
|
|
|
348
350
|
const { success: commandSuccess, sessionId, limitReached: limitReachedResult } = result;
|
|
@@ -370,11 +372,11 @@ Review this pull request thoroughly.`;
|
|
|
370
372
|
} else {
|
|
371
373
|
// Check if review was submitted
|
|
372
374
|
await log('\nš Checking for submitted review...');
|
|
373
|
-
|
|
375
|
+
|
|
374
376
|
try {
|
|
375
377
|
// Get reviews for the PR
|
|
376
378
|
const reviewsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews --jq '.[] | select(.user.login == "'$(gh api user --jq .login)'") | {state, submitted_at}'`;
|
|
377
|
-
|
|
379
|
+
|
|
378
380
|
if (reviewsResult.code === 0 && reviewsResult.stdout.toString().trim()) {
|
|
379
381
|
await log(`ā
Review has been submitted to PR #${prNumber}`);
|
|
380
382
|
await log(`š View at: ${prUrl}`);
|
|
@@ -385,11 +387,11 @@ Review this pull request thoroughly.`;
|
|
|
385
387
|
reportError(error, {
|
|
386
388
|
context: 'verify_review_status',
|
|
387
389
|
prNumber,
|
|
388
|
-
level: 'warning'
|
|
390
|
+
level: 'warning',
|
|
389
391
|
});
|
|
390
392
|
await log('ā ļø Could not verify review status');
|
|
391
393
|
}
|
|
392
|
-
|
|
394
|
+
|
|
393
395
|
// Show command to resume session in interactive mode
|
|
394
396
|
await log('\nš” To continue this session in Claude Code interactive mode:\n');
|
|
395
397
|
await log(` (cd ${tempDir} && claude --resume ${sessionId})`);
|
|
@@ -402,11 +404,10 @@ Review this pull request thoroughly.`;
|
|
|
402
404
|
|
|
403
405
|
await log('\n⨠Review process complete. Check the PR for review comments.');
|
|
404
406
|
await log(`š Pull Request: ${prUrl}`);
|
|
405
|
-
|
|
406
407
|
} catch (error) {
|
|
407
408
|
reportError(error, {
|
|
408
409
|
context: 'review_execution',
|
|
409
|
-
prUrl: prUrl
|
|
410
|
+
prUrl: prUrl,
|
|
410
411
|
});
|
|
411
412
|
await log('Error executing review:', error.message, { level: 'error' });
|
|
412
413
|
process.exit(1);
|
|
@@ -421,7 +422,7 @@ Review this pull request thoroughly.`;
|
|
|
421
422
|
reportError(cleanupError, {
|
|
422
423
|
context: 'cleanup_temp_dir',
|
|
423
424
|
level: 'warning',
|
|
424
|
-
tempDir
|
|
425
|
+
tempDir,
|
|
425
426
|
});
|
|
426
427
|
await log(' ā ļø (failed)');
|
|
427
428
|
}
|
|
@@ -430,4 +431,4 @@ Review this pull request thoroughly.`;
|
|
|
430
431
|
} else if (limitReached) {
|
|
431
432
|
await log(`\nš Keeping directory for future resume: ${tempDir}`);
|
|
432
433
|
}
|
|
433
|
-
}
|
|
434
|
+
}
|