@yemi33/minions 0.1.888 → 0.1.890

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,11 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.888 (2026-04-11)
3
+ ## 0.1.890 (2026-04-11)
4
4
 
5
5
  ### Features
6
6
  - add 13 missing CC action types for full dashboard parity
7
7
 
8
8
  ### Fixes
9
+ - parse review verdict from agent output for GitHub self-approval (#912) (#918)
10
+ - verify PRD file exists before marking plan-to-prd done (#893) (#911)
11
+ - re-open done work items when PRD item reset to missing (#906) (#910)
9
12
  - require explicit vote step in review playbook
10
13
  - CC queued message silently retries on 429 after abort
11
14
  - track e2e/ and fix/ branches in PR reconciler
@@ -779,6 +779,27 @@ function syncPrsFromOutput(output, agentId, meta, config) {
779
779
 
780
780
  // ─── Post-Completion Hooks ──────────────────────────────────────────────────
781
781
 
782
+ /**
783
+ * Parse review verdict from agent output text.
784
+ * Looks for "VERDICT: APPROVE" or "VERDICT: REQUEST_CHANGES" markers that the
785
+ * review playbook instructs agents to include. This is the primary mechanism for
786
+ * GitHub repos where formal --approve/--request-changes votes are blocked by
787
+ * self-approval restrictions.
788
+ * @param {string} text - Agent output / resultSummary
789
+ * @returns {'approved'|'changes-requested'|null}
790
+ */
791
+ function parseReviewVerdict(text) {
792
+ if (!text) return null;
793
+ // Match "VERDICT: APPROVE" or "VERDICT: REQUEST_CHANGES" (case-insensitive, optional markdown bold)
794
+ const verdictMatch = text.match(/VERDICT[:\s]+\*{0,2}(APPROVE|REQUEST[_\s-]?CHANGES)\*{0,2}/i);
795
+ if (verdictMatch) {
796
+ const v = verdictMatch[1].toUpperCase().replace(/[\s-]/g, '_');
797
+ if (v === 'APPROVE') return 'approved';
798
+ if (v.includes('CHANGES')) return 'changes-requested';
799
+ }
800
+ return null;
801
+ }
802
+
782
803
  async function updatePrAfterReview(agentId, pr, project, config, resultSummary) {
783
804
 
784
805
  if (!pr?.id) return;
@@ -804,6 +825,15 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary)
804
825
  }
805
826
  } catch (e) { log('warn', `Post-review status check for ${pr.id}: ${e.message}`); }
806
827
 
828
+ // Fallback: if live check returned pending (e.g., GitHub self-approval blocked), parse verdict from agent output
829
+ if (!postReviewStatus) {
830
+ const verdict = parseReviewVerdict(resultSummary);
831
+ if (verdict) {
832
+ postReviewStatus = verdict;
833
+ log('info', `Parsed review verdict from agent output for ${pr.id}: ${verdict}`);
834
+ }
835
+ }
836
+
807
837
  const prPath = project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json');
808
838
  let updatedTarget = null;
809
839
  shared.mutateJsonFileLocked(prPath, (prs) => {
@@ -839,7 +869,7 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary)
839
869
  }, { defaultValue: {} });
840
870
  }
841
871
 
842
- log('info', `Updated ${pr.id} → minions review: waiting by ${reviewerName}`);
872
+ log('info', `Updated ${pr.id} → minions review: ${postReviewStatus || 'waiting'} by ${reviewerName}`);
843
873
  if (updatedTarget) createReviewFeedbackForAuthor(agentId, updatedTarget, config);
844
874
  }
845
875
 
@@ -1499,6 +1529,54 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1499
1529
  // If decomposition produced nothing, fall through to mark parent as done
1500
1530
  }
1501
1531
 
1532
+ // Verify plan-to-prd actually created the PRD file before marking done (#893)
1533
+ // Must run BEFORE updateWorkItemStatus(DONE) — otherwise _retryCount is deleted and retries never advance
1534
+ if (effectiveSuccess && type === WORK_TYPE.PLAN_TO_PRD && meta?.item?.id) {
1535
+ let prdFound = false;
1536
+ const expectedFile = meta.item._prdFilename;
1537
+ if (expectedFile) {
1538
+ prdFound = fs.existsSync(path.join(PRD_DIR, expectedFile));
1539
+ }
1540
+ if (!prdFound && meta.item.planFile) {
1541
+ try {
1542
+ for (const f of fs.readdirSync(PRD_DIR)) {
1543
+ if (!f.endsWith('.json')) continue;
1544
+ try {
1545
+ const prd = safeJson(path.join(PRD_DIR, f));
1546
+ if (prd && prd.source_plan === meta.item.planFile) { prdFound = true; break; }
1547
+ } catch {}
1548
+ }
1549
+ } catch {}
1550
+ }
1551
+ if (!prdFound) {
1552
+ skipDoneStatus = true;
1553
+ const wiPath = resolveWorkItemPath(meta);
1554
+ if (wiPath) {
1555
+ try {
1556
+ mutateJsonFileLocked(wiPath, data => {
1557
+ if (!Array.isArray(data)) return data;
1558
+ const w = data.find(i => i.id === meta.item.id);
1559
+ if (!w) return data;
1560
+ const retries = w._retryCount || 0;
1561
+ if (retries < ENGINE_DEFAULTS.maxRetries) {
1562
+ w.status = WI_STATUS.PENDING;
1563
+ w._retryCount = retries + 1;
1564
+ delete w.dispatched_at;
1565
+ delete w.completedAt;
1566
+ log('warn', `plan-to-prd ${meta.item.id} completed without PRD file — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
1567
+ } else {
1568
+ w.status = WI_STATUS.FAILED;
1569
+ w.failReason = 'PRD file not written after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
1570
+ w.failedAt = ts();
1571
+ log('warn', `plan-to-prd ${meta.item.id} failed — no PRD file after ${ENGINE_DEFAULTS.maxRetries} retries`);
1572
+ }
1573
+ return data;
1574
+ });
1575
+ } catch (err) { log('warn', `plan-to-prd PRD check: ${err.message}`); }
1576
+ }
1577
+ }
1578
+ }
1579
+
1502
1580
  if (effectiveSuccess && meta?.item?.id && !skipDoneStatus) {
1503
1581
  meta._agentId = agentId;
1504
1582
  updateWorkItemStatus(meta, WI_STATUS.DONE, '');
@@ -1622,26 +1700,15 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1622
1700
  }
1623
1701
  }
1624
1702
 
1625
- // Detect plan-to-prd tasks that completed without creating a PRD file
1626
- if (effectiveSuccess && type === WORK_TYPE.PLAN_TO_PRD && meta?.item?.planFile) {
1627
- // Check by stored filename first (deterministic), fall back to source_plan scan
1628
- let prdFound = false;
1629
- const expectedFile = meta.item._prdFilename;
1630
- if (expectedFile) {
1631
- prdFound = fs.existsSync(path.join(PRD_DIR, expectedFile));
1632
- }
1633
- if (!prdFound) {
1634
- try {
1635
- for (const f of fs.readdirSync(PRD_DIR)) {
1636
- if (!f.endsWith('.json')) continue;
1637
- try {
1638
- const prd = safeJson(path.join(PRD_DIR, f));
1639
- if (prd && prd.source_plan === meta.item.planFile) { prdFound = true; break; }
1640
- } catch {}
1641
- }
1642
- } catch {}
1643
- }
1644
- if (!prdFound) {
1703
+ // Old plan-to-prd PRD check removed moved before updateWorkItemStatus(DONE) to fix #893
1704
+ // (retryCount was being deleted by done-marking before the check could read it)
1705
+
1706
+ // Verify review work items include a verdict — mirrors the PRD file existence check pattern.
1707
+ // If agent completed (exit 0) but output has no VERDICT: APPROVE/REQUEST_CHANGES, the review
1708
+ // is incomplete. Auto-retry up to maxRetries, then fail.
1709
+ if (effectiveSuccess && type === WORK_TYPE.REVIEW && meta?.item?.id) {
1710
+ const verdict = parseReviewVerdict(resultSummary);
1711
+ if (!verdict) {
1645
1712
  const wiPath = resolveWorkItemPath(meta);
1646
1713
  if (wiPath) {
1647
1714
  mutateJsonFileLocked(wiPath, data => {
@@ -1653,11 +1720,11 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1653
1720
  w.status = WI_STATUS.PENDING;
1654
1721
  w._retryCount = retries + 1;
1655
1722
  delete w.dispatched_at;
1656
- log('warn', `plan-to-prd ${meta.item.id} completed without PRD file — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
1723
+ log('warn', `Review ${meta.item.id} completed without verdict — auto-retry ${retries + 1}/${ENGINE_DEFAULTS.maxRetries}`);
1657
1724
  } else {
1658
1725
  w.status = WI_STATUS.FAILED;
1659
- w.failReason = 'Completed without creating PRD file after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
1660
- log('warn', `plan-to-prd ${meta.item.id} failed — no PRD file after ${ENGINE_DEFAULTS.maxRetries} retries`);
1726
+ w.failReason = 'No review verdict after ' + ENGINE_DEFAULTS.maxRetries + ' attempts';
1727
+ log('warn', `Review ${meta.item.id} failed — no verdict after ${ENGINE_DEFAULTS.maxRetries} retries`);
1661
1728
  }
1662
1729
  return data;
1663
1730
  });
@@ -1819,6 +1886,7 @@ module.exports = {
1819
1886
  createReviewFeedbackForAuthor,
1820
1887
  updateMetrics,
1821
1888
  parseAgentOutput,
1889
+ parseReviewVerdict,
1822
1890
  parseStructuredCompletion,
1823
1891
  runPostCompletionHooks,
1824
1892
  syncPrdFromPrs,
@@ -81,13 +81,14 @@ function getPrVoteInstructions(project) {
81
81
  if (host === 'github') {
82
82
  const org = project?.adoOrg || '';
83
83
  const repo = project?.repoName || '';
84
- return `Use \`gh pr review\` to submit a review on the PR:\n` +
85
- `- Approve: \`gh pr review <number> --approve --body "Approval comment" --repo ${org}/${repo}\`\n` +
86
- `- Request changes: \`gh pr review <number> --request-changes --body "What needs to change" --repo ${org}/${repo}\`\n` +
87
- `- Comment only: \`gh pr review <number> --comment --body "Review comment" --repo ${org}/${repo}\`\n` +
84
+ return `**IMPORTANT: GitHub blocks self-approval** — all agents share the same credentials, so \`--approve\` and \`--request-changes\` will fail with "can't approve your own PR." Use \`--comment\` instead.\n\n` +
85
+ `Submit your review verdict using \`gh pr review\` with \`--comment\`:\n` +
86
+ `- Approve: \`gh pr review <number> --comment --body "VERDICT: APPROVE\\n\\n<your review details>" --repo ${org}/${repo}\`\n` +
87
+ `- Request changes: \`gh pr review <number> --comment --body "VERDICT: REQUEST_CHANGES\\n\\n<your review details>" --repo ${org}/${repo}\`\n` +
88
88
  `- Replace <number> with the PR number\n` +
89
89
  `- Always set --repo to \`${org}/${repo}\` to target the correct repository\n` +
90
- `- Use --body to provide a review summary (supports Markdown)`;
90
+ `- **Your comment body MUST start with \`VERDICT: APPROVE\` or \`VERDICT: REQUEST_CHANGES\`** on its own line — the engine parses this to record your vote\n` +
91
+ `- Do NOT use \`--approve\` or \`--request-changes\` flags — they will fail`;
91
92
  }
92
93
  return `Use \`mcp__azure-ado__repo_update_pull_request_reviewers\`:\n- repositoryId: \`${repoId}\`\n- Set your reviewer vote on the PR (10=approve, 5=approve-with-suggestions, -10=reject)`;
93
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.888",
3
+ "version": "0.1.890",
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"
@@ -40,28 +40,22 @@ Use subagents only for genuinely parallel, independent tasks (e.g., reviewing un
40
40
  - Verdict: **APPROVE**
41
41
  - Note any minor non-blocking suggestions
42
42
 
43
- ## Post Review — Two required actions, both mandatory
43
+ ## Post Review — Submit your verdict
44
44
 
45
- You MUST complete BOTH steps below. The review is NOT done until both are confirmed. A comment without a vote is an incomplete review.
45
+ You MUST post a review comment with a clear verdict. The engine parses your verdict to update PR status **a review without a verdict line is incomplete and will be retried.**
46
46
 
47
- ### Step 1: Post a detailed review comment
48
-
49
- {{pr_comment_instructions}}
50
- - content: Your full review with verdict, findings, and sign-off
51
- - Sign: `Review by Minions ({{agent_name}} — {{agent_role}})`
52
-
53
- ### Step 2: Submit a formal review vote — THIS IS REQUIRED
54
-
55
- **This is a separate action from Step 1.** Posting a comment does NOT submit a vote. You must explicitly run the vote command:
47
+ ### Post your review with verdict
56
48
 
57
49
  {{pr_vote_instructions}}
58
- - If your verdict is **APPROVE**: use `--approve`
59
- - If your verdict is **REQUEST_CHANGES**: use `--request-changes`
60
- - If you have minor non-blocking suggestions only: use `--approve` with a note
61
50
 
62
- **Do not stop after Step 1.** The task is incomplete until Step 2 is done.
51
+ Your review body **MUST** start with one of these verdict lines (exactly as shown):
52
+ - `VERDICT: APPROVE` — if the code is ready to merge
53
+ - `VERDICT: REQUEST_CHANGES` — if there are issues that must be fixed
54
+
55
+ Follow the verdict line with your detailed review findings, then sign off:
56
+ - Sign: `Review by Minions ({{agent_name}} — {{agent_role}})`
63
57
 
64
- After running the vote command, confirm it succeeded (check the command output for errors). If it fails, retry once.
58
+ After running the command, confirm it succeeded (check the command output for errors). If it fails, retry once.
65
59
 
66
60
  ## Handling Merge Conflicts
67
61
  If you encounter merge conflicts (e.g., the PR shows conflicts):
@@ -71,9 +65,7 @@ If you encounter merge conflicts (e.g., the PR shows conflicts):
71
65
 
72
66
  ## When to Stop
73
67
 
74
- Your task is complete only when BOTH of these are true:
75
- 1. Review comment posted (Step 1)
76
- 2. Formal vote submitted via `gh pr review --approve` or `--request-changes` (Step 2)
68
+ Your task is complete when your review comment (with `VERDICT: APPROVE` or `VERDICT: REQUEST_CHANGES` on the first line) has been posted successfully.
77
69
 
78
- Do NOT stop after only posting a comment. Do NOT continue reading unrelated files after voting.
70
+ Do NOT stop before posting the review. Do NOT continue reading unrelated files after posting.
79
71