@yemi33/minions 0.1.916 → 0.1.918

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.916 (2026-04-13)
3
+ ## 0.1.918 (2026-04-13)
4
4
 
5
5
  ### Features
6
6
  - CC tab unread dot + reopened badge on work items
@@ -18,6 +18,9 @@
18
18
  - add red dot notification on CC tab when response completes (#934) (#946)
19
19
 
20
20
  ### Fixes
21
+ - pipeline artifact links use pushModalBack instead of setTimeout race (#993)
22
+ - close unclosed code fences and lone backticks in renderMd (#991)
23
+ - PRD item status stuck at dispatched when fix completes (#989)
21
24
  - dep merge ancestor pruning + pre-flight simulation (#958) (#979)
22
25
  - add MAX_TURNS failure class and fix enum count test (#983)
23
26
  - prevent Create Plan from meeting saving doc-chat context bleed (#980)
@@ -64,43 +64,35 @@ function _renderMonitoredResources(resources, options) {
64
64
  /**
65
65
  * Render clickable artifact links for a pipeline stage.
66
66
  * Each artifact type gets an icon and navigates to the relevant detail view.
67
+ * @param {Object} artifacts - artifact map from stage run
68
+ * @param {string} [pipelineId] - pipeline ID for modal back-navigation
67
69
  */
68
- function _renderArtifactLinks(artifacts) {
70
+ function _renderArtifactLinks(artifacts, pipelineId) {
69
71
  if (!artifacts) return '';
70
72
  var links = [];
71
73
  var linkStyle = 'display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:10px;font-size:10px;cursor:pointer;text-decoration:none;color:var(--blue);background:color-mix(in srgb, var(--blue) 10%, transparent);border:1px solid color-mix(in srgb, var(--blue) 20%, transparent)';
72
74
 
73
- // Work items navigate to work page & open detail
75
+ // Pushes current pipeline modal onto back stack so detail modals can navigate back
76
+ var backFn = pipelineId ? "pushModalBack(function(){openPipelineDetail('" + escHtml(pipelineId) + "')});" : '';
77
+
74
78
  (artifacts.workItems || []).forEach(function(id) {
75
- links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'work\');setTimeout(function(){openWorkItemDetail(\'' + escHtml(id) + '\')},200)" title="Open work item ' + escHtml(id) + '">⚙ ' + escHtml(id) + '</span>');
79
+ links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();' + backFn + 'openWorkItemDetail(\'' + escHtml(id) + '\')" title="Open work item ' + escHtml(id) + '">⚙ ' + escHtml(id) + '</span>');
76
80
  });
77
-
78
- // Meetings → navigate to meetings page & open detail
79
81
  (artifacts.meetings || []).forEach(function(id) {
80
- links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'meetings\');setTimeout(function(){openMeetingDetail(\'' + escHtml(id) + '\')},200)" title="Open meeting ' + escHtml(id) + '">💬 ' + escHtml(id) + '</span>');
82
+ links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();' + backFn + 'openMeetingDetail(\'' + escHtml(id) + '\')" title="Open meeting ' + escHtml(id) + '">💬 ' + escHtml(id) + '</span>');
81
83
  });
82
-
83
- // Plans → navigate to plans page
84
84
  (artifacts.plans || []).forEach(function(name) {
85
- links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'plans\')" title="Plan: ' + escHtml(name) + '">📋 ' + escHtml(name.replace(/\.md$/, '').slice(0, 30)) + '</span>');
85
+ links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();' + backFn + 'planView(\'' + escHtml(name) + '\')" title="Plan: ' + escHtml(name) + '">📋 ' + escHtml(name.replace(/\.md$/, '').slice(0, 30)) + '</span>');
86
86
  });
87
-
88
- // PRDs → navigate to PRD page
89
87
  (artifacts.prds || []).forEach(function(name) {
90
- links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'prd\')" title="PRD: ' + escHtml(name) + '">📄 ' + escHtml(name.replace(/\.json$/, '').slice(0, 30)) + '</span>');
88
+ links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();' + backFn + 'planView(\'' + escHtml(name) + '\')" title="PRD: ' + escHtml(name) + '">📄 ' + escHtml(name.replace(/\.json$/, '').slice(0, 30)) + '</span>');
91
89
  });
92
-
93
- // PRs → navigate to PRs page
94
90
  (artifacts.prs || []).forEach(function(id) {
95
91
  links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'prs\')" title="Pull request ' + escHtml(id) + '">🔀 PR-' + escHtml(id) + '</span>');
96
92
  });
97
-
98
- // Sub-stages (parallel) — just label them, no nav needed
99
93
  (artifacts.subStages || []).forEach(function(id) {
100
94
  links.push('<span style="' + linkStyle + ';cursor:default;color:var(--muted);background:color-mix(in srgb, var(--muted) 8%, transparent);border-color:color-mix(in srgb, var(--muted) 15%, transparent)" title="Sub-stage ' + escHtml(id) + '">⚓ ' + escHtml(id) + '</span>');
101
95
  });
102
-
103
- // Notes → navigate to inbox page
104
96
  (artifacts.notes || []).forEach(function(name) {
105
97
  links.push('<span style="' + linkStyle + '" onclick="event.stopPropagation();closeModal();switchPage(\'inbox\')" title="Note: ' + escHtml(name) + '">📝 ' + escHtml(name.replace(/\.md$/, '').replace(/^\d{4}-\d{2}-\d{2}-/, '').slice(0, 30)) + '</span>');
106
98
  });
@@ -367,7 +359,7 @@ function openPipelineDetail(id) {
367
359
  (s.type === 'condition' ? ' | Check: ' + escHtml(typeof (s.check || s.condition) === 'string' ? (s.check || s.condition) : ((s.check || s.condition || {}).check || '?')) + (s.onMet ? ' | onMet: ' + escHtml(s.onMet) : '') + (s.onUnmet ? ' | onUnmet: ' + escHtml(s.onUnmet) : '') : '') +
368
360
  '</div>' +
369
361
  _renderMonitoredResources(s.monitoredResources || []) +
370
- _renderArtifactLinks(stageRun.artifacts) +
362
+ _renderArtifactLinks(stageRun.artifacts, id) +
371
363
  (stageRun.output ? '<div style="margin-top:6px;font-size:11px;max-height:150px;overflow-y:auto">' + renderMd(stageRun.output.slice(0, 500)) + '</div>' : '') +
372
364
  (stageStatus === 'waiting-human' ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);border-color:var(--green);margin-top:6px" onclick="_continuePipeline(\'' + escHtml(id) + '\',\'' + escHtml(s.id) + '\',this)">Continue</button>' : '') +
373
365
  '</div>';
@@ -390,7 +382,7 @@ function openPipelineDetail(id) {
390
382
  (r.completedAt ? '<span style="color:var(--muted)">\u2192 ' + new Date(r.completedAt).toLocaleString() + '</span>' : '') +
391
383
  (artifactCount > 0 ? '<span style="color:var(--blue);cursor:pointer;user-select:none" onclick="var el=document.getElementById(\'' + toggleId + '\');el.style.display=el.style.display===\'none\'?\'flex\':\'none\'" title="Toggle artifacts">' + artifactCount + ' artifact' + (artifactCount !== 1 ? 's' : '') + ' ▾</span>' : '') +
392
384
  '</div>' +
393
- (artifactCount > 0 ? '<div id="' + toggleId + '" style="display:none;flex-wrap:wrap;gap:4px;margin-top:4px;margin-left:12px">' + _renderArtifactLinks(runArtifacts.merged) + '</div>' : '') +
385
+ (artifactCount > 0 ? '<div id="' + toggleId + '" style="display:none;flex-wrap:wrap;gap:4px;margin-top:4px;margin-left:12px">' + _renderArtifactLinks(runArtifacts.merged, id) + '</div>' : '') +
394
386
  '</div>';
395
387
  });
396
388
  }
@@ -152,15 +152,33 @@ function renderMd(s) {
152
152
  return _renderMdCore(s);
153
153
  }
154
154
 
155
+ function _normalizeCodeFences(s) {
156
+ var fenceCount = (s.match(/```/g) || []).length;
157
+ if (fenceCount % 2 !== 0) s = s + '\n```';
158
+ return s;
159
+ }
160
+
155
161
  function _renderMdCore(s) {
156
162
  let html = escHtml(s);
157
163
 
164
+ // Close unclosed code fences before any regex processing
165
+ html = _normalizeCodeFences(html);
166
+
158
167
  // 1. Extract code blocks and inline code into placeholders (protect from other transforms)
159
168
  const codeSlots = [];
160
169
  html = html.replace(/```(\w*)\n([\s\S]*?)```/g, function(_, lang, code) {
161
170
  codeSlots.push('<pre style="background:var(--bg);padding:8px;border-radius:4px;overflow-x:auto;font-size:11px;margin:4px 0"><code>' + code + '</code></pre>');
162
171
  return '\x00CB' + (codeSlots.length - 1) + '\x00';
163
172
  });
173
+
174
+ // Close lone unclosed backticks per-line before inline code extraction
175
+ html = html.split('\n').map(function(line) {
176
+ // Skip code block placeholders
177
+ if (/^\x00CB\d+\x00$/.test(line)) return line;
178
+ var ticks = (line.match(/`/g) || []).length;
179
+ return (ticks % 2 !== 0) ? line + '`' : line;
180
+ }).join('\n');
181
+
164
182
  html = html.replace(/`([^`\n]+)`/g, function(_, code) {
165
183
  codeSlots.push('<code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:0.9em">' + code + '</code>');
166
184
  return '\x00CB' + (codeSlots.length - 1) + '\x00';
@@ -294,6 +312,9 @@ var MD_CHUNK_SIZE = 10000;
294
312
  var _mdChunkUid = 0;
295
313
 
296
314
  function _renderMdChunked(fullText) {
315
+ // Close unclosed code fences before chunking
316
+ fullText = _normalizeCodeFences(fullText);
317
+
297
318
  // Split at blank-line boundaries to avoid breaking code blocks and other multi-line elements
298
319
  var chunks = [];
299
320
  var pos = 0;
@@ -617,6 +617,8 @@ function updateWorkItemStatus(meta, status, reason) {
617
617
  }
618
618
 
619
619
  const _VALID_PRD_STATUSES = new Set([...Object.values(WI_STATUS), 'missing']);
620
+ // (#984) PRD statuses that are stale when the work item is actually done
621
+ const _STALE_PRD_STATUSES = new Set([WI_STATUS.DISPATCHED, WI_STATUS.FAILED, WI_STATUS.PENDING]);
620
622
  function syncPrdItemStatus(itemId, status, sourcePlan) {
621
623
  if (!itemId) return;
622
624
  if (!_VALID_PRD_STATUSES.has(status)) return;
@@ -637,11 +639,12 @@ function syncPrdItemStatus(itemId, status, sourcePlan) {
637
639
  } catch (err) { log('warn', `PRD status sync: ${err.message}`); }
638
640
  }
639
641
 
640
- // ─── PRD Backward-Scan Reconciliation (#929) ────────────────────────────────
641
- // Proactive counterpart to syncPrdItemStatus. Scans all active PRDs and promotes
642
- // "missing" items to "updated" when a done work item already exists for that ID.
643
- // This catches cases where a PRD regeneration incorrectly sets items to "missing"
644
- // and no subsequent work item state change triggers the reactive sync.
642
+ // ─── PRD Backward-Scan Reconciliation (#929, #984) ─────────────────────────
643
+ // Proactive counterpart to syncPrdItemStatus. Scans all active PRDs and:
644
+ // 1. Promotes "missing" items to "updated" when a done work item already exists (#929)
645
+ // 2. Promotes stale "dispatched"/"failed"/"pending" items to "done" when the work item
646
+ // is actually done (#984) catches cases where fix work items complete with a
647
+ // different ID than the original PRD feature, leaving the PRD status stale.
645
648
 
646
649
  function reconcilePrdStatuses(config) {
647
650
  if (!fs.existsSync(PRD_DIR)) return;
@@ -674,6 +677,14 @@ function reconcilePrdStatuses(config) {
674
677
  modified = true;
675
678
  log('info', `PRD backward-scan: promoted ${feature.id} from missing→updated in ${file} (done work item exists)`);
676
679
  }
680
+ // (#984) Stale status: PRD item stuck at dispatched/failed/pending while WI is done —
681
+ // happens when fix work items complete with a different ID than the original PRD feature
682
+ else if (_STALE_PRD_STATUSES.has(feature.status) && doneWiById.has(feature.id)) {
683
+ const prev = feature.status;
684
+ feature.status = WI_STATUS.DONE;
685
+ modified = true;
686
+ log('info', `PRD backward-scan: promoted ${feature.id} from ${prev}→done in ${file} (done work item exists)`);
687
+ }
677
688
  }
678
689
 
679
690
  if (modified) {
@@ -1790,7 +1801,23 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1790
1801
  }
1791
1802
 
1792
1803
  if (type === WORK_TYPE.REVIEW) await updatePrAfterReview(agentId, meta?.pr, meta?.project, config, resultSummary);
1793
- if (type === WORK_TYPE.FIX) updatePrAfterFix(meta?.pr, meta?.project, meta?.source);
1804
+ if (type === WORK_TYPE.FIX) {
1805
+ updatePrAfterFix(meta?.pr, meta?.project, meta?.source);
1806
+ // (#984) Sync PRD status for PR-linked features: fix work items have a different ID
1807
+ // than the original PRD feature, so syncPrdItemStatus(fixWiId, ...) finds nothing.
1808
+ // Use the PR's prdItems to propagate done status when the original work item is done.
1809
+ if (effectiveSuccess && meta?.pr?.prdItems?.length) {
1810
+ try {
1811
+ const allWis = queries.getWorkItems(config);
1812
+ for (const prdItemId of meta.pr.prdItems) {
1813
+ const wi = allWis.find(w => w.id === prdItemId);
1814
+ if (wi && DONE_STATUSES.has(wi.status) && wi.sourcePlan) {
1815
+ syncPrdItemStatus(prdItemId, WI_STATUS.DONE, wi.sourcePlan);
1816
+ }
1817
+ }
1818
+ } catch (err) { log('warn', `PRD sync after fix: ${err.message}`); }
1819
+ }
1820
+ }
1794
1821
  checkForLearnings(agentId, config.agents[agentId], dispatchItem.task);
1795
1822
  if (effectiveSuccess) {
1796
1823
  extractSkillsFromOutput(stdout, agentId, dispatchItem, config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.916",
3
+ "version": "0.1.918",
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"