@yemi33/minions 0.1.802 → 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 +3 -1
- package/dashboard/js/render-prd.js +16 -10
- package/dashboard.js +68 -1
- package/engine.js +23 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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,8 @@
|
|
|
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)
|
|
13
|
+
- handle force-pushed dep branches on retry (closes #738) (#806)
|
|
12
14
|
- project-scoped skill work items instruct wrong file path format (closes #790) (#804)
|
|
13
15
|
- move ignoredAuthors construction outside inner comment loop (ADO)
|
|
14
16
|
- approved is permanent — no path can ever downgrade it
|
|
@@ -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
|
-
|
|
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
|
-
|
|
385
|
-
const rq = getPrdRequeueState(
|
|
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
|
-
|
|
396
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
@@ -493,8 +493,25 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
493
493
|
await execAsync(`git merge "origin/${depBranch}" --no-edit`, { ..._gitOpts, cwd: worktreePath });
|
|
494
494
|
log('info', `Merged dependency branch ${depBranch} (${prId}) into worktree ${branchName}`);
|
|
495
495
|
} catch (mergeErr) {
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
// Merge failed — possibly due to diverged history from a force-pushed (rebased) dep branch.
|
|
497
|
+
// Abort partial merge, reset worktree to clean main base, and re-merge all deps from scratch.
|
|
498
|
+
log('warn', `Merge of ${depBranch} into ${branchName} failed: ${mergeErr.message} — attempting reset and re-merge of all deps`);
|
|
499
|
+
try { await execAsync(`git merge --abort`, { ..._gitOpts, cwd: worktreePath }); } catch (_) { /* no merge in progress */ }
|
|
500
|
+
const mainRef = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
|
|
501
|
+
try {
|
|
502
|
+
await execAsync(`git reset --hard "origin/${mainRef}"`, { ..._gitOpts, cwd: worktreePath });
|
|
503
|
+
log('info', `Reset worktree ${branchName} to origin/${mainRef} for clean dep re-merge`);
|
|
504
|
+
// Re-merge ALL fetched dep branches from scratch on clean base
|
|
505
|
+
for (const { branch: reBranch, prId: rePrId } of fetched) {
|
|
506
|
+
await execAsync(`git merge "origin/${reBranch}" --no-edit`, { ..._gitOpts, cwd: worktreePath });
|
|
507
|
+
log('info', `Re-merged dependency branch ${reBranch} (${rePrId}) into worktree ${branchName}`);
|
|
508
|
+
}
|
|
509
|
+
log('info', `Successfully re-merged all ${fetched.length} dep branches after reset for ${branchName}`);
|
|
510
|
+
} catch (resetErr) {
|
|
511
|
+
log('warn', `Failed to reset and re-merge deps for ${branchName}: ${resetErr.message}`);
|
|
512
|
+
try { await execAsync(`git merge --abort`, { ..._gitOpts, cwd: worktreePath }); } catch (_) { /* no merge in progress */ }
|
|
513
|
+
depMergeFailed = true;
|
|
514
|
+
}
|
|
498
515
|
break;
|
|
499
516
|
}
|
|
500
517
|
}
|
|
@@ -1185,6 +1202,10 @@ function autoCleanPrdWorkItems(prdFile, config) {
|
|
|
1185
1202
|
}
|
|
1186
1203
|
return dispatch;
|
|
1187
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
|
+
}
|
|
1188
1209
|
log('info', `Plan sync: cleared ${deletedIds.length} pending/failed work items for ${prdFile}`);
|
|
1189
1210
|
}
|
|
1190
1211
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
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"
|