@link-assistant/hive-mind 2.11.13 → 2.12.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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
|
@@ -15,14 +15,12 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @see https://github.com/link-assistant/hive-mind/issues/1143
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
18
|
import { getAllReadyPRs, checkPRCIStatus, checkPRMergeable, mergePullRequest, waitForCI, ensureReadyLabel, waitForBranchCI, getDefaultBranch, waitForCommitCI, checkBranchCIHealth, getMergeCommitSha, getPRStatus, syncReadyTags, closeLinkedIssueIfNotAutoClosed } from './github-merge.lib.mjs';
|
|
20
19
|
import { resolveMergeTargetItems } from './github-merge-targets.lib.mjs';
|
|
21
20
|
import { waitForPRReady as waitForPRReadyHelper } from './telegram-merge-wait.lib.mjs';
|
|
22
21
|
import { mergeQueue as mergeQueueConfig } from './config.lib.mjs';
|
|
23
22
|
import { getProgressBar } from './limits.lib.mjs';
|
|
24
23
|
import { cancellableSleep as cancellableSleepUntil } from './interruptible-sleep.lib.mjs';
|
|
25
|
-
|
|
26
24
|
/**
|
|
27
25
|
* Status enum for merge queue operations
|
|
28
26
|
*/
|
|
@@ -34,7 +32,6 @@ export const MergeStatus = {
|
|
|
34
32
|
FAILED: 'failed',
|
|
35
33
|
CANCELLED: 'cancelled',
|
|
36
34
|
};
|
|
37
|
-
|
|
38
35
|
/**
|
|
39
36
|
* Status enum for individual merge items
|
|
40
37
|
*/
|
|
@@ -48,14 +45,10 @@ export const MergeItemStatus = {
|
|
|
48
45
|
MERGED: 'merged',
|
|
49
46
|
FAILED: 'failed',
|
|
50
47
|
SKIPPED: 'skipped',
|
|
51
|
-
// Issue #1805: states reached during the post-queue `--auto-resolve` pass.
|
|
52
|
-
// RESOLVING is set while a `/solve <pr> --auto-merge` session is being
|
|
53
|
-
// spawned for a previously-skipped PR; RESOLVE_FAILED records that the
|
|
54
|
-
// spawn (or the resolution itself) didn't succeed.
|
|
48
|
+
// Issue #1805: states reached during the post-queue `--auto-resolve` pass. RESOLVING is set while a `/solve <pr> --auto-merge` session is being spawned for a previously-skipped PR; RESOLVE_FAILED records that the spawn (or the resolution itself) didn't succeed.
|
|
55
49
|
RESOLVING: 'resolving',
|
|
56
50
|
RESOLVE_FAILED: 'resolve_failed',
|
|
57
51
|
};
|
|
58
|
-
|
|
59
52
|
/**
|
|
60
53
|
* Marker that identifies SKIPPED items that the auto-resolve pass should
|
|
61
54
|
* pick up. The same string is returned by `checkPRMergeable()` for
|
|
@@ -65,7 +58,6 @@ export const MergeItemStatus = {
|
|
|
65
58
|
* @see https://github.com/link-assistant/hive-mind/issues/1805
|
|
66
59
|
*/
|
|
67
60
|
export const MERGE_CONFLICT_SKIP_REASON = 'PR has merge conflicts';
|
|
68
|
-
|
|
69
61
|
/**
|
|
70
62
|
* Configuration for merge queue operations
|
|
71
63
|
* Values are loaded from config.lib.mjs which supports environment variable overrides.
|
|
@@ -81,39 +73,28 @@ export const MERGE_QUEUE_CONFIG = {
|
|
|
81
73
|
// CI/CD wait settings - check every 5 minutes per issue #1143 feedback
|
|
82
74
|
CI_POLL_INTERVAL_MS: mergeQueueConfig.ciPollIntervalMs,
|
|
83
75
|
CI_TIMEOUT_MS: mergeQueueConfig.ciTimeoutMs,
|
|
84
|
-
|
|
85
76
|
// Post-merge wait settings
|
|
86
77
|
POST_MERGE_WAIT_MS: mergeQueueConfig.postMergeWaitMs,
|
|
87
|
-
|
|
88
78
|
// Telegram message update interval - same as CI polling
|
|
89
79
|
MESSAGE_UPDATE_INTERVAL_MS: mergeQueueConfig.ciPollIntervalMs,
|
|
90
|
-
|
|
91
80
|
// Maximum PRs to process in one session (configurable, default 10)
|
|
92
81
|
MAX_PRS_PER_SESSION: mergeQueueConfig.maxPrsPerSession,
|
|
93
|
-
|
|
94
|
-
// Merge method: 'merge', 'squash', or 'rebase' (Issue #1269)
|
|
95
|
-
// gh pr merge requires explicit method when running non-interactively
|
|
82
|
+
// Merge method: 'merge', 'squash', or 'rebase' (Issue #1269) gh pr merge requires explicit method when running non-interactively
|
|
96
83
|
MERGE_METHOD: mergeQueueConfig.mergeMethod,
|
|
97
|
-
|
|
98
84
|
// Issue #1307: Wait for target branch CI before first merge
|
|
99
85
|
WAIT_FOR_TARGET_BRANCH_CI: mergeQueueConfig.waitForTargetBranchCI,
|
|
100
86
|
TARGET_BRANCH_CI_TIMEOUT_MS: mergeQueueConfig.targetBranchCITimeoutMs,
|
|
101
87
|
TARGET_BRANCH_CI_POLL_INTERVAL_MS: mergeQueueConfig.targetBranchCIPollIntervalMs,
|
|
102
|
-
|
|
103
88
|
// Issue #1341: Wait for post-merge CI to complete before merging next PR
|
|
104
89
|
WAIT_FOR_POST_MERGE_CI: mergeQueueConfig.waitForPostMergeCI,
|
|
105
90
|
STOP_ON_POST_MERGE_CI_FAILURE: mergeQueueConfig.stopOnPostMergeCIFailure,
|
|
106
91
|
CHECK_BRANCH_CI_HEALTH_BEFORE_START: mergeQueueConfig.checkBranchCIHealthBeforeStart,
|
|
107
92
|
POST_MERGE_CI_TIMEOUT_MS: mergeQueueConfig.postMergeCITimeoutMs,
|
|
108
93
|
POST_MERGE_CI_POLL_INTERVAL_MS: mergeQueueConfig.postMergeCIPollIntervalMs,
|
|
109
|
-
|
|
110
|
-
// Issue #1807: Sequential auto-resolve — wait for each `/solve --auto-merge`
|
|
111
|
-
// session to land its PR (or fail) before spawning the next one. These
|
|
112
|
-
// timeouts apply to the polling loop in `waitForAutoResolveCompletion`.
|
|
94
|
+
// Issue #1807: Sequential auto-resolve — wait for each `/solve --auto-merge` session to land its PR (or fail) before spawning the next one. These timeouts apply to the polling loop in `waitForAutoResolveCompletion`.
|
|
113
95
|
AUTO_RESOLVE_WAIT_TIMEOUT_MS: mergeQueueConfig.autoResolveWaitTimeoutMs,
|
|
114
96
|
AUTO_RESOLVE_POLL_INTERVAL_MS: mergeQueueConfig.autoResolvePollIntervalMs,
|
|
115
97
|
};
|
|
116
|
-
|
|
117
98
|
/**
|
|
118
99
|
* Merge queue item representing a PR to merge
|
|
119
100
|
*/
|
|
@@ -130,7 +111,6 @@ class MergeQueueItem {
|
|
|
130
111
|
// Issue #1341: Track merge commit SHA for post-merge CI waiting
|
|
131
112
|
this.mergeCommitSha = null;
|
|
132
113
|
}
|
|
133
|
-
|
|
134
114
|
/**
|
|
135
115
|
* Get a display-friendly description
|
|
136
116
|
*/
|
|
@@ -138,7 +118,6 @@ class MergeQueueItem {
|
|
|
138
118
|
const issueRef = this.issue ? ` (Issue #${this.issue.number})` : '';
|
|
139
119
|
return `PR #${this.pr.number}: ${this.pr.title}${issueRef}`;
|
|
140
120
|
}
|
|
141
|
-
|
|
142
121
|
/**
|
|
143
122
|
* Get emoji for current status
|
|
144
123
|
*/
|
|
@@ -172,7 +151,6 @@ class MergeQueueItem {
|
|
|
172
151
|
}
|
|
173
152
|
}
|
|
174
153
|
}
|
|
175
|
-
|
|
176
154
|
/**
|
|
177
155
|
* Merge Queue Processor
|
|
178
156
|
* Handles sequential merging of PRs with CI/CD monitoring
|
|
@@ -193,7 +171,6 @@ export class MergeQueueProcessor {
|
|
|
193
171
|
this.spawnSolveSession = typeof options.spawnSolveSession === 'function' ? options.spawnSolveSession : null;
|
|
194
172
|
this.target = options.target || { mode: 'repository', owner: this.owner, repo: this.repo, url: `https://github.com/${this.owner}/${this.repo}` };
|
|
195
173
|
this.waitForUnfinished = options.waitForUnfinished !== false;
|
|
196
|
-
|
|
197
174
|
// State
|
|
198
175
|
this.items = [];
|
|
199
176
|
this.currentIndex = 0;
|
|
@@ -227,7 +204,6 @@ export class MergeQueueProcessor {
|
|
|
227
204
|
this.getAllReadyPRs = typeof options.getAllReadyPRs === 'function' ? options.getAllReadyPRs : getAllReadyPRs;
|
|
228
205
|
this.targetItemsTimeoutMs = options.targetItemsTimeoutMs ?? MERGE_QUEUE_CONFIG.CI_TIMEOUT_MS;
|
|
229
206
|
this.targetItemsPollIntervalMs = options.targetItemsPollIntervalMs ?? MERGE_QUEUE_CONFIG.CI_POLL_INTERVAL_MS;
|
|
230
|
-
|
|
231
207
|
// Statistics
|
|
232
208
|
this.stats = {
|
|
233
209
|
total: 0,
|
|
@@ -241,7 +217,6 @@ export class MergeQueueProcessor {
|
|
|
241
217
|
autoResolveFailed: 0,
|
|
242
218
|
};
|
|
243
219
|
}
|
|
244
|
-
|
|
245
220
|
/**
|
|
246
221
|
* Log message if verbose mode is enabled
|
|
247
222
|
*/
|
|
@@ -250,7 +225,6 @@ export class MergeQueueProcessor {
|
|
|
250
225
|
console.log(`[VERBOSE] /merge-queue: ${message}`);
|
|
251
226
|
}
|
|
252
227
|
}
|
|
253
|
-
|
|
254
228
|
/**
|
|
255
229
|
* Initialize the merge queue by fetching ready PRs
|
|
256
230
|
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
@@ -258,7 +232,6 @@ export class MergeQueueProcessor {
|
|
|
258
232
|
async initialize() {
|
|
259
233
|
try {
|
|
260
234
|
this.log(`Initializing merge queue for ${this.owner}/${this.repo}`);
|
|
261
|
-
|
|
262
235
|
// Ensure ready label exists
|
|
263
236
|
const labelResult = await this.ensureReadyLabel(this.owner, this.repo, this.verbose);
|
|
264
237
|
if (!labelResult.success) {
|
|
@@ -267,7 +240,6 @@ export class MergeQueueProcessor {
|
|
|
267
240
|
if (labelResult.created) {
|
|
268
241
|
this.log("Created 'ready' label in repository");
|
|
269
242
|
}
|
|
270
|
-
|
|
271
243
|
let readyPRs;
|
|
272
244
|
if (this.target?.mode && this.target.mode !== 'repository') {
|
|
273
245
|
readyPRs = await this.resolveMergeTargetItemsWithWait();
|
|
@@ -281,25 +253,19 @@ export class MergeQueueProcessor {
|
|
|
281
253
|
if (syncResult.errors > 0) {
|
|
282
254
|
this.log(`Tag sync had ${syncResult.errors} error(s) (non-fatal, proceeding)`);
|
|
283
255
|
}
|
|
284
|
-
|
|
285
256
|
// Fetch all ready PRs
|
|
286
257
|
readyPRs = await this.getAllReadyPRs(this.owner, this.repo, this.verbose);
|
|
287
258
|
}
|
|
288
|
-
|
|
289
259
|
if (readyPRs.length === 0) {
|
|
290
260
|
const message = this.target?.mode === 'issue' ? `No open PRs linked to issue #${this.target.issueNumber} found` : this.target?.mode === 'pull' ? `Pull request #${this.target.prNumber} was not found` : "No PRs with 'ready' label found";
|
|
291
261
|
return { success: true, error: null, message };
|
|
292
262
|
}
|
|
293
|
-
|
|
294
263
|
// Limit to max PRs per session
|
|
295
264
|
const limitedPRs = readyPRs.slice(0, MERGE_QUEUE_CONFIG.MAX_PRS_PER_SESSION);
|
|
296
|
-
|
|
297
265
|
// Create queue items
|
|
298
266
|
this.items = limitedPRs.map(pr => new MergeQueueItem(pr));
|
|
299
267
|
this.stats.total = this.items.length;
|
|
300
|
-
|
|
301
268
|
this.log(`Initialized with ${this.items.length} PRs to merge`);
|
|
302
|
-
|
|
303
269
|
return {
|
|
304
270
|
success: true,
|
|
305
271
|
error: null,
|
|
@@ -312,7 +278,6 @@ export class MergeQueueProcessor {
|
|
|
312
278
|
return { success: false, error: error.message };
|
|
313
279
|
}
|
|
314
280
|
}
|
|
315
|
-
|
|
316
281
|
/**
|
|
317
282
|
* Start processing the merge queue
|
|
318
283
|
* @returns {Promise<{success: boolean, stats: Object, error: string|null}>}
|
|
@@ -321,15 +286,12 @@ export class MergeQueueProcessor {
|
|
|
321
286
|
if (this.status === MergeStatus.RUNNING) {
|
|
322
287
|
return { success: false, stats: this.stats, error: 'Queue is already running' };
|
|
323
288
|
}
|
|
324
|
-
|
|
325
289
|
if (this.items.length === 0) {
|
|
326
290
|
return { success: true, stats: this.stats, error: null };
|
|
327
291
|
}
|
|
328
|
-
|
|
329
292
|
this.status = MergeStatus.RUNNING;
|
|
330
293
|
this.startedAt = new Date();
|
|
331
294
|
this.isCancelled = false;
|
|
332
|
-
|
|
333
295
|
try {
|
|
334
296
|
// Issue #1341: Check if the default branch has any failed CI runs before starting
|
|
335
297
|
// This prevents merging on top of a broken branch
|
|
@@ -341,11 +303,9 @@ export class MergeQueueProcessor {
|
|
|
341
303
|
this.completedAt = new Date();
|
|
342
304
|
// Store the failed runs for the error report
|
|
343
305
|
this.branchCIFailedRuns = healthCheckResult.failedRuns;
|
|
344
|
-
|
|
345
306
|
if (this.onError) {
|
|
346
307
|
await this.onError(new Error(healthCheckResult.error));
|
|
347
308
|
}
|
|
348
|
-
|
|
349
309
|
return {
|
|
350
310
|
success: false,
|
|
351
311
|
stats: this.stats,
|
|
@@ -353,34 +313,28 @@ export class MergeQueueProcessor {
|
|
|
353
313
|
};
|
|
354
314
|
}
|
|
355
315
|
}
|
|
356
|
-
|
|
357
316
|
// Issue #1307: Wait for any active CI runs on the target branch before processing
|
|
358
317
|
// This prevents merging while post-merge CI from previous merges is still running
|
|
359
318
|
if (MERGE_QUEUE_CONFIG.WAIT_FOR_TARGET_BRANCH_CI) {
|
|
360
319
|
await this.waitForTargetBranchCI();
|
|
361
320
|
}
|
|
362
|
-
|
|
363
321
|
// Process each PR sequentially
|
|
364
322
|
for (this.currentIndex = 0; this.currentIndex < this.items.length; this.currentIndex++) {
|
|
365
323
|
if (this.isCancelled) {
|
|
366
324
|
this.status = MergeStatus.CANCELLED;
|
|
367
325
|
break;
|
|
368
326
|
}
|
|
369
|
-
|
|
370
327
|
const item = this.items[this.currentIndex];
|
|
371
328
|
await this.processItem(item);
|
|
372
|
-
|
|
373
329
|
// Report progress
|
|
374
330
|
if (this.onProgress) {
|
|
375
331
|
await this.onProgress(this.getProgressUpdate());
|
|
376
332
|
}
|
|
377
|
-
|
|
378
333
|
// Issue #1341: If merged successfully, wait for post-merge CI to complete
|
|
379
334
|
// This ensures each PR's CI completes (including releases) before merging the next
|
|
380
335
|
if (item.status === MergeItemStatus.MERGED && this.currentIndex < this.items.length - 1) {
|
|
381
336
|
if (MERGE_QUEUE_CONFIG.WAIT_FOR_POST_MERGE_CI && item.mergeCommitSha) {
|
|
382
337
|
const postMergeCIResult = await this.waitForPostMergeCI(item);
|
|
383
|
-
|
|
384
338
|
// Issue #1341: Stop the queue if post-merge CI failed
|
|
385
339
|
if (!postMergeCIResult.success && MERGE_QUEUE_CONFIG.STOP_ON_POST_MERGE_CI_FAILURE) {
|
|
386
340
|
this.status = MergeStatus.FAILED;
|
|
@@ -388,11 +342,9 @@ export class MergeQueueProcessor {
|
|
|
388
342
|
this.completedAt = new Date();
|
|
389
343
|
// Store the failed runs for the error report
|
|
390
344
|
this.postMergeCIFailedRuns = postMergeCIResult.failedRuns;
|
|
391
|
-
|
|
392
345
|
if (this.onError) {
|
|
393
346
|
await this.onError(new Error(postMergeCIResult.error));
|
|
394
347
|
}
|
|
395
|
-
|
|
396
348
|
return {
|
|
397
349
|
success: false,
|
|
398
350
|
stats: this.stats,
|
|
@@ -406,7 +358,6 @@ export class MergeQueueProcessor {
|
|
|
406
358
|
}
|
|
407
359
|
}
|
|
408
360
|
}
|
|
409
|
-
|
|
410
361
|
// Issue #1805: After the normal queue settles, optionally hand off
|
|
411
362
|
// every PR that was SKIPPED with a merge-conflict reason to the
|
|
412
363
|
// `/solve <pr> --auto-merge` flow. This lets a single `/merge`
|
|
@@ -415,14 +366,11 @@ export class MergeQueueProcessor {
|
|
|
415
366
|
if (this.autoResolve && !this.isCancelled) {
|
|
416
367
|
await this.runAutoResolve();
|
|
417
368
|
}
|
|
418
|
-
|
|
419
369
|
this.completedAt = new Date();
|
|
420
370
|
this.status = this.isCancelled ? MergeStatus.CANCELLED : MergeStatus.COMPLETED;
|
|
421
|
-
|
|
422
371
|
if (this.onComplete) {
|
|
423
372
|
await this.onComplete(this.getFinalReport());
|
|
424
373
|
}
|
|
425
|
-
|
|
426
374
|
return {
|
|
427
375
|
success: true,
|
|
428
376
|
stats: this.stats,
|
|
@@ -432,11 +380,9 @@ export class MergeQueueProcessor {
|
|
|
432
380
|
this.status = MergeStatus.FAILED;
|
|
433
381
|
this.error = error.message;
|
|
434
382
|
this.completedAt = new Date();
|
|
435
|
-
|
|
436
383
|
if (this.onError) {
|
|
437
384
|
await this.onError(error);
|
|
438
385
|
}
|
|
439
|
-
|
|
440
386
|
return {
|
|
441
387
|
success: false,
|
|
442
388
|
stats: this.stats,
|
|
@@ -444,7 +390,6 @@ export class MergeQueueProcessor {
|
|
|
444
390
|
};
|
|
445
391
|
}
|
|
446
392
|
}
|
|
447
|
-
|
|
448
393
|
/**
|
|
449
394
|
* Resolve targeted PR data, waiting for issue-linked PRs that may be created
|
|
450
395
|
* shortly after a replied `/codex <issue>` message.
|
|
@@ -454,28 +399,22 @@ export class MergeQueueProcessor {
|
|
|
454
399
|
async resolveMergeTargetItemsWithWait() {
|
|
455
400
|
const maxPollInterval = Math.max(1, this.targetItemsPollIntervalMs || 1);
|
|
456
401
|
const maxAttempts = Math.max(1, Math.ceil((this.targetItemsTimeoutMs || 0) / maxPollInterval));
|
|
457
|
-
|
|
458
402
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
459
403
|
if (this.isCancelled) {
|
|
460
404
|
return [];
|
|
461
405
|
}
|
|
462
|
-
|
|
463
406
|
const items = (await this.resolveMergeTargetItems(this.target, this.verbose)) || [];
|
|
464
407
|
if (items.length > 0 || this.target?.mode !== 'issue') {
|
|
465
408
|
return items;
|
|
466
409
|
}
|
|
467
|
-
|
|
468
410
|
if (attempt === maxAttempts) {
|
|
469
411
|
return [];
|
|
470
412
|
}
|
|
471
|
-
|
|
472
413
|
this.log(`Waiting for a linked PR to appear for issue #${this.target.issueNumber}`);
|
|
473
414
|
await this.sleep(this.targetItemsPollIntervalMs);
|
|
474
415
|
}
|
|
475
|
-
|
|
476
416
|
return [];
|
|
477
417
|
}
|
|
478
|
-
|
|
479
418
|
async waitForPRReady(item, initialCheck) {
|
|
480
419
|
return waitForPRReadyHelper(this, item, initialCheck, {
|
|
481
420
|
MergeItemStatus,
|
|
@@ -484,7 +423,6 @@ export class MergeQueueProcessor {
|
|
|
484
423
|
pollIntervalMs: MERGE_QUEUE_CONFIG.CI_POLL_INTERVAL_MS,
|
|
485
424
|
});
|
|
486
425
|
}
|
|
487
|
-
|
|
488
426
|
/**
|
|
489
427
|
* Process a single merge queue item
|
|
490
428
|
* @param {MergeQueueItem} item
|
|
@@ -492,13 +430,11 @@ export class MergeQueueProcessor {
|
|
|
492
430
|
async processItem(item) {
|
|
493
431
|
this.log(`Processing ${item.getDescription()}`);
|
|
494
432
|
item.startedAt = new Date();
|
|
495
|
-
|
|
496
433
|
try {
|
|
497
434
|
// Step 1: Check if PR is mergeable
|
|
498
435
|
item.status = MergeItemStatus.CHECKING_CI;
|
|
499
436
|
// Issue #2072: pass cancellation down so the UNKNOWN-mergeability retry delay aborts early
|
|
500
437
|
const mergeableCheck = await this.checkPRMergeable(this.owner, this.repo, item.pr.number, this.verbose, { isCancelled: () => this.isCancelled });
|
|
501
|
-
|
|
502
438
|
// Issue #2072: a cancel during the mergeability check must skip the PR, not fail it.
|
|
503
439
|
if (mergeableCheck.cancelled || this.isCancelled) {
|
|
504
440
|
item.status = MergeItemStatus.SKIPPED;
|
|
@@ -507,7 +443,6 @@ export class MergeQueueProcessor {
|
|
|
507
443
|
this.log(`Skipped PR #${item.pr.number}: cancelled during mergeability check`);
|
|
508
444
|
return;
|
|
509
445
|
}
|
|
510
|
-
|
|
511
446
|
if (mergeableCheck.terminal) {
|
|
512
447
|
item.status = MergeItemStatus.FAILED;
|
|
513
448
|
item.error = mergeableCheck.reason || 'GitHub repository, pull request, issue, or branch is no longer accessible';
|
|
@@ -515,7 +450,6 @@ export class MergeQueueProcessor {
|
|
|
515
450
|
this.log(`Failed PR #${item.pr.number}: ${item.error}`);
|
|
516
451
|
return;
|
|
517
452
|
}
|
|
518
|
-
|
|
519
453
|
if (!mergeableCheck.mergeable) {
|
|
520
454
|
if (!this.waitForUnfinished || mergeableCheck.reason === MERGE_CONFLICT_SKIP_REASON) {
|
|
521
455
|
item.status = MergeItemStatus.SKIPPED;
|
|
@@ -524,7 +458,6 @@ export class MergeQueueProcessor {
|
|
|
524
458
|
this.log(`Skipped PR #${item.pr.number}: ${mergeableCheck.reason}`);
|
|
525
459
|
return;
|
|
526
460
|
}
|
|
527
|
-
|
|
528
461
|
const waitForReadyResult = await this.waitForPRReady(item, mergeableCheck);
|
|
529
462
|
if (!waitForReadyResult.success) {
|
|
530
463
|
if (waitForReadyResult.status === 'cancelled' || waitForReadyResult.status === 'conflict') {
|
|
@@ -534,7 +467,6 @@ export class MergeQueueProcessor {
|
|
|
534
467
|
this.log(`Skipped PR #${item.pr.number}: ${waitForReadyResult.error}`);
|
|
535
468
|
return;
|
|
536
469
|
}
|
|
537
|
-
|
|
538
470
|
item.status = MergeItemStatus.FAILED;
|
|
539
471
|
item.error = waitForReadyResult.error;
|
|
540
472
|
this.stats.failed++;
|
|
@@ -542,11 +474,9 @@ export class MergeQueueProcessor {
|
|
|
542
474
|
return;
|
|
543
475
|
}
|
|
544
476
|
}
|
|
545
|
-
|
|
546
477
|
// Step 2: Check CI status
|
|
547
478
|
const ciStatus = await this.checkPRCIStatus(this.owner, this.repo, item.pr.number, this.verbose);
|
|
548
479
|
item.ciStatus = ciStatus;
|
|
549
|
-
|
|
550
480
|
if (ciStatus.status === 'failure') {
|
|
551
481
|
item.status = MergeItemStatus.FAILED;
|
|
552
482
|
item.error = 'CI checks failed';
|
|
@@ -554,7 +484,6 @@ export class MergeQueueProcessor {
|
|
|
554
484
|
this.log(`Failed PR #${item.pr.number}: CI checks failed`);
|
|
555
485
|
return;
|
|
556
486
|
}
|
|
557
|
-
|
|
558
487
|
if (ciStatus.status === 'terminal_github_entity_error') {
|
|
559
488
|
item.status = MergeItemStatus.FAILED;
|
|
560
489
|
item.error = ciStatus.error || 'GitHub repository, pull request, issue, or branch is no longer accessible';
|
|
@@ -562,11 +491,9 @@ export class MergeQueueProcessor {
|
|
|
562
491
|
this.log(`Failed PR #${item.pr.number}: ${item.error}`);
|
|
563
492
|
return;
|
|
564
493
|
}
|
|
565
|
-
|
|
566
494
|
// Step 3: Wait for CI if pending
|
|
567
495
|
if (ciStatus.status === 'pending') {
|
|
568
496
|
item.status = MergeItemStatus.WAITING_CI;
|
|
569
|
-
|
|
570
497
|
const waitResult = await this.waitForCI(
|
|
571
498
|
this.owner,
|
|
572
499
|
this.repo,
|
|
@@ -585,7 +512,6 @@ export class MergeQueueProcessor {
|
|
|
585
512
|
},
|
|
586
513
|
this.verbose
|
|
587
514
|
);
|
|
588
|
-
|
|
589
515
|
if (!waitResult.success) {
|
|
590
516
|
// Issue #1407: If cancelled during CI wait, mark as skipped (not failed)
|
|
591
517
|
// so the queue can cleanly stop without misleading failure statistics
|
|
@@ -603,12 +529,10 @@ export class MergeQueueProcessor {
|
|
|
603
529
|
return;
|
|
604
530
|
}
|
|
605
531
|
}
|
|
606
|
-
|
|
607
532
|
// Step 4: Merge the PR
|
|
608
533
|
// Issue #1269: Pass the configured merge method to prevent "not running interactively" error
|
|
609
534
|
item.status = MergeItemStatus.MERGING;
|
|
610
535
|
const mergeResult = await this.mergePullRequest(this.owner, this.repo, item.pr.number, { mergeMethod: MERGE_QUEUE_CONFIG.MERGE_METHOD }, this.verbose);
|
|
611
|
-
|
|
612
536
|
if (!mergeResult.success) {
|
|
613
537
|
item.status = MergeItemStatus.FAILED;
|
|
614
538
|
item.error = mergeResult.error;
|
|
@@ -616,13 +540,11 @@ export class MergeQueueProcessor {
|
|
|
616
540
|
this.log(`Failed to merge PR #${item.pr.number}: ${mergeResult.error}`);
|
|
617
541
|
return;
|
|
618
542
|
}
|
|
619
|
-
|
|
620
543
|
// Success!
|
|
621
544
|
item.status = MergeItemStatus.MERGED;
|
|
622
545
|
item.completedAt = new Date();
|
|
623
546
|
this.stats.merged++;
|
|
624
547
|
this.log(`Successfully merged PR #${item.pr.number}`);
|
|
625
|
-
|
|
626
548
|
// Issue #1895: GitHub does not auto-close linked issues for PRs merged into
|
|
627
549
|
// a non-default branch. Close the linked issue explicitly in that case.
|
|
628
550
|
try {
|
|
@@ -633,7 +555,6 @@ export class MergeQueueProcessor {
|
|
|
633
555
|
} catch (closeError) {
|
|
634
556
|
this.log(`Could not close linked issue for PR #${item.pr.number}: ${closeError.message}`);
|
|
635
557
|
}
|
|
636
|
-
|
|
637
558
|
// Issue #1341: Get the merge commit SHA for post-merge CI tracking
|
|
638
559
|
// Need a small delay to allow GitHub to update the PR state
|
|
639
560
|
await this.sleep(5000);
|
|
@@ -657,7 +578,6 @@ export class MergeQueueProcessor {
|
|
|
657
578
|
this.log(`Error processing PR #${item.pr.number}: ${error.message}`);
|
|
658
579
|
}
|
|
659
580
|
}
|
|
660
|
-
|
|
661
581
|
/**
|
|
662
582
|
* Cancel the merge queue operation
|
|
663
583
|
*/
|
|
@@ -665,7 +585,6 @@ export class MergeQueueProcessor {
|
|
|
665
585
|
this.isCancelled = true;
|
|
666
586
|
this.log('Cancellation requested');
|
|
667
587
|
}
|
|
668
|
-
|
|
669
588
|
/**
|
|
670
589
|
* Issue #1805: Return queue items that were skipped because of merge
|
|
671
590
|
* conflicts. These are the candidates the auto-resolve pass hands off
|
|
@@ -676,7 +595,6 @@ export class MergeQueueProcessor {
|
|
|
676
595
|
getConflictedItems() {
|
|
677
596
|
return this.items.filter(item => item.status === MergeItemStatus.SKIPPED && item.error === MERGE_CONFLICT_SKIP_REASON);
|
|
678
597
|
}
|
|
679
|
-
|
|
680
598
|
/**
|
|
681
599
|
* Issue #1805 / #1807: Iterate every conflict-skipped item and hand it off
|
|
682
600
|
* to a `/solve <pr-url> --auto-merge` session via the injected
|
|
@@ -698,7 +616,6 @@ export class MergeQueueProcessor {
|
|
|
698
616
|
this.log('Auto-resolve: no merge-conflict skips to process');
|
|
699
617
|
return;
|
|
700
618
|
}
|
|
701
|
-
|
|
702
619
|
if (!this.spawnSolveSession) {
|
|
703
620
|
// Guard against misconfiguration — the queue can't resolve without a
|
|
704
621
|
// spawner. Surface this to the user via the same channel as other
|
|
@@ -714,7 +631,6 @@ export class MergeQueueProcessor {
|
|
|
714
631
|
}
|
|
715
632
|
return;
|
|
716
633
|
}
|
|
717
|
-
|
|
718
634
|
this.autoResolveActive = true;
|
|
719
635
|
this.log(`Auto-resolve: dispatching ${conflicted.length} conflict PR(s) sequentially to /solve --auto-merge`);
|
|
720
636
|
try {
|
|
@@ -723,7 +639,6 @@ export class MergeQueueProcessor {
|
|
|
723
639
|
this.log('Auto-resolve: cancelled mid-pass');
|
|
724
640
|
break;
|
|
725
641
|
}
|
|
726
|
-
|
|
727
642
|
item.status = MergeItemStatus.RESOLVING;
|
|
728
643
|
this.autoResolveCurrent = item.pr.number;
|
|
729
644
|
this.autoResolvePhase = 'spawning';
|
|
@@ -731,7 +646,6 @@ export class MergeQueueProcessor {
|
|
|
731
646
|
if (this.onProgress) {
|
|
732
647
|
await this.onProgress(this.getProgressUpdate());
|
|
733
648
|
}
|
|
734
|
-
|
|
735
649
|
// Step 1 — spawn the solve session.
|
|
736
650
|
let spawned = false;
|
|
737
651
|
try {
|
|
@@ -742,7 +656,6 @@ export class MergeQueueProcessor {
|
|
|
742
656
|
prNumber: item.pr.number,
|
|
743
657
|
title: item.pr.title,
|
|
744
658
|
});
|
|
745
|
-
|
|
746
659
|
if (result && result.success) {
|
|
747
660
|
item.autoResolveSession = result.sessionName || result.session || null;
|
|
748
661
|
this.log(`Auto-resolve: spawned solve session for PR #${item.pr.number}${item.autoResolveSession ? ` (session ${item.autoResolveSession})` : ''}`);
|
|
@@ -759,7 +672,6 @@ export class MergeQueueProcessor {
|
|
|
759
672
|
this.stats.autoResolveFailed++;
|
|
760
673
|
console.error(`[ERROR] /merge-queue: auto-resolve error for PR #${item.pr.number}: ${item.autoResolveError}`);
|
|
761
674
|
}
|
|
762
|
-
|
|
763
675
|
if (!spawned) {
|
|
764
676
|
this.autoResolvePhase = null;
|
|
765
677
|
this.autoResolveWaitStartedAt = null;
|
|
@@ -768,7 +680,6 @@ export class MergeQueueProcessor {
|
|
|
768
680
|
}
|
|
769
681
|
continue;
|
|
770
682
|
}
|
|
771
|
-
|
|
772
683
|
// Step 2 — wait for the spawned session to actually land (or fail)
|
|
773
684
|
// before starting the next one. This is the heart of issue #1807.
|
|
774
685
|
this.autoResolvePhase = 'awaiting-resolution';
|
|
@@ -776,9 +687,7 @@ export class MergeQueueProcessor {
|
|
|
776
687
|
if (this.onProgress) {
|
|
777
688
|
await this.onProgress(this.getProgressUpdate());
|
|
778
689
|
}
|
|
779
|
-
|
|
780
690
|
const waitResult = await this.waitForAutoResolveCompletion(item);
|
|
781
|
-
|
|
782
691
|
if (waitResult.outcome === 'merged') {
|
|
783
692
|
// Treat the PR as merged for accounting purposes. We bump the
|
|
784
693
|
// dedicated `autoResolved` counter (kept for backwards-compat with
|
|
@@ -794,7 +703,6 @@ export class MergeQueueProcessor {
|
|
|
794
703
|
// we don't double-count it.
|
|
795
704
|
if (this.stats.skipped > 0) this.stats.skipped--;
|
|
796
705
|
this.log(`Auto-resolve: PR #${item.pr.number} merged by solve session`);
|
|
797
|
-
|
|
798
706
|
// Best-effort: capture the merge commit SHA so post-merge CI wait
|
|
799
707
|
// has something to poll on.
|
|
800
708
|
try {
|
|
@@ -806,7 +714,6 @@ export class MergeQueueProcessor {
|
|
|
806
714
|
} catch (error) {
|
|
807
715
|
this.log(`Auto-resolve: could not get merge commit SHA for PR #${item.pr.number}: ${error.message}`);
|
|
808
716
|
}
|
|
809
|
-
|
|
810
717
|
// Step 3 — drain the merged PR's CI before continuing. We reuse
|
|
811
718
|
// the same `waitForPostMergeCI` path the main loop already uses so
|
|
812
719
|
// release workflows finish before the next resolution starts.
|
|
@@ -848,7 +755,6 @@ export class MergeQueueProcessor {
|
|
|
848
755
|
this.stats.autoResolveFailed++;
|
|
849
756
|
this.log(`Auto-resolve: error waiting for PR #${item.pr.number}: ${item.autoResolveError}`);
|
|
850
757
|
}
|
|
851
|
-
|
|
852
758
|
this.autoResolvePhase = null;
|
|
853
759
|
this.autoResolveWaitStartedAt = null;
|
|
854
760
|
if (this.onProgress) {
|
|
@@ -862,7 +768,6 @@ export class MergeQueueProcessor {
|
|
|
862
768
|
this.autoResolveWaitStartedAt = null;
|
|
863
769
|
}
|
|
864
770
|
}
|
|
865
|
-
|
|
866
771
|
/**
|
|
867
772
|
* Issue #1807: Poll the PR's lifecycle state until the spawned solve
|
|
868
773
|
* session either lands (MERGED), gives up (CLOSED without merge), or the
|
|
@@ -887,14 +792,11 @@ export class MergeQueueProcessor {
|
|
|
887
792
|
const startTime = Date.now();
|
|
888
793
|
let consecutiveErrors = 0;
|
|
889
794
|
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
890
|
-
|
|
891
795
|
this.log(`Auto-resolve: polling PR #${item.pr.number} until merged/closed (timeout=${Math.round(timeout / 60000)}m, poll=${Math.round(pollInterval / 1000)}s)`);
|
|
892
|
-
|
|
893
796
|
while (Date.now() - startTime < timeout) {
|
|
894
797
|
if (this.isCancelled) {
|
|
895
798
|
return { outcome: 'cancelled' };
|
|
896
799
|
}
|
|
897
|
-
|
|
898
800
|
let status;
|
|
899
801
|
try {
|
|
900
802
|
status = await this.getPRStatus(this.owner, this.repo, item.pr.number, this.verbose);
|
|
@@ -907,7 +809,6 @@ export class MergeQueueProcessor {
|
|
|
907
809
|
await this.sleep(pollInterval);
|
|
908
810
|
continue;
|
|
909
811
|
}
|
|
910
|
-
|
|
911
812
|
if (status && !status.error) {
|
|
912
813
|
consecutiveErrors = 0;
|
|
913
814
|
if (status.state === 'MERGED') {
|
|
@@ -922,13 +823,10 @@ export class MergeQueueProcessor {
|
|
|
922
823
|
return { outcome: 'error', error: status.error };
|
|
923
824
|
}
|
|
924
825
|
}
|
|
925
|
-
|
|
926
826
|
await this.sleep(pollInterval);
|
|
927
827
|
}
|
|
928
|
-
|
|
929
828
|
return { outcome: 'timeout' };
|
|
930
829
|
}
|
|
931
|
-
|
|
932
830
|
/**
|
|
933
831
|
* Wait for any active CI runs on the target branch to complete
|
|
934
832
|
* Issue #1307: Prevents merging while post-merge CI from previous merges is still running
|
|
@@ -938,12 +836,10 @@ export class MergeQueueProcessor {
|
|
|
938
836
|
// Track if we're waiting for CI (for progress updates)
|
|
939
837
|
this.waitingForTargetBranchCI = true;
|
|
940
838
|
this.targetBranchCIStatus = null;
|
|
941
|
-
|
|
942
839
|
try {
|
|
943
840
|
// Get the default branch (usually 'main' or 'master')
|
|
944
841
|
const targetBranch = await getDefaultBranch(this.owner, this.repo, this.verbose);
|
|
945
842
|
this.log(`Checking for active CI runs on ${targetBranch} branch before processing queue...`);
|
|
946
|
-
|
|
947
843
|
const waitResult = await waitForBranchCI(
|
|
948
844
|
this.owner,
|
|
949
845
|
this.repo,
|
|
@@ -953,7 +849,6 @@ export class MergeQueueProcessor {
|
|
|
953
849
|
pollInterval: MERGE_QUEUE_CONFIG.TARGET_BRANCH_CI_POLL_INTERVAL_MS,
|
|
954
850
|
onStatusUpdate: async status => {
|
|
955
851
|
this.targetBranchCIStatus = status;
|
|
956
|
-
|
|
957
852
|
// Report progress while waiting
|
|
958
853
|
if (this.onProgress) {
|
|
959
854
|
await this.onProgress(this.getProgressUpdate());
|
|
@@ -964,7 +859,6 @@ export class MergeQueueProcessor {
|
|
|
964
859
|
},
|
|
965
860
|
this.verbose
|
|
966
861
|
);
|
|
967
|
-
|
|
968
862
|
if (!waitResult.success) {
|
|
969
863
|
// Log warning but don't fail - proceed with merge anyway
|
|
970
864
|
console.warn(`[WARN] /merge-queue: ${waitResult.error}. Proceeding with merge anyway.`);
|
|
@@ -978,7 +872,6 @@ export class MergeQueueProcessor {
|
|
|
978
872
|
this.targetBranchCIStatus = null;
|
|
979
873
|
}
|
|
980
874
|
}
|
|
981
|
-
|
|
982
875
|
/**
|
|
983
876
|
* Check if the default branch has any failed CI runs before starting the queue
|
|
984
877
|
* Issue #1341: Prevents merging on top of a broken branch
|
|
@@ -988,9 +881,7 @@ export class MergeQueueProcessor {
|
|
|
988
881
|
try {
|
|
989
882
|
const targetBranch = await getDefaultBranch(this.owner, this.repo, this.verbose);
|
|
990
883
|
this.log(`Checking CI health on ${targetBranch} branch before starting queue...`);
|
|
991
|
-
|
|
992
884
|
const healthResult = await checkBranchCIHealth(this.owner, this.repo, targetBranch, {}, this.verbose);
|
|
993
|
-
|
|
994
885
|
if (!healthResult.healthy) {
|
|
995
886
|
this.log(`Branch ${targetBranch} has ${healthResult.failedRuns.length} failed CI run(s)`);
|
|
996
887
|
return {
|
|
@@ -999,7 +890,6 @@ export class MergeQueueProcessor {
|
|
|
999
890
|
error: `Cannot start merge queue: ${healthResult.error}. Please fix the CI failures first.`,
|
|
1000
891
|
};
|
|
1001
892
|
}
|
|
1002
|
-
|
|
1003
893
|
// Issue #1425: If the latest commit's CI is still in progress, wait for it to complete
|
|
1004
894
|
// rather than proceeding immediately. The WAIT_FOR_TARGET_BRANCH_CI step (below) will
|
|
1005
895
|
// also wait, but checking here ensures we don't skip the health check entirely.
|
|
@@ -1012,7 +902,6 @@ export class MergeQueueProcessor {
|
|
|
1012
902
|
error: null,
|
|
1013
903
|
};
|
|
1014
904
|
}
|
|
1015
|
-
|
|
1016
905
|
this.log(`Branch ${targetBranch} CI is healthy. Ready to proceed.`);
|
|
1017
906
|
return {
|
|
1018
907
|
healthy: true,
|
|
@@ -1029,7 +918,6 @@ export class MergeQueueProcessor {
|
|
|
1029
918
|
};
|
|
1030
919
|
}
|
|
1031
920
|
}
|
|
1032
|
-
|
|
1033
921
|
/**
|
|
1034
922
|
* Wait for post-merge CI to complete for a merged PR
|
|
1035
923
|
* Issue #1341: Ensures each PR's CI completes (including releases) before merging the next
|
|
@@ -1041,15 +929,12 @@ export class MergeQueueProcessor {
|
|
|
1041
929
|
this.log(`No merge commit SHA available for PR #${item.pr.number}, skipping post-merge CI wait`);
|
|
1042
930
|
return { success: true, failedRuns: [], error: null };
|
|
1043
931
|
}
|
|
1044
|
-
|
|
1045
932
|
// Track that we're waiting for post-merge CI (for progress updates)
|
|
1046
933
|
this.waitingForPostMergeCI = true;
|
|
1047
934
|
this.postMergeCIStatus = null;
|
|
1048
935
|
this.currentPostMergePR = item.pr.number;
|
|
1049
|
-
|
|
1050
936
|
try {
|
|
1051
937
|
this.log(`Waiting for post-merge CI on commit ${item.mergeCommitSha.substring(0, 7)} (PR #${item.pr.number})...`);
|
|
1052
|
-
|
|
1053
938
|
const waitResult = await waitForCommitCI(
|
|
1054
939
|
this.owner,
|
|
1055
940
|
this.repo,
|
|
@@ -1059,7 +944,6 @@ export class MergeQueueProcessor {
|
|
|
1059
944
|
pollInterval: MERGE_QUEUE_CONFIG.POST_MERGE_CI_POLL_INTERVAL_MS,
|
|
1060
945
|
onStatusUpdate: async status => {
|
|
1061
946
|
this.postMergeCIStatus = status;
|
|
1062
|
-
|
|
1063
947
|
// Report progress while waiting
|
|
1064
948
|
if (this.onProgress) {
|
|
1065
949
|
await this.onProgress(this.getProgressUpdate());
|
|
@@ -1070,7 +954,6 @@ export class MergeQueueProcessor {
|
|
|
1070
954
|
},
|
|
1071
955
|
this.verbose
|
|
1072
956
|
);
|
|
1073
|
-
|
|
1074
957
|
if (waitResult.success) {
|
|
1075
958
|
if (waitResult.status === 'no_runs') {
|
|
1076
959
|
this.log(`No CI runs found for merge commit ${item.mergeCommitSha.substring(0, 7)}. Proceeding.`);
|
|
@@ -1092,14 +975,12 @@ export class MergeQueueProcessor {
|
|
|
1092
975
|
this.currentPostMergePR = null;
|
|
1093
976
|
}
|
|
1094
977
|
}
|
|
1095
|
-
|
|
1096
978
|
/**
|
|
1097
979
|
* Get current progress update
|
|
1098
980
|
*/
|
|
1099
981
|
getProgressUpdate() {
|
|
1100
982
|
const currentItem = this.items[this.currentIndex];
|
|
1101
983
|
const processed = this.stats.merged + this.stats.failed + this.stats.skipped;
|
|
1102
|
-
|
|
1103
984
|
return {
|
|
1104
985
|
status: this.status,
|
|
1105
986
|
current: currentItem ? currentItem.getDescription() : null,
|
|
@@ -1138,13 +1019,11 @@ export class MergeQueueProcessor {
|
|
|
1138
1019
|
})),
|
|
1139
1020
|
};
|
|
1140
1021
|
}
|
|
1141
|
-
|
|
1142
1022
|
/**
|
|
1143
1023
|
* Get final report
|
|
1144
1024
|
*/
|
|
1145
1025
|
getFinalReport() {
|
|
1146
1026
|
const duration = this.completedAt && this.startedAt ? Math.round((this.completedAt - this.startedAt) / 1000) : 0;
|
|
1147
|
-
|
|
1148
1027
|
return {
|
|
1149
1028
|
status: this.status,
|
|
1150
1029
|
duration: `${Math.floor(duration / 60)}m ${duration % 60}s`,
|
|
@@ -1170,7 +1049,6 @@ export class MergeQueueProcessor {
|
|
|
1170
1049
|
})),
|
|
1171
1050
|
};
|
|
1172
1051
|
}
|
|
1173
|
-
|
|
1174
1052
|
/**
|
|
1175
1053
|
* Format a Telegram message for the current progress
|
|
1176
1054
|
* Progress bar is rendered in a code block for better style (per issue #1143)
|
|
@@ -1178,29 +1056,24 @@ export class MergeQueueProcessor {
|
|
|
1178
1056
|
*/
|
|
1179
1057
|
formatProgressMessage() {
|
|
1180
1058
|
const update = this.getProgressUpdate();
|
|
1181
|
-
|
|
1182
1059
|
let message = `*Merge Queue*\n`;
|
|
1183
1060
|
// Issue #1292: Escape owner/repo for MarkdownV2 (may contain hyphens, underscores, etc.)
|
|
1184
1061
|
message += `${this.escapeMarkdown(this.owner)}/${this.escapeMarkdown(this.repo)}\n\n`;
|
|
1185
|
-
|
|
1186
1062
|
// Progress bar in code block for better style
|
|
1187
1063
|
const progressBar = getProgressBar(update.progress.percentage);
|
|
1188
1064
|
message += '```\n';
|
|
1189
1065
|
message += `${progressBar} ${update.progress.percentage}%\n`;
|
|
1190
1066
|
message += `${update.progress.processed}/${update.progress.total} PRs processed\n`;
|
|
1191
1067
|
message += '```\n\n';
|
|
1192
|
-
|
|
1193
1068
|
// Issue #1407: Show cancelling indicator when cancellation requested but queue still running
|
|
1194
1069
|
if (this.isCancelled) {
|
|
1195
1070
|
message += `🛑 *Cancelling\\.\\.\\.*\n\n`;
|
|
1196
1071
|
}
|
|
1197
|
-
|
|
1198
1072
|
// Status summary with emojis
|
|
1199
1073
|
message += `✅ Merged: ${update.stats.merged} `;
|
|
1200
1074
|
message += `❌ Failed: ${update.stats.failed} `;
|
|
1201
1075
|
message += `⏭️ Skipped: ${update.stats.skipped} `;
|
|
1202
1076
|
message += `⏳ Pending: ${update.stats.total - update.progress.processed}\n\n`;
|
|
1203
|
-
|
|
1204
1077
|
// Issue #1307: Show waiting status for target branch CI
|
|
1205
1078
|
if (this.waitingForTargetBranchCI && this.targetBranchCIStatus) {
|
|
1206
1079
|
const elapsedSec = Math.round(this.targetBranchCIStatus.elapsedMs / 1000);
|
|
@@ -1210,7 +1083,6 @@ export class MergeQueueProcessor {
|
|
|
1210
1083
|
} else if (this.waitingForTargetBranchCI) {
|
|
1211
1084
|
message += `⏱️ Checking for active CI runs on target branch\\.\\.\\.\n\n`;
|
|
1212
1085
|
}
|
|
1213
|
-
|
|
1214
1086
|
// Issue #1341: Show waiting status for post-merge CI
|
|
1215
1087
|
if (this.waitingForPostMergeCI && this.postMergeCIStatus) {
|
|
1216
1088
|
const elapsedSec = Math.round(this.postMergeCIStatus.elapsedMs / 1000);
|
|
@@ -1223,7 +1095,6 @@ export class MergeQueueProcessor {
|
|
|
1223
1095
|
} else if (this.waitingForPostMergeCI) {
|
|
1224
1096
|
message += `⏱️ Waiting for post\\-merge CI \\(PR \\#${this.currentPostMergePR}\\)\\.\\.\\.\n\n`;
|
|
1225
1097
|
}
|
|
1226
|
-
|
|
1227
1098
|
// Issue #1805: surface the auto-resolve pass when it is currently
|
|
1228
1099
|
// active. This appears in place of "current item" because by then the
|
|
1229
1100
|
// main queue loop has finished.
|
|
@@ -1261,7 +1132,6 @@ export class MergeQueueProcessor {
|
|
|
1261
1132
|
message += `${statusEmoji} ${this.escapeMarkdown(update.current)}\n\n`;
|
|
1262
1133
|
}
|
|
1263
1134
|
}
|
|
1264
|
-
|
|
1265
1135
|
// Show errors/failures/skips inline so user gets immediate feedback (Issue #1269, #1294)
|
|
1266
1136
|
// Include both FAILED and SKIPPED items with their reasons
|
|
1267
1137
|
const problemItems = update.items.filter(item => (item.status === MergeItemStatus.FAILED || item.status === MergeItemStatus.SKIPPED) && item.error);
|
|
@@ -1279,21 +1149,17 @@ export class MergeQueueProcessor {
|
|
|
1279
1149
|
}
|
|
1280
1150
|
message += '\n';
|
|
1281
1151
|
}
|
|
1282
|
-
|
|
1283
1152
|
// PRs list with emojis (Issue #1805: render as clickable MarkdownV2 links)
|
|
1284
1153
|
message += `*Queue:*\n`;
|
|
1285
1154
|
for (const item of update.items.slice(0, 10)) {
|
|
1286
1155
|
message += `${item.emoji} ${this.formatPrLink(item.prNumber, item.title, item.prUrl)}\n`;
|
|
1287
1156
|
}
|
|
1288
|
-
|
|
1289
1157
|
if (update.items.length > 10) {
|
|
1290
1158
|
// Issue #1339: escape the ellipsis '...' for MarkdownV2
|
|
1291
1159
|
message += `_\\.\\.\\.and ${update.items.length - 10} more_\n`;
|
|
1292
1160
|
}
|
|
1293
|
-
|
|
1294
1161
|
return message;
|
|
1295
1162
|
}
|
|
1296
|
-
|
|
1297
1163
|
/**
|
|
1298
1164
|
* Format a Telegram message for the final report
|
|
1299
1165
|
* Progress bar is rendered in a code block for better style (per issue #1143)
|
|
@@ -1301,7 +1167,6 @@ export class MergeQueueProcessor {
|
|
|
1301
1167
|
*/
|
|
1302
1168
|
formatFinalMessage() {
|
|
1303
1169
|
const report = this.getFinalReport();
|
|
1304
|
-
|
|
1305
1170
|
let statusEmoji, statusText;
|
|
1306
1171
|
switch (report.status) {
|
|
1307
1172
|
case MergeStatus.COMPLETED:
|
|
@@ -1320,11 +1185,9 @@ export class MergeQueueProcessor {
|
|
|
1320
1185
|
statusEmoji = '❓';
|
|
1321
1186
|
statusText = report.status;
|
|
1322
1187
|
}
|
|
1323
|
-
|
|
1324
1188
|
let message = `${statusEmoji} *Merge Queue ${statusText}*\n`;
|
|
1325
1189
|
// Issue #1292: Escape owner/repo for MarkdownV2 (may contain hyphens, underscores, etc.)
|
|
1326
1190
|
message += `${this.escapeMarkdown(this.owner)}/${this.escapeMarkdown(this.repo)}\n\n`;
|
|
1327
|
-
|
|
1328
1191
|
// Final progress bar in code block
|
|
1329
1192
|
const percentage = report.stats.total > 0 ? Math.round((report.stats.merged / report.stats.total) * 100) : 0;
|
|
1330
1193
|
const progressBar = getProgressBar(percentage);
|
|
@@ -1332,13 +1195,11 @@ export class MergeQueueProcessor {
|
|
|
1332
1195
|
message += `${progressBar} ${percentage}%\n`;
|
|
1333
1196
|
message += `Duration: ${report.duration}\n`;
|
|
1334
1197
|
message += '```\n\n';
|
|
1335
|
-
|
|
1336
1198
|
// Summary with emojis
|
|
1337
1199
|
message += `✅ Merged: ${report.stats.merged} `;
|
|
1338
1200
|
message += `❌ Failed: ${report.stats.failed} `;
|
|
1339
1201
|
message += `⏭️ Skipped: ${report.stats.skipped} `;
|
|
1340
1202
|
message += `📋 Total: ${report.stats.total}\n`;
|
|
1341
|
-
|
|
1342
1203
|
// Issue #1805: surface the auto-resolve pass summary when it ran. We
|
|
1343
1204
|
// always show the line when the flag was set so users see "0 dispatched"
|
|
1344
1205
|
// when there was nothing to do.
|
|
@@ -1349,9 +1210,7 @@ export class MergeQueueProcessor {
|
|
|
1349
1210
|
}
|
|
1350
1211
|
message += '\n';
|
|
1351
1212
|
}
|
|
1352
|
-
|
|
1353
1213
|
message += '\n';
|
|
1354
|
-
|
|
1355
1214
|
// Issue #1341: Show branch CI health failure details if applicable
|
|
1356
1215
|
if (this.branchCIFailedRuns && this.branchCIFailedRuns.length > 0) {
|
|
1357
1216
|
message += `⚠️ *Branch CI Failures \\(blocked queue\\):*\n`;
|
|
@@ -1366,7 +1225,6 @@ export class MergeQueueProcessor {
|
|
|
1366
1225
|
}
|
|
1367
1226
|
message += '\n';
|
|
1368
1227
|
}
|
|
1369
|
-
|
|
1370
1228
|
// Issue #1341: Show post-merge CI failure details if applicable
|
|
1371
1229
|
if (this.postMergeCIFailedRuns && this.postMergeCIFailedRuns.length > 0) {
|
|
1372
1230
|
message += `⚠️ *Post\\-Merge CI Failures \\(stopped queue\\):*\n`;
|
|
@@ -1381,7 +1239,6 @@ export class MergeQueueProcessor {
|
|
|
1381
1239
|
}
|
|
1382
1240
|
message += '\n';
|
|
1383
1241
|
}
|
|
1384
|
-
|
|
1385
1242
|
// Details (Issue #1805: render PR and issue references as clickable
|
|
1386
1243
|
// MarkdownV2 links so the user can jump directly to the PR or issue).
|
|
1387
1244
|
if (report.items.length > 0) {
|
|
@@ -1403,17 +1260,14 @@ export class MergeQueueProcessor {
|
|
|
1403
1260
|
message += `${item.emoji} ${prLink}${issueRef}${reasonText}\n`;
|
|
1404
1261
|
}
|
|
1405
1262
|
}
|
|
1406
|
-
|
|
1407
1263
|
return message;
|
|
1408
1264
|
}
|
|
1409
|
-
|
|
1410
1265
|
/**
|
|
1411
1266
|
* Escape special characters for Telegram Markdown
|
|
1412
1267
|
*/
|
|
1413
1268
|
escapeMarkdown(text) {
|
|
1414
1269
|
return String(text).replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&');
|
|
1415
1270
|
}
|
|
1416
|
-
|
|
1417
1271
|
/**
|
|
1418
1272
|
* Issue #1805: Escape `)` and `\` inside a URL for a MarkdownV2 inline link.
|
|
1419
1273
|
* URLs must NOT be passed through `escapeMarkdown()` because that would also
|
|
@@ -1422,7 +1276,6 @@ export class MergeQueueProcessor {
|
|
|
1422
1276
|
escapeMarkdownLinkUrl(url) {
|
|
1423
1277
|
return String(url).replace(/[\\)]/g, '\\$&');
|
|
1424
1278
|
}
|
|
1425
|
-
|
|
1426
1279
|
/**
|
|
1427
1280
|
* Issue #1805: Build a clickable MarkdownV2 link for a PR's `\#N: title`
|
|
1428
1281
|
* reference. Falls back to plain escaped text when no URL is available so
|
|
@@ -1438,7 +1291,6 @@ export class MergeQueueProcessor {
|
|
|
1438
1291
|
if (!url) return label;
|
|
1439
1292
|
return `[${label}](${this.escapeMarkdownLinkUrl(url)})`;
|
|
1440
1293
|
}
|
|
1441
|
-
|
|
1442
1294
|
/**
|
|
1443
1295
|
* Issue #1805: Build the ` (Issue #N)` suffix as a clickable link. The
|
|
1444
1296
|
* outer parentheses are literal MarkdownV2 (escaped), so the inner inline
|
|
@@ -1450,7 +1302,6 @@ export class MergeQueueProcessor {
|
|
|
1450
1302
|
if (!url) return ` \\(${label}\\)`;
|
|
1451
1303
|
return ` \\([${label}](${this.escapeMarkdownLinkUrl(url)})\\)`;
|
|
1452
1304
|
}
|
|
1453
|
-
|
|
1454
1305
|
/**
|
|
1455
1306
|
* Sleep helper.
|
|
1456
1307
|
*
|
|
@@ -1465,7 +1316,6 @@ export class MergeQueueProcessor {
|
|
|
1465
1316
|
return cancellableSleepUntil(ms, () => this.isCancelled);
|
|
1466
1317
|
}
|
|
1467
1318
|
}
|
|
1468
|
-
|
|
1469
1319
|
/**
|
|
1470
1320
|
* Create and run a merge queue processor
|
|
1471
1321
|
* @param {string} owner - Repository owner
|
|
@@ -1479,10 +1329,8 @@ export async function createMergeQueueProcessor(owner, repo, options = {}) {
|
|
|
1479
1329
|
repo,
|
|
1480
1330
|
...options,
|
|
1481
1331
|
});
|
|
1482
|
-
|
|
1483
1332
|
return processor;
|
|
1484
1333
|
}
|
|
1485
|
-
|
|
1486
1334
|
export default {
|
|
1487
1335
|
MergeStatus,
|
|
1488
1336
|
MergeItemStatus,
|