@yemi33/minions 0.1.454 → 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,15 @@
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
+
8
+ ## 0.1.455 (2026-04-07)
9
+
10
+ ### Fixes
11
+ - agents write one inbox file per task, not two
12
+
3
13
  ## 0.1.454 (2026-04-07)
4
14
 
5
15
  ### 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
 
@@ -241,12 +241,13 @@ function renderPlaybook(type, vars) {
241
241
  content += `**Never delete, move, or overwrite files in \`knowledge/\`.** The sweep (consolidation engine) is the only process that writes to \`knowledge/\`. If you think a KB file is wrong, note it in your learnings file — do not touch \`knowledge/\` directly.\n`;
242
242
 
243
243
  // Inject learnings requirement
244
- content += `\n\n---\n\n## REQUIRED: Write Learnings\n\n`;
245
- content += `After completing your task, you MUST write a findings/learnings file to:\n`;
246
244
  const timeStamp = new Date().toISOString().slice(11, 16).replace(':', '');
247
245
  const inboxSlug = [vars.agent_id || 'agent', vars.task_id || '', dateStamp(), timeStamp].filter(Boolean).join('-');
246
+ content += `\n\n---\n\n## REQUIRED: Write Learnings\n\n`;
247
+ content += `After completing your task, write **one** findings file to:\n`;
248
248
  content += `\`${MINIONS_DIR}/notes/inbox/${inboxSlug}.md\`\n\n`;
249
- content += `Include:\n`;
249
+ content += `**IMPORTANT: Write exactly ONE inbox file per task.** If the playbook above already specifies an inbox path, use THAT path instead and include your learnings in the same document. Do NOT create a second file — duplicates clog consolidation.\n\n`;
250
+ content += `Include in your findings file:\n`;
250
251
  content += `- What you learned about the codebase\n`;
251
252
  content += `- Patterns you discovered or established\n`;
252
253
  content += `- Gotchas or warnings for future agents\n`;
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.454",
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"