@yemi33/minions 0.1.1729 → 0.1.1731

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,10 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1729 (2026-05-05)
3
+ ## 0.1.1731 (2026-05-05)
4
+
5
+ ### Features
6
+ - fix project discovery main branch fallback (#2085)
7
+ - detect no-op PR fixes (#2077)
8
+
9
+ ## 0.1.1728 (2026-05-05)
4
10
 
5
11
  ### Features
6
- - suppress comment-only fix loops (#2076)
7
- - Allow explicit doc chat dispatch requests (#2072)
8
12
  - redact repository URLs in filed issues (#2069)
9
13
 
10
14
  ## 0.1.1727 (2026-05-05)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-05T09:47:21.371Z"
4
+ "cachedAt": "2026-05-05T09:51:05.191Z"
5
5
  }
@@ -1466,16 +1466,153 @@ function fixCompletionChangedBranch(structuredCompletion) {
1466
1466
  return true;
1467
1467
  }
1468
1468
 
1469
- function updatePrAfterFix(pr, project, source, opts = {}, dispatchId = '') {
1469
+ function normalizePrFixBranchName(branch) {
1470
+ return String(branch || '').trim().replace(/^refs\/heads\//, '');
1471
+ }
1472
+
1473
+ function getPrFixBaselineHead(pr) {
1474
+ return String(pr?.headSha || pr?._adoSourceCommit || pr?._adoHeadCommit || '').trim();
1475
+ }
1476
+
1477
+ function findPrFixWorktree(meta, project, config) {
1478
+ const branch = normalizePrFixBranchName(meta?.branch || meta?.pr?.branch);
1479
+ const rootDir = project?.localPath ? path.resolve(project.localPath) : null;
1480
+ if (!branch || !rootDir) return null;
1481
+ const worktreeRoot = path.resolve(rootDir, config?.engine?.worktreeRoot || ENGINE_DEFAULTS.worktreeRoot);
1482
+ try {
1483
+ if (!fs.existsSync(worktreeRoot)) return null;
1484
+ for (const dir of fs.readdirSync(worktreeRoot)) {
1485
+ const wtPath = path.join(worktreeRoot, dir);
1486
+ if (!fs.statSync(wtPath).isDirectory()) continue;
1487
+ if (worktreeMatchesBranch(dir.toLowerCase(), branch, getWorktreeBranch(wtPath))) return wtPath;
1488
+ }
1489
+ } catch (err) {
1490
+ log('warn', `PR fix no-op worktree lookup for ${meta?.pr?.id || branch}: ${err.message}`);
1491
+ }
1492
+ return null;
1493
+ }
1494
+
1495
+ async function gitRevParse(cwd, ref) {
1496
+ try {
1497
+ const out = await runFileCapture('git', ['rev-parse', '--verify', `${ref}^{commit}`], { cwd, timeout: 10000 });
1498
+ return String(out || '').trim();
1499
+ } catch {
1500
+ return '';
1501
+ }
1502
+ }
1503
+
1504
+ async function detectPrFixBranchChange(meta, config) {
1505
+ const branch = normalizePrFixBranchName(meta?.branch || meta?.pr?.branch);
1506
+ const project = resolvePrFallbackProject(meta, config) || meta?.project || null;
1507
+ const rootDir = project?.localPath ? path.resolve(project.localPath) : null;
1508
+ const beforeHead = getPrFixBaselineHead(meta?.pr);
1509
+ if (!meta?.pr?.id || !branch || !rootDir || !beforeHead) {
1510
+ return { changed: null, reason: 'missing PR branch baseline' };
1511
+ }
1512
+
1513
+ const worktreePath = findPrFixWorktree(meta, project, config);
1514
+ if (worktreePath) {
1515
+ try {
1516
+ const dirty = await runFileCapture('git', ['status', '--porcelain'], { cwd: worktreePath, timeout: 10000 });
1517
+ if (String(dirty || '').trim()) {
1518
+ return { changed: true, beforeHead, afterHead: beforeHead, evidence: 'worktree-diff' };
1519
+ }
1520
+ } catch (err) {
1521
+ log('warn', `PR fix no-op dirty check for ${meta.pr.id}: ${err.message}`);
1522
+ }
1523
+ }
1524
+
1525
+ let fetched = false;
1526
+ try {
1527
+ await runFileCapture('git', ['fetch', 'origin', branch], { cwd: rootDir, timeout: 30000 });
1528
+ fetched = true;
1529
+ } catch (err) {
1530
+ log('warn', `PR fix no-op fetch for ${meta.pr.id} (${branch}) failed: ${err.message}`);
1531
+ }
1470
1532
 
1533
+ const remoteHead = await gitRevParse(rootDir, `refs/remotes/origin/${branch}`);
1534
+ if (remoteHead && remoteHead !== beforeHead) {
1535
+ return { changed: true, beforeHead, afterHead: remoteHead, evidence: 'remote-head' };
1536
+ }
1537
+
1538
+ if (worktreePath) {
1539
+ const localHead = await gitRevParse(worktreePath, 'HEAD');
1540
+ if (localHead && localHead !== beforeHead) {
1541
+ return { changed: true, beforeHead, afterHead: localHead, evidence: 'local-head' };
1542
+ }
1543
+ }
1544
+
1545
+ if (fetched && remoteHead && remoteHead === beforeHead) {
1546
+ return { changed: false, beforeHead, afterHead: remoteHead, evidence: 'remote-head' };
1547
+ }
1548
+
1549
+ return { changed: null, beforeHead, afterHead: remoteHead || '', reason: 'unable to prove branch head after fix' };
1550
+ }
1551
+
1552
+ function recordPrNoOpFixAttempt(target, cause, source, dispatchItem, branchChange, config) {
1553
+ const evidenceFingerprint = shared.prFixEvidenceFingerprint(target, cause);
1554
+ const prior = shared.getPrNoOpFixRecord(target, cause);
1555
+ const sameEvidence = prior?.evidenceFingerprint === evidenceFingerprint;
1556
+ const count = sameEvidence ? (Number(prior.count) || 0) + 1 : 1;
1557
+ const pauseAfter = Number(config?.engine?.prNoOpFixPauseAttempts) || ENGINE_DEFAULTS.prNoOpFixPauseAttempts;
1558
+ const paused = count >= pauseAfter;
1559
+ const now = ts();
1560
+ target._noOpFixes = target._noOpFixes && typeof target._noOpFixes === 'object' ? target._noOpFixes : {};
1561
+ target._noOpFixes[cause] = {
1562
+ count,
1563
+ paused,
1564
+ evidenceFingerprint,
1565
+ firstAt: sameEvidence ? (prior.firstAt || now) : now,
1566
+ lastAt: now,
1567
+ dispatchId: dispatchItem?.id || null,
1568
+ dispatchKey: dispatchItem?.meta?.dispatchKey || null,
1569
+ source: source || null,
1570
+ beforeHead: branchChange?.beforeHead || '',
1571
+ afterHead: branchChange?.afterHead || '',
1572
+ reason: branchChange?.reason || 'fix completed without changing the PR branch',
1573
+ };
1574
+ target._lastNoOpFix = {
1575
+ cause,
1576
+ at: now,
1577
+ paused,
1578
+ dispatchId: dispatchItem?.id || null,
1579
+ beforeHead: branchChange?.beforeHead || '',
1580
+ afterHead: branchChange?.afterHead || '',
1581
+ };
1582
+
1583
+ if (cause === shared.PR_FIX_CAUSE.HUMAN_FEEDBACK && target.humanFeedback) {
1584
+ target.humanFeedback.pendingFix = !paused;
1585
+ if (paused) target.humanFeedback.noOpPaused = true;
1586
+ else delete target.humanFeedback.noOpPaused;
1587
+ }
1588
+ if (cause === shared.PR_FIX_CAUSE.BUILD_FAILURE) delete target._buildFixPushedAt;
1589
+ if (cause === shared.PR_FIX_CAUSE.MERGE_CONFLICT) delete target._conflictFixedAt;
1590
+ return target._noOpFixes[cause];
1591
+ }
1592
+
1593
+ function clearPrNoOpFixAttempt(target, cause) {
1594
+ if (!target?._noOpFixes || !target._noOpFixes[cause]) return;
1595
+ delete target._noOpFixes[cause];
1596
+ if (Object.keys(target._noOpFixes).length === 0) delete target._noOpFixes;
1597
+ if (target._lastNoOpFix?.cause === cause) delete target._lastNoOpFix;
1598
+ if (target.humanFeedback) delete target.humanFeedback.noOpPaused;
1599
+ }
1600
+
1601
+ function updatePrAfterFix(pr, project, source, options = {}, legacyDispatchId = '') {
1471
1602
  if (!pr?.id) return;
1472
- const options = opts && typeof opts === 'object' && !Array.isArray(opts)
1473
- ? opts
1474
- : { automationCauseKey: opts, dispatchId };
1475
- const branchChanged = options.branchChanged !== false;
1476
- const automationCauseKey = options.automationCauseKey || '';
1477
- const fixDispatchId = options.dispatchId || dispatchId || '';
1603
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
1604
+ options = { automationCauseKey: options, dispatchId: legacyDispatchId };
1605
+ }
1606
+ const explicitlyChangedBranch = options.branchChanged !== false;
1478
1607
  const prPath = project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json');
1608
+ const automationCauseKey = options.automationCauseKey || options.dispatchItem?.meta?.automationCauseKey || '';
1609
+ const fixDispatchId = options.dispatchItem?.id || options.dispatchId || legacyDispatchId || '';
1610
+ const cause = shared.getPrFixAutomationCause({
1611
+ dispatchKey: options.dispatchItem?.meta?.dispatchKey,
1612
+ source,
1613
+ task: options.dispatchItem?.task,
1614
+ });
1615
+ let result = null;
1479
1616
  shared.mutateJsonFileLocked(prPath, (prs) => {
1480
1617
  if (!Array.isArray(prs)) return prs;
1481
1618
  const target = shared.findPrRecord(prs, pr, project);
@@ -1485,10 +1622,17 @@ function updatePrAfterFix(pr, project, source, opts = {}, dispatchId = '') {
1485
1622
  delete next.fixedAt;
1486
1623
  target.minionsReview = next;
1487
1624
  };
1625
+ if (explicitlyChangedBranch && options.branchChange?.changed === false) {
1626
+ const record = recordPrNoOpFixAttempt(target, cause, source, options.dispatchItem, options.branchChange, options.config);
1627
+ result = { noOp: true, cause, paused: !!record.paused, count: record.count };
1628
+ log('warn', `Updated ${pr.id} → recorded no-op ${cause} fix attempt ${record.count}${record.paused ? ' (paused)' : ''}; PR branch was unchanged`);
1629
+ return prs;
1630
+ }
1631
+ clearPrNoOpFixAttempt(target, cause);
1488
1632
  if (source === 'pr-human-feedback') {
1489
1633
  const clearPendingFix = shouldClearHumanFeedbackPendingFix(target, pr, automationCauseKey);
1490
1634
  if (target.humanFeedback && clearPendingFix) target.humanFeedback.pendingFix = false;
1491
- if (branchChanged) {
1635
+ if (explicitlyChangedBranch) {
1492
1636
  // Never downgrade from approved — fix was dispatched but PR is already approved
1493
1637
  if (target.reviewStatus !== 'approved') target.reviewStatus = 'waiting';
1494
1638
  target.minionsReview = { ...target.minionsReview, note: 'Fixed human feedback, awaiting re-review', fixedAt: ts() };
@@ -1507,7 +1651,7 @@ function updatePrAfterFix(pr, project, source, opts = {}, dispatchId = '') {
1507
1651
  }
1508
1652
  } else {
1509
1653
  if (target.reviewStatus !== 'approved') target.reviewStatus = 'waiting';
1510
- if (branchChanged) {
1654
+ if (explicitlyChangedBranch) {
1511
1655
  target.minionsReview = { ...target.minionsReview, note: 'Fixed, awaiting re-review', fixedAt: ts() };
1512
1656
  log('info', `Updated ${pr.id} → reviewStatus: waiting (fix pushed)`);
1513
1657
  } else {
@@ -1523,8 +1667,10 @@ function updatePrAfterFix(pr, project, source, opts = {}, dispatchId = '') {
1523
1667
  handledAt: ts(),
1524
1668
  });
1525
1669
  }
1670
+ result = { noOp: false, cause };
1526
1671
  return prs;
1527
1672
  }, { defaultValue: [] });
1673
+ return result;
1528
1674
  }
1529
1675
 
1530
1676
  // ─── Post-Merge Rebase ──────────────────────────────────────────────────────
@@ -2754,6 +2900,16 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
2754
2900
 
2755
2901
  // Archive is manual — user archives plans from the dashboard when ready
2756
2902
 
2903
+ let prFixBranchChange = null;
2904
+ if (type === WORK_TYPE.FIX && effectiveSuccess && meta?.pr?.id) {
2905
+ try {
2906
+ prFixBranchChange = await detectPrFixBranchChange(meta, config);
2907
+ } catch (err) {
2908
+ log('warn', `PR fix no-op detection for ${meta.pr.id}: ${err.message}`);
2909
+ prFixBranchChange = { changed: null, reason: err.message };
2910
+ }
2911
+ }
2912
+
2757
2913
  // Scheduled task back-reference: update schedule-runs.json and write linked inbox note
2758
2914
  if (meta?.item?._scheduleId) {
2759
2915
  try {
@@ -2839,7 +2995,9 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
2839
2995
  updatePrAfterFix(meta?.pr, meta?.project, meta?.source, {
2840
2996
  branchChanged: fixCompletionChangedBranch(structuredCompletion),
2841
2997
  automationCauseKey: meta?.automationCauseKey,
2842
- dispatchId: dispatchItem?.id,
2998
+ dispatchItem,
2999
+ branchChange: prFixBranchChange,
3000
+ config,
2843
3001
  });
2844
3002
  // (#984) Sync PRD status for PR-linked features: fix work items have a different ID
2845
3003
  // than the original PRD feature, so syncPrdItemStatus(fixWiId, ...) finds nothing.
@@ -245,23 +245,33 @@ function execGit(execFileSync, targetDir, args, timeout = 5000) {
245
245
  })).trim();
246
246
  }
247
247
 
248
+ function parseOriginHeadBranch(headRef) {
249
+ const branch = String(headRef || '').trim().replace(/^refs\/remotes\/origin\//, '');
250
+ return branch && branch !== 'HEAD' ? branch : '';
251
+ }
252
+
253
+ function discoverMainBranch(execFileSync, targetDir) {
254
+ try {
255
+ return parseOriginHeadBranch(execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']));
256
+ } catch {}
257
+
258
+ try {
259
+ execGit(execFileSync, targetDir, ['remote', 'set-head', 'origin', '-a'], 10000);
260
+ return parseOriginHeadBranch(execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']));
261
+ } catch {}
262
+
263
+ return '';
264
+ }
265
+
248
266
  function discoverProjectMetadata(targetDir, options = {}) {
249
267
  const execFileSync = options.execFileSync || defaultExecFileSync;
250
268
  const result = { _found: [] };
251
269
 
252
- try {
253
- let head = '';
254
- try {
255
- head = execGit(execFileSync, targetDir, ['symbolic-ref', 'refs/remotes/origin/HEAD']);
256
- } catch {
257
- head = execGit(execFileSync, targetDir, ['symbolic-ref', 'HEAD']);
258
- }
259
- const branch = head.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
260
- if (branch) {
261
- result.mainBranch = branch;
262
- result._found.push('main branch');
263
- }
264
- } catch {}
270
+ const branch = discoverMainBranch(execFileSync, targetDir);
271
+ if (branch) {
272
+ result.mainBranch = branch;
273
+ result._found.push('main branch');
274
+ }
265
275
 
266
276
  try {
267
277
  const remoteUrl = execGit(execFileSync, targetDir, ['remote', 'get-url', 'origin']);
package/engine/shared.js CHANGED
@@ -834,6 +834,7 @@ const ENGINE_DEFAULTS = {
834
834
  autoReReviewPrs: true, // auto-dispatch review agents after a PR fix is pushed
835
835
  autoFixReviewFeedback: true, // auto-dispatch fix agents for minions review changes-requested verdicts
836
836
  autoFixHumanComments: true, // auto-dispatch fix agents for actionable human PR comments
837
+ prNoOpFixPauseAttempts: 2, // pause one PR automation cause after repeated no-op fixes for unchanged evidence
837
838
  completionReportRetentionDays: 90, // retain completion report sidecars beyond capped dispatch history
838
839
  completionReportMaxFiles: 5000, // hard cap for completion report sidecars during cleanup
839
840
  meetingRoundTimeout: 900000, // 15min per meeting round before auto-advance
@@ -2492,6 +2493,62 @@ const _WIN_RESERVED_NAMES = new Set([
2492
2493
  'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
2493
2494
  ]);
2494
2495
 
2496
+ const PR_FIX_CAUSE = {
2497
+ HUMAN_FEEDBACK: 'human-feedback',
2498
+ REVIEW_FEEDBACK: 'review-feedback',
2499
+ BUILD_FAILURE: 'build-failure',
2500
+ MERGE_CONFLICT: 'merge-conflict',
2501
+ UNKNOWN: 'pr-fix',
2502
+ };
2503
+
2504
+ function getPrFixAutomationCause({ dispatchKey = '', source = '', task = '' } = {}) {
2505
+ const key = String(dispatchKey || '').toLowerCase();
2506
+ const src = String(source || '').toLowerCase();
2507
+ const title = String(task || '').toLowerCase();
2508
+ if (src === 'pr-human-feedback' || key.startsWith('human-fix-') || title.includes('human feedback')) return PR_FIX_CAUSE.HUMAN_FEEDBACK;
2509
+ if (key.startsWith('build-fix-') || title.includes('build failure')) return PR_FIX_CAUSE.BUILD_FAILURE;
2510
+ if (key.startsWith('conflict-fix-') || title.includes('merge conflict')) return PR_FIX_CAUSE.MERGE_CONFLICT;
2511
+ if (key.startsWith('fix-') || src === 'pr') return PR_FIX_CAUSE.REVIEW_FEEDBACK;
2512
+ return PR_FIX_CAUSE.UNKNOWN;
2513
+ }
2514
+
2515
+ function prFixEvidenceFingerprint(pr, cause = PR_FIX_CAUSE.UNKNOWN) {
2516
+ const review = pr?.minionsReview || {};
2517
+ const feedback = pr?.humanFeedback || {};
2518
+ const evidence = { cause };
2519
+ if (cause === PR_FIX_CAUSE.HUMAN_FEEDBACK) {
2520
+ evidence.lastProcessedCommentDate = feedback.lastProcessedCommentDate || '';
2521
+ evidence.feedbackContent = feedback.feedbackContent || '';
2522
+ } else if (cause === PR_FIX_CAUSE.BUILD_FAILURE) {
2523
+ evidence.buildStatus = pr?.buildStatus || '';
2524
+ evidence.buildFailReason = pr?.buildFailReason || '';
2525
+ evidence.buildErrorLog = pr?.buildErrorLog || '';
2526
+ evidence.buildStatusDetail = pr?._buildStatusDetail || '';
2527
+ } else if (cause === PR_FIX_CAUSE.MERGE_CONFLICT) {
2528
+ evidence.mergeConflict = !!pr?._mergeConflict;
2529
+ evidence.mergeStatus = pr?.mergeStatus || '';
2530
+ evidence.mergeConflictDetail = pr?._mergeConflictDetail || '';
2531
+ } else {
2532
+ evidence.reviewStatus = pr?.reviewStatus || '';
2533
+ evidence.lastReviewedAt = pr?.lastReviewedAt || '';
2534
+ evidence.reviewedAt = review.reviewedAt || '';
2535
+ evidence.reviewNote = review.note || pr?.reviewNote || '';
2536
+ }
2537
+ return crypto.createHash('sha1').update(JSON.stringify(evidence)).digest('hex').slice(0, 16);
2538
+ }
2539
+
2540
+ function getPrNoOpFixRecord(pr, cause) {
2541
+ if (!pr || !cause || !pr._noOpFixes || typeof pr._noOpFixes !== 'object') return null;
2542
+ const record = pr._noOpFixes[cause];
2543
+ return record && typeof record === 'object' ? record : null;
2544
+ }
2545
+
2546
+ function isPrNoOpFixCausePaused(pr, cause) {
2547
+ const record = getPrNoOpFixRecord(pr, cause);
2548
+ if (!record?.paused) return false;
2549
+ return record.evidenceFingerprint === prFixEvidenceFingerprint(pr, cause);
2550
+ }
2551
+
2495
2552
  /**
2496
2553
  * Recursively purge Windows reserved-name pseudo-files (NUL, CON, PRN, AUX, etc.)
2497
2554
  * using the \\?\ extended path prefix that bypasses reserved-name interpretation.
@@ -2787,6 +2844,11 @@ module.exports = {
2787
2844
  validateProjectName,
2788
2845
  validateProjectPath,
2789
2846
  validatePid,
2847
+ PR_FIX_CAUSE,
2848
+ getPrFixAutomationCause,
2849
+ prFixEvidenceFingerprint,
2850
+ getPrNoOpFixRecord,
2851
+ isPrNoOpFixCausePaused,
2790
2852
  parseSkillFrontmatter,
2791
2853
  sleepMs,
2792
2854
  killGracefully,
package/engine.js CHANGED
@@ -2214,6 +2214,12 @@ function clearPendingHumanFeedbackFlag(projectMeta, prId) {
2214
2214
  } catch (e) { log('warn', 'clear pending human feedback flag: ' + e.message); }
2215
2215
  }
2216
2216
 
2217
+ function isPrNoOpFixCauseSuppressed(pr, cause) {
2218
+ if (!shared.isPrNoOpFixCausePaused(pr, cause)) return false;
2219
+ log('warn', `PR ${pr.id}: suppressing ${cause} automation after repeated no-op fix attempts; waiting for human resume or new evidence`);
2220
+ return true;
2221
+ }
2222
+
2217
2223
  const PR_PENDING_MISSING_BRANCH = shared.PR_PENDING_REASON.MISSING_BRANCH;
2218
2224
 
2219
2225
  function normalizePrBranch(value) {
@@ -2539,7 +2545,8 @@ async function discoverFromPrs(config, project) {
2539
2545
  const humanCauseKey = getPrAutomationCauseKey('human-comment', pr);
2540
2546
  const humanFixKey = getPrAutomationDispatchKey(humanFixBaseKey, humanCauseKey);
2541
2547
  const hasCoalescedFeedback = (dispatchCooldowns.get(humanFixKey)?.pendingContexts || []).length > 0;
2542
- if (pollEnabled && autoFixHumanComments && (pr.humanFeedback?.pendingFix || hasCoalescedFeedback) && !fixDispatched) {
2548
+ if (pollEnabled && autoFixHumanComments && (pr.humanFeedback?.pendingFix || hasCoalescedFeedback) && !fixDispatched
2549
+ && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.HUMAN_FEEDBACK)) {
2543
2550
  const key = humanFixKey;
2544
2551
  if (isPrAutomationCauseHandledOrPending(project, pr, humanCauseKey)) continue;
2545
2552
  let staleCoalesced = [];
@@ -2636,7 +2643,8 @@ async function discoverFromPrs(config, project) {
2636
2643
 
2637
2644
  // PRs with changes requested → route back to author for fix.
2638
2645
  // Gate on evalLoopEnabled and provider polling — the review→fix cycle depends on fresh vote state.
2639
- if (evalLoopEnabled && pollEnabled && autoFixReviewFeedback && reviewStatus === 'changes-requested' && !awaitingReReview && !fixDispatched) {
2646
+ if (evalLoopEnabled && pollEnabled && autoFixReviewFeedback && reviewStatus === 'changes-requested' && !awaitingReReview && !fixDispatched
2647
+ && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.REVIEW_FEEDBACK)) {
2640
2648
  const reviewCauseKey = getPrAutomationCauseKey('review-feedback', pr);
2641
2649
  const key = getPrAutomationDispatchKey(`fix-${project?.name || 'default'}-${prDisplayId}`, reviewCauseKey);
2642
2650
  if (isPrAutomationCauseHandledOrPending(project, pr, reviewCauseKey)) continue;
@@ -2663,7 +2671,8 @@ async function discoverFromPrs(config, project) {
2663
2671
  if (Date.now() - new Date(pr._buildFixPushedAt).getTime() < gracePeriodMs) continue;
2664
2672
  }
2665
2673
  const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
2666
- if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing') {
2674
+ if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing'
2675
+ && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.BUILD_FAILURE)) {
2667
2676
  const buildCauseKey = getPrAutomationCauseKey('build', pr);
2668
2677
  const key = getPrAutomationDispatchKey(`build-fix-${project?.name || 'default'}-${prDisplayId}`, buildCauseKey);
2669
2678
  if (isPrAutomationCauseHandledOrPending(project, pr, buildCauseKey)) continue;
@@ -2757,7 +2766,8 @@ async function discoverFromPrs(config, project) {
2757
2766
 
2758
2767
  // PRs with merge conflicts — dispatch fix to resolve (gated by provider polling + autoFixConflicts)
2759
2768
  const autoFixConflicts = config.engine?.autoFixConflicts ?? ENGINE_DEFAULTS.autoFixConflicts;
2760
- if (pollEnabled && autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched) {
2769
+ if (pollEnabled && autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched
2770
+ && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.MERGE_CONFLICT)) {
2761
2771
  const conflictCauseKey = getPrAutomationCauseKey('merge-conflict', pr);
2762
2772
  const key = getPrAutomationDispatchKey(`conflict-fix-${project?.name || 'default'}-${prDisplayId}`, conflictCauseKey);
2763
2773
  // Suppress re-dispatch for 10 min after last attempt — ADO/GitHub recomputes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1729",
3
+ "version": "0.1.1731",
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"