@yemi33/minions 0.1.785 → 0.1.787
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 -1
- package/engine/ado.js +16 -4
- package/engine/github.js +14 -1
- package/engine.js +29 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.787 (2026-04-10)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- cap review→fix cycles at evalMaxIterations (default 3)
|
|
7
7
|
|
|
8
8
|
### Fixes
|
|
9
|
+
- 5 bugs from comprehensive audit
|
|
10
|
+
- stale ADO build detection + merge conflict auto-fix for both platforms
|
|
9
11
|
- don't overwrite reviewStatus when platform vote hasn't propagated
|
|
10
12
|
- don't overwrite approval with 'waiting' when platform API lags
|
|
11
13
|
|
package/engine/ado.js
CHANGED
|
@@ -362,9 +362,8 @@ async function pollPrStatus(config) {
|
|
|
362
362
|
// Find builds where sourceVersion matches the PR's merge commit
|
|
363
363
|
const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?$top=10&api-version=7.1`;
|
|
364
364
|
const buildsData = await adoFetch(buildsUrl, token);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
);
|
|
365
|
+
// Only match builds against the current merge commit — sourceBranch match catches stale builds
|
|
366
|
+
const prBuilds = (buildsData?.value || []).filter(b => b.sourceVersion === mergeCommitId);
|
|
368
367
|
|
|
369
368
|
if (prBuilds.length > 0) {
|
|
370
369
|
// partiallySucceeded = warnings, not failures — counts as passing
|
|
@@ -396,8 +395,9 @@ async function pollPrStatus(config) {
|
|
|
396
395
|
pr.buildStatus = buildStatus;
|
|
397
396
|
if (buildFailReason) pr.buildFailReason = buildFailReason;
|
|
398
397
|
else delete pr.buildFailReason;
|
|
399
|
-
// Build transitioned — clear grace period
|
|
398
|
+
// Build transitioned — clear grace period and auto-complete flag
|
|
400
399
|
delete pr._buildFixPushedAt;
|
|
400
|
+
if (buildStatus === 'failing') delete pr._autoCompleted;
|
|
401
401
|
if (buildStatus !== 'failing') {
|
|
402
402
|
delete pr._buildFailNotified;
|
|
403
403
|
delete pr.buildErrorLog;
|
|
@@ -447,6 +447,18 @@ async function pollPrStatus(config) {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// Merge conflict detection
|
|
451
|
+
if (prData.mergeStatus === 'conflicts') {
|
|
452
|
+
if (!pr._mergeConflict) {
|
|
453
|
+
pr._mergeConflict = true;
|
|
454
|
+
log('info', `PR ${pr.id} has merge conflicts — will dispatch fix if not already in progress`);
|
|
455
|
+
updated = true;
|
|
456
|
+
}
|
|
457
|
+
} else if (pr._mergeConflict) {
|
|
458
|
+
delete pr._mergeConflict;
|
|
459
|
+
updated = true;
|
|
460
|
+
}
|
|
461
|
+
|
|
450
462
|
return updated;
|
|
451
463
|
} catch (err) {
|
|
452
464
|
// Auth errors → mark build status stale so dashboard shows uncertainty
|
package/engine/github.js
CHANGED
|
@@ -400,8 +400,9 @@ async function pollPrStatus(config) {
|
|
|
400
400
|
pr.buildStatus = buildStatus;
|
|
401
401
|
if (buildFailReason) pr.buildFailReason = buildFailReason;
|
|
402
402
|
else delete pr.buildFailReason;
|
|
403
|
-
// Build transitioned — clear grace period
|
|
403
|
+
// Build transitioned — clear grace period and auto-complete flag
|
|
404
404
|
delete pr._buildFixPushedAt;
|
|
405
|
+
if (buildStatus === 'failing') delete pr._autoCompleted; // allow re-merge after fix
|
|
405
406
|
if (buildStatus !== 'failing') {
|
|
406
407
|
delete pr._buildFailNotified;
|
|
407
408
|
delete pr.buildErrorLog;
|
|
@@ -423,6 +424,18 @@ async function pollPrStatus(config) {
|
|
|
423
424
|
}
|
|
424
425
|
}
|
|
425
426
|
|
|
427
|
+
// Merge conflict detection
|
|
428
|
+
if (prData.state === 'open' && prData.mergeable === false) {
|
|
429
|
+
if (!pr._mergeConflict) {
|
|
430
|
+
pr._mergeConflict = true;
|
|
431
|
+
log('info', `PR ${pr.id} has merge conflicts — will dispatch fix if not already in progress`);
|
|
432
|
+
updated = true;
|
|
433
|
+
}
|
|
434
|
+
} else if (pr._mergeConflict) {
|
|
435
|
+
delete pr._mergeConflict;
|
|
436
|
+
updated = true;
|
|
437
|
+
}
|
|
438
|
+
|
|
426
439
|
// Auto-complete: merge PR when builds green + review approved
|
|
427
440
|
if (pr.status === PR_STATUS.ACTIVE && pr.reviewStatus === 'approved' && pr.buildStatus === 'passing' && !pr._autoCompleted) {
|
|
428
441
|
const autoComplete = config.engine?.autoCompletePrs === true; // opt-in
|
package/engine.js
CHANGED
|
@@ -1576,10 +1576,11 @@ async function discoverFromPrs(config, project) {
|
|
|
1576
1576
|
// The poller holds reviewStatus at 'waiting' until the reviewer acts on the new code.
|
|
1577
1577
|
const awaitingReReview = reviewStatus === 'waiting' && !!pr.minionsReview?.fixedAt;
|
|
1578
1578
|
|
|
1579
|
-
// Review→fix cycle cap — stop after N iterations,
|
|
1579
|
+
// Review→fix cycle cap — stop review/fix dispatch after N iterations, but allow build fixes and conflict fixes
|
|
1580
1580
|
const evalMax = config.engine?.evalMaxIterations ?? DEFAULTS.evalMaxIterations;
|
|
1581
1581
|
const evalCycles = pr._reviewFixCycles || 0;
|
|
1582
|
-
|
|
1582
|
+
const evalEscalated = evalCycles >= evalMax;
|
|
1583
|
+
if (evalEscalated && !pr._evalEscalated) {
|
|
1583
1584
|
writeInboxAlert(`eval-escalated-${pr.agent || 'unassigned'}-${pr.id}`,
|
|
1584
1585
|
`# Review Loop Escalation\n\n**PR ${pr.id}** on branch \`${pr.branch || 'unknown'}\` has gone through **${evalCycles}** review→fix cycles without approval.\n\n` +
|
|
1585
1586
|
`Last review: ${pr.minionsReview?.note ? pr.minionsReview.note.slice(0, 200) : 'See PR thread'}\n\n` +
|
|
@@ -1591,13 +1592,12 @@ async function discoverFromPrs(config, project) {
|
|
|
1591
1592
|
});
|
|
1592
1593
|
} catch (e) { log('warn', 'mark eval escalated: ' + e.message); }
|
|
1593
1594
|
log('warn', `PR ${pr.id}: review→fix escalated after ${evalCycles} cycles — suspending auto-dispatch`);
|
|
1594
|
-
continue;
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
1597
|
// PRs needing review: pending review status and not already reviewed without new commits
|
|
1598
1598
|
const autoReview = config.engine?.autoReview !== false;
|
|
1599
1599
|
const alreadyReviewed = pr.lastReviewedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.lastReviewedAt);
|
|
1600
|
-
const needsReview = autoReview && reviewStatus === 'pending' && !alreadyReviewed;
|
|
1600
|
+
const needsReview = autoReview && reviewStatus === 'pending' && !alreadyReviewed && !evalEscalated;
|
|
1601
1601
|
if (needsReview) {
|
|
1602
1602
|
const key = `review-${project?.name || 'default'}-${pr.id}`;
|
|
1603
1603
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
@@ -1634,7 +1634,7 @@ async function discoverFromPrs(config, project) {
|
|
|
1634
1634
|
|
|
1635
1635
|
// PRs with changes requested → route back to author for fix
|
|
1636
1636
|
let fixDispatched = false;
|
|
1637
|
-
if (reviewStatus === 'changes-requested' && !awaitingReReview) {
|
|
1637
|
+
if (reviewStatus === 'changes-requested' && !awaitingReReview && !evalEscalated) {
|
|
1638
1638
|
const key = `fix-${project?.name || 'default'}-${pr.id}`;
|
|
1639
1639
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
1640
1640
|
const agentId = resolveAgent('fix', config, pr.agent);
|
|
@@ -1683,7 +1683,15 @@ async function discoverFromPrs(config, project) {
|
|
|
1683
1683
|
reviewer: 'Human Reviewer',
|
|
1684
1684
|
review_note: reviewNote,
|
|
1685
1685
|
}, `Fix PR ${pr.id} — human feedback`, { dispatchKey: key, source: 'pr-human-feedback', pr, branch: pr.branch, project: projMeta });
|
|
1686
|
-
if (item) {
|
|
1686
|
+
if (item) {
|
|
1687
|
+
newWork.push(item); setCooldown(key); fixDispatched = true;
|
|
1688
|
+
try {
|
|
1689
|
+
mutatePullRequests(projectPrPath(project), prs => {
|
|
1690
|
+
const target = prs.find(p => p.id === pr.id);
|
|
1691
|
+
if (target) target._reviewFixCycles = (target._reviewFixCycles || 0) + 1;
|
|
1692
|
+
});
|
|
1693
|
+
} catch (e) { log('warn', 'increment review-fix cycles (human): ' + e.message); }
|
|
1694
|
+
}
|
|
1687
1695
|
}
|
|
1688
1696
|
|
|
1689
1697
|
// PRs with build failures — route to author (has session context from implementing)
|
|
@@ -1771,6 +1779,21 @@ async function discoverFromPrs(config, project) {
|
|
|
1771
1779
|
}
|
|
1772
1780
|
}
|
|
1773
1781
|
|
|
1782
|
+
// PRs with merge conflicts — dispatch fix to resolve
|
|
1783
|
+
if (pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched) {
|
|
1784
|
+
const key = `conflict-fix-${project?.name || 'default'}-${pr.id}`;
|
|
1785
|
+
if (!isAlreadyDispatched(key) && !isOnCooldown(key, cooldownMs)) {
|
|
1786
|
+
const agentId = resolveAgent('fix', config, pr.agent);
|
|
1787
|
+
if (agentId) {
|
|
1788
|
+
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
1789
|
+
pr_id: pr.id, pr_branch: pr.branch || '',
|
|
1790
|
+
review_note: `This PR has merge conflicts with the target branch. Resolve the conflicts:\n\n1. Pull latest from main/master\n2. Resolve all conflicts (prefer PR branch changes unless main has critical fixes)\n3. Build and test after resolving\n4. Push the resolved branch`,
|
|
1791
|
+
}, `Fix merge conflicts on PR ${pr.id}`, { dispatchKey: key, source: 'pr', pr, branch: pr.branch, project: projMeta });
|
|
1792
|
+
if (item) { newWork.push(item); setCooldown(key); }
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1774
1797
|
}
|
|
1775
1798
|
// Build & test now runs once at PRD completion (via verify task), not per-PR.
|
|
1776
1799
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.787",
|
|
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"
|