@yemi33/minions 0.1.476 → 0.1.477

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,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.476 (2026-04-07)
3
+ ## 0.1.477 (2026-04-07)
4
4
 
5
5
  ### Features
6
+ - Add mtime-based caching to getPrdInfo()
6
7
  - Optimize getAgentStatus() to read only head+tail of live-output.log
7
8
  - Fix unlocked metrics.json in lifecycle.js post-merge hook
8
9
  - Fix unlocked metrics.json read-modify-write in trackEngineUsage
package/engine/queries.js CHANGED
@@ -627,12 +627,51 @@ function getWorkItems(config) {
627
627
 
628
628
  // ── PRD Progress ────────────────────────────────────────────────────────────
629
629
 
630
+ // Module-level caches for getPrdInfo() — avoids re-reading unchanged PRD files
631
+ const _prdFileCache = new Map(); // filePath → { mtimeMs, plan }
632
+ let _prdDirMtimes = { prd: 0, archive: 0 }; // directory mtimes to detect new/deleted files
633
+ let _prdResultCache = null; // cached final result
634
+ let _prdResultInputHash = ''; // hash of all input mtimes to detect any change
635
+
636
+ /**
637
+ * Collect mtimes of all input files that affect getPrdInfo() output.
638
+ * Returns a string hash for quick equality check plus the dir mtimes.
639
+ */
640
+ function _getPrdInputHash(projects) {
641
+ const mtimes = [];
642
+ // PRD directory mtimes (detect new/deleted files)
643
+ let prdDirMtime = 0, archiveDirMtime = 0;
644
+ try { prdDirMtime = fs.statSync(PRD_DIR).mtimeMs; } catch { /* optional */ }
645
+ const archiveDir = path.join(PRD_DIR, 'archive');
646
+ try { archiveDirMtime = fs.statSync(archiveDir).mtimeMs; } catch { /* optional */ }
647
+ mtimes.push(prdDirMtime, archiveDirMtime);
648
+ // Work-items file mtimes (affect status display)
649
+ for (const project of projects) {
650
+ try { mtimes.push(fs.statSync(projectWorkItemsPath(project)).mtimeMs); } catch { mtimes.push(0); }
651
+ }
652
+ try { mtimes.push(fs.statSync(path.join(MINIONS_DIR, 'work-items.json')).mtimeMs); } catch { mtimes.push(0); }
653
+ // PR file mtimes (affect PR links)
654
+ for (const project of projects) {
655
+ try { mtimes.push(fs.statSync(projectPrPath(project)).mtimeMs); } catch { mtimes.push(0); }
656
+ }
657
+ return { hash: mtimes.join(','), prdDirMtime, archiveDirMtime };
658
+ }
659
+
630
660
  function getPrdInfo(config) {
631
661
  config = config || getConfig();
632
662
  const projects = getProjects(config);
663
+
664
+ // Quick mtime check — return cached result if nothing changed
665
+ const { hash, prdDirMtime, archiveDirMtime } = _getPrdInputHash(projects);
666
+ if (_prdResultCache && hash === _prdResultInputHash) return _prdResultCache;
667
+
633
668
  let allPrdItems = [];
634
669
  let latestStat = null;
635
670
 
671
+ // Check if directory listings need refresh
672
+ const dirsChanged = prdDirMtime !== _prdDirMtimes.prd || archiveDirMtime !== _prdDirMtimes.archive;
673
+ _prdDirMtimes = { prd: prdDirMtime, archive: archiveDirMtime };
674
+
636
675
  // Scan active PRDs and archived PRDs (completed PRDs still need to show progress)
637
676
  const planDirs = [
638
677
  { dir: PRD_DIR, archived: false },
@@ -643,10 +682,21 @@ function getPrdInfo(config) {
643
682
  const planFiles = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
644
683
  for (const pf of planFiles) {
645
684
  try {
646
- const plan = safeJson(path.join(dir, pf));
647
- if (!plan || !plan.missing_features) continue;
648
- const stat = fs.statSync(path.join(dir, pf));
685
+ const filePath = path.join(dir, pf);
686
+ const stat = fs.statSync(filePath);
649
687
  if (!latestStat || stat.mtimeMs > latestStat.mtimeMs) latestStat = stat;
688
+
689
+ // Per-file mtime cache: only re-read files that changed
690
+ const cached = _prdFileCache.get(filePath);
691
+ let plan;
692
+ if (cached && cached.mtimeMs === stat.mtimeMs) {
693
+ plan = cached.plan;
694
+ } else {
695
+ plan = safeJson(filePath);
696
+ _prdFileCache.set(filePath, { mtimeMs: stat.mtimeMs, plan });
697
+ }
698
+ if (!plan || !plan.missing_features) continue;
699
+
650
700
  // Staleness: compare source plan mtime to recorded sourcePlanModifiedAt
651
701
  let planStale = false;
652
702
  if (!archived && plan.source_plan) {
@@ -668,6 +718,12 @@ function getPrdInfo(config) {
668
718
  }
669
719
  } catch { /* optional */ }
670
720
  }
721
+ // Clean stale entries from file cache when dirs changed
722
+ if (dirsChanged) {
723
+ for (const cachedPath of _prdFileCache.keys()) {
724
+ if (cachedPath.startsWith(dir) && !fs.existsSync(cachedPath)) _prdFileCache.delete(cachedPath);
725
+ }
726
+ }
671
727
  } catch { /* optional */ }
672
728
  }
673
729
 
@@ -766,7 +822,18 @@ function getPrdInfo(config) {
766
822
  missingList: items.filter(i => i.status === 'missing').map(f => ({ id: f.id, name: f.name || f.title, priority: f.priority, complexity: f.estimated_complexity || f.size })),
767
823
  };
768
824
 
769
- return { progress, status };
825
+ const result = { progress, status };
826
+ _prdResultCache = result;
827
+ _prdResultInputHash = hash;
828
+ return result;
829
+ }
830
+
831
+ /** Reset PRD info cache — exported for testing */
832
+ function resetPrdInfoCache() {
833
+ _prdFileCache.clear();
834
+ _prdDirMtimes = { prd: 0, archive: 0 };
835
+ _prdResultCache = null;
836
+ _prdResultInputHash = '';
770
837
  }
771
838
 
772
839
  // ── Exports ─────────────────────────────────────────────────────────────────
@@ -779,6 +846,7 @@ module.exports = {
779
846
  // Helpers
780
847
  timeSince,
781
848
  readHeadTail, // exported for testing
849
+ resetPrdInfoCache,
782
850
 
783
851
  // Core state
784
852
  getConfig, getControl, getDispatch, getDispatchQueue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.476",
3
+ "version": "0.1.477",
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"