@link-assistant/hive-mind 1.50.7 → 1.50.8
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,16 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.50.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5760755: fix: default to PR-branch-only CI check, add pagination and typo fix (#1573)
|
|
8
|
+
- Fix typo: `--wait-for-all-actions-in-repository-before-mergable` → `--wait-for-all-actions-in-repository-before-mergeable` (deprecated alias kept for backward compatibility)
|
|
9
|
+
- When repo-wide flag is enabled, block on ALL active runs regardless of branch (no branch filtering) to ensure safety when CI/CD pipelines interact
|
|
10
|
+
- Add `--paginate` to `getPRCommitShas()` to load all PR commits (not just first page)
|
|
11
|
+
- Add all-commits CI check: verify CI completes for every commit on the PR branch, not just HEAD
|
|
12
|
+
- Add `getPRCommitShas()` and `checkAllPRCommitsCI()` for per-commit CI verification
|
|
13
|
+
|
|
3
14
|
## 1.50.7
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -69,9 +69,61 @@ export async function waitForAllRepoActions(owner, repo, options = {}, verbose =
|
|
|
69
69
|
return { success: false, waitedForRuns: true, timedOut: true, remainingRuns: finalRuns.runs };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Get all commit SHAs for a pull request branch.
|
|
74
|
+
* @param {string} owner - Repository owner
|
|
75
|
+
* @param {string} repo - Repository name
|
|
76
|
+
* @param {number} prNumber - Pull request number
|
|
77
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
78
|
+
* @returns {Promise<string[]>} Array of commit SHAs
|
|
79
|
+
*/
|
|
80
|
+
export async function getPRCommitShas(owner, repo, prNumber, verbose = false) {
|
|
81
|
+
try {
|
|
82
|
+
const { stdout } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits" --paginate --jq '[.[].sha]'`);
|
|
83
|
+
const shas = JSON.parse(stdout.trim() || '[]');
|
|
84
|
+
if (verbose && shas.length > 1) {
|
|
85
|
+
console.log(`[VERBOSE] pr-commits: ${shas.length} commits on PR #${prNumber}`);
|
|
86
|
+
}
|
|
87
|
+
return shas;
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check that workflow runs for ALL commits on the PR branch have completed.
|
|
95
|
+
* @param {string} owner - Repository owner
|
|
96
|
+
* @param {string} repo - Repository name
|
|
97
|
+
* @param {number} prNumber - Pull request number
|
|
98
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
99
|
+
* @param {Function} getWorkflowRunsForSha - Function to get workflow runs for a SHA
|
|
100
|
+
* @returns {Promise<{allComplete: boolean, totalCommits: number, pendingCommits: string[], details: Object[]}>}
|
|
101
|
+
*/
|
|
102
|
+
export async function checkAllPRCommitsCI(owner, repo, prNumber, verbose, getWorkflowRunsForSha) {
|
|
103
|
+
const shas = await getPRCommitShas(owner, repo, prNumber, verbose);
|
|
104
|
+
if (shas.length === 0) return { allComplete: true, totalCommits: 0, pendingCommits: [], details: [] };
|
|
105
|
+
|
|
106
|
+
const details = [];
|
|
107
|
+
const pendingCommits = [];
|
|
108
|
+
for (const sha of shas) {
|
|
109
|
+
const runs = await getWorkflowRunsForSha(owner, repo, sha, false);
|
|
110
|
+
const inProgress = runs.filter(r => r.status !== 'completed');
|
|
111
|
+
const complete = inProgress.length === 0;
|
|
112
|
+
details.push({ sha: sha.substring(0, 7), total: runs.length, inProgress: inProgress.length, complete });
|
|
113
|
+
if (!complete) pendingCommits.push(sha.substring(0, 7));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (verbose && pendingCommits.length > 0) {
|
|
117
|
+
console.log(`[VERBOSE] pr-commits: ${pendingCommits.length}/${shas.length} commits have in-progress CI: ${pendingCommits.join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { allComplete: pendingCommits.length === 0, totalCommits: shas.length, pendingCommits, details };
|
|
121
|
+
}
|
|
122
|
+
|
|
72
123
|
/**
|
|
73
124
|
* Multi-mechanism CI consensus check. Requires Check Runs API, Workflow Runs API,
|
|
74
125
|
* and optionally repo-wide active runs to ALL agree before concluding CI is complete.
|
|
126
|
+
* Issue #1573: Also checks all PR commits' CI (not just head SHA) by default.
|
|
75
127
|
* @param {Object} params
|
|
76
128
|
* @returns {Promise<{allAgree: boolean, mechanisms: Object, ciStatus: Object, workflowRuns: Array}>}
|
|
77
129
|
*/
|
|
@@ -82,6 +134,15 @@ export async function checkCIConsensus({ owner, repo, prNumber, sha, waitForAllR
|
|
|
82
134
|
const workflowRuns = await getWorkflowRunsForSha(owner, repo, sha, verbose);
|
|
83
135
|
const workflowsOK = workflowRuns.length === 0 || workflowRuns.every(r => r.status === 'completed');
|
|
84
136
|
|
|
137
|
+
let allCommitsOK = true;
|
|
138
|
+
let allCommitsInfo = null;
|
|
139
|
+
if (checkRunsOK && workflowsOK && prNumber) {
|
|
140
|
+
allCommitsInfo = await checkAllPRCommitsCI(owner, repo, prNumber, verbose, getWorkflowRunsForSha);
|
|
141
|
+
allCommitsOK = allCommitsInfo.allComplete;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// When enabled, block on ANY active CI/CD run in the repository regardless of branch.
|
|
145
|
+
// This ensures safety when CI/CD pipelines interact or depend on each other.
|
|
85
146
|
let repoOK = true;
|
|
86
147
|
let repoInfo = null;
|
|
87
148
|
if (waitForAllRepoActionsFlag) {
|
|
@@ -89,15 +150,18 @@ export async function checkCIConsensus({ owner, repo, prNumber, sha, waitForAllR
|
|
|
89
150
|
repoOK = !repoInfo.hasActiveRuns;
|
|
90
151
|
}
|
|
91
152
|
|
|
92
|
-
const allAgree = checkRunsOK && workflowsOK && repoOK;
|
|
153
|
+
const allAgree = checkRunsOK && workflowsOK && allCommitsOK && repoOK;
|
|
93
154
|
const mechanisms = {
|
|
94
155
|
checkRunsAPI: { complete: checkRunsOK, status: ciStatus.status },
|
|
95
156
|
workflowRunsAPI: { complete: workflowsOK, total: workflowRuns.length, inProgress: workflowRuns.filter(r => r.status !== 'completed').length },
|
|
157
|
+
allCommitsCI: allCommitsInfo ? { complete: allCommitsOK, totalCommits: allCommitsInfo.totalCommits, pendingCommits: allCommitsInfo.pendingCommits } : { skipped: true },
|
|
96
158
|
repoActions: waitForAllRepoActionsFlag ? { complete: repoOK, count: repoInfo?.count ?? 0 } : { skipped: true },
|
|
97
159
|
};
|
|
98
160
|
|
|
99
161
|
if (verbose) {
|
|
100
|
-
|
|
162
|
+
const repoLabel = waitForAllRepoActionsFlag ? `${repoOK}(${repoInfo?.count ?? 0} active)` : 'skip';
|
|
163
|
+
const commitsLabel = allCommitsInfo ? `${allCommitsOK}(${allCommitsInfo.totalCommits} commits${allCommitsInfo.pendingCommits.length > 0 ? `, ${allCommitsInfo.pendingCommits.length} pending` : ''})` : 'skip';
|
|
164
|
+
console.log(`[VERBOSE] consensus: CheckRuns=${checkRunsOK}(${ciStatus.status}), WorkflowRuns=${workflowsOK}(${workflowRuns.length}), AllCommits=${commitsLabel}, RepoActions=${repoLabel} → ${allAgree ? 'AGREE' : 'DISAGREE'}`);
|
|
101
165
|
}
|
|
102
166
|
return { allAgree, mechanisms, ciStatus, workflowRuns };
|
|
103
167
|
}
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -1332,7 +1332,7 @@ export async function getCommitDate(owner, repo, sha, verbose = false) {
|
|
|
1332
1332
|
export async function checkPreviousPRCommitsHadCI(owner, repo, prNumber, headSha, verbose = false) {
|
|
1333
1333
|
try {
|
|
1334
1334
|
// Get all commits in the PR
|
|
1335
|
-
const { stdout: commitsJson } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits
|
|
1335
|
+
const { stdout: commitsJson } = await exec(`gh api "repos/${owner}/${repo}/pulls/${prNumber}/commits" --paginate --jq '[.[].sha]'`);
|
|
1336
1336
|
const allShas = JSON.parse(commitsJson.trim() || '[]');
|
|
1337
1337
|
|
|
1338
1338
|
// Exclude the current HEAD SHA
|
|
@@ -1455,8 +1455,8 @@ export async function checkWorkflowsHavePRTriggers(owner, repo, verbose = false,
|
|
|
1455
1455
|
import { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha } from './github-merge-ci.lib.mjs';
|
|
1456
1456
|
export { waitForCommitCI, checkBranchCIHealth, getMergeCommitSha };
|
|
1457
1457
|
|
|
1458
|
-
import { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus } from './github-merge-repo-actions.lib.mjs'; // Issue #1503
|
|
1459
|
-
export { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus };
|
|
1458
|
+
import { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus, checkAllPRCommitsCI, getPRCommitShas } from './github-merge-repo-actions.lib.mjs'; // Issue #1503
|
|
1459
|
+
export { getAllActiveRepoRuns, waitForAllRepoActions, checkCIConsensus, checkAllPRCommitsCI, getPRCommitShas };
|
|
1460
1460
|
|
|
1461
1461
|
export default {
|
|
1462
1462
|
READY_LABEL,
|
|
@@ -69,8 +69,9 @@ export const watchUntilMergeable = async params => {
|
|
|
69
69
|
const MIN_CI_CHECK_INTERVAL_SECONDS = 120;
|
|
70
70
|
const watchInterval = Math.max(rawWatchInterval, MIN_CI_CHECK_INTERVAL_SECONDS);
|
|
71
71
|
const isAutoMerge = argv.autoMerge || false;
|
|
72
|
-
// Issue #1503: --wait-for-all-actions-in-repository-before-
|
|
73
|
-
|
|
72
|
+
// Issue #1503/#1573: --wait-for-all-actions-in-repository-before-mergeable
|
|
73
|
+
// When enabled (default: true), blocks merge if ANY CI/CD run in the repo is active — ensures safety when pipelines interact.
|
|
74
|
+
const waitForAllRepoActionsFlag = argv.waitForAllActionsInRepositoryBeforeMergeable ?? argv['wait-for-all-actions-in-repository-before-mergeable'] ?? argv.waitForAllActionsInRepositoryBeforeMergable ?? argv['wait-for-all-actions-in-repository-before-mergable'] ?? false;
|
|
74
75
|
|
|
75
76
|
// Track latest session data across all iterations for accurate pricing
|
|
76
77
|
let latestSessionId = null;
|
|
@@ -219,7 +220,9 @@ export const watchUntilMergeable = async params => {
|
|
|
219
220
|
|
|
220
221
|
if (!consensus.allAgree) {
|
|
221
222
|
const m = consensus.mechanisms;
|
|
222
|
-
|
|
223
|
+
const repoLabel = m.repoActions.skipped ? 'skipped' : `${m.repoActions.count} active`;
|
|
224
|
+
const commitsLabel = m.allCommitsCI.skipped ? 'skipped' : `${m.allCommitsCI.pendingCommits.length} pending of ${m.allCommitsCI.totalCommits}`;
|
|
225
|
+
await log(formatAligned('🔄', 'CI mechanisms DISAGREE:', `CheckRuns=${m.checkRunsAPI.status}, WorkflowRuns=${m.workflowRunsAPI.inProgress} in-progress, AllCommits=${commitsLabel}, RepoActions=${repoLabel}`, 2));
|
|
223
226
|
await log(formatAligned('⏳', 'Continuing to monitor...', 'Mechanisms must agree before declaring mergeable', 2));
|
|
224
227
|
consecutiveNoRunsChecks = 0;
|
|
225
228
|
lastCheckTime = currentTime;
|
|
@@ -229,7 +232,8 @@ export const watchUntilMergeable = async params => {
|
|
|
229
232
|
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
230
233
|
continue;
|
|
231
234
|
}
|
|
232
|
-
|
|
235
|
+
const acLabel = consensus.mechanisms.allCommitsCI.skipped ? '' : `, AllCommits=complete(${consensus.mechanisms.allCommitsCI.totalCommits})`;
|
|
236
|
+
await log(formatAligned('✅', 'All CI mechanisms agree:', `CheckRuns=${consensus.mechanisms.checkRunsAPI.status}, WorkflowRuns=complete(${consensus.mechanisms.workflowRunsAPI.total})${acLabel}, RepoActions=${consensus.mechanisms.repoActions.skipped ? 'skipped' : 'clear'}`, 2));
|
|
233
237
|
} else if (waitForAllRepoActionsFlag) {
|
|
234
238
|
// Even with no CI configured, check repo-wide actions for absolute safety
|
|
235
239
|
const repoRuns = await getAllActiveRepoRuns(owner, repo, argv.verbose);
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -186,11 +186,16 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
186
186
|
description: 'Auto-restart until PR becomes mergeable (no iteration limit). Restarts on new comments from non-bot users, CI failures, merge conflicts, or other issues. Does NOT auto-merge.',
|
|
187
187
|
default: true,
|
|
188
188
|
},
|
|
189
|
-
'wait-for-all-actions-in-repository-before-
|
|
189
|
+
'wait-for-all-actions-in-repository-before-mergeable': {
|
|
190
190
|
type: 'boolean',
|
|
191
|
-
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable.
|
|
191
|
+
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable. When enabled, blocks merge if ANY CI/CD run in the repository is active, regardless of branch — this ensures safety when CI/CD pipelines interact or depend on each other. Enabled by default.',
|
|
192
192
|
default: true,
|
|
193
193
|
},
|
|
194
|
+
'wait-for-all-actions-in-repository-before-mergable': {
|
|
195
|
+
type: 'boolean',
|
|
196
|
+
description: 'Deprecated alias for --wait-for-all-actions-in-repository-before-mergeable (fixes typo).',
|
|
197
|
+
hidden: true,
|
|
198
|
+
},
|
|
194
199
|
'auto-restart-on-non-updated-pull-request-description': {
|
|
195
200
|
type: 'boolean',
|
|
196
201
|
description: 'Automatically restart if PR title or description still contains auto-generated placeholder text after agent execution. Restarts with a hint about what was not updated.',
|