@yemi33/minions 0.1.1003 → 0.1.1005
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 +3 -2
- package/dashboard.js +13 -3
- package/engine/ado-status.js +2 -1
- package/engine/ado.js +22 -21
- package/engine/consolidation.js +2 -2
- package/engine/dispatch.js +4 -3
- package/engine/github.js +11 -20
- package/engine/lifecycle.js +102 -55
- package/engine/pipeline.js +1 -1
- package/engine/queries.js +39 -16
- package/engine/shared.js +287 -12
- package/engine/teams.js +1 -1
- package/engine/watches.js +10 -6
- package/engine.js +52 -42
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1005 (2026-04-16)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- fix stopAfter=0 watch expiry — run forever for all conditions (#1117)
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
- gate auto-fix conflict dispatch behind autoFixConflicts flag
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
|
+
- harden repo-scoped PR identity handling
|
|
28
29
|
- only gate reviews/fixes when no free agent slots remain
|
|
29
30
|
- extend auto-link fallback to ADO projects
|
|
30
31
|
- auto-link existing GitHub PR when agent completes without creating one
|
|
@@ -44,9 +45,9 @@
|
|
|
44
45
|
- defer review setCooldown to post-gating in discoverWork
|
|
45
46
|
- move review verdict check before updateWorkItemStatus(DONE)
|
|
46
47
|
- address review feedback — move writeToInbox outside lock, add absolute condition auto-expire
|
|
47
|
-
- fix watches feature gaps — human notifications, branch stub, status-change init, unique keys
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
|
+
- refactor: migrate PR links to array format and consolidate shared helpers
|
|
50
51
|
- docs: update CLAUDE.md with recent Minions architecture changes
|
|
51
52
|
- chore: stop tracking runtime pipeline state
|
|
52
53
|
- chore: strengthen prompts and routing
|
package/dashboard.js
CHANGED
|
@@ -2172,7 +2172,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
2172
2172
|
}
|
|
2173
2173
|
|
|
2174
2174
|
// Build a manifest of all KB entries with their content (skip pinned — user wants to keep them)
|
|
2175
|
-
const
|
|
2175
|
+
const requestPinnedKeys = Array.isArray(body.pinnedKeys)
|
|
2176
|
+
? body.pinnedKeys.filter(k => typeof k === 'string' && k.startsWith('knowledge/'))
|
|
2177
|
+
: [];
|
|
2178
|
+
const serverPinnedKeys = shared.getPinnedItems().filter(k => k.startsWith('knowledge/'));
|
|
2179
|
+
const pinnedKeys = new Set([...serverPinnedKeys, ...requestPinnedKeys]);
|
|
2176
2180
|
const manifest = [];
|
|
2177
2181
|
for (const e of entries) {
|
|
2178
2182
|
if (pinnedKeys.has('knowledge/' + e.cat + '/' + e.file)) continue;
|
|
@@ -2180,6 +2184,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
2180
2184
|
if (!content) continue;
|
|
2181
2185
|
manifest.push({ category: e.cat, file: e.file, title: e.title, agent: e.agent, date: e.date, content: content.slice(0, 3000) });
|
|
2182
2186
|
}
|
|
2187
|
+
if (manifest.length < 2) {
|
|
2188
|
+
global._kbSweepLastResult = { ok: true, summary: 'nothing to sweep (< 2 unpinned entries)' };
|
|
2189
|
+
global._kbSweepLastCompletedAt = Date.now();
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2183
2192
|
|
|
2184
2193
|
const { callLLM, trackEngineUsage } = require('./engine/llm');
|
|
2185
2194
|
const BATCH_SIZE = 30; // ~30 entries per batch to stay within Haiku context
|
|
@@ -2533,7 +2542,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2533
2542
|
const prLinks = shared.getPrLinks();
|
|
2534
2543
|
const implContext = (plan.missing_features || []).map(f => {
|
|
2535
2544
|
const wi = planWis.find(w => w.id === f.id);
|
|
2536
|
-
const pr = allPrs.find(p => prLinks[p.id]
|
|
2545
|
+
const pr = allPrs.find(p => (prLinks[p.id] || []).includes(f.id) || (p.prdItems || []).includes(f.id));
|
|
2537
2546
|
return `- **${f.id}**: ${f.name} [status: ${wi?.status || f.status}]${pr ? ` (PR: ${pr.id}, branch: \`${pr.branch}\`)` : ''}`;
|
|
2538
2547
|
}).join('\n');
|
|
2539
2548
|
|
|
@@ -4364,7 +4373,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4364
4373
|
// Extract PR number from URL
|
|
4365
4374
|
const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
|
|
4366
4375
|
const prNum = prNumMatch ? (prNumMatch[1] || prNumMatch[2]) : Date.now().toString().slice(-6);
|
|
4367
|
-
const prId =
|
|
4376
|
+
const prId = shared.getCanonicalPrId(targetProject, prNum, url);
|
|
4368
4377
|
|
|
4369
4378
|
// Atomic check-and-insert to prevent duplicates and races with polling loops
|
|
4370
4379
|
let duplicate = false;
|
|
@@ -4373,6 +4382,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4373
4382
|
if (prs.some(p => p.id === prId || p.url === url)) { duplicate = true; return prs; }
|
|
4374
4383
|
prs.push({
|
|
4375
4384
|
id: prId,
|
|
4385
|
+
prNumber: parseInt(prNum, 10) || null,
|
|
4376
4386
|
title: (title || 'PR #' + prNum + ' (polling...)').slice(0, 120),
|
|
4377
4387
|
description: '',
|
|
4378
4388
|
agent: 'human',
|
package/engine/ado-status.js
CHANGED
|
@@ -57,7 +57,8 @@ const prNumber = parseInt(prNumberArg, 10);
|
|
|
57
57
|
function findInCache(projects) {
|
|
58
58
|
for (const project of projects) {
|
|
59
59
|
const prs = safeJsonArr(projectPrPath(project));
|
|
60
|
-
|
|
60
|
+
shared.normalizePrRecords(prs, project);
|
|
61
|
+
const pr = prs.find(p => p.prNumber === prNumber || p.id === shared.getCanonicalPrId(project, prNumber) || shared.getPrDisplayId(p) === `PR-${prNumber}`);
|
|
61
62
|
if (!pr) continue;
|
|
62
63
|
const out = {
|
|
63
64
|
prNumber: pr.prNumber || prNumber,
|
package/engine/ado.js
CHANGED
|
@@ -17,6 +17,11 @@ function engine() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const stripRefsHeads = s => (s || '').replace('refs/heads/', '');
|
|
20
|
+
const getAdoPrUrl = (project, prNumber) => {
|
|
21
|
+
if (project.prUrlBase) return `${project.prUrlBase}${prNumber}`;
|
|
22
|
+
const repoPath = encodeURIComponent(project.repoName || project.repositoryId || '');
|
|
23
|
+
return `https://dev.azure.com/${project.adoOrg}/${project.adoProject}/_git/${repoPath}/pullrequest/${prNumber}`;
|
|
24
|
+
};
|
|
20
25
|
|
|
21
26
|
// ── Build/Review Status Helpers ───────────────────────────────────────────────
|
|
22
27
|
|
|
@@ -250,7 +255,7 @@ async function forEachActivePr(config, token, callback) {
|
|
|
250
255
|
for (let i = 0; i < activePrs.length; i += CONCURRENCY) {
|
|
251
256
|
const batch = activePrs.slice(i, i + CONCURRENCY);
|
|
252
257
|
const results = await Promise.allSettled(batch.map(async (pr) => {
|
|
253
|
-
const prNum = (pr
|
|
258
|
+
const prNum = shared.getPrNumber(pr);
|
|
254
259
|
if (!prNum) return false;
|
|
255
260
|
return callback(project, pr, prNum, orgBase);
|
|
256
261
|
}));
|
|
@@ -675,6 +680,7 @@ async function reconcilePrs(config) {
|
|
|
675
680
|
|
|
676
681
|
const prPath = shared.projectPrPath(project);
|
|
677
682
|
const existingPrs = shared.safeJson(prPath) || [];
|
|
683
|
+
shared.normalizePrRecords(existingPrs, project);
|
|
678
684
|
const existingIds = new Set(existingPrs.map(p => p.id));
|
|
679
685
|
let projectAdded = 0;
|
|
680
686
|
|
|
@@ -687,7 +693,8 @@ async function reconcilePrs(config) {
|
|
|
687
693
|
|
|
688
694
|
let projectUpdated = 0;
|
|
689
695
|
for (const adoPr of adoPrs) {
|
|
690
|
-
const
|
|
696
|
+
const prUrl = getAdoPrUrl(project, adoPr.pullRequestId);
|
|
697
|
+
const prId = shared.getCanonicalPrId(project, adoPr.pullRequestId, prUrl);
|
|
691
698
|
const branch = stripRefsHeads(adoPr.sourceRefName);
|
|
692
699
|
const title = adoPr.title || '';
|
|
693
700
|
// Extract item ID from branch name or PR title (e.g., feat(P-2cafdc2a): ...)
|
|
@@ -705,7 +712,7 @@ async function reconcilePrs(config) {
|
|
|
705
712
|
}
|
|
706
713
|
// PR already tracked — write link to pr-links.json if we can extract an ID
|
|
707
714
|
if (confirmedItemId) {
|
|
708
|
-
addPrLink(prId, confirmedItemId);
|
|
715
|
+
addPrLink(prId, confirmedItemId, { project, prNumber: adoPr.pullRequestId, url: prUrl });
|
|
709
716
|
if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
|
|
710
717
|
existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
|
|
711
718
|
existing.prdItems.push(confirmedItemId);
|
|
@@ -720,7 +727,6 @@ async function reconcilePrs(config) {
|
|
|
720
727
|
// are human-authored and should not be auto-tracked or auto-reviewed.
|
|
721
728
|
if (!confirmedItemId) continue;
|
|
722
729
|
|
|
723
|
-
const prUrl = project.prUrlBase ? project.prUrlBase + adoPr.pullRequestId : '';
|
|
724
730
|
existingPrs.push({
|
|
725
731
|
id: prId,
|
|
726
732
|
prNumber: adoPr.pullRequestId,
|
|
@@ -733,7 +739,7 @@ async function reconcilePrs(config) {
|
|
|
733
739
|
url: prUrl,
|
|
734
740
|
prdItems: [confirmedItemId],
|
|
735
741
|
});
|
|
736
|
-
addPrLink(prId, confirmedItemId);
|
|
742
|
+
addPrLink(prId, confirmedItemId, { project, prNumber: adoPr.pullRequestId, url: prUrl });
|
|
737
743
|
existingIds.add(prId);
|
|
738
744
|
projectAdded++;
|
|
739
745
|
log('info', `PR reconciliation: added ${prId} (branch: ${branch}, linked to ${confirmedItemId}) to ${project.name}`);
|
|
@@ -742,22 +748,13 @@ async function reconcilePrs(config) {
|
|
|
742
748
|
// Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
|
|
743
749
|
for (const pr of existingPrs) {
|
|
744
750
|
if (pr.prNumber == null) {
|
|
745
|
-
const derived =
|
|
751
|
+
const derived = shared.getPrNumber(pr);
|
|
746
752
|
if (derived) pr.prNumber = derived;
|
|
747
753
|
}
|
|
748
754
|
}
|
|
749
755
|
|
|
750
756
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
751
|
-
const
|
|
752
|
-
let backfilled = 0;
|
|
753
|
-
for (const pr of existingPrs) {
|
|
754
|
-
const linked = prLinks[pr.id];
|
|
755
|
-
if (linked && !(pr.prdItems || []).includes(linked)) {
|
|
756
|
-
pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
|
|
757
|
-
pr.prdItems.push(linked);
|
|
758
|
-
backfilled++;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
757
|
+
const backfilled = shared.backfillPrPrdItems(existingPrs, shared.getPrLinks());
|
|
761
758
|
|
|
762
759
|
if (projectAdded > 0 || projectUpdated > 0 || backfilled > 0) {
|
|
763
760
|
mutateJsonFileLocked(prPath, (currentPrs) => {
|
|
@@ -790,7 +787,8 @@ async function checkLiveReviewStatus(pr, project) {
|
|
|
790
787
|
const token = await getAdoToken();
|
|
791
788
|
if (!token) return null;
|
|
792
789
|
const orgBase = shared.getAdoOrgBase(project);
|
|
793
|
-
const prNum = (pr
|
|
790
|
+
const prNum = shared.getPrNumber(pr);
|
|
791
|
+
if (!prNum) return null;
|
|
794
792
|
const url = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}/pullrequests/${prNum}?api-version=7.1`;
|
|
795
793
|
const result = await execAsync(`curl -s --max-time 4 -H "Authorization: Bearer ${token}" "${url}"`, { encoding: 'utf-8', timeout: 5000, windowsHide: true });
|
|
796
794
|
const prData = JSON.parse(result);
|
|
@@ -855,7 +853,7 @@ async function fetchSinglePrBuildStatus(project, prNumber) {
|
|
|
855
853
|
}));
|
|
856
854
|
const logParts = [];
|
|
857
855
|
const seenBuildIds = new Set();
|
|
858
|
-
const pr = { id:
|
|
856
|
+
const pr = { id: shared.getCanonicalPrId(project, prNumber, getAdoPrUrl(project, prNumber)), branch: stripRefsHeads(prData.sourceRefName) };
|
|
859
857
|
for (const fb of failedBuilds.slice(0, 3)) {
|
|
860
858
|
const errorLog = await fetchAdoBuildErrorLog(orgBase, project, fb, token, pr, seenBuildIds);
|
|
861
859
|
if (errorLog) logParts.push(errorLog);
|
|
@@ -865,7 +863,7 @@ async function fetchSinglePrBuildStatus(project, prNumber) {
|
|
|
865
863
|
}
|
|
866
864
|
|
|
867
865
|
const votes = (prData.reviewers || []).map(r => r.vote);
|
|
868
|
-
const prUrl =
|
|
866
|
+
const prUrl = getAdoPrUrl(project, prNumber);
|
|
869
867
|
|
|
870
868
|
return {
|
|
871
869
|
prNumber,
|
|
@@ -900,6 +898,10 @@ const getAdoThrottleState = () => _adoThrottle.getState();
|
|
|
900
898
|
*/
|
|
901
899
|
async function findOpenPrOnBranch(project, branch) {
|
|
902
900
|
if (!project.adoOrg || !project.adoProject || !project.repositoryId || !branch) return null;
|
|
901
|
+
if (isAdoThrottled()) {
|
|
902
|
+
log('debug', `[ado] Skipping branch PR lookup for ${project.name || project.repoName || 'unknown project'}:${branch} — throttled`);
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
903
905
|
const token = await getAdoToken();
|
|
904
906
|
if (!token) return null;
|
|
905
907
|
const orgBase = shared.getAdoOrgBase(project);
|
|
@@ -909,7 +911,7 @@ async function findOpenPrOnBranch(project, branch) {
|
|
|
909
911
|
const pr = (data.value || [])[0];
|
|
910
912
|
if (!pr) return null;
|
|
911
913
|
const prNumber = pr.pullRequestId;
|
|
912
|
-
const prUrl = project
|
|
914
|
+
const prUrl = getAdoPrUrl(project, prNumber);
|
|
913
915
|
return { prNumber, url: prUrl };
|
|
914
916
|
}
|
|
915
917
|
|
|
@@ -940,4 +942,3 @@ module.exports = {
|
|
|
940
942
|
_resetAdoThrottle, // exported for testing
|
|
941
943
|
_setAdoThrottleForTest, // exported for testing
|
|
942
944
|
};
|
|
943
|
-
|
package/engine/consolidation.js
CHANGED
|
@@ -25,7 +25,8 @@ function consolidateInbox(config) {
|
|
|
25
25
|
|
|
26
26
|
const { ENGINE_DEFAULTS } = shared;
|
|
27
27
|
const threshold = config.engine?.inboxConsolidateThreshold || ENGINE_DEFAULTS.inboxConsolidateThreshold;
|
|
28
|
-
const
|
|
28
|
+
const pinnedInboxKeys = new Set(shared.getPinnedItems().filter(k => k.startsWith('notes/inbox/')));
|
|
29
|
+
const files = getInboxFiles().filter(f => !_processingFiles.has(f) && !pinnedInboxKeys.has('notes/inbox/' + f));
|
|
29
30
|
if (files.length < threshold) return;
|
|
30
31
|
// Auto-reset stale flag if consolidation has been running for >5 minutes (process died without cleanup)
|
|
31
32
|
if (_consolidationInFlight && (Date.now() - _consolidationStartedAt) > 300000) {
|
|
@@ -485,4 +486,3 @@ module.exports = {
|
|
|
485
486
|
classifyToKnowledgeBase,
|
|
486
487
|
checkDuplicateHash,
|
|
487
488
|
};
|
|
488
|
-
|
package/engine/dispatch.js
CHANGED
|
@@ -223,7 +223,7 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
223
223
|
try {
|
|
224
224
|
const prsPath = projectPrPath(project);
|
|
225
225
|
mutatePullRequests(prsPath, prs => {
|
|
226
|
-
const target =
|
|
226
|
+
const target = shared.findPrRecord(prs, { id: prId }, project);
|
|
227
227
|
if (target?.humanFeedback) target.humanFeedback.pendingFix = true;
|
|
228
228
|
});
|
|
229
229
|
log('info', `Restored pendingFix=true on ${prId} after failed human-feedback fix`);
|
|
@@ -246,9 +246,10 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
246
246
|
|
|
247
247
|
function writeInboxAlert(slug, content) {
|
|
248
248
|
try {
|
|
249
|
-
const
|
|
249
|
+
const safeSlug = shared.safeSlugComponent(slug, 100);
|
|
250
|
+
const file = path.join(INBOX_DIR, `engine-alert-${safeSlug}-${dateStamp()}.md`);
|
|
250
251
|
// Dedupe: don't write the same alert twice in the same day
|
|
251
|
-
const existing = safeReadDir(INBOX_DIR).find(f => f.startsWith(`engine-alert-${
|
|
252
|
+
const existing = safeReadDir(INBOX_DIR).find(f => f.startsWith(`engine-alert-${safeSlug}-${dateStamp()}`));
|
|
252
253
|
if (existing) return;
|
|
253
254
|
safeWrite(file, content);
|
|
254
255
|
} catch (e) { log('warn', 'write inbox alert: ' + e.message); }
|
package/engine/github.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const shared = require('./shared');
|
|
8
|
-
const { exec, execAsync, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, mutateJsonFileLocked, MINIONS_DIR, addPrLink, getPrLinks, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES, createThrottleTracker } = shared;
|
|
8
|
+
const { exec, execAsync, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, mutateJsonFileLocked, MINIONS_DIR, addPrLink, getPrLinks, backfillPrPrdItems, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES, createThrottleTracker } = shared;
|
|
9
9
|
const { getPrs } = require('./queries');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -199,7 +199,7 @@ async function forEachActiveGhPr(config, callback) {
|
|
|
199
199
|
let projectUpdated = 0;
|
|
200
200
|
|
|
201
201
|
for (const pr of activePrs) {
|
|
202
|
-
const prNum = (pr
|
|
202
|
+
const prNum = shared.getPrNumber(pr);
|
|
203
203
|
if (!prNum) continue;
|
|
204
204
|
|
|
205
205
|
try {
|
|
@@ -621,6 +621,7 @@ async function reconcilePrs(config) {
|
|
|
621
621
|
|
|
622
622
|
const prPath = projectPrPath(project);
|
|
623
623
|
const currentPrs = safeJson(prPath) || [];
|
|
624
|
+
shared.normalizePrRecords(currentPrs, project);
|
|
624
625
|
const existingIds = new Set(currentPrs.map(p => p.id));
|
|
625
626
|
let projectAdded = 0;
|
|
626
627
|
|
|
@@ -632,7 +633,8 @@ async function reconcilePrs(config) {
|
|
|
632
633
|
const allItems = [...workItems, ...centralItems];
|
|
633
634
|
|
|
634
635
|
for (const ghPr of ghPrs) {
|
|
635
|
-
const
|
|
636
|
+
const prUrl = project.prUrlBase ? project.prUrlBase + ghPr.number : ghPr.html_url || '';
|
|
637
|
+
const prId = shared.getCanonicalPrId(project, ghPr.number, prUrl);
|
|
636
638
|
const branch = ghPr.head?.ref || '';
|
|
637
639
|
const wiMatch = branch.match(/(P-[a-z0-9]{6,})/i) || branch.match(/(W-[a-z0-9]{6,})/i) || branch.match(/(PL-[a-z0-9]{6,})/i);
|
|
638
640
|
const linkedItemId = wiMatch ? wiMatch[1] : null;
|
|
@@ -646,7 +648,7 @@ async function reconcilePrs(config) {
|
|
|
646
648
|
existing.prNumber = ghPr.number;
|
|
647
649
|
}
|
|
648
650
|
if (confirmedItemId) {
|
|
649
|
-
addPrLink(prId, confirmedItemId);
|
|
651
|
+
addPrLink(prId, confirmedItemId, { project, prNumber: ghPr.number, url: prUrl });
|
|
650
652
|
if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
|
|
651
653
|
existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
|
|
652
654
|
existing.prdItems.push(confirmedItemId);
|
|
@@ -661,8 +663,6 @@ async function reconcilePrs(config) {
|
|
|
661
663
|
// Only auto-track PRs linked to a minions work item — skip human-authored PRs
|
|
662
664
|
if (!confirmedItemId && !isE2eBranch) continue;
|
|
663
665
|
|
|
664
|
-
const prUrl = project.prUrlBase ? project.prUrlBase + ghPr.number : ghPr.html_url || '';
|
|
665
|
-
|
|
666
666
|
currentPrs.push({
|
|
667
667
|
id: prId,
|
|
668
668
|
prNumber: ghPr.number,
|
|
@@ -675,7 +675,7 @@ async function reconcilePrs(config) {
|
|
|
675
675
|
url: prUrl,
|
|
676
676
|
prdItems: [confirmedItemId],
|
|
677
677
|
});
|
|
678
|
-
addPrLink(prId, confirmedItemId);
|
|
678
|
+
addPrLink(prId, confirmedItemId, { project, prNumber: ghPr.number, url: prUrl });
|
|
679
679
|
existingIds.add(prId);
|
|
680
680
|
projectAdded++;
|
|
681
681
|
|
|
@@ -685,22 +685,13 @@ async function reconcilePrs(config) {
|
|
|
685
685
|
// Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
|
|
686
686
|
for (const pr of currentPrs) {
|
|
687
687
|
if (pr.prNumber == null) {
|
|
688
|
-
const derived =
|
|
688
|
+
const derived = shared.getPrNumber(pr);
|
|
689
689
|
if (derived) pr.prNumber = derived;
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
692
|
|
|
693
693
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
694
|
-
const
|
|
695
|
-
let backfilled = 0;
|
|
696
|
-
for (const pr of currentPrs) {
|
|
697
|
-
const linked = prLinks[pr.id];
|
|
698
|
-
if (linked && !(pr.prdItems || []).includes(linked)) {
|
|
699
|
-
pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
|
|
700
|
-
pr.prdItems.push(linked);
|
|
701
|
-
backfilled++;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
694
|
+
const backfilled = backfillPrPrdItems(currentPrs, getPrLinks());
|
|
704
695
|
|
|
705
696
|
if (projectAdded > 0 || backfilled > 0) {
|
|
706
697
|
mutateJsonFileLocked(prPath, (lockedPrs) => {
|
|
@@ -728,7 +719,8 @@ async function checkLiveReviewStatus(pr, project) {
|
|
|
728
719
|
try {
|
|
729
720
|
const slug = getRepoSlug(project);
|
|
730
721
|
if (!slug) return null;
|
|
731
|
-
const prNum = (pr
|
|
722
|
+
const prNum = shared.getPrNumber(pr);
|
|
723
|
+
if (!prNum) return null;
|
|
732
724
|
const reviews = await ghApi(`/pulls/${prNum}/reviews`, slug);
|
|
733
725
|
if (!reviews || !Array.isArray(reviews)) return null;
|
|
734
726
|
const latestByUser = new Map();
|
|
@@ -761,4 +753,3 @@ module.exports = {
|
|
|
761
753
|
_ghPollBackoff,
|
|
762
754
|
_ghThrottle, // exported for testing
|
|
763
755
|
};
|
|
764
|
-
|