@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 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
- function openMeetingDetail(id) {
91
- fetch('/api/meetings/' + encodeURIComponent(id))
92
- .then(r => r.json())
93
- .then(data => {
94
- if (!data.meeting) { alert('Meeting not found'); return; }
95
- const m = data.meeting;
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').innerHTML = html;
186
- document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
187
- document.getElementById('modal-body').style.whiteSpace = 'normal';
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
- // Completed/archived meetings: read-only Q&A (no file editing to avoid corrupting JSON)
196
- _modalFilePath = (m.status === 'completed' || m.status === 'archived') ? null : 'meetings/' + m.id + '.json';
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
- if (normalizedFile.endsWith('.json')) {
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">custom multi-stage workflows — e.g. audit meeting plan merge, or any sequence you define</span>
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">cron-style recurring tasks dispatched automatically</span>
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
- const worktreeRoot = path.resolve(root, config.engine?.worktreeRoot || '../worktrees');
83
- if (!fs.existsSync(worktreeRoot)) continue;
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
- const dirs = fs.readdirSync(worktreeRoot);
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 dirs) {
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.140",
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
- ```bash
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
 
@@ -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. Create a git worktree: `git worktree add ../worktrees/explore-{{task_id}} -b feat/{{task_id}}-<short-desc> {{main_branch}}`
46
- 2. Write the document (e.g., `docs/design-<topic>.md`)
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. Create or enter the worktree (this checks out the existing PR branch, not creating a new one):
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
- 4. Clean up:
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):
@@ -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 (WORKTREE — CRITICAL)
37
+ ## Git Workflow
38
38
 
39
- ```bash
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
- Do ALL work in the worktree. When done:
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
- Then return and clean up:
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. **If the task references a branch or PR**, check it out in a worktree:
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
- ## Cleanup
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.
@@ -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. **Create a worktree** for your changes:
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 -A
32
+ git add <specific files>
38
33
  git commit -m "feat({{item_id}}): <description>"
39
- git push -u origin feat/{{item_id}}-<short-desc>
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. **Clean up worktree**:
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