claude-flow 3.10.39 → 3.10.41
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/.claude/helpers/statusline.cjs +166 -19
- package/README.md +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/hive-mind.js +14 -1
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +16 -0
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +1 -1
- package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +166 -19
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +26 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +2 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/enhanced-model-router.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/enhanced-model-router.js +20 -12
- package/v3/@claude-flow/cli/dist/src/ruvector/model-router.d.ts +25 -12
- package/v3/@claude-flow/cli/dist/src/ruvector/model-router.js +25 -12
- package/v3/@claude-flow/cli/package.json +1 -1
- package/.claude/scheduled_tasks.lock +0 -1
|
@@ -42,10 +42,51 @@ const CONFIG = {
|
|
|
42
42
|
const CWD = process.cwd();
|
|
43
43
|
|
|
44
44
|
// ─── Delegation cache ───────────────────────────────────────────
|
|
45
|
-
// Cache the CLI JSON result for
|
|
46
|
-
// (
|
|
45
|
+
// Cache the CLI JSON result for 60s so rapid prompt re-renders
|
|
46
|
+
// (Claude Code refreshes the statusline several times a second while
|
|
47
|
+
// streaming) don't re-invoke the CLI each time. #2337: bumped 10s→60s
|
|
48
|
+
// because 10s was far too short for how often Claude Code re-renders.
|
|
47
49
|
const CACHE_FILE = path.join(os.tmpdir(), 'ruflo-statusline-cache-' + require('crypto').createHash('md5').update(CWD).digest('hex').slice(0, 8) + '.json');
|
|
48
|
-
const CACHE_TTL_MS =
|
|
50
|
+
const CACHE_TTL_MS = 60000;
|
|
51
|
+
|
|
52
|
+
// #2337: resolve an already-installed @claude-flow/cli (or ruflo) bin so we
|
|
53
|
+
// can invoke it directly via `node`. The previous version called
|
|
54
|
+
// `npx --yes @claude-flow/cli@latest` on every uncached render, which forces
|
|
55
|
+
// a registry resolution + cold-start of the entire CLI per render. With
|
|
56
|
+
// multiple concurrent Claude Code sessions this storms the host (reporter
|
|
57
|
+
// saw load average 40-65 on a 12-core box).
|
|
58
|
+
//
|
|
59
|
+
// Returns the absolute path to bin/cli.js or null. Mirrors getPkgVersion()'s
|
|
60
|
+
// path probing (project, monorepo, plugin marketplace, global node_modules
|
|
61
|
+
// including custom-prefix layouts like ~/.npm-global).
|
|
62
|
+
function resolveCliBin() {
|
|
63
|
+
try {
|
|
64
|
+
const home = os.homedir();
|
|
65
|
+
const candidates = [
|
|
66
|
+
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
67
|
+
path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
68
|
+
path.join(CWD, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
69
|
+
path.join(CWD, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
70
|
+
];
|
|
71
|
+
try {
|
|
72
|
+
const binDir = path.dirname(process.execPath);
|
|
73
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
74
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
75
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
76
|
+
}
|
|
77
|
+
for (const gm of globalModuleDirs) {
|
|
78
|
+
candidates.push(
|
|
79
|
+
path.join(gm, 'ruflo', 'bin', 'cli.js'),
|
|
80
|
+
path.join(gm, '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} catch { /* ignore */ }
|
|
84
|
+
for (const p of candidates) {
|
|
85
|
+
if (fs.existsSync(p)) return p;
|
|
86
|
+
}
|
|
87
|
+
} catch { /* ignore */ }
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
49
90
|
|
|
50
91
|
function readCache() {
|
|
51
92
|
try {
|
|
@@ -76,16 +117,25 @@ function getStatuslineData() {
|
|
|
76
117
|
if (cached) return cached;
|
|
77
118
|
|
|
78
119
|
try {
|
|
120
|
+
// #2337: prefer an already-installed CLI bin via direct `node` invocation
|
|
121
|
+
// — no npx, no registry round-trip, no @latest re-resolve per render.
|
|
122
|
+
// Fall back to `npx --prefer-offline @claude-flow/cli` (no @latest) only
|
|
123
|
+
// when nothing is installed locally, so a cold environment still works.
|
|
124
|
+
const cliBin = resolveCliBin();
|
|
125
|
+
const cmd = cliBin
|
|
126
|
+
? '"' + process.execPath + '" "' + cliBin + '" hooks statusline --json 2>/dev/null'
|
|
127
|
+
: 'npx --prefer-offline @claude-flow/cli hooks statusline --json 2>/dev/null';
|
|
79
128
|
const raw = execSync(
|
|
80
|
-
|
|
129
|
+
cmd,
|
|
81
130
|
{ encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], cwd: CWD }
|
|
82
131
|
).trim();
|
|
83
132
|
// The CLI may emit preamble lines before the JSON — find the first '{'.
|
|
84
133
|
const jsonStart = raw.indexOf('{');
|
|
85
134
|
if (jsonStart === -1) throw new Error('no JSON in CLI output');
|
|
86
135
|
const data = JSON.parse(raw.slice(jsonStart));
|
|
87
|
-
// Overlay
|
|
88
|
-
|
|
136
|
+
// Overlay every block the CLI JSON omits (adrs/agentdb/tests/hooks/integration)
|
|
137
|
+
// with real local reads, so those segments reflect actual state instead of 0.
|
|
138
|
+
applyLocalOverlays(data);
|
|
89
139
|
writeCache(data);
|
|
90
140
|
return data;
|
|
91
141
|
} catch { /* CLI unavailable or timed out */ }
|
|
@@ -118,13 +168,40 @@ function getLocalADRCount() {
|
|
|
118
168
|
return { count: total, implemented: total, compliance: 0 };
|
|
119
169
|
}
|
|
120
170
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const adrs = getLocalADRCount();
|
|
171
|
+
// ─── Local overlays for segments the CLI JSON omits ──────────────
|
|
172
|
+
// 'hooks statusline --json' only returns user/v3Progress/security/swarm/system.
|
|
173
|
+
// agentdb/tests/hooks/integration are never populated, so without these overlays
|
|
174
|
+
// they render as a permanent 0. Each reader is cheap and degrades to zeros.
|
|
126
175
|
|
|
127
|
-
|
|
176
|
+
// Real AgentDB stats from the local memory DB. Vectors live in .swarm/memory.db
|
|
177
|
+
// (sql.js + HNSW); ruvector.db is an opaque redb store counted only toward size.
|
|
178
|
+
// One read-only sqlite3 query (mode=ro never takes a write lock the daemon owns).
|
|
179
|
+
function getLocalAgentDB() {
|
|
180
|
+
const result = { vectorCount: 0, dbSizeKB: 0, hasHnsw: false };
|
|
181
|
+
try {
|
|
182
|
+
let bytes = 0;
|
|
183
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
184
|
+
try { bytes += fs.statSync(path.join(CWD, f)).size; } catch { /* missing */ }
|
|
185
|
+
}
|
|
186
|
+
result.dbSizeKB = Math.round(bytes / 1024);
|
|
187
|
+
|
|
188
|
+
const memDb = path.join(CWD, '.swarm', 'memory.db');
|
|
189
|
+
if (fs.existsSync(memDb)) {
|
|
190
|
+
const Q = String.fromCharCode(34);
|
|
191
|
+
const sql = Q + 'SELECT (SELECT COUNT(*) FROM memory_entries WHERE embedding IS NOT NULL)||' + "'|'" + '||(SELECT COUNT(*) FROM vector_indexes);' + Q;
|
|
192
|
+
const out = safeExec("sqlite3 'file:" + memDb + "?mode=ro' " + sql, 1500);
|
|
193
|
+
if (out && out.indexOf('|') !== -1) {
|
|
194
|
+
const parts = out.split('|');
|
|
195
|
+
result.vectorCount = parseInt(parts[0], 10) || 0;
|
|
196
|
+
result.hasHnsw = (parseInt(parts[1], 10) || 0) > 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch { /* ignore */ }
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Count test files via a bounded directory walk (no file reads).
|
|
204
|
+
function getLocalTests() {
|
|
128
205
|
let testFiles = 0;
|
|
129
206
|
function countTests(dir, depth) {
|
|
130
207
|
if ((depth || 0) > 4) return;
|
|
@@ -140,19 +217,82 @@ function buildLocalFallback() {
|
|
|
140
217
|
} catch { /* ignore */ }
|
|
141
218
|
}
|
|
142
219
|
for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d));
|
|
220
|
+
return { testFiles, testCases: testFiles * 4 };
|
|
221
|
+
}
|
|
143
222
|
|
|
144
|
-
|
|
223
|
+
// Count configured hooks from project .claude/settings.json. Claude Code hooks
|
|
224
|
+
// have no enabled/disabled flag, so every configured hook counts as enabled.
|
|
225
|
+
function getLocalHooks() {
|
|
226
|
+
const result = { enabled: 0, total: 0 };
|
|
227
|
+
try {
|
|
228
|
+
const settings = readJSON(path.join(CWD, '.claude', 'settings.json'));
|
|
229
|
+
const hooks = settings && settings.hooks;
|
|
230
|
+
if (hooks && typeof hooks === 'object') {
|
|
231
|
+
let n = 0;
|
|
232
|
+
for (const ev of Object.keys(hooks)) {
|
|
233
|
+
const groups = hooks[ev];
|
|
234
|
+
if (Array.isArray(groups)) {
|
|
235
|
+
for (const g of groups) {
|
|
236
|
+
if (g && Array.isArray(g.hooks)) n += g.hooks.length;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
result.total = n;
|
|
241
|
+
result.enabled = n;
|
|
242
|
+
}
|
|
243
|
+
} catch { /* ignore */ }
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Best-effort integration block: DB presence + locally-configured stdio MCP
|
|
248
|
+
// servers (project .mcp.json + global ~/.claude.json). Remote connectors are
|
|
249
|
+
// account-managed and not present in local config, so they are not counted.
|
|
250
|
+
function getLocalIntegration() {
|
|
251
|
+
const integration = { mcpServers: { enabled: 0, total: 0 }, hasDatabase: false };
|
|
252
|
+
try {
|
|
253
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
254
|
+
if (fs.existsSync(path.join(CWD, f))) { integration.hasDatabase = true; break; }
|
|
255
|
+
}
|
|
256
|
+
const names = new Set();
|
|
257
|
+
const projMcp = readJSON(path.join(CWD, '.mcp.json'));
|
|
258
|
+
if (projMcp && projMcp.mcpServers) for (const k of Object.keys(projMcp.mcpServers)) names.add(k);
|
|
259
|
+
const claudeJson = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
260
|
+
if (claudeJson) {
|
|
261
|
+
if (claudeJson.mcpServers) for (const k of Object.keys(claudeJson.mcpServers)) names.add(k);
|
|
262
|
+
const proj = claudeJson.projects && claudeJson.projects[CWD];
|
|
263
|
+
if (proj && proj.mcpServers && !Array.isArray(proj.mcpServers)) {
|
|
264
|
+
for (const k of Object.keys(proj.mcpServers)) names.add(k);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
integration.mcpServers.total = names.size;
|
|
268
|
+
integration.mcpServers.enabled = names.size;
|
|
269
|
+
} catch { /* ignore */ }
|
|
270
|
+
return integration;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Overlay every locally-derived block onto the CLI data (mutates in place).
|
|
274
|
+
function applyLocalOverlays(data) {
|
|
275
|
+
data.adrs = getLocalADRCount();
|
|
276
|
+
data.agentdb = getLocalAgentDB();
|
|
277
|
+
data.tests = getLocalTests();
|
|
278
|
+
data.hooks = getLocalHooks();
|
|
279
|
+
data.integration = getLocalIntegration();
|
|
280
|
+
return data;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Minimal local fallback when the CLI is not installed or times out.
|
|
284
|
+
// Returns a structure that matches the CLI JSON schema so the renderer works.
|
|
285
|
+
function buildLocalFallback() {
|
|
286
|
+
const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
287
|
+
|
|
288
|
+
return applyLocalOverlays({
|
|
145
289
|
user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
|
|
146
290
|
v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
|
|
147
291
|
security: { status: 'NONE', cvesFixed: 0, totalCves: 0 },
|
|
148
292
|
swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
|
|
149
293
|
system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
|
|
150
|
-
adrs,
|
|
151
|
-
hooks: { enabled: 0, total: 0 },
|
|
152
|
-
agentdb: { vectorCount: 0, dbSizeKB: 0, hasHnsw: false },
|
|
153
|
-
tests: { testFiles, testCases: testFiles * 4 },
|
|
154
294
|
lastUpdated: new Date().toISOString(),
|
|
155
|
-
};
|
|
295
|
+
});
|
|
156
296
|
}
|
|
157
297
|
|
|
158
298
|
// ANSI colors
|
|
@@ -352,7 +492,14 @@ function getPkgVersion() {
|
|
|
352
492
|
// (bin/node_modules) layouts.
|
|
353
493
|
try {
|
|
354
494
|
const binDir = path.dirname(process.execPath);
|
|
355
|
-
|
|
495
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
496
|
+
// #2221 follow-up: a custom npm prefix (e.g. ~/.npm-global) is decoupled from
|
|
497
|
+
// the node binary location, so the binDir-derived probes above all miss. Also
|
|
498
|
+
// probe the npm prefix from the environment and the common ~/.npm-global default.
|
|
499
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
500
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
501
|
+
}
|
|
502
|
+
for (const gm of globalModuleDirs) {
|
|
356
503
|
pkgPaths.push(
|
|
357
504
|
path.join(gm, 'ruflo', 'package.json'),
|
|
358
505
|
path.join(gm, '@claude-flow', 'cli', 'package.json'),
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Orchestrate 100+ specialized AI agents across machines, teams, and trust boundar
|
|
|
35
35
|
|
|
36
36
|
### What Ruflo Does
|
|
37
37
|
|
|
38
|
-
One `npx
|
|
38
|
+
One `npx ruflo init` gives Claude Code a nervous system: agents self-organize into swarms, learn from every task, remember across sessions, and — with federation — securely talk to agents on other machines without leaking data. You keep writing code. Ruflo handles the coordination.
|
|
39
39
|
|
|
40
40
|
```
|
|
41
41
|
Self-Learning / Self-Optimizing Agent Architecture
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.41",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@ Orchestrate 100+ specialized AI agents across machines, teams, and trust boundar
|
|
|
35
35
|
|
|
36
36
|
### What Ruflo Does
|
|
37
37
|
|
|
38
|
-
One `npx
|
|
38
|
+
One `npx ruflo init` gives Claude Code a nervous system: agents self-organize into swarms, learn from every task, remember across sessions, and — with federation — securely talk to agents on other machines without leaking data. You keep writing code. Ruflo handles the coordination.
|
|
39
39
|
|
|
40
40
|
```
|
|
41
41
|
Self-Learning / Self-Optimizing Agent Architecture
|
|
@@ -306,7 +306,20 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
|
|
|
306
306
|
output.printSuccess('Claude Code launched with Hive Mind coordination');
|
|
307
307
|
output.printInfo('The Queen coordinator will orchestrate all worker agents');
|
|
308
308
|
output.writeln(output.dim(`Prompt file saved at: ${promptFile}`));
|
|
309
|
-
|
|
309
|
+
// #2297: await child exit before returning. Without this, the CLI
|
|
310
|
+
// process resolves immediately, finishes, and the still-initializing
|
|
311
|
+
// `claude` child loses its controlling terminal and is killed mid-launch
|
|
312
|
+
// — visible as a stray XTVERSION reply leaking onto the next shell
|
|
313
|
+
// prompt (the terminal queried for capabilities, but the child died
|
|
314
|
+
// before reading the answer). Awaiting also makes the existing
|
|
315
|
+
// claudeProcess.on('exit', ...) log lines actually print, and lets the
|
|
316
|
+
// non-interactive (-p / --non-interactive) path complete only after
|
|
317
|
+
// Claude Code finishes.
|
|
318
|
+
const claudeExitCode = await new Promise((resolve) => {
|
|
319
|
+
claudeProcess.on('exit', (c) => resolve(c ?? 0));
|
|
320
|
+
claudeProcess.on('error', () => resolve(1));
|
|
321
|
+
});
|
|
322
|
+
return { success: claudeExitCode === 0, promptFile };
|
|
310
323
|
}
|
|
311
324
|
else if (dryRun) {
|
|
312
325
|
output.writeln();
|
|
@@ -1560,6 +1560,19 @@ const postTaskCommand = {
|
|
|
1560
1560
|
short: 'a',
|
|
1561
1561
|
description: 'Agent that executed the task',
|
|
1562
1562
|
type: 'string'
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
// ADR-147 P2: nested-subagent spawn-tree capture
|
|
1566
|
+
name: 'parent-agent-id',
|
|
1567
|
+
description: 'ID of the parent agent (from Claude Code\'s parent_agent_id OTel span tag). Omit for top-level work.',
|
|
1568
|
+
type: 'string',
|
|
1569
|
+
required: false
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
name: 'depth',
|
|
1573
|
+
description: 'Chain depth from root lead session (0 = lead, 1+ = subagent). Used by ADR-147 P3 depth-aware guardrail.',
|
|
1574
|
+
type: 'number',
|
|
1575
|
+
required: false
|
|
1563
1576
|
}
|
|
1564
1577
|
],
|
|
1565
1578
|
examples: [
|
|
@@ -1579,6 +1592,9 @@ const postTaskCommand = {
|
|
|
1579
1592
|
quality: ctx.flags.quality,
|
|
1580
1593
|
agent: ctx.flags.agent,
|
|
1581
1594
|
timestamp: Date.now(),
|
|
1595
|
+
// ADR-147 P2: forward spawn-tree lineage if caller supplied it
|
|
1596
|
+
parentAgentId: ctx.flags.parentAgentId,
|
|
1597
|
+
depth: ctx.flags.depth,
|
|
1582
1598
|
});
|
|
1583
1599
|
if (ctx.flags.format === 'json') {
|
|
1584
1600
|
output.printJson(result);
|
|
@@ -65,10 +65,51 @@ const CONFIG = {
|
|
|
65
65
|
const CWD = process.cwd();
|
|
66
66
|
|
|
67
67
|
// ─── Delegation cache ───────────────────────────────────────────
|
|
68
|
-
// Cache the CLI JSON result for
|
|
69
|
-
// (
|
|
68
|
+
// Cache the CLI JSON result for 60s so rapid prompt re-renders
|
|
69
|
+
// (Claude Code refreshes the statusline several times a second while
|
|
70
|
+
// streaming) don't re-invoke the CLI each time. #2337: bumped 10s→60s
|
|
71
|
+
// because 10s was far too short for how often Claude Code re-renders.
|
|
70
72
|
const CACHE_FILE = path.join(os.tmpdir(), 'ruflo-statusline-cache-' + require('crypto').createHash('md5').update(CWD).digest('hex').slice(0, 8) + '.json');
|
|
71
|
-
const CACHE_TTL_MS =
|
|
73
|
+
const CACHE_TTL_MS = 60000;
|
|
74
|
+
|
|
75
|
+
// #2337: resolve an already-installed @claude-flow/cli (or ruflo) bin so we
|
|
76
|
+
// can invoke it directly via \`node\`. The previous version called
|
|
77
|
+
// \`npx --yes @claude-flow/cli@latest\` on every uncached render, which forces
|
|
78
|
+
// a registry resolution + cold-start of the entire CLI per render. With
|
|
79
|
+
// multiple concurrent Claude Code sessions this storms the host (reporter
|
|
80
|
+
// saw load average 40-65 on a 12-core box).
|
|
81
|
+
//
|
|
82
|
+
// Returns the absolute path to bin/cli.js or null. Mirrors getPkgVersion()'s
|
|
83
|
+
// path probing (project, monorepo, plugin marketplace, global node_modules
|
|
84
|
+
// including custom-prefix layouts like ~/.npm-global).
|
|
85
|
+
function resolveCliBin() {
|
|
86
|
+
try {
|
|
87
|
+
const home = os.homedir();
|
|
88
|
+
const candidates = [
|
|
89
|
+
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
90
|
+
path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
91
|
+
path.join(CWD, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
92
|
+
path.join(CWD, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
93
|
+
];
|
|
94
|
+
try {
|
|
95
|
+
const binDir = path.dirname(process.execPath);
|
|
96
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
97
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
98
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
99
|
+
}
|
|
100
|
+
for (const gm of globalModuleDirs) {
|
|
101
|
+
candidates.push(
|
|
102
|
+
path.join(gm, 'ruflo', 'bin', 'cli.js'),
|
|
103
|
+
path.join(gm, '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
} catch { /* ignore */ }
|
|
107
|
+
for (const p of candidates) {
|
|
108
|
+
if (fs.existsSync(p)) return p;
|
|
109
|
+
}
|
|
110
|
+
} catch { /* ignore */ }
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
72
113
|
|
|
73
114
|
function readCache() {
|
|
74
115
|
try {
|
|
@@ -99,16 +140,25 @@ function getStatuslineData() {
|
|
|
99
140
|
if (cached) return cached;
|
|
100
141
|
|
|
101
142
|
try {
|
|
143
|
+
// #2337: prefer an already-installed CLI bin via direct \`node\` invocation
|
|
144
|
+
// — no npx, no registry round-trip, no @latest re-resolve per render.
|
|
145
|
+
// Fall back to \`npx --prefer-offline @claude-flow/cli\` (no @latest) only
|
|
146
|
+
// when nothing is installed locally, so a cold environment still works.
|
|
147
|
+
const cliBin = resolveCliBin();
|
|
148
|
+
const cmd = cliBin
|
|
149
|
+
? '"' + process.execPath + '" "' + cliBin + '" hooks statusline --json 2>/dev/null'
|
|
150
|
+
: 'npx --prefer-offline @claude-flow/cli hooks statusline --json 2>/dev/null';
|
|
102
151
|
const raw = execSync(
|
|
103
|
-
|
|
152
|
+
cmd,
|
|
104
153
|
{ encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], cwd: CWD }
|
|
105
154
|
).trim();
|
|
106
155
|
// The CLI may emit preamble lines before the JSON — find the first '{'.
|
|
107
156
|
const jsonStart = raw.indexOf('{');
|
|
108
157
|
if (jsonStart === -1) throw new Error('no JSON in CLI output');
|
|
109
158
|
const data = JSON.parse(raw.slice(jsonStart));
|
|
110
|
-
// Overlay
|
|
111
|
-
|
|
159
|
+
// Overlay every block the CLI JSON omits (adrs/agentdb/tests/hooks/integration)
|
|
160
|
+
// with real local reads, so those segments reflect actual state instead of 0.
|
|
161
|
+
applyLocalOverlays(data);
|
|
112
162
|
writeCache(data);
|
|
113
163
|
return data;
|
|
114
164
|
} catch { /* CLI unavailable or timed out */ }
|
|
@@ -141,13 +191,40 @@ function getLocalADRCount() {
|
|
|
141
191
|
return { count: total, implemented: total, compliance: 0 };
|
|
142
192
|
}
|
|
143
193
|
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const adrs = getLocalADRCount();
|
|
194
|
+
// ─── Local overlays for segments the CLI JSON omits ──────────────
|
|
195
|
+
// 'hooks statusline --json' only returns user/v3Progress/security/swarm/system.
|
|
196
|
+
// agentdb/tests/hooks/integration are never populated, so without these overlays
|
|
197
|
+
// they render as a permanent 0. Each reader is cheap and degrades to zeros.
|
|
149
198
|
|
|
150
|
-
|
|
199
|
+
// Real AgentDB stats from the local memory DB. Vectors live in .swarm/memory.db
|
|
200
|
+
// (sql.js + HNSW); ruvector.db is an opaque redb store counted only toward size.
|
|
201
|
+
// One read-only sqlite3 query (mode=ro never takes a write lock the daemon owns).
|
|
202
|
+
function getLocalAgentDB() {
|
|
203
|
+
const result = { vectorCount: 0, dbSizeKB: 0, hasHnsw: false };
|
|
204
|
+
try {
|
|
205
|
+
let bytes = 0;
|
|
206
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
207
|
+
try { bytes += fs.statSync(path.join(CWD, f)).size; } catch { /* missing */ }
|
|
208
|
+
}
|
|
209
|
+
result.dbSizeKB = Math.round(bytes / 1024);
|
|
210
|
+
|
|
211
|
+
const memDb = path.join(CWD, '.swarm', 'memory.db');
|
|
212
|
+
if (fs.existsSync(memDb)) {
|
|
213
|
+
const Q = String.fromCharCode(34);
|
|
214
|
+
const sql = Q + 'SELECT (SELECT COUNT(*) FROM memory_entries WHERE embedding IS NOT NULL)||' + "'|'" + '||(SELECT COUNT(*) FROM vector_indexes);' + Q;
|
|
215
|
+
const out = safeExec("sqlite3 'file:" + memDb + "?mode=ro' " + sql, 1500);
|
|
216
|
+
if (out && out.indexOf('|') !== -1) {
|
|
217
|
+
const parts = out.split('|');
|
|
218
|
+
result.vectorCount = parseInt(parts[0], 10) || 0;
|
|
219
|
+
result.hasHnsw = (parseInt(parts[1], 10) || 0) > 0;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch { /* ignore */ }
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Count test files via a bounded directory walk (no file reads).
|
|
227
|
+
function getLocalTests() {
|
|
151
228
|
let testFiles = 0;
|
|
152
229
|
function countTests(dir, depth) {
|
|
153
230
|
if ((depth || 0) > 4) return;
|
|
@@ -163,19 +240,82 @@ function buildLocalFallback() {
|
|
|
163
240
|
} catch { /* ignore */ }
|
|
164
241
|
}
|
|
165
242
|
for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d));
|
|
243
|
+
return { testFiles, testCases: testFiles * 4 };
|
|
244
|
+
}
|
|
166
245
|
|
|
167
|
-
|
|
246
|
+
// Count configured hooks from project .claude/settings.json. Claude Code hooks
|
|
247
|
+
// have no enabled/disabled flag, so every configured hook counts as enabled.
|
|
248
|
+
function getLocalHooks() {
|
|
249
|
+
const result = { enabled: 0, total: 0 };
|
|
250
|
+
try {
|
|
251
|
+
const settings = readJSON(path.join(CWD, '.claude', 'settings.json'));
|
|
252
|
+
const hooks = settings && settings.hooks;
|
|
253
|
+
if (hooks && typeof hooks === 'object') {
|
|
254
|
+
let n = 0;
|
|
255
|
+
for (const ev of Object.keys(hooks)) {
|
|
256
|
+
const groups = hooks[ev];
|
|
257
|
+
if (Array.isArray(groups)) {
|
|
258
|
+
for (const g of groups) {
|
|
259
|
+
if (g && Array.isArray(g.hooks)) n += g.hooks.length;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
result.total = n;
|
|
264
|
+
result.enabled = n;
|
|
265
|
+
}
|
|
266
|
+
} catch { /* ignore */ }
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Best-effort integration block: DB presence + locally-configured stdio MCP
|
|
271
|
+
// servers (project .mcp.json + global ~/.claude.json). Remote connectors are
|
|
272
|
+
// account-managed and not present in local config, so they are not counted.
|
|
273
|
+
function getLocalIntegration() {
|
|
274
|
+
const integration = { mcpServers: { enabled: 0, total: 0 }, hasDatabase: false };
|
|
275
|
+
try {
|
|
276
|
+
for (const f of ['.swarm/memory.db', 'ruvector.db']) {
|
|
277
|
+
if (fs.existsSync(path.join(CWD, f))) { integration.hasDatabase = true; break; }
|
|
278
|
+
}
|
|
279
|
+
const names = new Set();
|
|
280
|
+
const projMcp = readJSON(path.join(CWD, '.mcp.json'));
|
|
281
|
+
if (projMcp && projMcp.mcpServers) for (const k of Object.keys(projMcp.mcpServers)) names.add(k);
|
|
282
|
+
const claudeJson = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
283
|
+
if (claudeJson) {
|
|
284
|
+
if (claudeJson.mcpServers) for (const k of Object.keys(claudeJson.mcpServers)) names.add(k);
|
|
285
|
+
const proj = claudeJson.projects && claudeJson.projects[CWD];
|
|
286
|
+
if (proj && proj.mcpServers && !Array.isArray(proj.mcpServers)) {
|
|
287
|
+
for (const k of Object.keys(proj.mcpServers)) names.add(k);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
integration.mcpServers.total = names.size;
|
|
291
|
+
integration.mcpServers.enabled = names.size;
|
|
292
|
+
} catch { /* ignore */ }
|
|
293
|
+
return integration;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Overlay every locally-derived block onto the CLI data (mutates in place).
|
|
297
|
+
function applyLocalOverlays(data) {
|
|
298
|
+
data.adrs = getLocalADRCount();
|
|
299
|
+
data.agentdb = getLocalAgentDB();
|
|
300
|
+
data.tests = getLocalTests();
|
|
301
|
+
data.hooks = getLocalHooks();
|
|
302
|
+
data.integration = getLocalIntegration();
|
|
303
|
+
return data;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Minimal local fallback when the CLI is not installed or times out.
|
|
307
|
+
// Returns a structure that matches the CLI JSON schema so the renderer works.
|
|
308
|
+
function buildLocalFallback() {
|
|
309
|
+
const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
310
|
+
|
|
311
|
+
return applyLocalOverlays({
|
|
168
312
|
user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
|
|
169
313
|
v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
|
|
170
314
|
security: { status: 'NONE', cvesFixed: 0, totalCves: 0 },
|
|
171
315
|
swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
|
|
172
316
|
system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
|
|
173
|
-
adrs,
|
|
174
|
-
hooks: { enabled: 0, total: 0 },
|
|
175
|
-
agentdb: { vectorCount: 0, dbSizeKB: 0, hasHnsw: false },
|
|
176
|
-
tests: { testFiles, testCases: testFiles * 4 },
|
|
177
317
|
lastUpdated: new Date().toISOString(),
|
|
178
|
-
};
|
|
318
|
+
});
|
|
179
319
|
}
|
|
180
320
|
|
|
181
321
|
// ANSI colors
|
|
@@ -375,7 +515,14 @@ function getPkgVersion() {
|
|
|
375
515
|
// (bin/node_modules) layouts.
|
|
376
516
|
try {
|
|
377
517
|
const binDir = path.dirname(process.execPath);
|
|
378
|
-
|
|
518
|
+
const globalModuleDirs = [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')];
|
|
519
|
+
// #2221 follow-up: a custom npm prefix (e.g. ~/.npm-global) is decoupled from
|
|
520
|
+
// the node binary location, so the binDir-derived probes above all miss. Also
|
|
521
|
+
// probe the npm prefix from the environment and the common ~/.npm-global default.
|
|
522
|
+
for (const prefix of [process.env.npm_config_prefix, process.env.PREFIX, path.join(home, '.npm-global')]) {
|
|
523
|
+
if (prefix) globalModuleDirs.push(path.join(prefix, 'lib', 'node_modules'));
|
|
524
|
+
}
|
|
525
|
+
for (const gm of globalModuleDirs) {
|
|
379
526
|
pkgPaths.push(
|
|
380
527
|
path.join(gm, 'ruflo', 'package.json'),
|
|
381
528
|
path.join(gm, '@claude-flow', 'cli', 'package.json'),
|
|
@@ -1239,6 +1239,9 @@ export const hooksPostTask = {
|
|
|
1239
1239
|
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1240
1240
|
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1241
1241
|
storeDecisions: { type: 'boolean', description: 'Also store routing decision in memory DB' },
|
|
1242
|
+
// ADR-147 P2: nested-subagent spawn-tree capture
|
|
1243
|
+
parentAgentId: { type: 'string', description: 'ID of the parent agent (from Claude Code\'s parent_agent_id OTel span tag / x-claude-code-parent-agent-id header). Omit for top-level work.' },
|
|
1244
|
+
depth: { type: 'number', description: 'Chain depth from root lead session (0 = lead, 1+ = subagent). Used by ADR-147 P3 depth-aware guardrail.' },
|
|
1242
1245
|
},
|
|
1243
1246
|
required: ['taskId'],
|
|
1244
1247
|
},
|
|
@@ -1258,6 +1261,22 @@ export const hooksPostTask = {
|
|
|
1258
1261
|
if (!v.valid)
|
|
1259
1262
|
return { success: false, error: v.error };
|
|
1260
1263
|
}
|
|
1264
|
+
// ADR-147 P2: validate spawn-tree lineage if provided
|
|
1265
|
+
const parentAgentId = params.parentAgentId;
|
|
1266
|
+
if (parentAgentId !== undefined) {
|
|
1267
|
+
const v = validateIdentifier(parentAgentId, 'parentAgentId');
|
|
1268
|
+
if (!v.valid)
|
|
1269
|
+
return { success: false, error: v.error };
|
|
1270
|
+
}
|
|
1271
|
+
const depthRaw = params.depth;
|
|
1272
|
+
let depth;
|
|
1273
|
+
if (depthRaw !== undefined && depthRaw !== null) {
|
|
1274
|
+
const n = Number(depthRaw);
|
|
1275
|
+
if (!Number.isInteger(n) || n < 0 || n > 32) {
|
|
1276
|
+
return { success: false, error: 'depth must be a non-negative integer ≤ 32' };
|
|
1277
|
+
}
|
|
1278
|
+
depth = n;
|
|
1279
|
+
}
|
|
1261
1280
|
// Phase 3: Wire recordFeedback through bridge → LearningSystem + ReasoningBank
|
|
1262
1281
|
let feedbackResult = null;
|
|
1263
1282
|
try {
|
|
@@ -1269,6 +1288,9 @@ export const hooksPostTask = {
|
|
|
1269
1288
|
agent,
|
|
1270
1289
|
duration: params.duration || undefined,
|
|
1271
1290
|
patterns: params.patterns || undefined,
|
|
1291
|
+
// ADR-147 P2: forward spawn-tree lineage so it lands in feedback + memory
|
|
1292
|
+
parentAgentId,
|
|
1293
|
+
depth,
|
|
1272
1294
|
});
|
|
1273
1295
|
}
|
|
1274
1296
|
catch {
|
|
@@ -3931,7 +3953,10 @@ export const hooksModelRoute = {
|
|
|
3931
3953
|
alternatives: result.alternatives,
|
|
3932
3954
|
inferenceTimeUs: result.inferenceTimeUs,
|
|
3933
3955
|
costMultiplier: result.costMultiplier,
|
|
3934
|
-
|
|
3956
|
+
// Historical name kept for telemetry / dashboard schema stability.
|
|
3957
|
+
// The shipped router is the heuristic + Thompson-bandit described in
|
|
3958
|
+
// ruvector/model-router.ts — not a neural network. See #2329.
|
|
3959
|
+
implementation: 'heuristic-thompson-bandit',
|
|
3935
3960
|
};
|
|
3936
3961
|
},
|
|
3937
3962
|
};
|
|
@@ -1422,6 +1422,8 @@ export async function bridgeRecordFeedback(options) {
|
|
|
1422
1422
|
await learningSystem.recordFeedback({
|
|
1423
1423
|
taskId: options.taskId, success: options.success, quality: options.quality,
|
|
1424
1424
|
agent: options.agent, duration: options.duration, timestamp: Date.now(),
|
|
1425
|
+
// ADR-147 P2: forward spawn-tree lineage if present
|
|
1426
|
+
parentAgentId: options.parentAgentId, depth: options.depth,
|
|
1425
1427
|
});
|
|
1426
1428
|
controller = 'learningSystem';
|
|
1427
1429
|
updated++;
|
|
@@ -71,7 +71,7 @@ export interface EnhancedModelRouterConfig {
|
|
|
71
71
|
*/
|
|
72
72
|
export declare class EnhancedModelRouter {
|
|
73
73
|
private config;
|
|
74
|
-
private
|
|
74
|
+
private baseRouter;
|
|
75
75
|
constructor(config?: Partial<EnhancedModelRouterConfig>);
|
|
76
76
|
/**
|
|
77
77
|
* Detect code editing intent from task description
|
|
@@ -177,7 +177,12 @@ const LANGUAGE_MAP = {
|
|
|
177
177
|
*/
|
|
178
178
|
export class EnhancedModelRouter {
|
|
179
179
|
config;
|
|
180
|
-
|
|
180
|
+
// The base text-routing path delegated to here is the local
|
|
181
|
+
// heuristic + Thompson-bandit ModelRouter — NOT the @ruvector/tiny-dancer
|
|
182
|
+
// neural router that an earlier design (ADR-026) described (#2329). The
|
|
183
|
+
// public `getStats()` return still exposes the field as `tinyDancerStats`
|
|
184
|
+
// for telemetry-schema stability.
|
|
185
|
+
baseRouter;
|
|
181
186
|
constructor(config) {
|
|
182
187
|
this.config = {
|
|
183
188
|
agentBoosterEnabled: true,
|
|
@@ -199,7 +204,7 @@ export class EnhancedModelRouter {
|
|
|
199
204
|
preferQuality: false,
|
|
200
205
|
...config,
|
|
201
206
|
};
|
|
202
|
-
this.
|
|
207
|
+
this.baseRouter = getModelRouter();
|
|
203
208
|
}
|
|
204
209
|
/**
|
|
205
210
|
* Detect code editing intent from task description
|
|
@@ -355,13 +360,13 @@ export class EnhancedModelRouter {
|
|
|
355
360
|
// AST analysis not available, continue with text-based routing
|
|
356
361
|
}
|
|
357
362
|
}
|
|
358
|
-
// Step 4: Text-based complexity +
|
|
359
|
-
const
|
|
360
|
-
// Step 5: Combine AST complexity with
|
|
361
|
-
// Also boost if single tier3 keyword found
|
|
363
|
+
// Step 4: Text-based complexity via the local heuristic + bandit router.
|
|
364
|
+
const baseResult = await this.baseRouter.route(task);
|
|
365
|
+
// Step 5: Combine AST complexity with the text-routing result.
|
|
366
|
+
// Also boost if a single tier3 keyword is found.
|
|
362
367
|
let finalComplexity = astComplexity !== undefined
|
|
363
|
-
? (astComplexity +
|
|
364
|
-
:
|
|
368
|
+
? (astComplexity + baseResult.complexity) / 2
|
|
369
|
+
: baseResult.complexity;
|
|
365
370
|
// Boost complexity if tier3 keywords found (even just one)
|
|
366
371
|
if (tier3Check.matches) {
|
|
367
372
|
finalComplexity = Math.min(1.0, finalComplexity + 0.25);
|
|
@@ -373,7 +378,7 @@ export class EnhancedModelRouter {
|
|
|
373
378
|
tier: 2,
|
|
374
379
|
handler: 'haiku',
|
|
375
380
|
model: 'haiku',
|
|
376
|
-
confidence:
|
|
381
|
+
confidence: baseResult.confidence,
|
|
377
382
|
complexity: finalComplexity,
|
|
378
383
|
reasoning: `Low complexity (${(finalComplexity * 100).toFixed(0)}%) - using haiku`,
|
|
379
384
|
canSkipLLM: false,
|
|
@@ -386,7 +391,7 @@ export class EnhancedModelRouter {
|
|
|
386
391
|
tier: 2,
|
|
387
392
|
handler: 'sonnet',
|
|
388
393
|
model: 'sonnet',
|
|
389
|
-
confidence:
|
|
394
|
+
confidence: baseResult.confidence,
|
|
390
395
|
complexity: finalComplexity,
|
|
391
396
|
reasoning: `Medium complexity (${(finalComplexity * 100).toFixed(0)}%) - using sonnet`,
|
|
392
397
|
canSkipLLM: false,
|
|
@@ -398,7 +403,7 @@ export class EnhancedModelRouter {
|
|
|
398
403
|
tier: 3,
|
|
399
404
|
handler: 'opus',
|
|
400
405
|
model: 'opus',
|
|
401
|
-
confidence:
|
|
406
|
+
confidence: baseResult.confidence,
|
|
402
407
|
complexity: finalComplexity,
|
|
403
408
|
reasoning: `High complexity (${(finalComplexity * 100).toFixed(0)}%) - using opus`,
|
|
404
409
|
canSkipLLM: false,
|
|
@@ -494,7 +499,10 @@ export class EnhancedModelRouter {
|
|
|
494
499
|
getStats() {
|
|
495
500
|
return {
|
|
496
501
|
config: { ...this.config },
|
|
497
|
-
tinyDancerStats
|
|
502
|
+
// Field name kept as `tinyDancerStats` for telemetry-schema
|
|
503
|
+
// stability; the underlying router is the local heuristic + bandit
|
|
504
|
+
// ModelRouter, not @ruvector/tiny-dancer. See #2329.
|
|
505
|
+
tinyDancerStats: this.baseRouter.getStats(),
|
|
498
506
|
};
|
|
499
507
|
}
|
|
500
508
|
}
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Intelligent Model Router
|
|
2
|
+
* Intelligent Model Router — lexical complexity heuristic + Thompson bandit
|
|
3
3
|
*
|
|
4
|
-
* Dynamically routes requests to optimal Claude model (haiku/sonnet/opus)
|
|
5
|
-
* based on task complexity,
|
|
4
|
+
* Dynamically routes requests to the optimal Claude model (haiku/sonnet/opus)
|
|
5
|
+
* based on task complexity, uncertainty, and online-learned routing outcomes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
7
|
+
* Mechanism (shipped):
|
|
8
|
+
* - Complexity score = blend of lexical, semantic-depth, task-scope, and
|
|
9
|
+
* uncertainty heuristics (see `computeLexicalComplexity` and friends).
|
|
10
|
+
* Pure JS arithmetic — no model load, no tensor math.
|
|
11
|
+
* - Model selection = Thompson-sampling Beta-Bernoulli bandit with
|
|
12
|
+
* complexity-bucketed Beta(α,β) priors, persisted to
|
|
13
|
+
* `.swarm/model-router-state.json` and updated by `recordOutcome` after
|
|
14
|
+
* each routing decision.
|
|
15
|
+
* - Uncertainty quantification + a circuit breaker drive escalation when
|
|
16
|
+
* the bandit's confidence is low or downstream failures are observed.
|
|
13
17
|
*
|
|
14
18
|
* Routing Strategy:
|
|
15
|
-
* - Haiku:
|
|
16
|
-
* - Sonnet:
|
|
17
|
-
* - Opus:
|
|
19
|
+
* - Haiku: high confidence, low complexity (fast, cheap)
|
|
20
|
+
* - Sonnet: medium confidence, moderate complexity (balanced)
|
|
21
|
+
* - Opus: low confidence, high complexity (most capable)
|
|
22
|
+
*
|
|
23
|
+
* Note (#2329): An earlier design (ADR-026 + this file's previous header)
|
|
24
|
+
* described a Tiny-Dancer / FastGRNN neural router with embedding-based
|
|
25
|
+
* complexity scoring. That path was never wired in — `@ruvector/tiny-dancer`
|
|
26
|
+
* is not imported here and the `embedding`-consuming branch in
|
|
27
|
+
* `computeSemanticDepth` is only reachable via the externally-callable
|
|
28
|
+
* `routeToModelFull(task, embedding)` wrapper (no internal callers). The
|
|
29
|
+
* shipped router is the heuristic + bandit described above; the neural
|
|
30
|
+
* path remains a future direction tracked in #2329.
|
|
18
31
|
*
|
|
19
32
|
* @module model-router
|
|
20
33
|
*/
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Intelligent Model Router
|
|
2
|
+
* Intelligent Model Router — lexical complexity heuristic + Thompson bandit
|
|
3
3
|
*
|
|
4
|
-
* Dynamically routes requests to optimal Claude model (haiku/sonnet/opus)
|
|
5
|
-
* based on task complexity,
|
|
4
|
+
* Dynamically routes requests to the optimal Claude model (haiku/sonnet/opus)
|
|
5
|
+
* based on task complexity, uncertainty, and online-learned routing outcomes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
7
|
+
* Mechanism (shipped):
|
|
8
|
+
* - Complexity score = blend of lexical, semantic-depth, task-scope, and
|
|
9
|
+
* uncertainty heuristics (see `computeLexicalComplexity` and friends).
|
|
10
|
+
* Pure JS arithmetic — no model load, no tensor math.
|
|
11
|
+
* - Model selection = Thompson-sampling Beta-Bernoulli bandit with
|
|
12
|
+
* complexity-bucketed Beta(α,β) priors, persisted to
|
|
13
|
+
* `.swarm/model-router-state.json` and updated by `recordOutcome` after
|
|
14
|
+
* each routing decision.
|
|
15
|
+
* - Uncertainty quantification + a circuit breaker drive escalation when
|
|
16
|
+
* the bandit's confidence is low or downstream failures are observed.
|
|
13
17
|
*
|
|
14
18
|
* Routing Strategy:
|
|
15
|
-
* - Haiku:
|
|
16
|
-
* - Sonnet:
|
|
17
|
-
* - Opus:
|
|
19
|
+
* - Haiku: high confidence, low complexity (fast, cheap)
|
|
20
|
+
* - Sonnet: medium confidence, moderate complexity (balanced)
|
|
21
|
+
* - Opus: low confidence, high complexity (most capable)
|
|
22
|
+
*
|
|
23
|
+
* Note (#2329): An earlier design (ADR-026 + this file's previous header)
|
|
24
|
+
* described a Tiny-Dancer / FastGRNN neural router with embedding-based
|
|
25
|
+
* complexity scoring. That path was never wired in — `@ruvector/tiny-dancer`
|
|
26
|
+
* is not imported here and the `embedding`-consuming branch in
|
|
27
|
+
* `computeSemanticDepth` is only reachable via the externally-callable
|
|
28
|
+
* `routeToModelFull(task, embedding)` wrapper (no internal callers). The
|
|
29
|
+
* shipped router is the heuristic + bandit described above; the neural
|
|
30
|
+
* path remains a future direction tracked in #2329.
|
|
18
31
|
*
|
|
19
32
|
* @module model-router
|
|
20
33
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"52c25bf9-4a48-4a92-b86e-952e2e8c9fd2","pid":39252,"acquiredAt":1780929066497}
|