@yemi33/minions 0.1.110 → 0.1.112

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.112 (2026-04-01)
4
+
5
+ ### Engine
6
+ - engine/ado.js
7
+ - engine/github.js
8
+ - engine/lifecycle.js
9
+
10
+ ### Dashboard
11
+ - dashboard.js
12
+ - dashboard/js/live-stream.js
13
+ - dashboard/js/modal-qa.js
14
+ - dashboard/js/utils.js
15
+
3
16
  ## 0.1.110 (2026-04-01)
4
17
 
5
18
  ### Dashboard
@@ -19,7 +19,7 @@ function renderLiveChatMessage(raw) {
19
19
  if (block.type === 'tool_use') {
20
20
  el.innerHTML += '<div style="background:var(--surface);border:1px solid var(--border);padding:4px 8px;border-radius:4px;margin:2px 0;font-size:10px;color:var(--muted);cursor:pointer" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display===\'none\'?\'block\':\'none\'">' +
21
21
  '\u{1F527} ' + escHtml(block.name || 'tool') + '</div>' +
22
- '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + escHtml(JSON.stringify(block.input, null, 2).slice(0, 500)) + '</div>';
22
+ '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + escHtml(JSON.stringify(block.input || {}, null, 2).slice(0, 500)) + '</div>';
23
23
  }
24
24
  }
25
25
  }
@@ -227,8 +227,8 @@ async function _processQaMessage(message, selection) {
227
227
  _modalDocContext.content = display;
228
228
  }
229
229
 
230
- // If editing paused an active PRD, show re-execute actions
231
- if (data.pausedPrd && capturedFilePath) {
230
+ // If editing paused an active PRD, show re-execute actions (only for plan .md files)
231
+ if (data.pausedPrd && capturedFilePath && /^plans\/.*\.md$/.test(capturedFilePath)) {
232
232
  const planFile = capturedFilePath.replace(/^plans\//, '');
233
233
  const esc = planFile.replace(/'/g, "\\'");
234
234
  const actionDiv = document.createElement('div');
@@ -67,7 +67,10 @@ function renderMd(s) {
67
67
  html = html.replace(/(?<!\w)\*([^*\n]+)\*(?!\w)/g, '<em>$1</em>');
68
68
  html = html.replace(/(?<!\w)_([^_\n]+)_(?!\w)/g, '<em>$1</em>');
69
69
  html = html.replace(/~~(.+?)~~/g, '<s>$1</s>');
70
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:var(--blue)">$1</a>');
70
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_, text, href) {
71
+ if (/^(javascript|data|vbscript):/i.test(href)) return text;
72
+ return '<a href="' + href + '" target="_blank" rel="noopener" style="color:var(--blue)">' + text + '</a>';
73
+ });
71
74
 
72
75
  // 3. Block-level processing (line by line)
73
76
  var lines = html.split('\n');
package/dashboard.js CHANGED
@@ -1453,55 +1453,50 @@ const server = http.createServer(async (req, res) => {
1453
1453
  manifest.push({ category: e.cat, file: e.file, title: e.title, agent: e.agent, date: e.date, content: content.slice(0, 3000) });
1454
1454
  }
1455
1455
 
1456
- const prompt = `You are a knowledge base curator. Analyze these ${manifest.length} knowledge base entries and produce a cleanup plan.
1456
+ const { callLLM, trackEngineUsage } = require('./engine/llm');
1457
+ const BATCH_SIZE = 30; // ~30 entries per batch to stay within Haiku context
1458
+ const batches = [];
1459
+ for (let i = 0; i < manifest.length; i += BATCH_SIZE) {
1460
+ batches.push(manifest.slice(i, i + BATCH_SIZE));
1461
+ }
1462
+
1463
+ const plan = { duplicates: [], reclassify: [], remove: [] };
1464
+ for (let b = 0; b < batches.length; b++) {
1465
+ const batch = batches[b];
1466
+ const offset = b * BATCH_SIZE;
1467
+ const prompt = `You are a knowledge base curator. Analyze these ${batch.length} entries (batch ${b + 1}/${batches.length}, indices ${offset}-${offset + batch.length - 1}) and produce a cleanup plan.
1457
1468
 
1458
1469
  ## Entries
1459
1470
 
1460
- ${manifest.map((m, i) => `<entry index="${i}" category="${m.category}" file="${m.file}" date="${m.date}" agent="${m.agent || 'unknown'}">
1461
- ${m.title}
1462
- ${m.content.slice(0, 1500)}
1463
- </entry>`).join('\n\n')}
1471
+ ${batch.map((m, i) => `[${offset + i}] ${m.category}/${m.file} | ${m.title} | ${m.date} | ${m.agent || '?'} | ${(m.content || '').slice(0, 200).replace(/\n/g, ' ')}`).join('\n')}
1464
1472
 
1465
1473
  ## Instructions
1466
1474
 
1467
- 1. **Find duplicates**: entries with substantially the same content or insights (same findings from different agents or dispatch runs). List pairs/groups by index. When choosing which to keep, prefer the more recent entry (later date) as it likely reflects the current state of the codebase.
1468
-
1469
- 2. **Find misclassified**: entries in the wrong category. Common: build reports in conventions, reviews in architecture.
1475
+ 1. **Find duplicates**: entries with substantially the same content (same findings, different agents/runs). List pairs by index. Prefer keeping the more recent entry.
1476
+ 2. **Find misclassified**: entries in the wrong category.
1477
+ 3. **Find stale/empty**: entries with no actionable content (boilerplate, bail-out notes, "no changes needed").
1470
1478
 
1471
- 3. **Find stale/empty**: entries with no actionable content (boilerplate, "no changes needed", bail-out notes).
1472
-
1473
- ## Output Format
1474
-
1475
- Respond with ONLY valid JSON (no markdown fences, no preamble):
1476
-
1477
- {
1478
- "duplicates": [
1479
- { "keep": 0, "remove": [1, 5], "reason": "same PR review findings" }
1480
- ],
1481
- "reclassify": [
1482
- { "index": 3, "from": "conventions", "to": "build-reports", "reason": "..." }
1483
- ],
1484
- "remove": [
1485
- { "index": 7, "reason": "empty bail-out note" }
1486
- ]
1487
- }
1479
+ Respond with ONLY valid JSON: { "duplicates": [{ "keep": N, "remove": [N], "reason": "..." }], "reclassify": [{ "index": N, "from": "cat", "to": "cat", "reason": "..." }], "remove": [{ "index": N, "reason": "..." }] }
1480
+ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
1488
1481
 
1489
- If nothing to do, return: { "duplicates": [], "reclassify": [], "remove": [] }`;
1490
-
1491
- const { callLLM, trackEngineUsage } = require('./engine/llm');
1492
- const result = await callLLM(prompt, 'You are a concise knowledge curator. Output only JSON.', {
1493
- timeout: 180000, label: 'kb-sweep', model: 'haiku', maxTurns: 1
1494
- });
1495
- trackEngineUsage('kb-sweep', result.usage);
1482
+ const result = await callLLM(prompt, 'Output only JSON.', {
1483
+ timeout: 120000, label: 'kb-sweep', model: 'haiku', maxTurns: 1
1484
+ });
1485
+ trackEngineUsage('kb-sweep', result.usage);
1496
1486
 
1497
- let plan;
1498
- try {
1499
- let jsonStr = result.text.trim();
1500
- const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
1501
- if (fenceMatch) jsonStr = fenceMatch[1].trim();
1502
- plan = JSON.parse(jsonStr);
1503
- } catch {
1504
- return jsonReply(res, 200, { ok: false, error: 'LLM returned invalid JSON', raw: result.text.slice(0, 500) });
1487
+ let batchPlan;
1488
+ try {
1489
+ let jsonStr = (result.text || '').trim();
1490
+ const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
1491
+ if (fenceMatch) jsonStr = fenceMatch[1].trim();
1492
+ batchPlan = JSON.parse(jsonStr);
1493
+ } catch {
1494
+ console.log(`[kb-sweep] batch ${b + 1}/${batches.length} returned invalid JSON, skipping`);
1495
+ continue;
1496
+ }
1497
+ if (batchPlan.duplicates) plan.duplicates.push(...batchPlan.duplicates);
1498
+ if (batchPlan.reclassify) plan.reclassify.push(...batchPlan.reclassify);
1499
+ if (batchPlan.remove) plan.remove.push(...batchPlan.remove);
1505
1500
  }
1506
1501
 
1507
1502
  let removed = 0, reclassified = 0, merged = 0;
package/engine/ado.js CHANGED
@@ -389,7 +389,19 @@ async function reconcilePrs(config) {
389
389
  e.log('info', `PR reconciliation: added ${prId} (branch: ${branch}${confirmedItemId ? ', linked to ' + confirmedItemId : ''}) to ${project.name}`);
390
390
  }
391
391
 
392
- if (projectAdded > 0 || projectUpdated > 0) {
392
+ // Backfill prdItems from pr-links for any PR with empty array
393
+ const prLinks = shared.getPrLinks();
394
+ let backfilled = 0;
395
+ for (const pr of existingPrs) {
396
+ const linked = prLinks[pr.id];
397
+ if (linked && !(pr.prdItems || []).includes(linked)) {
398
+ pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
399
+ pr.prdItems.push(linked);
400
+ backfilled++;
401
+ }
402
+ }
403
+
404
+ if (projectAdded > 0 || projectUpdated > 0 || backfilled > 0) {
393
405
  shared.safeWrite(prPath, existingPrs);
394
406
  totalAdded += projectAdded;
395
407
  if (projectUpdated > 0) e.log('info', `PR reconciliation: linked ${projectUpdated} existing PR(s) to PRD items in ${project.name}`);
package/engine/github.js CHANGED
@@ -376,7 +376,19 @@ async function reconcilePrs(config) {
376
376
  e.log('info', `GitHub PR reconciliation: added ${prId} (branch: ${branch}${confirmedItemId ? ', linked to ' + confirmedItemId : ''}) to ${project.name}`);
377
377
  }
378
378
 
379
- if (projectAdded > 0) {
379
+ // Backfill prdItems from pr-links for any PR with empty array
380
+ const prLinks = getPrLinks();
381
+ let backfilled = 0;
382
+ for (const pr of existingPrs) {
383
+ const linked = prLinks[pr.id];
384
+ if (linked && !(pr.prdItems || []).includes(linked)) {
385
+ pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
386
+ pr.prdItems.push(linked);
387
+ backfilled++;
388
+ }
389
+ }
390
+
391
+ if (projectAdded > 0 || backfilled > 0) {
380
392
  safeWrite(prPath, existingPrs);
381
393
  totalAdded += projectAdded;
382
394
  }
@@ -724,6 +724,9 @@ async function handlePostMerge(pr, project, config, newStatus) {
724
724
 
725
725
  if (newStatus !== 'merged') return;
726
726
 
727
+ // Mark review as approved since it was merged
728
+ pr.reviewStatus = 'approved';
729
+
727
730
  // Resolve linked work item from pr-links or PR branch name
728
731
  let mergedItemId = getPrLinks()[pr.id];
729
732
  if (!mergedItemId && pr.branch) {
@@ -761,7 +764,7 @@ async function handlePostMerge(pr, project, config, newStatus) {
761
764
  if (item && item.status !== 'done') {
762
765
  log('info', `Post-merge: marking work item ${mergedItemId} as done (was ${item.status}) for ${pr.id}`);
763
766
  item.status = 'done';
764
- item.completedAt = e.ts();
767
+ item.completedAt = ts();
765
768
  item._mergedVia = pr.id;
766
769
  shared.safeWrite(wiPath, items);
767
770
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.110",
3
+ "version": "0.1.112",
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"