@yemi33/minions 0.1.2230 → 0.1.2231
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/dashboard/js/detail-panel.js +90 -2
- package/dashboard/js/refresh.js +1 -1
- package/dashboard/js/render-prd.js +25 -8
- package/engine/lifecycle.js +118 -0
- package/engine/queries.js +13 -0
- package/engine.js +2 -1
- package/package.json +1 -1
|
@@ -33,6 +33,87 @@ function switchTab(tabId) {
|
|
|
33
33
|
renderDetailContent(detail, tabId);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// W-mqptc3n3 — Normalize a completion-report `pr` value (canonical id or URL)
|
|
37
|
+
// into the `github:owner/repo#N` / `ado:org/proj/repo#N` form openArtifact
|
|
38
|
+
// expects. Returns null when the value isn't a recognizable PR pointer so the
|
|
39
|
+
// caller can render no chip rather than a broken one.
|
|
40
|
+
function _detailNormalizePrId(value) {
|
|
41
|
+
if (!value || typeof value !== 'string') return null;
|
|
42
|
+
var v = value.trim();
|
|
43
|
+
if (!v || v === 'N/A') return null;
|
|
44
|
+
if (/^(github|ado):.+#\d+$/i.test(v)) return v;
|
|
45
|
+
if (typeof _wiDeriveCanonicalPrIdFromUrl === 'function') {
|
|
46
|
+
var derived = _wiDeriveCanonicalPrIdFromUrl(v);
|
|
47
|
+
if (derived) return derived;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// W-mqptc3n3 — Pick the completion report of the most recent SUCCESSFUL
|
|
53
|
+
// (result !== 'error') run for this agent. statusData reflects the latest run
|
|
54
|
+
// inside the 5-min window; when that run is done with an available report it IS
|
|
55
|
+
// the last successful run. Otherwise (idle >5min, latest run errored, or no
|
|
56
|
+
// fresh report) walk recentDispatches (most-recent-first) for the newest
|
|
57
|
+
// non-error run with an available report. Returns null when none qualifies.
|
|
58
|
+
function _lastSuccessfulCompletionReport(detail) {
|
|
59
|
+
if (!detail || typeof detail !== 'object') return null;
|
|
60
|
+
var sd = detail.statusData;
|
|
61
|
+
if (sd && sd.status === 'done' && sd.completionReport && sd.completionReport.available) {
|
|
62
|
+
return sd.completionReport;
|
|
63
|
+
}
|
|
64
|
+
var rds = Array.isArray(detail.recentDispatches) ? detail.recentDispatches : [];
|
|
65
|
+
for (var i = 0; i < rds.length; i++) {
|
|
66
|
+
var rd = rds[i];
|
|
67
|
+
if (rd && rd.result !== 'error' && rd.completionReport && rd.completionReport.available) {
|
|
68
|
+
return rd.completionReport;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// W-mqptc3n3 — Render artifact chips (PR / note / KB) for a successful run's
|
|
75
|
+
// completion report. All chips route through renderArtifactLink (which escapes
|
|
76
|
+
// every field) so click → openArtifact() → modal-stack push comes for free.
|
|
77
|
+
// Returns '' when there is no linkable artifact (no empty header, no broken
|
|
78
|
+
// chip).
|
|
79
|
+
function _renderLastResultArtifactChips(report) {
|
|
80
|
+
if (!report || typeof report !== 'object') return '';
|
|
81
|
+
var chips = '';
|
|
82
|
+
var seen = {};
|
|
83
|
+
var addPr = function(value, title) {
|
|
84
|
+
var prId = _detailNormalizePrId(value);
|
|
85
|
+
if (!prId || seen['pr:' + prId]) return;
|
|
86
|
+
seen['pr:' + prId] = true;
|
|
87
|
+
chips += renderArtifactLink({ type: 'pr', id: prId, label: prId, title: title || ('Pull request ' + prId) }) + ' ';
|
|
88
|
+
};
|
|
89
|
+
addPr(report.pr, '');
|
|
90
|
+
var arts = Array.isArray(report.artifacts) ? report.artifacts : [];
|
|
91
|
+
arts.forEach(function(a) {
|
|
92
|
+
if (!a || typeof a !== 'object') return;
|
|
93
|
+
var t = String(a.type || '');
|
|
94
|
+
var p = String(a.path || '');
|
|
95
|
+
if (!p) return;
|
|
96
|
+
if (t === 'pr') {
|
|
97
|
+
addPr(p, a.title || '');
|
|
98
|
+
} else if (t === 'note') {
|
|
99
|
+
var base = p.replace(/^.*[\\/]/, '');
|
|
100
|
+
if (!base || seen['note:' + base]) return;
|
|
101
|
+
seen['note:' + base] = true;
|
|
102
|
+
var noteLabel = base.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '').slice(0, 30) || base;
|
|
103
|
+
chips += renderArtifactLink({ type: 'note', id: base, label: noteLabel, title: a.title || ('Note: ' + base) }) + ' ';
|
|
104
|
+
} else if (t === 'kb') {
|
|
105
|
+
var kbId = p.replace(/^kb:/, '').replace(/^knowledge\//, '');
|
|
106
|
+
if (!kbId || kbId.indexOf('/') <= 0 || seen['kb:' + kbId]) return;
|
|
107
|
+
seen['kb:' + kbId] = true;
|
|
108
|
+
var kbFile = kbId.slice(kbId.indexOf('/') + 1);
|
|
109
|
+
var kbLabel = kbFile.replace(/\.md$/, '').slice(0, 30) || kbFile;
|
|
110
|
+
chips += renderArtifactLink({ type: 'kb', id: kbId, label: kbLabel, title: a.title || ('KB: ' + kbFile) }) + ' ';
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (!chips) return '';
|
|
114
|
+
return '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:8px">' + chips + '</div>';
|
|
115
|
+
}
|
|
116
|
+
|
|
36
117
|
function renderDetailContent(detail, tab) {
|
|
37
118
|
const el = document.getElementById('detail-content');
|
|
38
119
|
|
|
@@ -72,8 +153,15 @@ function renderDetailContent(detail, tab) {
|
|
|
72
153
|
if (last.result) html += 'Result: <span style="color:var(--' + (last.result === 'error' ? 'red' : 'green') + ')">' + escHtml(last.result) + '</span>\n';
|
|
73
154
|
}
|
|
74
155
|
html += '</div>';
|
|
156
|
+
// W-mqptc3n3 — Surface artifact links (PR / note / KB) from the last
|
|
157
|
+
// SUCCESSFUL run under 'Last Result'. The chips render even when the agent
|
|
158
|
+
// is idle (>5min, so resultSummary is absent) by sourcing the report from
|
|
159
|
+
// recentDispatches.
|
|
160
|
+
var lastResultArtifacts = _renderLastResultArtifactChips(_lastSuccessfulCompletionReport(detail));
|
|
75
161
|
if (detail.statusData.resultSummary) {
|
|
76
|
-
html += '<h4>Last Result</h4><div class="section" style="border-left:3px solid var(--green);padding-left:12px">' + renderMd(detail.statusData.resultSummary) + '</div>';
|
|
162
|
+
html += '<h4>Last Result</h4><div class="section" style="border-left:3px solid var(--green);padding-left:12px">' + renderMd(detail.statusData.resultSummary) + lastResultArtifacts + '</div>';
|
|
163
|
+
} else if (lastResultArtifacts) {
|
|
164
|
+
html += '<h4>Last Result</h4><div class="section" style="border-left:3px solid var(--green);padding-left:12px">' + lastResultArtifacts + '</div>';
|
|
77
165
|
}
|
|
78
166
|
}
|
|
79
167
|
|
|
@@ -90,7 +178,7 @@ function renderDetailContent(detail, tab) {
|
|
|
90
178
|
html += '<h4>Latest Output</h4><div class="section">' + renderMd(detail.outputLog) + '</div>';
|
|
91
179
|
}
|
|
92
180
|
|
|
93
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml()/renderMd() (fields: status task, dispatch task/result/reason, inbox name/content, output log, result summary)
|
|
181
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml()/renderMd()/renderArtifactLink() (fields: status task, dispatch task/result/reason, inbox name/content, output log, result summary, last-result artifact chips (pr/note/kb via renderArtifactLink))
|
|
94
182
|
el.innerHTML = html;
|
|
95
183
|
} else if (tab === 'live') {
|
|
96
184
|
var startedAt = detail.statusData?.started_at;
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -606,6 +606,10 @@ function renderPrdProgress(prog) {
|
|
|
606
606
|
if (!e2eByPlan[planKey]) e2eByPlan[planKey] = [];
|
|
607
607
|
e2eByPlan[planKey].push(pr);
|
|
608
608
|
}
|
|
609
|
+
// W-mqps9jlb — PRD-persisted verify/E2E PR refs (prd.verifyPrs[], surfaced by
|
|
610
|
+
// queries.getPrdInfo as progress.verifyPrsByPlan). Merged into renderE2eSection
|
|
611
|
+
// so a merged aggregate PR survives removal of its live tracker record.
|
|
612
|
+
const persistedVerifyByPlan = (prog && prog.verifyPrsByPlan) || {};
|
|
609
613
|
|
|
610
614
|
// Find testing guides in prd/ (verify-*.md files). Issue #2949 —
|
|
611
615
|
// verifyGuides moved off /api/status to /api/verify-guides; refresh.js
|
|
@@ -618,13 +622,24 @@ function renderPrdProgress(prog) {
|
|
|
618
622
|
}
|
|
619
623
|
|
|
620
624
|
function renderE2eSection(planFile) {
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
// so
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
625
|
+
// Merge the LIVE tracker E2E PRs (e2eByPlan[planFile]) with the PRD-persisted
|
|
626
|
+
// verify PR refs (persistedVerifyByPlan[planFile], stamped onto prd.verifyPrs
|
|
627
|
+
// by lifecycle.persistVerifyPrsToPrd). Dedup by pr.id with the LIVE record
|
|
628
|
+
// winning, so a merged aggregate PR keeps rendering on its PRD even after its
|
|
629
|
+
// live tracker record has been swept (e.g. daily-remove-merged-prs) — W-mqps9jlb.
|
|
630
|
+
const livePrs = e2eByPlan[planFile] || [];
|
|
631
|
+
const persistedPrs = persistedVerifyByPlan[planFile] || [];
|
|
632
|
+
const mergedById = new Map();
|
|
633
|
+
for (const p of persistedPrs) { if (p && p.id != null) mergedById.set(p.id, { ...p, _project: p.project || p._project || '' }); }
|
|
634
|
+
const liveNoId = [];
|
|
635
|
+
for (const p of livePrs) { if (p && p.id != null) { mergedById.set(p.id, p); } else if (p) { liveNoId.push(p); } }
|
|
636
|
+
// Keep abandoned/closed/merged E2E PRs visible (rendered de-emphasized via a
|
|
637
|
+
// muted status badge + reduced opacity) so a terminal verify PR still shows on
|
|
638
|
+
// its PRD rather than silently vanishing. Sort terminal rows after active ones;
|
|
639
|
+
// abandoned last of all.
|
|
640
|
+
const termRank = (s) => (s === 'abandoned' ? 2 : (s && s !== 'active' ? 1 : 0));
|
|
641
|
+
const prs = [...mergedById.values(), ...liveNoId]
|
|
642
|
+
.sort((a, b) => (termRank(a.status) - termRank(b.status)) || ((a.status === 'abandoned' ? 1 : 0) - (b.status === 'abandoned' ? 1 : 0)));
|
|
628
643
|
const guide = guideByPlan[planFile];
|
|
629
644
|
if (prs.length === 0 && !guide) return '';
|
|
630
645
|
let html = '<div style="margin:6px 0 10px;padding:6px 10px;background:rgba(56,139,253,0.08);border:1px solid rgba(56,139,253,0.25);border-radius:4px">';
|
|
@@ -632,11 +647,13 @@ function renderPrdProgress(prog) {
|
|
|
632
647
|
html += '<div style="font-size:var(--text-sm);font-weight:600;color:var(--blue);margin-bottom:4px">E2E Aggregate PRs</div>';
|
|
633
648
|
html += prs.map(pr => {
|
|
634
649
|
const statusColor = _prStatusColor(pr.status);
|
|
650
|
+
// Terminal (non-active) aggregates render de-emphasized like the abandoned case.
|
|
651
|
+
const rowOpacity = (pr.status && pr.status !== 'active') ? '0.65' : '1';
|
|
635
652
|
// P-79b47b0c — render PR id as in-stack chip (was raw <a target="_blank">).
|
|
636
653
|
const prChip = (typeof renderArtifactLink === 'function')
|
|
637
654
|
? renderArtifactLink({ type: 'pr', id: pr.id, label: pr.id, title: pr.title || pr.id })
|
|
638
655
|
: '<code>' + escHtml(pr.id) + '</code>';
|
|
639
|
-
return '<div style="display:flex;align-items:center;gap:6px;padding:2px 0;font-size:var(--text-base)">' +
|
|
656
|
+
return '<div style="display:flex;align-items:center;gap:6px;padding:2px 0;font-size:var(--text-base);opacity:' + rowOpacity + '">' +
|
|
640
657
|
'<span style="color:' + statusColor + ';font-size:var(--text-xs);font-weight:600;padding:1px 4px;border:1px solid;border-radius:3px">' + escHtml(pr.status || 'active') + '</span>' +
|
|
641
658
|
prChip +
|
|
642
659
|
'<span style="color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml(pr.title || '') + '</span>' +
|
package/engine/lifecycle.js
CHANGED
|
@@ -5557,6 +5557,123 @@ function syncPrdFromPrs(config) {
|
|
|
5557
5557
|
}
|
|
5558
5558
|
}
|
|
5559
5559
|
|
|
5560
|
+
// ─── Persist verify/E2E aggregate PRs onto the PRD JSON (W-mqps9jlb) ──────────
|
|
5561
|
+
// The PRD view's "E2E Aggregate PRs" section (dashboard/js/render-prd.js#
|
|
5562
|
+
// renderE2eSection) sources its PRs from the LIVE tracker (pull-requests.json).
|
|
5563
|
+
// A merged aggregate PR therefore VANISHES from the PRD page once its record is
|
|
5564
|
+
// swept — by the daily-remove-merged-prs schedule, a manual delete, or a
|
|
5565
|
+
// merge-back/reconcile flow. To make the PRD view survive removal of the live
|
|
5566
|
+
// record we durably stamp a compact reference array (`prd.verifyPrs`) onto the
|
|
5567
|
+
// owning PRD JSON.
|
|
5568
|
+
//
|
|
5569
|
+
// This runs per-tick (right after syncPrdFromPrs). It reads the NORMALIZED
|
|
5570
|
+
// pull-requests.json schema, so a single sweep covers BOTH the GitHub and ADO
|
|
5571
|
+
// pollers at once (engine/github.js + engine/ado.js are intentional mirrors
|
|
5572
|
+
// writing the same schema) — the durable single-source approach the task
|
|
5573
|
+
// allows in lieu of hooking each poller's merged transition separately. Because
|
|
5574
|
+
// it re-stamps every tick, the persisted copy is KEPT CURRENT through the
|
|
5575
|
+
// merged transition: the last poll before the sweep records status:'merged' +
|
|
5576
|
+
// mergedAt. Append-only-ish — a previously-recorded verify PR is never dropped
|
|
5577
|
+
// just because it left the live tracker.
|
|
5578
|
+
|
|
5579
|
+
/** Match the dashboard's renderE2eSection predicate (render-prd.js ~line 603). */
|
|
5580
|
+
function _isVerifyAggregatePr(pr) {
|
|
5581
|
+
if (!pr || typeof pr !== 'object') return false;
|
|
5582
|
+
// Literal 'verify'/'pr' itemType strings to mirror render-prd.js exactly —
|
|
5583
|
+
// note there is no WORK_TYPE.PR constant; the PR aggregate itemType is 'pr'.
|
|
5584
|
+
return pr.itemType === WORK_TYPE.VERIFY
|
|
5585
|
+
|| pr.itemType === 'pr'
|
|
5586
|
+
|| pr.e2e === true
|
|
5587
|
+
|| (typeof pr.title === 'string' && pr.title.startsWith('[E2E]'));
|
|
5588
|
+
}
|
|
5589
|
+
|
|
5590
|
+
/** Compact, render-ready reference persisted onto prd.verifyPrs[]. */
|
|
5591
|
+
function _verifyPrRef(pr, projectName) {
|
|
5592
|
+
const ref = {
|
|
5593
|
+
id: pr.id,
|
|
5594
|
+
url: pr.url || '',
|
|
5595
|
+
title: pr.title || '',
|
|
5596
|
+
status: pr.status || PR_STATUS.ACTIVE,
|
|
5597
|
+
project: pr._project || projectName || '',
|
|
5598
|
+
};
|
|
5599
|
+
if (pr.mergedAt) ref.mergedAt = pr.mergedAt;
|
|
5600
|
+
return ref;
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
/** True when persisting `refMap` would change the existing prd.verifyPrs[]. */
|
|
5604
|
+
function _verifyPrsNeedUpdate(existing, refMap) {
|
|
5605
|
+
const byId = new Map();
|
|
5606
|
+
for (const e of (existing || [])) { if (e && e.id) byId.set(e.id, e); }
|
|
5607
|
+
for (const [id, ref] of refMap) {
|
|
5608
|
+
const cur = byId.get(id);
|
|
5609
|
+
if (!cur) return true;
|
|
5610
|
+
if ((cur.status || '') !== (ref.status || '')) return true;
|
|
5611
|
+
if ((cur.url || '') !== (ref.url || '')) return true;
|
|
5612
|
+
if ((cur.title || '') !== (ref.title || '')) return true;
|
|
5613
|
+
if ((cur.project || '') !== (ref.project || '')) return true;
|
|
5614
|
+
if ((cur.mergedAt || '') !== (ref.mergedAt || '')) return true;
|
|
5615
|
+
}
|
|
5616
|
+
return false;
|
|
5617
|
+
}
|
|
5618
|
+
|
|
5619
|
+
function persistVerifyPrsToPrd(config) {
|
|
5620
|
+
try {
|
|
5621
|
+
config = config || queries.getConfig();
|
|
5622
|
+
if (!fs.existsSync(PRD_DIR)) return;
|
|
5623
|
+
const projects = shared.getProjects(config);
|
|
5624
|
+
if (!projects.length) return;
|
|
5625
|
+
|
|
5626
|
+
// Group verify/E2E PR refs by sourcePlan (PRD filename) across all projects.
|
|
5627
|
+
const refsByPlan = new Map(); // planFile -> Map(id -> ref)
|
|
5628
|
+
for (const project of projects) {
|
|
5629
|
+
let prs;
|
|
5630
|
+
try { prs = safeJsonArr(shared.projectPrPath(project)); } catch { continue; }
|
|
5631
|
+
for (const pr of (prs || [])) {
|
|
5632
|
+
if (!pr || !pr.id) continue;
|
|
5633
|
+
if (!_isVerifyAggregatePr(pr)) continue;
|
|
5634
|
+
const planFile = typeof pr.sourcePlan === 'string' ? pr.sourcePlan.trim() : '';
|
|
5635
|
+
if (!planFile) continue; // can't attribute to a PRD without a sourcePlan
|
|
5636
|
+
if (!refsByPlan.has(planFile)) refsByPlan.set(planFile, new Map());
|
|
5637
|
+
// A canonical PR id is unique to one project, so last-writer-wins here
|
|
5638
|
+
// only ever collapses byte-identical duplicates within a tick.
|
|
5639
|
+
refsByPlan.get(planFile).set(pr.id, _verifyPrRef(pr, project.name));
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
if (refsByPlan.size === 0) return;
|
|
5643
|
+
|
|
5644
|
+
for (const [planFile, refMap] of refsByPlan) {
|
|
5645
|
+
// Resolve the PRD file (active or archived). sourcePlan is a bare filename.
|
|
5646
|
+
const candidates = [path.join(PRD_DIR, planFile), path.join(PRD_DIR, 'archive', planFile)];
|
|
5647
|
+
const fpath = candidates.find(p => { try { return fs.existsSync(p); } catch { return false; } });
|
|
5648
|
+
if (!fpath) continue;
|
|
5649
|
+
|
|
5650
|
+
// Lock-free peek (mirrors syncPrdItemStatus): only take the write lock
|
|
5651
|
+
// when the persisted refs would actually change. safeJsonNoRestore so an
|
|
5652
|
+
// archived PRD's .backup sidecar can't resurrect stale state.
|
|
5653
|
+
let plan;
|
|
5654
|
+
try { plan = safeJsonNoRestore(fpath); } catch { continue; }
|
|
5655
|
+
if (!plan || typeof plan !== 'object') continue;
|
|
5656
|
+
if (!_verifyPrsNeedUpdate(plan.verifyPrs, refMap)) continue;
|
|
5657
|
+
|
|
5658
|
+
mutateJsonFileLocked(fpath, (fresh) => {
|
|
5659
|
+
if (!fresh || typeof fresh !== 'object') return fresh;
|
|
5660
|
+
const byId = new Map();
|
|
5661
|
+
for (const e of (Array.isArray(fresh.verifyPrs) ? fresh.verifyPrs : [])) {
|
|
5662
|
+
if (e && e.id) byId.set(e.id, e);
|
|
5663
|
+
}
|
|
5664
|
+
// Upsert live refs (kept current); never drop a previously-recorded one.
|
|
5665
|
+
for (const [id, ref] of refMap) {
|
|
5666
|
+
byId.set(id, { ...(byId.get(id) || {}), ...ref });
|
|
5667
|
+
}
|
|
5668
|
+
fresh.verifyPrs = [...byId.values()];
|
|
5669
|
+
return fresh;
|
|
5670
|
+
}, { skipWriteIfUnchanged: true });
|
|
5671
|
+
}
|
|
5672
|
+
} catch (err) {
|
|
5673
|
+
try { log('warn', `persistVerifyPrsToPrd error: ${err?.message || err}`); } catch { /* engine not available */ }
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5560
5677
|
// ─── Failure Classification ─────────────────────────────────────────────────
|
|
5561
5678
|
|
|
5562
5679
|
/**
|
|
@@ -5794,6 +5911,7 @@ module.exports = {
|
|
|
5794
5911
|
promoteCompletionArtifacts,
|
|
5795
5912
|
runPostCompletionHooks,
|
|
5796
5913
|
syncPrdFromPrs,
|
|
5914
|
+
persistVerifyPrsToPrd,
|
|
5797
5915
|
resolveWorkItemPath,
|
|
5798
5916
|
isItemCompleted,
|
|
5799
5917
|
classifyFailure,
|
package/engine/queries.js
CHANGED
|
@@ -1971,6 +1971,11 @@ function getPrdInfo(config) {
|
|
|
1971
1971
|
|
|
1972
1972
|
let allPrdItems = [];
|
|
1973
1973
|
const existingPrds = [];
|
|
1974
|
+
// W-mqps9jlb — verify/E2E aggregate PR refs persisted onto each PRD JSON
|
|
1975
|
+
// (prd.verifyPrs[], stamped by lifecycle.persistVerifyPrsToPrd). Keyed by PRD
|
|
1976
|
+
// filename so render-prd.js#renderE2eSection can merge them with the live
|
|
1977
|
+
// tracker and keep rendering a merged aggregate after its live record is swept.
|
|
1978
|
+
const verifyPrsByPlan = {};
|
|
1974
1979
|
let latestStat = null;
|
|
1975
1980
|
|
|
1976
1981
|
// Check if directory listings need refresh
|
|
@@ -2019,6 +2024,13 @@ function getPrdInfo(config) {
|
|
|
2019
2024
|
completedAt: plan.completedAt || '',
|
|
2020
2025
|
_archived: archived,
|
|
2021
2026
|
});
|
|
2027
|
+
if (Array.isArray(plan.verifyPrs) && plan.verifyPrs.length > 0) {
|
|
2028
|
+
verifyPrsByPlan[pf] = plan.verifyPrs.map(r => ({
|
|
2029
|
+
id: r.id, url: r.url || '', title: r.title || '',
|
|
2030
|
+
status: r.status || 'active', project: r.project || '',
|
|
2031
|
+
...(r.mergedAt ? { mergedAt: r.mergedAt } : {}),
|
|
2032
|
+
}));
|
|
2033
|
+
}
|
|
2022
2034
|
for (const f of plan.missing_features) {
|
|
2023
2035
|
allPrdItems.push({
|
|
2024
2036
|
...f, _source: pf, _planStatus: plan.status || 'active',
|
|
@@ -2205,6 +2217,7 @@ function getPrdInfo(config) {
|
|
|
2205
2217
|
|
|
2206
2218
|
const progress = {
|
|
2207
2219
|
total, complete, inProgress, paused, missing, donePercent, planTimings,
|
|
2220
|
+
verifyPrsByPlan,
|
|
2208
2221
|
items: items.map(i => ({
|
|
2209
2222
|
id: i.id, name: i.name || i.title, priority: i.priority,
|
|
2210
2223
|
complexity: i.estimated_complexity || i.size, status: i.status || 'missing',
|
package/engine.js
CHANGED
|
@@ -166,7 +166,7 @@ const ghToken = require('./engine/gh-token');
|
|
|
166
166
|
|
|
167
167
|
const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, reconcilePrdStatuses, handlePostMerge, checkPlanCompletion,
|
|
168
168
|
syncPrsFromOutput, updatePrAfterReview, updatePrAfterFix, checkForLearnings, extractSkillsFromOutput,
|
|
169
|
-
updateAgentHistory, updateMetrics, createReviewFeedbackForAuthor, parseAgentOutput, syncPrdFromPrs,
|
|
169
|
+
updateAgentHistory, updateMetrics, createReviewFeedbackForAuthor, parseAgentOutput, syncPrdFromPrs, persistVerifyPrsToPrd,
|
|
170
170
|
isItemCompleted, classifyFailure: classifyFailureFallback, diagnoseEmptyOutput, processPendingRebases, resolveWorkItemPath,
|
|
171
171
|
mergeArtifactNotes, promoteCompletionArtifacts, pruneScopeMismatchDuplicatePrs } = require('./engine/lifecycle');
|
|
172
172
|
|
|
@@ -9281,6 +9281,7 @@ async function tickInner() {
|
|
|
9281
9281
|
if (_isTickStale(myGeneration)) return;
|
|
9282
9282
|
// Sync PR status back to PRD items (missing → done when active PR exists)
|
|
9283
9283
|
try { syncPrdFromPrs(config); } catch (err) { log('warn', `PRD sync error: ${err?.message || err}`); }
|
|
9284
|
+
try { persistVerifyPrsToPrd(config); } catch (err) { log('warn', `PRD verify-PR persist error: ${err?.message || err}`); }
|
|
9284
9285
|
// Check if any plans can be marked completed (all features done/in-pr)
|
|
9285
9286
|
try {
|
|
9286
9287
|
const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2231",
|
|
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"
|