@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 +11 -2
- package/engine/lifecycle.js +38 -33
- package/engine.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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
|
package/engine/lifecycle.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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.
|
|
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"
|