@yemi33/minions 0.1.1553 → 0.1.1554

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1554 (2026-04-27)
4
+
5
+ ### Fixes
6
+ - archive bugs — undefined resetBtn, .backup re-trigger, dispatch leak
7
+
3
8
  ## 0.1.1553 (2026-04-27)
4
9
 
5
10
  ### Other
@@ -601,13 +601,12 @@ async function planArchive(file, btn) {
601
601
  if (!confirm(confirmMsg)) return;
602
602
  _stopPlanPoll();
603
603
  markDeleted('plan:' + file);
604
- // Also optimistically hide the linked source plan (server archives both)
605
604
  if (isPrd) {
606
605
  var linkedPlan = (window._lastPlans || []).find(function(p) { return p.file === file && p.sourcePlan; });
607
606
  if (linkedPlan) markDeleted('plan:' + linkedPlan.sourcePlan);
608
607
  }
609
608
  try { closeModal(); } catch { /* may not be open */ }
610
- showToast('cmd-toast', 'Archiving...', true);
609
+ showToast('cmd-toast', 'Archived', true);
611
610
  try {
612
611
  const res = await fetch('/api/plans/archive', {
613
612
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -617,16 +616,18 @@ async function planArchive(file, btn) {
617
616
  if (!ct.includes('json')) { refresh(); return; }
618
617
  const d = await res.json().catch(() => ({}));
619
618
  if (res.ok && d.ok) {
620
- var msg = 'Archived';
621
- if (d.archivedSource) msg += ' PRD + source plan (' + d.archivedSource + ')';
622
- if (d.cancelledItems) msg += ', cancelled ' + d.cancelledItems + ' pending item(s)';
623
- showToast('cmd-toast', msg, true);
619
+ if (d.archivedSource || d.cancelledItems) {
620
+ var msg = 'Archived';
621
+ if (d.archivedSource) msg += ' PRD + source plan';
622
+ if (d.cancelledItems) msg += ', cancelled ' + d.cancelledItems + ' pending item(s)';
623
+ showToast('cmd-toast', msg, true);
624
+ }
624
625
  refresh();
625
626
  } else {
626
- resetBtn();
627
- alert('Archive failed: ' + (d.error || 'unknown'));
627
+ showToast('cmd-toast', 'Archive failed: ' + (d.error || 'unknown'), false);
628
+ refresh();
628
629
  }
629
- } catch (e) { resetBtn(); alert('Error: ' + e.message); }
630
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
630
631
  }
631
632
 
632
633
  async function planPause(file, btn) {
@@ -793,8 +794,12 @@ async function planUnarchive(file, btn) {
793
794
  body: JSON.stringify({ file })
794
795
  });
795
796
  if (res.ok) { refreshPlans(); refresh(); }
796
- else { const d = await res.json().catch(() => ({})); alert('Unarchive failed: ' + (d.error || 'unknown')); refresh(); }
797
- } catch (e) { alert('Error: ' + e.message); refresh(); }
797
+ else {
798
+ const d = await res.json().catch(() => ({}));
799
+ showToast('cmd-toast', 'Unarchive failed: ' + (d.error || 'unknown'), false);
800
+ refresh();
801
+ }
802
+ } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
798
803
  }
799
804
 
800
805
  window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
package/dashboard.js CHANGED
@@ -3341,46 +3341,69 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
3341
3341
  try {
3342
3342
  const body = await readBody(req);
3343
3343
  if (!body.file) return jsonReply(res, 400, { error: 'file required' });
3344
- shared.sanitizePath(body.file, body.file.endsWith('.json') ? PRD_DIR : PLANS_DIR);
3344
+ const isPrd = body.file.endsWith('.json');
3345
+ shared.sanitizePath(body.file, isPrd ? PRD_DIR : PLANS_DIR);
3345
3346
  const planPath = resolvePlanPath(body.file);
3346
3347
  if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
3347
3348
 
3348
- // Move to archive directory
3349
- const archiveDir = body.file.endsWith('.json') ? path.join(PRD_DIR, 'archive') : path.join(PLANS_DIR, 'archive');
3349
+ const archiveDir = isPrd ? path.join(PRD_DIR, 'archive') : path.join(PLANS_DIR, 'archive');
3350
3350
  if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
3351
3351
  const archivePath = path.join(archiveDir, body.file);
3352
3352
  fs.renameSync(planPath, archivePath);
3353
3353
 
3354
- // Mark archived in JSON if PRD
3355
3354
  let archivedSource = null;
3356
- if (body.file.endsWith('.json')) {
3355
+ let plan = {};
3356
+ if (isPrd) {
3357
3357
  try {
3358
- const prd = safeJsonObj(archivePath);
3359
- prd.status = 'archived';
3360
- prd.archivedAt = new Date().toISOString();
3361
- safeWrite(archivePath, prd);
3362
- // Also archive linked source plan
3363
- if (prd.source_plan) {
3364
- const mdPath = path.join(PLANS_DIR, prd.source_plan);
3358
+ plan = safeJsonObj(archivePath) || {};
3359
+ plan.status = 'archived';
3360
+ plan.archivedAt = new Date().toISOString();
3361
+ safeWrite(archivePath, plan);
3362
+ // Without removing the .backup sidecar, safeJson would auto-restore the
3363
+ // pre-completion snapshot on engine restart, re-triggering plan completion
3364
+ // and spawning duplicate verify tasks (regression of #f28162b0).
3365
+ const backupPath = planPath + '.backup';
3366
+ try { fs.unlinkSync(backupPath); } catch {
3367
+ try { fs.writeFileSync(backupPath, JSON.stringify({ status: 'archived' })); } catch { /* best-effort */ }
3368
+ }
3369
+ if (plan.source_plan) {
3370
+ const mdPath = path.join(PLANS_DIR, plan.source_plan);
3365
3371
  if (fs.existsSync(mdPath)) {
3366
3372
  const planArchive = path.join(PLANS_DIR, 'archive');
3367
3373
  if (!fs.existsSync(planArchive)) fs.mkdirSync(planArchive, { recursive: true });
3368
- fs.renameSync(mdPath, path.join(planArchive, prd.source_plan));
3369
- archivedSource = prd.source_plan;
3374
+ fs.renameSync(mdPath, path.join(planArchive, plan.source_plan));
3375
+ archivedSource = plan.source_plan;
3370
3376
  }
3371
3377
  }
3372
3378
  } catch { /* optional */ }
3373
3379
  }
3374
3380
 
3375
- // Clean up worktrees associated with this plan
3381
+ // Cancel pending work items linked to this plan so the engine stops
3382
+ // dispatching for an archived plan. Done items are preserved as history.
3383
+ let cancelledItems = 0;
3384
+ const wiPaths = [path.join(MINIONS_DIR, 'work-items.json'), ...PROJECTS.map(p => shared.projectWorkItemsPath(p))];
3385
+ for (const wiPath of wiPaths) {
3386
+ try {
3387
+ mutateWorkItems(wiPath, items => {
3388
+ for (const w of items) {
3389
+ if (w.sourcePlan !== body.file) continue;
3390
+ if (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.QUEUED) {
3391
+ w.status = WI_STATUS.CANCELLED;
3392
+ w._cancelledBy = 'plan-archived';
3393
+ cancelledItems++;
3394
+ }
3395
+ }
3396
+ });
3397
+ } catch (e) { console.error('plan archive cancel:', e.message); }
3398
+ }
3399
+
3376
3400
  try {
3377
- const plan = body.file.endsWith('.json') ? (safeJsonObj(archivePath) || {}) : {};
3378
3401
  const { cleanupPlanWorktrees } = require('./engine/lifecycle');
3379
3402
  cleanupPlanWorktrees(body.file, plan, PROJECTS, getConfig());
3380
3403
  } catch (e) { console.error('plan worktree cleanup:', e.message); }
3381
3404
 
3382
3405
  invalidateStatusCache();
3383
- return jsonReply(res, 200, { ok: true, archived: body.file, archivedSource });
3406
+ return jsonReply(res, 200, { ok: true, archived: body.file, archivedSource, cancelledItems });
3384
3407
  } catch (e) { return jsonReply(res, 400, { error: e.message }); }
3385
3408
  }
3386
3409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1553",
3
+ "version": "0.1.1554",
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"