@link-assistant/hive-mind 1.74.4 → 1.74.5
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.74.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c20c2ec: Stop auto-restart-until-mergeable from restarting on CodeRabbit review quota/credit failures, and report them as Ready for review with skipped checks instead.
|
|
8
|
+
|
|
3
9
|
## 1.74.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helpers for external review services whose checks fail because the service
|
|
5
|
+
* could not run, not because repository code failed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EXTERNAL_REVIEW_SERVICE_PATTERNS = [/coderabbit/i, /code\s*rabbit/i];
|
|
9
|
+
|
|
10
|
+
const EXTERNAL_REVIEW_LIMIT_PATTERNS = [/insufficient\s+(?:review\s+)?credits?/i, /review\s+limit\s+reached/i, /rate\s+limit(?:ed|s)?/i, /run\s+out\s+of\s+usage\s+credits?/i, /out\s+of\s+usage\s+credits?/i, /usage\s+credits?\s+(?:exhausted|reached|insufficient)/i, /insufficient\s+(?:balance|limits?)/i, /no\s+(?:credits?|balance)\s+(?:left|remaining)/i];
|
|
11
|
+
|
|
12
|
+
const checkText = check => {
|
|
13
|
+
if (!check || typeof check !== 'object') return '';
|
|
14
|
+
return [check.name, check.context, check.description, check.summary, check.text, check.html_url, check.details_url].filter(Boolean).join('\n');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const isExternalReviewLimitCheck = check => {
|
|
18
|
+
const text = checkText(check);
|
|
19
|
+
if (!text) return false;
|
|
20
|
+
return EXTERNAL_REVIEW_SERVICE_PATTERNS.some(pattern => pattern.test(text)) && EXTERNAL_REVIEW_LIMIT_PATTERNS.some(pattern => pattern.test(text));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const splitExternalReviewLimitChecks = checks => {
|
|
24
|
+
const limitedChecks = [];
|
|
25
|
+
const actionableFailedChecks = [];
|
|
26
|
+
|
|
27
|
+
for (const check of checks || []) {
|
|
28
|
+
if (isExternalReviewLimitCheck(check)) {
|
|
29
|
+
limitedChecks.push(check);
|
|
30
|
+
} else {
|
|
31
|
+
actionableFailedChecks.push(check);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { limitedChecks, actionableFailedChecks };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const formatExternalReviewLimitCheck = check => {
|
|
39
|
+
const name = check?.name || check?.context || 'External review';
|
|
40
|
+
const description = check?.description && check.description !== name ? ` — ${check.description}` : '';
|
|
41
|
+
const url = check?.html_url || check?.details_url;
|
|
42
|
+
return `${name}${description}${url ? ` — ${url}` : ''}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const formatList = items => items.map(item => `- ${item}`).join('\n');
|
|
46
|
+
|
|
47
|
+
export const buildReadyForReviewComment = ({ blocker, ciStatus } = {}) => {
|
|
48
|
+
const skippedChecks = blocker?.details?.length ? blocker.details : (blocker?.checks || []).map(formatExternalReviewLimitCheck);
|
|
49
|
+
const skippedList = skippedChecks.length > 0 ? skippedChecks : ['External review check — blocked by service credits/rate limits'];
|
|
50
|
+
const passedChecks = (ciStatus?.passedChecks || []).map(formatExternalReviewLimitCheck);
|
|
51
|
+
const passedSection = passedChecks.length > 0 ? `\n\n**Checks completed successfully:**\n${formatList(passedChecks)}` : '';
|
|
52
|
+
|
|
53
|
+
return `## 🟡 Ready for review
|
|
54
|
+
|
|
55
|
+
Hive Mind stopped automatic restart because the remaining failed check is an external review quota/credit limit, not a code failure it can fix.
|
|
56
|
+
|
|
57
|
+
**Checks not executed:**
|
|
58
|
+
${formatList(skippedList)}${passedSection}
|
|
59
|
+
|
|
60
|
+
**Action required:**
|
|
61
|
+
- Restore the external review credits/rate limit and rerun the review, or decide manually whether this PR can proceed.
|
|
62
|
+
- No new AI session was started for this blocker.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
*Monitored by hive-mind with --auto-restart-until-mergeable flag.*`;
|
|
66
|
+
};
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -346,12 +346,14 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
|
|
|
346
346
|
status: check.status,
|
|
347
347
|
conclusion: check.conclusion,
|
|
348
348
|
type: 'check_run',
|
|
349
|
+
description: check.output?.title || check.output?.summary || null,
|
|
349
350
|
})),
|
|
350
351
|
...statuses.map(status => ({
|
|
351
352
|
name: status.context,
|
|
352
353
|
status: status.state === 'pending' ? 'in_progress' : 'completed',
|
|
353
354
|
conclusion: status.state === 'pending' ? null : status.state === 'success' ? 'success' : status.state === 'failure' ? 'failure' : status.state,
|
|
354
355
|
type: 'status',
|
|
356
|
+
description: status.description || null,
|
|
355
357
|
})),
|
|
356
358
|
];
|
|
357
359
|
|
|
@@ -375,7 +377,7 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
|
|
|
375
377
|
|
|
376
378
|
const hasPending = allChecks.some(c => c.status !== 'completed' || c.conclusion === null);
|
|
377
379
|
const allPassed = !hasPending && allChecks.every(c => c.conclusion === 'success' || c.conclusion === 'skipped' || c.conclusion === 'neutral');
|
|
378
|
-
const hasFailed = allChecks.some(c => c.conclusion === 'failure' || c.conclusion === 'cancelled' || c.conclusion === 'timed_out');
|
|
380
|
+
const hasFailed = allChecks.some(c => c.conclusion === 'failure' || c.conclusion === 'error' || c.conclusion === 'cancelled' || c.conclusion === 'timed_out');
|
|
379
381
|
|
|
380
382
|
let status;
|
|
381
383
|
if (hasPending) {
|
|
@@ -1126,6 +1128,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1126
1128
|
conclusion: check.conclusion, // success, failure, cancelled, timed_out, skipped, neutral, action_required, stale, null
|
|
1127
1129
|
type: 'check_run',
|
|
1128
1130
|
id: check.id,
|
|
1131
|
+
description: check.output?.title || check.output?.summary || null,
|
|
1129
1132
|
html_url: check.html_url || check.details_url || null,
|
|
1130
1133
|
})),
|
|
1131
1134
|
...statuses.map(status => ({
|
|
@@ -1134,6 +1137,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1134
1137
|
conclusion: status.state === 'pending' ? null : status.state === 'success' ? 'success' : status.state === 'failure' ? 'failure' : status.state,
|
|
1135
1138
|
type: 'status',
|
|
1136
1139
|
id: null,
|
|
1140
|
+
description: status.description || null,
|
|
1137
1141
|
html_url: status.target_url || null,
|
|
1138
1142
|
})),
|
|
1139
1143
|
];
|
|
@@ -1173,7 +1177,7 @@ export async function getDetailedCIStatus(owner, repo, prNumber, verbose = false
|
|
|
1173
1177
|
// neutral, action_required, stale, null (not yet completed)
|
|
1174
1178
|
// GitHub check run statuses include: queued, in_progress, completed, waiting, requested, pending
|
|
1175
1179
|
const passedChecks = allChecks.filter(c => c.conclusion === 'success' || c.conclusion === 'skipped' || c.conclusion === 'neutral');
|
|
1176
|
-
const failedChecks = allChecks.filter(c => c.conclusion === 'failure' || c.conclusion === 'timed_out' || c.conclusion === 'action_required');
|
|
1180
|
+
const failedChecks = allChecks.filter(c => c.conclusion === 'failure' || c.conclusion === 'error' || c.conclusion === 'timed_out' || c.conclusion === 'action_required');
|
|
1177
1181
|
const cancelledChecks = allChecks.filter(c => c.conclusion === 'cancelled');
|
|
1178
1182
|
const staleChecks = allChecks.filter(c => c.conclusion === 'stale');
|
|
1179
1183
|
const pendingChecks = allChecks.filter(c => (c.status === 'in_progress' || c.status === 'waiting' || c.status === 'requested' || c.status === 'pending') && c.conclusion === null);
|
|
@@ -78,6 +78,9 @@ const formatRunLine = run => {
|
|
|
78
78
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
79
79
|
const { SESSION_ENDING_MARKERS, isToolGeneratedComment, isToolTrackedCommentId, trackToolCommentId } = toolComments;
|
|
80
80
|
|
|
81
|
+
const externalReviewLimitLib = await import('./external-review-limit.lib.mjs');
|
|
82
|
+
const { formatExternalReviewLimitCheck, splitExternalReviewLimitChecks } = externalReviewLimitLib;
|
|
83
|
+
|
|
81
84
|
/**
|
|
82
85
|
* Issue #1323: Check if a comment with specific content already exists on the PR
|
|
83
86
|
* This prevents duplicate status comments when multiple processes or restarts occur
|
|
@@ -798,11 +801,22 @@ export const getMergeBlockers = async (owner, repo, prNumber, verbose = false, c
|
|
|
798
801
|
sha: ciStatus.sha,
|
|
799
802
|
});
|
|
800
803
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
804
|
+
const { limitedChecks, actionableFailedChecks } = splitExternalReviewLimitChecks(ciStatus.failedChecks);
|
|
805
|
+
if (limitedChecks.length > 0) {
|
|
806
|
+
blockers.push({
|
|
807
|
+
type: 'external_review_limit',
|
|
808
|
+
message: 'External review check was not executed because credits/rate limits are exhausted',
|
|
809
|
+
details: limitedChecks.map(formatExternalReviewLimitCheck),
|
|
810
|
+
checks: limitedChecks,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
if (actionableFailedChecks.length > 0) {
|
|
814
|
+
blockers.push({
|
|
815
|
+
type: 'ci_failure',
|
|
816
|
+
message: 'CI/CD checks are failing',
|
|
817
|
+
details: actionableFailedChecks.map(c => c.name),
|
|
818
|
+
});
|
|
819
|
+
}
|
|
806
820
|
}
|
|
807
821
|
} else if (ciStatus.status === 'unknown') {
|
|
808
822
|
// Unable to determine CI status - treat as pending to be safe
|
|
@@ -61,7 +61,10 @@ const { buildCancelledCIReviewComment, getRetriggerableWorkflowRuns, shouldStopF
|
|
|
61
61
|
|
|
62
62
|
// Issue #1625: Shared marker constants + posting/tracking helpers
|
|
63
63
|
const toolComments = await import('./tool-comments.lib.mjs');
|
|
64
|
-
const { READY_TO_MERGE_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
64
|
+
const { READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
|
|
65
|
+
|
|
66
|
+
const externalReviewLimitLib = await import('./external-review-limit.lib.mjs');
|
|
67
|
+
const { buildReadyForReviewComment } = externalReviewLimitLib;
|
|
65
68
|
|
|
66
69
|
// Issue #1728: Per-iteration working session summary attachment helper
|
|
67
70
|
// Issue #1763: Per-iteration PR ↔ issue link verification (so a clobbered
|
|
@@ -517,7 +520,44 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
|
|
|
517
520
|
|
|
518
521
|
// Reason 2: CI failures (only if NOT a billing limit issue and NOT just cancelled)
|
|
519
522
|
// Only restart AI when we have genuine code failures (real feedback to act on)
|
|
523
|
+
const externalReviewLimitBlocker = blockers.find(b => b.type === 'external_review_limit');
|
|
520
524
|
const ciBlocker = blockers.find(b => b.type === 'ci_failure');
|
|
525
|
+
const hasMergeConflictBlocker = blockers.some(b => b.type === 'not_mergeable' && b.message?.includes('conflicts'));
|
|
526
|
+
if (externalReviewLimitBlocker && !ciBlocker && !billingBlocker && !cancelledBlocker && !hasNewComments && !hasUncommittedChanges && !hasMergeConflictBlocker) {
|
|
527
|
+
await log('');
|
|
528
|
+
await log(formatAligned('🟡', 'READY FOR REVIEW', 'External review quota/credit limit requires human decision'));
|
|
529
|
+
for (const detail of externalReviewLimitBlocker.details || []) {
|
|
530
|
+
await log(formatAligned('', 'Check not executed:', detail, 2));
|
|
531
|
+
}
|
|
532
|
+
await log(formatAligned('', 'Action:', 'Stopping auto-restart without starting another AI session', 2));
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const commentSignature = `## 🟡 ${READY_FOR_REVIEW_MARKER}`;
|
|
536
|
+
const hasExistingReadyForReviewComment = await checkForExistingComment(owner, repo, prNumber, commentSignature, argv.verbose);
|
|
537
|
+
if (hasExistingReadyForReviewComment) {
|
|
538
|
+
await log(formatAligned('', `Skipping duplicate "${READY_FOR_REVIEW_MARKER}" comment (already posted by another process)`, '', 2));
|
|
539
|
+
} else {
|
|
540
|
+
const commentBody = buildReadyForReviewComment({
|
|
541
|
+
blocker: externalReviewLimitBlocker,
|
|
542
|
+
ciStatus,
|
|
543
|
+
});
|
|
544
|
+
await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: commentBody });
|
|
545
|
+
await log(formatAligned('', '💬 Posted ready-for-review notification to PR', '', 2));
|
|
546
|
+
}
|
|
547
|
+
} catch (commentError) {
|
|
548
|
+
reportError(commentError, {
|
|
549
|
+
context: 'post_external_review_limit_comment',
|
|
550
|
+
owner,
|
|
551
|
+
repo,
|
|
552
|
+
prNumber,
|
|
553
|
+
operation: 'comment_on_pr',
|
|
554
|
+
});
|
|
555
|
+
await log(formatAligned('', '⚠️ Could not post ready-for-review comment to PR', '', 2));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return { success: false, reason: 'external_review_limit', latestSessionId, latestAnthropicCost };
|
|
559
|
+
}
|
|
560
|
+
|
|
521
561
|
if (ciBlocker && !billingBlocker) {
|
|
522
562
|
shouldRestart = true;
|
|
523
563
|
restartReason = restartReason ? `${restartReason}; CI failures` : 'CI failures detected';
|
|
@@ -49,6 +49,9 @@ export const AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER = 'Auto-restart-until-merge
|
|
|
49
49
|
// solve.auto-merge.lib.mjs — "ready to merge" status comments
|
|
50
50
|
export const READY_TO_MERGE_MARKER = 'Ready to merge';
|
|
51
51
|
|
|
52
|
+
// solve.auto-merge.lib.mjs — external review quota/credit stop comments
|
|
53
|
+
export const READY_FOR_REVIEW_MARKER = 'Ready for review';
|
|
54
|
+
|
|
52
55
|
// solve.auto-merge.lib.mjs — "auto-merged successfully" status comments
|
|
53
56
|
export const AUTO_MERGED_MARKER = 'Auto-merged';
|
|
54
57
|
|
|
@@ -107,7 +110,7 @@ export const USAGE_LIMIT_REACHED_MARKER = 'Usage Limit Reached';
|
|
|
107
110
|
* named constants above so that adding a new marker only requires adding
|
|
108
111
|
* the constant and appending it here.
|
|
109
112
|
*/
|
|
110
|
-
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_AUTOMATION_MARKER];
|
|
113
|
+
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, READY_FOR_REVIEW_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_AUTOMATION_MARKER];
|
|
111
114
|
|
|
112
115
|
/**
|
|
113
116
|
* Markers that indicate the end of a working session. Used by
|