@yemi33/minions 0.1.1692 → 0.1.1693

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,13 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1692 (2026-05-04)
3
+ ## 0.1.1693 (2026-05-04)
4
+
5
+ ### Features
6
+ - sync review verdict PR status (#2008)
7
+
8
+ ## 0.1.1691 (2026-05-04)
4
9
 
5
10
  ### Features
6
- - stop Azure auth for GitHub agents (#2005)
7
- - recover stalled runtime resumes (#2004)
8
11
  - preserve doc-chat response fragments (#2003)
9
12
 
10
13
  ## 0.1.1689 (2026-05-03)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-04T01:58:58.614Z"
4
+ "cachedAt": "2026-05-04T03:30:25.499Z"
5
5
  }
package/engine/github.js CHANGED
@@ -39,6 +39,19 @@ function getRepoSlug(project) {
39
39
  return `${org}/${repo}`;
40
40
  }
41
41
 
42
+ function _hasMinionsReviewVerdict(body) {
43
+ return /(?:^|\n)\s*\*{0,2}VERDICT[:\s]+\*{0,2}(?:APPROVE|REQUEST[_\s-]?CHANGES)\*{0,2}\b/i.test(String(body || ''));
44
+ }
45
+
46
+ function _isAgentComment(c) {
47
+ const body = c.body || '';
48
+ if (_hasMinionsReviewVerdict(body)) return true;
49
+ if (/\bMinions\s*\(/i.test(body)) return true;
50
+ if (/\bby\s+Minions\b/i.test(body)) return true;
51
+ if (/\[minions\]/i.test(body)) return true;
52
+ return false;
53
+ }
54
+
42
55
  // ─── Per-Repo Poll Backoff ──────────────────────────────────────────────────
43
56
  // Tracks consecutive poll failures per repo slug to avoid spamming logs when
44
57
  // a repo is inaccessible. Backoff doubles each failure: 2min, 4min, 8min, 16min, max 30min.
@@ -572,13 +585,6 @@ async function pollPrHumanComments(config) {
572
585
  if (/!\[.*\]\(https?:\/\/.*badge/i.test(body)) return true;
573
586
  return false;
574
587
  }
575
- function _isAgentComment(c) {
576
- const body = c.body || '';
577
- if (/\bMinions\s*\(/i.test(body)) return true;
578
- if (/\bby\s+Minions\b/i.test(body)) return true;
579
- if (/\[minions\]/i.test(body)) return true;
580
- return false;
581
- }
582
588
  const actionableComments = allComments.filter(c => !_isIgnoredComment(c));
583
589
 
584
590
  const cutoffStr = pr.humanFeedback?.lastProcessedCommentDate || pr.created || '1970-01-01';
@@ -907,4 +913,6 @@ module.exports = {
907
913
  GH_MAX_BUFFER, // exported for testing
908
914
  GH_POLL_BACKOFF_BASE_MS, // exported for testing
909
915
  GH_POLL_BACKOFF_MAX_MS, // exported for testing
916
+ _hasMinionsReviewVerdict, // exported for testing
917
+ _isAgentComment, // exported for testing
910
918
  };
@@ -1266,11 +1266,61 @@ function isReviewBailout(text) {
1266
1266
  return /bail(ing)?\s+out/i.test(text) || /already\s+posted/i.test(text);
1267
1267
  }
1268
1268
 
1269
- async function updatePrAfterReview(agentId, pr, project, config, resultSummary, structuredCompletion = null) {
1269
+ function reviewPrRefFromCompletion(completion) {
1270
+ if (!completion || typeof completion !== 'object') return null;
1271
+ const value = String(completion.pr || completion.pull_request || completion.pullRequest || '').trim();
1272
+ if (!value || /^N\/?A$/i.test(value)) return null;
1273
+ return value;
1274
+ }
1270
1275
 
1271
- if (!pr?.id) return;
1276
+ function centralPrPath() {
1277
+ return path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json');
1278
+ }
1279
+
1280
+ function resolveReviewPrContext(pr, project, config, structuredCompletion = null) {
1281
+ const refs = [pr, reviewPrRefFromCompletion(structuredCompletion)].filter(Boolean);
1282
+ if (refs.length === 0) return null;
1283
+
1284
+ const projects = shared.getProjects(config);
1285
+ const projectCandidates = [];
1286
+ if (project) projectCandidates.push(project);
1287
+ for (const p of projects) {
1288
+ if (!projectCandidates.some(existing => existing?.name === p.name)) projectCandidates.push(p);
1289
+ }
1290
+
1291
+ for (const candidateProject of projectCandidates) {
1292
+ const prPath = shared.projectPrPath(candidateProject);
1293
+ const prs = safeJson(prPath) || [];
1294
+ for (const ref of refs) {
1295
+ const target = shared.findPrRecord(prs, ref, candidateProject);
1296
+ if (target) return { pr: { ...target }, project: candidateProject, prPath };
1297
+ }
1298
+ }
1299
+
1300
+ const centralPath = centralPrPath();
1301
+ const centralPrs = safeJson(centralPath) || [];
1302
+ for (const ref of refs) {
1303
+ const target = shared.findPrRecord(centralPrs, ref, null);
1304
+ if (target) return { pr: { ...target }, project: null, prPath: centralPath };
1305
+ }
1306
+
1307
+ return pr?.id
1308
+ ? { pr, project: project || null, prPath: project ? shared.projectPrPath(project) : centralPath }
1309
+ : null;
1310
+ }
1311
+
1312
+ async function updatePrAfterReview(agentId, pr, project, config, resultSummary, structuredCompletion = null) {
1272
1313
 
1273
1314
  if (!config) config = getConfig();
1315
+ const reviewContext = resolveReviewPrContext(pr, project, config, structuredCompletion);
1316
+ if (!reviewContext?.pr?.id) {
1317
+ const reportedPr = reviewPrRefFromCompletion(structuredCompletion);
1318
+ if (reportedPr) log('warn', `Review completion reported PR ${reportedPr}, but no tracked PR record was found`);
1319
+ return;
1320
+ }
1321
+ const reviewPr = reviewContext.pr;
1322
+ const reviewProject = reviewContext.project;
1323
+ const prPath = reviewContext.prPath;
1274
1324
  const reviewerName = config.agents?.[agentId]?.name || agentId;
1275
1325
  const dispatch = getDispatch();
1276
1326
  const completedEntry = (dispatch.completed || []).find(d => d.agent === agentId && d.type === 'review');
@@ -1280,31 +1330,30 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
1280
1330
  // The poller will pick up the real status on the next cycle (~3 min).
1281
1331
  let postReviewStatus = null; // null = don't change
1282
1332
  try {
1283
- const projectObj = project || shared.getProjects(config)[0];
1333
+ const projectObj = reviewProject || shared.getProjects(config)[0];
1284
1334
  if (projectObj) {
1285
1335
  const host = projectObj.repoHost || 'ado';
1286
1336
  const checkFn = host === 'github'
1287
1337
  ? require('./github').checkLiveReviewStatus
1288
1338
  : require('./ado').checkLiveReviewStatus;
1289
- const liveStatus = await checkFn(pr, projectObj);
1339
+ const liveStatus = await checkFn(reviewPr, projectObj);
1290
1340
  if (liveStatus && liveStatus !== 'pending') postReviewStatus = liveStatus;
1291
1341
  }
1292
- } catch (e) { log('warn', `Post-review status check for ${pr.id}: ${e.message}`); }
1342
+ } catch (e) { log('warn', `Post-review status check for ${reviewPr.id}: ${e.message}`); }
1293
1343
 
1294
1344
  // Fallback: if live check returned pending (e.g., GitHub self-approval blocked), use the agent's completion report.
1295
1345
  if (!postReviewStatus) {
1296
1346
  const verdict = reviewVerdictFromCompletion(structuredCompletion) || parseReviewVerdict(resultSummary);
1297
1347
  if (verdict) {
1298
1348
  postReviewStatus = verdict;
1299
- log('info', `Read review verdict from agent completion for ${pr.id}: ${verdict}`);
1349
+ log('info', `Read review verdict from agent completion for ${reviewPr.id}: ${verdict}`);
1300
1350
  }
1301
1351
  }
1302
1352
 
1303
- const prPath = project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json');
1304
1353
  let updatedTarget = null;
1305
1354
  shared.mutateJsonFileLocked(prPath, (prs) => {
1306
1355
  if (!Array.isArray(prs)) return prs;
1307
- const target = shared.findPrRecord(prs, pr, project);
1356
+ const target = shared.findPrRecord(prs, reviewPr, reviewProject);
1308
1357
  if (!target) return prs;
1309
1358
  // Once approved, stays approved — only changes-requested can override
1310
1359
  if (postReviewStatus) {
@@ -1323,12 +1372,12 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
1323
1372
  // Drop it when reviewer requests changes again — that starts a new fix cycle.
1324
1373
  ...(target.minionsReview?.fixedAt && postReviewStatus !== 'changes-requested' ? { fixedAt: target.minionsReview.fixedAt } : {}),
1325
1374
  };
1326
- updatedTarget = { ...pr, ...target };
1375
+ updatedTarget = { ...reviewPr, ...target };
1327
1376
  return prs;
1328
1377
  }, { defaultValue: [] });
1329
1378
 
1330
1379
  // Track reviewer for metrics purposes (separate file, separate lock)
1331
- const authorAgentId = (pr.agent || '').toLowerCase();
1380
+ const authorAgentId = (reviewPr.agent || '').toLowerCase();
1332
1381
  if (authorAgentId && config.agents?.[authorAgentId]) {
1333
1382
  shared.mutateJsonFileLocked(path.join(ENGINE_DIR, 'metrics.json'), (metrics) => {
1334
1383
  if (!metrics[authorAgentId]) metrics[authorAgentId] = { ...DEFAULT_AGENT_METRICS };
@@ -1338,7 +1387,7 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
1338
1387
  }, { defaultValue: {} });
1339
1388
  }
1340
1389
 
1341
- log('info', `Updated ${pr.id} → minions review: ${postReviewStatus || 'waiting'} by ${reviewerName}`);
1390
+ log('info', `Updated ${reviewPr.id} → minions review: ${postReviewStatus || 'waiting'} by ${reviewerName}`);
1342
1391
  if (updatedTarget) createReviewFeedbackForAuthor(agentId, updatedTarget, config);
1343
1392
  }
1344
1393
 
package/engine/shared.js CHANGED
@@ -1310,14 +1310,27 @@ function projectPrPath(project) {
1310
1310
  return path.join(projectStateDir(project), 'pull-requests.json');
1311
1311
  }
1312
1312
 
1313
+ function comparablePath(filePath) {
1314
+ const resolved = path.resolve(filePath);
1315
+ try {
1316
+ return fs.realpathSync.native(resolved);
1317
+ } catch {
1318
+ try {
1319
+ return path.join(fs.realpathSync.native(path.dirname(resolved)), path.basename(resolved));
1320
+ } catch {
1321
+ return resolved;
1322
+ }
1323
+ }
1324
+ }
1325
+
1313
1326
  function resolveProjectForPrPath(filePath, config = null) {
1314
- const resolvedPaths = new Set([path.resolve(filePath)]);
1327
+ const resolvedPaths = new Set([comparablePath(filePath)]);
1315
1328
  if (filePath && !path.isAbsolute(filePath)) {
1316
- resolvedPaths.add(path.resolve(MINIONS_DIR, filePath));
1329
+ resolvedPaths.add(comparablePath(path.resolve(MINIONS_DIR, filePath)));
1317
1330
  }
1318
1331
  const projects = getProjects(config);
1319
1332
  for (const project of projects) {
1320
- if (resolvedPaths.has(path.resolve(projectPrPath(project)))) return project;
1333
+ if (resolvedPaths.has(comparablePath(projectPrPath(project)))) return project;
1321
1334
  }
1322
1335
  if (projects.length === 1) return projects[0];
1323
1336
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1692",
3
+ "version": "0.1.1693",
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"