@yemi33/minions 0.1.151 → 0.1.153

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,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.153 (2026-04-02)
4
+
5
+ ### Engine
6
+ - engine.js
7
+ - engine/lifecycle.js
8
+ - engine/playbook.js
9
+ - engine/queries.js
10
+
11
+ ### Dashboard
12
+ - dashboard.html
13
+ - dashboard.js
14
+ - dashboard/js/modal-qa.js
15
+ - dashboard/js/modal.js
16
+ - dashboard/js/render-inbox.js
17
+ - dashboard/js/render-kb.js
18
+ - dashboard/js/render-plans.js
19
+ - dashboard/js/render-prd.js
20
+ - dashboard/styles.css
21
+
22
+ ### Documentation
23
+ - deprecated.json
24
+
25
+ ### Other
26
+ - test/unit.test.js
27
+
28
+ ## 0.1.152 (2026-04-02)
29
+
30
+ ### Dashboard
31
+ - dashboard.js
32
+
3
33
  ## 0.1.151 (2026-04-02)
4
34
 
5
35
  ### Engine
@@ -2,7 +2,6 @@
2
2
 
3
3
  let _modalDocContext = { title: '', content: '', selection: '' };
4
4
  let _modalFilePath = null; // file path for steering (null = read-only Q&A only)
5
- let _modalOriginalPlan = null; // tracks original plan file when editing a forked version
6
5
 
7
6
  function showModalQa() {
8
7
  document.getElementById('modal-qa').style.display = '';
@@ -235,9 +234,9 @@ async function _processQaMessage(message, selection) {
235
234
  actionDiv.style.cssText = 'margin:8px 0;padding:8px 12px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:6px;display:flex;flex-wrap:wrap;align-items:center;gap:8px';
236
235
  actionDiv.innerHTML =
237
236
  '<span style="color:var(--orange);font-weight:600;font-size:12px;width:100%">Execution paused — plan was updated</span>' +
238
- '<button onclick="qaReplacePrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Re-execute with new PRD</button>' +
237
+ '<button onclick="planExecute(\'' + esc + '\', \'\', null)" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Re-execute plan</button>' +
239
238
  '<button onclick="this.closest(\'div\').innerHTML=\'<span style=color:var(--muted);font-size:11px>Paused. No work dispatched.</span>\'" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer">Keep paused</button>' +
240
- '<span style="color:var(--muted);font-size:10px;width:100%">Re-execute replaces the old PRD with a fresh one from the updated plan.</span>';
239
+ '<span style="color:var(--muted);font-size:10px;width:100%">Re-execute creates a new PRD from the updated plan.</span>';
241
240
  thread.appendChild(actionDiv);
242
241
  }
243
242
  } else {
@@ -289,45 +288,4 @@ async function _processQaMessage(message, selection) {
289
288
  }
290
289
  }
291
290
 
292
- async function qaNewPrd(planFile) {
293
- qaDisablePrdButtons();
294
- const allPlans = window._lastStatus?.plans || [];
295
- const mdPlan = allPlans.find(p => p.file === planFile);
296
- const project = mdPlan?.project || '';
297
-
298
- planExecute(planFile, project, null);
299
-
300
- const btn = document.getElementById('qa-generate-prd-btn');
301
- if (btn) btn.innerHTML = '<span style="color:var(--green);font-size:12px">New PRD dispatched — existing work continues, agent creating fresh PRD from revised plan.</span>';
302
- }
303
-
304
- async function qaReplacePrd(planFile) {
305
- qaDisablePrdButtons();
306
- const allPlans = window._lastStatus?.plans || [];
307
- const mdPlan = allPlans.find(p => p.file === planFile);
308
- const project = mdPlan?.project || '';
309
- const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project);
310
-
311
- if (existingPrd) {
312
- // Pause first to stop materialization, then clean pending items
313
- try {
314
- await fetch('/api/plans/pause', {
315
- method: 'POST', headers: { 'Content-Type': 'application/json' },
316
- body: JSON.stringify({ file: existingPrd.file })
317
- });
318
- } catch (e) { console.error('plan pause:', e.message); }
319
- try {
320
- await fetch('/api/plans/regenerate', {
321
- method: 'POST', headers: { 'Content-Type': 'application/json' },
322
- body: JSON.stringify({ source: existingPrd.file })
323
- });
324
- } catch (e) { console.error('plan regenerate:', e.message); }
325
- }
326
-
327
- planExecute(planFile, project, null);
328
-
329
- const btn = document.getElementById('qa-generate-prd-btn');
330
- if (btn) btn.innerHTML = '<span style="color:var(--orange);font-size:12px">Replacing PRD — old items paused, agent regenerating from revised plan.</span>';
331
- }
332
-
333
- window.MinionsQA = { showModalQa, modalAskAboutSelection, clearQaSelection, clearQaConversation, modalSend, qaNewPrd, qaReplacePrd };
291
+ window.MinionsQA = { showModalQa, modalAskAboutSelection, clearQaSelection, clearQaConversation, modalSend };
@@ -35,8 +35,6 @@ function closeModal() {
35
35
  // Clear edit/steer state
36
36
  _modalEditable = null;
37
37
  _modalFilePath = null;
38
- _modalOriginalPlan = null;
39
- // steer btn removed — unified send
40
38
  const body = document.getElementById('modal-body');
41
39
  body.contentEditable = 'false';
42
40
  body.style.border = '';
@@ -72,7 +72,6 @@ function openNotesModal() {
72
72
  _modalEditable = 'notes.md';
73
73
  _modalFilePath = 'notes.md'; showModalQa();
74
74
  document.getElementById('modal-edit-btn').style.display = '';
75
- // steer btn removed — unified send
76
75
  document.getElementById('modal').classList.add('open');
77
76
  }
78
77
 
@@ -169,7 +169,6 @@ async function kbOpenItem(category, file) {
169
169
  // Clear notification badge when opening this document
170
170
  const card = findCardForFile(_modalFilePath);
171
171
  if (card) clearNotifBadge(card);
172
- // steer btn removed — unified send
173
172
  document.getElementById('modal').classList.add('open');
174
173
  } catch (e) {
175
174
  console.error('Failed to load KB item:', e);
@@ -67,7 +67,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
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
69
  const allDone = implementWi.length > 0 && implementWi.every(w =>
70
- w.status === 'done' || w.status === 'in-pr' || w.status === 'implemented' || w.status === 'complete'
70
+ w.status === 'done'
71
71
  );
72
72
  const hasFailed = implementWi.some(w => w.status === 'failed');
73
73
 
@@ -304,48 +304,6 @@ function openArchivedPlansModal() {
304
304
  document.getElementById('modal').classList.add('open');
305
305
  }
306
306
 
307
- // Disable all PRD action buttons to prevent double-clicks
308
- function qaDisablePrdButtons() {
309
- const container = document.getElementById('qa-generate-prd-btn');
310
- if (container) container.querySelectorAll('button').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
311
- }
312
-
313
- // Show plan version action buttons (Run alongside / Replace / Just save)
314
- function showPlanVersionActions(thread, newFile, originalFile) {
315
- const esc = newFile.replace(/'/g, "\\'");
316
- // Look up existing PRD for the original plan's project
317
- const allPlans = window._lastStatus?.plans || [];
318
- const origPlan = allPlans.find(p => p.file === originalFile);
319
- const project = origPlan?.project || '';
320
- const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project && p.status !== 'completed');
321
-
322
- const btn = document.createElement('div');
323
- btn.id = 'qa-generate-prd-btn';
324
- 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';
325
-
326
- if (existingPrd) {
327
- btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created — existing PRD running</span>' +
328
- '<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>' +
329
- '<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>' +
330
- '<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>' +
331
- '<span style="color:var(--muted);font-size:10px;width:100%">Run alongside keeps current work going. Replace pauses it and starts fresh.</span>';
332
- } else {
333
- btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created</span>' +
334
- '<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>' +
335
- '<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>' +
336
- '<span style="color:var(--muted);font-size:10px">Execute dispatches an agent to create PRD items from this plan</span>';
337
- }
338
- // Remove any previous action buttons
339
- const old = thread.querySelector('#qa-generate-prd-btn');
340
- if (old) old.remove();
341
- thread.appendChild(btn);
342
- }
343
-
344
- function qaJustSave(el) {
345
- const container = el.closest('#qa-generate-prd-btn');
346
- if (container) container.innerHTML = '<span style="color:var(--muted);font-size:11px">Saved. No work dispatched.</span>';
347
- }
348
-
349
307
  async function planExecute(file, project, btn) {
350
308
  if (btn) { btn.textContent = 'Executing...'; btn.disabled = true; btn.style.color = 'var(--blue)'; }
351
309
  try {
@@ -736,4 +694,4 @@ async function planUnarchive(file, btn) {
736
694
  } catch (e) { resetBtn(); alert('Error: ' + e.message); }
737
695
  }
738
696
 
739
- 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 };
697
+ window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
@@ -24,7 +24,7 @@ function renderPrd(prd, prog) {
24
24
  if (existing.length <= 1) {
25
25
  // Single PRD — show status + actions in header
26
26
  const implementItems = allWi.filter(w => prdItems.some(pi => pi.id === w.id));
27
- const allDone = implementItems.length > 0 && implementItems.every(w => w.status === 'done' || w.status === 'in-pr' || w.status === 'implemented' || w.status === 'complete');
27
+ const allDone = implementItems.length > 0 && implementItems.every(w => w.status === 'done');
28
28
  const hasActive = implementItems.some(w => w.status === 'pending' || w.status === 'dispatched');
29
29
  const prdFile = existing[0]?.file || '';
30
30
  const prdStatus = existing[0]?.status || '';
@@ -52,7 +52,7 @@ function renderPrd(prd, prog) {
52
52
  for (const p of existing) {
53
53
  const items = prdItems.filter(i => i.source === p.file);
54
54
  const wiForPrd = allWi.filter(w => items.some(pi => pi.id === w.id));
55
- const allDone = wiForPrd.length > 0 && wiForPrd.every(w => w.status === 'done' || w.status === 'in-pr' || w.status === 'implemented' || w.status === 'complete');
55
+ const allDone = wiForPrd.length > 0 && wiForPrd.every(w => w.status === 'done');
56
56
  const hasActive = wiForPrd.some(w => w.status === 'pending' || w.status === 'dispatched');
57
57
  const s = allDone && !hasActive ? 'completed' : hasActive ? 'in-progress' : p.status || 'active';
58
58
  counts[s] = (counts[s] || 0) + 1;
@@ -73,7 +73,7 @@ function renderPrdProgress(prog) {
73
73
  // Compute overall progress from active (non-archived) items
74
74
  const activeItems = (prog.items || []).filter(i => !i._archived);
75
75
  if (activeItems.length > 0) {
76
- const activeDone = activeItems.filter(i => i.status === 'done' || i.status === 'implemented' || i.status === 'in-pr').length;
76
+ const activeDone = activeItems.filter(i => i.status === 'done').length;
77
77
  countEl.textContent = Math.round((activeDone / activeItems.length) * 100) + '%';
78
78
  } else {
79
79
  countEl.textContent = '—';
@@ -82,7 +82,7 @@ function renderPrdProgress(prog) {
82
82
  function renderGroupStats(items) {
83
83
  const total = items.length;
84
84
  if (total === 0) return '';
85
- const done = items.filter(i => i.status === 'done' || i.status === 'implemented' || i.status === 'in-pr').length; // in-pr counted as done for backward compat
85
+ const done = items.filter(i => i.status === 'done').length;
86
86
  const inProgress = items.filter(i => i.status === 'in-progress').length;
87
87
  const failed = items.filter(i => i.status === 'failed').length;
88
88
  const paused = items.filter(i => i.status === 'paused').length;
@@ -112,13 +112,11 @@ function renderPrdProgress(prog) {
112
112
  const statusBadge = (s) => {
113
113
  const styles = {
114
114
  'done': 'background:rgba(63,185,80,0.15);color:var(--green)',
115
- 'implemented': 'background:rgba(63,185,80,0.15);color:var(--green)', /* legacy alias */
116
- 'in-pr': 'background:rgba(63,185,80,0.15);color:var(--green)', /* backward compat: displayed as done */
117
115
  'in-progress': 'background:rgba(210,153,34,0.15);color:var(--yellow);animation:wipPulse 1.5s infinite',
118
116
  'failed': 'background:rgba(248,81,73,0.15);color:var(--red)',
119
117
  'paused': 'background:rgba(139,148,158,0.15);color:var(--muted)',
120
118
  };
121
- const labels = { 'done': 'DONE', 'implemented': 'DONE', 'in-pr': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '—' };
119
+ const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '—' };
122
120
  const style = styles[s] || 'background:var(--surface);color:var(--muted)';
123
121
  const label = labels[s] || '—';
124
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>';
@@ -197,7 +195,7 @@ function renderPrdProgress(prog) {
197
195
  const timings = prog.planTimings || {};
198
196
 
199
197
  const renderGroupHeader = (g) => {
200
- const done = g.items.filter(i => i.status === 'done' || i.status === 'complete' || i.status === 'implemented').length;
198
+ const done = g.items.filter(i => i.status === 'done').length;
201
199
  const wip = g.items.filter(i => i.status === 'in-progress' || i.status === 'dispatched').length;
202
200
  const summary = (g.summary || '').replace(/^Convert plan to PRD:\s*/i, '').slice(0, 80);
203
201
  const isAwaitingApproval = g.planStatus === 'awaiting-approval';
@@ -304,7 +302,7 @@ function renderPrdProgress(prog) {
304
302
  });
305
303
 
306
304
  const statusColor = (s) => {
307
- if (s === 'done' || s === 'implemented' || s === 'in-pr') return 'var(--green)'; // in-pr treated as done
305
+ if (s === 'done') return 'var(--green)';
308
306
  if (s === 'in-progress') return 'var(--yellow)';
309
307
  if (s === 'failed') return 'var(--red)';
310
308
  if (s === 'paused') return 'var(--muted)';
@@ -475,7 +473,7 @@ function openArchivedPrdModal() {
475
473
  // Picker: list archived plans, click to expand
476
474
  html = '<div style="margin-bottom:12px;font-size:12px;color:var(--muted)">Select an archived PRD to view:</div>';
477
475
  html += groups.map((g, i) => {
478
- const done = g.items.filter(it => it.status === 'done' || it.status === 'implemented' || it.status === 'complete').length;
476
+ const done = g.items.filter(it => it.status === 'done').length;
479
477
  const failed = g.items.filter(it => it.status === 'failed').length;
480
478
  return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(' + i + ')">' +
481
479
  '<div class="plan-card-title" style="font-size:13px">' + escHtml(g.summary || g.file) + '</div>' +
@@ -538,7 +536,7 @@ async function prdItemEdit(source, itemId) {
538
536
 
539
537
  // Build completion summary section
540
538
  let completionHtml = '';
541
- const isDone = item.status === 'done' || item.status === 'implemented' || item.status === 'complete' || item.status === 'in-pr'; // in-pr treated as done for backward compat
539
+ const isDone = item.status === 'done';
542
540
  const isFailed = item.status === 'failed';
543
541
  const isActive = item.status === 'in-progress' || item.status === 'dispatched';
544
542
 
@@ -711,7 +709,7 @@ function openArchive(i) {
711
709
  html += '<div class="archive-feature">' +
712
710
  '<span class="feat-id">' + escHtml(f.id) + '</span> ' +
713
711
  '<span class="prd-item-priority ' + pClass + '">' + escHtml(f.priority || '') + '</span>' +
714
- (f.status ? ' <span class="pr-badge ' + (f.status === 'complete' || f.status === 'done' || f.status === 'in-pr' ? 'approved' : 'draft') + '" style="font-size:9px">' + escHtml(f.status === 'in-pr' ? 'done' : f.status) + '</span>' : '') +
712
+ (f.status ? ' <span class="pr-badge ' + (f.status === 'done' ? 'approved' : 'draft') + '" style="font-size:9px">' + escHtml(f.status) + '</span>' : '') +
715
713
  '<div class="feat-name">' + escHtml(f.name) + '</div>' +
716
714
  '<div class="feat-desc">' + escHtml(f.description || '') + '</div>' +
717
715
  (f.rationale ? '<div class="feat-desc" style="margin-top:4px;color:var(--yellow)">Rationale: ' + escHtml(f.rationale) + '</div>' : '') +
@@ -181,8 +181,6 @@
181
181
  .prd-items-list { display: flex; flex-direction: column; gap: 3px; max-height: 400px; overflow-y: auto; padding: 0 8px; }
182
182
  .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
183
  .prd-item-row.st-done { border-left-color: var(--green); }
184
- .prd-item-row.st-implemented { border-left-color: var(--green); } /* legacy alias */
185
- .prd-item-row.st-in-pr { border-left-color: var(--green); } /* backward compat: treated as done */
186
184
  .prd-item-row.st-in-progress { border-left-color: var(--yellow); animation: prdWipPulse 2s infinite; }
187
185
  @keyframes prdWipPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(210,153,34,0); } 50% { box-shadow: 0 0 0 4px rgba(210,153,34,0.2); } }
188
186
  .prd-item-row.st-failed { border-left-color: var(--red); }
package/dashboard.html CHANGED
@@ -178,8 +178,6 @@
178
178
  .prd-items-list { display: flex; flex-direction: column; gap: 3px; max-height: 400px; overflow-y: auto; padding: 0 8px; }
179
179
  .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); }
180
180
  .prd-item-row.st-done { border-left-color: var(--green); }
181
- .prd-item-row.st-implemented { border-left-color: var(--green); } /* legacy alias */
182
- .prd-item-row.st-in-pr { border-left-color: var(--green); } /* backward compat: treated as done */
183
181
  .prd-item-row.st-in-progress { border-left-color: var(--yellow); animation: prdWipPulse 2s infinite; }
184
182
  @keyframes prdWipPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(210,153,34,0); } 50% { box-shadow: 0 0 0 4px rgba(210,153,34,0.2); } }
185
183
  .prd-item-row.st-failed { border-left-color: var(--red); }
@@ -1171,7 +1169,7 @@ function renderPrdProgress(prog) {
1171
1169
  // Compute progress from active (non-archived) items only
1172
1170
  const activeItems = (prog.items || []).filter(i => !i._archived);
1173
1171
  if (activeItems.length > 0) {
1174
- const activeDone = activeItems.filter(i => i.status === 'done' || i.status === 'implemented' || i.status === 'in-pr').length; // in-pr counted as done for backward compat
1172
+ const activeDone = activeItems.filter(i => i.status === 'done').length;
1175
1173
  countEl.textContent = Math.round((activeDone / activeItems.length) * 100) + '%';
1176
1174
  } else {
1177
1175
  countEl.textContent = '—';
@@ -1180,7 +1178,7 @@ function renderPrdProgress(prog) {
1180
1178
  function renderGroupStats(items) {
1181
1179
  const total = items.length;
1182
1180
  if (total === 0) return '';
1183
- const done = items.filter(i => i.status === 'done' || i.status === 'implemented' || i.status === 'in-pr').length; // in-pr counted as done for backward compat
1181
+ const done = items.filter(i => i.status === 'done').length;
1184
1182
  const inProgress = items.filter(i => i.status === 'in-progress').length;
1185
1183
  const failed = items.filter(i => i.status === 'failed').length;
1186
1184
  const paused = items.filter(i => i.status === 'paused').length;
@@ -1210,13 +1208,11 @@ function renderPrdProgress(prog) {
1210
1208
  const statusBadge = (s) => {
1211
1209
  const styles = {
1212
1210
  'done': 'background:rgba(63,185,80,0.15);color:var(--green)',
1213
- 'implemented': 'background:rgba(63,185,80,0.15);color:var(--green)', /* legacy alias */
1214
- 'in-pr': 'background:rgba(63,185,80,0.15);color:var(--green)', /* backward compat: displayed as done */
1215
1211
  'in-progress': 'background:rgba(210,153,34,0.15);color:var(--yellow);animation:wipPulse 1.5s infinite',
1216
1212
  'failed': 'background:rgba(248,81,73,0.15);color:var(--red)',
1217
1213
  'paused': 'background:rgba(139,148,158,0.15);color:var(--muted)',
1218
1214
  };
1219
- const labels = { 'done': 'DONE', 'implemented': 'DONE', 'in-pr': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '—' };
1215
+ const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '—' };
1220
1216
  const style = styles[s] || 'background:var(--surface);color:var(--muted)';
1221
1217
  const label = labels[s] || '—';
1222
1218
  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>';
@@ -1295,7 +1291,7 @@ function renderPrdProgress(prog) {
1295
1291
  const timings = prog.planTimings || {};
1296
1292
 
1297
1293
  const renderGroupHeader = (g) => {
1298
- const done = g.items.filter(i => i.status === 'done' || i.status === 'complete' || i.status === 'implemented').length;
1294
+ const done = g.items.filter(i => i.status === 'done').length;
1299
1295
  const wip = g.items.filter(i => i.status === 'in-progress' || i.status === 'dispatched').length;
1300
1296
  const summary = (g.summary || '').replace(/^Convert plan to PRD:\s*/i, '').slice(0, 80);
1301
1297
  const isAwaitingApproval = g.planStatus === 'awaiting-approval';
@@ -1394,7 +1390,7 @@ function renderPrdProgress(prog) {
1394
1390
  });
1395
1391
 
1396
1392
  const statusColor = (s) => {
1397
- if (s === 'done' || s === 'implemented' || s === 'in-pr') return 'var(--green)'; // in-pr treated as done
1393
+ if (s === 'done') return 'var(--green)';
1398
1394
  if (s === 'in-progress') return 'var(--yellow)';
1399
1395
  if (s === 'failed') return 'var(--red)';
1400
1396
  if (s === 'paused') return 'var(--muted)';
@@ -1565,7 +1561,7 @@ function openArchivedPrdModal() {
1565
1561
  // Picker: list archived plans, click to expand
1566
1562
  html = '<div style="margin-bottom:12px;font-size:12px;color:var(--muted)">Select an archived PRD to view:</div>';
1567
1563
  html += groups.map((g, i) => {
1568
- const done = g.items.filter(it => it.status === 'done' || it.status === 'implemented' || it.status === 'complete').length;
1564
+ const done = g.items.filter(it => it.status === 'done').length;
1569
1565
  const failed = g.items.filter(it => it.status === 'failed').length;
1570
1566
  return '<div class="plan-card" style="cursor:pointer;margin-bottom:8px" onclick="showArchivedPrdDetail(' + i + ')">' +
1571
1567
  '<div class="plan-card-title" style="font-size:13px">' + escHtml(g.summary || g.file) + '</div>' +
@@ -1661,7 +1657,7 @@ async function prdItemEdit(source, itemId) {
1661
1657
 
1662
1658
  // Build completion summary section
1663
1659
  let completionHtml = '';
1664
- const isDone = item.status === 'done' || item.status === 'implemented' || item.status === 'complete' || item.status === 'in-pr'; // in-pr treated as done for backward compat
1660
+ const isDone = item.status === 'done';
1665
1661
  const isFailed = item.status === 'failed';
1666
1662
  const isActive = item.status === 'in-progress' || item.status === 'dispatched';
1667
1663
 
@@ -1931,7 +1927,6 @@ function openNotesModal() {
1931
1927
  _modalEditable = 'notes.md';
1932
1928
  _modalFilePath = 'notes.md'; showModalQa();
1933
1929
  document.getElementById('modal-edit-btn').style.display = '';
1934
- // steer btn removed — unified send
1935
1930
  document.getElementById('modal').classList.add('open');
1936
1931
  }
1937
1932
 
@@ -2130,8 +2125,6 @@ function closeModal() {
2130
2125
  // Clear edit/steer state
2131
2126
  _modalEditable = null;
2132
2127
  _modalFilePath = null;
2133
- _modalOriginalPlan = null;
2134
- // steer btn removed — unified send
2135
2128
  const body = document.getElementById('modal-body');
2136
2129
  body.contentEditable = 'false';
2137
2130
  body.style.border = '';
@@ -2194,7 +2187,7 @@ function openArchive(i) {
2194
2187
  html += '<div class="archive-feature">' +
2195
2188
  '<span class="feat-id">' + escHtml(f.id) + '</span> ' +
2196
2189
  '<span class="prd-item-priority ' + pClass + '">' + escHtml(f.priority || '') + '</span>' +
2197
- (f.status ? ' <span class="pr-badge ' + (f.status === 'complete' || f.status === 'done' || f.status === 'in-pr' ? 'approved' : 'draft') + '" style="font-size:9px">' + escHtml(f.status === 'in-pr' ? 'done' : f.status) + '</span>' : '') +
2190
+ (f.status ? ' <span class="pr-badge ' + (f.status === 'done' ? 'approved' : 'draft') + '" style="font-size:9px">' + escHtml(f.status) + '</span>' : '') +
2198
2191
  '<div class="feat-name">' + escHtml(f.name) + '</div>' +
2199
2192
  '<div class="feat-desc">' + escHtml(f.description || '') + '</div>' +
2200
2193
  (f.rationale ? '<div class="feat-desc" style="margin-top:4px;color:var(--yellow)">Rationale: ' + escHtml(f.rationale) + '</div>' : '') +
@@ -3539,7 +3532,6 @@ async function cmdSubmit() {
3539
3532
 
3540
3533
  let _modalDocContext = { title: '', content: '', selection: '' };
3541
3534
  let _modalFilePath = null; // file path for steering (null = read-only Q&A only)
3542
- let _modalOriginalPlan = null; // tracks original plan file when editing a forked version
3543
3535
 
3544
3536
  // ─── Notification Badges ──────────────────────────────────────────────────────
3545
3537
  // Show a red dot on a card/button when a background response arrives
@@ -4131,91 +4123,6 @@ async function planApprove(file) {
4131
4123
  } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); }
4132
4124
  }
4133
4125
 
4134
- // Disable all PRD action buttons to prevent double-clicks
4135
- function qaDisablePrdButtons() {
4136
- const container = document.getElementById('qa-generate-prd-btn');
4137
- if (container) container.querySelectorAll('button').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
4138
- }
4139
-
4140
- // Show plan version action buttons (Run alongside / Replace / Just save)
4141
- function showPlanVersionActions(thread, newFile, originalFile) {
4142
- const esc = newFile.replace(/'/g, "\\'");
4143
- // Look up existing PRD for the original plan's project
4144
- const allPlans = window._lastStatus?.plans || [];
4145
- const origPlan = allPlans.find(p => p.file === originalFile);
4146
- const project = origPlan?.project || '';
4147
- const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project && p.status !== 'completed');
4148
-
4149
- const btn = document.createElement('div');
4150
- btn.id = 'qa-generate-prd-btn';
4151
- 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';
4152
-
4153
- if (existingPrd) {
4154
- btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created — existing PRD running</span>' +
4155
- '<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>' +
4156
- '<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>' +
4157
- '<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>' +
4158
- '<span style="color:var(--muted);font-size:10px;width:100%">Run alongside keeps current work going. Replace pauses it and starts fresh.</span>';
4159
- } else {
4160
- btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created</span>' +
4161
- '<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>' +
4162
- '<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>' +
4163
- '<span style="color:var(--muted);font-size:10px">Execute dispatches an agent to create PRD items from this plan</span>';
4164
- }
4165
- // Remove any previous action buttons
4166
- const old = thread.querySelector('#qa-generate-prd-btn');
4167
- if (old) old.remove();
4168
- thread.appendChild(btn);
4169
- }
4170
-
4171
- function qaJustSave(el) {
4172
- const container = el.closest('#qa-generate-prd-btn');
4173
- if (container) container.innerHTML = '<span style="color:var(--muted);font-size:11px">Saved. No work dispatched.</span>';
4174
- }
4175
-
4176
- // Replace existing PRD: pause old, clean pending items, regenerate from revised plan
4177
- async function qaReplacePrd(planFile) {
4178
- qaDisablePrdButtons();
4179
- const allPlans = window._lastStatus?.plans || [];
4180
- const mdPlan = allPlans.find(p => p.file === planFile);
4181
- const project = mdPlan?.project || '';
4182
- const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project);
4183
-
4184
- if (existingPrd) {
4185
- // Pause first to stop materialization, then clean pending items
4186
- try {
4187
- await fetch('/api/plans/pause', {
4188
- method: 'POST', headers: { 'Content-Type': 'application/json' },
4189
- body: JSON.stringify({ file: existingPrd.file })
4190
- });
4191
- } catch {}
4192
- try {
4193
- await fetch('/api/plans/regenerate', {
4194
- method: 'POST', headers: { 'Content-Type': 'application/json' },
4195
- body: JSON.stringify({ source: existingPrd.file })
4196
- });
4197
- } catch {}
4198
- }
4199
-
4200
- planExecute(planFile, project, null);
4201
-
4202
- const btn = document.getElementById('qa-generate-prd-btn');
4203
- if (btn) btn.innerHTML = '<span style="color:var(--orange);font-size:12px">Replacing PRD — old items paused, agent regenerating from revised plan.</span>';
4204
- }
4205
-
4206
- // New PRD: keep existing PRD running, create fresh PRD from revised plan
4207
- async function qaNewPrd(planFile) {
4208
- qaDisablePrdButtons();
4209
- const allPlans = window._lastStatus?.plans || [];
4210
- const mdPlan = allPlans.find(p => p.file === planFile);
4211
- const project = mdPlan?.project || '';
4212
-
4213
- planExecute(planFile, project, null);
4214
-
4215
- const btn = document.getElementById('qa-generate-prd-btn');
4216
- if (btn) btn.innerHTML = '<span style="color:var(--green);font-size:12px">New PRD dispatched — existing work continues, agent creating fresh PRD from revised plan.</span>';
4217
- }
4218
-
4219
4126
  async function planExecute(file, project, btn) {
4220
4127
  if (btn) { btn.textContent = 'Executing...'; btn.disabled = true; btn.style.color = 'var(--blue)'; }
4221
4128
  try {
@@ -4429,7 +4336,6 @@ async function planView(file) {
4429
4336
  // Clear notification badge when opening this document
4430
4337
  const card = findCardForFile(_modalFilePath);
4431
4338
  if (card) clearNotifBadge(card);
4432
- // steer btn removed — unified send
4433
4339
  document.getElementById('modal').classList.add('open');
4434
4340
  } catch (e) { console.error(e); }
4435
4341
  }
@@ -4568,7 +4474,6 @@ async function kbOpenItem(category, file) {
4568
4474
  // Clear notification badge when opening this document
4569
4475
  const card = findCardForFile(_modalFilePath);
4570
4476
  if (card) clearNotifBadge(card);
4571
- // steer btn removed — unified send
4572
4477
  document.getElementById('modal').classList.add('open');
4573
4478
  } catch (e) {
4574
4479
  console.error('Failed to load KB item:', e);
package/dashboard.js CHANGED
@@ -1857,7 +1857,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
1857
1857
  for (const w of items) {
1858
1858
  if (w.sourcePlan !== body.file) continue;
1859
1859
  // Keep completed items as-is, reset everything else to pending.
1860
- if (w.status === 'done' || w.status === 'implemented' || w.status === 'complete' || w.status === 'in-pr') continue;
1860
+ if (w.status === 'done') continue;
1861
1861
 
1862
1862
  if (w.status === 'dispatched') {
1863
1863
  // Kill the agent working on this item, if any.
@@ -2259,148 +2259,6 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2259
2259
  } catch (e) { return jsonReply(res, 400, { error: e.message }); }
2260
2260
  }
2261
2261
 
2262
- // POST /api/plans/revise-and-regenerate — REMOVED: plan versioning now handled by /api/doc-chat
2263
- // The "Replace old PRD" flow uses qaReplacePrd (frontend) which calls /api/plans/pause + /api/plans/regenerate + planExecute
2264
- async function handlePlansReviseAndRegenerate(req, res) {
2265
- try {
2266
- const body = await readBody(req);
2267
- if (!body.source || !body.instruction) return jsonReply(res, 400, { error: 'source and instruction required' });
2268
-
2269
- // Find the source plan .md file for this PRD
2270
- // Convention: PRD JSON references plan via plan_summary containing the work item ID,
2271
- // or the .md file has a matching name prefix
2272
- const prdPath = path.join(PRD_DIR, body.source);
2273
- if (!fs.existsSync(prdPath)) return jsonReply(res, 404, { error: 'PRD file not found' });
2274
-
2275
- // Look for corresponding .md plan file
2276
- let sourcePlanFile = null;
2277
- const planFiles = safeReadDir(PLANS_DIR).filter(f => f.endsWith('.md'));
2278
- if (body.sourcePlan) {
2279
- // Explicit source plan provided
2280
- sourcePlanFile = body.sourcePlan;
2281
- } else {
2282
- // Heuristic: find .md plan by matching prefix or by reading PRD's generated_from field
2283
- const prd = JSON.parse(safeRead(prdPath) || '{}');
2284
- if (prd.source_plan) {
2285
- sourcePlanFile = prd.source_plan;
2286
- } else {
2287
- // Match by prefix: officeagent-2026-03-15.json → plan-*officeagent* or plan-w025*.md
2288
- const prdBase = body.source.replace('.json', '');
2289
- for (const f of planFiles) {
2290
- // Check if plan file mentions the same project or was created around same time
2291
- const content = safeRead(path.join(PLANS_DIR, f)) || '';
2292
- if (content.includes(prd.project || '___nomatch___') || content.includes(prd.plan_summary?.slice(0, 40) || '___nomatch___')) {
2293
- sourcePlanFile = f;
2294
- break;
2295
- }
2296
- }
2297
- // Last resort: most recent .md plan
2298
- if (!sourcePlanFile && planFiles.length > 0) {
2299
- sourcePlanFile = planFiles.sort((a, b) => {
2300
- try { return fs.statSync(path.join(PLANS_DIR, b)).mtimeMs - fs.statSync(path.join(PLANS_DIR, a)).mtimeMs; } catch { return 0; }
2301
- })[0];
2302
- }
2303
- }
2304
- }
2305
-
2306
- if (!sourcePlanFile) {
2307
- return jsonReply(res, 404, { error: 'No source plan (.md) found for this PRD. You can edit the PRD JSON directly using "Edit Plan".' });
2308
- }
2309
-
2310
- const sourcePlanPath = path.join(PLANS_DIR, sourcePlanFile);
2311
- const planContent = safeRead(sourcePlanPath);
2312
- if (!planContent) return jsonReply(res, 404, { error: 'Source plan file not readable: ' + sourcePlanFile });
2313
-
2314
- // Step 1: Steer the source plan with the user's instruction via CC
2315
- const result = await ccDocCall({
2316
- message: body.instruction,
2317
- document: planContent,
2318
- title: sourcePlanFile,
2319
- filePath: 'plans/' + sourcePlanFile,
2320
- selection: body.selection || '',
2321
- canEdit: true,
2322
- isJson: false,
2323
- });
2324
-
2325
- if (!result.content) {
2326
- return jsonReply(res, 200, { ok: true, answer: result.answer, updated: false });
2327
- }
2328
-
2329
- // Save the revised plan
2330
- safeWrite(sourcePlanPath, result.content);
2331
-
2332
- // Step 2: Pause the old PRD so it stops materializing items
2333
- const prd = JSON.parse(safeRead(prdPath) || '{}');
2334
- prd.status = 'revision-requested';
2335
- prd.revision_feedback = body.instruction;
2336
- prd.revisionRequestedAt = new Date().toISOString();
2337
- safeWrite(prdPath, prd);
2338
-
2339
- // Step 3: Clean up pending/failed work items from old PRD
2340
- let reset = 0, kept = 0;
2341
- const wiPaths = [{ path: path.join(MINIONS_DIR, 'work-items.json'), label: 'central' }];
2342
- for (const proj of PROJECTS) {
2343
- wiPaths.push({ path: shared.projectWorkItemsPath(proj), label: proj.name });
2344
- }
2345
- const deletedItemIds = [];
2346
- for (const wiInfo of wiPaths) {
2347
- try {
2348
- const items = safeJson(wiInfo.path);
2349
- const filtered = [];
2350
- for (const w of items) {
2351
- if (w.sourcePlan === body.source) {
2352
- if (w.status === 'pending' || w.status === 'failed') {
2353
- reset++;
2354
- deletedItemIds.push(w.id);
2355
- } else {
2356
- kept++;
2357
- filtered.push(w);
2358
- }
2359
- } else {
2360
- filtered.push(w);
2361
- }
2362
- }
2363
- if (filtered.length < items.length) safeWrite(wiInfo.path, filtered);
2364
- } catch (e) { console.error('work item deletion:', e.message); }
2365
- }
2366
- for (const itemId of deletedItemIds) {
2367
- cleanDispatchEntries(d =>
2368
- d.meta?.item?.sourcePlan === body.source && d.meta?.item?.id === itemId
2369
- );
2370
- }
2371
-
2372
- // Step 4: Dispatch plan-to-prd to regenerate PRD from revised plan
2373
- const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
2374
- let centralItems = [];
2375
- try { centralItems = JSON.parse(safeRead(centralWiPath) || '[]'); } catch {}
2376
- const wiId = 'W-' + shared.uid();
2377
- centralItems.push({
2378
- id: wiId,
2379
- title: 'Regenerate PRD from revised plan: ' + sourcePlanFile,
2380
- type: 'plan-to-prd',
2381
- priority: 'high',
2382
- description: `The source plan \`${sourcePlanFile}\` has been revised. Convert it into a fresh PRD JSON.\n\nRevision instruction: ${body.instruction}\n\nRead the revised plan, generate updated PRD items (missing_features), and write to \`prd/${body.source}\`. Set status to "approved". Include \`"source_plan": "${sourcePlanFile}"\` in the JSON root.\n\nPreserve items that are already done (status "implemented" or "complete"). Reset or replace items that were pending/failed.`,
2383
- status: 'pending',
2384
- created: new Date().toISOString(),
2385
- createdBy: 'dashboard:revise-and-regenerate',
2386
- project: prd.project || '',
2387
- planFile: sourcePlanFile,
2388
- });
2389
- safeWrite(centralWiPath, centralItems);
2390
-
2391
- return jsonReply(res, 200, {
2392
- ok: true,
2393
- answer: result.answer,
2394
- updated: true,
2395
- sourcePlan: sourcePlanFile,
2396
- prdPaused: true,
2397
- reset,
2398
- kept,
2399
- workItemId: wiId,
2400
- });
2401
- } catch (e) { return jsonReply(res, 500, { error: e.message }); }
2402
- }
2403
-
2404
2262
  async function handlePlansDiscuss(req, res) {
2405
2263
  try {
2406
2264
  const body = await readBody(req);
@@ -2570,7 +2428,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
2570
2428
  let changed = false;
2571
2429
  for (const w of items) {
2572
2430
  if (w.sourcePlan !== f) continue;
2573
- if (w.status === 'done' || w.status === 'implemented' || w.status === 'complete' || w.status === 'in-pr') continue;
2431
+ if (w.status === 'done') continue;
2574
2432
  if (w.status === 'dispatched') {
2575
2433
  const activeEntry = (dispatch.active || []).find(d => d.meta?.item?.id === w.id || d.meta?.dispatchKey?.includes(w.id));
2576
2434
  if (activeEntry) {
@@ -3078,30 +2936,33 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3078
2936
  if (!config.agents) config.agents = {};
3079
2937
 
3080
2938
  if (body.engine) {
3081
- // Validate and apply engine settings
3082
2939
  const e = body.engine;
3083
2940
  const D = shared.ENGINE_DEFAULTS;
3084
- if (e.tickInterval !== undefined) config.engine.tickInterval = Math.max(10000, Number(e.tickInterval) || D.tickInterval);
3085
- if (e.maxConcurrent !== undefined) config.engine.maxConcurrent = Math.max(1, Math.min(10, Number(e.maxConcurrent) || D.maxConcurrent));
3086
- if (e.inboxConsolidateThreshold !== undefined) config.engine.inboxConsolidateThreshold = Math.max(1, Number(e.inboxConsolidateThreshold) || D.inboxConsolidateThreshold);
3087
- if (e.agentTimeout !== undefined) config.engine.agentTimeout = Math.max(60000, Number(e.agentTimeout) || D.agentTimeout);
3088
- if (e.maxTurns !== undefined) config.engine.maxTurns = Math.max(5, Math.min(500, Number(e.maxTurns) || D.maxTurns));
3089
- if (e.heartbeatTimeout !== undefined) config.engine.heartbeatTimeout = Math.max(60000, Number(e.heartbeatTimeout) || D.heartbeatTimeout);
3090
- if (e.worktreeCreateTimeout !== undefined) config.engine.worktreeCreateTimeout = Math.max(60000, Number(e.worktreeCreateTimeout) || D.worktreeCreateTimeout);
3091
- if (e.worktreeCreateRetries !== undefined) config.engine.worktreeCreateRetries = Math.max(0, Math.min(3, Number(e.worktreeCreateRetries) || D.worktreeCreateRetries));
2941
+ const numericFields = {
2942
+ tickInterval: [10000], maxConcurrent: [1, 10], inboxConsolidateThreshold: [1],
2943
+ agentTimeout: [60000], maxTurns: [5, 500], heartbeatTimeout: [60000],
2944
+ worktreeCreateTimeout: [60000], worktreeCreateRetries: [0, 3],
2945
+ idleAlertMinutes: [1], shutdownTimeout: [30000], restartGracePeriod: [60000],
2946
+ meetingRoundTimeout: [60000],
2947
+ };
2948
+ for (const [key, [min, max]] of Object.entries(numericFields)) {
2949
+ if (e[key] !== undefined) {
2950
+ let val = Number(e[key]) || D[key];
2951
+ val = Math.max(min, val);
2952
+ if (max !== undefined) val = Math.min(max, val);
2953
+ config.engine[key] = val;
2954
+ }
2955
+ }
3092
2956
  if (e.worktreeRoot !== undefined) config.engine.worktreeRoot = String(e.worktreeRoot || D.worktreeRoot);
3093
- if (e.idleAlertMinutes !== undefined) config.engine.idleAlertMinutes = Math.max(1, Number(e.idleAlertMinutes) || D.idleAlertMinutes);
3094
- if (e.shutdownTimeout !== undefined) config.engine.shutdownTimeout = Math.max(30000, Number(e.shutdownTimeout) || D.shutdownTimeout);
3095
- if (e.restartGracePeriod !== undefined) config.engine.restartGracePeriod = Math.max(60000, Number(e.restartGracePeriod) || D.restartGracePeriod);
3096
- if (e.meetingRoundTimeout !== undefined) config.engine.meetingRoundTimeout = Math.max(60000, Number(e.meetingRoundTimeout) || D.meetingRoundTimeout);
3097
- if (e.autoApprovePlans !== undefined) config.engine.autoApprovePlans = !!e.autoApprovePlans;
3098
- if (e.autoDecompose !== undefined) config.engine.autoDecompose = !!e.autoDecompose;
3099
- if (e.allowTempAgents !== undefined) config.engine.allowTempAgents = !!e.allowTempAgents;
2957
+ for (const key of ['autoApprovePlans', 'autoReview', 'autoDecompose', 'allowTempAgents']) {
2958
+ if (e[key] !== undefined) config.engine[key] = !!e[key];
2959
+ }
3100
2960
  }
3101
2961
 
3102
2962
  if (body.claude) {
3103
- if (body.claude.allowedTools !== undefined) config.claude.allowedTools = String(body.claude.allowedTools);
3104
- if (body.claude.outputFormat !== undefined) config.claude.outputFormat = String(body.claude.outputFormat);
2963
+ for (const key of ['allowedTools', 'outputFormat']) {
2964
+ if (body.claude[key] !== undefined) config.claude[key] = String(body.claude[key]);
2965
+ }
3105
2966
  }
3106
2967
 
3107
2968
  if (body.agents) {
@@ -1,83 +1 @@
1
- [
2
- {
3
- "id": "status-in-pr",
4
- "summary": "in-pr status alias — backward compat shim for done",
5
- "deprecated": "2026-03-21",
6
- "reason": "Simplified status model: agents mark items done directly, no intermediate in-pr state",
7
- "locations": [
8
- "dashboard.html: CSS classes, progress bar, status labels, graph colors (~10 locations)",
9
- "dashboard.js:1525 completedStatuses set",
10
- "engine.js:1165 PRD_MET_STATUSES set",
11
- "engine.js:1180 dependency check",
12
- "engine.js:1974 completedStatuses set",
13
- "engine/lifecycle.js:54,61 plan completion gate",
14
- "engine/queries.js:466,580,597 status ordering and counting"
15
- ],
16
- "cleanup": "Remove in-pr from all status checks, CSS, and display logic. Only keep done."
17
- },
18
- {
19
- "id": "status-implemented",
20
- "summary": "implemented status alias — legacy alias for done",
21
- "deprecated": "2026-03-21",
22
- "reason": "Canonical status is done. implemented was the original name before standardization.",
23
- "locations": [
24
- "dashboard.html: CSS, progress filters, status labels (~8 locations)",
25
- "dashboard.js:1525 completedStatuses set",
26
- "engine.js:1165,1974 status sets",
27
- "engine/lifecycle.js:694 sets implemented on post-merge"
28
- ],
29
- "cleanup": "Replace all implemented references with done. Update lifecycle.js post-merge to set done."
30
- },
31
- {
32
- "id": "status-complete",
33
- "summary": "complete status alias — another legacy alias for done",
34
- "deprecated": "2026-03-21",
35
- "reason": "Canonical status is done. complete appeared in early PRD schemas.",
36
- "locations": [
37
- "dashboard.html: progress filters, completion checks (~4 locations)",
38
- "engine.js:1165 PRD_MET_STATUSES set"
39
- ],
40
- "cleanup": "Remove complete from all status checks. Only keep done."
41
- },
42
- {
43
- "id": "dead-plan-version-actions",
44
- "summary": "showPlanVersionActions, qaJustSave, qaReplacePrd, qaNewPrd, qaDisablePrdButtons — dead code",
45
- "deprecated": "2026-03-21",
46
- "reason": "Doc-chat fork logic removed. These functions are unreachable.",
47
- "locations": [
48
- "dashboard.html:3775-3860 five function definitions (~80 lines)"
49
- ],
50
- "cleanup": "Delete the five functions entirely."
51
- },
52
- {
53
- "id": "dead-modal-original-plan",
54
- "summary": "_modalOriginalPlan variable — dead code",
55
- "deprecated": "2026-03-21",
56
- "reason": "Tracked original plan for fork edits. Fork logic removed.",
57
- "locations": [
58
- "dashboard.html:3187 declaration",
59
- "dashboard.html:2018 reset in closeModal"
60
- ],
61
- "cleanup": "Delete the variable declaration and all references."
62
- },
63
- {
64
- "id": "dead-revise-and-regenerate",
65
- "summary": "/api/plans/revise-and-regenerate endpoint — disabled behind if(false)",
66
- "deprecated": "2026-03-21",
67
- "reason": "Plan versioning handled differently now. Endpoint was explicitly disabled.",
68
- "locations": [
69
- "dashboard.js:1782-1829 (~45 lines of dead code)"
70
- ],
71
- "cleanup": "Delete the entire if(false) block."
72
- },
73
- {
74
- "id": "dead-steer-btn-comments",
75
- "summary": "// steer btn removed — unified send comments",
76
- "deprecated": "2026-03-21",
77
- "reason": "Steer button was removed long ago. Comments are noise.",
78
- "locations": [
79
- "dashboard.html: 4 occurrences"
80
- ],
81
- "cleanup": "Delete the comments."
82
- }
83
- ]
1
+ []
@@ -58,7 +58,7 @@ function checkPlanCompletion(meta, config) {
58
58
  const unmaterialized = [...planFeatureIds].filter(id => {
59
59
  if (workItemById[id]) return false;
60
60
  const prdItem = (plan.missing_features || []).find(f => f.id === id);
61
- return !(prdItem && (prdItem.status === 'done' || prdItem.status === 'in-pr'));
61
+ return !(prdItem && prdItem.status === 'done');
62
62
  });
63
63
  if (unmaterialized.length > 0) {
64
64
  log('info', `Plan ${planFile}: ${unmaterialized.length}/${planFeatureIds.size} feature(s) not yet materialized as work items: ${unmaterialized.join(', ')}`);
@@ -68,16 +68,16 @@ function checkPlanCompletion(meta, config) {
68
68
  // Check 2: every feature's work item must be done (or PRD item marked done externally)
69
69
  const notDone = [...planFeatureIds].filter(id => {
70
70
  const w = workItemById[id];
71
- if (w && (w.status === 'done' || w.status === 'in-pr')) return false; // in-pr accepted for backward compat
71
+ if (w && w.status === 'done') return false;
72
72
  const prdItem = (plan.missing_features || []).find(f => f.id === id);
73
- return !(prdItem && (prdItem.status === 'done' || prdItem.status === 'in-pr'));
73
+ return !(prdItem && prdItem.status === 'done');
74
74
  });
75
75
  if (notDone.length > 0) {
76
76
  log('info', `Plan ${planFile}: waiting for done on ${notDone.length}/${planFeatureIds.size} item(s): ${notDone.join(', ')}`);
77
77
  return;
78
78
  }
79
79
 
80
- const doneItems = planItems.filter(w => w.status === 'done' || w.status === 'in-pr');
80
+ const doneItems = planItems.filter(w => w.status === 'done');
81
81
  const failedItems = planItems.filter(w => w.status === 'failed');
82
82
 
83
83
  // 1. Mark plan as completed
@@ -740,7 +740,7 @@ async function handlePostMerge(pr, project, config, newStatus) {
740
740
  }
741
741
 
742
742
  if (mergedItemId) {
743
- // Mark PRD feature as implemented
743
+ // Mark PRD feature as done
744
744
  const prdDir = path.join(MINIONS_DIR, 'prd');
745
745
  try {
746
746
  const planFiles = fs.readdirSync(prdDir).filter(f => f.endsWith('.json'));
@@ -749,13 +749,13 @@ async function handlePostMerge(pr, project, config, newStatus) {
749
749
  const plan = safeJson(path.join(prdDir, pf));
750
750
  if (!plan?.missing_features) continue;
751
751
  const feature = plan.missing_features.find(f => f.id === mergedItemId);
752
- if (feature && feature.status !== 'implemented') {
753
- feature.status = 'implemented';
752
+ if (feature && feature.status !== 'done') {
753
+ feature.status = 'done';
754
754
  shared.safeWrite(path.join(prdDir, pf), plan);
755
755
  updated++;
756
756
  }
757
757
  }
758
- if (updated > 0) log('info', `Post-merge: marked ${mergedItemId} as implemented for ${pr.id}`);
758
+ if (updated > 0) log('info', `Post-merge: marked ${mergedItemId} as done for ${pr.id}`);
759
759
  } catch (err) { log('warn', `Post-merge PRD update: ${err.message}`); }
760
760
 
761
761
  // Mark work item as done
@@ -1011,7 +1011,8 @@ function updateMetrics(agentId, dispatchItem, result, taskUsage, prsCreatedCount
1011
1011
  if (taskUsage.numTurns > cp.maxTurns) cp.maxTurns = taskUsage.numTurns;
1012
1012
  // Check if this dispatch hit the turn limit
1013
1013
  const engineConfig = require('./queries').getConfig()?.engine || {};
1014
- const turnLimit = engineConfig.maxTurns || 100;
1014
+ // maxTurns default defined in ENGINE_DEFAULTS (shared.js) avoid hardcoded fallback
1015
+ const turnLimit = engineConfig.maxTurns || shared.ENGINE_DEFAULTS.maxTurns;
1015
1016
  if (taskUsage.numTurns >= turnLimit) cp.turnLimitHits++;
1016
1017
  }
1017
1018
 
@@ -1206,7 +1207,8 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1206
1207
  if (meta?.item?.id) {
1207
1208
  try {
1208
1209
  const engineConfig = (config.engine || {});
1209
- const turnLimit = engineConfig.maxTurns || 100;
1210
+ // maxTurns resolved from config, fallback to ENGINE_DEFAULTS (shared.js)
1211
+ const turnLimit = engineConfig.maxTurns || shared.ENGINE_DEFAULTS.maxTurns;
1210
1212
  const turnCount = taskUsage?.numTurns || 0;
1211
1213
  const hitTurnLimit = turnCount >= turnLimit;
1212
1214
  let outputLogSizeBytes = 0;
@@ -478,7 +478,7 @@ function selectPlaybook(workType, item) {
478
478
  if (workType === 'review' && !item?._pr && !item?.pr_id) {
479
479
  return 'work-item';
480
480
  }
481
- const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
481
+ const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'evaluate', 'decompose', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
482
482
  return typeSpecificPlaybooks.includes(workType) ? workType : 'work-item';
483
483
  }
484
484
 
package/engine/queries.js CHANGED
@@ -506,9 +506,7 @@ function getWorkItems(config) {
506
506
  pending: 0,
507
507
  queued: 0,
508
508
  dispatched: 1,
509
- 'in-pr': 3, // backward compat — treated as done
510
509
  done: 3,
511
- implemented: 3,
512
510
  failed: 4,
513
511
  paused: 5,
514
512
  };
@@ -627,7 +625,7 @@ function getPrdInfo(config) {
627
625
 
628
626
  const byStatus = {};
629
627
  items.forEach(item => { const s = item.status || 'missing'; byStatus[s] = byStatus[s] || []; byStatus[s].push(item); });
630
- const complete = (byStatus['done'] || []).length + (byStatus['in-pr'] || []).length; // in-pr counted as done for backward compat
628
+ const complete = (byStatus['done'] || []).length;
631
629
  const inProgress = (byStatus['in-progress'] || []).length;
632
630
  const paused = (byStatus['paused'] || []).length;
633
631
  const missing = (byStatus['missing'] || []).length;
@@ -641,7 +639,7 @@ function getPrdInfo(config) {
641
639
  const t = planTimings[wi.sourcePlan];
642
640
  if (wi.dispatched_at) { const d = new Date(wi.dispatched_at).getTime(); if (!t.firstDispatched || d < t.firstDispatched) t.firstDispatched = d; }
643
641
  if (wi.completedAt) { const c = new Date(wi.completedAt).getTime(); if (!t.lastCompleted || c > t.lastCompleted) t.lastCompleted = c; }
644
- if (wi.status !== 'done' && wi.status !== 'in-pr') t.allDone = false; // in-pr treated as done for backward compat
642
+ if (wi.status !== 'done') t.allDone = false;
645
643
  }
646
644
 
647
645
  const progress = {
package/engine.js CHANGED
@@ -743,7 +743,7 @@ function areDependenciesMet(item, config) {
743
743
  } catch (e) { log('warn', 'read project work items for deps: ' + e.message); }
744
744
  }
745
745
  // PRD item statuses that count as "done" for dep resolution
746
- const PRD_MET_STATUSES = new Set(['done', 'in-pr', 'implemented', 'complete']);
746
+ const PRD_MET_STATUSES = new Set(['done']);
747
747
 
748
748
  for (const depId of deps) {
749
749
  const depItem = allWorkItems.find(w => w.id === depId);
@@ -977,7 +977,7 @@ function materializePlansAsWorkItems(config) {
977
977
  log('info', `PRD ${file} invalidated (was awaiting-approval) — queuing regeneration from revised plan`);
978
978
 
979
979
  // Collect completed items to carry over to new PRD
980
- const completedStatuses = new Set(['done', 'in-pr', 'implemented']); // in-pr kept for backward compat
980
+ const completedStatuses = new Set(['done']);
981
981
  const completedItems = (plan.missing_features || [])
982
982
  .filter(f => completedStatuses.has(f.status))
983
983
  .map(f => ({ id: f.id, name: f.name, status: f.status }));
@@ -1057,7 +1057,7 @@ function materializePlansAsWorkItems(config) {
1057
1057
  const useCentral = !defaultProject;
1058
1058
 
1059
1059
  const statusFilter = ['missing', 'planned'];
1060
- // Also materialize in-pr/done items that never got a work item (race with PR status sync)
1060
+ // Also materialize done items that never got a work item (race with PR status sync)
1061
1061
  const allExistingWiIds = new Set();
1062
1062
  for (const p of allProjects) {
1063
1063
  for (const w of (safeJson(projectWorkItemsPath(p)) || [])) {
@@ -1070,7 +1070,7 @@ function materializePlansAsWorkItems(config) {
1070
1070
  }
1071
1071
  const items = plan.missing_features.filter(f =>
1072
1072
  statusFilter.includes(f.status) ||
1073
- ((f.status === 'in-pr' || f.status === 'done') && f.id && !allExistingWiIds.has(f.id))
1073
+ (f.status === 'done' && f.id && !allExistingWiIds.has(f.id))
1074
1074
  );
1075
1075
 
1076
1076
  // Group items by target project (per-item project field overrides plan-level project)
@@ -2203,7 +2203,7 @@ async function tickInner() {
2203
2203
  try { await ghPollPrStatus(config); } catch (err) { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
2204
2204
  // Sync PR status back to PRD items (missing → done when active PR exists)
2205
2205
  try { syncPrdFromPrs(config); } catch (err) { log('warn', `PRD sync error: ${err?.message || err}`); }
2206
- // Check if any plans can be marked completed (all features done/in-pr)
2206
+ // Check if any plans can be marked completed (all features done)
2207
2207
  try {
2208
2208
  const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
2209
2209
  for (const file of prdFiles) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.151",
3
+ "version": "0.1.153",
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"