@yemi33/minions 0.1.307 → 0.1.309

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,9 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.307 (2026-04-03)
3
+ ## 0.1.309 (2026-04-03)
4
+
5
+ ### Fixes
6
+ - per-item try-catch in discovery loops — one bad item no longer blocks tick
7
+
8
+ ## 0.1.308 (2026-04-03)
9
+
10
+ ### Features
11
+ - Fix PR write race conditions in lifecycle.js
12
+
13
+ ## 0.1.306 (2026-04-03)
4
14
 
5
15
  ### Features
6
- - guard projects[0] fallbacks in lifecycle.js
7
16
  - Add null guards to dashboard.js request handlers
8
17
 
9
18
  ### Fixes
@@ -142,7 +142,8 @@ function checkPlanCompletion(meta, config) {
142
142
  if (wrote) log('info', `PRD completion summary written to notes/inbox/`);
143
143
 
144
144
  // Persist status and _completionNotified atomically BEFORE creating work items
145
- plan._completionNotified = true;
145
+ // NOTE: Do NOT set plan._completionNotified in-memory before persist —
146
+ // if persist fails, the in-memory flag would prevent retry on next tick.
146
147
  mutateJsonFileLocked(planPath, (data) => {
147
148
  data.status = 'completed';
148
149
  data.completedAt = plan.completedAt;
@@ -624,47 +625,51 @@ function syncPrsFromOutput(output, agentId, meta, config) {
624
625
  const agentName = config.agents?.[agentId]?.name || agentId;
625
626
  let added = 0;
626
627
  const centralPrPath = path.join(MINIONS_DIR, 'pull-requests.json');
627
- // Track which PR files need writing — keyed by target name
628
- const dirtyTargets = new Map(); // name -> { prs, prPath }
629
628
 
629
+ // Group PR matches by target file so we take one lock per target
630
+ const targetPrIds = new Map(); // targetName -> { prPath, prIds: [{ prId, fullId }] }
630
631
  for (const prId of prMatches) {
631
632
  const fullId = `PR-${prId}`;
632
633
  const targetProject = useCentral ? null : resolveProjectForPr(prId);
633
634
  const targetName = targetProject ? targetProject.name : '_central';
634
635
  const prPath = targetProject ? shared.projectPrPath(targetProject) : centralPrPath;
635
-
636
- // Load PRs for this target (cache per target)
637
- if (!dirtyTargets.has(targetName)) {
638
- dirtyTargets.set(targetName, { prs: safeJson(prPath) || [], prPath });
639
- }
640
- const entry = dirtyTargets.get(targetName);
641
- if (entry.prs.some(p => p.id === fullId || String(p.id) === String(prId))) continue;
642
-
643
- let title = meta?.item?.title || '';
644
- const titleMatch = output.match(new RegExp(`${prId}[^\\n]*?[—–-]\\s*([^\\n]+)`, 'i'));
645
- if (titleMatch) title = titleMatch[1].trim();
646
- if (title.includes('session_id') || title.includes('is_error') || title.includes('uuid') || title.length > 120) {
647
- title = meta?.item?.title || '';
636
+ if (!targetPrIds.has(targetName)) {
637
+ targetPrIds.set(targetName, { prPath, prIds: [] });
648
638
  }
649
- entry.prs.push({
650
- id: fullId,
651
- title: (title || `PR created by ${agentName}`).slice(0, 120),
652
- agent: agentName,
653
- branch: meta?.branch || '',
654
- reviewStatus: 'pending',
655
- status: 'active',
656
- created: dateStamp(),
657
- url: extractPrUrl(prId),
658
- prdItems: meta?.item?.id ? [meta.item.id] : [],
659
- sourcePlan: meta?.item?.sourcePlan || '',
660
- itemType: meta?.item?.itemType || ''
661
- });
662
- if (meta?.item?.id) addPrLink(fullId, meta.item.id);
663
- added++;
639
+ targetPrIds.get(targetName).prIds.push({ prId, fullId });
664
640
  }
665
641
 
666
- for (const [name, entry] of dirtyTargets) {
667
- shared.safeWrite(entry.prPath, entry.prs);
642
+ // For each target, read+modify+write inside a single locked callback
643
+ for (const [name, { prPath, prIds }] of targetPrIds) {
644
+ mutateJsonFileLocked(prPath, (prs) => {
645
+ if (!Array.isArray(prs)) prs = [];
646
+ for (const { prId, fullId } of prIds) {
647
+ if (prs.some(p => p.id === fullId || String(p.id) === String(prId))) continue;
648
+
649
+ let title = meta?.item?.title || '';
650
+ const titleMatch = output.match(new RegExp(`${prId}[^\\n]*?[—–-]\\s*([^\\n]+)`, 'i'));
651
+ if (titleMatch) title = titleMatch[1].trim();
652
+ if (title.includes('session_id') || title.includes('is_error') || title.includes('uuid') || title.length > 120) {
653
+ title = meta?.item?.title || '';
654
+ }
655
+ prs.push({
656
+ id: fullId,
657
+ title: (title || `PR created by ${agentName}`).slice(0, 120),
658
+ agent: agentName,
659
+ branch: meta?.branch || '',
660
+ reviewStatus: 'pending',
661
+ status: 'active',
662
+ created: dateStamp(),
663
+ url: extractPrUrl(prId),
664
+ prdItems: meta?.item?.id ? [meta.item.id] : [],
665
+ sourcePlan: meta?.item?.sourcePlan || '',
666
+ itemType: meta?.item?.itemType || ''
667
+ });
668
+ if (meta?.item?.id) addPrLink(fullId, meta.item.id);
669
+ added++;
670
+ }
671
+ return prs;
672
+ }, { defaultValue: [] });
668
673
  log('info', `Synced PR(s) from ${agentName}'s output to ${name === '_central' ? 'central' : name}/pull-requests.json`);
669
674
  }
670
675
  return added;
package/engine.js CHANGED
@@ -1373,6 +1373,7 @@ function discoverFromWorkItems(config, project) {
1373
1373
  let needsWrite = false;
1374
1374
 
1375
1375
  for (const item of items) {
1376
+ try {
1376
1377
  // Re-evaluate failed items: if deps have recovered, reset to pending
1377
1378
  if (item.status === 'failed' && item.failReason === 'Dependency failed — cannot proceed') {
1378
1379
  const depStatus = areDependenciesMet(item, config);
@@ -1562,6 +1563,7 @@ function discoverFromWorkItems(config, project) {
1562
1563
  });
1563
1564
 
1564
1565
  setCooldown(key);
1566
+ } catch (err) { log('warn', `discoverFromWorkItems: skipping ${item.id}: ${err.message}`); }
1565
1567
  }
1566
1568
 
1567
1569
  // Write back updated statuses (always, since we mark items dispatched before newWork check)
@@ -1768,6 +1770,7 @@ function discoverCentralWorkItems(config) {
1768
1770
  const newWork = [];
1769
1771
 
1770
1772
  for (const item of items) {
1773
+ try {
1771
1774
  if (item.status !== 'queued' && item.status !== 'pending') continue;
1772
1775
 
1773
1776
  const key = `central-work-${item.id}`;
@@ -2001,6 +2004,7 @@ function discoverCentralWorkItems(config) {
2001
2004
  item.dispatched_to = agentId;
2002
2005
  setCooldown(key);
2003
2006
  }
2007
+ } catch (err) { log('warn', `discoverCentralWorkItems: skipping ${item.id}: ${err.message}`); }
2004
2008
  }
2005
2009
 
2006
2010
  if (newWork.length > 0) safeWrite(centralPath, items);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.307",
3
+ "version": "0.1.309",
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"