@yemi33/minions 0.1.154 → 0.1.156
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 +18 -1
- package/dashboard/js/render-pipelines.js +138 -6
- package/dashboard/js/render-plans.js +44 -4
- package/dashboard/js/render-prd.js +1 -1
- package/dashboard/js/render-work-items.js +12 -0
- package/dashboard/styles.css +12 -0
- package/dashboard.js +5 -5
- package/engine/shared.js +50 -1
- package/engine.js +39 -10
- package/package.json +1 -1
- package/playbooks/implement.md +1 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.156 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/shared.js
|
|
8
|
+
|
|
9
|
+
### Dashboard
|
|
10
|
+
- dashboard/js/render-plans.js
|
|
11
|
+
- dashboard/js/render-prd.js
|
|
12
|
+
|
|
13
|
+
### Other
|
|
14
|
+
- test/unit.test.js
|
|
15
|
+
|
|
16
|
+
## 0.1.155 (2026-04-02)
|
|
4
17
|
|
|
5
18
|
### Engine
|
|
6
19
|
- engine.js
|
|
@@ -17,11 +30,15 @@
|
|
|
17
30
|
- dashboard/js/refresh.js
|
|
18
31
|
- dashboard/js/render-inbox.js
|
|
19
32
|
- dashboard/js/render-kb.js
|
|
33
|
+
- dashboard/js/render-pipelines.js
|
|
20
34
|
- dashboard/js/render-plans.js
|
|
21
35
|
- dashboard/js/render-prd.js
|
|
22
36
|
- dashboard/js/render-work-items.js
|
|
23
37
|
- dashboard/styles.css
|
|
24
38
|
|
|
39
|
+
### Playbooks
|
|
40
|
+
- implement.md
|
|
41
|
+
|
|
25
42
|
### Documentation
|
|
26
43
|
- deprecated.json
|
|
27
44
|
|
|
@@ -2,6 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
let _pipelinesData = [];
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Render clickable artifact links for a pipeline stage.
|
|
7
|
+
* Each artifact type gets an icon and navigates to the relevant detail view.
|
|
8
|
+
*/
|
|
9
|
+
function _renderArtifactLinks(artifacts) {
|
|
10
|
+
if (!artifacts) return '';
|
|
11
|
+
var links = [];
|
|
12
|
+
var linkStyle = 'display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:10px;font-size:10px;cursor:pointer;text-decoration:none;color:var(--blue);background:color-mix(in srgb, var(--blue) 10%, transparent);border:1px solid color-mix(in srgb, var(--blue) 20%, transparent)';
|
|
13
|
+
|
|
14
|
+
// Work items → navigate to work page & open detail
|
|
15
|
+
(artifacts.workItems || []).forEach(function(id) {
|
|
16
|
+
links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'work\');setTimeout(function(){openWorkItemDetail(\'' + escHtml(id) + '\')},200)" title="Open work item ' + escHtml(id) + '">⚙ ' + escHtml(id) + '</span>');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Meetings → navigate to meetings page & open detail
|
|
20
|
+
(artifacts.meetings || []).forEach(function(id) {
|
|
21
|
+
links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'meetings\');setTimeout(function(){openMeetingDetail(\'' + escHtml(id) + '\')},200)" title="Open meeting ' + escHtml(id) + '">💬 ' + escHtml(id) + '</span>');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Plans → navigate to plans page
|
|
25
|
+
(artifacts.plans || []).forEach(function(name) {
|
|
26
|
+
links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'plans\')" title="Plan: ' + escHtml(name) + '">📋 ' + escHtml(name.replace(/\.md$/, '').slice(0, 30)) + '</span>');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// PRDs → navigate to PRD page
|
|
30
|
+
(artifacts.prds || []).forEach(function(name) {
|
|
31
|
+
links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'prd\')" title="PRD: ' + escHtml(name) + '">📄 ' + escHtml(name.replace(/\.json$/, '').slice(0, 30)) + '</span>');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// PRs → navigate to PRs page
|
|
35
|
+
(artifacts.prs || []).forEach(function(id) {
|
|
36
|
+
links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'prs\')" title="Pull request ' + escHtml(id) + '">🔀 PR-' + escHtml(id) + '</span>');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Sub-stages (parallel) — just label them, no nav needed
|
|
40
|
+
(artifacts.subStages || []).forEach(function(id) {
|
|
41
|
+
links.push('<span style="' + linkStyle + ';cursor:default;color:var(--muted);background:color-mix(in srgb, var(--muted) 8%, transparent);border-color:color-mix(in srgb, var(--muted) 15%, transparent)" title="Sub-stage ' + escHtml(id) + '">⚓ ' + escHtml(id) + '</span>');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (links.length === 0) return '';
|
|
45
|
+
return '<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px">' + links.join('') + '</div>';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Collect and deduplicate artifacts from all stages in a run.
|
|
50
|
+
* Returns { merged: { workItems, meetings, plans, prds, prs, subStages }, total }.
|
|
51
|
+
*/
|
|
52
|
+
function _collectRunArtifacts(run) {
|
|
53
|
+
var merged = { workItems: [], meetings: [], plans: [], prds: [], prs: [], subStages: [] };
|
|
54
|
+
var stages = run.stages || {};
|
|
55
|
+
for (var stageId in stages) {
|
|
56
|
+
var a = stages[stageId].artifacts || {};
|
|
57
|
+
['workItems', 'meetings', 'plans', 'prds', 'prs', 'subStages'].forEach(function(key) {
|
|
58
|
+
(a[key] || []).forEach(function(v) {
|
|
59
|
+
if (merged[key].indexOf(v) === -1) merged[key].push(v);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
var total = merged.workItems.length + merged.meetings.length + merged.plans.length + merged.prds.length + merged.prs.length;
|
|
64
|
+
return { merged: merged, total: total };
|
|
65
|
+
}
|
|
66
|
+
|
|
5
67
|
function renderPipelines(pipelines) {
|
|
6
68
|
_pipelinesData = pipelines || [];
|
|
7
69
|
const el = document.getElementById('pipelines-content');
|
|
@@ -29,6 +91,45 @@ function renderPipelines(pipelines) {
|
|
|
29
91
|
return '<span style="color:' + color + ';font-size:11px" title="' + escHtml(s.id) + ': ' + escHtml(s.title || s.type) + ' (' + stageStatus + ')">' + icon + ' ' + escHtml(s.id) + '</span>';
|
|
30
92
|
}).join(' <span style="color:var(--border)">\u2192</span> ');
|
|
31
93
|
|
|
94
|
+
// Build step-progress indicator for pipelines with a run
|
|
95
|
+
var progressHtml = '';
|
|
96
|
+
var displayRun = activeRun || lastRun;
|
|
97
|
+
if (displayRun && (p.stages || []).length > 0) {
|
|
98
|
+
var totalStages = (p.stages || []).length;
|
|
99
|
+
var completedCount = 0;
|
|
100
|
+
var runningCount = 0;
|
|
101
|
+
var failedCount = 0;
|
|
102
|
+
(p.stages || []).forEach(function(s) {
|
|
103
|
+
var st = displayRun.stages?.[s.id]?.status;
|
|
104
|
+
if (st === 'completed') completedCount++;
|
|
105
|
+
else if (st === 'running') runningCount++;
|
|
106
|
+
else if (st === 'failed') failedCount++;
|
|
107
|
+
});
|
|
108
|
+
var pct = Math.round((completedCount / totalStages) * 100);
|
|
109
|
+
|
|
110
|
+
// Segmented progress bar — one segment per stage
|
|
111
|
+
var segments = (p.stages || []).map(function(s) {
|
|
112
|
+
var st = displayRun.stages?.[s.id]?.status || 'pending';
|
|
113
|
+
var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
|
|
114
|
+
return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / totalStages) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
|
|
115
|
+
}).join('');
|
|
116
|
+
|
|
117
|
+
var statusParts = [];
|
|
118
|
+
if (completedCount) statusParts.push(completedCount + ' done');
|
|
119
|
+
if (runningCount) statusParts.push(runningCount + ' running');
|
|
120
|
+
if (failedCount) statusParts.push(failedCount + ' failed');
|
|
121
|
+
var remaining = totalStages - completedCount - runningCount - failedCount;
|
|
122
|
+
if (remaining > 0) statusParts.push(remaining + ' pending');
|
|
123
|
+
|
|
124
|
+
progressHtml = '<div class="pl-progress-wrap">' +
|
|
125
|
+
'<div class="pl-progress-bar">' + segments + '</div>' +
|
|
126
|
+
'<div class="pl-progress-label">' +
|
|
127
|
+
'<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '%</span>' +
|
|
128
|
+
'<span style="color:var(--muted)">' + statusParts.join(' \u00b7 ') + '</span>' +
|
|
129
|
+
'</div>' +
|
|
130
|
+
'</div>';
|
|
131
|
+
}
|
|
132
|
+
|
|
32
133
|
return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openPipelineDetail(\'' + escHtml(p.id) + '\')">' +
|
|
33
134
|
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
34
135
|
'<strong style="font-size:13px">' + escHtml(p.title) + '</strong>' +
|
|
@@ -39,6 +140,7 @@ function renderPipelines(pipelines) {
|
|
|
39
140
|
'</div>' +
|
|
40
141
|
'</div>' +
|
|
41
142
|
'<div style="margin-top:6px;display:flex;gap:4px;align-items:center;flex-wrap:wrap">' + stageFlow + '</div>' +
|
|
143
|
+
progressHtml +
|
|
42
144
|
'</div>';
|
|
43
145
|
}).join('');
|
|
44
146
|
}
|
|
@@ -61,7 +163,28 @@ function openPipelineDetail(id) {
|
|
|
61
163
|
'</div>' +
|
|
62
164
|
'</div>';
|
|
63
165
|
|
|
64
|
-
// Stage detail
|
|
166
|
+
// Stage detail with progress bar
|
|
167
|
+
var detailRun = activeRun || (p.runs || []).slice(-1)[0];
|
|
168
|
+
if (detailRun && (p.stages || []).length > 0) {
|
|
169
|
+
var dtotal = (p.stages || []).length;
|
|
170
|
+
var ddone = 0, drun = 0, dfail = 0;
|
|
171
|
+
(p.stages || []).forEach(function(s) {
|
|
172
|
+
var st = detailRun.stages?.[s.id]?.status;
|
|
173
|
+
if (st === 'completed') ddone++;
|
|
174
|
+
else if (st === 'running') drun++;
|
|
175
|
+
else if (st === 'failed') dfail++;
|
|
176
|
+
});
|
|
177
|
+
var dpct = Math.round((ddone / dtotal) * 100);
|
|
178
|
+
var dsegs = (p.stages || []).map(function(s) {
|
|
179
|
+
var st = detailRun.stages?.[s.id]?.status || 'pending';
|
|
180
|
+
var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
|
|
181
|
+
return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / dtotal) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
|
|
182
|
+
}).join('');
|
|
183
|
+
html += '<div class="pl-progress-wrap">' +
|
|
184
|
+
'<div class="pl-progress-bar" style="height:8px">' + dsegs + '</div>' +
|
|
185
|
+
'<div class="pl-progress-label"><span style="font-weight:600;color:' + (dpct === 100 ? 'var(--green)' : dfail ? 'var(--red)' : 'var(--blue)') + '">' + dpct + '% complete</span> <span style="color:var(--muted)">(' + ddone + '/' + dtotal + ' stages)</span></div>' +
|
|
186
|
+
'</div>';
|
|
187
|
+
}
|
|
65
188
|
html += '<h4 style="font-size:12px;color:var(--blue);margin:0">Stages</h4>';
|
|
66
189
|
(p.stages || []).forEach(function(s, i) {
|
|
67
190
|
var stageRun = activeRun?.stages?.[s.id] || {};
|
|
@@ -75,6 +198,7 @@ function openPipelineDetail(id) {
|
|
|
75
198
|
'<span style="color:' + statusColor + ';font-size:10px;font-weight:600">' + stageStatus.toUpperCase() + '</span>' +
|
|
76
199
|
'</div>' +
|
|
77
200
|
'<div style="font-size:10px;color:var(--muted);margin-top:4px">Type: ' + escHtml(s.type) + ' | Depends on: ' + escHtml(deps) + (s.agent ? ' | Agent: ' + escHtml(s.agent) : '') + '</div>' +
|
|
201
|
+
_renderArtifactLinks(stageRun.artifacts) +
|
|
78
202
|
(stageRun.output ? '<div style="margin-top:6px;font-size:11px;max-height:150px;overflow-y:auto">' + renderMd(stageRun.output.slice(0, 500)) + '</div>' : '') +
|
|
79
203
|
(stageStatus === 'waiting-human' ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);border-color:var(--green);margin-top:6px" onclick="_continuePipeline(\'' + escHtml(id) + '\',\'' + escHtml(s.id) + '\',this)">Continue</button>' : '') +
|
|
80
204
|
'</div>';
|
|
@@ -84,12 +208,20 @@ function openPipelineDetail(id) {
|
|
|
84
208
|
var runs = (p.runs || []).slice(-5).reverse();
|
|
85
209
|
if (runs.length > 0) {
|
|
86
210
|
html += '<h4 style="font-size:12px;color:var(--blue);margin:0">Recent Runs</h4>';
|
|
87
|
-
runs.forEach(function(r) {
|
|
211
|
+
runs.forEach(function(r, ri) {
|
|
88
212
|
var color = r.status === 'completed' ? 'var(--green)' : r.status === 'failed' ? 'var(--red)' : r.status === 'running' ? 'var(--blue)' : 'var(--muted)';
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
213
|
+
// Collect all artifacts across stages for this run
|
|
214
|
+
var runArtifacts = _collectRunArtifacts(r);
|
|
215
|
+
var artifactCount = runArtifacts.total;
|
|
216
|
+
var toggleId = 'run-artifacts-' + ri;
|
|
217
|
+
html += '<div style="font-size:10px">' +
|
|
218
|
+
'<div style="display:flex;gap:8px;align-items:center">' +
|
|
219
|
+
'<span style="color:' + color + ';font-weight:600">' + r.status + '</span>' +
|
|
220
|
+
'<span style="color:var(--muted)">' + (r.startedAt ? new Date(r.startedAt).toLocaleString() : '') + '</span>' +
|
|
221
|
+
(r.completedAt ? '<span style="color:var(--muted)">\u2192 ' + new Date(r.completedAt).toLocaleString() + '</span>' : '') +
|
|
222
|
+
(artifactCount > 0 ? '<span style="color:var(--blue);cursor:pointer;user-select:none" onclick="var el=document.getElementById(\'' + toggleId + '\');el.style.display=el.style.display===\'none\'?\'flex\':\'none\'" title="Toggle artifacts">' + artifactCount + ' artifact' + (artifactCount !== 1 ? 's' : '') + ' ▾</span>' : '') +
|
|
223
|
+
'</div>' +
|
|
224
|
+
(artifactCount > 0 ? '<div id="' + toggleId + '" style="display:none;flex-wrap:wrap;gap:4px;margin-top:4px;margin-left:12px">' + _renderArtifactLinks(runArtifacts.merged) + '</div>' : '') +
|
|
93
225
|
'</div>';
|
|
94
226
|
});
|
|
95
227
|
}
|
|
@@ -66,9 +66,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
66
66
|
const implementWi = wi.filter(w => w.type !== 'plan-to-prd' && w.type !== 'verify');
|
|
67
67
|
const hasPendingPrd = wi.some(w => w.type === 'plan-to-prd' && (w.status === 'pending' || w.status === 'dispatched'));
|
|
68
68
|
const hasActiveWork = implementWi.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
69
|
-
const allDone = implementWi.length > 0 && implementWi.every(w =>
|
|
70
|
-
w.status === 'done'
|
|
71
|
-
);
|
|
69
|
+
const allDone = implementWi.length > 0 && implementWi.every(w => w.status === 'done');
|
|
72
70
|
const hasFailed = implementWi.some(w => w.status === 'failed');
|
|
73
71
|
|
|
74
72
|
// User-set statuses take priority when no work has started
|
|
@@ -304,6 +302,48 @@ function openArchivedPlansModal() {
|
|
|
304
302
|
document.getElementById('modal').classList.add('open');
|
|
305
303
|
}
|
|
306
304
|
|
|
305
|
+
// Disable all PRD action buttons to prevent double-clicks
|
|
306
|
+
function qaDisablePrdButtons() {
|
|
307
|
+
const container = document.getElementById('qa-generate-prd-btn');
|
|
308
|
+
if (container) container.querySelectorAll('button').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Show plan version action buttons (Run alongside / Replace / Just save)
|
|
312
|
+
function showPlanVersionActions(thread, newFile, originalFile) {
|
|
313
|
+
const esc = newFile.replace(/'/g, "\\'");
|
|
314
|
+
// Look up existing PRD for the original plan's project
|
|
315
|
+
const allPlans = window._lastStatus?.plans || [];
|
|
316
|
+
const origPlan = allPlans.find(p => p.file === originalFile);
|
|
317
|
+
const project = origPlan?.project || '';
|
|
318
|
+
const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project && p.status !== 'completed');
|
|
319
|
+
|
|
320
|
+
const btn = document.createElement('div');
|
|
321
|
+
btn.id = 'qa-generate-prd-btn';
|
|
322
|
+
btn.style.cssText = 'margin:8px 0;padding:8px 12px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:6px;display:flex;flex-wrap:wrap;align-items:center;gap:8px';
|
|
323
|
+
|
|
324
|
+
if (existingPrd) {
|
|
325
|
+
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created — existing PRD running</span>' +
|
|
326
|
+
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Execute this plan as a separate PRD alongside the current one">Run alongside</button>' +
|
|
327
|
+
'<button onclick="qaReplacePrd(\'' + esc + '\')" style="background:var(--orange);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Pause existing PRD, clean pending items, execute this plan instead">Replace old PRD</button>' +
|
|
328
|
+
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer" title="Keep the new version saved without dispatching any work">Just save</button>' +
|
|
329
|
+
'<span style="color:var(--muted);font-size:10px;width:100%">Run alongside keeps current work going. Replace pauses it and starts fresh.</span>';
|
|
330
|
+
} else {
|
|
331
|
+
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created</span>' +
|
|
332
|
+
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Execute plan</button>' +
|
|
333
|
+
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer">Just save</button>' +
|
|
334
|
+
'<span style="color:var(--muted);font-size:10px">Execute dispatches an agent to create PRD items from this plan</span>';
|
|
335
|
+
}
|
|
336
|
+
// Remove any previous action buttons
|
|
337
|
+
const old = thread.querySelector('#qa-generate-prd-btn');
|
|
338
|
+
if (old) old.remove();
|
|
339
|
+
thread.appendChild(btn);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function qaJustSave(el) {
|
|
343
|
+
const container = el.closest('#qa-generate-prd-btn');
|
|
344
|
+
if (container) container.innerHTML = '<span style="color:var(--muted);font-size:11px">Saved. No work dispatched.</span>';
|
|
345
|
+
}
|
|
346
|
+
|
|
307
347
|
async function planExecute(file, project, btn) {
|
|
308
348
|
if (btn) { btn.textContent = 'Executing...'; btn.disabled = true; btn.style.color = 'var(--blue)'; }
|
|
309
349
|
try {
|
|
@@ -694,4 +734,4 @@ async function planUnarchive(file, btn) {
|
|
|
694
734
|
} catch (e) { resetBtn(); alert('Error: ' + e.message); }
|
|
695
735
|
}
|
|
696
736
|
|
|
697
|
-
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
737
|
+
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, qaDisablePrdButtons, showPlanVersionActions, qaJustSave, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
@@ -116,7 +116,7 @@ function renderPrdProgress(prog) {
|
|
|
116
116
|
'failed': 'background:rgba(248,81,73,0.15);color:var(--red)',
|
|
117
117
|
'paused': 'background:rgba(139,148,158,0.15);color:var(--muted)',
|
|
118
118
|
};
|
|
119
|
-
const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '
|
|
119
|
+
const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '\u2014' };
|
|
120
120
|
const style = styles[s] || 'background:var(--surface);color:var(--muted)';
|
|
121
121
|
const label = labels[s] || '—';
|
|
122
122
|
return '<span style="font-size:9px;font-weight:700;padding:2px 6px;border-radius:3px;letter-spacing:0.5px;white-space:nowrap;' + style + '">' + label + '</span>';
|
|
@@ -345,12 +345,22 @@ function openCreateWorkItemModal() {
|
|
|
345
345
|
'</div>' +
|
|
346
346
|
'<label style="color:var(--text);font-size:var(--text-md)">Acceptance Criteria <textarea id="wi-new-ac" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="One criterion per line (optional)"></textarea></label>' +
|
|
347
347
|
'<label style="color:var(--text);font-size:var(--text-md)">References <textarea id="wi-new-refs" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="url | title | type — one per line (optional)"></textarea></label>' +
|
|
348
|
+
'<label id="wi-new-skippr-row" style="color:var(--text);font-size:var(--text-md);display:flex;gap:8px;align-items:center;cursor:pointer"><input type="checkbox" id="wi-new-skippr"> Skip PR creation (push branch only)</label>' +
|
|
348
349
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
349
350
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
350
351
|
'<button onclick="_submitCreateWorkItem()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create</button>' +
|
|
351
352
|
'</div>' +
|
|
352
353
|
'</div>';
|
|
353
354
|
document.getElementById('modal').classList.add('open');
|
|
355
|
+
// Show skipPr checkbox only for implement/fix types
|
|
356
|
+
const typeSelect = document.getElementById('wi-new-type');
|
|
357
|
+
const skipPrRow = document.getElementById('wi-new-skippr-row');
|
|
358
|
+
function _toggleSkipPr() {
|
|
359
|
+
const v = typeSelect?.value || '';
|
|
360
|
+
if (skipPrRow) skipPrRow.style.display = (v === 'implement' || v === 'fix') ? 'flex' : 'none';
|
|
361
|
+
}
|
|
362
|
+
_toggleSkipPr();
|
|
363
|
+
if (typeSelect) typeSelect.addEventListener('change', _toggleSkipPr);
|
|
354
364
|
setTimeout(() => document.getElementById('wi-new-title')?.focus(), 100);
|
|
355
365
|
}
|
|
356
366
|
|
|
@@ -376,6 +386,8 @@ async function _submitCreateWorkItem() {
|
|
|
376
386
|
if (project) body.project = project;
|
|
377
387
|
if (acceptanceCriteria.length) body.acceptanceCriteria = acceptanceCriteria;
|
|
378
388
|
if (references.length && references[0].url) body.references = references;
|
|
389
|
+
const skipPr = document.getElementById('wi-new-skippr')?.checked || false;
|
|
390
|
+
if (skipPr) body.skipPr = true;
|
|
379
391
|
|
|
380
392
|
try { closeModal(); } catch { /* expected */ }
|
|
381
393
|
showToast('cmd-toast', 'Creating work item...', true);
|
package/dashboard/styles.css
CHANGED
|
@@ -178,6 +178,18 @@
|
|
|
178
178
|
.prd-legend-dot.complete { background: var(--green); }
|
|
179
179
|
.prd-legend-dot.in-progress { background: var(--yellow); }
|
|
180
180
|
.prd-legend-dot.missing { background: var(--border); }
|
|
181
|
+
|
|
182
|
+
/* Pipeline Step-Progress */
|
|
183
|
+
.pl-progress-wrap { margin-top: 8px; }
|
|
184
|
+
.pl-progress-bar { width: 100%; height: 6px; background: var(--bg); border-radius: 3px; overflow: hidden; display: flex; border: 1px solid var(--border); }
|
|
185
|
+
.pl-prog-seg { height: 100%; transition: background 0.3s ease; }
|
|
186
|
+
.pl-prog-seg.complete { background: var(--green); }
|
|
187
|
+
.pl-prog-seg.running { background: var(--blue); animation: plSegPulse 1.5s ease-in-out infinite; }
|
|
188
|
+
.pl-prog-seg.failed { background: var(--red); }
|
|
189
|
+
.pl-prog-seg.waiting { background: var(--yellow); }
|
|
190
|
+
.pl-prog-seg.pending { background: transparent; }
|
|
191
|
+
@keyframes plSegPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
192
|
+
.pl-progress-label { display: flex; gap: 8px; align-items: center; margin-top: 4px; font-size: 10px; }
|
|
181
193
|
.prd-items-list { display: flex; flex-direction: column; gap: 3px; max-height: 400px; overflow-y: auto; padding: 0 8px; }
|
|
182
194
|
.prd-item-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--radius-sm); font-size: var(--text-base); background: var(--surface2); border: 1px solid var(--border); border-left: 3px solid var(--border); }
|
|
183
195
|
.prd-item-row.st-done { border-left-color: var(--green); }
|
package/dashboard.js
CHANGED
|
@@ -1038,6 +1038,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1038
1038
|
if (body.agents) item.agents = body.agents;
|
|
1039
1039
|
if (body.references) item.references = body.references;
|
|
1040
1040
|
if (body.acceptanceCriteria) item.acceptanceCriteria = body.acceptanceCriteria;
|
|
1041
|
+
if (body.skipPr === true) item.skipPr = true;
|
|
1041
1042
|
items.push(item);
|
|
1042
1043
|
safeWrite(wiPath, items);
|
|
1043
1044
|
return jsonReply(res, 200, { ok: true, id });
|
|
@@ -1076,6 +1077,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1076
1077
|
if (agent !== undefined) item.agent = agent || null;
|
|
1077
1078
|
if (body.references !== undefined) item.references = body.references;
|
|
1078
1079
|
if (body.acceptanceCriteria !== undefined) item.acceptanceCriteria = body.acceptanceCriteria;
|
|
1080
|
+
if (body.skipPr !== undefined) item.skipPr = body.skipPr === true;
|
|
1079
1081
|
item.updatedAt = new Date().toISOString();
|
|
1080
1082
|
|
|
1081
1083
|
safeWrite(wiPath, items);
|
|
@@ -1643,7 +1645,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1643
1645
|
file: f, format: 'draft', archived,
|
|
1644
1646
|
project: projectMatch ? projectMatch[1].trim() : '',
|
|
1645
1647
|
summary: titleMatch ? titleMatch[1].trim() : f.replace('.md', ''),
|
|
1646
|
-
status: archived ? 'completed' : completedPrdFiles.has(f) ? '
|
|
1648
|
+
status: archived ? 'completed' : completedPrdFiles.has(f) ? 'approved' : 'active',
|
|
1647
1649
|
branchStrategy: '',
|
|
1648
1650
|
featureBranch: '',
|
|
1649
1651
|
itemCount: (content.match(/^\d+\.\s+\*\*/gm) || []).length,
|
|
@@ -3057,7 +3059,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3057
3059
|
}},
|
|
3058
3060
|
|
|
3059
3061
|
// Work items
|
|
3060
|
-
{ method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: 'title, type?, description?, priority?, project?, agent?, agents?, scope?, references?, acceptanceCriteria?', handler: handleWorkItemsCreate },
|
|
3062
|
+
{ method: 'POST', path: '/api/work-items', desc: 'Create a new work item', params: 'title, type?, description?, priority?, project?, agent?, agents?, scope?, references?, acceptanceCriteria?, skipPr?', handler: handleWorkItemsCreate },
|
|
3061
3063
|
{ method: 'POST', path: '/api/work-items/update', desc: 'Edit a pending/failed work item', params: 'id, source?, title?, description?, type?, priority?, agent?, references?, acceptanceCriteria?', handler: handleWorkItemsUpdate },
|
|
3062
3064
|
{ method: 'POST', path: '/api/work-items/retry', desc: 'Reset a failed/dispatched item to pending', params: 'id, source?', handler: handleWorkItemsRetry },
|
|
3063
3065
|
{ method: 'POST', path: '/api/work-items/delete', desc: 'Remove a work item, kill agent, clear dispatch', params: 'id, source?', handler: handleWorkItemsDelete },
|
|
@@ -3136,12 +3138,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3136
3138
|
{ method: 'POST', path: '/api/plans/reject', desc: 'Reject a plan', params: 'file, rejectedBy?, reason?', handler: handlePlansReject },
|
|
3137
3139
|
{ method: 'POST', path: '/api/plans/regenerate', desc: 'Reset pending/failed work items for a plan so they re-materialize', params: 'source', handler: handlePlansRegenerate },
|
|
3138
3140
|
{ method: 'POST', path: '/api/plans/delete', desc: 'Delete a plan file and clean up work items', params: 'file', handler: handlePlansDelete },
|
|
3139
|
-
{ method: 'POST', path: '/api/plans/archive', desc: '
|
|
3141
|
+
{ method: 'POST', path: '/api/plans/archive', desc: 'Archive a plan/PRD (move to archive folder, also archives source .md plan if PRD)', params: 'file', handler: handlePlansArchiveMove },
|
|
3140
3142
|
{ method: 'POST', path: '/api/plans/unarchive', desc: 'Restore a plan/PRD from archive', params: 'file', handler: handlePlansUnarchive },
|
|
3141
3143
|
{ method: 'POST', path: '/api/plans/revise', desc: 'Request revision with feedback, dispatches agent to revise', params: 'file, feedback, requestedBy?', handler: handlePlansRevise },
|
|
3142
3144
|
{ method: 'POST', path: '/api/plans/discuss', desc: 'Generate a plan discussion session script for Claude CLI', params: 'file', handler: handlePlansDiscuss },
|
|
3143
|
-
{ method: 'POST', path: '/api/plans/archive', desc: 'Archive a plan/PRD (move to archive folder)', params: 'file', handler: handlePlansArchiveMove },
|
|
3144
|
-
{ method: 'POST', path: '/api/plans/unarchive', desc: 'Unarchive a plan/PRD (restore from archive folder)', params: 'file', handler: handlePlansUnarchive },
|
|
3145
3145
|
{ method: 'GET', path: /^\/api\/plans\/archive\/([^?]+)$/, desc: 'Read an archived plan file', handler: handlePlansArchiveRead },
|
|
3146
3146
|
{ method: 'GET', path: /^\/api\/plans\/([^?]+)$/, desc: 'Read a full plan (JSON from prd/ or markdown from plans/)', handler: handlePlansRead },
|
|
3147
3147
|
|
package/engine/shared.js
CHANGED
|
@@ -501,7 +501,27 @@ function parseSkillFrontmatter(content, filename) {
|
|
|
501
501
|
// Never touched by polling loops — only written when a PR is first linked to a PRD item.
|
|
502
502
|
|
|
503
503
|
function getPrLinks() {
|
|
504
|
-
|
|
504
|
+
// Derive from PR.prdItems (single source of truth) + legacy pr-links.json as fallback
|
|
505
|
+
const links = {};
|
|
506
|
+
try {
|
|
507
|
+
const projects = getProjects();
|
|
508
|
+
for (const project of projects) {
|
|
509
|
+
const prs = safeJson(projectPrPath(project)) || [];
|
|
510
|
+
for (const pr of prs) {
|
|
511
|
+
for (const itemId of (pr.prdItems || [])) {
|
|
512
|
+
if (!links[pr.id]) links[pr.id] = itemId;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch { /* optional */ }
|
|
517
|
+
// Merge legacy pr-links.json for items not yet in PR.prdItems
|
|
518
|
+
try {
|
|
519
|
+
const legacy = JSON.parse(require('fs').readFileSync(PR_LINKS_PATH, 'utf8'));
|
|
520
|
+
for (const [prId, itemId] of Object.entries(legacy)) {
|
|
521
|
+
if (!links[prId]) links[prId] = itemId;
|
|
522
|
+
}
|
|
523
|
+
} catch { /* optional */ }
|
|
524
|
+
return links;
|
|
505
525
|
}
|
|
506
526
|
|
|
507
527
|
function addPrLink(prId, itemId) {
|
|
@@ -512,6 +532,33 @@ function addPrLink(prId, itemId) {
|
|
|
512
532
|
safeWrite(PR_LINKS_PATH, links);
|
|
513
533
|
}
|
|
514
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Locked mutation of a project's pull-requests.json.
|
|
537
|
+
* Single source of truth for PR data including prdItems links.
|
|
538
|
+
*/
|
|
539
|
+
function mutatePrs(project, mutateFn) {
|
|
540
|
+
const prPath = projectPrPath(project);
|
|
541
|
+
return mutateJsonFileLocked(prPath, (prs) => {
|
|
542
|
+
return mutateFn(Array.isArray(prs) ? prs : []);
|
|
543
|
+
}, { defaultValue: [] });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Link a PR to a work item via PR.prdItems (single source of truth).
|
|
548
|
+
* Uses file-locked mutation to prevent race conditions.
|
|
549
|
+
*/
|
|
550
|
+
function linkPrToItem(project, prId, itemId) {
|
|
551
|
+
if (!prId || !itemId) return;
|
|
552
|
+
mutatePrs(project, (prs) => {
|
|
553
|
+
const pr = prs.find(p => p.id === prId);
|
|
554
|
+
if (pr) {
|
|
555
|
+
pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
|
|
556
|
+
if (!pr.prdItems.includes(itemId)) pr.prdItems.push(itemId);
|
|
557
|
+
}
|
|
558
|
+
return prs;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
515
562
|
module.exports = {
|
|
516
563
|
MINIONS_DIR,
|
|
517
564
|
PR_LINKS_PATH,
|
|
@@ -549,6 +596,8 @@ module.exports = {
|
|
|
549
596
|
projectPrPath,
|
|
550
597
|
getPrLinks,
|
|
551
598
|
addPrLink,
|
|
599
|
+
mutatePrs,
|
|
600
|
+
linkPrToItem,
|
|
552
601
|
nextWorkItemId,
|
|
553
602
|
getAdoOrgBase,
|
|
554
603
|
sanitizePath,
|
package/engine.js
CHANGED
|
@@ -307,7 +307,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
307
307
|
|
|
308
308
|
if (isSharedBranch) {
|
|
309
309
|
log('info', `Creating worktree for shared branch: ${worktreePath} on ${branchName}`);
|
|
310
|
-
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
310
|
+
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git fetch: ' + e.message); }
|
|
311
311
|
try {
|
|
312
312
|
runWorktreeAdd(rootDir, worktreePath, `"${branchName}"`, _worktreeGitOpts, worktreeCreateRetries);
|
|
313
313
|
} catch (eShared) {
|
|
@@ -317,6 +317,11 @@ function spawnAgent(dispatchItem, config) {
|
|
|
317
317
|
log('info', `Shared branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
318
318
|
worktreePath = existingWtPath;
|
|
319
319
|
} else { throw eShared; }
|
|
320
|
+
} else if (eShared.message?.includes('invalid reference') || eShared.message?.includes('not a valid branch')) {
|
|
321
|
+
// Branch doesn't exist yet — create it from main
|
|
322
|
+
log('info', `Shared branch ${branchName} not found — creating from ${project.mainBranch || 'main'}`);
|
|
323
|
+
const mainRef = sanitizeBranch(project.mainBranch || 'main');
|
|
324
|
+
runWorktreeAdd(rootDir, worktreePath, `-b "${branchName}" ${mainRef}`, _worktreeGitOpts, worktreeCreateRetries);
|
|
320
325
|
} else { throw eShared; }
|
|
321
326
|
}
|
|
322
327
|
} else {
|
|
@@ -1190,9 +1195,17 @@ function materializePlansAsWorkItems(config) {
|
|
|
1190
1195
|
const root = path.resolve(firstProject.localPath);
|
|
1191
1196
|
const mainBranch = firstProject.mainBranch || 'main';
|
|
1192
1197
|
const branch = sanitizeBranch(plan.feature_branch);
|
|
1193
|
-
// Create branch from main
|
|
1194
|
-
|
|
1195
|
-
|
|
1198
|
+
// Create branch from main — verify it actually succeeded
|
|
1199
|
+
try {
|
|
1200
|
+
exec(`git branch "${branch}" "${mainBranch}"`, { cwd: root, stdio: 'pipe', windowsHide: true });
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
// Branch may already exist — that's fine
|
|
1203
|
+
if (!e.message?.includes('already exists')) throw e;
|
|
1204
|
+
}
|
|
1205
|
+
// Push to remote (best-effort — may not have a remote)
|
|
1206
|
+
try {
|
|
1207
|
+
exec(`git push -u origin "${branch}"`, { cwd: root, stdio: 'pipe', windowsHide: true, timeout: 15000 });
|
|
1208
|
+
} catch { /* no remote or push failed — branch still exists locally */ }
|
|
1196
1209
|
log('info', `Shared branch pre-created: ${branch} for plan ${file}`);
|
|
1197
1210
|
} catch (err) {
|
|
1198
1211
|
log('warn', `Failed to pre-create shared branch for ${file}: ${err.message}`);
|
|
@@ -1373,6 +1386,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
1373
1386
|
}
|
|
1374
1387
|
}
|
|
1375
1388
|
|
|
1389
|
+
if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
|
|
1376
1390
|
if (item.status !== 'queued' && item.status !== 'pending') continue;
|
|
1377
1391
|
|
|
1378
1392
|
// Dependency gate: skip items whose depends_on are not yet met; propagate failure
|
|
@@ -1474,6 +1488,11 @@ function discoverFromWorkItems(config, project) {
|
|
|
1474
1488
|
const ac = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
|
|
1475
1489
|
vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
|
|
1476
1490
|
|
|
1491
|
+
// Inject PR section — conditional based on skipPr flag
|
|
1492
|
+
vars.pr_section = item.skipPr
|
|
1493
|
+
? '## Push Branch\n\n**PR creation is skipped for this work item.** Push your branch and report the branch name.\n\n```bash\ngit push -u origin {{branch_name}}\n```\n\nInclude the branch name in your completion summary.'
|
|
1494
|
+
: '## Create PR (MANDATORY)\n\n**Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.\n\n{{pr_create_instructions}}\n- sourceRefName: `refs/heads/{{branch_name}}`\n- targetRefName: `refs/heads/{{main_branch}}`\n- title: `{{commit_message}}`\n- labels: `["minions:{{agent_id}}"]`\n\nInclude in the PR description:\n- What was built and why\n- Files changed\n- How to build and test, browser URL if applicable\n- Test plan\n\n## Post self-review on PR\n\n{{pr_comment_instructions}}\n- pullRequestId: `<from PR creation>`\n- Re-read your own diff critically before posting\n- Sign: `Built by Minions ({{agent_name}} — {{agent_role}})`';
|
|
1495
|
+
|
|
1477
1496
|
// Inject checkpoint context if agent left a checkpoint.json from a prior run
|
|
1478
1497
|
vars.checkpoint_context = '';
|
|
1479
1498
|
try {
|
|
@@ -1559,15 +1578,13 @@ function discoverFromWorkItems(config, project) {
|
|
|
1559
1578
|
setCooldown(key);
|
|
1560
1579
|
}
|
|
1561
1580
|
|
|
1562
|
-
// Write back updated statuses
|
|
1563
|
-
if (newWork.length > 0) {
|
|
1581
|
+
// Write back updated statuses — needsWrite covers mutation-only ticks, newWork covers dispatches
|
|
1582
|
+
if (needsWrite || newWork.length > 0) {
|
|
1564
1583
|
const workItemsPath = projectWorkItemsPath(project);
|
|
1565
1584
|
safeWrite(workItemsPath, items);
|
|
1566
1585
|
for (const s of prdSyncQueue) syncPrdItemStatus(s.id, 'dispatched', s.sourcePlan);
|
|
1567
1586
|
}
|
|
1568
1587
|
|
|
1569
|
-
if (needsWrite) safeWrite(projectWorkItemsPath(project), items);
|
|
1570
|
-
|
|
1571
1588
|
const skipTotal = skipped.gated + skipped.noAgent;
|
|
1572
1589
|
if (skipTotal > 0) {
|
|
1573
1590
|
log('debug', `Work item discovery (${project?.name}): skipped ${skipTotal} items (${skipped.gated} gated, ${skipped.noAgent} no agent)`);
|
|
@@ -1755,8 +1772,10 @@ function discoverCentralWorkItems(config) {
|
|
|
1755
1772
|
const items = safeJson(centralPath) || [];
|
|
1756
1773
|
const projects = getProjects(config);
|
|
1757
1774
|
const newWork = [];
|
|
1775
|
+
let needsWrite = false;
|
|
1758
1776
|
|
|
1759
1777
|
for (const item of items) {
|
|
1778
|
+
if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
|
|
1760
1779
|
if (item.status !== 'queued' && item.status !== 'pending') continue;
|
|
1761
1780
|
|
|
1762
1781
|
const key = `central-work-${item.id}`;
|
|
@@ -1809,6 +1828,12 @@ function discoverCentralWorkItems(config) {
|
|
|
1809
1828
|
const fanAc = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
|
|
1810
1829
|
vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
|
|
1811
1830
|
|
|
1831
|
+
// Inject PR section — conditional based on skipPr flag
|
|
1832
|
+
const fanBranch = '{{branch_name}}';
|
|
1833
|
+
vars.pr_section = item.skipPr
|
|
1834
|
+
? '## Push Branch\n\n**PR creation is skipped for this work item.** Push your branch and report the branch name.\n\n```bash\ngit push -u origin ' + fanBranch + '\n```\n\nInclude the branch name in your completion summary.'
|
|
1835
|
+
: '## Create PR (MANDATORY)\n\n**Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.\n\n{{pr_create_instructions}}\n- sourceRefName: `refs/heads/' + fanBranch + '`\n- targetRefName: `refs/heads/{{main_branch}}`\n- title: `{{commit_message}}`\n- labels: `["minions:{{agent_id}}"]`\n\nInclude in the PR description:\n- What was built and why\n- Files changed\n- How to build and test, browser URL if applicable\n- Test plan\n\n## Post self-review on PR\n\n{{pr_comment_instructions}}\n- pullRequestId: `<from PR creation>`\n- Re-read your own diff critically before posting\n- Sign: `Built by Minions ({{agent_name}} — {{agent_role}})`';
|
|
1836
|
+
|
|
1812
1837
|
if (workType === 'ask') {
|
|
1813
1838
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1814
1839
|
vars.task_id = item.id;
|
|
@@ -1828,7 +1853,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1828
1853
|
if (!prompt) {
|
|
1829
1854
|
if (renderError) {
|
|
1830
1855
|
log('warn', `Fan-out: ${item.id} → ${agent.id}: ${renderError.message}`);
|
|
1831
|
-
if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; }
|
|
1856
|
+
if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; needsWrite = true; }
|
|
1832
1857
|
} else {
|
|
1833
1858
|
log('warn', `Fan-out: playbook '${playbookName}' failed to render for ${item.id} → ${agent.id}, skipping`);
|
|
1834
1859
|
}
|
|
@@ -1854,6 +1879,7 @@ function discoverCentralWorkItems(config) {
|
|
|
1854
1879
|
item.dispatched_to = idleAgents.map(a => a.id).join(', ');
|
|
1855
1880
|
item.scope = 'fan-out';
|
|
1856
1881
|
item.fanOutAgents = idleAgents.map(a => a.id);
|
|
1882
|
+
needsWrite = true;
|
|
1857
1883
|
setCooldown(key);
|
|
1858
1884
|
log('info', `Fan-out: ${item.id} dispatched to ${idleAgents.length} agents: ${idleAgents.map(a => a.name).join(', ')}`);
|
|
1859
1885
|
|
|
@@ -1981,10 +2007,12 @@ function discoverCentralWorkItems(config) {
|
|
|
1981
2007
|
if (renderError) {
|
|
1982
2008
|
log('warn', `Dispatch: ${item.id}: ${renderError.message}`);
|
|
1983
2009
|
item._pendingReason = 'critical_vars_missing';
|
|
2010
|
+
needsWrite = true;
|
|
1984
2011
|
} else {
|
|
1985
2012
|
log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
|
|
1986
2013
|
}
|
|
1987
2014
|
item.status = 'pending';
|
|
2015
|
+
needsWrite = true;
|
|
1988
2016
|
continue;
|
|
1989
2017
|
}
|
|
1990
2018
|
|
|
@@ -2001,11 +2029,12 @@ function discoverCentralWorkItems(config) {
|
|
|
2001
2029
|
item.status = 'dispatched';
|
|
2002
2030
|
item.dispatched_at = ts();
|
|
2003
2031
|
item.dispatched_to = agentId;
|
|
2032
|
+
needsWrite = true;
|
|
2004
2033
|
setCooldown(key);
|
|
2005
2034
|
}
|
|
2006
2035
|
}
|
|
2007
2036
|
|
|
2008
|
-
if (newWork.length > 0) safeWrite(centralPath, items);
|
|
2037
|
+
if (needsWrite || newWork.length > 0) safeWrite(centralPath, items);
|
|
2009
2038
|
return newWork;
|
|
2010
2039
|
}
|
|
2011
2040
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.156",
|
|
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"
|
package/playbooks/implement.md
CHANGED
|
@@ -50,28 +50,7 @@ git push -u origin {{branch_name}}
|
|
|
50
50
|
|
|
51
51
|
Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
**Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.
|
|
56
|
-
|
|
57
|
-
{{pr_create_instructions}}
|
|
58
|
-
- sourceRefName: `refs/heads/{{branch_name}}`
|
|
59
|
-
- targetRefName: `refs/heads/{{main_branch}}`
|
|
60
|
-
- title: `{{commit_message}}`
|
|
61
|
-
- labels: `["minions:{{agent_id}}"]`
|
|
62
|
-
|
|
63
|
-
Include in the PR description:
|
|
64
|
-
- What was built and why
|
|
65
|
-
- Files changed
|
|
66
|
-
- How to build and test, browser URL if applicable
|
|
67
|
-
- Test plan
|
|
68
|
-
|
|
69
|
-
## Post self-review on PR
|
|
70
|
-
|
|
71
|
-
{{pr_comment_instructions}}
|
|
72
|
-
- pullRequestId: `<from PR creation>`
|
|
73
|
-
- Re-read your own diff critically before posting
|
|
74
|
-
- Sign: `Built by Minions ({{agent_name}} — {{agent_role}})`
|
|
53
|
+
{{pr_section}}
|
|
75
54
|
|
|
76
55
|
## Signal Completion
|
|
77
56
|
|