@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 CHANGED
@@ -1,11 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.785 (2026-04-10)
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
- const prBuilds = (buildsData?.value || []).filter(b =>
366
- b.sourceVersion === mergeCommitId || b.sourceBranch === prData.sourceRefName
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 so next failure can trigger immediately
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 so next failure can trigger immediately
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, alert human
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
- if (evalCycles >= evalMax && !pr._evalEscalated) {
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) { newWork.push(item); setCooldown(key); }
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.785",
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"