@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +4 -1
  3. package/src/agent-command.lib.mjs +74 -0
  4. package/src/agent.lib.mjs +59 -34
  5. package/src/agentic-cli-updater.lib.mjs +241 -0
  6. package/src/claude.connection.lib.mjs +209 -0
  7. package/src/claude.lib.mjs +6 -202
  8. package/src/codex.lib.mjs +0 -128
  9. package/src/formal-ai-isolation.lib.mjs +62 -0
  10. package/src/formal-ai-maintenance.lib.mjs +106 -0
  11. package/src/formal-ai-model.lib.mjs +25 -0
  12. package/src/formal-ai-runtime.lib.mjs +10 -0
  13. package/src/formal-ai-sidecar.lib.mjs +565 -0
  14. package/src/formal-ai-updater.lib.mjs +294 -0
  15. package/src/formal-ai-version.lib.mjs +100 -0
  16. package/src/formal-ai.lib.mjs +11 -16
  17. package/src/github-rate-limit.lib.mjs +3 -0
  18. package/src/github-url-parser.lib.mjs +255 -0
  19. package/src/github.lib.mjs +22 -343
  20. package/src/hive.mjs +0 -152
  21. package/src/interactive-mode.lib.mjs +0 -43
  22. package/src/isolation-runner.lib.mjs +44 -173
  23. package/src/limits.lib.mjs +0 -89
  24. package/src/model-args.lib.mjs +32 -0
  25. package/src/models/index.mjs +5 -19
  26. package/src/session-monitor.lib.mjs +14 -172
  27. package/src/solve.auto-merge.lib.mjs +70 -164
  28. package/src/solve.mjs +31 -193
  29. package/src/solve.repository.lib.mjs +0 -83
  30. package/src/solve.results.lib.mjs +2 -92
  31. package/src/solve.session.lib.mjs +52 -19
  32. package/src/solve.tool-uncommitted.lib.mjs +22 -0
  33. package/src/state-lock.lib.mjs +82 -0
  34. package/src/telegram-bot.mjs +17 -65
  35. package/src/telegram-fix-command.lib.mjs +1 -8
  36. package/src/telegram-merge-queue.lib.mjs +3 -155
  37. package/src/telegram-solve-queue.lib.mjs +9 -168
  38. package/src/telegram-task-command.lib.mjs +1 -8
  39. package/src/use-m-bootstrap.lib.mjs +6 -5
  40. package/src/use-with-retry.lib.mjs +128 -2
  41. 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,