@yemi33/minions 0.1.455 → 0.1.456

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.456 (2026-04-07)
4
+
5
+ ### Fixes
6
+ - pre-dispatch live vote check prevents reviewing approved PRs
7
+
3
8
  ## 0.1.455 (2026-04-07)
4
9
 
5
10
  ### Fixes
package/engine/ado.js CHANGED
@@ -482,11 +482,39 @@ async function reconcilePrs(config) {
482
482
  }
483
483
  }
484
484
 
485
+ /**
486
+ * Fetch live review status for a single PR from ADO (synchronous).
487
+ * Returns 'approved', 'changes-requested', 'waiting', or 'pending'.
488
+ * Returns null if the check fails (token unavailable, API error).
489
+ * Used as a pre-dispatch gate to avoid dispatching reviews for already-approved PRs.
490
+ */
491
+ function checkLiveReviewStatus(pr, project) {
492
+ try {
493
+ const token = getAdoToken();
494
+ if (!token) return null;
495
+ const orgBase = shared.getAdoOrgBase(project);
496
+ const prNum = (pr.id || '').replace(/^PR-/, '');
497
+ const url = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}/pullrequests/${prNum}?api-version=7.1`;
498
+ const result = exec(`curl -s -H "Authorization: Bearer ${token}" "${url}"`, { encoding: 'utf-8', timeout: 15000, windowsHide: true });
499
+ const prData = JSON.parse(result);
500
+ const votes = (prData.reviewers || []).map(r => r.vote).filter(v => v !== undefined);
501
+ if (votes.length === 0) return 'pending';
502
+ if (votes.some(v => v === -10)) return 'changes-requested';
503
+ if (votes.some(v => v >= 5)) return 'approved';
504
+ if (votes.some(v => v === -5)) return 'waiting';
505
+ return 'pending';
506
+ } catch (e) {
507
+ log('warn', `Live review check for ${pr.id}: ${e.message}`);
508
+ return null;
509
+ }
510
+ }
511
+
485
512
  module.exports = {
486
513
  getAdoToken,
487
514
  adoFetch,
488
515
  pollPrStatus,
489
516
  pollPrHumanComments,
490
517
  reconcilePrs,
518
+ checkLiveReviewStatus,
491
519
  };
492
520
 
package/engine/github.js CHANGED
@@ -466,9 +466,37 @@ async function reconcilePrs(config) {
466
466
  }
467
467
  }
468
468
 
469
+ /**
470
+ * Fetch live review status for a single PR from GitHub. Returns 'approved', 'changes-requested',
471
+ * 'waiting', or 'pending'. Returns null if the check fails.
472
+ */
473
+ function checkLiveReviewStatus(pr, project) {
474
+ try {
475
+ const slug = getRepoSlug(project);
476
+ if (!slug) return null;
477
+ const prNum = (pr.id || '').replace(/^PR-/, '');
478
+ const reviews = ghApi(`/pulls/${prNum}/reviews`, slug);
479
+ if (!reviews || !Array.isArray(reviews)) return null;
480
+ const latestByUser = new Map();
481
+ for (const r of reviews) {
482
+ if (r.state === 'COMMENTED') continue;
483
+ latestByUser.set(r.user?.login || '', r.state);
484
+ }
485
+ const states = [...latestByUser.values()];
486
+ if (states.some(s => s === 'CHANGES_REQUESTED')) return 'changes-requested';
487
+ if (states.some(s => s === 'APPROVED')) return 'approved';
488
+ if (states.length > 0) return 'pending';
489
+ return 'pending';
490
+ } catch (e) {
491
+ log('warn', `Live review check for ${pr.id}: ${e.message}`);
492
+ return null;
493
+ }
494
+ }
495
+
469
496
  module.exports = {
470
497
  pollPrStatus,
471
498
  pollPrHumanComments,
472
499
  reconcilePrs,
500
+ checkLiveReviewStatus,
473
501
  };
474
502
 
package/engine.js CHANGED
@@ -830,8 +830,8 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
830
830
  // ─── Inbox Consolidation (extracted to engine/consolidation.js) ──────────────
831
831
 
832
832
  const { consolidateInbox } = require('./engine/consolidation');
833
- const { pollPrStatus, pollPrHumanComments, reconcilePrs } = require('./engine/ado');
834
- const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs } = require('./engine/github');
833
+ const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview } = require('./engine/ado');
834
+ const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview } = require('./engine/github');
835
835
 
836
836
  // ─── State Snapshot ─────────────────────────────────────────────────────────
837
837
 
@@ -1291,6 +1291,27 @@ function discoverFromPrs(config, project) {
1291
1291
  if (needsReview) {
1292
1292
  const key = `review-${project?.name || 'default'}-${pr.id}`;
1293
1293
  if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
1294
+
1295
+ // Pre-dispatch live vote check — cached reviewStatus may be stale (poll lag ~6 min)
1296
+ try {
1297
+ const checkFn = project.repoHost === 'github' ? ghCheckLiveReview : adoCheckLiveReview;
1298
+ const liveStatus = checkFn(pr, project);
1299
+ if (liveStatus && liveStatus !== 'pending') {
1300
+ log('info', `Pre-dispatch vote check: ${pr.id} is ${liveStatus} (cached was pending) — skipping review`);
1301
+ pr.reviewStatus = liveStatus;
1302
+ // Persist so next tick doesn't re-check
1303
+ try {
1304
+ mutateJsonFileLocked(projectPrPath(project), data => {
1305
+ if (!Array.isArray(data)) return data;
1306
+ const target = data.find(p => p.id === pr.id);
1307
+ if (target) target.reviewStatus = liveStatus;
1308
+ return data;
1309
+ });
1310
+ } catch {}
1311
+ continue;
1312
+ }
1313
+ } catch (e) { log('warn', `Pre-dispatch vote check for ${pr.id}: ${e.message}`); }
1314
+
1294
1315
  const agentId = resolveAgent('review', config);
1295
1316
  if (!agentId) continue;
1296
1317
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.455",
3
+ "version": "0.1.456",
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"