@yemi33/minions 0.1.96 → 0.1.97

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.97 (2026-04-01)
4
+
5
+ ### Engine
6
+ - engine/ado.js
7
+ - engine/github.js
8
+
9
+ ### Dashboard
10
+ - dashboard.js
11
+ - dashboard/js/render-prd.js
12
+ - dashboard/js/render-prs.js
13
+
3
14
  ## 0.1.96 (2026-04-01)
4
15
 
5
16
  ### Engine
@@ -238,9 +238,7 @@ function renderPrdProgress(prog) {
238
238
  : isCompleted
239
239
  ? '<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>'
240
240
  : '<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>';
241
- const archiveBtn = isCompleted
242
- ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" 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>'
243
- : '';
241
+ const archiveBtn = (isCompleted || isPaused) ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" style="color:var(--muted);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(139,148,158,0.1);border:1px solid rgba(139,148,158,0.3);border-radius:3px">Archive</span>' : '';
244
242
  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>';
245
243
  const sourcePlanLink = g.sourcePlan
246
244
  ? '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan) + '\')" style="color:var(--blue);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="View source plan">&#x1F4C4; Plan</span>'
@@ -509,6 +507,7 @@ function showArchivedPrdDetail(idx) {
509
507
  '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'graph\';showArchivedPrdDetail(' + idx + ')">Graph</button>' +
510
508
  '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (!isGraph ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="window._archivedPrdViewMode=\'list\';showArchivedPrdDetail(' + idx + ')">List</button>' +
511
509
  '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);margin-left:auto" onclick="triggerVerify(\'' + escHtml(g.file) + '\')">Trigger Verify</button>' +
510
+ '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="planUnarchive(\'' + escHtml(g.file) + '\',this)">Unarchive</button>' +
512
511
  '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="openArchivedPrdModal()">Back</button>' +
513
512
  '</div>';
514
513
 
@@ -7,9 +7,11 @@ const PR_PER_PAGE = 25;
7
7
  function prRow(pr) {
8
8
  // Minions review (agent) state — separate from ADO human review
9
9
  const sq = pr.minionsReview || {};
10
- const reviewSource = sq.status || pr.reviewStatus || 'pending';
10
+ // If PR is merged/abandoned, treat 'waiting' review as resolved
11
+ const effectiveReviewStatus = (pr.status === 'merged' || pr.status === 'abandoned') && pr.reviewStatus === 'waiting' ? (pr.status === 'merged' ? 'approved' : 'pending') : pr.reviewStatus;
12
+ const reviewSource = sq.status || effectiveReviewStatus || 'pending';
11
13
  const reviewClass = reviewSource === 'approved' ? 'approved' : (reviewSource === 'changes-requested' || reviewSource === 'rejected') ? 'rejected' : reviewSource === 'waiting' ? 'building' : 'draft';
12
- const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (pr.reviewStatus || 'pending');
14
+ const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (effectiveReviewStatus || 'pending');
13
15
  const buildClass = pr.buildStatus === 'passing' ? 'build-pass' : pr.buildStatus === 'failing' ? 'build-fail' : pr.buildStatus === 'running' ? 'building' : 'no-build';
14
16
  const buildLabel = pr.buildStatus || 'none';
15
17
  const statusClass = pr.status === 'merged' ? 'merged' : pr.status === 'abandoned' ? 'rejected' : pr.status === 'active' ? 'active' : 'draft';
package/dashboard.js CHANGED
@@ -1647,6 +1647,80 @@ If nothing to do, return: { "duplicates": [], "reclassify": [], "remove": [] }`;
1647
1647
  return jsonReply(res, 200, plans);
1648
1648
  }
1649
1649
 
1650
+ async function handlePlansArchiveMove(req, res) {
1651
+ try {
1652
+ const body = await readBody(req);
1653
+ if (!body.file) return jsonReply(res, 400, { error: 'file required' });
1654
+ const file = body.file;
1655
+ if (file.includes('..') || file.includes('\0')) return jsonReply(res, 400, { error: 'invalid' });
1656
+
1657
+ const isJson = file.endsWith('.json');
1658
+ const sourceDir = isJson ? PRD_DIR : PLANS_DIR;
1659
+ const archiveDir = path.join(sourceDir, 'archive');
1660
+ const sourcePath = path.join(sourceDir, file);
1661
+
1662
+ if (!fs.existsSync(sourcePath)) return jsonReply(res, 404, { error: 'File not found' });
1663
+ if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
1664
+
1665
+ fs.renameSync(sourcePath, path.join(archiveDir, file));
1666
+
1667
+ // If archiving a PRD .json, also archive its source .md plan
1668
+ let archivedSource = null;
1669
+ if (isJson) {
1670
+ try {
1671
+ const prd = safeJson(path.join(archiveDir, file));
1672
+ if (prd?.source_plan) {
1673
+ const mdPath = path.join(PLANS_DIR, prd.source_plan);
1674
+ if (fs.existsSync(mdPath)) {
1675
+ const planArchive = path.join(PLANS_DIR, 'archive');
1676
+ if (!fs.existsSync(planArchive)) fs.mkdirSync(planArchive, { recursive: true });
1677
+ fs.renameSync(mdPath, path.join(planArchive, prd.source_plan));
1678
+ archivedSource = prd.source_plan;
1679
+ }
1680
+ }
1681
+ } catch { /* optional — source plan may not exist */ }
1682
+ }
1683
+
1684
+ invalidateStatusCache();
1685
+ return jsonReply(res, 200, { ok: true, archivedSource });
1686
+ } catch (e) { return jsonReply(res, 400, { error: e.message }); }
1687
+ }
1688
+
1689
+ async function handlePlansUnarchive(req, res) {
1690
+ try {
1691
+ const body = await readBody(req);
1692
+ if (!body.file) return jsonReply(res, 400, { error: 'file required' });
1693
+ const file = body.file;
1694
+ if (file.includes('..') || file.includes('\0')) return jsonReply(res, 400, { error: 'invalid' });
1695
+
1696
+ const isJson = file.endsWith('.json');
1697
+ const targetDir = isJson ? PRD_DIR : PLANS_DIR;
1698
+ const archiveDir = path.join(targetDir, 'archive');
1699
+ const archivePath = path.join(archiveDir, file);
1700
+
1701
+ if (!fs.existsSync(archivePath)) return jsonReply(res, 404, { error: 'File not found in archive' });
1702
+ fs.renameSync(archivePath, path.join(targetDir, file));
1703
+
1704
+ // If unarchiving a PRD .json, also unarchive its source .md plan
1705
+ let unarchivedSource = null;
1706
+ if (isJson) {
1707
+ try {
1708
+ const prd = safeJson(path.join(targetDir, file));
1709
+ if (prd?.source_plan) {
1710
+ const mdArchivePath = path.join(PLANS_DIR, 'archive', prd.source_plan);
1711
+ if (fs.existsSync(mdArchivePath)) {
1712
+ fs.renameSync(mdArchivePath, path.join(PLANS_DIR, prd.source_plan));
1713
+ unarchivedSource = prd.source_plan;
1714
+ }
1715
+ }
1716
+ } catch { /* optional */ }
1717
+ }
1718
+
1719
+ invalidateStatusCache();
1720
+ return jsonReply(res, 200, { ok: true, unarchivedSource });
1721
+ } catch (e) { return jsonReply(res, 400, { error: e.message }); }
1722
+ }
1723
+
1650
1724
  async function handlePlansArchiveRead(req, res, match) {
1651
1725
  const file = decodeURIComponent(match[1]);
1652
1726
  if (file.includes('..') || file.includes('\0')) return jsonReply(res, 400, { error: 'invalid' });
@@ -3162,6 +3236,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3162
3236
  { method: 'POST', path: '/api/plans/unarchive', desc: 'Restore a plan/PRD from archive', params: 'file', handler: handlePlansUnarchive },
3163
3237
  { method: 'POST', path: '/api/plans/revise', desc: 'Request revision with feedback, dispatches agent to revise', params: 'file, feedback, requestedBy?', handler: handlePlansRevise },
3164
3238
  { method: 'POST', path: '/api/plans/discuss', desc: 'Generate a plan discussion session script for Claude CLI', params: 'file', handler: handlePlansDiscuss },
3239
+ { method: 'POST', path: '/api/plans/archive', desc: 'Archive a plan/PRD (move to archive folder)', params: 'file', handler: handlePlansArchiveMove },
3240
+ { method: 'POST', path: '/api/plans/unarchive', desc: 'Unarchive a plan/PRD (restore from archive folder)', params: 'file', handler: handlePlansUnarchive },
3165
3241
  { method: 'GET', path: /^\/api\/plans\/archive\/([^?]+)$/, desc: 'Read an archived plan file', handler: handlePlansArchiveRead },
3166
3242
  { method: 'GET', path: /^\/api\/plans\/([^?]+)$/, desc: 'Read a full plan (JSON from prd/ or markdown from plans/)', handler: handlePlansRead },
3167
3243
 
package/engine/ado.js CHANGED
@@ -131,6 +131,10 @@ async function pollPrStatus(config) {
131
131
  updated = true;
132
132
 
133
133
  if (newStatus === 'merged' || newStatus === 'abandoned') {
134
+ if (pr.reviewStatus === 'waiting') {
135
+ pr.reviewStatus = newStatus === 'merged' ? 'approved' : 'pending';
136
+ e.log('info', `PR ${pr.id} reviewStatus: waiting → ${pr.reviewStatus} (${newStatus})`);
137
+ }
134
138
  await engine().handlePostMerge(pr, project, config, newStatus);
135
139
  }
136
140
  }
package/engine/github.js CHANGED
@@ -135,6 +135,11 @@ async function pollPrStatus(config) {
135
135
  updated = true;
136
136
 
137
137
  if (newStatus === 'merged' || newStatus === 'abandoned') {
138
+ // Resolve stale 'waiting' review status — won't be polled again after this
139
+ if (pr.reviewStatus === 'waiting') {
140
+ pr.reviewStatus = newStatus === 'merged' ? 'approved' : 'pending';
141
+ e.log('info', `PR ${pr.id} reviewStatus: waiting → ${pr.reviewStatus} (${newStatus})`);
142
+ }
138
143
  await engine().handlePostMerge(pr, project, config, newStatus);
139
144
  }
140
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.96",
3
+ "version": "0.1.97",
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"