@yemi33/minions 0.1.87 → 0.1.88

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.88 (2026-04-01)
4
+
5
+ ### Dashboard
6
+ - dashboard.js
7
+ - dashboard/js/render-plans.js
8
+ - dashboard/js/render-prd.js
9
+
3
10
  ## 0.1.87 (2026-04-01)
4
11
 
5
12
  ### Engine
@@ -461,9 +461,16 @@ async function planView(file) {
461
461
  'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
462
462
  '</div>';
463
463
  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;
464
- document.getElementById('modal-body').textContent = text;
465
- document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
466
- document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
464
+ const modalBody = document.getElementById('modal-body');
465
+ if (normalizedFile.endsWith('.json')) {
466
+ modalBody.textContent = text;
467
+ modalBody.style.fontFamily = 'Consolas, monospace';
468
+ modalBody.style.whiteSpace = 'pre-wrap';
469
+ } else {
470
+ modalBody.innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(text) + '</div>';
471
+ modalBody.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
472
+ modalBody.style.whiteSpace = 'normal';
473
+ }
467
474
  _modalDocContext = { title, content: text, selection: '' };
468
475
  _modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
469
476
  // Clear notification badge when opening this document
@@ -509,6 +516,25 @@ async function planDelete(file) {
509
516
  } catch (e) { alert('Error: ' + e.message); }
510
517
  }
511
518
 
519
+ async function planArchive(file) {
520
+ if (!confirm('Archive PRD "' + file + '"? Work items will be preserved.')) return;
521
+ try {
522
+ const res = await fetch('/api/plans/archive', {
523
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
524
+ body: JSON.stringify({ file })
525
+ });
526
+ if (res.ok) {
527
+ closeModal();
528
+ showToast('cmd-toast', 'PRD archived', true);
529
+ refreshPlans();
530
+ refresh();
531
+ } else {
532
+ const d = await res.json();
533
+ alert('Archive failed: ' + (d.error || 'unknown'));
534
+ }
535
+ } catch (e) { alert('Error: ' + e.message); }
536
+ }
537
+
512
538
  async function planPause(file, btn) {
513
539
  if (btn) { btn.dataset.origText = btn.textContent; btn.textContent = 'Pausing...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
514
540
  try {
@@ -578,9 +604,15 @@ async function planOpenInDocChat(file) {
578
604
  try { title = JSON.parse(raw).plan_summary || file; } catch {}
579
605
  }
580
606
  document.getElementById('modal-title').textContent = 'Edit: ' + title;
581
- document.getElementById('modal-body').textContent = text;
582
- document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
583
- document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
607
+ if (normalizedFile.endsWith('.json')) {
608
+ document.getElementById('modal-body').textContent = text;
609
+ document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
610
+ document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
611
+ } else {
612
+ document.getElementById('modal-body').innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(text) + '</div>';
613
+ document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
614
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
615
+ }
584
616
  _modalDocContext = { title: title, content: text, selection: '' };
585
617
  _modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
586
618
  const card = findCardForFile(_modalFilePath);
@@ -612,9 +644,9 @@ async function openVerifyGuide(file) {
612
644
  const content = await fetch('/api/plans/' + encodeURIComponent(normalizedFile)).then(r => r.text());
613
645
  document.getElementById('modal-title').innerHTML = 'Manual Testing Guide' +
614
646
  ' <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px;vertical-align:middle" onclick="openArchivedPrdModal()">Back</button>';
615
- document.getElementById('modal-body').textContent = content;
616
- document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
617
- document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
647
+ document.getElementById('modal-body').innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(content) + '</div>';
648
+ document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
649
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
618
650
  _modalDocContext = { title: 'Manual Testing Guide', content, selection: '' };
619
651
  _modalFilePath = 'prd/' + normalizedFile; showModalQa();
620
652
  const card = findCardForFile(_modalFilePath);
@@ -642,4 +674,4 @@ async function triggerVerify(file, btn) {
642
674
  } catch (e) { if (btn) { btn.textContent = btn.dataset.origText || 'Verify'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Error: ' + e.message); }
643
675
  }
644
676
 
645
- window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, qaDisablePrdButtons, showPlanVersionActions, qaJustSave, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
677
+ window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, qaDisablePrdButtons, showPlanVersionActions, qaJustSave, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
@@ -36,7 +36,7 @@ function renderPrd(prd, prog) {
36
36
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Approve</button>';
37
37
  } else if (effectiveStatus === 'completed') {
38
38
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="triggerVerify(\'' + escHtml(prdFile) + '\',this)">Verify</button>' +
39
- ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--muted);border-color:var(--border);margin-left:4px" onclick="planDelete(\'' + escHtml(prdFile) + '\')">Archive</button>';
39
+ ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--muted);border-color:var(--border);margin-left:4px" onclick="planArchive(\'' + escHtml(prdFile) + '\')">Archive</button>';
40
40
  } else if (effectiveStatus === 'in-progress') {
41
41
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button>';
42
42
  } else if (effectiveStatus === 'paused') {
@@ -238,7 +238,7 @@ function renderPrdProgress(prog) {
238
238
  ? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
239
239
  : '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\',this)" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
240
240
  const archiveBtn = isCompleted
241
- ? '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--muted);cursor:pointer;font-size:9px;padding:1px 6px;background:var(--surface);border:1px solid var(--border);border-radius:3px">Archive</span>'
241
+ ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\')" style="color:var(--muted);cursor:pointer;font-size:9px;padding:1px 6px;background:var(--surface);border:1px solid var(--border);border-radius:3px">Archive</span>'
242
242
  : '';
243
243
  const deleteBtn = '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--red);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);border-radius:3px">Delete</span>';
244
244
  const sourcePlanLink = g.sourcePlan
@@ -250,6 +250,7 @@ function renderPrdProgress(prog) {
250
250
  '<span style="color:var(--text)">' + escHtml(summary || g.file) + '</span>' +
251
251
  pausedLabel +
252
252
  staleLabel +
253
+ '<span style="font-weight:700;font-size:11px;color:' + (done === g.items.length && g.items.length > 0 ? 'var(--green)' : 'var(--text)') + '">' + (g.items.length > 0 ? Math.round((done / g.items.length) * 100) : 0) + '%</span>' +
253
254
  '<span style="color:var(--muted);font-weight:400;font-size:10px">' + g.items.length + ' items' +
254
255
  (done ? ' · ' + done + ' done' : '') + (wip ? ' · ' + wip + ' active' : '') +
255
256
  '</span>' +
package/dashboard.js CHANGED
@@ -2065,6 +2065,37 @@ If nothing to do, return: { "duplicates": [], "reclassify": [], "remove": [] }`;
2065
2065
  } catch (e) { return jsonReply(res, 400, { error: e.message }); }
2066
2066
  }
2067
2067
 
2068
+ async function handlePlansArchive(req, res) {
2069
+ try {
2070
+ const body = await readBody(req);
2071
+ if (!body.file) return jsonReply(res, 400, { error: 'file required' });
2072
+ if (body.file.includes('..') || body.file.includes('\0') || body.file.includes('/') || body.file.includes('\\')) {
2073
+ return jsonReply(res, 400, { error: 'invalid filename' });
2074
+ }
2075
+ const planPath = resolvePlanPath(body.file);
2076
+ if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
2077
+
2078
+ // Move to archive directory
2079
+ const archiveDir = body.file.endsWith('.json') ? path.join(PRD_DIR, 'archive') : path.join(PLANS_DIR, 'archive');
2080
+ if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
2081
+ const archivePath = path.join(archiveDir, body.file);
2082
+ fs.renameSync(planPath, archivePath);
2083
+
2084
+ // Mark archived in JSON if PRD
2085
+ if (body.file.endsWith('.json')) {
2086
+ try {
2087
+ const prd = JSON.parse(safeRead(archivePath) || '{}');
2088
+ prd.status = 'archived';
2089
+ prd.archivedAt = new Date().toISOString();
2090
+ safeWrite(archivePath, prd);
2091
+ } catch { /* optional */ }
2092
+ }
2093
+
2094
+ invalidateStatusCache();
2095
+ return jsonReply(res, 200, { ok: true, archived: body.file });
2096
+ } catch (e) { return jsonReply(res, 400, { error: e.message }); }
2097
+ }
2098
+
2068
2099
  async function handlePlansRevise(req, res) {
2069
2100
  try {
2070
2101
  const body = await readBody(req);
@@ -3083,6 +3114,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3083
3114
  { method: 'POST', path: '/api/plans/reject', desc: 'Reject a plan', params: 'file, rejectedBy?, reason?', handler: handlePlansReject },
3084
3115
  { method: 'POST', path: '/api/plans/regenerate', desc: 'Reset pending/failed work items for a plan so they re-materialize', params: 'source', handler: handlePlansRegenerate },
3085
3116
  { method: 'POST', path: '/api/plans/delete', desc: 'Delete a plan file and clean up work items', params: 'file', handler: handlePlansDelete },
3117
+ { method: 'POST', path: '/api/plans/archive', desc: 'Move a plan/PRD to archive (preserves work items)', params: 'file', handler: handlePlansArchive },
3086
3118
  { method: 'POST', path: '/api/plans/revise', desc: 'Request revision with feedback, dispatches agent to revise', params: 'file, feedback, requestedBy?', handler: handlePlansRevise },
3087
3119
  { method: 'POST', path: '/api/plans/discuss', desc: 'Generate a plan discussion session script for Claude CLI', params: 'file', handler: handlePlansDiscuss },
3088
3120
  { method: 'GET', path: /^\/api\/plans\/archive\/([^?]+)$/, desc: 'Read an archived plan file', handler: handlePlansArchiveRead },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
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"