@link-assistant/hive-mind 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 37303d7: Stop auto-restarting for stale failed check-runs when GitHub reports the pull request as clean and mergeable
8
+
3
9
  ## 2.2.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -446,7 +446,7 @@ export async function checkPRCIStatus(owner, repo, prNumber, verbose = false) {
446
446
  * @param {string} repo - Repository name
447
447
  * @param {number} prNumber - Pull request number
448
448
  * @param {boolean} verbose - Whether to log verbose output
449
- * @returns {Promise<{mergeable: boolean, reason: string|null, terminal?: boolean}>}
449
+ * @returns {Promise<{mergeable: boolean, mergeableState?: string|null, mergeStateStatus?: string|null, reason: string|null, terminal?: boolean}>}
450
450
  */
451
451
  export async function checkPRMergeable(owner, repo, prNumber, verbose = false) {
452
452
  // Issue #1339: GitHub computes mergeability asynchronously. When mergeStateStatus is
@@ -473,7 +473,7 @@ export async function checkPRMergeable(owner, repo, prNumber, verbose = false) {
473
473
  if (verbose) {
474
474
  console.log(`[VERBOSE] /merge: PR #${prNumber} mergeability still UNKNOWN after ${MAX_UNKNOWN_RETRIES} attempts`);
475
475
  }
476
- return { mergeable: false, reason: `Merge state: UNKNOWN (GitHub could not compute mergeability after ${MAX_UNKNOWN_RETRIES} attempts)` };
476
+ return { mergeable: false, mergeableState: pr.mergeable, mergeStateStatus: pr.mergeStateStatus, reason: `Merge state: UNKNOWN (GitHub could not compute mergeability after ${MAX_UNKNOWN_RETRIES} attempts)` };
477
477
  }
478
478
 
479
479
  const mergeable = pr.mergeable === 'MERGEABLE';
@@ -505,7 +505,7 @@ export async function checkPRMergeable(owner, repo, prNumber, verbose = false) {
505
505
  console.log(`[VERBOSE] /merge: PR #${prNumber} mergeable: ${mergeable}, state: ${pr.mergeStateStatus}`);
506
506
  }
507
507
 
508
- return { mergeable, reason };
508
+ return { mergeable, mergeableState: pr.mergeable, mergeStateStatus: pr.mergeStateStatus, reason };
509
509
  } catch (error) {
510
510
  if (isTerminalGitHubEntityError(error)) {
511
511
  const terminalError = getTerminalGitHubEntityErrorMessage(error);
@@ -464,6 +464,22 @@ export const shouldResetNoRunsCounter = (ciStatus, noWorkflowRunsForCommit = fal
464
464
  return Boolean(ciStatus && ciStatus.status !== 'no_checks');
465
465
  };
466
466
 
467
+ /**
468
+ * Issue #2031: GitHub can retain a failed check-run from an older workflow run
469
+ * alongside a newer successful run for the same job and HEAD SHA. The raw
470
+ * check-run rollup then says "failure" even though GitHub's live PR state is
471
+ * CLEAN/MERGEABLE. Only this explicit pair is strong enough to override the
472
+ * rollup; every other merge state remains conservative.
473
+ */
474
+ export const isAuthoritativeCleanMergeState = mergeStatus => mergeStatus?.mergeable === true && mergeStatus?.mergeableState === 'MERGEABLE' && mergeStatus?.mergeStateStatus === 'CLEAN';
475
+
476
+ export const reconcileStaleCIBlockers = (blockers, ciStatus, mergeStatus) => {
477
+ if (ciStatus?.status !== 'failure' || !isAuthoritativeCleanMergeState(mergeStatus)) {
478
+ return blockers;
479
+ }
480
+ return blockers.filter(blocker => blocker.type !== 'ci_failure' && blocker.type !== 'ci_cancelled');
481
+ };
482
+
467
483
  export const getMergeBlockers = async (owner, repo, prNumber, verbose = false, checkCount = 1, prBranchRef = null) => {
468
484
  const blockers = [];
469
485
 
@@ -945,6 +961,16 @@ export const getMergeBlockers = async (owner, repo, prNumber, verbose = false, c
945
961
  return { blockers, ciStatus, noCiConfigured: false, noCiTriggered: false, noWorkflowRunsForCommit };
946
962
  }
947
963
 
964
+ if (ciStatus.status === 'failure' && isAuthoritativeCleanMergeState(mergeStatus)) {
965
+ const staleFailureBlockers = blockers.filter(blocker => blocker.type === 'ci_failure' || blocker.type === 'ci_cancelled');
966
+ if (staleFailureBlockers.length > 0) {
967
+ if (verbose) {
968
+ await log(`[VERBOSE] /merge: PR #${prNumber} is CLEAN/MERGEABLE according to GitHub; ignoring ${staleFailureBlockers.length} stale CI failure/cancellation blocker(s) from the raw check-run rollup`);
969
+ }
970
+ blockers.splice(0, blockers.length, ...reconcileStaleCIBlockers(blockers, ciStatus, mergeStatus));
971
+ }
972
+ }
973
+
948
974
  if (!mergeStatus.mergeable) {
949
975
  blockers.push({
950
976
  type: 'not_mergeable',