@link-assistant/hive-mind 2.15.0 → 2.15.2
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 +40 -0
- package/package.json +1 -1
- package/src/git-auth-transport.lib.mjs +309 -0
- package/src/github-merge.lib.mjs +24 -28
- package/src/lib.mjs +7 -0
- package/src/merge-error-classification.lib.mjs +134 -0
- package/src/option-suggestions.lib.mjs +1 -0
- package/src/pr-draft-state.lib.mjs +96 -0
- package/src/review.mjs +6 -0
- package/src/solve.auto-merge-attempt.lib.mjs +42 -3
- package/src/solve.auto-merge-guards.lib.mjs +152 -0
- package/src/solve.auto-merge-helpers.lib.mjs +20 -1
- package/src/solve.auto-merge-preflight.lib.mjs +122 -0
- package/src/solve.auto-merge.lib.mjs +70 -84
- package/src/solve.config.lib.mjs +8 -0
- package/src/solve.interrupt.lib.mjs +18 -1
- package/src/solve.mjs +19 -0
- package/src/solve.repo-setup.lib.mjs +10 -0
- package/src/solve.repository.lib.mjs +26 -0
- package/src/solve.restart-shared.lib.mjs +302 -278
- package/src/solve.results.lib.mjs +6 -12
- package/src/solve.session.lib.mjs +15 -4
- package/src/telegram-merge-queue.lib.mjs +3 -1
- package/src/telegram-merge-wait.lib.mjs +7 -0
- package/src/transient-errors.lib.mjs +34 -3
|
@@ -159,12 +159,21 @@ export async function startWorkSession({ isContinueMode, prNumber, argv, log, fo
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
export async function endWorkSession({ isContinueMode, prNumber, argv, log, formatAligned, $, logsAttached = false }) {
|
|
162
|
-
// Post end work session comment and convert PR back to ready
|
|
162
|
+
// Post end work session comment and convert PR back to ready for review.
|
|
163
|
+
//
|
|
163
164
|
// Issue #2123: the ready conversion mirrors startWorkSession's draft conversion, so it must
|
|
164
165
|
// run for every continue-mode session, not only for --watch/--auto-continue ones.
|
|
165
|
-
|
|
166
|
+
//
|
|
167
|
+
// Issue #2182: the `isContinueMode` gate is deliberately gone from the ready conversion.
|
|
168
|
+
// A session that created the pull request itself (isContinueMode === false) can still leave
|
|
169
|
+
// it in draft — either because the AI opened it as a draft, or because an auto-restart
|
|
170
|
+
// iteration converted it — and hive-mind, not the AI, owns putting it back. In the reported
|
|
171
|
+
// run isContinueMode was false for the whole process, so this function did nothing and the
|
|
172
|
+
// PR stayed a draft while --auto-merge retried the merge 2692 times over 4d 12h.
|
|
173
|
+
// Session *comments* stay gated: they are about --watch/--auto-continue reporting, not state.
|
|
174
|
+
if (prNumber) {
|
|
166
175
|
const workEndTime = new Date();
|
|
167
|
-
const shouldPostSessionComment = argv.watch || argv.autoContinue;
|
|
176
|
+
const shouldPostSessionComment = isContinueMode && (argv.watch || argv.autoContinue);
|
|
168
177
|
await log(`\n${formatAligned('🏁', 'Ending work session:', workEndTime.toISOString())}`);
|
|
169
178
|
|
|
170
179
|
// Only post end comment if logs were NOT already attached
|
|
@@ -194,7 +203,9 @@ export async function endWorkSession({ isContinueMode, prNumber, argv, log, form
|
|
|
194
203
|
await log(formatAligned('ℹ️', 'Skipping:', 'End comment (logs already attached with session end message)', 2));
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
// Convert PR back to ready for review (issue #2123: shared implementation)
|
|
206
|
+
// Convert PR back to ready for review (issue #2123: shared implementation).
|
|
207
|
+
// This is the invariant of the whole draft/ready state machine: a finished working
|
|
208
|
+
// session never leaves a pull request in draft (issue #2182).
|
|
198
209
|
const { reportError } = await import('./sentry.lib.mjs');
|
|
199
210
|
await ensurePullRequestIsReady({
|
|
200
211
|
owner: global.owner,
|
|
@@ -460,7 +460,9 @@ export class MergeQueueProcessor {
|
|
|
460
460
|
}
|
|
461
461
|
const waitForReadyResult = await this.waitForPRReady(item, mergeableCheck);
|
|
462
462
|
if (!waitForReadyResult.success) {
|
|
463
|
-
|
|
463
|
+
// Issue #2182: 'draft' joins cancel/conflict as a skip, not a failure —
|
|
464
|
+
// the pull request author has to mark it ready for review first.
|
|
465
|
+
if (waitForReadyResult.status === 'cancelled' || waitForReadyResult.status === 'conflict' || waitForReadyResult.status === 'draft') {
|
|
464
466
|
item.status = MergeItemStatus.SKIPPED;
|
|
465
467
|
item.error = waitForReadyResult.error;
|
|
466
468
|
this.stats.skipped++;
|
|
@@ -45,6 +45,13 @@ export async function waitForPRReady(processor, item, initialCheck, options) {
|
|
|
45
45
|
return { success: true, status: 'ready', error: null };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Issue #2182: a draft pull request never becomes mergeable on its own —
|
|
49
|
+
// waiting for the full CI timeout only delays the queue. Skip it right away
|
|
50
|
+
// with the real reason instead of a generic "did not become mergeable".
|
|
51
|
+
if (latestCheck?.isDraft) {
|
|
52
|
+
return { success: false, status: 'draft', error: `PR #${item.pr.number} is a draft — mark it ready for review before merging` };
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
if (latestCheck?.reason === conflictSkipReason) {
|
|
49
56
|
return { success: false, status: 'conflict', error: conflictSkipReason };
|
|
50
57
|
}
|
|
@@ -75,11 +75,25 @@ export const GITHUB_SERVER_TRANSIENT_PATTERNS = Object.freeze([
|
|
|
75
75
|
*/
|
|
76
76
|
export const GIT_TRANSIENT_PATTERNS = Object.freeze(['unexpected disconnect', 'sideband', 'early eof', 'the remote end hung up', 'remote end hung up unexpectedly', 'rpc failed', 'fetch-pack', 'index-pack failed', 'transfer closed', 'unable to access', 'could not read from remote repository', 'failed to connect to github.com', 'operation timed out after', 'gnutls_handshake() failed', 'the requested url returned error: 5']);
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* GitHub throttling *anonymous* git downloads (issue #2192):
|
|
80
|
+
*
|
|
81
|
+
* fatal: remote error: GitHub is temporarily limiting some unauthenticated
|
|
82
|
+
* downloads to protect the stability of the platform. Please retry later or
|
|
83
|
+
* authenticate.
|
|
84
|
+
*
|
|
85
|
+
* Retryable — GitHub itself says "retry later" — but the *real* fix is to
|
|
86
|
+
* authenticate, which `src/git-auth-transport.lib.mjs` does before retrying.
|
|
87
|
+
* Kept as its own category so a run that hits this is never diagnosed as a
|
|
88
|
+
* generic network fault (the failing run reported "Unknown error" three times).
|
|
89
|
+
*/
|
|
90
|
+
export const ANONYMOUS_DOWNLOAD_LIMIT_PATTERNS = Object.freeze(['temporarily limiting some unauthenticated downloads', 'limiting some unauthenticated downloads', 'please retry later or authenticate', 'retry later or authenticate']);
|
|
91
|
+
|
|
78
92
|
/**
|
|
79
93
|
* Union used by the general-purpose `isTransientNetworkError` helpers. Kept as
|
|
80
94
|
* a single flat list so a caller cannot accidentally miss a category.
|
|
81
95
|
*/
|
|
82
|
-
export const ALL_TRANSIENT_PATTERNS = Object.freeze([...NETWORK_TRANSIENT_PATTERNS, ...GITHUB_SERVER_TRANSIENT_PATTERNS, ...GIT_TRANSIENT_PATTERNS]);
|
|
96
|
+
export const ALL_TRANSIENT_PATTERNS = Object.freeze([...NETWORK_TRANSIENT_PATTERNS, ...GITHUB_SERVER_TRANSIENT_PATTERNS, ...GIT_TRANSIENT_PATTERNS, ...ANONYMOUS_DOWNLOAD_LIMIT_PATTERNS]);
|
|
83
97
|
|
|
84
98
|
/**
|
|
85
99
|
* Pull every plausible string out of an error-ish value so pattern matches
|
|
@@ -126,6 +140,15 @@ const matchPattern = (error, patterns) => {
|
|
|
126
140
|
*/
|
|
127
141
|
export const isTransientNetworkError = error => matchPattern(error, ALL_TRANSIENT_PATTERNS) !== null;
|
|
128
142
|
|
|
143
|
+
/**
|
|
144
|
+
* True when `error` is GitHub refusing an *unauthenticated* git download.
|
|
145
|
+
* The remedy is to authenticate the transport, not merely to wait.
|
|
146
|
+
*
|
|
147
|
+
* @param {unknown} error
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
export const isAnonymousDownloadLimit = error => matchPattern(error, ANONYMOUS_DOWNLOAD_LIMIT_PATTERNS) !== null;
|
|
151
|
+
|
|
129
152
|
/**
|
|
130
153
|
* True when `error` is a GitHub server-side fault (5xx or GraphQL internal).
|
|
131
154
|
* Narrower than `isTransientNetworkError` — used for logging/classification.
|
|
@@ -174,7 +197,7 @@ export const parseGitHubRequestId = error => {
|
|
|
174
197
|
* Full classification of a failure, for retry decisions *and* for diagnostics.
|
|
175
198
|
*
|
|
176
199
|
* @param {unknown} error
|
|
177
|
-
* @returns {{transient: boolean, category: 'network'|'github-server'|'git-transport'|null, matchedPattern: string|null, requestId: string|null, text: string}}
|
|
200
|
+
* @returns {{transient: boolean, category: 'network'|'github-server'|'git-transport'|'github-anonymous-rate-limit'|null, matchedPattern: string|null, requestId: string|null, text: string}}
|
|
178
201
|
*/
|
|
179
202
|
export const describeTransientError = error => {
|
|
180
203
|
const text = collectErrorText(error);
|
|
@@ -184,10 +207,16 @@ export const describeTransientError = error => {
|
|
|
184
207
|
const networkPattern = find(NETWORK_TRANSIENT_PATTERNS);
|
|
185
208
|
const serverPattern = find(GITHUB_SERVER_TRANSIENT_PATTERNS);
|
|
186
209
|
const gitPattern = find(GIT_TRANSIENT_PATTERNS);
|
|
210
|
+
const anonymousPattern = find(ANONYMOUS_DOWNLOAD_LIMIT_PATTERNS);
|
|
187
211
|
|
|
188
212
|
let category = null;
|
|
189
213
|
let matchedPattern = null;
|
|
190
|
-
if (
|
|
214
|
+
if (anonymousPattern) {
|
|
215
|
+
// Checked first: this failure has a specific remedy (authenticate) and its
|
|
216
|
+
// text also mentions timeouts/retries that the other lists could match.
|
|
217
|
+
category = 'github-anonymous-rate-limit';
|
|
218
|
+
matchedPattern = anonymousPattern;
|
|
219
|
+
} else if (networkPattern) {
|
|
191
220
|
category = 'network';
|
|
192
221
|
matchedPattern = networkPattern;
|
|
193
222
|
} else if (serverPattern) {
|
|
@@ -224,11 +253,13 @@ export const formatTransientDiagnostics = description => {
|
|
|
224
253
|
};
|
|
225
254
|
|
|
226
255
|
export default {
|
|
256
|
+
ANONYMOUS_DOWNLOAD_LIMIT_PATTERNS,
|
|
227
257
|
NETWORK_TRANSIENT_PATTERNS,
|
|
228
258
|
GITHUB_SERVER_TRANSIENT_PATTERNS,
|
|
229
259
|
GIT_TRANSIENT_PATTERNS,
|
|
230
260
|
ALL_TRANSIENT_PATTERNS,
|
|
231
261
|
collectErrorText,
|
|
262
|
+
isAnonymousDownloadLimit,
|
|
232
263
|
isTransientNetworkError,
|
|
233
264
|
isGitHubServerError,
|
|
234
265
|
matchTransientPattern,
|