@yemi33/minions 0.1.2027 → 0.1.2028

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.
Files changed (2) hide show
  1. package/engine.js +73 -4
  2. package/package.json +1 -1
package/engine.js CHANGED
@@ -588,7 +588,23 @@ function resolveDependencyBranches(depIds, sourcePlan, project, config) {
588
588
  *
589
589
  * @returns {Promise<{skipped: boolean, reason?: string}>}
590
590
  */
591
- async function syncReusedWorktree(rootDir, worktreePath, branchName, gitOpts = {}) {
591
+ async function syncReusedWorktree(rootDir, worktreePath, branchName, gitOpts = {}, opts = {}) {
592
+ // W-mph6n4p00006ce38: Freshen origin/<mainRef> and fast-forward the
593
+ // worktree to it BEFORE the branch sync. Without this, reused worktrees
594
+ // inherit whatever stale local master the engine clone happens to be on,
595
+ // so downstream dep-merges (and zero-dep agent work) get layered onto a
596
+ // stale base. Shared-branch / useExistingBranch dispatches MUST NOT
597
+ // auto-pick-up master mid-flight, so the carve-out is explicit.
598
+ const { mainRef, isSharedBranch } = opts;
599
+ if (mainRef && !isSharedBranch) {
600
+ try { await shared.shellSafeGit(['fetch', 'origin', mainRef], { ...gitOpts, cwd: rootDir }); }
601
+ catch (e) { log('warn', `git: failed to fetch origin/${mainRef} during reuse-sync: ${e.message}`); }
602
+ try { await shared.shellSafeGit(['merge', `origin/${mainRef}`, '--no-edit', '--no-ff'], { ...gitOpts, cwd: worktreePath }); }
603
+ catch (e) {
604
+ log('warn', `git: failed to merge origin/${mainRef} into ${branchName} during reuse-sync: ${e.message}`);
605
+ try { await shared.shellSafeGit(['merge', '--abort'], { ...gitOpts, cwd: worktreePath }); } catch (_) { /* no merge in progress */ }
606
+ }
607
+ }
592
608
  // ls-remote --exit-code returns 2 when no matching refs are found on the
593
609
  // remote. The probe only lists refs (no object transfer), so it's cheap
594
610
  // even on slow links.
@@ -1063,7 +1079,12 @@ async function spawnAgent(dispatchItem, config) {
1063
1079
  // (orphan/timeout retry before first push) would otherwise emit a
1064
1080
  // "couldn't find remote ref" warn pair on every reuse.
1065
1081
  _phaseT.reuseSyncStart = Date.now();
1066
- await syncReusedWorktree(rootDir, existingWt, branchName, _gitOpts);
1082
+ // W-mph6n4p00006ce38: hand syncReusedWorktree the main ref + shared-branch
1083
+ // flag so it can freshen origin/<mainRef> into the worktree (skipped on
1084
+ // shared-branch dispatches — see syncReusedWorktree's opts contract).
1085
+ const _reuseMainRef = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
1086
+ const _reuseIsShared = meta?.branchStrategy === 'shared-branch' || meta?.useExistingBranch;
1087
+ await syncReusedWorktree(rootDir, existingWt, branchName, _gitOpts, { mainRef: _reuseMainRef, isSharedBranch: _reuseIsShared });
1067
1088
  _phaseT.reuseSyncEnd = Date.now();
1068
1089
  } else {
1069
1090
  _phaseT.createWorktreeStart = Date.now();
@@ -1167,8 +1188,24 @@ async function spawnAgent(dispatchItem, config) {
1167
1188
  } else {
1168
1189
  log('info', `Creating worktree: ${worktreePath} on branch ${branchName}`);
1169
1190
  const mainRef = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
1191
+ // W-mph6n4p00006ce38: mirror the pool-borrow path (~line 1110-1114)
1192
+ // — fetch fresh origin/<mainRef> and start the new branch off it,
1193
+ // not the local ref. Without this, fresh-create dispatches inherit
1194
+ // whatever stale local master the engine clone happens to be on
1195
+ // (most painful: long-lived engine processes between restarts).
1196
+ // Non-fatal: if the fetch fails (network blip, transient auth),
1197
+ // fall back to local mainRef so the dispatch still progresses;
1198
+ // the dep-merge phase's own fetch + the on-failure
1199
+ // `git reset --hard origin/<mainRef>` recovery remain as safety nets.
1200
+ let _freshCreateBase = mainRef;
1170
1201
  try {
1171
- await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, mainRef], _worktreeGitOpts, worktreeCreateRetries);
1202
+ await shared.shellSafeGit(['fetch', 'origin', mainRef], { ..._gitOpts, cwd: rootDir, timeout: 30000 });
1203
+ _freshCreateBase = `origin/${mainRef}`;
1204
+ } catch (mainFetchErr) {
1205
+ log('warn', `Failed to fetch origin/${mainRef} before fresh-create worktree for ${branchName}: ${mainFetchErr.message} — falling back to local ${mainRef}`);
1206
+ }
1207
+ try {
1208
+ await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, _freshCreateBase], _worktreeGitOpts, worktreeCreateRetries);
1172
1209
  } catch (e1) {
1173
1210
  const branchExists = e1.message?.includes('already exists');
1174
1211
  log('warn', `Worktree -b failed for ${branchName}: ${e1.message?.split('\n')[0]}`);
@@ -1180,7 +1217,7 @@ async function spawnAgent(dispatchItem, config) {
1180
1217
  // Clean up partial worktree directory from failed attempt
1181
1218
  try { if (fs.existsSync(worktreePath)) fs.rmSync(worktreePath, { recursive: true, force: true }); } catch { /* optional */ }
1182
1219
  try {
1183
- await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, mainRef], _worktreeGitOpts, 0);
1220
+ await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, _freshCreateBase], _worktreeGitOpts, 0);
1184
1221
  } catch (e1b) {
1185
1222
  log('error', `Worktree -b retry also failed for ${branchName}: ${e1b.message?.split('\n')[0]}`);
1186
1223
  throw e1b;
@@ -1303,6 +1340,38 @@ async function spawnAgent(dispatchItem, config) {
1303
1340
  // of FAILURE_CLASS.MERGE_CONFLICT (which retries 3x against the
1304
1341
  // same broken auth path).
1305
1342
  let _depAuthFailed = false;
1343
+ // W-mph6n4p00006ce38: Refresh origin/<mainRef> + freshen the worktree
1344
+ // BEFORE the parallel dep fetches and preflight sim. Without this,
1345
+ // preflight + real dep-merges run against whatever stale local master
1346
+ // the engine clone happens to be on, producing false-positive
1347
+ // conflicts whenever master drifted ahead of the engine's last fetch.
1348
+ // The pool-borrow + fresh-create paths already start at fresh
1349
+ // origin/<mainRef>, but the reuse path can outrun its sync; and even
1350
+ // for the start-fresh paths, master can advance between worktree
1351
+ // creation and dep-merge (long-lived dispatches). Shared-branch
1352
+ // dispatches MUST NOT auto-pick-up master mid-flight — the carve-out
1353
+ // is explicit. Non-fatal: failures log + continue with the stale base,
1354
+ // and the existing on-failure `git reset --hard origin/<mainRef>`
1355
+ // recovery (~line 1452) remains the safety net.
1356
+ const _depMainRef = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
1357
+ const _depIsSharedBranch = meta?.branchStrategy === 'shared-branch' || meta?.useExistingBranch;
1358
+ if (!_failedRefCache.has(_depMainRef)) {
1359
+ try {
1360
+ await adoGitAuth.runAdoGit(project, ['fetch', 'origin', _depMainRef], { ..._gitOpts, cwd: rootDir });
1361
+ } catch (mainFetchErr) {
1362
+ log('warn', `Failed to fetch origin/${_depMainRef} before dep merge: ${mainFetchErr.message} — proceeding with stale base`);
1363
+ if (adoGitAuth.isAdoAuthFailure(mainFetchErr)) _depAuthFailed = true;
1364
+ _failedRefCache.add(_depMainRef);
1365
+ }
1366
+ }
1367
+ if (!_depIsSharedBranch) {
1368
+ try {
1369
+ await shared.shellSafeGit(['merge', `origin/${_depMainRef}`, '--no-edit', '--no-ff'], { ..._gitOpts, cwd: worktreePath });
1370
+ } catch (mainMergeErr) {
1371
+ log('warn', `Failed to merge origin/${_depMainRef} into ${branchName} before dep merge: ${mainMergeErr.message} — proceeding without main refresh`);
1372
+ try { await shared.shellSafeGit(['merge', '--abort'], { ..._gitOpts, cwd: worktreePath }); } catch (_) { /* no merge in progress */ }
1373
+ }
1374
+ }
1306
1375
  // Fetch all dependency branches in parallel (git fetches are independent)
1307
1376
  const fetchable = depBranches.filter(d => !_failedRefCache.has(d.branch));
1308
1377
  const unfetchable = depBranches.filter(d => _failedRefCache.has(d.branch));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2027",
3
+ "version": "0.1.2028",
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"