@yemi33/minions 0.1.140 → 0.1.142
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 +24 -0
- package/dashboard/js/render-meetings.js +40 -11
- package/dashboard/js/render-plans.js +103 -80
- package/dashboard/pages/pipelines.html +1 -1
- package/dashboard/pages/schedule.html +1 -1
- package/dashboard.js +11 -0
- package/engine/cleanup.js +30 -6
- package/package.json +1 -1
- package/playbooks/build-and-test.md +1 -6
- package/playbooks/explore.md +3 -4
- package/playbooks/fix.md +2 -13
- package/playbooks/implement.md +4 -12
- package/playbooks/test.md +2 -13
- package/playbooks/work-item.md +4 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.142 (2026-04-01)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine/cleanup.js
|
|
7
|
+
|
|
8
|
+
### Dashboard
|
|
9
|
+
- dashboard.js
|
|
10
|
+
- dashboard/js/render-meetings.js
|
|
11
|
+
- dashboard/js/render-plans.js
|
|
12
|
+
|
|
13
|
+
### Playbooks
|
|
14
|
+
- build-and-test.md
|
|
15
|
+
- explore.md
|
|
16
|
+
- fix.md
|
|
17
|
+
- implement.md
|
|
18
|
+
- test.md
|
|
19
|
+
- work-item.md
|
|
20
|
+
|
|
21
|
+
## 0.1.141 (2026-04-01)
|
|
22
|
+
|
|
23
|
+
### Dashboard
|
|
24
|
+
- dashboard/pages/pipelines.html
|
|
25
|
+
- dashboard/pages/schedule.html
|
|
26
|
+
|
|
3
27
|
## 0.1.140 (2026-04-01)
|
|
4
28
|
|
|
5
29
|
### Dashboard
|
|
@@ -87,12 +87,15 @@ function _toggleArchivedMeetings() {
|
|
|
87
87
|
refresh();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
let _meetingPollInterval = null;
|
|
91
|
+
let _meetingPollId = null;
|
|
92
|
+
|
|
93
|
+
function _stopMeetingPoll() {
|
|
94
|
+
if (_meetingPollInterval) { clearInterval(_meetingPollInterval); _meetingPollInterval = null; }
|
|
95
|
+
_meetingPollId = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _renderMeetingDetail(m) {
|
|
96
99
|
const statusColors = { investigating: 'var(--blue)', debating: 'var(--purple,#a855f7)', concluding: 'var(--yellow)', completed: 'var(--green)' };
|
|
97
100
|
const statusLabels = { investigating: 'Round 1 — Investigating', debating: 'Round 2 — Debating', concluding: 'Round 3 — Concluding', completed: 'Completed' };
|
|
98
101
|
|
|
@@ -182,9 +185,12 @@ function openMeetingDetail(id) {
|
|
|
182
185
|
html += '</div>';
|
|
183
186
|
|
|
184
187
|
document.getElementById('modal-title').textContent = 'Meeting: ' + m.title;
|
|
185
|
-
document.getElementById('modal-body')
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
var body = document.getElementById('modal-body');
|
|
189
|
+
var scrollTop = body.scrollTop;
|
|
190
|
+
body.innerHTML = html;
|
|
191
|
+
body.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
192
|
+
body.style.whiteSpace = 'normal';
|
|
193
|
+
body.scrollTop = scrollTop;
|
|
188
194
|
|
|
189
195
|
// Wire up doc-chat Q&A panel for the meeting transcript
|
|
190
196
|
const transcript = (m.transcript || []).map(t =>
|
|
@@ -192,11 +198,33 @@ function openMeetingDetail(id) {
|
|
|
192
198
|
).join('\n\n---\n\n');
|
|
193
199
|
const meetingDoc = '# Meeting: ' + m.title + '\n\n**Agenda:** ' + m.agenda + '\n\n' + transcript;
|
|
194
200
|
_modalDocContext = { title: 'Meeting: ' + m.title, content: meetingDoc, selection: '' };
|
|
195
|
-
//
|
|
196
|
-
|
|
201
|
+
// Always set filePath so doc-chat detects this as a meeting (uses Sonnet with tools).
|
|
202
|
+
// Server-side handleDocChat prevents writes to completed meeting JSON.
|
|
203
|
+
_modalFilePath = 'meetings/' + m.id + '.json';
|
|
197
204
|
try { showModalQa(); } catch { /* expected if QA not loaded */ }
|
|
198
205
|
|
|
199
206
|
document.getElementById('modal').classList.add('open');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function openMeetingDetail(id) {
|
|
210
|
+
_stopMeetingPoll();
|
|
211
|
+
fetch('/api/meetings/' + encodeURIComponent(id))
|
|
212
|
+
.then(r => r.json())
|
|
213
|
+
.then(data => {
|
|
214
|
+
if (!data.meeting) { alert('Meeting not found'); return; }
|
|
215
|
+
_renderMeetingDetail(data.meeting);
|
|
216
|
+
|
|
217
|
+
// Live-poll while modal is open
|
|
218
|
+
_meetingPollId = id;
|
|
219
|
+
_meetingPollInterval = setInterval(function() {
|
|
220
|
+
if (!document.getElementById('modal')?.classList?.contains('open') || _meetingPollId !== id) {
|
|
221
|
+
_stopMeetingPoll(); return;
|
|
222
|
+
}
|
|
223
|
+
fetch('/api/meetings/' + encodeURIComponent(id))
|
|
224
|
+
.then(r => r.json())
|
|
225
|
+
.then(d => { if (d.meeting && _meetingPollId === id) _renderMeetingDetail(d.meeting); })
|
|
226
|
+
.catch(function() {});
|
|
227
|
+
}, 3000);
|
|
200
228
|
})
|
|
201
229
|
.catch(e => alert('Error: ' + e.message));
|
|
202
230
|
}
|
|
@@ -310,6 +338,7 @@ async function _unarchiveMeeting(id) {
|
|
|
310
338
|
}
|
|
311
339
|
|
|
312
340
|
function _viewPlanWithBack(file, meetingId) {
|
|
341
|
+
_stopMeetingPoll();
|
|
313
342
|
planView(file);
|
|
314
343
|
// After modal opens, prepend a back button to return to meeting
|
|
315
344
|
setTimeout(function() {
|
|
@@ -403,100 +403,121 @@ function planHideRevise(file) {
|
|
|
403
403
|
document.getElementById(id).style.display = 'none';
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
let _planPollInterval = null;
|
|
407
|
+
let _planPollFile = null;
|
|
408
|
+
|
|
409
|
+
function _stopPlanPoll() {
|
|
410
|
+
if (_planPollInterval) { clearInterval(_planPollInterval); _planPollInterval = null; }
|
|
411
|
+
_planPollFile = null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
415
|
+
let title = normalizedFile;
|
|
416
|
+
let text = '';
|
|
417
|
+
|
|
418
|
+
if (normalizedFile.endsWith('.json')) {
|
|
419
|
+
const plan = JSON.parse(raw);
|
|
420
|
+
title = plan.plan_summary || normalizedFile;
|
|
421
|
+
const items = (plan.missing_features || []).map((f, i) =>
|
|
422
|
+
(i + 1) + '. [' + f.id + '] ' + f.name + ' (' + (f.estimated_complexity || '?') + ', ' + (f.priority || '?') + ')' +
|
|
423
|
+
(f.depends_on?.length ? ' \u2192 depends on: ' + f.depends_on.join(', ') : '') +
|
|
424
|
+
'\n ' + (f.description || '').slice(0, 200) +
|
|
425
|
+
(f.acceptance_criteria?.length ? '\n Criteria: ' + f.acceptance_criteria.join('; ') : '')
|
|
426
|
+
).join('\n\n');
|
|
427
|
+
text = 'Project: ' + (plan.project || '?') +
|
|
428
|
+
'\nStrategy: ' + (plan.branch_strategy || 'parallel') +
|
|
429
|
+
'\nBranch: ' + (plan.feature_branch || 'per-item') +
|
|
430
|
+
'\nStatus: ' + (plan.status || 'active') +
|
|
431
|
+
'\nGenerated by: ' + (plan.generated_by || '?') + ' on ' + (plan.generated_at || '?') +
|
|
432
|
+
'\n\n--- Items (' + (plan.missing_features || []).length + ') ---\n\n' + items +
|
|
433
|
+
(plan.open_questions?.length ? '\n\n--- Open Questions ---\n\n' + plan.open_questions.map(q => '\u2022 ' + q).join('\n') : '');
|
|
434
|
+
} else {
|
|
435
|
+
text = raw;
|
|
436
|
+
const titleMatch = raw.match(/^#\s+(?:Plan:\s*)?(.+)/m);
|
|
437
|
+
if (titleMatch) title = titleMatch[1];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const vMatch = normalizedFile.match(/-v(\d+)/);
|
|
441
|
+
const versionLabel = vMatch ? ' (v' + vMatch[1] + ')' : '';
|
|
442
|
+
const isMdPlan = normalizedFile.endsWith('.md');
|
|
443
|
+
let planStatus = '';
|
|
444
|
+
try { if (normalizedFile.endsWith('.json')) planStatus = JSON.parse(raw).status || ''; } catch {}
|
|
445
|
+
const isActive = planStatus === 'approved' || planStatus === 'active';
|
|
446
|
+
const isPaused = planStatus === 'awaiting-approval' || planStatus === 'paused';
|
|
447
|
+
const wi = window._lastWorkItems || [];
|
|
448
|
+
const linkedPrdFile = isMdPlan ? (window._lastStatus?.plans || []).find(p => p.sourcePlan === normalizedFile && p.format === 'prd')?.file : null;
|
|
449
|
+
const hasActiveWork = wi.some(w =>
|
|
450
|
+
(w.status === 'pending' || w.status === 'dispatched') &&
|
|
451
|
+
(w.planFile === normalizedFile || w.sourcePlan === normalizedFile ||
|
|
452
|
+
(linkedPrdFile && w.sourcePlan === linkedPrdFile))
|
|
453
|
+
);
|
|
454
|
+
const prdCompleted = wi.some(w => w.type === 'plan-to-prd' && w.status === 'done' && w.planFile === normalizedFile);
|
|
455
|
+
const hasPrd = (window._lastStatus?.plans || []).some(p => p.sourcePlan === normalizedFile && p.format === 'prd');
|
|
456
|
+
const modalShowResume = isPaused;
|
|
457
|
+
const modalExecuteBtn = isMdPlan && !modalShowResume && !hasActiveWork && !prdCompleted && !hasPrd ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green);font-weight:600" ' +
|
|
458
|
+
'onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Execute</button>' : '';
|
|
459
|
+
const modalCompletedLabel = prdCompleted && !hasActiveWork ? '<span style="font-size:10px;color:var(--green);font-weight:600">Completed</span>' : '';
|
|
460
|
+
const modalInProgressLabel = hasActiveWork ? '<span style="font-size:10px;color:var(--blue)">In Progress</span>' : '';
|
|
461
|
+
const isModalCompleted = planStatus === 'completed';
|
|
462
|
+
const modalPauseBtn = isActive && !isMdPlan && !isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" ' +
|
|
463
|
+
'onclick="planPause(\'' + escHtml(normalizedFile) + '\',this)">Pause</button>' : '';
|
|
464
|
+
const modalResumeBtn = isPaused ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
465
|
+
'onclick="planApprove(\'' + escHtml(normalizedFile) + '\',this)">Resume</button>' : '';
|
|
466
|
+
const modalVerifyBtn = isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
467
|
+
'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\',this)">Verify</button>' : '';
|
|
468
|
+
const modalArchiveBtn = '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--muted)" ' +
|
|
469
|
+
'onclick="planArchive(\'' + escHtml(normalizedFile) + '\')">Archive</button>';
|
|
470
|
+
const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
|
|
471
|
+
const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
|
|
472
|
+
(modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') + (modalVerifyBtn || '') +
|
|
473
|
+
' ' + modalArchiveBtn +
|
|
474
|
+
' <button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
|
|
475
|
+
'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
|
|
476
|
+
'</div>';
|
|
477
|
+
|
|
478
|
+
document.getElementById('modal-title').innerHTML = escHtml(title) + (versionLabel ? ' <span style="font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue)">' + escHtml(versionLabel) + '</span>' : '') + lastModLabel + actionBtns;
|
|
479
|
+
const modalBody = document.getElementById('modal-body');
|
|
480
|
+
const scrollTop = modalBody.scrollTop;
|
|
481
|
+
if (normalizedFile.endsWith('.json')) {
|
|
482
|
+
modalBody.textContent = text;
|
|
483
|
+
modalBody.style.fontFamily = 'Consolas, monospace';
|
|
484
|
+
modalBody.style.whiteSpace = 'pre-wrap';
|
|
485
|
+
} else {
|
|
486
|
+
modalBody.innerHTML = renderMd(text);
|
|
487
|
+
}
|
|
488
|
+
modalBody.scrollTop = scrollTop;
|
|
489
|
+
|
|
490
|
+
return { title, text };
|
|
491
|
+
}
|
|
492
|
+
|
|
406
493
|
async function planView(file) {
|
|
494
|
+
_stopPlanPoll();
|
|
407
495
|
try {
|
|
408
496
|
const normalizedFile = normalizePlanFile(file);
|
|
409
497
|
const planRes = await fetch('/api/plans/' + encodeURIComponent(normalizedFile));
|
|
410
498
|
const lastMod = planRes.headers.get('Last-Modified');
|
|
411
499
|
const resolvedPath = planRes.headers.get('X-Resolved-Path');
|
|
412
500
|
const raw = await planRes.text();
|
|
413
|
-
let title = normalizedFile;
|
|
414
|
-
let text = '';
|
|
415
501
|
|
|
416
|
-
|
|
417
|
-
// PRD JSON — format nicely
|
|
418
|
-
const plan = JSON.parse(raw);
|
|
419
|
-
title = plan.plan_summary || normalizedFile;
|
|
420
|
-
const items = (plan.missing_features || []).map((f, i) =>
|
|
421
|
-
(i + 1) + '. [' + f.id + '] ' + f.name + ' (' + (f.estimated_complexity || '?') + ', ' + (f.priority || '?') + ')' +
|
|
422
|
-
(f.depends_on?.length ? ' → depends on: ' + f.depends_on.join(', ') : '') +
|
|
423
|
-
'\n ' + (f.description || '').slice(0, 200) +
|
|
424
|
-
(f.acceptance_criteria?.length ? '\n Criteria: ' + f.acceptance_criteria.join('; ') : '')
|
|
425
|
-
).join('\n\n');
|
|
426
|
-
text = 'Project: ' + (plan.project || '?') +
|
|
427
|
-
'\nStrategy: ' + (plan.branch_strategy || 'parallel') +
|
|
428
|
-
'\nBranch: ' + (plan.feature_branch || 'per-item') +
|
|
429
|
-
'\nStatus: ' + (plan.status || 'active') +
|
|
430
|
-
'\nGenerated by: ' + (plan.generated_by || '?') + ' on ' + (plan.generated_at || '?') +
|
|
431
|
-
'\n\n--- Items (' + (plan.missing_features || []).length + ') ---\n\n' + items +
|
|
432
|
-
(plan.open_questions?.length ? '\n\n--- Open Questions ---\n\n' + plan.open_questions.map(q => '• ' + q).join('\n') : '');
|
|
433
|
-
} else {
|
|
434
|
-
// Markdown plan — show as-is
|
|
435
|
-
text = raw;
|
|
436
|
-
const titleMatch = raw.match(/^#\s+(?:Plan:\s*)?(.+)/m);
|
|
437
|
-
if (titleMatch) title = titleMatch[1];
|
|
438
|
-
}
|
|
502
|
+
const { title, text } = _renderPlanModal(normalizedFile, raw, lastMod);
|
|
439
503
|
|
|
440
|
-
// Version badge for the modal title
|
|
441
|
-
const vMatch = normalizedFile.match(/-v(\d+)/);
|
|
442
|
-
const versionLabel = vMatch ? ' (v' + vMatch[1] + ')' : '';
|
|
443
|
-
|
|
444
|
-
// Determine plan type and status for action buttons
|
|
445
|
-
const isMdPlan = normalizedFile.endsWith('.md');
|
|
446
|
-
let planStatus = '';
|
|
447
|
-
try { if (normalizedFile.endsWith('.json')) planStatus = JSON.parse(raw).status || ''; } catch {}
|
|
448
|
-
const isActive = planStatus === 'approved' || planStatus === 'active';
|
|
449
|
-
const isPaused = planStatus === 'awaiting-approval' || planStatus === 'paused';
|
|
450
|
-
// Check if work is in progress for this plan
|
|
451
|
-
const wi = window._lastWorkItems || [];
|
|
452
|
-
// Find the linked PRD for this .md plan (if any)
|
|
453
|
-
const linkedPrdFile = isMdPlan ? (window._lastStatus?.plans || []).find(p => p.sourcePlan === normalizedFile && p.format === 'prd')?.file : null;
|
|
454
|
-
const hasActiveWork = wi.some(w =>
|
|
455
|
-
(w.status === 'pending' || w.status === 'dispatched') &&
|
|
456
|
-
(w.planFile === normalizedFile || w.sourcePlan === normalizedFile ||
|
|
457
|
-
(linkedPrdFile && w.sourcePlan === linkedPrdFile))
|
|
458
|
-
);
|
|
459
|
-
const prdCompleted = wi.some(w => w.type === 'plan-to-prd' && w.status === 'done' && w.planFile === normalizedFile);
|
|
460
|
-
// Check if a PRD already exists for this plan (via plans list sourcePlan linkage)
|
|
461
|
-
const hasPrd = (window._lastStatus?.plans || []).some(p => p.sourcePlan === normalizedFile && p.format === 'prd');
|
|
462
|
-
const modalShowResume = isPaused;
|
|
463
|
-
const modalExecuteBtn = isMdPlan && !modalShowResume && !hasActiveWork && !prdCompleted && !hasPrd ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green);font-weight:600" ' +
|
|
464
|
-
'onclick="planExecute(\'' + escHtml(normalizedFile) + '\',\'\',this)">Execute</button>' : '';
|
|
465
|
-
const modalCompletedLabel = prdCompleted && !hasActiveWork ? '<span style="font-size:10px;color:var(--green);font-weight:600">Completed</span>' : '';
|
|
466
|
-
const modalInProgressLabel = hasActiveWork ? '<span style="font-size:10px;color:var(--blue)">In Progress</span>' : '';
|
|
467
|
-
const isModalCompleted = planStatus === 'completed';
|
|
468
|
-
const modalPauseBtn = isActive && !isMdPlan && !isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--yellow)" ' +
|
|
469
|
-
'onclick="planPause(\'' + escHtml(normalizedFile) + '\',this)">Pause</button>' : '';
|
|
470
|
-
const modalResumeBtn = isPaused ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
471
|
-
'onclick="planApprove(\'' + escHtml(normalizedFile) + '\',this)">Resume</button>' : '';
|
|
472
|
-
const modalVerifyBtn = isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
473
|
-
'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\',this)">Verify</button>' : '';
|
|
474
|
-
|
|
475
|
-
const modalArchiveBtn = '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--muted)" ' +
|
|
476
|
-
'onclick="planArchive(\'' + escHtml(normalizedFile) + '\')">Archive</button>';
|
|
477
|
-
const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
|
|
478
|
-
const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
|
|
479
|
-
(modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') + (modalVerifyBtn || '') +
|
|
480
|
-
' ' + modalArchiveBtn +
|
|
481
|
-
' <button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
|
|
482
|
-
'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
|
|
483
|
-
'</div>';
|
|
484
|
-
document.getElementById('modal-title').innerHTML = escHtml(title) + (versionLabel ? ' <span style="font-size:11px;font-weight:700;padding:1px 6px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue)">' + escHtml(versionLabel) + '</span>' : '') + lastModLabel + actionBtns;
|
|
485
|
-
const modalBody = document.getElementById('modal-body');
|
|
486
|
-
if (normalizedFile.endsWith('.json')) {
|
|
487
|
-
modalBody.textContent = text;
|
|
488
|
-
modalBody.style.fontFamily = 'Consolas, monospace';
|
|
489
|
-
modalBody.style.whiteSpace = 'pre-wrap';
|
|
490
|
-
} else {
|
|
491
|
-
modalBody.innerHTML = renderMd(text);
|
|
492
|
-
}
|
|
493
504
|
_modalDocContext = { title, content: text, selection: '' };
|
|
494
505
|
_modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
|
|
495
|
-
// Clear notification badge when opening this document
|
|
496
506
|
const card = findCardForFile(_modalFilePath);
|
|
497
507
|
if (card) clearNotifBadge(card);
|
|
498
|
-
// steer btn removed — unified send
|
|
499
508
|
document.getElementById('modal').classList.add('open');
|
|
509
|
+
|
|
510
|
+
// Live-poll while modal is open
|
|
511
|
+
_planPollFile = normalizedFile;
|
|
512
|
+
_planPollInterval = setInterval(function() {
|
|
513
|
+
if (!document.getElementById('modal')?.classList?.contains('open') || _planPollFile !== normalizedFile) {
|
|
514
|
+
_stopPlanPoll(); return;
|
|
515
|
+
}
|
|
516
|
+
fetch('/api/plans/' + encodeURIComponent(normalizedFile))
|
|
517
|
+
.then(function(r) { return r.text().then(function(raw) { return { raw: raw, lastMod: r.headers.get('Last-Modified') }; }); })
|
|
518
|
+
.then(function(d) { if (_planPollFile === normalizedFile) _renderPlanModal(normalizedFile, d.raw, d.lastMod); })
|
|
519
|
+
.catch(function() {});
|
|
520
|
+
}, 3000);
|
|
500
521
|
} catch (e) { console.error(e); }
|
|
501
522
|
}
|
|
502
523
|
|
|
@@ -517,6 +538,7 @@ async function planApprove(file, btn) {
|
|
|
517
538
|
}
|
|
518
539
|
|
|
519
540
|
async function planDelete(file) {
|
|
541
|
+
_stopPlanPoll();
|
|
520
542
|
if (!confirm('Delete plan "' + file + '"? This cannot be undone.')) return;
|
|
521
543
|
try {
|
|
522
544
|
const res = await fetch('/api/plans/delete', {
|
|
@@ -536,6 +558,7 @@ async function planDelete(file) {
|
|
|
536
558
|
}
|
|
537
559
|
|
|
538
560
|
async function planArchive(file, btn) {
|
|
561
|
+
_stopPlanPoll();
|
|
539
562
|
if (btn) { btn.dataset.origText = btn.textContent; btn.textContent = 'Archiving...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
540
563
|
function resetBtn() { if (btn) { btn.textContent = btn.dataset.origText || 'Archive'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
541
564
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section>
|
|
2
2
|
<h2>Pipelines <span class="count" id="pipelines-count">0</span>
|
|
3
3
|
<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openCreatePipelineModal()">+ New Pipeline</button>
|
|
4
|
-
<span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">
|
|
4
|
+
<span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">multi-stage workflows with dependencies — chain meetings, plans, tasks, merges in any order, on a schedule or manual</span>
|
|
5
5
|
</h2>
|
|
6
6
|
<div id="pipelines-content"><p class="empty">No pipelines yet. Create one to chain stages like audit → meeting → plan → merge.</p></div>
|
|
7
7
|
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section id="scheduled-section">
|
|
2
2
|
<h2>Scheduled Tasks <span class="count" id="scheduled-count">0</span>
|
|
3
3
|
<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openCreateScheduleModal()">+ New</button>
|
|
4
|
-
<span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">
|
|
4
|
+
<span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">single recurring tasks on a cron — for multi-step workflows, use Pipelines</span>
|
|
5
5
|
</h2>
|
|
6
6
|
<div id="scheduled-content"><p class="empty">No scheduled tasks. Add one to automate recurring work.</p></div>
|
|
7
7
|
</section>
|
package/dashboard.js
CHANGED
|
@@ -644,6 +644,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
644
644
|
});
|
|
645
645
|
|
|
646
646
|
if (result.code !== 0 || !result.text) {
|
|
647
|
+
console.error(`[doc-chat] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, model=${isRich ? 'sonnet' : 'haiku'}, stderr=${(result.stderr || '').slice(0, 200)}`);
|
|
647
648
|
return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
|
|
648
649
|
}
|
|
649
650
|
|
|
@@ -2525,6 +2526,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2525
2526
|
}
|
|
2526
2527
|
}
|
|
2527
2528
|
if (canEdit && fullPath) {
|
|
2529
|
+
// Block writes to completed/archived meeting JSON files
|
|
2530
|
+
if (body.filePath && /^meetings\//.test(body.filePath) && isJson) {
|
|
2531
|
+
try {
|
|
2532
|
+
const mtg = safeJson(fullPath);
|
|
2533
|
+
if (mtg && (mtg.status === 'completed' || mtg.status === 'archived')) {
|
|
2534
|
+
return jsonReply(res, 200, { ok: true, answer, edited: false, actions });
|
|
2535
|
+
}
|
|
2536
|
+
} catch { /* proceed with write if can't read */ }
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2528
2539
|
safeWrite(fullPath, content);
|
|
2529
2540
|
|
|
2530
2541
|
// If editing a plan .md that has an active PRD, auto-pause execution
|
package/engine/cleanup.js
CHANGED
|
@@ -79,8 +79,14 @@ function runCleanup(config, verbose = false) {
|
|
|
79
79
|
const root = project.localPath ? path.resolve(project.localPath) : null;
|
|
80
80
|
if (!root || !fs.existsSync(root)) continue;
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// Scan all potential worktree locations: configured root + common project-local dirs
|
|
83
|
+
const worktreeRoots = new Set();
|
|
84
|
+
const configuredRoot = path.resolve(root, config.engine?.worktreeRoot || '../worktrees');
|
|
85
|
+
if (fs.existsSync(configuredRoot)) worktreeRoots.add(configuredRoot);
|
|
86
|
+
const localDirs = ['worktrees', '.claude/worktrees'].map(d => path.join(root, d));
|
|
87
|
+
for (const d of localDirs) { if (fs.existsSync(d)) worktreeRoots.add(d); }
|
|
88
|
+
|
|
89
|
+
for (const worktreeRoot of worktreeRoots) {
|
|
84
90
|
|
|
85
91
|
// Get PRs for this project
|
|
86
92
|
const prs = safeJson(projectPrPath(project)) || [];
|
|
@@ -94,13 +100,30 @@ function runCleanup(config, verbose = false) {
|
|
|
94
100
|
// List worktrees — collect info for age-based + cap-based cleanup
|
|
95
101
|
const MAX_WORKTREES = 10;
|
|
96
102
|
try {
|
|
97
|
-
|
|
103
|
+
// Collect all worktree directories (including nested ones like minions-work/P-xxx)
|
|
104
|
+
const allDirs = [];
|
|
105
|
+
const topDirs = fs.readdirSync(worktreeRoot);
|
|
106
|
+
for (const dir of topDirs) {
|
|
107
|
+
const dirPath = path.join(worktreeRoot, dir);
|
|
108
|
+
try { if (!fs.statSync(dirPath).isDirectory()) continue; } catch { continue; }
|
|
109
|
+
// Check if this is a git worktree (has .git file) or a parent directory
|
|
110
|
+
if (fs.existsSync(path.join(dirPath, '.git'))) {
|
|
111
|
+
allDirs.push({ dir, wtPath: dirPath });
|
|
112
|
+
} else {
|
|
113
|
+
// Scan subdirectories for worktrees
|
|
114
|
+
try {
|
|
115
|
+
for (const sub of fs.readdirSync(dirPath)) {
|
|
116
|
+
const subPath = path.join(dirPath, sub);
|
|
117
|
+
try { if (fs.statSync(subPath).isDirectory()) allDirs.push({ dir: dir + '/' + sub, wtPath: subPath }); } catch { /* skip */ }
|
|
118
|
+
}
|
|
119
|
+
} catch { /* skip */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
const wtEntries = []; // { dir, wtPath, mtime, shouldClean, isProtected }
|
|
99
124
|
const dispatch = getDispatch();
|
|
100
125
|
|
|
101
|
-
for (const dir of
|
|
102
|
-
const wtPath = path.join(worktreeRoot, dir);
|
|
103
|
-
try { if (!fs.statSync(wtPath).isDirectory()) continue; } catch { continue; }
|
|
126
|
+
for (const { dir, wtPath } of allDirs) {
|
|
104
127
|
|
|
105
128
|
let shouldClean = false;
|
|
106
129
|
let isProtected = false;
|
|
@@ -189,6 +212,7 @@ function runCleanup(config, verbose = false) {
|
|
|
189
212
|
}
|
|
190
213
|
}
|
|
191
214
|
} catch (e) { log('warn', 'cleanup worktrees: ' + e.message); }
|
|
215
|
+
} // end worktreeRoots loop
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
// 4. Kill zombie claude processes not tracked by the engine
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.142",
|
|
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"
|
|
@@ -19,12 +19,7 @@ Your job is to **check out the branch, build it, run tests, and if it's a webapp
|
|
|
19
19
|
|
|
20
20
|
### 1. Set up a worktree for the PR branch
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
cd {{project_path}}
|
|
24
|
-
git fetch origin {{pr_branch}}
|
|
25
|
-
git worktree add ../worktrees/bt-{{pr_number}} origin/{{pr_branch}}
|
|
26
|
-
cd ../worktrees/bt-{{pr_number}}
|
|
27
|
-
```
|
|
22
|
+
You are already in the correct working directory on branch `{{pr_branch}}`. Do NOT create additional worktrees.
|
|
28
23
|
|
|
29
24
|
### 2. Install dependencies
|
|
30
25
|
|
package/playbooks/explore.md
CHANGED
|
@@ -42,12 +42,11 @@ Write your findings to `{{team_root}}/notes/inbox/{{agent_id}}-explore-{{task_id
|
|
|
42
42
|
|
|
43
43
|
### 5. Create Deliverable (if the task asks for one)
|
|
44
44
|
If the task asks you to write a design doc, architecture doc, or any durable artifact:
|
|
45
|
-
1.
|
|
46
|
-
2.
|
|
47
|
-
3. Commit, push, and create a PR:
|
|
45
|
+
1. Write the document in the current working directory (e.g., `docs/design-<topic>.md`)
|
|
46
|
+
2. Commit, push, and create a PR:
|
|
48
47
|
{{pr_create_instructions}}
|
|
49
|
-
4. Clean up worktree when done
|
|
50
48
|
|
|
49
|
+
Do NOT create additional worktrees — the engine handles worktree management.
|
|
51
50
|
If the task is purely exploratory (no deliverable requested), skip this step.
|
|
52
51
|
|
|
53
52
|
### 6. Status
|
package/playbooks/fix.md
CHANGED
|
@@ -17,14 +17,7 @@ Branch: `{{pr_branch}}`
|
|
|
17
17
|
|
|
18
18
|
## How to Fix
|
|
19
19
|
|
|
20
|
-
1.
|
|
21
|
-
```bash
|
|
22
|
-
cd {{team_root}}
|
|
23
|
-
git fetch origin {{pr_branch}}
|
|
24
|
-
git worktree add ../worktrees/{{pr_branch}} {{pr_branch}} 2>/dev/null || true
|
|
25
|
-
cd ../worktrees/{{pr_branch}}
|
|
26
|
-
git pull origin {{pr_branch}}
|
|
27
|
-
```
|
|
20
|
+
1. You are already in the correct worktree on branch `{{pr_branch}}`. Do NOT create additional worktrees.
|
|
28
21
|
|
|
29
22
|
2. Fix each issue listed above
|
|
30
23
|
|
|
@@ -35,11 +28,7 @@ Branch: `{{pr_branch}}`
|
|
|
35
28
|
git push
|
|
36
29
|
```
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
cd {{team_root}}
|
|
41
|
-
git worktree remove ../worktrees/{{pr_branch}} --force
|
|
42
|
-
```
|
|
31
|
+
Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
43
32
|
|
|
44
33
|
## Handling Merge Conflicts
|
|
45
34
|
If you encounter merge conflicts (e.g., during `git pull` or when the PR shows conflicts):
|
package/playbooks/implement.md
CHANGED
|
@@ -34,15 +34,11 @@ If this feature spans multiple projects, you may need to:
|
|
|
34
34
|
2. Follow existing patterns exactly — check `agents/create-agent/` or the closest comparable agent
|
|
35
35
|
3. Follow the project's logging and coding conventions (check CLAUDE.md)
|
|
36
36
|
|
|
37
|
-
## Git Workflow
|
|
37
|
+
## Git Workflow
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
cd {{team_root}}
|
|
41
|
-
git worktree add ../worktrees/{{branch_name}} -b {{branch_name}} {{main_branch}}
|
|
42
|
-
cd ../worktrees/{{branch_name}}
|
|
43
|
-
```
|
|
39
|
+
You are already running in a git worktree on branch `{{branch_name}}`. Do NOT create additional worktrees — the engine pre-created one for you.
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
When done:
|
|
46
42
|
|
|
47
43
|
```bash
|
|
48
44
|
git add <specific files>
|
|
@@ -50,11 +46,7 @@ git commit -m "{{commit_message}}"
|
|
|
50
46
|
git push -u origin {{branch_name}}
|
|
51
47
|
```
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
cd {{team_root}}
|
|
56
|
-
git worktree remove ../worktrees/{{branch_name}} --force
|
|
57
|
-
```
|
|
49
|
+
Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
58
50
|
|
|
59
51
|
## Create PR (MANDATORY)
|
|
60
52
|
|
package/playbooks/test.md
CHANGED
|
@@ -20,12 +20,7 @@ Team root: {{team_root}}
|
|
|
20
20
|
This is a **test/build/run task**. Your goal is to build, run, test, or verify something — NOT to create new features or PRs.
|
|
21
21
|
|
|
22
22
|
1. **Navigate** to the correct project directory
|
|
23
|
-
2.
|
|
24
|
-
```bash
|
|
25
|
-
cd {{project_path}}
|
|
26
|
-
git worktree add ../worktrees/test-{{item_id}} <branch-name>
|
|
27
|
-
cd ../worktrees/test-{{item_id}}
|
|
28
|
-
```
|
|
23
|
+
2. You are already in the correct working directory. If you need a specific branch, use `git checkout` — do NOT create additional worktrees.
|
|
29
24
|
3. **Build** the project — follow the repo's build instructions (check CLAUDE.md, package.json, README)
|
|
30
25
|
4. **Run** if the task asks for it (e.g., `yarn start`, `yarn dev`, docker-compose, etc.)
|
|
31
26
|
5. **Test** if the task asks for it (e.g., `yarn test`, `pytest`, etc.)
|
|
@@ -66,10 +61,4 @@ Include:
|
|
|
66
61
|
|
|
67
62
|
**Note:** Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
If you created a worktree, clean it up when done:
|
|
72
|
-
```bash
|
|
73
|
-
cd {{project_path}}
|
|
74
|
-
git worktree remove ../worktrees/test-{{item_id}} --force
|
|
75
|
-
```
|
|
64
|
+
Do NOT remove worktrees — the engine handles cleanup automatically.
|
package/playbooks/work-item.md
CHANGED
|
@@ -24,19 +24,14 @@ Keep branch names lowercase, use hyphens, max 60 chars.
|
|
|
24
24
|
|
|
25
25
|
1. **Understand the task** — read the description carefully, explore relevant code
|
|
26
26
|
2. **Navigate** to the correct project directory: `{{project_path}}`
|
|
27
|
-
3.
|
|
28
|
-
```bash
|
|
29
|
-
cd {{project_path}}
|
|
30
|
-
git worktree add ../worktrees/feat-{{item_id}} -b feat/{{item_id}}-<short-desc> {{main_branch}}
|
|
31
|
-
cd ../worktrees/feat-{{item_id}}
|
|
32
|
-
```
|
|
27
|
+
3. You are already in a worktree on branch `{{branch_name}}`. Do NOT create additional worktrees.
|
|
33
28
|
4. **Implement** the changes
|
|
34
29
|
5. **Build and verify** — ensure the build passes. If it fails, fix and retry (up to 3 times)
|
|
35
30
|
6. **Commit and push**:
|
|
36
31
|
```bash
|
|
37
|
-
git add
|
|
32
|
+
git add <specific files>
|
|
38
33
|
git commit -m "feat({{item_id}}): <description>"
|
|
39
|
-
git push -u origin
|
|
34
|
+
git push -u origin {{branch_name}}
|
|
40
35
|
```
|
|
41
36
|
7. **Create a PR:**
|
|
42
37
|
{{pr_create_instructions}}
|
|
@@ -49,11 +44,7 @@ Keep branch names lowercase, use hyphens, max 60 chars.
|
|
|
49
44
|
```json
|
|
50
45
|
{ "id": "PR-<number>", "title": "...", "agent": "{{agent_name}}", "branch": "...", "reviewStatus": "pending", "status": "active", "created": "<date>", "url": "<pr-url>", "prdItems": ["{{item_id}}"] }
|
|
51
46
|
```
|
|
52
|
-
10.
|
|
53
|
-
```bash
|
|
54
|
-
cd {{project_path}}
|
|
55
|
-
git worktree remove ../worktrees/feat-{{item_id}} --force
|
|
56
|
-
```
|
|
47
|
+
10. Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
57
48
|
|
|
58
49
|
## After Completion
|
|
59
50
|
|