@yemi33/minions 0.1.2026 → 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.
- package/dashboard/js/refresh.js +1 -0
- package/dashboard.js +50 -5
- package/engine/queries.js +95 -3
- package/engine.js +73 -4
- package/package.json +1 -1
package/dashboard/js/refresh.js
CHANGED
|
@@ -13,6 +13,7 @@ const _pageCounters = {
|
|
|
13
13
|
pipelines: function(d) { return (d.pipelines || []).length + '|' + (d.pipelines || []).reduce(function(s, p) { return s + (p.runs || []).length; }, 0); },
|
|
14
14
|
schedule: function(d) { return (d.schedules || []).length; },
|
|
15
15
|
engine: function(d) { return (d.dispatch?.completed || []).filter(function(c) { return c.result === 'error'; }).length; },
|
|
16
|
+
qa: function(d) { return (d.qaRuns?.total || 0) + '|' + (d.qaRuns?.sig || ''); },
|
|
16
17
|
};
|
|
17
18
|
let _prevCounts = {};
|
|
18
19
|
function _detectPageChanges(data) {
|
package/dashboard.js
CHANGED
|
@@ -1589,7 +1589,9 @@ function _ifNoneMatchHasEtag(headerValue, currentEtag) {
|
|
|
1589
1589
|
// delegate so any module that contributes to `_buildStatusFastState()` can
|
|
1590
1590
|
// register its mtime inputs in one place.
|
|
1591
1591
|
const _mtimeTrackedFiles = () => queries.getStatusFastStateMtimePaths(CONFIG);
|
|
1592
|
-
|
|
1592
|
+
const _slowMtimeTrackedFiles = () => queries.getStatusSlowStateMtimePaths(CONFIG);
|
|
1593
|
+
let _lastMtimes = {}; // { filePath: mtimeMs } — fast-state baseline
|
|
1594
|
+
let _lastSlowMtimes = {}; // { filePath: mtimeMs } — slow-state baseline
|
|
1593
1595
|
|
|
1594
1596
|
function _getMtimes() {
|
|
1595
1597
|
const result = {};
|
|
@@ -1599,6 +1601,14 @@ function _getMtimes() {
|
|
|
1599
1601
|
return result;
|
|
1600
1602
|
}
|
|
1601
1603
|
|
|
1604
|
+
function _getSlowMtimes() {
|
|
1605
|
+
const result = {};
|
|
1606
|
+
for (const fp of _slowMtimeTrackedFiles()) {
|
|
1607
|
+
try { result[fp] = fs.statSync(fp).mtimeMs; } catch { result[fp] = 0; }
|
|
1608
|
+
}
|
|
1609
|
+
return result;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1602
1612
|
function _mtimesChanged(prev, curr) {
|
|
1603
1613
|
for (const fp of Object.keys(curr)) {
|
|
1604
1614
|
if (prev[fp] !== curr[fp]) return true;
|
|
@@ -1668,6 +1678,21 @@ function _buildStatusFastState() {
|
|
|
1668
1678
|
workItems: getWorkItems(),
|
|
1669
1679
|
watches: watchesMod.getWatches(),
|
|
1670
1680
|
meetings: (() => { try { return require('./engine/meeting').getMeetings(); } catch { return []; } })(),
|
|
1681
|
+
// QA runs — surfaced for the sidebar activity-dot counter and any future
|
|
1682
|
+
// CC/aggregate view. Tab-level rendering keeps its own /api/qa/runs poll
|
|
1683
|
+
// (5 s while the QA page is mounted). qa-runs.json is in the mtime tracker
|
|
1684
|
+
// so a new run lights the dot within one /api/status poll cycle (~4 s).
|
|
1685
|
+
qaRuns: (() => {
|
|
1686
|
+
try {
|
|
1687
|
+
const runs = require('./engine/qa-runs').listRuns({ limit: 50 }) || [];
|
|
1688
|
+
return {
|
|
1689
|
+
total: runs.length,
|
|
1690
|
+
// Signature of (id, status) for the most recent 20 runs so the
|
|
1691
|
+
// sidebar counter advances on status flips AND on new entries.
|
|
1692
|
+
sig: runs.slice(0, 20).map(r => (r && r.id || '') + ':' + (r && r.status || '')).join(','),
|
|
1693
|
+
};
|
|
1694
|
+
} catch { return { total: 0, sig: '' }; }
|
|
1695
|
+
})(),
|
|
1671
1696
|
};
|
|
1672
1697
|
}
|
|
1673
1698
|
|
|
@@ -1772,8 +1797,16 @@ function getStatus() {
|
|
|
1772
1797
|
if (_mtimesChanged(_lastMtimes, currMtimes)) fastStale = true;
|
|
1773
1798
|
}
|
|
1774
1799
|
|
|
1775
|
-
// Slow state: 60s TTL
|
|
1776
|
-
|
|
1800
|
+
// Slow state: 60s TTL with mtime-based validation for early bust.
|
|
1801
|
+
// The mtime tracker covers engine-driven slow-state writes (PRD updates,
|
|
1802
|
+
// pipeline-runs.json, schedule-runs.json, verify guides, project skills)
|
|
1803
|
+
// so changes surface within one SPA poll (~4 s) instead of waiting up to
|
|
1804
|
+
// 60 s for TTL. Same pre-build snapshot semantics as fast-state below.
|
|
1805
|
+
let slowStale = !_slowState || (now - _slowStateTs) >= SLOW_STATE_TTL;
|
|
1806
|
+
if (!slowStale) {
|
|
1807
|
+
const currSlowMtimes = _getSlowMtimes();
|
|
1808
|
+
if (_mtimesChanged(_lastSlowMtimes, currSlowMtimes)) slowStale = true;
|
|
1809
|
+
}
|
|
1777
1810
|
|
|
1778
1811
|
// If nothing stale, return cached merged result
|
|
1779
1812
|
if (!fastStale && !slowStale && _statusCache) return _statusCache;
|
|
@@ -1795,10 +1828,14 @@ function getStatus() {
|
|
|
1795
1828
|
_lastMtimes = preBuildMtimes;
|
|
1796
1829
|
}
|
|
1797
1830
|
|
|
1798
|
-
// Rebuild slow state (rarely-changing data: ~8-15 reads, 60s TTL)
|
|
1831
|
+
// Rebuild slow state (rarely-changing data: ~8-15 reads, 60s TTL).
|
|
1832
|
+
// Same pre-build snapshot pattern as fast-state — capture mtimes BEFORE
|
|
1833
|
+
// disk reads so any write landing mid-build busts the next poll.
|
|
1799
1834
|
if (slowStale) {
|
|
1835
|
+
const preBuildSlowMtimes = _getSlowMtimes();
|
|
1800
1836
|
_slowState = _buildStatusSlowState();
|
|
1801
1837
|
_slowStateTs = now;
|
|
1838
|
+
_lastSlowMtimes = preBuildSlowMtimes;
|
|
1802
1839
|
}
|
|
1803
1840
|
|
|
1804
1841
|
// Merge both tiers — no API contract change
|
|
@@ -1839,7 +1876,11 @@ function refreshStatusAsync() {
|
|
|
1839
1876
|
const currMtimes = _getMtimes();
|
|
1840
1877
|
if (_mtimesChanged(_lastMtimes, currMtimes)) fastStale = true;
|
|
1841
1878
|
}
|
|
1842
|
-
|
|
1879
|
+
let slowStale = !_slowState || (now - _slowStateTs) >= SLOW_STATE_TTL;
|
|
1880
|
+
if (!slowStale) {
|
|
1881
|
+
const currSlowMtimes = _getSlowMtimes();
|
|
1882
|
+
if (_mtimesChanged(_lastSlowMtimes, currSlowMtimes)) slowStale = true;
|
|
1883
|
+
}
|
|
1843
1884
|
|
|
1844
1885
|
if (!fastStale && !slowStale && _statusCache) return _statusCache;
|
|
1845
1886
|
|
|
@@ -1866,7 +1907,9 @@ function refreshStatusAsync() {
|
|
|
1866
1907
|
}
|
|
1867
1908
|
|
|
1868
1909
|
let slow = _slowState;
|
|
1910
|
+
let preBuildSlowMtimes = null;
|
|
1869
1911
|
if (slowStale) {
|
|
1912
|
+
preBuildSlowMtimes = _getSlowMtimes();
|
|
1870
1913
|
slow = _buildStatusSlowState();
|
|
1871
1914
|
}
|
|
1872
1915
|
|
|
@@ -1887,6 +1930,7 @@ function refreshStatusAsync() {
|
|
|
1887
1930
|
if (slowStale) {
|
|
1888
1931
|
_slowState = slow;
|
|
1889
1932
|
_slowStateTs = now;
|
|
1933
|
+
_lastSlowMtimes = preBuildSlowMtimes;
|
|
1890
1934
|
}
|
|
1891
1935
|
_statusCache = { ..._fastState, ..._slowState, timestamp: new Date().toISOString() };
|
|
1892
1936
|
_markStatusCacheBuilt();
|
|
@@ -1921,6 +1965,7 @@ function _resetStatusCacheForTesting() {
|
|
|
1921
1965
|
_statusInvalidationGeneration = 0;
|
|
1922
1966
|
_statusRefreshHook = null;
|
|
1923
1967
|
_lastMtimes = {};
|
|
1968
|
+
_lastSlowMtimes = {};
|
|
1924
1969
|
}
|
|
1925
1970
|
|
|
1926
1971
|
/** Return cached JSON string of status — single stringify, reused by SSE and /api/status */
|
package/engine/queries.js
CHANGED
|
@@ -1918,11 +1918,29 @@ function getStatusFastStateMtimePaths(config) {
|
|
|
1918
1918
|
// entry-add/remove signal even on Windows NTFS. Without it, PR-comment
|
|
1919
1919
|
// notifications, agent-failure summaries, follow-up build alerts, and
|
|
1920
1920
|
// meeting-transcript dumps all lagged up to 10 s before appearing on
|
|
1921
|
-
// the dashboard's inbox view.
|
|
1922
|
-
// (see "Files intentionally NOT tracked") because round transitions
|
|
1923
|
-
// edit existing files in-place, which dir mtime can't detect.
|
|
1921
|
+
// the dashboard's inbox view.
|
|
1924
1922
|
INBOX_DIR,
|
|
1923
|
+
// engine/qa-runs.json (surfaced by listRuns via fast-state qaRuns slice)
|
|
1924
|
+
// — new QA runs and status flips need to light the sidebar activity dot
|
|
1925
|
+
// within one SPA poll cycle. Single file, mtime advances on each write.
|
|
1926
|
+
path.join(ENGINE_DIR, 'qa-runs.json'),
|
|
1925
1927
|
];
|
|
1928
|
+
// meetings/<id>.json (surfaced by meeting.getMeetings) — round transitions
|
|
1929
|
+
// edit each file in-place via mutateMeeting, so the parent dir's mtime
|
|
1930
|
+
// does NOT advance on Windows. Tracking each file individually catches
|
|
1931
|
+
// in-file edits. Bounded by meeting count (typically <50 active); a 50-
|
|
1932
|
+
// meeting fleet adds ~50 statSync calls per cache miss — still cheap.
|
|
1933
|
+
// Filter `.backup` sidecars (from safe-write tempfile pattern) and
|
|
1934
|
+
// non-*.json entries so corrupted state can't pollute the registry.
|
|
1935
|
+
try {
|
|
1936
|
+
const meetingsDir = path.join(MINIONS_DIR, 'meetings');
|
|
1937
|
+
const entries = fs.readdirSync(meetingsDir);
|
|
1938
|
+
for (const f of entries) {
|
|
1939
|
+
if (f.endsWith('.json') && !f.endsWith('.backup.json')) {
|
|
1940
|
+
files.push(path.join(meetingsDir, f));
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
} catch { /* meetings dir absent → no meetings to track */ }
|
|
1926
1944
|
// Per-project work-items (surfaced by getWorkItems) and pull-requests
|
|
1927
1945
|
// (surfaced by getPullRequests). The PR file was the biggest miss in the
|
|
1928
1946
|
// original tracked list — PR status flips (running → passing, waiting →
|
|
@@ -1947,6 +1965,79 @@ function getStatusFastStateMtimePaths(config) {
|
|
|
1947
1965
|
return files;
|
|
1948
1966
|
}
|
|
1949
1967
|
|
|
1968
|
+
/**
|
|
1969
|
+
* Slow-state mtime tracker — symmetric with the fast-state registry above.
|
|
1970
|
+
*
|
|
1971
|
+
* Slow-state slices (`prdProgress`, `prd`, `verifyGuides`, `archivedPrds`,
|
|
1972
|
+
* `skills`, `mcpServers`, `schedules`, `pipelines`, `pinned`, `projects`,
|
|
1973
|
+
* `version`, etc.) live behind a 60 s TTL in `dashboard.js`. Without mtime
|
|
1974
|
+
* tracking, engine-driven writes to `prd/*.json`, `engine/schedule-runs.json`,
|
|
1975
|
+
* `engine/pipeline-runs.json`, or `prd/guides/*.md` waited up to the full
|
|
1976
|
+
* 60 s before surfacing in the dashboard — which produced the visible
|
|
1977
|
+
* "plan status doesn't update, pipelines never advance" symptom.
|
|
1978
|
+
*
|
|
1979
|
+
* The same conventions as fast-state apply:
|
|
1980
|
+
* - Entries must back something `_buildStatusSlowState()` reads; otherwise
|
|
1981
|
+
* the rebuild silently no-ops on detected change.
|
|
1982
|
+
* - Files mutated through a `mutate*` helper are reliable (`safeWrite`
|
|
1983
|
+
* rename advances mtime). Append-only logs reset the cache often but
|
|
1984
|
+
* are not relevant here.
|
|
1985
|
+
* - Per-project paths must use `shared.project*` so newly-added projects
|
|
1986
|
+
* are picked up without registry edits.
|
|
1987
|
+
*
|
|
1988
|
+
* Files intentionally NOT tracked here:
|
|
1989
|
+
* - `mcpServers`, version, autoMode, installId — change only on human/
|
|
1990
|
+
* CLI edits, which already pop the slow-state via reloadConfig + the
|
|
1991
|
+
* 60 s TTL.
|
|
1992
|
+
* - `~/.claude/skills/`, `~/.copilot/skills/` — user-home dirs that
|
|
1993
|
+
* `extractSkillsFromOutput` writes to from the agent-close path,
|
|
1994
|
+
* which already calls `invalidateStatusCache()` directly.
|
|
1995
|
+
* - project git state — already invalidated via the
|
|
1996
|
+
* `_setOnProjectGitStatusChanged` callback into `invalidateStatusCache`
|
|
1997
|
+
* (W-mpgrk5cy fix); also tracked in fast-state via `.git/logs/HEAD`.
|
|
1998
|
+
*/
|
|
1999
|
+
function getStatusSlowStateMtimePaths(config) {
|
|
2000
|
+
const projects = getProjects(config || getConfig());
|
|
2001
|
+
const files = [
|
|
2002
|
+
// prd/*.json (surfaced by getPrdInfo) — engine writes via syncPrdFromPrs,
|
|
2003
|
+
// the materializer, and plan-to-prd outputs. Dir mtime advances on entry
|
|
2004
|
+
// add/remove; in-file edits use getPrdInfo's own per-file mtime cache so
|
|
2005
|
+
// small-content flips still surface via the 10 s TTL backstop, but the
|
|
2006
|
+
// big "new PRD created" event surfaces immediately.
|
|
2007
|
+
PRD_DIR,
|
|
2008
|
+
// prd/archive/*.json — manual archive moves PRDs here; dir mtime catches
|
|
2009
|
+
// the move.
|
|
2010
|
+
path.join(PRD_DIR, 'archive'),
|
|
2011
|
+
// prd/guides/*.md — verify agent writes new files here on E2E completion.
|
|
2012
|
+
path.join(MINIONS_DIR, 'prd', 'guides'),
|
|
2013
|
+
// engine/schedule-runs.json — scheduler rewrites this on every cron fire.
|
|
2014
|
+
path.join(ENGINE_DIR, 'schedule-runs.json'),
|
|
2015
|
+
// engine/pipeline-runs.json — pipeline executor rewrites this on each
|
|
2016
|
+
// stage transition (the most user-visible slow-state lag pre-fix).
|
|
2017
|
+
path.join(ENGINE_DIR, 'pipeline-runs.json'),
|
|
2018
|
+
// pipelines/*.json — pipeline definitions, edited by humans + plan agents.
|
|
2019
|
+
// Dir mtime is fine because pipeline edits are wholesale file replacements
|
|
2020
|
+
// (no in-place tweaks once a pipeline is authored).
|
|
2021
|
+
path.join(MINIONS_DIR, 'pipelines'),
|
|
2022
|
+
// pinned.md — single file, dashboard-side writes already call
|
|
2023
|
+
// invalidateStatusCache({includeSlow:true}); tracker entry catches any
|
|
2024
|
+
// CLI/editor edit that bypasses the API.
|
|
2025
|
+
path.join(MINIONS_DIR, 'pinned.md'),
|
|
2026
|
+
// engine/skill-states/ — engine writes when agents extract new skills.
|
|
2027
|
+
// Dir mtime catches new skill files. Skips per-skill in-place edits which
|
|
2028
|
+
// the agent-close invalidate already covers.
|
|
2029
|
+
SKILLS_DIR,
|
|
2030
|
+
];
|
|
2031
|
+
// Per-project local skill dirs — agents extract project-scoped skills here.
|
|
2032
|
+
for (const p of projects) {
|
|
2033
|
+
if (p && p.localPath) {
|
|
2034
|
+
files.push(path.join(p.localPath, '.claude', 'skills'));
|
|
2035
|
+
files.push(path.join(p.localPath, '.github', 'skills'));
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
return files;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1950
2041
|
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
1951
2042
|
|
|
1952
2043
|
module.exports = {
|
|
@@ -1967,6 +2058,7 @@ module.exports = {
|
|
|
1967
2058
|
_setOnProjectGitStatusChanged,
|
|
1968
2059
|
// W-mpftp7na000td0f4 — engine→dashboard cache-invalidation registry
|
|
1969
2060
|
getStatusFastStateMtimePaths,
|
|
2061
|
+
getStatusSlowStateMtimePaths,
|
|
1970
2062
|
|
|
1971
2063
|
// Core state
|
|
1972
2064
|
getConfig, getControl, getDispatch, getDispatchQueue, getDispatchCompletionReport, invalidateDispatchCache,
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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"
|