@yemi33/minions 0.1.824 → 0.1.826

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,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.824 (2026-04-11)
3
+ ## 0.1.826 (2026-04-11)
4
+
5
+ ### Features
6
+ - stale PRD shows Regenerate PRD + Resume as-is buttons
4
7
 
5
8
  ### Fixes
9
+ - hide conflicting action buttons when stale PRD banner is showing
6
10
  - diff-aware plan-to-prd triggered by Resume, not auto-dispatch
7
11
 
8
12
  ## 0.1.823 (2026-04-11)
@@ -30,8 +30,9 @@ function renderPrd(prd, prog) {
30
30
  const prdStatus = existing[0]?.status || '';
31
31
  const effectiveStatus = allDone && !hasActive ? 'completed' : hasActive ? 'dispatched' : prdStatus || 'active';
32
32
 
33
+ const headerStale = existing[0]?.planStale || prdItems.some(i => i.planStale);
33
34
  let actions = '';
34
- if (prdFile) {
35
+ if (prdFile && !headerStale) {
35
36
  if (effectiveStatus === 'awaiting-approval') {
36
37
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Approve</button>';
37
38
  } else if (effectiveStatus === 'completed') {
@@ -269,19 +270,22 @@ function renderPrdProgress(prog) {
269
270
  const staleRecovery = (!isBlocked && g.planStale)
270
271
  ? '<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
272
  '<span style="color:var(--orange);font-size:10px;font-weight:600">&#x26A0;&#xFE0F; 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="Regenerate PRD from latest plan">Regenerate now</span>' +
273
+ '<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>' +
274
+ '<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
275
  '<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
276
  '</div>'
275
277
  : '';
276
278
  const isCompleted = done > 0 && done === g.items.length;
277
- const pauseResumeBtn = isAwaitingApproval
279
+ // Hide regular action buttons when stale banner is showing — stale banner has its own actions
280
+ const isStale = !isBlocked && g.planStale;
281
+ const pauseResumeBtn = isStale ? '' : isAwaitingApproval
278
282
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Approve</span>'
279
283
  : isPaused
280
284
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Resume</span>'
281
285
  : isCompleted && !(window._lastWorkItems || []).some(w => w.itemType === 'verify' && w.sourcePlan === g.file)
282
286
  ? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\',this)" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
283
287
  : isCompleted ? '' : '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\',this)" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
284
- const archiveBtn = (isCompleted || isPaused) ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" style="color:var(--muted);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(139,148,158,0.1);border:1px solid rgba(139,148,158,0.3);border-radius:3px">Archive</span>' : '';
288
+ const archiveBtn = (!isStale && (isCompleted || isPaused)) ? '<span onclick="event.stopPropagation();planArchive(\'' + escHtml(g.file) + '\',this)" style="color:var(--muted);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(139,148,158,0.1);border:1px solid rgba(139,148,158,0.3);border-radius:3px">Archive</span>' : '';
285
289
  const deleteBtn = '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--red);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);border-radius:3px">Delete</span>';
286
290
  const sourcePlanLink = g.sourcePlan
287
291
  ? '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan) + '\')" style="color:var(--blue);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="View source plan">&#x1F4C4; Plan</span>'
@@ -763,15 +767,32 @@ async function prdItemRequeue(workItemId, source, prdFile) {
763
767
  }
764
768
 
765
769
  async function prdRegenerate(prdFile) {
766
- if (!confirm('This PRD is stale because the source plan changed.\n\nRegenerate now from the latest plan?\n\nThis resets PRD status to awaiting-approval and queues a fresh plan-to-prd conversion.')) return;
770
+ 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
771
  try {
768
- const res = await fetch('/api/prd/regenerate', {
772
+ const res = await fetch('/api/plans/approve', {
769
773
  method: 'POST', headers: { 'Content-Type': 'application/json' },
770
774
  body: JSON.stringify({ file: prdFile })
771
775
  });
772
776
  const d = await res.json();
773
777
  if (res.ok) {
774
- showToast('cmd-toast', 'PRD regeneration queued', true);
778
+ showToast('cmd-toast', d.diffAwareUpdate ? 'Diff-aware PRD update queued' : 'Plan approved', true);
779
+ refresh();
780
+ } else {
781
+ alert('Failed: ' + (d.error || 'unknown'));
782
+ }
783
+ } catch (e) { alert('Error: ' + e.message); }
784
+ }
785
+
786
+ async function prdResumeWithoutRegen(prdFile) {
787
+ 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;
788
+ try {
789
+ const res = await fetch('/api/plans/approve', {
790
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
791
+ body: JSON.stringify({ file: prdFile, skipRegen: true })
792
+ });
793
+ const d = await res.json();
794
+ if (res.ok) {
795
+ showToast('cmd-toast', 'Plan resumed (PRD unchanged)', true);
775
796
  refresh();
776
797
  } else {
777
798
  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,55 +2228,52 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2227
2228
  }, { defaultValue: { pending: [], active: [], completed: [] } });
2228
2229
  }
2229
2230
 
2230
- // Diff-aware PRD update: if plan has done work and a source_plan .md, dispatch plan-to-prd
2231
- // to compare the revised plan against existing implementation
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
2232
2233
  let diffAwareQueued = false;
2233
- if (plan.source_plan && plan.missing_features) {
2234
+ if (plan.source_plan && plan.missing_features && wasStale && !body.skipRegen) {
2234
2235
  const config = queries.getConfig();
2235
2236
  const allWorkItems = queries.getWorkItems(config);
2236
2237
  const planWis = allWorkItems.filter(w => w.sourcePlan === body.file && w.itemType !== 'pr' && w.itemType !== 'verify');
2237
- const doneWis = planWis.filter(w => shared.DONE_STATUSES.has(w.status));
2238
- if (doneWis.length > 0) {
2239
- const allPrs = PROJECTS.flatMap(p => shared.safeJson(shared.projectPrPath(p)) || []);
2240
- const prLinks = shared.getPrLinks();
2241
- const implContext = (plan.missing_features || []).map(f => {
2242
- const wi = planWis.find(w => w.id === f.id);
2243
- const pr = allPrs.find(p => prLinks[p.id] === f.id || (p.prdItems || []).includes(f.id));
2244
- return `- **${f.id}**: ${f.name} [status: ${wi?.status || f.status}]${pr ? ` (PR: ${pr.id}, branch: \`${pr.branch}\`)` : ''}`;
2245
- }).join('\n');
2246
-
2247
- const projectName = plan.project || body.file.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
2248
- const targetProject = PROJECTS.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || PROJECTS[0];
2249
- if (targetProject) {
2250
- const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
2251
- mutateJsonFileLocked(centralWiPath, items => {
2252
- if (!Array.isArray(items)) items = [];
2253
- if (items.some(w => w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched'))) return items;
2254
- items.push({
2255
- id: 'W-' + shared.uid(),
2256
- title: `Update PRD from revised plan: ${plan.source_plan}`,
2257
- type: 'plan-to-prd',
2258
- priority: 'high',
2259
- description: `Plan file: plans/${plan.source_plan}\nPRD file: prd/${body.file}\n\n` +
2260
- `Source plan was revised. Compare the updated plan against existing implementation and update the PRD.\n\n` +
2261
- `**Existing implementation state:**\n${implContext}\n\n` +
2262
- `**Rules for updating:**\n` +
2263
- `- Items that are done and unchanged in the plan → keep status "done" (preserve their ID)\n` +
2264
- `- Items that are done but modified in the plan (new requirements) set status "updated" (engine will re-open)\n` +
2265
- `- New items in the plan set status "missing" (engine will materialize)\n` +
2266
- `- Items removed from the plan → drop from PRD (engine will cancel pending WIs)\n` +
2267
- `- Preserve all existing item IDs for unchanged/modified items — do NOT generate new IDs for them`,
2268
- status: 'pending',
2269
- created: new Date().toISOString(),
2270
- createdBy: 'dashboard:plan-resume',
2271
- project: targetProject.name,
2272
- planFile: plan.source_plan,
2273
- _existingPrdFile: body.file,
2274
- });
2275
- diffAwareQueued = true;
2276
- return items;
2277
- }, { defaultValue: [] });
2278
- }
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: [] });
2279
2277
  }
2280
2278
  }
2281
2279
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.824",
3
+ "version": "0.1.826",
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"