@yemi33/minions 0.1.869 → 0.1.870
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 +2 -1
- package/engine.js +82 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.870 (2026-04-11)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- ticking runtime counter on working agent cards
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- optimistic running state on pipeline Run Now click
|
|
10
10
|
|
|
11
11
|
### Fixes
|
|
12
|
+
- dep merge failures identify conflicting branch and auto-queue fix (closes #814) (#882)
|
|
12
13
|
- always show doc-chat expand bar when thread is collapsed
|
|
13
14
|
- poll for old process exit before steering resume (replaces fixed delay)
|
|
14
15
|
- add 3s delay before steering resume to let old process tree exit
|
package/engine.js
CHANGED
|
@@ -25,7 +25,8 @@ const fs = require('fs');
|
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const shared = require('./engine/shared');
|
|
27
27
|
const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS: DEFAULTS,
|
|
28
|
-
WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS
|
|
28
|
+
WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS,
|
|
29
|
+
FAILURE_CLASS } = shared;
|
|
29
30
|
const queries = require('./engine/queries');
|
|
30
31
|
|
|
31
32
|
// ─── Paths ──────────────────────────────────────────────────────────────────
|
|
@@ -149,6 +150,22 @@ let engineRestartGraceUntil = 0; // timestamp — suppress orphan detection unti
|
|
|
149
150
|
// Per-tick cache of refs that failed to fetch — avoids repeating 30s ETIMEDOUT for same missing ref
|
|
150
151
|
// Cleared at the start of each tick cycle (see tickInner)
|
|
151
152
|
const _failedRefCache = new Set();
|
|
153
|
+
|
|
154
|
+
// Parse conflicting file names from git merge error output (stderr/stdout combined)
|
|
155
|
+
function parseConflictFiles(mergeOutput) {
|
|
156
|
+
const files = [];
|
|
157
|
+
// Match "CONFLICT (content): Merge conflict in <file>" lines
|
|
158
|
+
const conflictRe = /CONFLICT \([^)]*\): .*?(?:in|for) (.+)/g;
|
|
159
|
+
let m;
|
|
160
|
+
while ((m = conflictRe.exec(mergeOutput)) !== null) files.push(m[1].trim());
|
|
161
|
+
// Also match "Auto-merging <file>" followed by conflict (less specific, fallback)
|
|
162
|
+
if (files.length === 0) {
|
|
163
|
+
const autoMergeRe = /Auto-merging (.+)/g;
|
|
164
|
+
while ((m = autoMergeRe.exec(mergeOutput)) !== null) files.push(m[1].trim());
|
|
165
|
+
}
|
|
166
|
+
return [...new Set(files)]; // dedupe
|
|
167
|
+
}
|
|
168
|
+
|
|
152
169
|
const _FAST_WORK_TYPES = new Set([WORK_TYPE.EXPLORE, WORK_TYPE.ASK, WORK_TYPE.REVIEW]);
|
|
153
170
|
const _MAX_TURNS_BY_TYPE = {
|
|
154
171
|
[WORK_TYPE.EXPLORE]: 30, [WORK_TYPE.ASK]: 20, [WORK_TYPE.REVIEW]: 30,
|
|
@@ -451,6 +468,8 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
451
468
|
try {
|
|
452
469
|
const depBranches = resolveDependencyBranches(depIds, meta?.item?.sourcePlan, project, config);
|
|
453
470
|
let depMergeFailed = false;
|
|
471
|
+
let depConflictBranch = null; // track which dep branch caused the conflict
|
|
472
|
+
let depConflictFiles = []; // conflicting file names parsed from git output
|
|
454
473
|
// Fetch all dependency branches in parallel (git fetches are independent)
|
|
455
474
|
const fetchable = depBranches.filter(d => !_failedRefCache.has(d.branch));
|
|
456
475
|
const unfetchable = depBranches.filter(d => _failedRefCache.has(d.branch));
|
|
@@ -525,8 +544,28 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
525
544
|
}
|
|
526
545
|
log('info', `Successfully re-merged all ${fetched.length} dep branches after reset for ${branchName}`);
|
|
527
546
|
} catch (resetErr) {
|
|
547
|
+
const errOutput = (resetErr.message || '') + '\n' + (resetErr.stdout?.toString?.() || '') + '\n' + (resetErr.stderr?.toString?.() || '');
|
|
528
548
|
log('warn', `Failed to reset and re-merge deps for ${branchName}: ${resetErr.message}`);
|
|
529
549
|
try { await execAsync(`git merge --abort`, { ..._gitOpts, cwd: worktreePath }); } catch (_) { /* no merge in progress */ }
|
|
550
|
+
// Identify which dep branch caused the conflict during re-merge
|
|
551
|
+
// The last branch attempted in the re-merge loop is the one that failed
|
|
552
|
+
for (const { branch: reBranch2 } of fetched) {
|
|
553
|
+
try {
|
|
554
|
+
const mainRef2 = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
|
|
555
|
+
const mergeBase = (await execAsync(`git merge-base "origin/${mainRef2}" "origin/${reBranch2}"`, { ..._gitOpts, cwd: rootDir })).stdout.toString().trim();
|
|
556
|
+
const treeResult = await execAsync(`git merge-tree "${mergeBase}" "origin/${mainRef2}" "origin/${reBranch2}"`, { ..._gitOpts, cwd: rootDir });
|
|
557
|
+
const treeOutput = treeResult.stdout?.toString?.() || '';
|
|
558
|
+
if (treeOutput.includes('<<<<<<<') || treeOutput.includes('changed in both')) {
|
|
559
|
+
depConflictBranch = reBranch2;
|
|
560
|
+
depConflictFiles = parseConflictFiles(treeOutput);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
} catch (_e) { /* merge-tree may fail — continue checking other branches */ }
|
|
564
|
+
}
|
|
565
|
+
// Fallback: parse conflict files from the error output if merge-tree didn't identify them
|
|
566
|
+
if (!depConflictBranch) {
|
|
567
|
+
depConflictFiles = parseConflictFiles(errOutput);
|
|
568
|
+
}
|
|
530
569
|
depMergeFailed = true;
|
|
531
570
|
}
|
|
532
571
|
break;
|
|
@@ -535,7 +574,47 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
535
574
|
}
|
|
536
575
|
if (depMergeFailed) {
|
|
537
576
|
_cleanupPromptFiles();
|
|
538
|
-
|
|
577
|
+
// Build actionable failReason identifying the conflicting branch and files
|
|
578
|
+
let failReason = 'Dependency merge failed';
|
|
579
|
+
if (depConflictBranch) {
|
|
580
|
+
failReason = `Dependency merge failed: ${depConflictBranch} conflicts with ${sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch))}`;
|
|
581
|
+
if (depConflictFiles.length > 0) failReason += ` in ${depConflictFiles.slice(0, 5).join(', ')}`;
|
|
582
|
+
failReason += ' — dep branch needs updating';
|
|
583
|
+
}
|
|
584
|
+
completeDispatch(id, DISPATCH_RESULT.ERROR, failReason, '', { failureClass: FAILURE_CLASS.MERGE_CONFLICT });
|
|
585
|
+
|
|
586
|
+
// Auto-queue conflict-fix work item when a specific dep branch is identified
|
|
587
|
+
if (depConflictBranch && meta?.item?.id && project) {
|
|
588
|
+
try {
|
|
589
|
+
const wiPath = project.name
|
|
590
|
+
? projectWorkItemsPath(project)
|
|
591
|
+
: path.join(MINIONS_DIR, 'work-items.json');
|
|
592
|
+
const conflictFixId = `conflict-fix-${depConflictBranch.replace(/[^a-zA-Z0-9-]/g, '-')}`;
|
|
593
|
+
const filesDesc = depConflictFiles.length > 0
|
|
594
|
+
? `\n\nConflicting files:\n${depConflictFiles.map(f => '- ' + f).join('\n')}`
|
|
595
|
+
: '';
|
|
596
|
+
const mainBranch = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
|
|
597
|
+
mutateWorkItems(wiPath, items => {
|
|
598
|
+
// Don't create duplicate conflict-fix items
|
|
599
|
+
const existing = items.find(i => i.id === conflictFixId && i.status !== WI_STATUS.DONE && i.status !== WI_STATUS.FAILED && i.status !== WI_STATUS.CANCELLED);
|
|
600
|
+
if (existing) return;
|
|
601
|
+
items.push({
|
|
602
|
+
id: conflictFixId,
|
|
603
|
+
title: `Fix merge conflict: ${depConflictBranch} conflicts with ${mainBranch}`,
|
|
604
|
+
type: WORK_TYPE.FIX,
|
|
605
|
+
priority: 'high',
|
|
606
|
+
status: WI_STATUS.PENDING,
|
|
607
|
+
description: `Branch \`${depConflictBranch}\` conflicts with \`${mainBranch}\`. Merge ${mainBranch} into the branch and resolve conflicts, then push.${filesDesc}\n\nBlocked downstream item: \`${meta.item.id}\` — ${meta.item.title || ''}`,
|
|
608
|
+
created: ts(),
|
|
609
|
+
createdBy: 'engine:dep-conflict-fix',
|
|
610
|
+
_branch: depConflictBranch,
|
|
611
|
+
_blockedItem: meta.item.id,
|
|
612
|
+
project: project.name || null,
|
|
613
|
+
});
|
|
614
|
+
log('info', `Auto-queued conflict-fix work item ${conflictFixId} for ${depConflictBranch} (blocked: ${meta.item.id})`);
|
|
615
|
+
});
|
|
616
|
+
} catch (e) { log('warn', `Failed to auto-queue conflict-fix: ${e.message}`); }
|
|
617
|
+
}
|
|
539
618
|
return;
|
|
540
619
|
}
|
|
541
620
|
} catch (e) {
|
|
@@ -3088,6 +3167,7 @@ module.exports = {
|
|
|
3088
3167
|
|
|
3089
3168
|
// Shared helpers (used by lifecycle.js and tests)
|
|
3090
3169
|
reconcileItemsWithPrs, detectDependencyCycles,
|
|
3170
|
+
parseConflictFiles, // exported for testing
|
|
3091
3171
|
|
|
3092
3172
|
// Playbooks
|
|
3093
3173
|
renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS, buildWorkItemDispatchVars,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.870",
|
|
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"
|