@yemi33/minions 0.1.154 → 0.1.155

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.154 (2026-04-02)
3
+ ## 0.1.155 (2026-04-02)
4
4
 
5
5
  ### Engine
6
6
  - engine.js
@@ -17,11 +17,15 @@
17
17
  - dashboard/js/refresh.js
18
18
  - dashboard/js/render-inbox.js
19
19
  - dashboard/js/render-kb.js
20
+ - dashboard/js/render-pipelines.js
20
21
  - dashboard/js/render-plans.js
21
22
  - dashboard/js/render-prd.js
22
23
  - dashboard/js/render-work-items.js
23
24
  - dashboard/styles.css
24
25
 
26
+ ### Playbooks
27
+ - implement.md
28
+
25
29
  ### Documentation
26
30
  - deprecated.json
27
31
 
@@ -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
- html += '<div style="font-size:10px;display:flex;gap:8px;align-items:center">' +
90
- '<span style="color:' + color + ';font-weight:600">' + r.status + '</span>' +
91
- '<span style="color:var(--muted)">' + (r.startedAt ? new Date(r.startedAt).toLocaleString() : '') + '</span>' +
92
- (r.completedAt ? '<span style="color:var(--muted)">\u2192 ' + new Date(r.completedAt).toLocaleString() + '</span>' : '') +
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
  }
@@ -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);
@@ -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) ? 'converted' : 'draft',
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: 'Move a plan/PRD to archive (preserves work items)', params: 'file', handler: handlePlansArchive },
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.js CHANGED
@@ -1373,6 +1373,7 @@ function discoverFromWorkItems(config, project) {
1373
1373
  }
1374
1374
  }
1375
1375
 
1376
+ if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
1376
1377
  if (item.status !== 'queued' && item.status !== 'pending') continue;
1377
1378
 
1378
1379
  // Dependency gate: skip items whose depends_on are not yet met; propagate failure
@@ -1474,6 +1475,11 @@ function discoverFromWorkItems(config, project) {
1474
1475
  const ac = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
1475
1476
  vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
1476
1477
 
1478
+ // Inject PR section — conditional based on skipPr flag
1479
+ vars.pr_section = item.skipPr
1480
+ ? '## 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.'
1481
+ : '## 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}})`';
1482
+
1477
1483
  // Inject checkpoint context if agent left a checkpoint.json from a prior run
1478
1484
  vars.checkpoint_context = '';
1479
1485
  try {
@@ -1559,15 +1565,13 @@ function discoverFromWorkItems(config, project) {
1559
1565
  setCooldown(key);
1560
1566
  }
1561
1567
 
1562
- // Write back updated statuses (always, since we mark items dispatched before newWork check)
1563
- if (newWork.length > 0) {
1568
+ // Write back updated statuses needsWrite covers mutation-only ticks, newWork covers dispatches
1569
+ if (needsWrite || newWork.length > 0) {
1564
1570
  const workItemsPath = projectWorkItemsPath(project);
1565
1571
  safeWrite(workItemsPath, items);
1566
1572
  for (const s of prdSyncQueue) syncPrdItemStatus(s.id, 'dispatched', s.sourcePlan);
1567
1573
  }
1568
1574
 
1569
- if (needsWrite) safeWrite(projectWorkItemsPath(project), items);
1570
-
1571
1575
  const skipTotal = skipped.gated + skipped.noAgent;
1572
1576
  if (skipTotal > 0) {
1573
1577
  log('debug', `Work item discovery (${project?.name}): skipped ${skipTotal} items (${skipped.gated} gated, ${skipped.noAgent} no agent)`);
@@ -1755,8 +1759,10 @@ function discoverCentralWorkItems(config) {
1755
1759
  const items = safeJson(centralPath) || [];
1756
1760
  const projects = getProjects(config);
1757
1761
  const newWork = [];
1762
+ let needsWrite = false;
1758
1763
 
1759
1764
  for (const item of items) {
1765
+ if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
1760
1766
  if (item.status !== 'queued' && item.status !== 'pending') continue;
1761
1767
 
1762
1768
  const key = `central-work-${item.id}`;
@@ -1809,6 +1815,12 @@ function discoverCentralWorkItems(config) {
1809
1815
  const fanAc = (Array.isArray(item.acceptanceCriteria) ? item.acceptanceCriteria : []).map(c => '- [ ] ' + c).join('\n');
1810
1816
  vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
1811
1817
 
1818
+ // Inject PR section — conditional based on skipPr flag
1819
+ const fanBranch = '{{branch_name}}';
1820
+ vars.pr_section = item.skipPr
1821
+ ? '## 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.'
1822
+ : '## 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}})`';
1823
+
1812
1824
  if (workType === 'ask') {
1813
1825
  vars.question = item.title + (item.description ? '\n\n' + item.description : '');
1814
1826
  vars.task_id = item.id;
@@ -1828,7 +1840,7 @@ function discoverCentralWorkItems(config) {
1828
1840
  if (!prompt) {
1829
1841
  if (renderError) {
1830
1842
  log('warn', `Fan-out: ${item.id} → ${agent.id}: ${renderError.message}`);
1831
- if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; }
1843
+ if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; needsWrite = true; }
1832
1844
  } else {
1833
1845
  log('warn', `Fan-out: playbook '${playbookName}' failed to render for ${item.id} → ${agent.id}, skipping`);
1834
1846
  }
@@ -1854,6 +1866,7 @@ function discoverCentralWorkItems(config) {
1854
1866
  item.dispatched_to = idleAgents.map(a => a.id).join(', ');
1855
1867
  item.scope = 'fan-out';
1856
1868
  item.fanOutAgents = idleAgents.map(a => a.id);
1869
+ needsWrite = true;
1857
1870
  setCooldown(key);
1858
1871
  log('info', `Fan-out: ${item.id} dispatched to ${idleAgents.length} agents: ${idleAgents.map(a => a.name).join(', ')}`);
1859
1872
 
@@ -1981,10 +1994,12 @@ function discoverCentralWorkItems(config) {
1981
1994
  if (renderError) {
1982
1995
  log('warn', `Dispatch: ${item.id}: ${renderError.message}`);
1983
1996
  item._pendingReason = 'critical_vars_missing';
1997
+ needsWrite = true;
1984
1998
  } else {
1985
1999
  log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
1986
2000
  }
1987
2001
  item.status = 'pending';
2002
+ needsWrite = true;
1988
2003
  continue;
1989
2004
  }
1990
2005
 
@@ -2001,11 +2016,12 @@ function discoverCentralWorkItems(config) {
2001
2016
  item.status = 'dispatched';
2002
2017
  item.dispatched_at = ts();
2003
2018
  item.dispatched_to = agentId;
2019
+ needsWrite = true;
2004
2020
  setCooldown(key);
2005
2021
  }
2006
2022
  }
2007
2023
 
2008
- if (newWork.length > 0) safeWrite(centralPath, items);
2024
+ if (needsWrite || newWork.length > 0) safeWrite(centralPath, items);
2009
2025
  return newWork;
2010
2026
  }
2011
2027
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.154",
3
+ "version": "0.1.155",
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"
@@ -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
- ## Create PR (MANDATORY)
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