@yemi33/minions 0.1.973 → 0.1.975

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.973 (2026-04-14)
3
+ ## 0.1.975 (2026-04-14)
4
4
 
5
5
  ### Features
6
6
  - flush queued CC messages as single combined request
@@ -25,6 +25,7 @@
25
25
  - replace magic string 'active' with PR_STATUS.ACTIVE in lifecycle.js (#969)
26
26
 
27
27
  ### Fixes
28
+ - move review verdict check before updateWorkItemStatus(DONE)
28
29
  - skip isAlreadyDispatched in needsReReview to allow re-review within 1hr
29
30
  - loop queue flush so messages queued during combined send aren't orphaned
30
31
  - set lastPushedAt on fix completion to unblock re-review without poller
@@ -44,7 +45,6 @@
44
45
  - add autoFixConflicts to boolean settings persist list
45
46
  - tighten build query guard to require mergeCommitId
46
47
  - restore sourceVersion filter after refs/pull merge ref switch
47
- - use refs/pull/{id}/merge for build status scoping
48
48
 
49
49
  ### Other
50
50
  - refactor(dashboard): extract _releaseCCTab helper and CC_LOCK_WAIT_MS constant
@@ -940,8 +940,6 @@ function updatePrAfterFix(pr, project, source) {
940
940
  if (!target) return prs;
941
941
  // Never downgrade from approved — fix was dispatched but PR is already approved
942
942
  if (target.reviewStatus !== 'approved') target.reviewStatus = 'waiting';
943
- // Advance lastPushedAt so alreadyReviewed resets immediately, without waiting for the poller
944
- target.lastPushedAt = ts();
945
943
  // Always clear pendingFix — a fix dispatch (regardless of source) addresses all pending feedback
946
944
  if (target.humanFeedback) target.humanFeedback.pendingFix = false;
947
945
  if (source === 'pr-human-feedback') {
@@ -1599,6 +1597,41 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1599
1597
  // If decomposition produced nothing, fall through to mark parent as done
1600
1598
  }
1601
1599
 
1600
+ // Verify review work items include a verdict — must run BEFORE updateWorkItemStatus(DONE),
1601
+ // same pattern as plan-to-prd (#893): updateWorkItemStatus deletes _retryCount, so the check
1602
+ // must read/increment it before that happens. Also sets skipDoneStatus so completedAt isn't
1603
+ // written and then left dangling when status is reset to pending for retry.
1604
+ if (effectiveSuccess && type === WORK_TYPE.REVIEW && meta?.item?.id) {
1605
+ const verdict = parseReviewVerdict(resultSummary);
1606
+ if (!verdict) {
1607
+ skipDoneStatus = true;
1608
+ const wiPath = resolveWorkItemPath(meta);
1609
+ if (wiPath) {
1610
+ try {
1611
+ mutateJsonFileLocked(wiPath, data => {
1612
+ if (!Array.isArray(data)) return data;
1613
+ const w = data.find(i => i.id === meta.item.id);
1614
+ if (!w) return data;
1615
+ const retries = w._retryCount || 0;
1616
+ if (retries < ENGINE_DEFAULTS.maxRetries) {
1617
+ w.status = WI_STATUS.PENDING;
1618
+ w._retryCount = retries + 1;
1619
+ delete w.dispatched_at;
1620
+ delete w.completedAt;
1621
+ log('warn', `Review ${meta.item.id} completed without verdict — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
1622
+ } else {
1623
+ w.status = WI_STATUS.FAILED;
1624
+ w.failReason = 'No review verdict after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
1625
+ w.failedAt = ts();
1626
+ log('warn', `Review ${meta.item.id} failed — no verdict after ${ENGINE_DEFAULTS.maxRetries} retries`);
1627
+ }
1628
+ return data;
1629
+ });
1630
+ } catch (err) { log('warn', `review verdict check: ${err.message}`); }
1631
+ }
1632
+ }
1633
+ }
1634
+
1602
1635
  // Verify plan-to-prd actually created the PRD file before marking done (#893)
1603
1636
  // Must run BEFORE updateWorkItemStatus(DONE) — otherwise _retryCount is deleted and retries never advance
1604
1637
  if (effectiveSuccess && type === WORK_TYPE.PLAN_TO_PRD && meta?.item?.id) {
@@ -1772,35 +1805,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1772
1805
 
1773
1806
  // Old plan-to-prd PRD check removed — moved before updateWorkItemStatus(DONE) to fix #893
1774
1807
  // (retryCount was being deleted by done-marking before the check could read it)
1775
-
1776
- // Verify review work items include a verdict — mirrors the PRD file existence check pattern.
1777
- // If agent completed (exit 0) but output has no VERDICT: APPROVE/REQUEST_CHANGES, the review
1778
- // is incomplete. Auto-retry up to maxRetries, then fail.
1779
- if (effectiveSuccess && type === WORK_TYPE.REVIEW && meta?.item?.id) {
1780
- const verdict = parseReviewVerdict(resultSummary);
1781
- if (!verdict) {
1782
- const wiPath = resolveWorkItemPath(meta);
1783
- if (wiPath) {
1784
- mutateJsonFileLocked(wiPath, data => {
1785
- if (!Array.isArray(data)) return data;
1786
- const w = data.find(i => i.id === meta.item.id);
1787
- if (!w) return data;
1788
- const retries = w._retryCount || 0;
1789
- if (retries < ENGINE_DEFAULTS.maxRetries) {
1790
- w.status = WI_STATUS.PENDING;
1791
- w._retryCount = retries + 1;
1792
- delete w.dispatched_at;
1793
- log('warn', `Review ${meta.item.id} completed without verdict — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
1794
- } else {
1795
- w.status = WI_STATUS.FAILED;
1796
- w.failReason = 'No review verdict after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
1797
- log('warn', `Review ${meta.item.id} failed — no verdict after ${ENGINE_DEFAULTS.maxRetries} retries`);
1798
- }
1799
- return data;
1800
- });
1801
- }
1802
- }
1803
- }
1808
+ // Review verdict check similarly moved before updateWorkItemStatus(DONE) — same root cause.
1804
1809
 
1805
1810
  if (type === WORK_TYPE.REVIEW) await updatePrAfterReview(agentId, meta?.pr, meta?.project, config, resultSummary);
1806
1811
  if (type === WORK_TYPE.FIX) {
package/engine.js CHANGED
@@ -1984,11 +1984,12 @@ async function discoverFromPrs(config, project) {
1984
1984
  if (item) { newWork.push(item); }
1985
1985
  }
1986
1986
 
1987
- // Re-review after fix: fall back to fixedAt > lastReviewedAt when lastPushedAt lags poller
1987
+ // Re-review after fix: only trigger when fixedAt > lastReviewedAt (not a broad !alreadyReviewed
1988
+ // fallback — that caused infinite re-review loops on GitHub where self-approval is blocked)
1988
1989
  const fixedAfterReview = !!(pr.minionsReview?.fixedAt &&
1989
1990
  pr.lastReviewedAt && pr.minionsReview.fixedAt > pr.lastReviewedAt);
1990
1991
  const needsReReview = autoReview && reviewStatus === 'waiting' &&
1991
- (fixedAfterReview || (!alreadyReviewed && !!pr.minionsReview?.fixedAt)) && !evalEscalated;
1992
+ fixedAfterReview && !evalEscalated;
1992
1993
  if (needsReReview) {
1993
1994
  const key = `review-${project?.name || 'default'}-${pr.id}`;
1994
1995
  // Skip isAlreadyDispatched — fixedAfterReview/alreadyReviewed already dedup; the 1hr
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.973",
3
+ "version": "0.1.975",
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"