@yemi33/minions 0.1.803 → 0.1.804

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.803 (2026-04-10)
3
+ ## 0.1.804 (2026-04-10)
4
4
 
5
5
  ### Features
6
6
  - configurable ignored comment authors — auto-filtered, never trigger fixes
@@ -9,6 +9,7 @@
9
9
  - cap review→fix cycles at evalMaxIterations (default 3)
10
10
 
11
11
  ### Fixes
12
+ - allow PRD item retry when work item is missing (closes #781) (#807)
12
13
  - handle force-pushed dep branches on retry (closes #738) (#806)
13
14
  - project-scoped skill work items instruct wrong file path format (closes #790) (#804)
14
15
  - move ignoredAuthors construction outside inner comment loop (ADO)
@@ -171,9 +171,10 @@ function renderPrdProgress(prog) {
171
171
  const agentLabel = wiAgent ? '<span style="font-size:9px;color:var(--muted)" title="' + escHtml(wiAgent.name || wi.dispatched_to) + '">' +
172
172
  (wiAgent.emoji || '') + ' ' + escHtml(wiAgent.name || wi.dispatched_to) + '</span>' : '';
173
173
 
174
- // Requeue button for failed items
175
- const canRequeue = wi && (wi.status === 'failed' || i.status === 'failed');
176
- const requeueState = wi ? getPrdRequeueState(wi.id) : null;
174
+ // Requeue button for failed items or PRD items with no work item (orphaned/deleted)
175
+ const canRequeue = (wi && (wi.status === 'failed' || i.status === 'failed')) ||
176
+ (!wi && i.status && i.status !== 'missing' && i.status !== 'done' && i.status !== 'planned');
177
+ const requeueState = getPrdRequeueState(wi ? wi.id : i.id);
177
178
  let requeueBtn = '';
178
179
  if (requeueState && requeueState.status === 'pending') {
179
180
  requeueBtn = '<span style="color:var(--yellow);cursor:wait;font-size:9px;padding:1px 5px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.35);border-radius:3px" title="Retry request in progress">requeuing…</span>';
@@ -182,7 +183,7 @@ function renderPrdProgress(prog) {
182
183
  } else if (requeueState && requeueState.status === 'error') {
183
184
  requeueBtn = '<span style="color:var(--red);cursor:default;font-size:9px;padding:1px 5px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.35);border-radius:3px" title="' + escHtml(requeueState.message || 'Retry failed') + '">retry failed</span>';
184
185
  } else if (canRequeue) {
185
- requeueBtn = '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(wi.id) + '\',\'' + escHtml(wi._source || '') + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 5px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px" title="Requeue this work item">retry</span>';
186
+ requeueBtn = '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(wi ? wi.id : i.id) + '\',\'' + escHtml(wi ? (wi._source || '') : '') + '\',\'' + escHtml(i.source || '') + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 5px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px" title="Requeue this work item">retry</span>';
186
187
  }
187
188
 
188
189
  return '<div class="prd-item-row st-' + (i.status || 'missing') + '" style="flex-wrap:wrap;cursor:pointer" onclick="prdItemEdit(\'' + src + '\',\'' + iid + '\')">' +
@@ -381,8 +382,8 @@ function renderPrdProgress(prog) {
381
382
  (agentDisplay ? '<span style="font-size:8px;color:var(--muted)">' + agentDisplay + '</span>' : '') +
382
383
  (function() {
383
384
  const w = wi[i.id];
384
- if (!w) return '';
385
- const rq = getPrdRequeueState(w.id);
385
+ const rqId = w ? w.id : i.id;
386
+ const rq = getPrdRequeueState(rqId);
386
387
  if (rq && rq.status === 'pending') {
387
388
  return '<span style="font-size:8px;color:var(--yellow);cursor:wait;padding:1px 4px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.35);border-radius:3px">requeuing…</span>';
388
389
  }
@@ -392,8 +393,11 @@ function renderPrdProgress(prog) {
392
393
  if (rq && rq.status === 'error') {
393
394
  return '<span style="font-size:8px;color:var(--red);cursor:default;padding:1px 4px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.35);border-radius:3px" title="' + escHtml(rq.message || 'Retry failed') + '">failed</span>';
394
395
  }
395
- if (i.status !== 'failed') return '';
396
- return '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(w.id) + '\',\'' + escHtml(w._source || '') + '\')" style="font-size:8px;color:var(--green);cursor:pointer;padding:1px 4px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">retry</span>';
396
+ // Show retry for failed items, or PRD items with no work item (orphaned/deleted)
397
+ const canRetry = (w && i.status === 'failed') ||
398
+ (!w && i.status && i.status !== 'missing' && i.status !== 'done' && i.status !== 'planned');
399
+ if (!canRetry) return '';
400
+ return '<span onclick="event.stopPropagation();prdItemRequeue(\'' + escHtml(rqId) + '\',\'' + escHtml(w ? (w._source || '') : '') + '\',\'' + escHtml(i.source || '') + '\')" style="font-size:8px;color:var(--green);cursor:pointer;padding:1px 4px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">retry</span>';
397
401
  })() +
398
402
  (deps ? '<span style="font-size:8px;color:var(--muted)" title="Depends on: ' + escHtml(deps) + '">deps: ' + escHtml(deps) + '</span>' : '') +
399
403
  '</div>' +
@@ -713,13 +717,15 @@ async function prdItemRemove(source, itemId) {
713
717
  } catch (e) { alert('Error: ' + e.message); refresh(); }
714
718
  }
715
719
 
716
- async function prdItemRequeue(workItemId, source) {
720
+ async function prdItemRequeue(workItemId, source, prdFile) {
717
721
  setPrdRequeueState(workItemId, { status: 'pending', message: '' });
718
722
  rerenderPrdFromCache();
719
723
  try {
724
+ const payload = { id: workItemId, source };
725
+ if (prdFile) payload.prdFile = prdFile;
720
726
  const res = await fetch('/api/work-items/retry', {
721
727
  method: 'POST', headers: { 'Content-Type': 'application/json' },
722
- body: JSON.stringify({ id: workItemId, source })
728
+ body: JSON.stringify(payload)
723
729
  });
724
730
  if (res.ok) {
725
731
  setPrdRequeueState(workItemId, { status: 'queued', until: Date.now() + 10000 });
package/dashboard.js CHANGED
@@ -1040,7 +1040,74 @@ const server = http.createServer(async (req, res) => {
1040
1040
  }
1041
1041
  }
1042
1042
  }
1043
- if (!wiPath) return jsonReply(res, 404, { error: 'work item not found in any source' });
1043
+ // If no work item found, attempt to re-materialize from PRD item definition
1044
+ if (!wiPath) {
1045
+ const prdFile = body.prdFile;
1046
+ if (!prdFile) return jsonReply(res, 404, { error: 'work item not found in any source' });
1047
+
1048
+ // Look up PRD item to create a new work item on-demand
1049
+ const prdPath = path.join(PRD_DIR, prdFile);
1050
+ const plan = shared.safeJson(prdPath);
1051
+ if (!plan?.missing_features) return jsonReply(res, 404, { error: 'PRD file not found or invalid' });
1052
+ const prdItem = plan.missing_features.find(f => f.id === id);
1053
+ if (!prdItem) return jsonReply(res, 404, { error: 'PRD item not found in ' + prdFile });
1054
+
1055
+ // Determine target work-items file (project from PRD item or plan, fallback to central)
1056
+ const projName = prdItem.project || plan.project || prdFile.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
1057
+ const proj = PROJECTS.find(p => p.name?.toLowerCase() === projName.toLowerCase());
1058
+ const targetWiPath = proj ? shared.projectWorkItemsPath(proj) : path.join(MINIONS_DIR, 'work-items.json');
1059
+
1060
+ // Create new work item from PRD item definition (same logic as materializePlansAsWorkItems)
1061
+ const complexity = prdItem.estimated_complexity || 'medium';
1062
+ const criteria = (prdItem.acceptance_criteria || []).map(c => `- ${c}`).join('\n');
1063
+ const newItem = {
1064
+ id,
1065
+ title: `Implement: ${prdItem.name}`,
1066
+ type: complexity === 'large' ? 'implement:large' : 'implement',
1067
+ priority: prdItem.priority || 'medium',
1068
+ description: `${prdItem.description || ''}\n\n**Plan:** ${prdFile}\n**Plan Item:** ${prdItem.id}\n**Complexity:** ${complexity}${criteria ? '\n\n**Acceptance Criteria:**\n' + criteria : ''}`,
1069
+ status: WI_STATUS.PENDING,
1070
+ created: new Date().toISOString(),
1071
+ createdBy: 'dashboard:prd-retry',
1072
+ sourcePlan: prdFile,
1073
+ depends_on: prdItem.depends_on || [],
1074
+ branchStrategy: plan.branch_strategy || 'parallel',
1075
+ featureBranch: plan.feature_branch || null,
1076
+ project: prdItem.project || plan.project || null,
1077
+ _source: proj?.name || 'central',
1078
+ _retryCount: 0,
1079
+ };
1080
+ mutateWorkItems(targetWiPath, items => { items.push(newItem); });
1081
+
1082
+ // Reset PRD item status to pending
1083
+ try {
1084
+ const lifecycle = require('./engine/lifecycle');
1085
+ lifecycle.syncPrdItemStatus(id, WI_STATUS.PENDING, prdFile);
1086
+ } catch (e) { console.error('PRD status sync:', e.message); }
1087
+
1088
+ // Clear dispatch history and cooldowns for this item
1089
+ const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
1090
+ const sourcePrefix = proj ? `work-${proj.name}-` : 'central-work-';
1091
+ const dispatchKey = sourcePrefix + id;
1092
+ try {
1093
+ mutateJsonFileLocked(dispatchPath, (dispatch) => {
1094
+ dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
1095
+ dispatch.completed = dispatch.completed.filter(d => d.meta?.dispatchKey !== dispatchKey);
1096
+ dispatch.completed = dispatch.completed.filter(d => !d.meta?.parentKey || d.meta.parentKey !== dispatchKey);
1097
+ return dispatch;
1098
+ }, { defaultValue: { pending: [], active: [], completed: [] } });
1099
+ } catch (e) { console.error('dispatch cleanup:', e.message); }
1100
+ try {
1101
+ const cooldownPath = path.join(MINIONS_DIR, 'engine', 'cooldowns.json');
1102
+ const cooldowns = safeJsonObj(cooldownPath);
1103
+ if (cooldowns[dispatchKey]) {
1104
+ delete cooldowns[dispatchKey];
1105
+ safeWrite(cooldownPath, cooldowns);
1106
+ }
1107
+ } catch (e) { console.error('cooldown cleanup:', e.message); }
1108
+
1109
+ return jsonReply(res, 200, { ok: true, id, rematerialized: true });
1110
+ }
1044
1111
 
1045
1112
  let found = false;
1046
1113
  mutateJsonFileLocked(wiPath, (items) => {
package/engine.js CHANGED
@@ -1202,6 +1202,10 @@ function autoCleanPrdWorkItems(prdFile, config) {
1202
1202
  }
1203
1203
  return dispatch;
1204
1204
  });
1205
+ // Reset PRD item status to 'missing' so engine re-materializes on next tick
1206
+ for (const id of deletedIds) {
1207
+ try { syncPrdItemStatus(id, 'missing', prdFile); } catch (e) { log('warn', `PRD status reset for ${id}: ${e.message}`); }
1208
+ }
1205
1209
  log('info', `Plan sync: cleared ${deletedIds.length} pending/failed work items for ${prdFile}`);
1206
1210
  }
1207
1211
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.803",
3
+ "version": "0.1.804",
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"