@yemi33/minions 0.1.2310 → 0.1.2311

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.
@@ -159,7 +159,7 @@ const RENDER_VERSIONS = {
159
159
  schedules: 1,
160
160
  watches: 3,
161
161
  meetings: 1,
162
- pipelines: 1,
162
+ pipelines: 2,
163
163
  pinned: 1,
164
164
  kbPayload: 2,
165
165
  // settings is a modal-driven section, not a tick-driven render — but the
@@ -352,6 +352,11 @@ function renderPipelines(pipelines, opts) {
352
352
  '<span style="color:' + statusColor + ';font-size:var(--text-base);font-weight:600">' + escHtml(statusLabel) + '</span>' +
353
353
  (p.stopWhen ? '<span style="font-size:var(--text-xs);color:var(--yellow)" title="Auto-stops when condition met: ' + escHtml(typeof p.stopWhen === 'string' ? p.stopWhen : (p.stopWhen.check || 'condition')) + '">STOP-WHEN</span>' : '') +
354
354
  (p.enabled === false ? '<span style="font-size:var(--text-xs);color:var(--red)"' + (p._stopReason ? ' title="' + escHtml(p._stopReason) + '"' : '') + '>' + (p._stoppedBy ? 'AUTO-STOPPED' : 'DISABLED') + '</span>' : '') +
355
+ (p.enabled !== false
356
+ ? (activeRun
357
+ ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="event.stopPropagation();_retriggerPipeline(\'' + escHtml(p.id) + '\',this)">Retrigger</button>'
358
+ : '<button class="btn-action btn-action-md" onclick="event.stopPropagation();_triggerPipeline(\'' + escHtml(p.id) + '\',this)">Run Now</button>')
359
+ : '') +
355
360
  '</div>' +
356
361
  '</div>' +
357
362
  resourcesHtml +
@@ -403,10 +408,12 @@ function openPipelineDetail(id) {
403
408
  html += '<div style="display:flex;justify-content:space-between;align-items:center">' +
404
409
  '<span style="font-size:var(--text-sm);color:var(--muted)">' + _renderPipelineTriggerLabel(p.trigger?.cron) + ' · ' + escHtml(_getPipelineStageLabel(p)) + '</span>' +
405
410
  '<div style="display:flex;gap:6px">' +
406
- (activeRun
407
- ? '<button class="btn-destructive btn-destructive-md" onclick="_abortPipeline(\'' + escHtml(id) + '\',this)">Abort</button>' +
408
- '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_retriggerPipeline(\'' + escHtml(id) + '\',this)">Retrigger</button>'
409
- : '<button class="btn-action btn-action-md" onclick="_triggerPipeline(\'' + escHtml(id) + '\',this)">Run Now</button>') +
411
+ (activeRun ? '<button class="btn-destructive btn-destructive-md" onclick="_abortPipeline(\'' + escHtml(id) + '\',this)">Abort</button>' : '') +
412
+ (p.enabled !== false
413
+ ? (activeRun
414
+ ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_retriggerPipeline(\'' + escHtml(id) + '\',this)">Retrigger</button>'
415
+ : '<button class="btn-action btn-action-md" onclick="_triggerPipeline(\'' + escHtml(id) + '\',this)">Run Now</button>')
416
+ : '') +
410
417
  '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--blue);border-color:var(--blue)" onclick="openEditPipelineModal(\'' + escHtml(id) + '\')">Edit</button>' +
411
418
  '<button class="pr-pager-btn' + (_pipelineToggleInFlight.has(id) ? ' disabled' : '') + '" style="font-size:var(--text-xs);padding:2px 8px" onclick="_togglePipelineEnabled(\'' + escHtml(id) + '\',' + !p.enabled + ',this)">' + (p.enabled !== false ? 'Disable' : 'Enable') + '</button>' +
412
419
  '<button class="btn-destructive btn-destructive-md" onclick="_deletePipelineConfirm(\'' + escHtml(id) + '\')">Delete</button>' +
package/engine/ado.js CHANGED
@@ -527,6 +527,35 @@ function votesToReviewStatus(votes) {
527
527
  return REVIEW_STATUS.PENDING;
528
528
  }
529
529
 
530
+ /**
531
+ * Issue #633 — ADO never clears a reviewer's numeric vote when new commits
532
+ * land on a PR, so a hard-reject -10 vote stays live forever unless the
533
+ * reviewer explicitly re-votes. Without this check, `reviewStatus` gets
534
+ * permanently stuck at 'changes-requested' after a fix is pushed, and the
535
+ * auto re-review-after-fix flow (engine.js needsReReview, which only fires on
536
+ * reviewStatus === 'waiting') can never dispatch again for that PR.
537
+ *
538
+ * Treat a live -10 vote as stale/superseded — i.e. resolve to 'waiting'
539
+ * instead of 'changes-requested' — once a fix has completed after the vote
540
+ * was (most recently) cast AND no further push has landed since that fix:
541
+ * - `pr.minionsReview.fixedAt` is set (a fix dispatch completed), and
542
+ * - the fix happened after the last minions review that produced the
543
+ * rejection (`!pr.lastReviewedAt || fixedAt > pr.lastReviewedAt` —
544
+ * mirrors the `fixedAfterReview` gate in engine.js needsReReview), and
545
+ * - no push has landed since that fix completed
546
+ * (`!pr.lastPushedAt || pr.lastPushedAt <= fixedAt`).
547
+ * If a new push lands after the fix without a following fix completion, or
548
+ * the last review (and its -10) postdates the recorded fix, the reject is
549
+ * still live/fresh and reviewStatus correctly stays 'changes-requested'.
550
+ */
551
+ function isStaleAdoRejectVote(pr) {
552
+ const fixedAt = pr?.minionsReview?.fixedAt;
553
+ if (!fixedAt) return false;
554
+ const fixedAfterReview = !pr.lastReviewedAt || fixedAt > pr.lastReviewedAt;
555
+ const noPushSinceFix = !pr.lastPushedAt || pr.lastPushedAt <= fixedAt;
556
+ return fixedAfterReview && noPushSinceFix;
557
+ }
558
+
530
559
  // ─── Reviewer Vote Snapshots (W-mpg58wv3) ────────────────────────────────────
531
560
  //
532
561
  // ADO's reviewer API does NOT include the source commit at which a reviewer
@@ -1569,11 +1598,7 @@ async function pollPrStatus(config) {
1569
1598
  }
1570
1599
  } else if (votes.length > 0) {
1571
1600
  if (votes.some(v => v === -10)) {
1572
- if (pr.reviewStatus === REVIEW_STATUS.WAITING && pr.minionsReview?.fixedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.minionsReview.fixedAt)) {
1573
- newReviewStatus = REVIEW_STATUS.WAITING;
1574
- } else {
1575
- newReviewStatus = REVIEW_STATUS.CHANGES_REQUESTED;
1576
- }
1601
+ newReviewStatus = isStaleAdoRejectVote(pr) ? REVIEW_STATUS.WAITING : REVIEW_STATUS.CHANGES_REQUESTED;
1577
1602
  }
1578
1603
  else if (votes.some(v => v >= 5)) newReviewStatus = REVIEW_STATUS.APPROVED;
1579
1604
  else if (votes.some(v => v === -5)) newReviewStatus = REVIEW_STATUS.WAITING;
@@ -2346,7 +2371,16 @@ async function checkLiveReviewStatus(pr, project) {
2346
2371
  if (!prData) return null;
2347
2372
  const votes = (prData.reviewers || []).map(r => r.vote).filter(v => v !== undefined);
2348
2373
  if (votes.length === 0) return 'pending';
2349
- return votesToReviewStatus(votes);
2374
+ const liveStatus = votesToReviewStatus(votes);
2375
+ // Issue #633: the live vote check is the pre-dispatch gate for
2376
+ // engine.js needsReReview — it must apply the same stale-reject-vote
2377
+ // override as pollPrStatus, or a cached 'waiting' status set below by
2378
+ // the poller gets immediately reverted back to 'changes-requested' here
2379
+ // before a re-review can ever be dispatched.
2380
+ if (liveStatus === REVIEW_STATUS.CHANGES_REQUESTED && isStaleAdoRejectVote(pr)) {
2381
+ return REVIEW_STATUS.WAITING;
2382
+ }
2383
+ return liveStatus;
2350
2384
  } catch (e) {
2351
2385
  log('warn', `Live review check for ${pr.id}: ${e.message}`);
2352
2386
  return null;
package/engine/github.js CHANGED
@@ -705,6 +705,24 @@ async function forEachActiveGhPr(config, callback) {
705
705
  return totalUpdated;
706
706
  }
707
707
 
708
+ /**
709
+ * Issue #633 (mirror of engine/ado.js isStaleAdoRejectVote) — GitHub reviews
710
+ * generally get dismissed/replaced when a reviewer re-reviews, but a stale
711
+ * CHANGES_REQUESTED review from a login that never re-reviews after a fix can
712
+ * still pin `reviewStatus` at 'changes-requested' forever (the reviewer's
713
+ * review state persists until dismissed). Mirror the ADO fix so the same
714
+ * fixedAfterReview + no-push-since-fix override applies here: treat a live
715
+ * CHANGES_REQUESTED review as stale/superseded once a fix has completed after
716
+ * the review that produced it and no further push has landed since that fix.
717
+ */
718
+ function isStaleGithubChangesRequested(pr) {
719
+ const fixedAt = pr?.minionsReview?.fixedAt;
720
+ if (!fixedAt) return false;
721
+ const fixedAfterReview = !pr.lastReviewedAt || fixedAt > pr.lastReviewedAt;
722
+ const noPushSinceFix = !pr.lastPushedAt || pr.lastPushedAt <= fixedAt;
723
+ return fixedAfterReview && noPushSinceFix;
724
+ }
725
+
708
726
  // ─── PR Status Polling ──────────────────────────────────────────────────────
709
727
 
710
728
  async function pollPrStatus(config) {
@@ -999,11 +1017,7 @@ async function pollPrStatus(config) {
999
1017
  if (pr.reviewStatus === REVIEW_STATUS.APPROVED) {
1000
1018
  newReviewStatus = REVIEW_STATUS.APPROVED;
1001
1019
  } else if (states.some(s => s === 'CHANGES_REQUESTED')) {
1002
- if (pr.reviewStatus === REVIEW_STATUS.WAITING && pr.minionsReview?.fixedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.minionsReview.fixedAt)) {
1003
- newReviewStatus = REVIEW_STATUS.WAITING;
1004
- } else {
1005
- newReviewStatus = REVIEW_STATUS.CHANGES_REQUESTED;
1006
- }
1020
+ newReviewStatus = isStaleGithubChangesRequested(pr) ? REVIEW_STATUS.WAITING : REVIEW_STATUS.CHANGES_REQUESTED;
1007
1021
  }
1008
1022
  else if (states.some(s => s === 'APPROVED')) newReviewStatus = REVIEW_STATUS.APPROVED;
1009
1023
  else if (states.length > 0) newReviewStatus = REVIEW_STATUS.PENDING;
@@ -1575,7 +1589,13 @@ async function checkLiveReviewStatus(pr, project) {
1575
1589
  latestByUser.set(r.user?.login || '', r.state);
1576
1590
  }
1577
1591
  const states = [...latestByUser.values()];
1578
- if (states.some(s => s === 'CHANGES_REQUESTED')) return REVIEW_STATUS.CHANGES_REQUESTED;
1592
+ // Issue #633: mirror the pollPrStatus stale-reject override here — this
1593
+ // live check is the pre-dispatch gate for engine.js needsReReview, and
1594
+ // without the override it immediately reverts a cached 'waiting' status
1595
+ // back to 'changes-requested' before a re-review can ever be dispatched.
1596
+ if (states.some(s => s === 'CHANGES_REQUESTED')) {
1597
+ return isStaleGithubChangesRequested(pr) ? REVIEW_STATUS.WAITING : REVIEW_STATUS.CHANGES_REQUESTED;
1598
+ }
1579
1599
  if (states.some(s => s === 'APPROVED')) return REVIEW_STATUS.APPROVED;
1580
1600
  if (states.length > 0) return REVIEW_STATUS.PENDING;
1581
1601
  return REVIEW_STATUS.PENDING;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2310",
3
+ "version": "0.1.2311",
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"