@yemi33/minions 0.1.2401 → 0.1.2403
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 +38 -8
- package/dashboard.js +272 -130
- package/docs/README.md +1 -1
- package/docs/named-agents.md +16 -9
- package/docs/team-memory.md +455 -137
- package/engine/queries.js +67 -68
- package/engine/tools-inventory.js +104 -0
- package/package.json +1 -1
package/engine/queries.js
CHANGED
|
@@ -1219,7 +1219,7 @@ let _skillIndexCache = null;
|
|
|
1219
1219
|
let _skillIndexCacheTs = 0;
|
|
1220
1220
|
let _skillIndexCacheKey = null;
|
|
1221
1221
|
// Top-level cache for getSkills (the read+parse + meta-build path layered on
|
|
1222
|
-
// top of collectSkillFiles). Without this, every /api/
|
|
1222
|
+
// top of collectSkillFiles). Without this, every /api/tools inventory scan
|
|
1223
1223
|
// rebuild re-read and re-parsed all ~80 SKILL.md files (~29ms warm even
|
|
1224
1224
|
// though collectSkillFiles itself was cached) because getSkills did the
|
|
1225
1225
|
// I/O on the cached file list. Shares the same 30s TTL + cacheKey so an
|
|
@@ -1465,8 +1465,8 @@ const SKILL_SOURCE_BY_SCOPE = {
|
|
|
1465
1465
|
};
|
|
1466
1466
|
|
|
1467
1467
|
function getSkills(config) {
|
|
1468
|
-
// Cache the entire read+parse round, not just the file list. /api/
|
|
1469
|
-
//
|
|
1468
|
+
// Cache the entire read+parse round, not just the file list. /api/tools
|
|
1469
|
+
// historically called getSkills on every rebuild; re-reading 80+ SKILL.md
|
|
1470
1470
|
// files + re-parsing their YAML frontmatter cost ~29ms each time (the
|
|
1471
1471
|
// single biggest hot-path offender after getPrdInfo cold). 30s TTL +
|
|
1472
1472
|
// shared cacheKey with collectSkillFiles so invalidateSkillsCache() still
|
|
@@ -1631,8 +1631,8 @@ function _commandTitle(content, fallback) {
|
|
|
1631
1631
|
}
|
|
1632
1632
|
|
|
1633
1633
|
// Dashboard-facing wrapper over `collectCommandFiles`. Returns a normalized
|
|
1634
|
-
// per-entry shape the tools.html "Slash commands" panel + `/api/
|
|
1635
|
-
//
|
|
1634
|
+
// per-entry shape the tools.html "Slash commands" panel + `/api/tools`
|
|
1635
|
+
// response renders directly. Includes the resolved title (from YAML
|
|
1636
1636
|
// `description:` frontmatter or first `# heading`) and a `dir`-with-forward-
|
|
1637
1637
|
// slashes copy so the renderer can build display strings without re-walking
|
|
1638
1638
|
// the file on the client. Sort matches getCommandIndex (project → plugin →
|
|
@@ -2838,6 +2838,31 @@ function _resolveCommonGitDir(gitDir) {
|
|
|
2838
2838
|
return path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
|
|
2839
2839
|
}
|
|
2840
2840
|
|
|
2841
|
+
async function _resolveGitDirAsync(localPath) {
|
|
2842
|
+
if (!localPath) return null;
|
|
2843
|
+
const gitPath = path.join(localPath, '.git');
|
|
2844
|
+
let stat;
|
|
2845
|
+
try { stat = await fs.promises.stat(gitPath); }
|
|
2846
|
+
catch { return null; }
|
|
2847
|
+
if (stat.isDirectory()) return gitPath;
|
|
2848
|
+
if (!stat.isFile()) return null;
|
|
2849
|
+
let raw;
|
|
2850
|
+
try { raw = (await fs.promises.readFile(gitPath, 'utf8')).slice(0, 4096); }
|
|
2851
|
+
catch { return null; }
|
|
2852
|
+
const match = /^gitdir:\s*(.+?)\s*$/m.exec(raw);
|
|
2853
|
+
if (!match) return null;
|
|
2854
|
+
return path.isAbsolute(match[1]) ? match[1] : path.resolve(localPath, match[1]);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
async function _resolveCommonGitDirAsync(gitDir) {
|
|
2858
|
+
if (!gitDir) return null;
|
|
2859
|
+
let raw;
|
|
2860
|
+
try { raw = (await fs.promises.readFile(path.join(gitDir, 'commondir'), 'utf8')).trim(); }
|
|
2861
|
+
catch { return gitDir; }
|
|
2862
|
+
if (!raw) return gitDir;
|
|
2863
|
+
return path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2841
2866
|
// Enumerate the per-project git ref files we watch for cache-busting, split
|
|
2842
2867
|
// into two groups (W-mrb6qnuc000785b2):
|
|
2843
2868
|
// identityFiles — HEAD + logs/HEAD, resolved against the PER-WORKTREE
|
|
@@ -3084,6 +3109,8 @@ let _fastMtimePathsCache = null;
|
|
|
3084
3109
|
let _fastMtimePathsCacheKey = null;
|
|
3085
3110
|
let _slowMtimePathsCache = null;
|
|
3086
3111
|
let _slowMtimePathsCacheKey = null;
|
|
3112
|
+
let _slowMtimePathsAsyncCache = null;
|
|
3113
|
+
let _slowMtimePathsAsyncCacheKey = null;
|
|
3087
3114
|
function _mtimePathsCacheKey(config) {
|
|
3088
3115
|
const projects = getProjects(config).map(p => (p.name || '') + '|' + (p.localPath || ''));
|
|
3089
3116
|
return projects.join(';');
|
|
@@ -3102,26 +3129,10 @@ function getStatusFastStateMtimePaths(config) {
|
|
|
3102
3129
|
/**
|
|
3103
3130
|
* Slow-state mtime tracker — symmetric with the fast-state registry above.
|
|
3104
3131
|
*
|
|
3105
|
-
* Slow-state mtime tracking covers only
|
|
3106
|
-
*
|
|
3107
|
-
*
|
|
3108
|
-
*
|
|
3109
|
-
* - Skill discovery roots (`~/.claude/skills`, `~/.copilot/skills`,
|
|
3110
|
-
* `~/.agents/skills`, `<project>/.claude/skills`, `<project>/.github/skills`,
|
|
3111
|
-
* `<project>/.agents/skills`) plus plugin registries are tracked here
|
|
3112
|
-
* because manual `SKILL.md` drops bypass the agent-close
|
|
3113
|
-
* `invalidateStatusCache({includeSlow:true})` path that previously
|
|
3114
|
-
* covered the agent-extraction case. Directory mtime advances reliably
|
|
3115
|
-
* on Windows NTFS when a subdirectory is added/removed (verified via
|
|
3116
|
-
* INBOX_DIR). Tracking the user-home dirs means non-Minions skill
|
|
3117
|
-
* installs on this machine also bust the fleet's slow-state — but skill
|
|
3118
|
-
* dirs only mutate on rare events (install / manual edit), not on every
|
|
3119
|
-
* CLI command, so the steady-state noise is negligible.
|
|
3120
|
-
* - MCP config files (`~/.claude.json`, `~/.copilot/mcp-config.json`,
|
|
3121
|
-
* `<project>/.mcp.json`) feed `getMcpServers()`. `~/.claude.json`
|
|
3122
|
-
* stores more than `mcpServers`, but it only flips on intentional
|
|
3123
|
-
* Claude CLI mutations (mcp add/remove, settings edits), not on every
|
|
3124
|
-
* prompt, so whole-file tracking is acceptable.
|
|
3132
|
+
* Slow-state mtime tracking covers only file-backed inputs that remain in
|
|
3133
|
+
* /api/status: config, install/version files, and project git metadata.
|
|
3134
|
+
* Skills, commands, and MCP settings are discovered by the isolated
|
|
3135
|
+
* /api/tools child process and no longer invalidate the global status cache.
|
|
3125
3136
|
*
|
|
3126
3137
|
* Per-project `.git/logs/HEAD` + `.git/FETCH_HEAD` ARE tracked here in
|
|
3127
3138
|
* addition to the fast-state tracker. The project payload (`projects:`
|
|
@@ -3142,21 +3153,14 @@ function getStatusFastStateMtimePaths(config) {
|
|
|
3142
3153
|
* gitdir, not the per-worktree subdir, so tracking the per-worktree
|
|
3143
3154
|
* path would silently miss every fetch).
|
|
3144
3155
|
*
|
|
3145
|
-
* NOTE: Detecting a change here busts the dashboard's slow-state cache, but
|
|
3146
|
-
* the inner per-source caches (`queries._skillsCache` 30 s, dashboard's
|
|
3147
|
-
* `_mcpServersCache` 5 min) survive across `_buildStatusSlowState()` calls.
|
|
3148
|
-
* dashboard.js calls `queries.invalidateSkillsCache()` and resets
|
|
3149
|
-
* `_mcpServersCache` whenever this tracker fires, so the rebuild reads
|
|
3150
|
-
* fresh disk state. Keep that invalidation wired up if you add new sources.
|
|
3151
3156
|
*/
|
|
3152
3157
|
function getStatusSlowStateMtimePaths(config) {
|
|
3153
3158
|
config = config || getConfig();
|
|
3154
3159
|
const cacheKey = _mtimePathsCacheKey(config);
|
|
3155
3160
|
if (_slowMtimePathsCache && _slowMtimePathsCacheKey === cacheKey) return _slowMtimePathsCache;
|
|
3156
3161
|
const projects = getProjects(config);
|
|
3157
|
-
|
|
3158
|
-
//
|
|
3159
|
-
// autoMode, initialized, installId, version. PRD/PRDProgress/verifyGuides/
|
|
3162
|
+
// Issue #2949 — slow-state now contains ONLY projects, autoMode,
|
|
3163
|
+
// initialized, installId, and version. PRD/PRDProgress/verifyGuides/
|
|
3160
3164
|
// archivedPrds/schedules/pipelines/pinned/work-items all moved to dedicated
|
|
3161
3165
|
// /api/<x> endpoints which carry their own ETags; tracking their backing
|
|
3162
3166
|
// files here would bust the /api/status outer cache for slices that aren't
|
|
@@ -3171,40 +3175,6 @@ function getStatusSlowStateMtimePaths(config) {
|
|
|
3171
3175
|
files.push(path.join(MINIONS_DIR, '.install-id'));
|
|
3172
3176
|
files.push(path.join(MINIONS_DIR, 'package.json'));
|
|
3173
3177
|
|
|
3174
|
-
// Skill discovery roots (surfaced by _buildStatusSlowState → getSkills).
|
|
3175
|
-
// Mirrors collectSkillFiles' source enumeration so adding a new runtime
|
|
3176
|
-
// adapter automatically extends the tracker. ENOENT is tolerated by
|
|
3177
|
-
// dashboard._statMtimeMs (returns 0), so absent dirs cost nothing.
|
|
3178
|
-
try {
|
|
3179
|
-
const { listRuntimes, resolveRuntime } = require('./runtimes');
|
|
3180
|
-
for (const runtimeName of listRuntimes()) {
|
|
3181
|
-
const runtime = resolveRuntime(runtimeName);
|
|
3182
|
-
if (typeof runtime.getSkillRoots !== 'function') continue;
|
|
3183
|
-
for (const root of runtime.getSkillRoots({ homeDir })) {
|
|
3184
|
-
if (root && root.dir) files.push(root.dir);
|
|
3185
|
-
}
|
|
3186
|
-
for (const project of projects) {
|
|
3187
|
-
if (!project || !project.localPath) continue;
|
|
3188
|
-
for (const root of runtime.getSkillRoots({ homeDir, project })) {
|
|
3189
|
-
if (root && root.dir) files.push(root.dir);
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
} catch { /* runtime registry optional in partial installs */ }
|
|
3194
|
-
|
|
3195
|
-
// Plugin skill registries (also feed collectSkillFiles).
|
|
3196
|
-
files.push(path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json'));
|
|
3197
|
-
files.push(path.join(homeDir, '.copilot', 'installed-plugins'));
|
|
3198
|
-
|
|
3199
|
-
// MCP server config files (surfaced by _buildStatusSlowState → getMcpServers).
|
|
3200
|
-
files.push(path.join(homeDir, '.claude.json'));
|
|
3201
|
-
files.push(path.join(homeDir, '.copilot', 'mcp-config.json'));
|
|
3202
|
-
for (const project of projects) {
|
|
3203
|
-
if (project && project.localPath) {
|
|
3204
|
-
files.push(path.join(project.localPath, '.mcp.json'));
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
|
|
3208
3178
|
// Per-project git refs — see the "Per-project .git/logs/HEAD" note in
|
|
3209
3179
|
// the header. Same pair the fast-state tracker watches; `logs/HEAD` is
|
|
3210
3180
|
// per-worktree (resolved via `_resolveGitDir`) while `FETCH_HEAD` lives
|
|
@@ -3230,6 +3200,34 @@ function getStatusSlowStateMtimePaths(config) {
|
|
|
3230
3200
|
return files;
|
|
3231
3201
|
}
|
|
3232
3202
|
|
|
3203
|
+
async function getStatusSlowStateMtimePathsAsync(config) {
|
|
3204
|
+
config = config || getConfig();
|
|
3205
|
+
const cacheKey = _mtimePathsCacheKey(config);
|
|
3206
|
+
if (_slowMtimePathsAsyncCache && _slowMtimePathsAsyncCacheKey === cacheKey) {
|
|
3207
|
+
return _slowMtimePathsAsyncCache;
|
|
3208
|
+
}
|
|
3209
|
+
const files = [
|
|
3210
|
+
CONFIG_PATH,
|
|
3211
|
+
path.join(MINIONS_DIR, '.install-id'),
|
|
3212
|
+
path.join(MINIONS_DIR, 'package.json'),
|
|
3213
|
+
];
|
|
3214
|
+
const projectPaths = await Promise.all(getProjects(config).map(async project => {
|
|
3215
|
+
if (!project?.localPath) return [];
|
|
3216
|
+
const gitDir = await _resolveGitDirAsync(project.localPath)
|
|
3217
|
+
|| path.join(project.localPath, '.git');
|
|
3218
|
+
const commonGitDir = await _resolveCommonGitDirAsync(gitDir);
|
|
3219
|
+
return [
|
|
3220
|
+
path.join(gitDir, 'HEAD'),
|
|
3221
|
+
path.join(gitDir, 'logs', 'HEAD'),
|
|
3222
|
+
path.join(commonGitDir, 'FETCH_HEAD'),
|
|
3223
|
+
];
|
|
3224
|
+
}));
|
|
3225
|
+
for (const paths of projectPaths) files.push(...paths);
|
|
3226
|
+
_slowMtimePathsAsyncCache = files;
|
|
3227
|
+
_slowMtimePathsAsyncCacheKey = cacheKey;
|
|
3228
|
+
return files;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3233
3231
|
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
3234
3232
|
|
|
3235
3233
|
module.exports = {
|
|
@@ -3255,6 +3253,7 @@ module.exports = {
|
|
|
3255
3253
|
// W-mpftp7na000td0f4 — engine→dashboard cache-invalidation registry
|
|
3256
3254
|
getStatusFastStateMtimePaths,
|
|
3257
3255
|
getStatusSlowStateMtimePaths,
|
|
3256
|
+
getStatusSlowStateMtimePathsAsync,
|
|
3258
3257
|
|
|
3259
3258
|
// Core state
|
|
3260
3259
|
getConfig, getControl, getDispatch, getDispatchQueue, getDispatchCompletionReport, invalidateDispatchCache,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const shared = require('./shared');
|
|
7
|
+
const queries = require('./queries');
|
|
8
|
+
|
|
9
|
+
function readClaudeUserMcpServers(homeDir = os.homedir()) {
|
|
10
|
+
try {
|
|
11
|
+
const data = shared.safeJsonObj(path.join(homeDir, '.claude.json'));
|
|
12
|
+
const servers = data?.mcpServers || {};
|
|
13
|
+
return Object.entries(servers).map(([name, cfg]) => ({
|
|
14
|
+
name,
|
|
15
|
+
source: 'Claude user',
|
|
16
|
+
command: cfg.command || cfg.url || '',
|
|
17
|
+
args: Array.isArray(cfg.args) ? cfg.args.join(' ') : (cfg.args || ''),
|
|
18
|
+
}));
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readCopilotUserMcpServers(homeDir = os.homedir()) {
|
|
25
|
+
try {
|
|
26
|
+
const configPath = path.join(homeDir, '.copilot', 'mcp-config.json');
|
|
27
|
+
if (!fs.existsSync(configPath)) return [];
|
|
28
|
+
let raw = fs.readFileSync(configPath, 'utf8');
|
|
29
|
+
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
|
|
30
|
+
const data = JSON.parse(raw);
|
|
31
|
+
const servers = data?.mcpServers || {};
|
|
32
|
+
return Object.entries(servers).map(([name, cfg]) => ({
|
|
33
|
+
name,
|
|
34
|
+
source: 'Copilot',
|
|
35
|
+
command: cfg.command || cfg.url || '',
|
|
36
|
+
args: Array.isArray(cfg.args) ? cfg.args.join(' ') : (cfg.args || ''),
|
|
37
|
+
}));
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readWorkspaceMcpServers(projects) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const project of projects || []) {
|
|
46
|
+
if (!project?.localPath) continue;
|
|
47
|
+
const data = shared.safeJsonObj(path.join(project.localPath, '.mcp.json'));
|
|
48
|
+
for (const [name, cfg] of Object.entries(data.mcpServers || {})) {
|
|
49
|
+
out.push({
|
|
50
|
+
name,
|
|
51
|
+
source: `Workspace: ${project.name}`,
|
|
52
|
+
command: cfg.command || cfg.url || '',
|
|
53
|
+
args: Array.isArray(cfg.args) ? cfg.args.join(' ') : (cfg.args || ''),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function dedupeMcpServers(entries) {
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const key = `${entry.source}::${entry.name}`;
|
|
65
|
+
if (seen.has(key)) continue;
|
|
66
|
+
seen.add(key);
|
|
67
|
+
out.push(entry);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getMcpServers(projects, homeDir = os.homedir()) {
|
|
73
|
+
return dedupeMcpServers([
|
|
74
|
+
...readClaudeUserMcpServers(homeDir),
|
|
75
|
+
...readCopilotUserMcpServers(homeDir),
|
|
76
|
+
...readWorkspaceMcpServers(projects),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildToolsInventory(config = queries.getConfig()) {
|
|
81
|
+
return {
|
|
82
|
+
skills: queries.getSkills(config),
|
|
83
|
+
commands: queries.getCommands(config),
|
|
84
|
+
mcpServers: getMcpServers(shared.getProjects(config)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (require.main === module) {
|
|
89
|
+
try {
|
|
90
|
+
process.stdout.write(JSON.stringify(buildToolsInventory()));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
process.stderr.write(`tools-inventory: ${error.message}\n`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
buildToolsInventory,
|
|
99
|
+
getMcpServers,
|
|
100
|
+
readClaudeUserMcpServers,
|
|
101
|
+
readCopilotUserMcpServers,
|
|
102
|
+
readWorkspaceMcpServers,
|
|
103
|
+
dedupeMcpServers,
|
|
104
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2403",
|
|
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"
|