@yemi33/minions 0.1.703 → 0.1.705

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,8 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.703 (2026-04-09)
3
+ ## 0.1.705 (2026-04-09)
4
+
5
+ ### Fixes
6
+ - modal buttons now derive status from linked PRD when plan not in status
7
+
8
+ ## 0.1.704 (2026-04-09)
4
9
 
5
10
  ### Other
11
+ - docs: add PR review & fix loop documentation
6
12
  - refactor: move buildFixGracePeriod to ENGINE_DEFAULTS (no magic numbers)
7
13
 
8
14
  ## 0.1.702 (2026-04-09)
@@ -453,35 +453,53 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
453
453
  const canExecute = isMdPlan && !hasActiveWork && !prdCompleted;
454
454
  const modalExecuteBtn = canExecute && !hasPrd ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green);font-weight:600" ' +
455
455
  'onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Execute</button>' : '';
456
- const modalReExecuteBtn = canExecute && linkedPrdAwaiting ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green);font-weight:600" ' +
457
- 'onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Re-execute</button>' : '';
458
- const modalCompletedLabel = prdCompleted && !hasActiveWork ? '<span style="font-size:10px;color:var(--green);font-weight:600">Completed</span>' : '';
459
- const modalAwaitingLabel = linkedPrdAwaiting && !hasActiveWork && !prdCompleted ? '<span style="font-size:10px;color:var(--yellow);font-weight:600">Awaiting Approval</span>' : '';
460
- const modalInProgressLabel = hasActiveWork ? '<span style="font-size:10px;color:var(--blue)">In Progress</span>' : '';
461
- const isModalCompleted = planStatus === 'completed';
462
- const modalPauseBtn = isActive && !isMdPlan && !isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" ' +
463
- 'onclick="planPause(\'' + escHtml(normalizedFile) + '\',this)">Pause</button>' : '';
464
- const modalApproveTarget = linkedPrdAwaiting ? linkedPrd.file : normalizedFile;
465
- const isActuallyPaused = planStatus === 'paused' || linkedPrd?.status === 'paused';
466
- const modalApproveLabel = isActuallyPaused ? 'Resume' : 'Approve';
467
- const showRejectInModal = linkedPrdAwaiting || planStatus === 'awaiting-approval';
468
- const modalApproveBtn = modalShowResume ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
469
- 'onclick="planApprove(\'' + escHtml(modalApproveTarget) + '\',this)">' + modalApproveLabel + '</button>' : '';
470
- const modalRejectBtn = showRejectInModal ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
471
- 'onclick="planReject(\'' + escHtml(modalApproveTarget) + '\')">Reject</button>' : '';
472
- const modalVerifyWi = (window._lastWorkItems || []).find(w => w.itemType === 'verify' && w.sourcePlan === normalizedFile);
473
- const modalVerifyBtn = isModalCompleted && !modalVerifyWi ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
474
- 'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\',this)">Verify</button>' : '';
475
- const modalVerifyInfo = modalVerifyWi ? _renderVerifyBadge(modalVerifyWi) : '';
476
- const modalArchiveBtn = '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--muted)" ' +
477
- 'onclick="planArchive(\'' + escHtml(normalizedFile) + '\')">Archive</button>';
456
+ // Modal buttons mirror card logic derive effectiveStatus the same way
457
+ const allPlans = window._lastStatus?.plans || [];
458
+ const cardPlan = allPlans.find(p => p.file === normalizedFile || p.sourcePlan === normalizedFile || (p.file?.endsWith('.json') && p.file === normalizedFile));
459
+ // Also check for linked PRD by searching all plans for a PRD whose source_plan matches this file
460
+ const linkedPrd = allPlans.find(p => p.sourcePlan === normalizedFile && p.format === 'prd');
461
+ const effectiveStatus = cardPlan ? derivePlanStatus(cardPlan) : (linkedPrd ? derivePlanStatus(linkedPrd) : (planStatus || 'active'));
462
+ const prdFile = cardPlan?.file?.endsWith('.json') ? cardPlan.file : (linkedPrd?.file || '');
463
+ const isArchived = !!(cardPlan?.archived || linkedPrd?.archived);
464
+ const isDraft = isMdPlan && !prdFile;
465
+ const isNeedsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
466
+
467
+ const bs = 'font-size:10px;padding:2px 10px'; // button style
468
+ let modalActions = '';
469
+
470
+ // Approve/Reject for awaiting-approval or paused
471
+ if (isNeedsAction) {
472
+ const target = prdFile || normalizedFile;
473
+ const label = effectiveStatus === 'paused' ? 'Resume' : 'Approve';
474
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--green)" onclick="planApprove(\'' + escHtml(target) + '\',this)">' + label + '</button> ';
475
+ // Re-execute: re-generate PRD from updated plan (only for .md drafts with existing PRD)
476
+ if (effectiveStatus === 'awaiting-approval' && isMdPlan && prdFile && canExecute) {
477
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--green);opacity:0.7" onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Re-execute</button> ';
478
+ }
479
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--red)" onclick="planReject(\'' + escHtml(target) + '\')">Reject</button> ';
480
+ }
481
+ // Execute (draft .md without PRD)
482
+ if (isDraft && (effectiveStatus === 'active' || effectiveStatus === 'draft') && !isArchived) {
483
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--green);font-weight:600" onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Execute</button> ';
484
+ }
485
+ // Pause (active PRD, not completed)
486
+ if (effectiveStatus === 'dispatched' && prdFile && !isArchived) {
487
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--yellow)" onclick="planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button> ';
488
+ }
489
+ // Verify / Verified badge
490
+ const modalVerifyWi = (window._lastWorkItems || []).find(w => w.itemType === 'verify' && w.sourcePlan === (prdFile || normalizedFile));
491
+ if (effectiveStatus === 'completed' && prdFile && !isArchived && !modalVerifyWi) {
492
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--green)" onclick="triggerVerify(\'' + escHtml(prdFile) + '\',this)">Verify</button> ';
493
+ }
494
+ if (modalVerifyWi) modalActions += _renderVerifyBadge(modalVerifyWi);
495
+ // Archive + Delete (always, unless archived)
496
+ if (!isArchived) {
497
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--muted)" onclick="planArchive(\'' + escHtml(prdFile || normalizedFile) + '\')">Archive</button> ';
498
+ modalActions += '<button class="pr-pager-btn" style="' + bs + ';color:var(--red)" onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>';
499
+ }
500
+
478
501
  const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
479
- const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
480
- (modalCompletedLabel || '') + (modalAwaitingLabel || '') + (modalInProgressLabel || '') + (modalApproveBtn || '') + (modalReExecuteBtn || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalRejectBtn || '') + (modalVerifyBtn || '') + (modalVerifyInfo || '') +
481
- ' ' + modalArchiveBtn +
482
- ' <button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
483
- 'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
484
- '</div>';
502
+ const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' + modalActions + '</div>';
485
503
 
486
504
  document.getElementById('modal-title').innerHTML = escHtml(title) + (versionLabel ? ' <span style="font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue)">' + escHtml(versionLabel) + '</span>' : '') + lastModLabel + actionBtns;
487
505
  const modalBody = document.getElementById('modal-body');
@@ -0,0 +1,110 @@
1
+ # PR Review & Fix Loop
2
+
3
+ How the engine manages the lifecycle of a PR from creation through review, fix, and re-review.
4
+
5
+ ## 1. Implement agent creates PR
6
+
7
+ - Agent pushes code, output contains PR URL
8
+ - `syncPrsFromOutput()` (lifecycle.js) extracts URL via regex, creates `pull-requests.json` entry, links to work item via `addPrLink()`
9
+
10
+ ## 2. Engine discovers PR needs review
11
+
12
+ - `discoverFromPrs()` (engine.js) runs each tick (~60s)
13
+ - Gates: `status === 'active'` + `reviewStatus === 'pending'` + not reviewed since last push + not dispatched + not on cooldown
14
+ - Pre-dispatch: `checkLiveReviewStatus()` hits GitHub/ADO API to catch stale cached status
15
+ - Routes to reviewer via `resolveAgent('review')`, dispatches with `review.md` playbook
16
+
17
+ ## 3. Review completes
18
+
19
+ - `updatePrAfterReview()` (lifecycle.js) re-checks live vote from platform (may have changed during execution)
20
+ - Sets `reviewStatus` to `approved` / `changes-requested` / `waiting`
21
+ - Stores `minionsReview: { reviewer, reviewedAt, note }`
22
+ - Creates feedback file for author agent
23
+
24
+ ## 4. Fix dispatch (3 independent triggers, at most one per tick)
25
+
26
+ ### A. Review feedback (`changes-requested`)
27
+
28
+ - Gate: `reviewStatus === 'changes-requested'` + `!awaitingReReview` + not dispatched + not on cooldown
29
+ - Routes to PR author via `_author_` routing token
30
+ - `review_note` = reviewer's feedback
31
+ - Sets `fixDispatched = true` — prevents trigger B from also firing this tick
32
+
33
+ ### B. Human comments (`humanFeedback.pendingFix`)
34
+
35
+ - Gate: `pendingFix || coalescedFeedback` + `!awaitingReReview` + `!fixDispatched`
36
+ - Agent comments filtered out via `/\bMinions\s*\(/i` regex on comment body
37
+ - Coalesces multiple comments arriving during cooldown into single fix
38
+ - Routes to author
39
+
40
+ ### C. Build failures (`buildStatus === 'failing'`)
41
+
42
+ - Gate: `buildFixAttempts < maxBuildFixAttempts` (default 3) + grace period expired
43
+ - **Grace period** (`_buildFixPushedAt`): after fix dispatches, waits `buildFixGracePeriod` (default 10min, configurable in `ENGINE_DEFAULTS`) for CI to run before re-dispatching. Cleared when poller detects build status transition (CI actually ran).
44
+ - **Error logs**: GitHub fetches annotations (failures only, not warnings) + Actions job log (always). ADO fetches build timeline + failed task logs. Both fetch up to 3 failing pipelines.
45
+ - **Escalation**: after 3 failed attempts, writes inbox alert, sets `buildFixEscalated = true`, stops auto-dispatch. Counter resets when build recovers.
46
+
47
+ ## 5. Fix completes
48
+
49
+ - `updatePrAfterFix()` (lifecycle.js) sets `reviewStatus = 'waiting'` + `fixedAt = ts()`
50
+ - Clears `humanFeedback.pendingFix`
51
+ - `awaitingReReview` gate (`waiting` + `fixedAt`) blocks all fix dispatch until reviewer acts
52
+
53
+ ## 6. Re-review cycle
54
+
55
+ - Poller (~3min): detects new commit (`head.sha` changed) → sets `lastPushedAt`
56
+ - Platform review state drives next action:
57
+ - Reviewer approves → `approved` → done
58
+ - Reviewer re-requests changes → `changes-requested` → triggers another fix
59
+ - No reviewer action yet → stays `waiting` → engine waits
60
+
61
+ ## 7. Build fix cycle after fix push
62
+
63
+ - Fix agent pushes → `_buildFixPushedAt` stamped
64
+ - Poller detects new commit → CI starts → `buildStatus` transitions (`failing` → `running`)
65
+ - `_buildFixPushedAt` cleared on any transition
66
+ - If CI passes → `buildFixAttempts` reset, `buildErrorLog` cleared → done
67
+ - If CI fails again → fresh error logs fetched → new fix dispatches immediately (grace already cleared by transition)
68
+
69
+ ## Race prevention
70
+
71
+ | Scenario | Guard |
72
+ |---|---|
73
+ | Simultaneous review + fix | `activePrIds` — skip PR if any dispatch in-flight |
74
+ | Duplicate fix (review + human) | `fixDispatched` flag — only one fix per PR per tick |
75
+ | Branch write conflict | `isBranchActive()` mutex |
76
+ | Fix while awaiting re-review | `awaitingReReview` (waiting + fixedAt) |
77
+ | Build fix before CI runs | `_buildFixPushedAt` grace period (10min) |
78
+ | Duplicate dispatch | `dispatchKey` dedup + cooldown |
79
+ | Stale review status | Pre-dispatch live API check |
80
+ | Orphan detection | Heartbeat timeout + output scan |
81
+
82
+ ## Key files
83
+
84
+ | File | Functions |
85
+ |---|---|
86
+ | `engine.js` | `discoverFromPrs()` — discovery + dispatch logic |
87
+ | `engine/lifecycle.js` | `syncPrsFromOutput()`, `updatePrAfterReview()`, `updatePrAfterFix()` |
88
+ | `engine/github.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchGhBuildErrorLog()` |
89
+ | `engine/ado.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchAdoBuildErrorLog()` |
90
+ | `engine/dispatch.js` | `addToDispatch()` — dedup by work item ID and dispatchKey |
91
+ | `engine/cooldown.js` | `isBranchActive()`, cooldown management |
92
+ | `playbooks/review.md` | Reviewer playbook |
93
+ | `playbooks/fix.md` | Fix agent playbook |
94
+
95
+ ## PR state fields
96
+
97
+ | Field | Set by | Purpose |
98
+ |---|---|---|
99
+ | `status` | Poller | `active` / `merged` / `abandoned` |
100
+ | `reviewStatus` | Poller + post-completion | `pending` / `approved` / `changes-requested` / `waiting` |
101
+ | `buildStatus` | Poller | `none` / `passing` / `failing` / `running` |
102
+ | `buildErrorLog` | Poller | Actual CI error output for fix agents |
103
+ | `buildFixAttempts` | Discovery (on dispatch) | Counter for escalation cap |
104
+ | `buildFixEscalated` | Discovery (on cap) | Stops auto-dispatch |
105
+ | `_buildFixPushedAt` | Discovery (on dispatch) | Grace period timestamp |
106
+ | `_buildFailNotified` | Discovery | Dedup for inbox alert |
107
+ | `lastPushedAt` | Poller (new commit) | Tracks latest push for re-review logic |
108
+ | `lastReviewedAt` | `updatePrAfterReview()` | Prevents re-dispatch if reviewed |
109
+ | `minionsReview` | Post-completion hooks | `{ reviewer, reviewedAt, note, fixedAt }` |
110
+ | `humanFeedback` | `pollPrHumanComments()` | `{ pendingFix, feedbackContent, lastProcessedCommentDate }` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.703",
3
+ "version": "0.1.705",
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"