@yemi33/minions 0.1.2275 → 0.1.2277

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.
Files changed (3) hide show
  1. package/dashboard.js +15 -0
  2. package/engine.js +28 -3
  3. package/package.json +1 -1
package/dashboard.js CHANGED
@@ -8260,6 +8260,21 @@ const server = http.createServer(async (req, res) => {
8260
8260
  cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
8261
8261
  } catch (e) { console.error('plan worktree cleanup:', e.message); }
8262
8262
  safeUnlink(planPath);
8263
+ // Neutralize the `.backup` sidecar so `safeJson` auto-restore can't
8264
+ // RESURRECT the PRD we just deleted (the live file is gone, but a stray
8265
+ // `prd/<plan>.json.backup` would be auto-restored on the next safeJson read
8266
+ // — the PRD comes back, with its prior `status: approved/active`, and the
8267
+ // materializer re-dispatches its work items). Mirrors the Archive handler's
8268
+ // backup cleanup (handlePlansArchive). A PRD delete must remove the restore
8269
+ // fuel, not just the live file.
8270
+ if (body.file.endsWith('.json')) {
8271
+ try {
8272
+ const backupCleanup = shared.neutralizeJsonBackupSidecar(planPath);
8273
+ if (!backupCleanup.ok) {
8274
+ console.warn(`Delete backup cleanup failed for ${body.file}: unlink (${backupCleanup.unlinkError}) / neutralize (${backupCleanup.writeError})`);
8275
+ }
8276
+ } catch (e) { console.warn(`Delete backup cleanup threw for ${body.file}: ${e.message}`); }
8277
+ }
8263
8278
 
8264
8279
  // Clean up materialized work items from all projects + central
8265
8280
  let cleaned = 0;
package/engine.js CHANGED
@@ -8782,18 +8782,41 @@ function discoverCentralWorkItems(config) {
8782
8782
  // the renderPlaybook pass logs an "unresolved template variables" warning
8783
8783
  // every time a fresh plan-to-prd dispatches.
8784
8784
  vars.existing_prd_json = '';
8785
- // Check if a PRD already exists for this plan — reuse its filename to avoid duplicates (#884)
8785
+ // Check if a PRD already exists for this plan — reuse its filename to
8786
+ // avoid duplicates (#884). Match by BASENAME (not exact string) so a
8787
+ // `plans/` prefix or separator difference between item.planFile and a
8788
+ // PRD's source_plan can't fork a duplicate PRD (#415 class).
8786
8789
  let prdFilename = null;
8790
+ const planKey = path.basename(String(item.planFile || ''));
8787
8791
  const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
8788
8792
  for (const pf of prdFiles) {
8789
8793
  const prd = safeJson(path.join(PRD_DIR, pf));
8790
- if (prd?.source_plan === item.planFile) {
8794
+ if (planKey && prd && path.basename(String(prd.source_plan || '')) === planKey) {
8791
8795
  prdFilename = pf;
8792
8796
  try { vars.existing_prd_json = fs.readFileSync(path.join(PRD_DIR, pf), 'utf8'); } catch (_) { /* ignore */ }
8793
8797
  log('info', `plan-to-prd: reusing existing PRD "${pf}" for plan "${item.planFile}" (#884)`);
8794
8798
  break;
8795
8799
  }
8796
8800
  }
8801
+ // Same-plan in-flight reuse (dedup hardening): no on-disk PRD found, but
8802
+ // if ANOTHER plan-to-prd item for the SAME plan already pinned a
8803
+ // _prdFilename — a sibling WI from a prior tick, or another item being
8804
+ // prepared this same tick — REUSE it so concurrent/repeat plan-to-prd
8805
+ // runs for one plan converge to a single PRD instead of splitting into
8806
+ // <slug>-<date>.json / -2.json / -3.json. Match by basename.
8807
+ if (!prdFilename && planKey) {
8808
+ for (const otherItem of items) {
8809
+ if (!otherItem || otherItem.id === item.id || !otherItem._prdFilename) continue;
8810
+ if (path.basename(String(otherItem.planFile || '')) === planKey) { prdFilename = otherItem._prdFilename; break; }
8811
+ }
8812
+ if (!prdFilename) {
8813
+ for (const [otherId, m] of mutations) {
8814
+ if (otherId === item.id || !m || !m._prdFilename) continue;
8815
+ if (m._planKey && m._planKey === planKey) { prdFilename = m._prdFilename; break; }
8816
+ }
8817
+ }
8818
+ if (prdFilename) log('info', `plan-to-prd: reusing in-flight PRD "${prdFilename}" for plan "${item.planFile}" (same-plan dedup)`);
8819
+ }
8797
8820
  if (!prdFilename) {
8798
8821
  // Generate unique PRD filename — check prd/, prd/archive/, AND any
8799
8822
  // _prdFilename already pinned by sibling work items (W-mozkn1bb001j66fd):
@@ -8832,7 +8855,9 @@ function discoverCentralWorkItems(config) {
8832
8855
  while (prdExisting.has(prdFilename)) { prdFilename = prdBase + '-' + prdCounter + '.json'; prdCounter++; }
8833
8856
  }
8834
8857
  vars.prd_filename = prdFilename;
8835
- mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _prdFilename: prdFilename }));
8858
+ // Pin _planKey alongside _prdFilename so the same-plan in-flight reuse
8859
+ // above can recognize a sibling prepared earlier THIS tick by plan.
8860
+ mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _prdFilename: prdFilename, _planKey: planKey }));
8836
8861
  vars.branch_strategy_hint = item.branchStrategy
8837
8862
  ? `The user requested **${item.branchStrategy}** strategy. Use this unless the analysis strongly suggests otherwise.`
8838
8863
  : 'Choose the best strategy based on your analysis of item dependencies.';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2275",
3
+ "version": "0.1.2277",
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"