@yemi33/minions 0.1.2137 → 0.1.2138

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/dashboard.js CHANGED
@@ -7470,6 +7470,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
7470
7470
  }
7471
7471
  safeWrite(notesPath, notes);
7472
7472
 
7473
+ // W-mq1j85cj00055a8f — rewrite WI references that pointed at the
7474
+ // now-archived inbox note to the persisted destination (notes.md).
7475
+ try { shared.rewriteInboxRefsAcrossProjects(name, 'notes.md'); }
7476
+ catch (e) { console.error('inbox-ref rewrite (persist):', e.message); }
7477
+
7473
7478
  // Move to archive
7474
7479
  const archiveDir = path.join(MINIONS_DIR, 'notes', 'archive');
7475
7480
  if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
@@ -7509,6 +7514,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
7509
7514
  safeWrite(kbFile, kbContent);
7510
7515
  queries.invalidateKnowledgeBaseCache();
7511
7516
 
7517
+ // W-mq1j85cj00055a8f — rewrite WI references that pointed at the
7518
+ // now-archived inbox note to the KB destination.
7519
+ try { shared.rewriteInboxRefsAcrossProjects(name, `knowledge/${category}/${name}`); }
7520
+ catch (e) { console.error('inbox-ref rewrite (promote-kb):', e.message); }
7521
+
7512
7522
  // Move inbox item to archive
7513
7523
  const archiveDir = path.join(MINIONS_DIR, 'notes', 'archive');
7514
7524
  if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
@@ -1155,7 +1155,22 @@ function archiveInboxFiles(files) {
1155
1155
 
1156
1156
  if (!fs.existsSync(ARCHIVE_DIR)) fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
1157
1157
  for (const f of files) {
1158
- try { fs.renameSync(path.join(INBOX_DIR, f), shared.uniquePath(path.join(ARCHIVE_DIR, `${dateStamp()}-${f}`))); } catch (err) { log('warn', `Inbox archive: ${err.message}`); }
1158
+ try {
1159
+ // Resolve the final destination path BEFORE rename so the WI-ref
1160
+ // rewrite can point at the actual on-disk location (uniquePath may
1161
+ // suffix `-2`, `-3` … if a same-day collision exists).
1162
+ const dest = shared.uniquePath(path.join(ARCHIVE_DIR, `${dateStamp()}-${f}`));
1163
+ // W-mq1j85cj00055a8f — rewrite WI references that pointed at the
1164
+ // now-archived inbox note to the archive destination. Computed
1165
+ // relative to MINIONS_DIR so it matches the dashboard's relative-URL
1166
+ // render convention. Wrapped so a rewrite failure can't break the
1167
+ // archive step itself (which is the load-bearing operation here).
1168
+ try {
1169
+ const rel = path.relative(shared.MINIONS_DIR, dest).replace(/\\/g, '/');
1170
+ shared.rewriteInboxRefsAcrossProjects(f, rel);
1171
+ } catch (e) { log('warn', `Inbox-ref rewrite (${f}): ${e.message}`); }
1172
+ fs.renameSync(path.join(INBOX_DIR, f), dest);
1173
+ } catch (err) { log('warn', `Inbox archive: ${err.message}`); }
1159
1174
  }
1160
1175
  }
1161
1176
 
package/engine/shared.js CHANGED
@@ -5090,6 +5090,87 @@ function extractStructuredWorkItemPrRef(item) {
5090
5090
  return null;
5091
5091
  }
5092
5092
 
5093
+ // W-mq1j85cj00055a8f — when an inbox note (notes/inbox/<name>) leaves the
5094
+ // inbox (persisted to notes.md, promoted to knowledge/, or auto-archived by
5095
+ // consolidation), any work-item reference pointing at the old inbox path
5096
+ // turns into a broken link. Rewrite those references across every project's
5097
+ // work-items.json + the central one to the new canonical location.
5098
+ //
5099
+ // Match semantics (intentionally narrow per the WI scope):
5100
+ // - Scope is strictly item.references[]; description prose is NOT scanned.
5101
+ // - String entries: 'notes/inbox/<inboxName>' (anchored on start or '/').
5102
+ // - Object entries: { url | path | href }. Other keys (label, kind, …) are
5103
+ // preserved.
5104
+ // - Trailing `?query` or `#fragment` is tolerated; substring overlaps in
5105
+ // unrelated path segments (e.g. .../notes/inbox/foobar for foo.md) are
5106
+ // NOT rewritten.
5107
+ // - Archived work-items files are skipped — only live work-items.json paths
5108
+ // surfaced by getProjects() + the central path are touched.
5109
+ //
5110
+ // Idempotent: re-running with the same inboxName after the rewrite is a
5111
+ // no-op (the references already read newLocation, which won't match the
5112
+ // notes/inbox/<inboxName> regex).
5113
+ //
5114
+ // Returns the count of references rewritten across all files (for logging).
5115
+ // Each file's mutate is wrapped in try/catch so one corrupt project can't
5116
+ // block the others.
5117
+ //
5118
+ // `opts._mutate` is an undocumented test seam — production callers always
5119
+ // take the default (the in-file `mutateWorkItems` closure reference). Tests
5120
+ // inject a wrapper to exercise the per-file try/catch boundary without
5121
+ // having to manufacture a real SQL-store failure.
5122
+ function rewriteInboxRefsAcrossProjects(inboxName, newLocation, opts = {}) {
5123
+ if (!inboxName || typeof newLocation !== 'string' || !newLocation) return 0;
5124
+ const baseName = String(inboxName).replace(/^.*[/\\]/, '').trim();
5125
+ if (!baseName) return 0;
5126
+ const escaped = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5127
+ const inboxRefRe = new RegExp(`(?:^|/)notes/inbox/${escaped}(?=$|[?#])`);
5128
+ const _mutate = (opts && typeof opts._mutate === 'function') ? opts._mutate : mutateWorkItems;
5129
+
5130
+ let rewritten = 0;
5131
+ const config = safeJson(path.join(MINIONS_DIR, 'config.json')) || {};
5132
+ const candidates = [];
5133
+ try {
5134
+ for (const project of getProjects(config)) {
5135
+ try { candidates.push(projectWorkItemsPath(project)); } catch { /* skip bad project entry */ }
5136
+ }
5137
+ } catch { /* getProjects failed — fall back to central only */ }
5138
+ candidates.push(centralWorkItemsPath());
5139
+
5140
+ for (const wiPath of candidates) {
5141
+ try {
5142
+ _mutate(wiPath, items => {
5143
+ if (!Array.isArray(items)) return items;
5144
+ for (const item of items) {
5145
+ if (!item || !Array.isArray(item.references)) continue;
5146
+ for (let i = 0; i < item.references.length; i++) {
5147
+ const ref = item.references[i];
5148
+ if (typeof ref === 'string') {
5149
+ if (inboxRefRe.test(ref)) {
5150
+ item.references[i] = newLocation;
5151
+ rewritten++;
5152
+ }
5153
+ } else if (ref && typeof ref === 'object') {
5154
+ for (const key of ['url', 'path', 'href']) {
5155
+ const v = ref[key];
5156
+ if (typeof v === 'string' && inboxRefRe.test(v)) {
5157
+ ref[key] = newLocation;
5158
+ rewritten++;
5159
+ break;
5160
+ }
5161
+ }
5162
+ }
5163
+ }
5164
+ }
5165
+ return items;
5166
+ });
5167
+ } catch (e) {
5168
+ try { console.warn(`rewriteInboxRefsAcrossProjects(${wiPath}): ${e.message}`); } catch { /* logging best-effort */ }
5169
+ }
5170
+ }
5171
+ return rewritten;
5172
+ }
5173
+
5093
5174
  function extractWorkItemPrRef(item) {
5094
5175
  if (!item || typeof item !== 'object') return null;
5095
5176
  const fromStructured = extractStructuredWorkItemPrRef(item);
@@ -6500,6 +6581,7 @@ module.exports = {
6500
6581
  extractPrRefFromText,
6501
6582
  extractWorkItemPrRef,
6502
6583
  extractStructuredWorkItemPrRef,
6584
+ rewriteInboxRefsAcrossProjects,
6503
6585
  getProjectPrScope,
6504
6586
  getPrNumber,
6505
6587
  getPrDisplayId,
package/engine/watches.js CHANGED
@@ -320,7 +320,10 @@ function evaluateWatch(watch, state) {
320
320
  if (!tt.conditions.includes(condition)) return { triggered: false, message: `Unknown condition: ${condition}` };
321
321
 
322
322
  const entity = tt.fetchEntity(target, state || {});
323
- if (!entity) return { triggered: false, message: `${tt.label} ${target} not found` };
323
+ if (!entity) {
324
+ const targetStr = typeof target === 'string' ? target : JSON.stringify(target);
325
+ return { triggered: false, message: `${tt.label} ${targetStr} not found` };
326
+ }
324
327
 
325
328
  const prevState = watch._lastState || {};
326
329
  let primary;
@@ -645,21 +648,35 @@ async function _runActionTask(task) {
645
648
  /**
646
649
  * Internal: capture state snapshot for a watch target.
647
650
  * Dispatches to the registered target type's captureState.
651
+ *
652
+ * W-mq1n83pw000844b8 — Preserve prior state on transient fetchEntity null
653
+ * (and on captureState exceptions / unknown target types). The old behavior
654
+ * wiped to {} whenever the entity could not be resolved, which then made
655
+ * line 431 re-initialize via captureState on the next tick, losing
656
+ * type-specific dedup keys (gh-author-prs `numbers`, work-item
657
+ * `_unchangedTicks`, pipeline `_stuckStageTicks`). The result was watches
658
+ * re-firing for already-seen entities after every engine restart while a
659
+ * plugin's background fetch cache warmed up. Preserving prevState is
660
+ * harmless for types where fetchEntity-null means "entity deleted" because
661
+ * evaluate already gates on entity being non-null, and is the bug-free
662
+ * behavior for plugins with transient nulls (cache miss, network hiccup).
648
663
  */
649
664
  function _captureState(watch, state) {
665
+ const prevState = watch._lastState || {};
650
666
  const tt = TARGET_TYPES[watch.targetType];
651
- if (!tt) return {};
667
+ if (!tt) return prevState;
652
668
  const entity = tt.fetchEntity(watch.target, state || {});
653
- if (!entity) return {};
669
+ if (!entity) return prevState;
654
670
  try {
655
671
  // P-w5b8d2c9 — Phase 2.2: pass prevState so captureState can carry
656
672
  // forward unchanged-tick counters (e.g. _unchangedTicks for work-item
657
673
  // stalled, _stuckStageTicks for pipeline stuck-in-stage). Existing
658
674
  // captureState fns that take only 1 arg ignore this — backward-compat.
659
- return tt.captureState(entity, watch._lastState || {}) || {};
675
+ const out = tt.captureState(entity, prevState);
676
+ return (out && typeof out === 'object') ? out : prevState;
660
677
  } catch (err) {
661
678
  log('warn', `_captureState ${watch.targetType}: ${err.message}`);
662
- return {};
679
+ return prevState;
663
680
  }
664
681
  }
665
682
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2137",
3
+ "version": "0.1.2138",
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"