@yemi33/minions 0.1.315 → 0.1.317

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,11 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.315 (2026-04-03)
3
+ ## 0.1.317 (2026-04-03)
4
4
 
5
5
  ### Fixes
6
+ - deduplicate PRs in pull-requests.json on write
6
7
  - show reviewer names in dashboard Signed Off By column
7
8
 
8
9
  ### Other
10
+ - refactor: use constants in lifecycle.js and timeout.js
11
+ - refactor: extract status/type/result constants to shared.js
9
12
  - cleanup: remove evaluate.md (re-created by agents), fix stale references
10
13
  - perf: CC message handling — debounce localStorage, cap array, batch scroll
11
14
 
package/engine/cleanup.js CHANGED
@@ -433,7 +433,7 @@ function runCleanup(config, verbose = false) {
433
433
 
434
434
  // 6. Migrate legacy work-item statuses to canonical values
435
435
  // in-pr, implemented, complete → done (one-time correction per item)
436
- const LEGACY_DONE_STATUSES = new Set(['in-pr', 'implemented', 'complete']);
436
+ const LEGACY_DONE_STATUSES = shared.DONE_STATUSES;
437
437
  for (const project of projects) {
438
438
  try {
439
439
  const wiPath = projectWorkItemsPath(project);
@@ -10,7 +10,8 @@ const queries = require('./queries');
10
10
  const { setCooldownFailure } = require('./cooldown');
11
11
 
12
12
  const { safeJson, safeWrite, safeReadDir, mutateJsonFileLocked,
13
- getProjects, projectWorkItemsPath, log, ts, dateStamp } = shared;
13
+ getProjects, projectWorkItemsPath, log, ts, dateStamp,
14
+ WI_STATUS, DISPATCH_RESULT, ENGINE_DEFAULTS } = shared;
14
15
  const { getConfig, getDispatch, DISPATCH_PATH, INBOX_DIR } = queries;
15
16
 
16
17
  const MINIONS_DIR = shared.MINIONS_DIR;
@@ -65,7 +66,7 @@ function isRetryableFailureReason(reason = '') {
65
66
 
66
67
  // ─── Complete Dispatch ───────────────────────────────────────────────────────
67
68
 
68
- function completeDispatch(id, result = 'success', reason = '', resultSummary = '', opts = {}) {
69
+ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', resultSummary = '', opts = {}) {
69
70
  const { processWorkItemFailure = true } = opts;
70
71
  let item = null;
71
72
 
@@ -98,9 +99,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
98
99
 
99
100
  // Update source work item status on failure + auto-retry with backoff
100
101
  const retryableFailure = isRetryableFailureReason(reason);
101
- if (result === 'error' && item.meta?.dispatchKey && retryableFailure) setCooldownFailure(item.meta.dispatchKey);
102
+ if (result === DISPATCH_RESULT.ERROR && item.meta?.dispatchKey && retryableFailure) setCooldownFailure(item.meta.dispatchKey);
102
103
 
103
- if (processWorkItemFailure && result === 'error' && item.meta?.item?.id) {
104
+ if (processWorkItemFailure && result === DISPATCH_RESULT.ERROR && item.meta?.item?.id) {
104
105
  let retries = (item.meta.item._retryCount || 0);
105
106
  try {
106
107
  const wiPath = item.meta.source === 'central-work-item' || item.meta.source === 'central-work-item-fanout'
@@ -112,8 +113,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
112
113
  if (wi) retries = wi._retryCount || 0;
113
114
  }
114
115
  } catch (e) { log('warn', 'read retry count: ' + e.message); }
115
- if (retryableFailure && retries < 3) {
116
- log('info', `Dispatch error for ${item.meta.item.id} — auto-retry ${retries + 1}/3`);
116
+ const maxRetries = ENGINE_DEFAULTS.maxRetries;
117
+ if (retryableFailure && retries < maxRetries) {
118
+ log('info', `Dispatch error for ${item.meta.item.id} — auto-retry ${retries + 1}/${maxRetries}`);
117
119
  lifecycle().updateWorkItemStatus(item.meta, 'pending', '');
118
120
  // Remove this dispatch key from completed so dedupe doesn't block immediate redispatch.
119
121
  if (item.meta?.dispatchKey) {
@@ -132,9 +134,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
132
134
  if (wiPath) {
133
135
  const items = safeJson(wiPath) || [];
134
136
  const wi = items.find(i => i.id === item.meta.item.id);
135
- if (wi && wi.status !== 'paused') {
137
+ if (wi && wi.status !== WI_STATUS.PAUSED) {
136
138
  wi._retryCount = retries + 1;
137
- wi.status = 'pending';
139
+ wi.status = WI_STATUS.PENDING;
138
140
  wi._lastRetryReason = reason || '';
139
141
  wi._lastRetryAt = ts();
140
142
  delete wi.failReason;
@@ -148,7 +150,7 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
148
150
  } else {
149
151
  const finalReason = !retryableFailure
150
152
  ? `Non-retryable failure: ${reason || 'Unknown error'}`
151
- : (reason || 'Failed after 3 retries');
153
+ : (reason || `Failed after ${maxRetries} retries`);
152
154
  lifecycle().updateWorkItemStatus(item.meta, 'failed', finalReason);
153
155
  // Alert: find items blocked by this failure and write inbox note
154
156
  try {
@@ -157,11 +159,11 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
157
159
  const blockedItems = [];
158
160
  for (const p of getProjects(config)) {
159
161
  const items = safeJson(projectWorkItemsPath(p)) || [];
160
- items.filter(w => w.status === 'pending' && (w.depends_on || []).includes(failedId))
162
+ items.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
161
163
  .forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
162
164
  }
163
165
  const centralItems = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
164
- centralItems.filter(w => w.status === 'pending' && (w.depends_on || []).includes(failedId))
166
+ centralItems.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
165
167
  .forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
166
168
 
167
169
  writeInboxAlert(`failed-${failedId}`,
@@ -8,7 +8,8 @@ const path = require('path');
8
8
  const os = require('os');
9
9
  const shared = require('./shared');
10
10
  const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, execSilent, projectPrPath, getPrLinks, addPrLink,
11
- log, ts, dateStamp } = shared;
11
+ log, ts, dateStamp, WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT,
12
+ ENGINE_DEFAULTS } = shared;
12
13
  const { trackEngineUsage } = require('./llm');
13
14
  const queries = require('./queries');
14
15
  const { getConfig, getInboxFiles, getNotes, getPrs, getDispatch,
@@ -57,7 +58,7 @@ function checkPlanCompletion(meta, config) {
57
58
  const unmaterialized = [...planFeatureIds].filter(id => {
58
59
  if (workItemById[id]) return false;
59
60
  const prdItem = (plan.missing_features || []).find(f => f.id === id);
60
- return !(prdItem && (prdItem.status === 'done' || prdItem.status === 'in-pr'));
61
+ return !(prdItem && DONE_STATUSES.has(prdItem.status));
61
62
  });
62
63
  if (unmaterialized.length > 0) {
63
64
  log('info', `Plan ${planFile}: ${unmaterialized.length}/${planFeatureIds.size} feature(s) not yet materialized as work items: ${unmaterialized.join(', ')}`);
@@ -67,17 +68,17 @@ function checkPlanCompletion(meta, config) {
67
68
  // Check 2: every feature's work item must be done (or PRD item marked done externally)
68
69
  const notDone = [...planFeatureIds].filter(id => {
69
70
  const w = workItemById[id];
70
- if (w && (w.status === 'done' || w.status === 'in-pr')) return false; // in-pr accepted for backward compat
71
+ if (w && DONE_STATUSES.has(w.status)) return false;
71
72
  const prdItem = (plan.missing_features || []).find(f => f.id === id);
72
- return !(prdItem && (prdItem.status === 'done' || prdItem.status === 'in-pr'));
73
+ return !(prdItem && DONE_STATUSES.has(prdItem.status));
73
74
  });
74
75
  if (notDone.length > 0) {
75
76
  log('info', `Plan ${planFile}: waiting for done on ${notDone.length}/${planFeatureIds.size} item(s): ${notDone.join(', ')}`);
76
77
  return;
77
78
  }
78
79
 
79
- const doneItems = planItems.filter(w => w.status === 'done' || w.status === 'in-pr');
80
- const failedItems = planItems.filter(w => w.status === 'failed');
80
+ const doneItems = planItems.filter(w => DONE_STATUSES.has(w.status));
81
+ const failedItems = planItems.filter(w => w.status === WI_STATUS.FAILED);
81
82
 
82
83
  // 1. Mark plan as completed
83
84
  plan.status = 'completed';
@@ -490,20 +491,20 @@ function updateWorkItemStatus(meta, status, reason) {
490
491
  target.agentResults[agent] = { status, completedAt: ts(), reason: reason || undefined };
491
492
 
492
493
  const results = Object.values(target.agentResults);
493
- const anySuccess = results.some(r => r.status === 'done');
494
+ const anySuccess = results.some(r => r.status === WI_STATUS.DONE);
494
495
  const allDone = Array.isArray(target.fanOutAgents) && target.fanOutAgents.length > 0 ? results.length >= target.fanOutAgents.length : false;
495
496
  const dispatchAge = target.dispatched_at ? Date.now() - new Date(target.dispatched_at).getTime() : 0;
496
497
  const timedOut = !allDone && dispatchAge > 6 * 60 * 60 * 1000 && results.length > 0;
497
498
 
498
499
  if (anySuccess) {
499
- target.status = 'done';
500
+ target.status = WI_STATUS.DONE;
500
501
  delete target.failReason;
501
502
  delete target.failedAt;
502
503
  target.completedAgents = Object.entries(target.agentResults)
503
- .filter(([, r]) => r.status === 'done')
504
+ .filter(([, r]) => r.status === WI_STATUS.DONE)
504
505
  .map(([a]) => a);
505
506
  } else if (allDone || timedOut) {
506
- target.status = 'failed';
507
+ target.status = WI_STATUS.FAILED;
507
508
  target.failReason = timedOut
508
509
  ? `Fan-out timed out: ${results.length}/${(target.fanOutAgents || []).length} agents reported (all failed)`
509
510
  : 'All fan-out agents failed';
@@ -511,11 +512,11 @@ function updateWorkItemStatus(meta, status, reason) {
511
512
  }
512
513
  } else {
513
514
  target.status = status;
514
- if (status === 'done') {
515
+ if (status === WI_STATUS.DONE) {
515
516
  delete target.failReason;
516
517
  delete target.failedAt;
517
518
  target.completedAt = ts();
518
- } else if (status === 'failed') {
519
+ } else if (status === WI_STATUS.FAILED) {
519
520
  if (reason) target.failReason = reason;
520
521
  target.failedAt = ts();
521
522
  }
@@ -643,6 +644,9 @@ function syncPrsFromOutput(output, agentId, meta, config) {
643
644
  for (const [name, { prPath, prIds }] of targetPrIds) {
644
645
  mutateJsonFileLocked(prPath, (prs) => {
645
646
  if (!Array.isArray(prs)) prs = [];
647
+ // Deduplicate any existing entries with same id (case-insensitive agent name race)
648
+ const seen = new Set();
649
+ prs = prs.filter(p => { const k = String(p.id); if (seen.has(k)) return false; seen.add(k); return true; });
646
650
  for (const { prId, fullId } of prIds) {
647
651
  if (prs.some(p => p.id === fullId || String(p.id) === String(prId))) continue;
648
652
 
@@ -981,7 +985,7 @@ function updateMetrics(agentId, dispatchItem, result, taskUsage, prsCreatedCount
981
985
  m.lastTask = dispatchItem.task;
982
986
  m.lastCompleted = ts();
983
987
  if (model) m.model = model;
984
- if (result === 'success') {
988
+ if (result === DISPATCH_RESULT.SUCCESS) {
985
989
  m.tasksCompleted++;
986
990
  if (prsCreatedCount > 0) m.prsCreated = (m.prsCreated || 0) + prsCreatedCount;
987
991
  if (dispatchItem.type === 'review') m.reviewsDone++;
@@ -1105,7 +1109,7 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1105
1109
  const type = dispatchItem.type;
1106
1110
  const meta = dispatchItem.meta;
1107
1111
  const isSuccess = code === 0;
1108
- const result = isSuccess ? 'success' : 'error';
1112
+ const result = isSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR;
1109
1113
  const { resultSummary, taskUsage, sessionId, model } = parseAgentOutput(stdout);
1110
1114
 
1111
1115
  // Save session for potential resume on next dispatch
@@ -1141,9 +1145,10 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1141
1145
  }
1142
1146
  } catch { /* optional */ }
1143
1147
 
1144
- if (retries < 3) {
1145
- log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/3`);
1146
- updateWorkItemStatus(meta, 'pending', '');
1148
+ const maxRetries = ENGINE_DEFAULTS.maxRetries;
1149
+ if (retries < maxRetries) {
1150
+ log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/${maxRetries}`);
1151
+ updateWorkItemStatus(meta, WI_STATUS.PENDING, '');
1147
1152
  try {
1148
1153
  const wiPath = meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout'
1149
1154
  ? path.join(MINIONS_DIR, 'work-items.json')
@@ -1152,14 +1157,14 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1152
1157
  const items = safeJson(wiPath) || [];
1153
1158
  const wi = items.find(i => i.id === meta.item.id);
1154
1159
  if (wi) {
1155
- wi._retryCount = retries + 1; wi.status = 'pending'; delete wi.dispatched_at; delete wi.dispatched_to;
1156
- if (type === 'decompose') delete wi._decomposing; // clear so item can retry decomposition
1160
+ wi._retryCount = retries + 1; wi.status = WI_STATUS.PENDING; delete wi.dispatched_at; delete wi.dispatched_to;
1161
+ if (type === WORK_TYPE.DECOMPOSE) delete wi._decomposing;
1157
1162
  shared.safeWrite(wiPath, items);
1158
1163
  }
1159
1164
  }
1160
1165
  } catch (err) { log('warn', `Retry update: ${err.message}`); }
1161
1166
  } else {
1162
- updateWorkItemStatus(meta, 'failed', 'Agent failed (3 retries exhausted)');
1167
+ updateWorkItemStatus(meta, WI_STATUS.FAILED, `Agent failed (${maxRetries} retries exhausted)`);
1163
1168
  }
1164
1169
  // Clear _decomposing flag on failure so item doesn't get permanently stuck
1165
1170
  if (type === 'decompose') {
@@ -1258,15 +1263,16 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1258
1263
  wi.noPr = true;
1259
1264
  wi.failReason = 'Completed without creating a pull request';
1260
1265
  const retries = wi._retryCount || 0;
1261
- if (retries < 3) {
1262
- wi.status = 'pending';
1266
+ const maxR = ENGINE_DEFAULTS.maxRetries;
1267
+ if (retries < maxR) {
1268
+ wi.status = WI_STATUS.PENDING;
1263
1269
  wi._retryCount = retries + 1;
1264
1270
  delete wi.dispatched_at;
1265
1271
  delete wi.dispatched_to;
1266
- e.log('info', `Auto-retry ${retries + 1}/3 for ${meta.item.id} (no PR created)`);
1272
+ e.log('info', `Auto-retry ${retries + 1}/${maxR} for ${meta.item.id} (no PR created)`);
1267
1273
  } else {
1268
- wi.status = 'failed';
1269
- e.log('warn', `${meta.item.id} failed after 3 retries — no PR created`);
1274
+ wi.status = WI_STATUS.FAILED;
1275
+ e.log('warn', `${meta.item.id} failed after ${maxR} retries — no PR created`);
1270
1276
  }
1271
1277
  shared.safeWrite(wiPath, items);
1272
1278
  }
package/engine/shared.js CHANGED
@@ -386,8 +386,29 @@ const ENGINE_DEFAULTS = {
386
386
  evalLoop: true, // enable review→fix loop after implementation completes
387
387
  evalMaxIterations: 3, // max review→fix cycles before escalating to human
388
388
  evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
389
+ maxRetries: 3, // max dispatch retries before marking work item as failed
389
390
  };
390
391
 
392
+ // ─── Status & Type Constants ─────────────────────────────────────────────────
393
+
394
+ const WI_STATUS = {
395
+ PENDING: 'pending', DISPATCHED: 'dispatched', DONE: 'done', FAILED: 'failed',
396
+ PAUSED: 'paused', QUEUED: 'queued', NEEDS_REVIEW: 'needs-human-review', DECOMPOSED: 'decomposed',
397
+ };
398
+ const DONE_STATUSES = new Set([WI_STATUS.DONE, 'in-pr', 'implemented', 'complete']); // includes legacy aliases
399
+ const WORK_TYPE = {
400
+ IMPLEMENT: 'implement', IMPLEMENT_LARGE: 'implement:large', FIX: 'fix', REVIEW: 'review',
401
+ VERIFY: 'verify', PLAN: 'plan', PLAN_TO_PRD: 'plan-to-prd', DECOMPOSE: 'decompose',
402
+ MEETING: 'meeting', EXPLORE: 'explore', ASK: 'ask', TEST: 'test', DOCS: 'docs',
403
+ };
404
+ const PLAN_STATUS = {
405
+ ACTIVE: 'active', AWAITING_APPROVAL: 'awaiting-approval', APPROVED: 'approved',
406
+ PAUSED: 'paused', REJECTED: 'rejected', COMPLETED: 'completed',
407
+ REVISION_REQUESTED: 'revision-requested',
408
+ };
409
+ const PR_STATUS = { ACTIVE: 'active', MERGED: 'merged', ABANDONED: 'abandoned', CLOSED: 'closed' };
410
+ const DISPATCH_RESULT = { SUCCESS: 'success', ERROR: 'error', TIMEOUT: 'timeout' };
411
+
391
412
  const DEFAULT_AGENTS = {
392
413
  ripley: { name: 'Ripley', emoji: '\u{1F3D7}\uFE0F', role: 'Lead / Explorer', skills: ['architecture', 'codebase-exploration', 'design-review'] },
393
414
  dallas: { name: 'Dallas', emoji: '\u{1F527}', role: 'Engineer', skills: ['implementation', 'typescript', 'docker', 'testing'] },
@@ -617,6 +638,7 @@ module.exports = {
617
638
  KB_CATEGORIES,
618
639
  classifyInboxItem,
619
640
  ENGINE_DEFAULTS,
641
+ WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT,
620
642
  DEFAULT_AGENTS,
621
643
  DEFAULT_CLAUDE,
622
644
  getProjects,
package/engine/timeout.js CHANGED
@@ -8,7 +8,8 @@ const path = require('path');
8
8
  const shared = require('./shared');
9
9
  const queries = require('./queries');
10
10
 
11
- const { safeRead, safeWrite, safeJson, getProjects, projectWorkItemsPath, log, ts, ENGINE_DEFAULTS: DEFAULTS } = shared;
11
+ const { safeRead, safeWrite, safeJson, getProjects, projectWorkItemsPath, log, ts,
12
+ ENGINE_DEFAULTS: DEFAULTS, WI_STATUS, DISPATCH_RESULT } = shared;
12
13
  const { getDispatch, getAgentStatus } = queries;
13
14
  const AGENTS_DIR = queries.AGENTS_DIR;
14
15
  const MINIONS_DIR = shared.MINIONS_DIR;
@@ -140,7 +141,7 @@ function checkTimeouts(config) {
140
141
  safeWrite(outputLogPath, `# Output for dispatch ${item.id}\n# Exit code: ${isSuccess ? 0 : 1}\n# Completed: ${ts()}\n# Detected via output scan\n\n## Result\n${text || '(no text)'}\n`);
141
142
  } catch (e) { log('warn', 'parse output result: ' + e.message); }
142
143
 
143
- completeDispatch(item.id, isSuccess ? 'success' : 'error', 'Completed (detected from output)');
144
+ completeDispatch(item.id, isSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR, 'Completed (detected from output)');
144
145
 
145
146
  // Run post-completion hooks via shared helper
146
147
  runPostCompletionHooks(item, item.agent, isSuccess ? 0 : 1, liveLog, config);
@@ -215,7 +216,7 @@ function checkTimeouts(config) {
215
216
 
216
217
  // Clean up dead items
217
218
  for (const { item, reason } of deadItems) {
218
- completeDispatch(item.id, 'error', reason);
219
+ completeDispatch(item.id, DISPATCH_RESULT.ERROR, reason);
219
220
  }
220
221
 
221
222
  // Agent status is now derived from dispatch.json at read time (getAgentStatus).
@@ -232,7 +233,7 @@ function checkTimeouts(config) {
232
233
  if (!items || !Array.isArray(items)) continue;
233
234
  let changed = false;
234
235
  for (const item of items) {
235
- if (item.status !== 'dispatched') continue;
236
+ if (item.status !== WI_STATUS.DISPATCHED) continue;
236
237
  // Check if any active dispatch references this item
237
238
  // Dispatch keys include project name: work-{project}-{id} or central-work-{id}
238
239
  const projectNames = getProjects(config).map(p => p.name);
@@ -244,19 +245,20 @@ function checkTimeouts(config) {
244
245
  (dispatchData.active || []).some(d => d.meta?.item?.id === item.id);
245
246
  if (!isActive) {
246
247
  // Don't revive items that were explicitly failed for non-retryable reasons
247
- if (item.status === 'failed' && item.failReason && !item.failReason.includes('Agent died')) continue;
248
+ if (item.status === WI_STATUS.FAILED && item.failReason && !item.failReason.includes('Agent died')) continue;
248
249
  const retries = (item._retryCount || 0);
249
- if (retries < 3) {
250
- log('info', `Reconcile: work item ${item.id} agent died — auto-retry ${retries + 1}/3`);
251
- item.status = 'pending';
250
+ const maxRetries = DEFAULTS.maxRetries;
251
+ if (retries < maxRetries) {
252
+ log('info', `Reconcile: work item ${item.id} agent died — auto-retry ${retries + 1}/${maxRetries}`);
253
+ item.status = WI_STATUS.PENDING;
252
254
  item._retryCount = retries + 1;
253
255
  delete item.dispatched_at;
254
256
  delete item.dispatched_to;
255
257
  delete item._pendingReason;
256
258
  } else {
257
259
  log('warn', `Reconcile: work item ${item.id} failed after ${retries} retries — marking as failed`);
258
- item.status = 'failed';
259
- item.failReason = 'Agent died or was killed (3 retries exhausted)';
260
+ item.status = WI_STATUS.FAILED;
261
+ item.failReason = `Agent died or was killed (${maxRetries} retries exhausted)`;
260
262
  item.failedAt = ts();
261
263
  delete item._pendingReason;
262
264
  }
package/engine.js CHANGED
@@ -24,7 +24,8 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./engine/shared');
27
- const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS } = shared;
27
+ const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS,
28
+ WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, DISPATCH_RESULT } = shared;
28
29
  const queries = require('./engine/queries');
29
30
 
30
31
  // ─── Paths ──────────────────────────────────────────────────────────────────
@@ -754,7 +755,7 @@ function areDependenciesMet(item, config) {
754
755
  } catch (e) { log('warn', 'read project work items for deps: ' + e.message); }
755
756
  }
756
757
  // PRD item statuses that count as "done" for dep resolution
757
- const PRD_MET_STATUSES = new Set(['done', 'in-pr', 'implemented', 'complete']);
758
+ const PRD_MET_STATUSES = DONE_STATUSES;
758
759
 
759
760
  for (const depId of deps) {
760
761
  const depItem = allWorkItems.find(w => w.id === depId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.315",
3
+ "version": "0.1.317",
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"