@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 +30 -0
- package/dashboard/js/modal-qa.js +3 -45
- package/dashboard/js/modal.js +0 -2
- package/dashboard/js/render-inbox.js +0 -1
- package/dashboard/js/render-kb.js +0 -1
- package/dashboard/js/render-plans.js +2 -44
- package/dashboard/js/render-prd.js +10 -12
- package/dashboard/styles.css +0 -2
- package/dashboard.html +8 -103
- package/dashboard.js +23 -162
- package/docs/deprecated.json +1 -83
- package/engine/lifecycle.js +12 -10
- package/engine/playbook.js +1 -1
- package/engine/queries.js +2 -4
- package/engine.js +5 -5
- package/package.json +1 -1
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
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -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="
|
|
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
|
|
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
|
-
|
|
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 };
|
package/dashboard/js/modal.js
CHANGED
|
@@ -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'
|
|
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,
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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', '
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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 === '
|
|
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>' : '') +
|
package/dashboard/styles.css
CHANGED
|
@@ -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'
|
|
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'
|
|
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', '
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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 === '
|
|
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'
|
|
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'
|
|
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
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
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
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
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
|
-
|
|
3104
|
-
|
|
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) {
|
package/docs/deprecated.json
CHANGED
|
@@ -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
|
+
[]
|
package/engine/lifecycle.js
CHANGED
|
@@ -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 &&
|
|
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 &&
|
|
71
|
+
if (w && w.status === 'done') return false;
|
|
72
72
|
const prdItem = (plan.missing_features || []).find(f => f.id === id);
|
|
73
|
-
return !(prdItem &&
|
|
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'
|
|
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
|
|
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 !== '
|
|
753
|
-
feature.status = '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/engine/playbook.js
CHANGED
|
@@ -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
|
|
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'
|
|
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'
|
|
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'
|
|
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
|
|
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
|
-
(
|
|
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
|
|
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.
|
|
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"
|