@yemi33/minions 0.1.823 → 0.1.825
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 +8 -0
- package/dashboard/js/render-prd.js +22 -4
- package/dashboard.js +51 -1
- package/engine.js +3 -58
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -269,7 +269,8 @@ function renderPrdProgress(prog) {
|
|
|
269
269
|
const staleRecovery = (!isBlocked && g.planStale)
|
|
270
270
|
? '<div style="width:100%;margin-top:4px;padding:6px 8px;border:1px solid rgba(210,153,34,0.35);border-radius:4px;background:rgba(210,153,34,0.08);display:flex;align-items:center;gap:8px;flex-wrap:wrap">' +
|
|
271
271
|
'<span style="color:var(--orange);font-size:10px;font-weight:600">⚠️ Source plan was revised. This PRD may be outdated.</span>' +
|
|
272
|
-
'<span onclick="event.stopPropagation();prdRegenerate(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:10px;font-weight:700;padding:2px 8px;background:rgba(63,185,80,0.12);border:1px solid rgba(63,185,80,0.35);border-radius:4px" title="
|
|
272
|
+
'<span onclick="event.stopPropagation();prdRegenerate(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:10px;font-weight:700;padding:2px 8px;background:rgba(63,185,80,0.12);border:1px solid rgba(63,185,80,0.35);border-radius:4px" title="Compare revised plan against existing PRD and update items">Regenerate PRD</span>' +
|
|
273
|
+
'<span onclick="event.stopPropagation();prdResumeWithoutRegen(\'' + escHtml(g.file) + '\')" style="color:var(--muted);cursor:pointer;font-size:10px;padding:2px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px" title="Resume without regenerating — use current PRD as-is">Resume as-is</span>' +
|
|
273
274
|
'<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan || g.file) + '\')" style="color:var(--blue);cursor:pointer;font-size:10px;padding:2px 8px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:4px" title="Review latest plan changes">Review plan</span>' +
|
|
274
275
|
'</div>'
|
|
275
276
|
: '';
|
|
@@ -763,15 +764,32 @@ async function prdItemRequeue(workItemId, source, prdFile) {
|
|
|
763
764
|
}
|
|
764
765
|
|
|
765
766
|
async function prdRegenerate(prdFile) {
|
|
766
|
-
if (!confirm('
|
|
767
|
+
if (!confirm('Source plan was revised.\n\nRegenerate PRD? An agent will compare the updated plan against existing implementation and update items accordingly.\n\nDone items stay done unless modified. New items are added. Removed items are cancelled.')) return;
|
|
767
768
|
try {
|
|
768
|
-
const res = await fetch('/api/
|
|
769
|
+
const res = await fetch('/api/plans/approve', {
|
|
769
770
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
770
771
|
body: JSON.stringify({ file: prdFile })
|
|
771
772
|
});
|
|
772
773
|
const d = await res.json();
|
|
773
774
|
if (res.ok) {
|
|
774
|
-
showToast('cmd-toast', 'PRD
|
|
775
|
+
showToast('cmd-toast', d.diffAwareUpdate ? 'Diff-aware PRD update queued' : 'Plan approved', true);
|
|
776
|
+
refresh();
|
|
777
|
+
} else {
|
|
778
|
+
alert('Failed: ' + (d.error || 'unknown'));
|
|
779
|
+
}
|
|
780
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
async function prdResumeWithoutRegen(prdFile) {
|
|
784
|
+
if (!confirm('Resume this plan without regenerating the PRD?\n\nThe current PRD items will be used as-is. New work items will be materialized from any missing/planned items.')) return;
|
|
785
|
+
try {
|
|
786
|
+
const res = await fetch('/api/plans/approve', {
|
|
787
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
788
|
+
body: JSON.stringify({ file: prdFile, skipRegen: true })
|
|
789
|
+
});
|
|
790
|
+
const d = await res.json();
|
|
791
|
+
if (res.ok) {
|
|
792
|
+
showToast('cmd-toast', 'Plan resumed (PRD unchanged)', true);
|
|
775
793
|
refresh();
|
|
776
794
|
} else {
|
|
777
795
|
alert('Failed: ' + (d.error || 'unknown'));
|
package/dashboard.js
CHANGED
|
@@ -2183,6 +2183,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2183
2183
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
2184
2184
|
const planPath = resolvePlanPath(body.file);
|
|
2185
2185
|
const plan = safeJsonObj(planPath);
|
|
2186
|
+
const wasStale = !!plan.planStale;
|
|
2186
2187
|
plan.status = 'approved';
|
|
2187
2188
|
plan.approvedAt = new Date().toISOString();
|
|
2188
2189
|
plan.approvedBy = body.approvedBy || os.userInfo().username;
|
|
@@ -2227,11 +2228,60 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2227
2228
|
}, { defaultValue: { pending: [], active: [], completed: [] } });
|
|
2228
2229
|
}
|
|
2229
2230
|
|
|
2231
|
+
// Diff-aware PRD update: if plan was stale (source .md revised), dispatch plan-to-prd
|
|
2232
|
+
// to compare the revised plan against existing PRD and produce an updated version
|
|
2233
|
+
let diffAwareQueued = false;
|
|
2234
|
+
if (plan.source_plan && plan.missing_features && wasStale && !body.skipRegen) {
|
|
2235
|
+
const config = queries.getConfig();
|
|
2236
|
+
const allWorkItems = queries.getWorkItems(config);
|
|
2237
|
+
const planWis = allWorkItems.filter(w => w.sourcePlan === body.file && w.itemType !== 'pr' && w.itemType !== 'verify');
|
|
2238
|
+
const allPrs = PROJECTS.flatMap(p => shared.safeJson(shared.projectPrPath(p)) || []);
|
|
2239
|
+
const prLinks = shared.getPrLinks();
|
|
2240
|
+
const implContext = (plan.missing_features || []).map(f => {
|
|
2241
|
+
const wi = planWis.find(w => w.id === f.id);
|
|
2242
|
+
const pr = allPrs.find(p => prLinks[p.id] === f.id || (p.prdItems || []).includes(f.id));
|
|
2243
|
+
return `- **${f.id}**: ${f.name} [status: ${wi?.status || f.status}]${pr ? ` (PR: ${pr.id}, branch: \`${pr.branch}\`)` : ''}`;
|
|
2244
|
+
}).join('\n');
|
|
2245
|
+
|
|
2246
|
+
const projectName = plan.project || body.file.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
|
|
2247
|
+
const targetProject = PROJECTS.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || PROJECTS[0];
|
|
2248
|
+
if (targetProject) {
|
|
2249
|
+
const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2250
|
+
mutateJsonFileLocked(centralWiPath, items => {
|
|
2251
|
+
if (!Array.isArray(items)) items = [];
|
|
2252
|
+
if (items.some(w => w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched'))) return items;
|
|
2253
|
+
items.push({
|
|
2254
|
+
id: 'W-' + shared.uid(),
|
|
2255
|
+
title: `Update PRD from revised plan: ${plan.source_plan}`,
|
|
2256
|
+
type: 'plan-to-prd',
|
|
2257
|
+
priority: 'high',
|
|
2258
|
+
description: `Plan file: plans/${plan.source_plan}\nPRD file: prd/${body.file}\n\n` +
|
|
2259
|
+
`Source plan was revised. Compare the updated plan against existing PRD and produce an updated version.\n\n` +
|
|
2260
|
+
`**Current PRD implementation state:**\n${implContext}\n\n` +
|
|
2261
|
+
`**Rules for updating:**\n` +
|
|
2262
|
+
`- Items that are done and unchanged in the plan → keep status "done" (preserve their ID)\n` +
|
|
2263
|
+
`- Items that are done but modified in the plan (new requirements) → set status "updated" (engine will re-open)\n` +
|
|
2264
|
+
`- New items in the plan → set status "missing" (engine will materialize)\n` +
|
|
2265
|
+
`- Items removed from the plan → drop from PRD (engine will cancel pending WIs)\n` +
|
|
2266
|
+
`- Preserve all existing item IDs for unchanged/modified items — do NOT generate new IDs for them`,
|
|
2267
|
+
status: 'pending',
|
|
2268
|
+
created: new Date().toISOString(),
|
|
2269
|
+
createdBy: 'dashboard:plan-resume',
|
|
2270
|
+
project: targetProject.name,
|
|
2271
|
+
planFile: plan.source_plan,
|
|
2272
|
+
_existingPrdFile: body.file,
|
|
2273
|
+
});
|
|
2274
|
+
diffAwareQueued = true;
|
|
2275
|
+
return items;
|
|
2276
|
+
}, { defaultValue: [] });
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2230
2280
|
// Teams notification for plan approval — non-blocking
|
|
2231
2281
|
try { teams.teamsNotifyPlanEvent({ name: plan.plan_summary || body.file, file: body.file }, 'plan-approved').catch(() => {}); } catch {}
|
|
2232
2282
|
|
|
2233
2283
|
invalidateStatusCache();
|
|
2234
|
-
return jsonReply(res, 200, { ok: true, status: 'approved', resumedWorkItems: resumed });
|
|
2284
|
+
return jsonReply(res, 200, { ok: true, status: 'approved', resumedWorkItems: resumed, diffAwareUpdate: diffAwareQueued });
|
|
2235
2285
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
2236
2286
|
}
|
|
2237
2287
|
|
package/engine.js
CHANGED
|
@@ -1331,65 +1331,10 @@ function materializePlansAsWorkItems(config) {
|
|
|
1331
1331
|
// Handle PRD based on current status
|
|
1332
1332
|
const prdStatus = plan.status || (plan.requires_approval ? 'awaiting-approval' : null);
|
|
1333
1333
|
|
|
1334
|
-
// Approved/completed PRDs
|
|
1335
|
-
if (prdStatus
|
|
1336
|
-
const allWorkItems = queries.getWorkItems(config);
|
|
1337
|
-
const planWis = allWorkItems.filter(w => w.sourcePlan === file && w.itemType !== 'pr' && w.itemType !== 'verify');
|
|
1338
|
-
const doneWis = planWis.filter(w => DONE_STATUSES.has(w.status));
|
|
1339
|
-
if (doneWis.length > 0) {
|
|
1340
|
-
const allPrs = getProjects(config).flatMap(p => safeJson(projectPrPath(p)) || []);
|
|
1341
|
-
const prLinks = shared.getPrLinks();
|
|
1342
|
-
const implContext = (plan.missing_features || []).map(f => {
|
|
1343
|
-
const wi = planWis.find(w => w.id === f.id);
|
|
1344
|
-
const pr = allPrs.find(p => prLinks[p.id] === f.id || (p.prdItems || []).includes(f.id));
|
|
1345
|
-
return `- **${f.id}**: ${f.name} [status: ${wi?.status || f.status}]${pr ? ` (PR: ${pr.id}, branch: \`${pr.branch}\`)` : ''}`;
|
|
1346
|
-
}).join('\n');
|
|
1347
|
-
|
|
1348
|
-
const planContent = safeRead(path.join(PLANS_DIR, plan.source_plan));
|
|
1349
|
-
if (planContent) {
|
|
1350
|
-
const projectName = plan.project || file.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
|
|
1351
|
-
const allProjects = getProjects(config);
|
|
1352
|
-
const targetProject = allProjects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || allProjects[0];
|
|
1353
|
-
if (targetProject) {
|
|
1354
|
-
const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1355
|
-
let queued = false;
|
|
1356
|
-
mutateJsonFileLocked(centralWiPath, items => {
|
|
1357
|
-
if (!Array.isArray(items)) items = [];
|
|
1358
|
-
if (items.some(w => w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === plan.source_plan && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.DISPATCHED))) return items;
|
|
1359
|
-
items.push({
|
|
1360
|
-
id: 'W-' + shared.uid(),
|
|
1361
|
-
title: `Update PRD from revised plan: ${plan.source_plan}`,
|
|
1362
|
-
type: 'plan-to-prd',
|
|
1363
|
-
priority: 'high',
|
|
1364
|
-
description: `Plan file: plans/${plan.source_plan}\nPRD file: prd/${file}\n\n` +
|
|
1365
|
-
`Source plan was revised. Compare the updated plan against existing implementation and update the PRD.\n\n` +
|
|
1366
|
-
`**Existing implementation state:**\n${implContext}\n\n` +
|
|
1367
|
-
`**Rules for updating:**\n` +
|
|
1368
|
-
`- Items that are done and unchanged in the plan → keep status "done" (preserve their ID)\n` +
|
|
1369
|
-
`- Items that are done but modified in the plan (new requirements) → set status "updated" (engine will re-open)\n` +
|
|
1370
|
-
`- New items in the plan → set status "missing" (engine will materialize)\n` +
|
|
1371
|
-
`- Items removed from the plan → drop from PRD (engine will cancel pending WIs)\n` +
|
|
1372
|
-
`- Preserve all existing item IDs for unchanged/modified items — do NOT generate new IDs for them`,
|
|
1373
|
-
status: 'pending',
|
|
1374
|
-
created: ts(),
|
|
1375
|
-
createdBy: 'engine:plan-revision',
|
|
1376
|
-
project: targetProject.name,
|
|
1377
|
-
planFile: plan.source_plan,
|
|
1378
|
-
_existingPrdFile: file,
|
|
1379
|
-
});
|
|
1380
|
-
queued = true;
|
|
1381
|
-
return items;
|
|
1382
|
-
}, { defaultValue: [] });
|
|
1383
|
-
if (queued) log('info', `Queued diff-aware plan-to-prd update for ${plan.source_plan} (${doneWis.length} done, ${planWis.length} total items)`);
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
} else {
|
|
1387
|
-
plan.planStale = true;
|
|
1388
|
-
log('info', `PRD ${file} flagged as stale (plan revised while ${prdStatus}, no done items) — user can re-execute from dashboard`);
|
|
1389
|
-
}
|
|
1390
|
-
} else if (prdStatus === PLAN_STATUS.PAUSED) {
|
|
1334
|
+
// Approved/completed/paused PRDs: flag stale — user clicks Resume to trigger diff-aware update
|
|
1335
|
+
if (prdStatus && prdStatus !== 'awaiting-approval') {
|
|
1391
1336
|
plan.planStale = true;
|
|
1392
|
-
log('info', `PRD ${file} flagged as stale (plan revised while
|
|
1337
|
+
log('info', `PRD ${file} flagged as stale (plan revised while ${prdStatus}) — user can resume from dashboard`);
|
|
1393
1338
|
}
|
|
1394
1339
|
|
|
1395
1340
|
// Awaiting-approval PRDs: auto-regenerate (no work started yet, safe to replace)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.825",
|
|
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"
|