@yemi33/minions 0.1.1637 → 0.1.1639
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 +6 -0
- package/dashboard/js/render-prs.js +5 -1
- package/dashboard.js +27 -5
- package/engine/ado.js +13 -1
- package/engine/copilot-models.json +1 -1
- package/engine/github.js +11 -2
- package/engine/lifecycle.js +27 -4
- package/engine.js +59 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -20,11 +20,15 @@ function prRow(pr) {
|
|
|
20
20
|
const statusLabel = pr.status || 'active';
|
|
21
21
|
const url = pr.url || '#';
|
|
22
22
|
const prId = pr.id || '—';
|
|
23
|
+
const pendingReason = pr._pendingReason ? String(pr._pendingReason) : '';
|
|
24
|
+
const pendingReasonHtml = pendingReason
|
|
25
|
+
? '<div style="font-size:9px;color:var(--muted);margin-top:2px" title="Pending reason: ' + escapeHtml(pendingReason) + '">' + escapeHtml(pendingReason.replace(/_/g, ' ')) + '</div>'
|
|
26
|
+
: '';
|
|
23
27
|
return '<tr>' +
|
|
24
28
|
'<td><span class="pr-id">' + escapeHtml(String(prId)) + '</span></td>' +
|
|
25
29
|
'<td><a class="pr-title" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(pr.title || 'Untitled') + '</a>' + (pr.description ? '<div class="pr-desc">' + escapeHtml(pr.description.length > 120 ? pr.description.slice(0, 120) + '...' : pr.description) + '</div>' : '') + '</td>' +
|
|
26
30
|
'<td><span class="pr-agent">' + escapeHtml(pr.agent || '—') + '</span></td>' +
|
|
27
|
-
'<td><span class="pr-branch">' + escapeHtml(pr.branch || '—') + '</span
|
|
31
|
+
'<td><span class="pr-branch">' + escapeHtml(pr.branch || '—') + '</span>' + pendingReasonHtml + '</td>' +
|
|
28
32
|
'<td><span class="pr-badge ' + reviewClass + '"' + (reviewTitle ? ' title="' + escapeHtml(reviewTitle) + '"' : '') + '>' + escapeHtml(reviewLabel) + '</span></td>' +
|
|
29
33
|
'<td>' + (sq.reviewer && sq.status !== 'waiting' ? '<span class="pr-agent" title="' + escapeHtml(sq.note || '') + '">' + escapeHtml(sq.reviewer) + '</span>' : sq.reviewer && sq.status === 'waiting' ? '<span class="pr-agent" style="color:var(--muted)" title="Vote pending confirmation">' + escapeHtml(sq.reviewer) + '…</span>' : pr.reviewedBy && pr.reviewedBy.length ? '<span class="pr-agent">' + escapeHtml(pr.reviewedBy.join(', ')) + '</span>' : '<span style="color:var(--muted);font-size:11px">—</span>') + '</td>' +
|
|
30
34
|
'<td><span class="pr-badge ' + buildClass + '">' + escapeHtml(buildLabel) + '</span></td>' +
|
package/dashboard.js
CHANGED
|
@@ -5838,9 +5838,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5838
5838
|
// /api/prd/regenerate removed — use /api/plans/approve which does diff-aware update
|
|
5839
5839
|
|
|
5840
5840
|
// Agents
|
|
5841
|
-
{ method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, autoObserve?, context?', handler: async (req, res) => {
|
|
5841
|
+
{ method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, autoObserve?, context?, workItemId?', handler: async (req, res) => {
|
|
5842
5842
|
const body = await readBody(req);
|
|
5843
|
-
const { url, title, project: projectName, autoObserve, context } = body;
|
|
5843
|
+
const { url, title, project: projectName, autoObserve, context, workItemId } = body;
|
|
5844
5844
|
if (!url) return jsonReply(res, 400, { error: 'url required' });
|
|
5845
5845
|
|
|
5846
5846
|
// Determine project
|
|
@@ -5853,6 +5853,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5853
5853
|
const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
|
|
5854
5854
|
const prNum = prNumMatch ? (prNumMatch[1] || prNumMatch[2]) : Date.now().toString().slice(-6);
|
|
5855
5855
|
const prId = shared.getCanonicalPrId(targetProject, prNum, url);
|
|
5856
|
+
const contextText = typeof context === 'string' ? context : (context == null ? '' : JSON.stringify(context));
|
|
5857
|
+
|
|
5858
|
+
// Resolve a work-item association from either the top-level workItemId
|
|
5859
|
+
// field (preferred) or context.workItemId (legacy CC payload shape).
|
|
5860
|
+
// Without this, manually-linked PRs end up with prdItems=[] and the
|
|
5861
|
+
// Work Items page renders no PR even though _context records the ID.
|
|
5862
|
+
const linkedItemId = (typeof workItemId === 'string' && workItemId.trim())
|
|
5863
|
+
|| (context && typeof context === 'object' && typeof context.workItemId === 'string' && context.workItemId.trim())
|
|
5864
|
+
|| '';
|
|
5856
5865
|
|
|
5857
5866
|
// Atomic check-and-insert to prevent duplicates and races with polling loops
|
|
5858
5867
|
let duplicate = false;
|
|
@@ -5870,15 +5879,25 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5870
5879
|
status: 'active',
|
|
5871
5880
|
created: new Date().toISOString(),
|
|
5872
5881
|
url,
|
|
5873
|
-
prdItems: [],
|
|
5882
|
+
prdItems: linkedItemId ? [linkedItemId] : [],
|
|
5874
5883
|
_manual: true,
|
|
5875
5884
|
_contextOnly: !autoObserve,
|
|
5876
5885
|
_autoObserve: !!autoObserve,
|
|
5877
|
-
_context:
|
|
5886
|
+
_context: contextText,
|
|
5878
5887
|
});
|
|
5879
5888
|
return prs;
|
|
5880
5889
|
}, { defaultValue: [] });
|
|
5881
5890
|
if (duplicate) return jsonReply(res, 400, { error: 'PR already tracked' });
|
|
5891
|
+
// Persist the work-item ↔ PR association in pr-links.json so
|
|
5892
|
+
// queries.getWorkItems() can render item._pr / item._prUrl. addPrLink
|
|
5893
|
+
// is idempotent and handles the central / project-scoped split.
|
|
5894
|
+
if (linkedItemId) {
|
|
5895
|
+
try {
|
|
5896
|
+
shared.addPrLink(prId, linkedItemId, { project: targetProject, prNumber: parseInt(prNum, 10) || null, url });
|
|
5897
|
+
} catch (e) {
|
|
5898
|
+
shared.log('warn', `PR link addPrLink failed for ${prId} → ${linkedItemId}: ${e.message}`);
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5882
5901
|
invalidateStatusCache();
|
|
5883
5902
|
jsonReply(res, 200, { ok: true, id: prId });
|
|
5884
5903
|
|
|
@@ -5906,7 +5925,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5906
5925
|
// Remote title always wins — any user-supplied title is a placeholder (closes #1283)
|
|
5907
5926
|
if (prData.title) pr.title = prData.title.slice(0, 120);
|
|
5908
5927
|
if (prData.description) pr.description = prData.description.slice(0, 500);
|
|
5909
|
-
if (!pr.branch && prData.branch)
|
|
5928
|
+
if (!pr.branch && prData.branch) {
|
|
5929
|
+
pr.branch = prData.branch;
|
|
5930
|
+
if (pr._pendingReason === 'missing_pr_branch') delete pr._pendingReason;
|
|
5931
|
+
}
|
|
5910
5932
|
if (pr.agent === 'human' && prData.author) pr.agent = prData.author;
|
|
5911
5933
|
return prs;
|
|
5912
5934
|
}, { defaultValue: [] });
|
package/engine/ado.js
CHANGED
|
@@ -405,6 +405,12 @@ async function pollPrStatus(config) {
|
|
|
405
405
|
pr._adoSourceCommit = sourceCommit;
|
|
406
406
|
updated = true;
|
|
407
407
|
}
|
|
408
|
+
const sourceBranch = stripRefsHeads(prData.sourceRefName);
|
|
409
|
+
if (!pr.branch && sourceBranch) {
|
|
410
|
+
pr.branch = sourceBranch;
|
|
411
|
+
if (pr._pendingReason === 'missing_pr_branch') delete pr._pendingReason;
|
|
412
|
+
updated = true;
|
|
413
|
+
}
|
|
408
414
|
|
|
409
415
|
const reviewers = prData.reviewers || [];
|
|
410
416
|
const votes = reviewers.map(r => r.vote).filter(v => v !== undefined);
|
|
@@ -759,6 +765,7 @@ async function reconcilePrs(config) {
|
|
|
759
765
|
shared.normalizePrRecords(existingPrs, project);
|
|
760
766
|
const existingIds = new Set(existingPrs.map(p => p.id));
|
|
761
767
|
let projectAdded = 0;
|
|
768
|
+
let metadataUpdated = 0;
|
|
762
769
|
|
|
763
770
|
// Load work items to match branches to work item IDs
|
|
764
771
|
const wiPath = shared.projectWorkItemsPath(project);
|
|
@@ -786,6 +793,11 @@ async function reconcilePrs(config) {
|
|
|
786
793
|
if (existing && existing.prNumber == null) {
|
|
787
794
|
existing.prNumber = adoPr.pullRequestId;
|
|
788
795
|
}
|
|
796
|
+
if (existing && !existing.branch && branch) {
|
|
797
|
+
existing.branch = branch;
|
|
798
|
+
if (existing._pendingReason === 'missing_pr_branch') delete existing._pendingReason;
|
|
799
|
+
metadataUpdated++;
|
|
800
|
+
}
|
|
789
801
|
// PR already tracked — write link to pr-links.json if we can extract an ID
|
|
790
802
|
if (confirmedItemId) {
|
|
791
803
|
addPrLink(prId, confirmedItemId, { project, prNumber: adoPr.pullRequestId, url: prUrl });
|
|
@@ -832,7 +844,7 @@ async function reconcilePrs(config) {
|
|
|
832
844
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
833
845
|
const backfilled = shared.backfillPrPrdItems(existingPrs, shared.getPrLinks());
|
|
834
846
|
|
|
835
|
-
if (projectAdded > 0 || projectUpdated > 0 || backfilled > 0) {
|
|
847
|
+
if (projectAdded > 0 || projectUpdated > 0 || backfilled > 0 || metadataUpdated > 0) {
|
|
836
848
|
mutateJsonFileLocked(prPath, (currentPrs) => {
|
|
837
849
|
// Merge reconciled PRs into the locked copy by ID
|
|
838
850
|
for (const pr of existingPrs) {
|
package/engine/github.js
CHANGED
|
@@ -282,7 +282,10 @@ async function forEachActiveGhPr(config, callback) {
|
|
|
282
282
|
}
|
|
283
283
|
if (pr.description === undefined) pr.description = (prData.body || '').slice(0, 500);
|
|
284
284
|
if (pr.agent === 'human' && prData.user?.login) pr.agent = prData.user.login;
|
|
285
|
-
if (!pr.branch && prData.head?.ref)
|
|
285
|
+
if (!pr.branch && prData.head?.ref) {
|
|
286
|
+
pr.branch = prData.head.ref;
|
|
287
|
+
if (pr._pendingReason === 'missing_pr_branch') delete pr._pendingReason;
|
|
288
|
+
}
|
|
286
289
|
}
|
|
287
290
|
}
|
|
288
291
|
centralUpdated++;
|
|
@@ -673,6 +676,7 @@ async function reconcilePrs(config) {
|
|
|
673
676
|
const currentPrs = safeJson(prPath) || [];
|
|
674
677
|
shared.normalizePrRecords(currentPrs, project);
|
|
675
678
|
const existingIds = new Set(currentPrs.map(p => p.id));
|
|
679
|
+
let metadataUpdated = 0;
|
|
676
680
|
let projectAdded = 0;
|
|
677
681
|
|
|
678
682
|
// Load work items to match branches
|
|
@@ -697,6 +701,11 @@ async function reconcilePrs(config) {
|
|
|
697
701
|
if (existing && existing.prNumber == null) {
|
|
698
702
|
existing.prNumber = ghPr.number;
|
|
699
703
|
}
|
|
704
|
+
if (existing && !existing.branch && branch) {
|
|
705
|
+
existing.branch = branch;
|
|
706
|
+
if (existing._pendingReason === 'missing_pr_branch') delete existing._pendingReason;
|
|
707
|
+
metadataUpdated++;
|
|
708
|
+
}
|
|
700
709
|
if (confirmedItemId) {
|
|
701
710
|
addPrLink(prId, confirmedItemId, { project, prNumber: ghPr.number, url: prUrl });
|
|
702
711
|
if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
|
|
@@ -743,7 +752,7 @@ async function reconcilePrs(config) {
|
|
|
743
752
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
744
753
|
const backfilled = backfillPrPrdItems(currentPrs, getPrLinks());
|
|
745
754
|
|
|
746
|
-
if (projectAdded > 0 || backfilled > 0) {
|
|
755
|
+
if (projectAdded > 0 || backfilled > 0 || metadataUpdated > 0) {
|
|
747
756
|
mutateJsonFileLocked(prPath, (lockedPrs) => {
|
|
748
757
|
for (const pr of currentPrs) {
|
|
749
758
|
const idx = lockedPrs.findIndex(p => p.id === pr.id);
|
package/engine/lifecycle.js
CHANGED
|
@@ -744,13 +744,33 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
744
744
|
}
|
|
745
745
|
} catch {}
|
|
746
746
|
|
|
747
|
+
// prId → URL captured from inbox notes. Populated alongside prMatches so
|
|
748
|
+
// extractPrUrl below has a fallback when the agent's stdout doesn't contain
|
|
749
|
+
// the URL (the W-moljyu60wuzr / #1902 case — gh pr create ran in a sibling
|
|
750
|
+
// dispatch and only the inbox note carries the link).
|
|
751
|
+
const inboxUrls = new Map();
|
|
747
752
|
const today = dateStamp();
|
|
748
753
|
const inboxFiles = getInboxFiles().filter(f => f.includes(agentId) && f.includes(today));
|
|
749
754
|
for (const f of inboxFiles) {
|
|
750
755
|
const content = safeRead(path.join(INBOX_DIR, f));
|
|
751
756
|
if (!content) continue;
|
|
752
|
-
|
|
753
|
-
|
|
757
|
+
// Match a PR declaration line in the agent's findings note: optional bold,
|
|
758
|
+
// optional "Pull Request" spelling, line-anchored so "see PR https://..."
|
|
759
|
+
// mid-paragraph mentions don't trigger a false-positive. The protocol
|
|
760
|
+
// and host prefix is optional so "PR: https://github.com/..." ,
|
|
761
|
+
// "**PR:** github.com/...", etc. all match.
|
|
762
|
+
const prHeaderPattern = /(?:^|\n)\s*\*{0,2}(?:PR|Pull\s+Request)[:\*]*\*?\s*[#-]*\s*(?:https?:\/\/)?[^\s"]*?(?:(?:visualstudio\.com|dev\.azure\.com)[^\s"]*?pullrequest\/(\d+)|github\.com\/[^\s"]*?\/pull\/(\d+))/gi;
|
|
763
|
+
while ((match = prHeaderPattern.exec(content)) !== null) {
|
|
764
|
+
const prId = match[1] || match[2];
|
|
765
|
+
prMatches.add(prId);
|
|
766
|
+
// Pull the URL substring out of the matched chunk so we can hand it to
|
|
767
|
+
// extractPrUrl as a fallback. Prefer the first inbox URL we see for a
|
|
768
|
+
// given prId — later notes don't override the canonical record.
|
|
769
|
+
if (!inboxUrls.has(prId)) {
|
|
770
|
+
const urlMatch = match[0].match(/https?:\/\/[^\s"\\)]+/);
|
|
771
|
+
if (urlMatch) inboxUrls.set(prId, urlMatch[0].replace(/[.,;:]+$/, ''));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
754
774
|
}
|
|
755
775
|
|
|
756
776
|
if (prMatches.size === 0) return 0;
|
|
@@ -773,7 +793,10 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
773
793
|
return defaultProject;
|
|
774
794
|
}
|
|
775
795
|
|
|
776
|
-
// Extract PR URL directly from agent output — no manual construction
|
|
796
|
+
// Extract PR URL directly from agent output — no manual construction.
|
|
797
|
+
// Falls back to the URL captured from the inbox note when the agent stdout
|
|
798
|
+
// doesn't contain the link (gh pr create may have run in a sibling dispatch
|
|
799
|
+
// whose stdout was rotated; the inbox note is the durable artifact).
|
|
777
800
|
function extractPrUrl(prId) {
|
|
778
801
|
// Stop at backslash in addition to whitespace/quotes — raw JSONL encodes newlines as \n (literal
|
|
779
802
|
// backslash-n), so without this the regex would capture e.g. "pull/1804\n/usr/bin/bash".
|
|
@@ -781,7 +804,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
781
804
|
if (ghMatch) return ghMatch[0].replace(/[.,;:]+$/, '');
|
|
782
805
|
const adoMatch = output.match(new RegExp(`https?://(?:dev\\.azure\\.com|[^/]+\\.visualstudio\\.com)[^\\s"'\\)\\]\\\\]*?pullrequest/${prId}(?:[^\\s"'\\)\\]\\\\]*)`, 'i'));
|
|
783
806
|
if (adoMatch) return adoMatch[0].replace(/[.,;:]+$/, '');
|
|
784
|
-
return '';
|
|
807
|
+
return inboxUrls.get(prId) || '';
|
|
785
808
|
}
|
|
786
809
|
|
|
787
810
|
const agentName = config.agents?.[agentId]?.name || agentId;
|
package/engine.js
CHANGED
|
@@ -1980,6 +1980,58 @@ function clearPendingHumanFeedbackFlag(projectMeta, prId) {
|
|
|
1980
1980
|
} catch (e) { log('warn', 'clear pending human feedback flag: ' + e.message); }
|
|
1981
1981
|
}
|
|
1982
1982
|
|
|
1983
|
+
const PR_PENDING_MISSING_BRANCH = 'missing_pr_branch';
|
|
1984
|
+
|
|
1985
|
+
function getPrDispatchBranch(pr) {
|
|
1986
|
+
return typeof pr?.branch === 'string' ? pr.branch.trim() : '';
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function mutatePrPendingReason(project, pr, mutator) {
|
|
1990
|
+
const prPath = projectPrPath(project);
|
|
1991
|
+
let changed = false;
|
|
1992
|
+
mutatePullRequests(prPath, prs => {
|
|
1993
|
+
const target = shared.findPrRecord(prs, pr, project);
|
|
1994
|
+
if (!target) return prs;
|
|
1995
|
+
changed = mutator(target) === true;
|
|
1996
|
+
return prs;
|
|
1997
|
+
});
|
|
1998
|
+
return changed;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function markPrMissingBranch(project, pr, action) {
|
|
2002
|
+
if (pr._pendingReason === PR_PENDING_MISSING_BRANCH) return false;
|
|
2003
|
+
const changed = mutatePrPendingReason(project, pr, target => {
|
|
2004
|
+
if (target._pendingReason === PR_PENDING_MISSING_BRANCH) return false;
|
|
2005
|
+
target._pendingReason = PR_PENDING_MISSING_BRANCH;
|
|
2006
|
+
return true;
|
|
2007
|
+
});
|
|
2008
|
+
if (changed) {
|
|
2009
|
+
pr._pendingReason = PR_PENDING_MISSING_BRANCH;
|
|
2010
|
+
log('warn', `PR ${pr.id}: cannot dispatch ${action} — missing pr_branch; waiting for PR metadata enrichment`);
|
|
2011
|
+
}
|
|
2012
|
+
return changed;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function clearPrMissingBranch(project, pr) {
|
|
2016
|
+
if (pr._pendingReason !== PR_PENDING_MISSING_BRANCH) return false;
|
|
2017
|
+
const changed = mutatePrPendingReason(project, pr, target => {
|
|
2018
|
+
if (target._pendingReason !== PR_PENDING_MISSING_BRANCH) return false;
|
|
2019
|
+
delete target._pendingReason;
|
|
2020
|
+
return true;
|
|
2021
|
+
});
|
|
2022
|
+
if (changed) delete pr._pendingReason;
|
|
2023
|
+
return changed;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function canDispatchPrBranch(project, pr, action) {
|
|
2027
|
+
if (getPrDispatchBranch(pr)) {
|
|
2028
|
+
clearPrMissingBranch(project, pr);
|
|
2029
|
+
return true;
|
|
2030
|
+
}
|
|
2031
|
+
markPrMissingBranch(project, pr, action);
|
|
2032
|
+
return false;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1983
2035
|
/**
|
|
1984
2036
|
* Scan pull-requests.json for PRs needing review or fixes
|
|
1985
2037
|
*/
|
|
@@ -2021,6 +2073,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2021
2073
|
const prDisplayId = shared.getPrDisplayId(pr);
|
|
2022
2074
|
const prCanonicalId = shared.getCanonicalPrId(project, pr, pr.url || '');
|
|
2023
2075
|
if (activePrIds.has(prCanonicalId)) continue; // Skip PRs with active dispatch (prevent race)
|
|
2076
|
+
if (getPrDispatchBranch(pr)) clearPrMissingBranch(project, pr);
|
|
2024
2077
|
// Branch mutex: skip if PR branch is locked by any active dispatch (cross-type collision)
|
|
2025
2078
|
if (pr.branch && isBranchActive(pr.branch)) {
|
|
2026
2079
|
log('info', `Branch mutex: skipping PR ${pr.id} dispatch — branch ${pr.branch} locked by another agent`);
|
|
@@ -2061,6 +2114,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2061
2114
|
if (needsReview) {
|
|
2062
2115
|
const key = `review-${project?.name || 'default'}-${prDisplayId}`;
|
|
2063
2116
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2117
|
+
if (!canDispatchPrBranch(project, pr, 'review')) continue;
|
|
2064
2118
|
|
|
2065
2119
|
// Pre-dispatch live vote check — cached reviewStatus may be stale (poll lag ~6 min)
|
|
2066
2120
|
try {
|
|
@@ -2104,6 +2158,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2104
2158
|
// Skip isAlreadyDispatched — fixedAfterReview/lastReviewedAt already dedupe; the 1hr
|
|
2105
2159
|
// completed-dispatch window would block legitimate re-reviews within the hour after a fix
|
|
2106
2160
|
if (isOnCooldown(key, cooldownMs)) continue;
|
|
2161
|
+
if (!canDispatchPrBranch(project, pr, 're-review')) continue;
|
|
2107
2162
|
|
|
2108
2163
|
// Pre-dispatch live vote check — cached 'waiting' may be stale if reviewer already acted
|
|
2109
2164
|
try {
|
|
@@ -2140,6 +2195,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2140
2195
|
if (evalLoopEnabled && reviewStatus === 'changes-requested' && !awaitingReReview && !evalEscalated) {
|
|
2141
2196
|
const key = `fix-${project?.name || 'default'}-${prDisplayId}`;
|
|
2142
2197
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2198
|
+
if (!canDispatchPrBranch(project, pr, 'fix')) continue;
|
|
2143
2199
|
const agentId = resolveAgent('fix', config, { authorAgent: pr.agent });
|
|
2144
2200
|
if (!agentId) continue;
|
|
2145
2201
|
|
|
@@ -2179,6 +2235,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2179
2235
|
}
|
|
2180
2236
|
continue;
|
|
2181
2237
|
}
|
|
2238
|
+
if (!canDispatchPrBranch(project, pr, 'human-feedback fix')) continue;
|
|
2182
2239
|
const agentId = resolveAgent('fix', config, { authorAgent: pr.agent });
|
|
2183
2240
|
if (!agentId) continue;
|
|
2184
2241
|
|
|
@@ -2226,6 +2283,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2226
2283
|
|
|
2227
2284
|
const key = `build-fix-${project?.name || 'default'}-${prDisplayId}`;
|
|
2228
2285
|
if (fixThrottled || isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2286
|
+
if (!canDispatchPrBranch(project, pr, 'build fix')) continue;
|
|
2229
2287
|
|
|
2230
2288
|
// Pre-dispatch live build check — cached buildStatus may be stale: ADO can
|
|
2231
2289
|
// recompute the merge commit when master moves and pollPrStatus deliberately
|
|
@@ -2310,6 +2368,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2310
2368
|
const conflictFixedAt = pr._conflictFixedAt;
|
|
2311
2369
|
const withinLag = conflictFixedAt && Date.now() - new Date(conflictFixedAt).getTime() < 10 * 60 * 1000;
|
|
2312
2370
|
if (!withinLag && !fixThrottled && !isAlreadyDispatched(key) && !isOnCooldown(key, cooldownMs)) {
|
|
2371
|
+
if (!canDispatchPrBranch(project, pr, 'conflict fix')) continue;
|
|
2313
2372
|
// Pre-dispatch live conflict check — cached `_mergeConflict` may be
|
|
2314
2373
|
// stale: ADO/GitHub recompute mergeStatus asynchronously (1–5 min lag),
|
|
2315
2374
|
// so a successful upstream merge can leave the flag set even after the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1639",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|