@yemi33/minions 0.1.317 → 0.1.319

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,8 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.317 (2026-04-03)
3
+ ## 0.1.319 (2026-04-03)
4
+
5
+ ### Other
6
+ - refactor: replace magic strings in engine.js with constants
7
+
8
+ ## 0.1.318 (2026-04-03)
4
9
 
5
10
  ### Fixes
11
+ - scan modal shows actual home directory instead of ~
6
12
  - deduplicate PRs in pull-requests.json on write
7
13
  - show reviewer names in dashboard Signed Off By column
8
14
 
@@ -249,7 +249,7 @@ async function openScanProjectsModal() {
249
249
  '<div style="display:flex;flex-direction:column;gap:12px">' +
250
250
  '<div style="display:flex;gap:8px;align-items:flex-end">' +
251
251
  '<label style="flex:1;color:var(--text);font-size:var(--text-md)">Directory to scan' +
252
- '<input id="scan-path" value="' + escHtml((typeof os !== 'undefined' ? os.homedir() : '~') || '~') + '" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
252
+ '<input id="scan-path" value="' + escHtml(window.__MINIONS_HOME || '~') + '" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
253
253
  '</label>' +
254
254
  '<label style="width:60px;color:var(--text);font-size:var(--text-md)">Depth' +
255
255
  '<input id="scan-depth" type="number" value="3" min="1" max="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
package/dashboard.js CHANGED
@@ -94,7 +94,7 @@ function buildDashboardHtml() {
94
94
  return layout
95
95
  .replace('/* __CSS__ */', () => css)
96
96
  .replace('<!-- __PAGES__ -->', () => pageHtml)
97
- .replace('/* __JS__ */', () => jsHtml);
97
+ .replace('/* __JS__ */', () => `window.__MINIONS_HOME = ${JSON.stringify(os.homedir())};\n${jsHtml}`);
98
98
  }
99
99
 
100
100
  let HTML_RAW = buildDashboardHtml();
package/engine.js CHANGED
@@ -396,7 +396,7 @@ function spawnAgent(dispatchItem, config) {
396
396
  log('warn', `Proceeding with recovered worktree after add failure for ${branchName}`);
397
397
  } else {
398
398
  log('error', `Failed to create worktree for ${branchName}: ${err.message}${err.stderr ? '\n' + err.stderr.toString().slice(0, 500) : ''}`);
399
- completeDispatch(id, 'error', 'Worktree creation failed: ' + (err.message || '').slice(0, 200));
399
+ completeDispatch(id, DISPATCH_RESULT.ERROR, 'Worktree creation failed: ' + (err.message || '').slice(0, 200));
400
400
  return null;
401
401
  }
402
402
  }
@@ -610,7 +610,7 @@ function spawnAgent(dispatchItem, config) {
610
610
  resumeProc.on('error', (err) => {
611
611
  log('error', `Steering re-spawn failed for ${agentId}: ${err.message}`);
612
612
  activeProcesses.delete(id);
613
- completeDispatch(id, 'error', `Steering re-spawn error: ${err.message}`);
613
+ completeDispatch(id, DISPATCH_RESULT.ERROR, `Steering re-spawn error: ${err.message}`);
614
614
  });
615
615
 
616
616
  // Don't run completion hooks — agent is still working
@@ -642,7 +642,7 @@ function spawnAgent(dispatchItem, config) {
642
642
  if (code === 78) {
643
643
  const errMsg = stderr.includes('claude-code') ? stderr.trim() : 'Configuration error — Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code';
644
644
  log('error', `Agent ${agentId} (${id}) failed: ${errMsg}`);
645
- completeDispatch(id, 'error', errMsg, '');
645
+ completeDispatch(id, DISPATCH_RESULT.ERROR, errMsg, '');
646
646
  try { fs.unlinkSync(sysPromptPath); } catch { /* cleanup */ }
647
647
  try { fs.unlinkSync(promptPath); } catch { /* cleanup */ }
648
648
  try { fs.unlinkSync(promptPath.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid')); } catch { /* cleanup */ }
@@ -653,7 +653,7 @@ function spawnAgent(dispatchItem, config) {
653
653
  const { resultSummary } = runPostCompletionHooks(dispatchItem, agentId, code, stdout, config);
654
654
 
655
655
  // Move from active to completed in dispatch (single source of truth for agent status)
656
- completeDispatch(id, code === 0 ? 'success' : 'error', '', resultSummary);
656
+ completeDispatch(id, code === 0 ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR, '', resultSummary);
657
657
 
658
658
  // Cleanup temp files (including PID file now that dispatch is complete)
659
659
  try { fs.unlinkSync(sysPromptPath); } catch { /* cleanup */ }
@@ -680,7 +680,7 @@ function spawnAgent(dispatchItem, config) {
680
680
  if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
681
681
  log('error', `Failed to spawn agent ${agentId}: ${err.message}`);
682
682
  activeProcesses.delete(id);
683
- completeDispatch(id, 'error', `Spawn error: ${err.message}`);
683
+ completeDispatch(id, DISPATCH_RESULT.ERROR, `Spawn error: ${err.message}`);
684
684
  });
685
685
 
686
686
  // Safety: if process exits immediately (within 3s), log it
@@ -769,7 +769,7 @@ function areDependenciesMet(item, config) {
769
769
  log('warn', `Dependency ${depId} not found for ${item.id} (plan: ${sourcePlan}) — treating as unmet`);
770
770
  return false;
771
771
  }
772
- if (depItem.status === 'failed') return 'failed';
772
+ if (depItem.status === WI_STATUS.FAILED) return 'failed';
773
773
  if (!PRD_MET_STATUSES.has(depItem.status)) return false; // Pending, dispatched, or retrying — wait (legacy aliases accepted)
774
774
  }
775
775
  return true;
@@ -801,7 +801,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
801
801
  const prLinks = shared.getPrLinks();
802
802
  let reconciled = 0;
803
803
  for (const wi of items) {
804
- if (wi.status !== 'pending' || wi._pr) continue;
804
+ if (wi.status !== WI_STATUS.PENDING || wi._pr) continue;
805
805
  if (onlyIds && !onlyIds.has(wi.id)) continue;
806
806
 
807
807
  let exactPr = allPrs.find(pr => (pr.prdItems || []).includes(wi.id));
@@ -810,7 +810,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
810
810
  if (linkedPrId) exactPr = allPrs.find(pr => pr.id === linkedPrId) || { id: linkedPrId };
811
811
  }
812
812
  if (exactPr) {
813
- wi.status = 'done';
813
+ wi.status = WI_STATUS.DONE;
814
814
  wi._pr = exactPr.id;
815
815
  reconciled++;
816
816
  }
@@ -894,7 +894,7 @@ function autoCleanPrdWorkItems(prdFile, config) {
894
894
  const items = safeJson(wiPath);
895
895
  if (!items) continue;
896
896
  const filtered = items.filter(w => {
897
- if (w.sourcePlan === prdFile && (w.status === 'pending' || w.status === 'failed')) {
897
+ if (w.sourcePlan === prdFile && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.FAILED)) {
898
898
  deletedIds.push(w.id); return false;
899
899
  }
900
900
  return true;
@@ -1011,7 +1011,7 @@ function materializePlansAsWorkItems(config) {
1011
1011
  const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
1012
1012
  const centralItems = safeJson(centralWiPath) || [];
1013
1013
  const alreadyQueued = centralItems.some(w =>
1014
- w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched')
1014
+ w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === plan.source_plan && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.DISPATCHED)
1015
1015
  );
1016
1016
  if (!alreadyQueued) {
1017
1017
  centralItems.push({
@@ -1054,7 +1054,7 @@ function materializePlansAsWorkItems(config) {
1054
1054
  continue; // Skip — waiting for human approval
1055
1055
  }
1056
1056
  }
1057
- if (planStatus === 'paused' || planStatus === 'rejected' || planStatus === 'revision-requested') {
1057
+ if (planStatus === PLAN_STATUS.PAUSED || planStatus === PLAN_STATUS.REJECTED || planStatus === PLAN_STATUS.REVISION_REQUESTED) {
1058
1058
  continue; // Skip — paused or revision requested
1059
1059
  }
1060
1060
  // Stale PRDs: source plan was revised — don't materialize NEW items until user regenerates
@@ -1082,7 +1082,7 @@ function materializePlansAsWorkItems(config) {
1082
1082
  }
1083
1083
  const items = plan.missing_features.filter(f =>
1084
1084
  statusFilter.includes(f.status) ||
1085
- ((f.status === 'in-pr' || f.status === 'done') && f.id && !allExistingWiIds.has(f.id))
1085
+ (DONE_STATUSES.has(f.status) && f.id && !allExistingWiIds.has(f.id))
1086
1086
  );
1087
1087
 
1088
1088
  // Group items by target project (per-item project field overrides plan-level project)
@@ -1175,7 +1175,7 @@ function materializePlansAsWorkItems(config) {
1175
1175
  const currentPrdIds = new Set(plan.missing_features.map(f => f.id));
1176
1176
  let cancelled = 0;
1177
1177
  for (const wi of existingItems) {
1178
- if (wi.status !== 'pending' || wi.sourcePlan !== file) continue;
1178
+ if (wi.status !== WI_STATUS.PENDING || wi.sourcePlan !== file) continue;
1179
1179
  if (!currentPrdIds.has(wi.id)) {
1180
1180
  wi.status = 'cancelled';
1181
1181
  wi.cancelledAt = ts();
@@ -1376,23 +1376,23 @@ function discoverFromWorkItems(config, project) {
1376
1376
  for (const item of items) {
1377
1377
  try {
1378
1378
  // Re-evaluate failed items: if deps have recovered, reset to pending
1379
- if (item.status === 'failed' && item.failReason === 'Dependency failed — cannot proceed') {
1379
+ if (item.status === WI_STATUS.FAILED && item.failReason === 'Dependency failed — cannot proceed') {
1380
1380
  const depStatus = areDependenciesMet(item, config);
1381
1381
  if (depStatus === true) {
1382
- item.status = 'pending';
1382
+ item.status = WI_STATUS.PENDING;
1383
1383
  delete item.failReason;
1384
1384
  log('info', `Recovered ${item.id} from dependency failure — deps now met`);
1385
1385
  needsWrite = true;
1386
1386
  }
1387
1387
  }
1388
1388
 
1389
- if (item.status !== 'queued' && item.status !== 'pending') continue;
1389
+ if (item.status !== WI_STATUS.QUEUED && item.status !== WI_STATUS.PENDING) continue;
1390
1390
 
1391
1391
  // Dependency gate: skip items whose depends_on are not yet met; propagate failure
1392
1392
  if (item.depends_on && item.depends_on.length > 0) {
1393
1393
  const depStatus = areDependenciesMet(item, config);
1394
1394
  if (depStatus === 'failed') {
1395
- item.status = 'failed';
1395
+ item.status = WI_STATUS.FAILED;
1396
1396
  item.failReason = 'Dependency failed — cannot proceed';
1397
1397
  delete item._pendingReason;
1398
1398
  log('warn', `Marking ${item.id} as failed: dependency failed (plan: ${item.sourcePlan})`);
@@ -1422,7 +1422,7 @@ function discoverFromWorkItems(config, project) {
1422
1422
  safeWrite(projectWorkItemsPath(project), items);
1423
1423
  }
1424
1424
  if (isAlreadyDispatched(key)) {
1425
- if (item.status === 'pending') { item.status = 'dispatched'; needsWrite = true; }
1425
+ if (item.status === WI_STATUS.PENDING) { item.status = WI_STATUS.DISPATCHED; needsWrite = true; }
1426
1426
  if (item._pendingReason !== 'already_dispatched') { item._pendingReason = 'already_dispatched'; needsWrite = true; }
1427
1427
  skipped.gated++; continue;
1428
1428
  }
@@ -1432,12 +1432,12 @@ function discoverFromWorkItems(config, project) {
1432
1432
  }
1433
1433
 
1434
1434
  let workType = item.type || 'implement';
1435
- if (workType === 'implement' && (item.complexity === 'large' || item.estimated_complexity === 'large')) {
1436
- workType = 'implement:large';
1435
+ if (workType === WORK_TYPE.IMPLEMENT && (item.complexity === 'large' || item.estimated_complexity === 'large')) {
1436
+ workType = WORK_TYPE.IMPLEMENT_LARGE;
1437
1437
  }
1438
1438
  // Auto-decompose large items before implementation
1439
1439
  if (workType === 'implement:large' && !item._decomposed && !item._decomposing && config.engine?.autoDecompose !== false) {
1440
- workType = 'decompose';
1440
+ workType = WORK_TYPE.DECOMPOSE;
1441
1441
  item._decomposing = true;
1442
1442
  needsWrite = true;
1443
1443
  }
@@ -1522,7 +1522,7 @@ function discoverFromWorkItems(config, project) {
1522
1522
  } catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
1523
1523
 
1524
1524
  // Inject ask-specific variables for the ask playbook
1525
- if (workType === 'ask') {
1525
+ if (workType === WORK_TYPE.ASK) {
1526
1526
  vars.question = item.title + (item.description ? '\n\n' + item.description : '');
1527
1527
  vars.task_id = item.id;
1528
1528
  vars.notes_content = '';
@@ -1537,7 +1537,7 @@ function discoverFromWorkItems(config, project) {
1537
1537
  }
1538
1538
 
1539
1539
  const playbookName = selectPlaybook(workType, item);
1540
- if (playbookName === 'work-item' && workType === 'review') {
1540
+ if (playbookName === 'work-item' && workType === WORK_TYPE.REVIEW) {
1541
1541
  log('info', `Work item ${item.id} is type "review" but has no PR — using work-item playbook`);
1542
1542
  }
1543
1543
  const prompt = item.prompt || renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars) || item.description;
@@ -1547,7 +1547,7 @@ function discoverFromWorkItems(config, project) {
1547
1547
  }
1548
1548
 
1549
1549
  // Mark item as dispatched BEFORE adding to newWork (prevents race on next tick)
1550
- item.status = 'dispatched';
1550
+ item.status = WI_STATUS.DISPATCHED;
1551
1551
  item.dispatched_at = ts();
1552
1552
  item.dispatched_to = agentId;
1553
1553
  delete item._pendingReason;
@@ -1772,7 +1772,7 @@ function discoverCentralWorkItems(config) {
1772
1772
 
1773
1773
  for (const item of items) {
1774
1774
  try {
1775
- if (item.status !== 'queued' && item.status !== 'pending') continue;
1775
+ if (item.status !== WI_STATUS.QUEUED && item.status !== WI_STATUS.PENDING) continue;
1776
1776
 
1777
1777
  const key = `central-work-${item.id}`;
1778
1778
  if (isAlreadyDispatched(key) || isOnCooldown(key, 0)) continue;
@@ -1824,7 +1824,7 @@ function discoverCentralWorkItems(config) {
1824
1824
  const fanAc = normalizeAc(item.acceptanceCriteria).map(c => '- [ ] ' + c).join('\n');
1825
1825
  vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
1826
1826
 
1827
- if (workType === 'ask') {
1827
+ if (workType === WORK_TYPE.ASK) {
1828
1828
  vars.question = item.title + (item.description ? '\n\n' + item.description : '');
1829
1829
  vars.task_id = item.id;
1830
1830
  vars.notes_content = '';
@@ -1858,7 +1858,7 @@ function discoverCentralWorkItems(config) {
1858
1858
  });
1859
1859
  }
1860
1860
 
1861
- item.status = 'dispatched';
1861
+ item.status = WI_STATUS.DISPATCHED;
1862
1862
  item.dispatched_at = ts();
1863
1863
  item.dispatched_to = idleAgents.map(a => a.id).join(', ');
1864
1864
  item.scope = 'fan-out';
@@ -1934,7 +1934,7 @@ function discoverCentralWorkItems(config) {
1934
1934
  } catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
1935
1935
 
1936
1936
  // Inject plan-specific variables for the plan playbook
1937
- if (workType === 'plan') {
1937
+ if (workType === WORK_TYPE.PLAN) {
1938
1938
  // Ensure plans directory exists before agent tries to write
1939
1939
  if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
1940
1940
  const planFileName = `plan-${item.id.toLowerCase()}-${dateStamp()}.md`;
@@ -1949,7 +1949,7 @@ function discoverCentralWorkItems(config) {
1949
1949
  }
1950
1950
 
1951
1951
  // Inject plan-to-prd variables — read the plan file content for the playbook
1952
- if (workType === 'plan-to-prd' && item.planFile) {
1952
+ if (workType === WORK_TYPE.PLAN_TO_PRD && item.planFile) {
1953
1953
  if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
1954
1954
  if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
1955
1955
  const planPath = path.join(PLANS_DIR, item.planFile);
@@ -1968,7 +1968,7 @@ function discoverCentralWorkItems(config) {
1968
1968
  }
1969
1969
 
1970
1970
  // Inject ask-specific variables for the ask playbook
1971
- if (workType === 'ask') {
1971
+ if (workType === WORK_TYPE.ASK) {
1972
1972
  vars.question = item.title + (item.description ? '\n\n' + item.description : '');
1973
1973
  vars.task_id = item.id;
1974
1974
  vars.notes_content = '';
@@ -1986,7 +1986,7 @@ function discoverCentralWorkItems(config) {
1986
1986
  const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
1987
1987
  if (!prompt) {
1988
1988
  log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
1989
- item.status = 'pending';
1989
+ item.status = WI_STATUS.PENDING;
1990
1990
  continue;
1991
1991
  }
1992
1992
 
@@ -2000,7 +2000,7 @@ function discoverCentralWorkItems(config) {
2000
2000
  meta: { dispatchKey: key, source: 'central-work-item', item, planFileName: item.planFile || item._planFileName || null, branch: item.branch || item.featureBranch || `work/${item.id}` }
2001
2001
  });
2002
2002
 
2003
- item.status = 'dispatched';
2003
+ item.status = WI_STATUS.DISPATCHED;
2004
2004
  item.dispatched_at = ts();
2005
2005
  item.dispatched_to = agentId;
2006
2006
  setCooldown(key);
@@ -2032,9 +2032,9 @@ function discoverWork(config) {
2032
2032
 
2033
2033
  // Source 1: Pull Requests → fixes, reviews, build-test
2034
2034
  const prWork = discoverFromPrs(config, project);
2035
- allFixes.push(...prWork.filter(w => w.type === 'fix'));
2036
- allReviews.push(...prWork.filter(w => w.type === 'review'));
2037
- allWorkItems.push(...prWork.filter(w => w.type === 'test'));
2035
+ allFixes.push(...prWork.filter(w => w.type === WORK_TYPE.FIX));
2036
+ allReviews.push(...prWork.filter(w => w.type === WORK_TYPE.REVIEW));
2037
+ allWorkItems.push(...prWork.filter(w => w.type === WORK_TYPE.TEST));
2038
2038
 
2039
2039
  // Side-effect: specs → work items (picked up below)
2040
2040
  materializeSpecsAsWorkItems(config, project);
@@ -2059,14 +2059,14 @@ function discoverWork(config) {
2059
2059
  const items = safeJson(centralPath) || [];
2060
2060
  let added = 0;
2061
2061
  for (const item of scheduledWork) {
2062
- if (item.type === 'meeting') {
2062
+ if (item.type === WORK_TYPE.MEETING) {
2063
2063
  // Create a real multi-agent meeting instead of a single-agent work item
2064
2064
  const sched = (config.schedules || []).find(s => s.id === item._scheduleId);
2065
2065
  const participants = (sched && sched.participants) || [];
2066
2066
  const meeting = createMeeting({ title: item.title, agenda: item.description, participants });
2067
2067
  log('info', `Scheduled meeting created: ${item._scheduleId} → ${meeting.id} (${participants.length} participants)`);
2068
2068
  } else {
2069
- if (!items.some(i => i._scheduleId === item._scheduleId && i.status !== 'done' && i.status !== 'failed')) {
2069
+ if (!items.some(i => i._scheduleId === item._scheduleId && i.status !== WI_STATUS.DONE && i.status !== WI_STATUS.FAILED)) {
2070
2070
  items.push(item);
2071
2071
  added++;
2072
2072
  log('info', `Scheduled task fired: ${item._scheduleId} → ${item.title}`);
@@ -2259,9 +2259,9 @@ async function tickInner() {
2259
2259
  const wiPath = projectWorkItemsPath(project);
2260
2260
  const items = safeJson(wiPath) || [];
2261
2261
  let changed = false;
2262
- const failedIds = new Set(items.filter(w => w.status === 'failed').map(w => w.id));
2262
+ const failedIds = new Set(items.filter(w => w.status === WI_STATUS.FAILED).map(w => w.id));
2263
2263
  const pendingWithBlockedDeps = items.filter(w =>
2264
- w.status === 'pending' && (w.depends_on || []).some(d => failedIds.has(d))
2264
+ w.status === WI_STATUS.PENDING && (w.depends_on || []).some(d => failedIds.has(d))
2265
2265
  );
2266
2266
 
2267
2267
  if (pendingWithBlockedDeps.length > 0) {
@@ -2269,11 +2269,11 @@ async function tickInner() {
2269
2269
  for (const item of items) {
2270
2270
  if (item.status !== 'failed') continue;
2271
2271
  // Only retry if something depends on this item
2272
- const isBlocking = items.some(w => w.status === 'pending' && (w.depends_on || []).includes(item.id));
2272
+ const isBlocking = items.some(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(item.id));
2273
2273
  if (!isBlocking) continue;
2274
2274
 
2275
2275
  log('info', `Stall recovery: auto-retrying ${item.id} (blocking ${pendingWithBlockedDeps.filter(w => (w.depends_on || []).includes(item.id)).length} items)`);
2276
- item.status = 'pending';
2276
+ item.status = WI_STATUS.PENDING;
2277
2277
  item._retryCount = 0;
2278
2278
  delete item.failReason;
2279
2279
  delete item.failedAt;
@@ -2303,9 +2303,9 @@ async function tickInner() {
2303
2303
 
2304
2304
  // Un-fail dependent items that were cascade-failed
2305
2305
  if (changed) {
2306
- const retriedIds = new Set(items.filter(w => w.status === 'pending' && w._retryCount === 0).map(w => w.id));
2306
+ const retriedIds = new Set(items.filter(w => w.status === WI_STATUS.PENDING && w._retryCount === 0).map(w => w.id));
2307
2307
  for (const dep of items) {
2308
- if (dep.status === 'failed' && dep.failReason === 'Dependency failed — cannot proceed') {
2308
+ if (dep.status === WI_STATUS.FAILED && dep.failReason === 'Dependency failed — cannot proceed') {
2309
2309
  const blockers = (dep.depends_on || []).filter(d => retriedIds.has(d));
2310
2310
  if (blockers.length > 0) {
2311
2311
  log('info', `Stall recovery: un-failing ${dep.id} (blocker ${blockers.join(',')} retried)`);
@@ -2418,7 +2418,7 @@ async function tickInner() {
2418
2418
  if (wiPath) {
2419
2419
  const items = safeJson(wiPath) || [];
2420
2420
  const wi = items.find(i => i.id === item.meta.item.id);
2421
- if (wi && wi.status === 'dispatched') {
2421
+ if (wi && wi.status === WI_STATUS.DISPATCHED) {
2422
2422
  // completeDispatch didn't update the work item — re-queue manually
2423
2423
  wi.status = 'pending';
2424
2424
  wi._retryCount = (wi._retryCount || 0) + 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.317",
3
+ "version": "0.1.319",
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"