@yemi33/minions 0.1.1680 → 0.1.1682

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/engine.js CHANGED
@@ -1767,6 +1767,16 @@ function buildWiDescription(item, planFile) {
1767
1767
 
1768
1768
  function materializePlansAsWorkItems(config) {
1769
1769
  if (!fs.existsSync(PRD_DIR)) { try { fs.mkdirSync(PRD_DIR, { recursive: true }); } catch (e) { log('warn', 'create PRD directory: ' + e.message); } }
1770
+ const writePrdLocked = (fileName, data) => {
1771
+ return mutateJsonFileLocked(path.join(PRD_DIR, fileName), () => data, { defaultValue: data });
1772
+ };
1773
+ const mutatePrdLocked = (fileName, fallback, mutator, options = {}) => {
1774
+ return mutateJsonFileLocked(path.join(PRD_DIR, fileName), (current) => {
1775
+ if (!current?.missing_features && fallback?.missing_features) current = fallback;
1776
+ if (!current || Array.isArray(current) || typeof current !== 'object') current = {};
1777
+ return mutator(current) || current;
1778
+ }, { defaultValue: fallback || {}, ...options });
1779
+ };
1770
1780
 
1771
1781
  // Enforce: PRDs must be .json — auto-rename .md files that contain valid PRD JSON
1772
1782
  // Check both prd/ and plans/ (agents may still write JSON to plans/)
@@ -1782,7 +1792,7 @@ function materializePlansAsWorkItems(config) {
1782
1792
  const parsed = JSON.parse(stripped);
1783
1793
  if (parsed.missing_features) {
1784
1794
  const jsonName = mf.replace(/\.md$/, '.json');
1785
- safeWrite(path.join(PRD_DIR, jsonName), parsed);
1795
+ writePrdLocked(jsonName, parsed);
1786
1796
  try { fs.unlinkSync(path.join(checkDir, mf)); } catch { /* cleanup */ }
1787
1797
  log('info', `Plan enforcement: moved ${mf} → prd/${jsonName} (PRDs must be .json in prd/)`);
1788
1798
  }
@@ -1797,7 +1807,7 @@ function materializePlansAsWorkItems(config) {
1797
1807
  try {
1798
1808
  const parsed = safeJson(path.join(PLANS_DIR, jf));
1799
1809
  if (parsed?.missing_features) {
1800
- safeWrite(path.join(PRD_DIR, jf), parsed);
1810
+ writePrdLocked(jf, parsed);
1801
1811
  try { fs.unlinkSync(path.join(PLANS_DIR, jf)); } catch { /* cleanup */ }
1802
1812
  log('info', `Auto-migrated PRD ${jf} from plans/ to prd/`);
1803
1813
  }
@@ -1814,7 +1824,7 @@ function materializePlansAsWorkItems(config) {
1814
1824
  const SEQUENTIAL_ID_RE = /^P-?\d+$/;
1815
1825
 
1816
1826
  for (const file of planFiles) {
1817
- const plan = safeJson(path.join(PRD_DIR, file));
1827
+ let plan = safeJson(path.join(PRD_DIR, file));
1818
1828
  if (!plan?.missing_features) continue;
1819
1829
 
1820
1830
  // ID collision prevention: remap sequential IDs (P-001, P-002) to globally unique P-<uid> IDs.
@@ -1826,19 +1836,25 @@ function materializePlansAsWorkItems(config) {
1826
1836
  const anyMaterialized = plan.missing_features.some(f =>
1827
1837
  SEQUENTIAL_ID_RE.test(f.id) && allWorkItems.some(w => w.id === f.id && w.sourcePlan === file));
1828
1838
  if (!anyMaterialized) {
1829
- const idMap = new Map();
1830
- for (const f of plan.missing_features) {
1831
- if (SEQUENTIAL_ID_RE.test(f.id)) {
1832
- const newId = 'P-' + shared.uid();
1833
- idMap.set(f.id, newId);
1834
- f.id = newId;
1839
+ let remappedCount = 0;
1840
+ plan = mutatePrdLocked(file, plan, (current) => {
1841
+ const features = Array.isArray(current.missing_features) ? current.missing_features : [];
1842
+ if (!features.some(f => SEQUENTIAL_ID_RE.test(f.id))) return current;
1843
+ const idMap = new Map();
1844
+ for (const f of features) {
1845
+ if (SEQUENTIAL_ID_RE.test(f.id)) {
1846
+ const newId = 'P-' + shared.uid();
1847
+ idMap.set(f.id, newId);
1848
+ f.id = newId;
1849
+ }
1835
1850
  }
1836
- }
1837
- for (const f of plan.missing_features) {
1838
- if (f.depends_on) f.depends_on = f.depends_on.map(d => idMap.get(d) || d);
1839
- }
1840
- safeWrite(path.join(PRD_DIR, file), plan);
1841
- log('info', `Remapped ${idMap.size} sequential ID(s) in ${file} to prevent cross-PRD collisions`);
1851
+ for (const f of features) {
1852
+ if (f.depends_on) f.depends_on = f.depends_on.map(d => idMap.get(d) || d);
1853
+ }
1854
+ remappedCount = idMap.size;
1855
+ return current;
1856
+ });
1857
+ if (remappedCount > 0) log('info', `Remapped ${remappedCount} sequential ID(s) in ${file} to prevent cross-PRD collisions`);
1842
1858
  }
1843
1859
  }
1844
1860
  } catch (e) { log('warn', `Sequential ID remapping failed for ${file}: ${e.message}`); }
@@ -1851,25 +1867,26 @@ function materializePlansAsWorkItems(config) {
1851
1867
  const recorded = plan.sourcePlanModifiedAt ? new Date(plan.sourcePlanModifiedAt).getTime() : null;
1852
1868
  if (!recorded) {
1853
1869
  // First time seeing this plan — record baseline mtime (no clean needed)
1854
- plan.sourcePlanModifiedAt = new Date(sourceMtime).toISOString();
1855
- safeWrite(path.join(PRD_DIR, file), plan);
1870
+ plan = mutatePrdLocked(file, plan, (current) => {
1871
+ if (!current.sourcePlanModifiedAt) current.sourcePlanModifiedAt = new Date(sourceMtime).toISOString();
1872
+ return current;
1873
+ }, { skipWriteIfUnchanged: true });
1856
1874
  } else if (sourceMtime > recorded) {
1857
1875
  // Source plan changed — auto-clean pending/failed items so they re-materialize with updated data
1858
1876
  log('info', `Source plan ${plan.source_plan} updated — re-syncing PRD ${file}`);
1859
1877
  autoCleanPrdWorkItems(file, config);
1860
- plan.sourcePlanModifiedAt = new Date(sourceMtime).toISOString();
1861
- plan.lastSyncedFromPlan = ts();
1862
1878
 
1863
1879
  // Handle PRD based on current status
1864
1880
  const prdStatus = plan.status || (plan.requires_approval ? 'awaiting-approval' : null);
1865
1881
 
1866
- // Flag stale for all statuses — user decides when to regenerate/resume from dashboard
1867
- if (prdStatus) {
1868
- plan.planStale = true;
1869
- log('info', `PRD ${file} flagged as stale (plan revised while ${prdStatus}) — user can regenerate from dashboard`);
1870
- }
1871
-
1872
- safeWrite(path.join(PRD_DIR, file), plan);
1882
+ plan = mutatePrdLocked(file, plan, (current) => {
1883
+ current.sourcePlanModifiedAt = new Date(sourceMtime).toISOString();
1884
+ current.lastSyncedFromPlan = ts();
1885
+ const currentPrdStatus = current.status || (current.requires_approval ? 'awaiting-approval' : null);
1886
+ if (currentPrdStatus) current.planStale = true;
1887
+ return current;
1888
+ });
1889
+ if (prdStatus) log('info', `PRD ${file} flagged as stale (plan revised while ${prdStatus}) — user can regenerate from dashboard`);
1873
1890
  }
1874
1891
  } catch (e) { log('warn', 'plan staleness check: ' + e.message); }
1875
1892
  }
@@ -1879,10 +1896,12 @@ function materializePlansAsWorkItems(config) {
1879
1896
  const planStatus = plan.status || (plan.requires_approval ? 'awaiting-approval' : null);
1880
1897
  if (planStatus === 'awaiting-approval') {
1881
1898
  if (config.engine?.autoApprovePlans) {
1882
- plan.status = 'approved';
1883
- plan.approvedAt = ts();
1884
- plan.approvedBy = 'auto-mode';
1885
- safeWrite(path.join(PRD_DIR, file), plan);
1899
+ plan = mutatePrdLocked(file, plan, (current) => {
1900
+ current.status = 'approved';
1901
+ current.approvedAt = ts();
1902
+ current.approvedBy = 'auto-mode';
1903
+ return current;
1904
+ });
1886
1905
  log('info', `Auto-approved plan: ${file}`);
1887
1906
  } else {
1888
1907
  continue; // Skip — waiting for human approval
@@ -3389,7 +3408,7 @@ function discoverCentralWorkItems(config) {
3389
3408
  vars.plan_file = planFileName;
3390
3409
  vars.task_description = item.title;
3391
3410
  // Notes already populated by buildWorkItemDispatchVars — no need to re-read
3392
- // Track expected plan filename in meta for chainPlanToPrd
3411
+ // Track expected plan filename for artifacts and follow-up plan-to-prd prompts.
3393
3412
  mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _planFileName: planFileName }));
3394
3413
  }
3395
3414
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1680",
3
+ "version": "0.1.1682",
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"