@yemi33/minions 0.1.2384 → 0.1.2386
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 +122 -39
- package/minions.js +5 -3
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -1965,51 +1965,125 @@ function getEngineState() { return queries.getControl(); }
|
|
|
1965
1965
|
|
|
1966
1966
|
let _worktreeCountCache = 0;
|
|
1967
1967
|
let _worktreeCountCacheTs = 0;
|
|
1968
|
+
let _worktreeCountRefreshPromise = null;
|
|
1969
|
+
const WORKTREE_COUNT_SCAN_CONCURRENCY = 8;
|
|
1968
1970
|
|
|
1969
|
-
function
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1971
|
+
function _isMissingWorktreePathError(error) {
|
|
1972
|
+
return !!(error && (error.code === 'ENOENT' || error.code === 'ENOTDIR'));
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
async function _readWorktreeDirectory(dirPath) {
|
|
1976
|
+
try {
|
|
1977
|
+
return await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
if (_isMissingWorktreePathError(error)) return [];
|
|
1980
|
+
throw error;
|
|
1973
1981
|
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
async function _hasGitEntry(dirPath) {
|
|
1974
1985
|
try {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
+
await fs.promises.access(path.join(dirPath, '.git'), fs.constants.F_OK);
|
|
1987
|
+
return true;
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
if (_isMissingWorktreePathError(error)) return false;
|
|
1990
|
+
throw error;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
async function _mapWorktreeEntriesBounded(entries, worker) {
|
|
1995
|
+
if (entries.length === 0) return [];
|
|
1996
|
+
const results = new Array(entries.length);
|
|
1997
|
+
let nextIndex = 0;
|
|
1998
|
+
|
|
1999
|
+
async function runWorker() {
|
|
2000
|
+
while (nextIndex < entries.length) {
|
|
2001
|
+
const index = nextIndex++;
|
|
2002
|
+
results[index] = await worker(entries[index]);
|
|
1986
2003
|
}
|
|
2004
|
+
}
|
|
1987
2005
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2006
|
+
const workerCount = Math.min(WORKTREE_COUNT_SCAN_CONCURRENCY, entries.length);
|
|
2007
|
+
await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
|
|
2008
|
+
return results;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
async function _countWorktreesInRoot(worktreeRoot) {
|
|
2012
|
+
const entries = await _readWorktreeDirectory(worktreeRoot);
|
|
2013
|
+
const directories = entries.filter(entry => entry.isDirectory());
|
|
2014
|
+
const inspected = await _mapWorktreeEntriesBounded(directories, async entry => {
|
|
2015
|
+
const dirPath = path.join(worktreeRoot, entry.name);
|
|
2016
|
+
if (await _hasGitEntry(dirPath)) return { direct: 1, nested: [] };
|
|
2017
|
+
|
|
2018
|
+
const nestedEntries = await _readWorktreeDirectory(dirPath);
|
|
2019
|
+
return {
|
|
2020
|
+
direct: 0,
|
|
2021
|
+
nested: nestedEntries
|
|
2022
|
+
.filter(nestedEntry => nestedEntry.isDirectory())
|
|
2023
|
+
.map(nestedEntry => path.join(dirPath, nestedEntry.name)),
|
|
2024
|
+
};
|
|
2025
|
+
});
|
|
2026
|
+
const directCount = inspected.reduce((total, result) => total + result.direct, 0);
|
|
2027
|
+
const nestedPaths = inspected.flatMap(result => result.nested);
|
|
2028
|
+
const nestedMatches = await _mapWorktreeEntriesBounded(nestedPaths, _hasGitEntry);
|
|
2029
|
+
return directCount + nestedMatches.filter(Boolean).length;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
async function _scanWorktreeCount() {
|
|
2033
|
+
const config = queries.getConfig();
|
|
2034
|
+
const worktreeRoots = new Map();
|
|
2035
|
+
for (const project of shared.getProjects(config)) {
|
|
2036
|
+
if (!project.localPath) continue;
|
|
2037
|
+
const worktreeRoot = path.resolve(
|
|
2038
|
+
project.localPath,
|
|
2039
|
+
config.engine?.worktreeRoot || shared.ENGINE_DEFAULTS.worktreeRoot,
|
|
2040
|
+
);
|
|
2041
|
+
const rootKey = shared._normalizeWorktreePath(worktreeRoot);
|
|
2042
|
+
if (!worktreeRoots.has(rootKey)) worktreeRoots.set(rootKey, worktreeRoot);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
let count = 0;
|
|
2046
|
+
for (const worktreeRoot of worktreeRoots.values()) {
|
|
2047
|
+
count += await _countWorktreesInRoot(worktreeRoot);
|
|
2048
|
+
}
|
|
2049
|
+
return count;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function _refreshWorktreeCount() {
|
|
2053
|
+
if (_worktreeCountRefreshPromise) return _worktreeCountRefreshPromise;
|
|
2054
|
+
|
|
2055
|
+
_worktreeCountRefreshPromise = (async () => {
|
|
2056
|
+
try {
|
|
2057
|
+
const count = await _scanWorktreeCount();
|
|
2058
|
+
const changed = count !== _worktreeCountCache;
|
|
2059
|
+
_worktreeCountCache = count;
|
|
2060
|
+
_worktreeCountCacheTs = Date.now();
|
|
2061
|
+
// A cold outer rebuild may already have captured the previous count.
|
|
2062
|
+
if (changed && (_statusCache || _statusRebuildPromise)) invalidateStatusCache();
|
|
2063
|
+
return count;
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
_worktreeCountCacheTs = Date.now();
|
|
2066
|
+
console.warn(`[dashboard] worktree count refresh failed: ${error.message}`);
|
|
2067
|
+
return _worktreeCountCache;
|
|
2068
|
+
} finally {
|
|
2069
|
+
_worktreeCountRefreshPromise = null;
|
|
2008
2070
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2071
|
+
})();
|
|
2072
|
+
return _worktreeCountRefreshPromise;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
function _countWorktrees() {
|
|
2076
|
+
const now = Date.now();
|
|
2077
|
+
if (!_worktreeCountCacheTs || (now - _worktreeCountCacheTs) >= shared.ENGINE_DEFAULTS.worktreeCountCacheTtl) {
|
|
2078
|
+
_refreshWorktreeCount();
|
|
2079
|
+
}
|
|
2080
|
+
return _worktreeCountCache;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
function _resetWorktreeCountCacheForTesting() {
|
|
2084
|
+
_worktreeCountCache = 0;
|
|
2085
|
+
_worktreeCountCacheTs = 0;
|
|
2086
|
+
_worktreeCountRefreshPromise = null;
|
|
2013
2087
|
}
|
|
2014
2088
|
|
|
2015
2089
|
// ── npm update check ────────────────────────────────────────────────────────
|
|
@@ -14770,6 +14844,7 @@ module.exports = {
|
|
|
14770
14844
|
_setStatusRefreshHook,
|
|
14771
14845
|
_resetStatusCacheForTesting,
|
|
14772
14846
|
_ifNoneMatchHasEtag,
|
|
14847
|
+
_countWorktrees, _refreshWorktreeCount, _scanWorktreeCount, _resetWorktreeCountCacheForTesting, // exported for testing
|
|
14773
14848
|
// W-status-swr — exported for direct unit tests of the subprocess-free git
|
|
14774
14849
|
// HEAD parser and the prefix-tolerant short-SHA comparison. No production
|
|
14775
14850
|
// caller imports these; they are test seams.
|
|
@@ -14833,6 +14908,14 @@ if (require.main === module) {
|
|
|
14833
14908
|
// block boot.
|
|
14834
14909
|
(async () => {
|
|
14835
14910
|
const _warmStartedAt = Date.now();
|
|
14911
|
+
// This reporting-only scan must never delay port binding.
|
|
14912
|
+
_refreshWorktreeCount()
|
|
14913
|
+
.then(worktreeCount => {
|
|
14914
|
+
console.log(`[boot] pre-warmed worktree count (${worktreeCount}) off the request path`);
|
|
14915
|
+
})
|
|
14916
|
+
.catch(error => {
|
|
14917
|
+
console.warn(`[boot] worktree count pre-warm failed: ${error && error.message}`);
|
|
14918
|
+
});
|
|
14836
14919
|
try {
|
|
14837
14920
|
await warmProjectGitStatusCache();
|
|
14838
14921
|
const ms = Date.now() - _warmStartedAt;
|
package/minions.js
CHANGED
|
@@ -423,11 +423,13 @@ async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
|
|
|
423
423
|
config.engine.defaultCli = detected[0];
|
|
424
424
|
console.log(`\n ✓ Detected ${detected[0]} CLI — set as fleet default runtime`);
|
|
425
425
|
} else if (detected.length > 1) {
|
|
426
|
-
//
|
|
427
|
-
config.engine.defaultCli = detected.includes(
|
|
426
|
+
// Prefer the canonical fleet default when it is installed.
|
|
427
|
+
config.engine.defaultCli = detected.includes(ENGINE_DEFAULTS.defaultCli)
|
|
428
|
+
? ENGINE_DEFAULTS.defaultCli
|
|
429
|
+
: detected[0];
|
|
428
430
|
console.log(`\n ✓ Detected ${detected.join(' + ')} — fleet default set to ${config.engine.defaultCli}`);
|
|
429
431
|
}
|
|
430
|
-
// If nothing detected, leave defaultCli unset
|
|
432
|
+
// If nothing is detected, leave defaultCli unset so the engine uses its canonical default.
|
|
431
433
|
}
|
|
432
434
|
saveConfig(config);
|
|
433
435
|
console.log(`\n Minions initialized at ${MINIONS_HOME}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2386",
|
|
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"
|