@yemi33/minions 0.1.307 → 0.1.308
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 +6 -2
- package/engine/lifecycle.js +38 -33
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.308 (2026-04-03)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- Fix PR write race conditions in lifecycle.js
|
|
7
|
+
|
|
8
|
+
## 0.1.306 (2026-04-03)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- guard projects[0] fallbacks in lifecycle.js
|
|
7
11
|
- Add null guards to dashboard.js request handlers
|
|
8
12
|
|
|
9
13
|
### 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.308",
|
|
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"
|