@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 +7 -1
- package/dashboard/js/render-plans.js +46 -28
- package/docs/pr-review-fix-loop.md +110 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
'onclick="
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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.
|
|
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"
|