@yemi33/minions 0.1.2329 → 0.1.2331

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.
@@ -1398,7 +1398,7 @@ function archiveInboxFiles(files) {
1398
1398
  const rel = path.relative(shared.MINIONS_DIR, dest).replace(/\\/g, '/');
1399
1399
  shared.rewriteInboxRefsAcrossProjects(f, rel);
1400
1400
  } catch (e) { log('warn', `Inbox-ref rewrite (${f}): ${e.message}`); }
1401
- fs.renameSync(path.join(INBOX_DIR, f), dest);
1401
+ shared._retryFsOp(() => fs.renameSync(path.join(INBOX_DIR, f), dest), `archive inbox ${f}`);
1402
1402
  } catch (err) { log('warn', `Inbox archive: ${err.message}`); }
1403
1403
  }
1404
1404
  }
@@ -14,7 +14,7 @@ const path = require('path');
14
14
  const crypto = require('crypto');
15
15
  const shared = require('./shared');
16
16
  const queries = require('./queries');
17
- const { safeRead, safeWrite, safeJson, safeUnlink, log, ts } = shared;
17
+ const { safeRead, safeReadOrNull, safeWrite, safeJson, safeUnlink, log, ts } = shared;
18
18
  const { MINIONS_DIR, ENGINE_DIR } = queries;
19
19
 
20
20
  const KB_DIR = path.join(MINIONS_DIR, 'knowledge');
@@ -58,11 +58,10 @@ function _serializeFrontmatter(fm, body) {
58
58
  }
59
59
 
60
60
  function _archiveKbFile(filePath, reason) {
61
- if (!fs.existsSync(filePath)) return false;
62
61
  if (!fs.existsSync(SWEPT_DIR)) fs.mkdirSync(SWEPT_DIR, { recursive: true });
63
62
  const destPath = shared.uniquePath(path.join(SWEPT_DIR, path.basename(filePath)));
64
63
  try {
65
- const content = safeRead(filePath);
64
+ const content = safeReadOrNull(filePath);
66
65
  if (content === null) return false;
67
66
  safeWrite(destPath, `<!-- swept: ${new Date().toISOString()} | reason: ${reason} -->\n${content}`);
68
67
  safeUnlink(filePath);
@@ -707,6 +706,7 @@ module.exports = {
707
706
  _parseFrontmatter,
708
707
  _serializeFrontmatter,
709
708
  _hashDedup,
709
+ _archiveKbFile,
710
710
  COMPRESS_THRESHOLD_BYTES,
711
711
  SWEPT_FLAG_KEY,
712
712
  };
@@ -6715,8 +6715,15 @@ function autoDispatchLiveValidationWi(meta, config) {
6715
6715
 
6716
6716
  // Resolve PR reference: prefer the canonical stamped _pr field (set by
6717
6717
  // stampWiPrRef which runs earlier in runPostCompletionHooks), then fall
6718
- // back to extractWorkItemPrRef for structured fields / references[].
6719
- const prRef = item._pr || item._prUrl || shared.extractWorkItemPrRef(item) || null;
6718
+ // back to extractStructuredWorkItemPrRef for structured fields /
6719
+ // references[] ONLY. This function auto-dispatches for ANY configured
6720
+ // liveValidation.type (not just type:'fix'), so we must NOT use
6721
+ // extractWorkItemPrRef's prose-scan fallback here — that fallback is
6722
+ // reserved for the type:'fix'-gated branch-derivation path
6723
+ // (W-mq18ec6h000p7b87) and would otherwise let a non-fix WI (e.g.
6724
+ // 'implement'/'docs') that merely *mentions* an unrelated PR number in
6725
+ // prose trigger a live-validation dispatch against the wrong PR.
6726
+ const prRef = item._pr || item._prUrl || shared.extractStructuredWorkItemPrRef(item) || null;
6720
6727
  if (!prRef) return;
6721
6728
 
6722
6729
  const codingWiId = item.id;
@@ -1053,7 +1053,19 @@ function waitForFirstHealth(spec, opts) {
1053
1053
  let lastError = null;
1054
1054
  const tick = async () => {
1055
1055
  if (stopped) return;
1056
- const result = await runHealthcheck(spec);
1056
+ let result;
1057
+ try {
1058
+ result = await runHealthcheck(spec);
1059
+ } catch (e) {
1060
+ // A healthcheck implementation throwing instead of resolving
1061
+ // {healthy:false, error} (probe bug, malformed spec, etc.) must not
1062
+ // become an unhandled rejection and must not leave this promise
1063
+ // hanging forever — resolve unhealthy so the caller's
1064
+ // Promise.allSettled/timeout path still progresses.
1065
+ if (stopped) return;
1066
+ stopped = true;
1067
+ return resolve({ healthy: false, error: String((e && e.message) || e), lastCheckAt: Date.now() });
1068
+ }
1057
1069
  if (stopped) return;
1058
1070
  if (result.healthy) {
1059
1071
  stopped = true;
@@ -1302,7 +1314,7 @@ function _rotateManagedLog(logPath, cap) {
1302
1314
  try {
1303
1315
  try { fs.unlinkSync(rotated); }
1304
1316
  catch (e) { if (e && e.code !== 'ENOENT') throw e; }
1305
- fs.renameSync(logPath, rotated);
1317
+ shared._retryFsOp(() => fs.renameSync(logPath, rotated), `rotate managed log ${logPath}`);
1306
1318
  return true;
1307
1319
  } catch (e) {
1308
1320
  log('warn', 'managed-spawn rotate: ' + logPath + ' failed: ' + e.message);
@@ -59,7 +59,15 @@ function savePipeline(pipeline) {
59
59
  function deletePipeline(id) {
60
60
  const filePath = path.join(PIPELINES_DIR, id + '.json');
61
61
  if (!fs.existsSync(filePath)) return false;
62
- fs.unlinkSync(filePath);
62
+ try {
63
+ fs.unlinkSync(filePath);
64
+ } catch (e) {
65
+ // TOCTOU: a concurrent delete of the same id may win the race between
66
+ // existsSync and unlinkSync. Treat ENOENT as "already deleted" instead
67
+ // of throwing, mirroring deleteMeeting's unlink guard (engine/meeting.js).
68
+ if (e.code !== 'ENOENT') throw e;
69
+ return false;
70
+ }
63
71
  return true;
64
72
  }
65
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2329",
3
+ "version": "0.1.2331",
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"