@claude-flow/cli 3.43.0 → 3.44.0
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/helpers.manifest.json +4 -3
- package/catalog-manifest.json +2 -2
- package/dist/src/init/executor.js +2 -1
- package/dist/src/init/helper-refresh.js +5 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +31 -0
- package/dist/src/mcp-tools/hooks-tools.js +248 -179
- package/dist/src/ruvector/router-embedder.d.ts +58 -0
- package/dist/src/ruvector/router-embedder.js +141 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +1 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.44.0",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "85fe05c757421c52137c0bc8545a0896bab6b4714538c2a11d1d0835bfcc8c1c",
|
|
6
6
|
"hook-handler.cjs": "209d9fafe10e17d1be0866727f6f9cf9ac66f9a0793f1c793a4f58319e8e4583",
|
|
7
7
|
"intelligence.cjs": "30e42ed7ec4ca5a94ac54fdb1330d2d47ac5f3fdeeef207753574723a9e77b5c",
|
|
8
|
-
"statusline.cjs": "4a48353b4f1566fa4379b00fd0321b6676a22b6cc91fbbd8380ac5183d619468"
|
|
8
|
+
"statusline.cjs": "4a48353b4f1566fa4379b00fd0321b6676a22b6cc91fbbd8380ac5183d619468",
|
|
9
|
+
"router.js": "b6998397e7883191b62229ccdc66fb87f1ccca1d5b2039ecb8c1e686d1ad81f8"
|
|
9
10
|
}
|
|
10
11
|
},
|
|
11
|
-
"signature": "
|
|
12
|
+
"signature": "vl9y84CRx7XaIXMi3iD1q3UVqeUvGsvpwjXkhMuA42myuVhCbgtnSNM9W8M3Pv9Odqy5qucwwDsz7MB7mUqDBg==",
|
|
12
13
|
"algorithm": "ed25519"
|
|
13
14
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -499,7 +499,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
499
499
|
const sourceHelpersForUpgrade = findSourceHelpersDir();
|
|
500
500
|
if (sourceHelpersForUpgrade) {
|
|
501
501
|
// Keep in sync with helper-refresh.ts:CRITICAL_HELPERS.
|
|
502
|
-
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'intelligence.cjs', 'statusline.cjs'];
|
|
502
|
+
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'intelligence.cjs', 'statusline.cjs', 'router.js'];
|
|
503
503
|
for (const helperName of criticalHelpers) {
|
|
504
504
|
const targetPath = path.join(targetDir, '.claude', 'helpers', helperName);
|
|
505
505
|
const sourcePath = path.join(sourceHelpersForUpgrade, helperName);
|
|
@@ -524,6 +524,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
524
524
|
'hook-handler.cjs': generateHookHandler(),
|
|
525
525
|
'intelligence.cjs': generateIntelligenceStub(),
|
|
526
526
|
'auto-memory-hook.mjs': generateAutoMemoryHook(),
|
|
527
|
+
'router.js': generateAgentRouter(), // ADR-389
|
|
527
528
|
};
|
|
528
529
|
for (const [helperName, content] of Object.entries(generatedCritical)) {
|
|
529
530
|
const targetPath = path.join(targetDir, '.claude', 'helpers', helperName);
|
|
@@ -71,6 +71,10 @@ export const CRITICAL_HELPERS = [
|
|
|
71
71
|
// statusline.cjs is here so the funnel disclosure row (ADR-301) reaches
|
|
72
72
|
// existing installs on the next `ruflo` command, not only fresh `ruflo init`.
|
|
73
73
|
'statusline.cjs',
|
|
74
|
+
// router.js is loaded by hook-handler.cjs to label each prompt with an agent.
|
|
75
|
+
// Without it here, installs kept the pre-#2257 substring router forever
|
|
76
|
+
// ("latest" -> tester). ADR-389 / #3401.
|
|
77
|
+
'router.js',
|
|
74
78
|
];
|
|
75
79
|
function errorCode(error) {
|
|
76
80
|
return typeof error === 'object' && error !== null && 'code' in error
|
|
@@ -309,6 +313,7 @@ async function writeCriticalHelpers(helpersDir, version, opts = {}) {
|
|
|
309
313
|
'hook-handler.cjs': gen.generateHookHandler(),
|
|
310
314
|
'intelligence.cjs': gen.generateIntelligenceStub(),
|
|
311
315
|
'auto-memory-hook.mjs': gen.generateAutoMemoryHook(),
|
|
316
|
+
'router.js': gen.generateAgentRouter(), // ADR-389
|
|
312
317
|
// Fallback needs the same generator inputs `ruflo init` uses. We match the
|
|
313
318
|
// hardcoded default (maxAgents 15) because the fallback fires when the
|
|
314
319
|
// installed package is unresolvable — no way to read the user's project
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Provides intelligent hooks functionality via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { type MCPTool } from './types.js';
|
|
6
|
+
import { type RouterEmbedderKind } from '../ruvector/router-embedder.js';
|
|
6
7
|
/**
|
|
7
8
|
* Strip extended-thinking blocks from text before it enters a learning
|
|
8
9
|
* trajectory (hermes-agent think_scrubber pattern). Claude models with extended
|
|
@@ -13,6 +14,8 @@ import { type MCPTool } from './types.js';
|
|
|
13
14
|
* the tag names untouched.
|
|
14
15
|
*/
|
|
15
16
|
export declare function scrubReasoningBlocks(text: string): string;
|
|
17
|
+
/** Test hook: drop the cached semantic index so the next route rebuilds it. */
|
|
18
|
+
export declare function resetSemanticRouterForTests(): void;
|
|
16
19
|
/** Exported for tests. */
|
|
17
20
|
export declare function suggestAgentsForTask(task: string): {
|
|
18
21
|
agents: string[];
|
|
@@ -23,6 +26,34 @@ export declare const hooksPostEdit: MCPTool;
|
|
|
23
26
|
export declare const hooksPreCommand: MCPTool;
|
|
24
27
|
export declare const hooksPostCommand: MCPTool;
|
|
25
28
|
export declare const hooksRoute: MCPTool;
|
|
29
|
+
/** Result of {@link routeTaskForBench}. */
|
|
30
|
+
export interface BenchRouteResult {
|
|
31
|
+
primaryAgent: string;
|
|
32
|
+
confidence: number;
|
|
33
|
+
/** Matched pattern name, or 'keyword-fallback'. */
|
|
34
|
+
pattern: string;
|
|
35
|
+
/** Embedder actually used ('hash' if MiniLM was requested but unavailable). */
|
|
36
|
+
embedder: RouterEmbedderKind;
|
|
37
|
+
embedderReason?: string;
|
|
38
|
+
/** routing.method of the underlying route ('semantic-native' | 'semantic-pure-js' | 'keyword'). */
|
|
39
|
+
method: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* INTERNAL / BENCH-ONLY (ADR-391). Not a public API; may change without notice.
|
|
43
|
+
*
|
|
44
|
+
* Routes `task` through the same local path `hooks_route` uses (semantic index +
|
|
45
|
+
* keyword fallback) with an explicit embedder, without the MCP layer. It skips
|
|
46
|
+
* the two steps that are neither ADR-391 candidate A nor B: the AgentDB
|
|
47
|
+
* pre-route (`bridgeRouteTask`, which answers first when its confidence > 0.5)
|
|
48
|
+
* and the opt-in typesafe wrapper (CLAUDE_FLOW_ROUTER_TYPESAFE). With those
|
|
49
|
+
* inactive, `primaryAgent` equals `hooks_route`'s `primaryAgent.type`.
|
|
50
|
+
*
|
|
51
|
+
* Switching `embedder` between calls rebuilds the index; benchmark in blocks.
|
|
52
|
+
*/
|
|
53
|
+
export declare function routeTaskForBench(task: string, opts: {
|
|
54
|
+
embedder: RouterEmbedderKind;
|
|
55
|
+
context?: string;
|
|
56
|
+
}): Promise<BenchRouteResult>;
|
|
26
57
|
export declare const hooksMetrics: MCPTool;
|
|
27
58
|
export declare const hooksList: MCPTool;
|
|
28
59
|
export declare const hooksPreTask: MCPTool;
|
|
@@ -10,6 +10,7 @@ import { validateIdentifier, validateText, validatePath } from './validate-input
|
|
|
10
10
|
import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
|
|
11
11
|
import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
|
|
12
12
|
import { applyTypesafeRouting, getTypesafeRouter } from '../ruvector/typesafe-router.js';
|
|
13
|
+
import { DEFAULT_ROUTER_EMBEDDER, embedForRouter, resolveRouterEmbedder, } from '../ruvector/router-embedder.js';
|
|
13
14
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
14
15
|
let searchEntriesFn = null;
|
|
15
16
|
/**
|
|
@@ -108,46 +109,16 @@ async function getMoERouter() {
|
|
|
108
109
|
// Tries native VectorDb first (16k+ routes/s HNSW), falls back to pure JS (47k routes/s cosine)
|
|
109
110
|
let semanticRouter = null;
|
|
110
111
|
let nativeVectorDb = null;
|
|
111
|
-
let semanticRouterInitialized = false;
|
|
112
112
|
let routerBackend = 'none';
|
|
113
113
|
// Pre-computed embeddings for common task patterns (cached)
|
|
114
114
|
const TASK_PATTERN_EMBEDDINGS = new Map();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
for (let i = 0; i < dimension; i++) {
|
|
123
|
-
let value = 0;
|
|
124
|
-
// Word-level features
|
|
125
|
-
for (let w = 0; w < words.length; w++) {
|
|
126
|
-
const word = words[w];
|
|
127
|
-
for (let c = 0; c < word.length; c++) {
|
|
128
|
-
const charCode = word.charCodeAt(c);
|
|
129
|
-
value += Math.sin((charCode * (i + 1) + w * 17 + c * 23) * 0.0137);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// Character-level features
|
|
133
|
-
for (let c = 0; c < text.length; c++) {
|
|
134
|
-
value += Math.cos((text.charCodeAt(c) * (i + 1) + c * 7) * 0.0073);
|
|
135
|
-
}
|
|
136
|
-
embedding[i] = value / Math.max(1, text.length);
|
|
137
|
-
}
|
|
138
|
-
// Normalize
|
|
139
|
-
let norm = 0;
|
|
140
|
-
for (let i = 0; i < dimension; i++) {
|
|
141
|
-
norm += embedding[i] * embedding[i];
|
|
142
|
-
}
|
|
143
|
-
norm = Math.sqrt(norm);
|
|
144
|
-
if (norm > 0) {
|
|
145
|
-
for (let i = 0; i < dimension; i++) {
|
|
146
|
-
embedding[i] /= norm;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return embedding;
|
|
150
|
-
}
|
|
115
|
+
// ADR-390: the router's embedder (MiniLM or the historical hash). Pattern and
|
|
116
|
+
// query vectors always come from the SAME embedder; `routerEmbedder` records the
|
|
117
|
+
// one that actually built the current index (after any degradation to hash).
|
|
118
|
+
let routerEmbedder = DEFAULT_ROUTER_EMBEDDER;
|
|
119
|
+
let routerEmbedderReason;
|
|
120
|
+
let routerRequestedEmbedder = null;
|
|
121
|
+
let routerInitPromise = null;
|
|
151
122
|
// ── Runtime routing outcome persistence ──────────────────────────────
|
|
152
123
|
// Closes the learning loop: post-task records outcomes → route loads them.
|
|
153
124
|
const ROUTING_OUTCOMES_PATH = join(resolve('.'), '.claude-flow/routing-outcomes.json');
|
|
@@ -191,7 +162,8 @@ function saveRoutingOutcomes(outcomes) {
|
|
|
191
162
|
// The prior singleton cache made the learned store inert until restart.
|
|
192
163
|
semanticRouter = null;
|
|
193
164
|
nativeVectorDb = null;
|
|
194
|
-
|
|
165
|
+
routerInitPromise = null;
|
|
166
|
+
routerRequestedEmbedder = null;
|
|
195
167
|
routerBackend = 'none';
|
|
196
168
|
TASK_PATTERN_EMBEDDINGS.clear();
|
|
197
169
|
}
|
|
@@ -277,12 +249,58 @@ function withTypesafeRouting(legacy) {
|
|
|
277
249
|
/**
|
|
278
250
|
* Get the semantic router with environment detection.
|
|
279
251
|
* Tries native VectorDb first (HNSW, 16k routes/s), falls back to pure JS (47k routes/s cosine).
|
|
252
|
+
*
|
|
253
|
+
* ADR-390: `requested` (else CLAUDE_FLOW_ROUTER_EMBEDDER, else the default)
|
|
254
|
+
* picks the embedder. The index is rebuilt when the requested embedder changes.
|
|
255
|
+
* Concurrent callers share one in-flight build.
|
|
280
256
|
*/
|
|
281
|
-
async function getSemanticRouter() {
|
|
282
|
-
|
|
283
|
-
|
|
257
|
+
async function getSemanticRouter(requested) {
|
|
258
|
+
const selection = resolveRouterEmbedder(requested);
|
|
259
|
+
if (routerInitPromise && routerRequestedEmbedder === selection.kind) {
|
|
260
|
+
return routerInitPromise;
|
|
284
261
|
}
|
|
285
|
-
|
|
262
|
+
semanticRouter = null;
|
|
263
|
+
nativeVectorDb = null;
|
|
264
|
+
routerBackend = 'none';
|
|
265
|
+
TASK_PATTERN_EMBEDDINGS.clear();
|
|
266
|
+
routerRequestedEmbedder = selection.kind;
|
|
267
|
+
routerInitPromise = buildSemanticRouter(selection.kind, selection.reason);
|
|
268
|
+
return routerInitPromise;
|
|
269
|
+
}
|
|
270
|
+
/** Test hook: drop the cached semantic index so the next route rebuilds it. */
|
|
271
|
+
export function resetSemanticRouterForTests() {
|
|
272
|
+
semanticRouter = null;
|
|
273
|
+
nativeVectorDb = null;
|
|
274
|
+
routerBackend = 'none';
|
|
275
|
+
routerInitPromise = null;
|
|
276
|
+
routerRequestedEmbedder = null;
|
|
277
|
+
TASK_PATTERN_EMBEDDINGS.clear();
|
|
278
|
+
}
|
|
279
|
+
/** Embed every pattern keyword with ONE embedder (ADR-390: never mix spaces). */
|
|
280
|
+
async function embedPatternKeywords(patterns, kind) {
|
|
281
|
+
const entries = Object.entries(patterns);
|
|
282
|
+
const flat = entries.flatMap(([, p]) => p.keywords);
|
|
283
|
+
const res = await embedForRouter(flat, kind);
|
|
284
|
+
const vectors = new Map();
|
|
285
|
+
let i = 0;
|
|
286
|
+
for (const [name, p] of entries) {
|
|
287
|
+
vectors.set(name, res.vectors.slice(i, i + p.keywords.length));
|
|
288
|
+
i += p.keywords.length;
|
|
289
|
+
}
|
|
290
|
+
return { vectors, embedder: res.embedder, reason: res.reason };
|
|
291
|
+
}
|
|
292
|
+
async function buildSemanticRouter(kind, selectionReason) {
|
|
293
|
+
const patterns = getMergedTaskPatterns();
|
|
294
|
+
const embedded = await embedPatternKeywords(patterns, kind);
|
|
295
|
+
routerEmbedder = embedded.embedder;
|
|
296
|
+
routerEmbedderReason = embedded.reason ?? selectionReason;
|
|
297
|
+
const handle = () => ({
|
|
298
|
+
router: semanticRouter,
|
|
299
|
+
backend: routerBackend,
|
|
300
|
+
native: nativeVectorDb,
|
|
301
|
+
embedder: routerEmbedder,
|
|
302
|
+
...(routerEmbedderReason ? { embedderReason: routerEmbedderReason } : {}),
|
|
303
|
+
});
|
|
286
304
|
// STEP 1: Try native VectorDb from @ruvector/router (HNSW-backed)
|
|
287
305
|
// Note: Native VectorDb uses a persistent database file which can have lock issues
|
|
288
306
|
// in concurrent environments. We try it first but fall back gracefully to pure JS.
|
|
@@ -304,17 +322,17 @@ async function getSemanticRouter() {
|
|
|
304
322
|
hnswEfSearch: 100,
|
|
305
323
|
});
|
|
306
324
|
// Initialize with static + runtime-learned task patterns
|
|
307
|
-
for (const [patternName, { keywords }] of Object.entries(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
db.insert(`${patternName}:${keyword}`,
|
|
311
|
-
TASK_PATTERN_EMBEDDINGS.set(`${patternName}:${keyword}`,
|
|
312
|
-
}
|
|
325
|
+
for (const [patternName, { keywords }] of Object.entries(patterns)) {
|
|
326
|
+
const embeddings = embedded.vectors.get(patternName) ?? [];
|
|
327
|
+
keywords.forEach((keyword, i) => {
|
|
328
|
+
db.insert(`${patternName}:${keyword}`, embeddings[i]);
|
|
329
|
+
TASK_PATTERN_EMBEDDINGS.set(`${patternName}:${keyword}`, embeddings[i]);
|
|
330
|
+
});
|
|
313
331
|
}
|
|
314
332
|
nativeVectorDb = db;
|
|
315
333
|
routerBackend = 'native';
|
|
316
|
-
console.log(
|
|
317
|
-
return
|
|
334
|
+
console.log(`[hooks] Semantic router initialized: native VectorDb (HNSW, 16k+ routes/s), embedder=${routerEmbedder}`);
|
|
335
|
+
return handle();
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
338
|
catch (err) {
|
|
@@ -326,8 +344,8 @@ async function getSemanticRouter() {
|
|
|
326
344
|
try {
|
|
327
345
|
const { SemanticRouter } = await import('../ruvector/semantic-router.js');
|
|
328
346
|
semanticRouter = new SemanticRouter({ dimension: 384 });
|
|
329
|
-
for (const [patternName, { keywords, agents, source, support, reliability }] of Object.entries(
|
|
330
|
-
const embeddings =
|
|
347
|
+
for (const [patternName, { keywords, agents, source, support, reliability }] of Object.entries(patterns)) {
|
|
348
|
+
const embeddings = embedded.vectors.get(patternName) ?? [];
|
|
331
349
|
semanticRouter.addIntentWithEmbeddings(patternName, embeddings, {
|
|
332
350
|
agents,
|
|
333
351
|
keywords,
|
|
@@ -341,14 +359,14 @@ async function getSemanticRouter() {
|
|
|
341
359
|
});
|
|
342
360
|
}
|
|
343
361
|
routerBackend = 'pure-js';
|
|
344
|
-
console.log(
|
|
362
|
+
console.log(`[hooks] Semantic router initialized: pure JS (cosine, 47k routes/s), embedder=${routerEmbedder}`);
|
|
345
363
|
}
|
|
346
364
|
catch {
|
|
347
365
|
semanticRouter = null;
|
|
348
366
|
routerBackend = 'none';
|
|
349
367
|
console.log('[hooks] Semantic router initialized: none (no backend available)');
|
|
350
368
|
}
|
|
351
|
-
return
|
|
369
|
+
return handle();
|
|
352
370
|
}
|
|
353
371
|
/**
|
|
354
372
|
* Get router backend info for status display.
|
|
@@ -1021,134 +1039,185 @@ export const hooksRoute = {
|
|
|
1021
1039
|
// AgentDB router not available — fall through to local routing
|
|
1022
1040
|
}
|
|
1023
1041
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const routeStart = performance.now();
|
|
1067
|
-
semanticResult = router.routeWithEmbedding(queryEmbedding, 3);
|
|
1042
|
+
return routeTaskLocal(task, context, useSemanticRouter);
|
|
1043
|
+
}),
|
|
1044
|
+
};
|
|
1045
|
+
/**
|
|
1046
|
+
* hooks_route's local routing (semantic index + keyword fallback), run after the
|
|
1047
|
+
* AgentDB pre-route. Shared with {@link routeTaskForBench} so the benchmark
|
|
1048
|
+
* measures exactly the path hooks_route takes. (Body kept at its original
|
|
1049
|
+
* indentation to keep this refactor's diff small.)
|
|
1050
|
+
*/
|
|
1051
|
+
async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride) {
|
|
1052
|
+
// Get router (tries native VectorDb first, falls back to pure JS)
|
|
1053
|
+
let handle = useSemanticRouter
|
|
1054
|
+
? await getSemanticRouter(embedderOverride)
|
|
1055
|
+
: {
|
|
1056
|
+
router: null, backend: 'none', native: null,
|
|
1057
|
+
embedder: resolveRouterEmbedder(embedderOverride).kind,
|
|
1058
|
+
embedderReason: 'semantic router disabled (useSemanticRouter=false)',
|
|
1059
|
+
};
|
|
1060
|
+
let semanticResult = [];
|
|
1061
|
+
let routingMethod = 'keyword';
|
|
1062
|
+
let routingLatencyMs = 0;
|
|
1063
|
+
let backendInfo = '';
|
|
1064
|
+
const queryText = context ? `${task} ${context}` : task;
|
|
1065
|
+
// ADR-390: the query is embedded with the embedder that built the index.
|
|
1066
|
+
let queryEmbedding = null;
|
|
1067
|
+
if (handle.router || handle.native) {
|
|
1068
|
+
const q = await embedForRouter([queryText], handle.embedder);
|
|
1069
|
+
if (q.embedder !== handle.embedder) {
|
|
1070
|
+
// MiniLM failed on the query after building a MiniLM index: rebuild the
|
|
1071
|
+
// index with the hash so patterns and query share one space again.
|
|
1072
|
+
routerInitPromise = buildSemanticRouter('hash', q.reason);
|
|
1073
|
+
handle = await routerInitPromise;
|
|
1074
|
+
}
|
|
1075
|
+
queryEmbedding = q.vectors[0];
|
|
1076
|
+
}
|
|
1077
|
+
const { router, backend, native } = handle;
|
|
1078
|
+
// Try native VectorDb (HNSW-backed)
|
|
1079
|
+
if (native && backend === 'native' && queryEmbedding) {
|
|
1080
|
+
const routeStart = performance.now();
|
|
1081
|
+
try {
|
|
1082
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1083
|
+
const results = native.search(queryEmbedding, 5);
|
|
1068
1084
|
routingLatencyMs = performance.now() - routeStart;
|
|
1069
|
-
routingMethod = 'semantic-
|
|
1070
|
-
backendInfo = '
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (!learned)
|
|
1088
|
-
return true;
|
|
1089
|
-
return Number(match.metadata.support ?? 0) >= 2
|
|
1090
|
-
&& Number(match.metadata.reliability ?? 0) >= 0.75;
|
|
1091
|
-
});
|
|
1092
|
-
if (eligibleSemantic) {
|
|
1093
|
-
const topMatch = eligibleSemantic;
|
|
1094
|
-
agents = topMatch.metadata.agents || ['coder', 'researcher'];
|
|
1095
|
-
confidence = topMatch.score;
|
|
1096
|
-
matchedPattern = topMatch.intent;
|
|
1085
|
+
routingMethod = 'semantic-native';
|
|
1086
|
+
backendInfo = 'native VectorDb (HNSW)';
|
|
1087
|
+
// Convert results to semantic format
|
|
1088
|
+
const mergedPatterns = getMergedTaskPatterns();
|
|
1089
|
+
semanticResult = results.map((r) => {
|
|
1090
|
+
const [patternName] = r.id.split(':');
|
|
1091
|
+
const pattern = mergedPatterns[patternName];
|
|
1092
|
+
return {
|
|
1093
|
+
intent: patternName,
|
|
1094
|
+
score: 1 - r.score, // Native uses distance (lower is better), convert to similarity
|
|
1095
|
+
metadata: {
|
|
1096
|
+
agents: pattern?.agents || (patternName.startsWith('learned-') ? [patternName.slice(8)] : ['coder']),
|
|
1097
|
+
source: pattern?.source ?? (patternName.startsWith('learned-') ? 'learned' : 'static'),
|
|
1098
|
+
support: pattern?.support,
|
|
1099
|
+
reliability: pattern?.reliability,
|
|
1100
|
+
},
|
|
1101
|
+
};
|
|
1102
|
+
});
|
|
1097
1103
|
}
|
|
1098
|
-
|
|
1099
|
-
//
|
|
1100
|
-
const suggestion = suggestAgentsForTask(task);
|
|
1101
|
-
agents = suggestion.agents;
|
|
1102
|
-
confidence = suggestion.confidence;
|
|
1103
|
-
matchedPattern = 'keyword-fallback';
|
|
1104
|
-
routingMethod = 'keyword';
|
|
1105
|
-
backendInfo = 'keyword matching';
|
|
1104
|
+
catch {
|
|
1105
|
+
// Native failed, try pure JS fallback
|
|
1106
1106
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1107
|
+
}
|
|
1108
|
+
// Try pure JS SemanticRouter fallback
|
|
1109
|
+
if (router && backend === 'pure-js' && queryEmbedding && semanticResult.length === 0) {
|
|
1110
|
+
const routeStart = performance.now();
|
|
1111
|
+
semanticResult = router.routeWithEmbedding(queryEmbedding, 3);
|
|
1112
|
+
routingLatencyMs = performance.now() - routeStart;
|
|
1113
|
+
routingMethod = 'semantic-pure-js';
|
|
1114
|
+
backendInfo = 'pure JS (cosine similarity)';
|
|
1115
|
+
}
|
|
1116
|
+
// Get agents from semantic routing or fall back to keyword
|
|
1117
|
+
let agents;
|
|
1118
|
+
let confidence;
|
|
1119
|
+
let matchedPattern = '';
|
|
1120
|
+
// Both static and learned patterns are gated on the same similarity
|
|
1121
|
+
// score. Learned patterns additionally require support/reliability as a
|
|
1122
|
+
// quality guard, but do NOT need a higher score bar — a learned pattern
|
|
1123
|
+
// that outscores every static candidate must not lose to one anyway
|
|
1124
|
+
// (#2864: a 25pp higher threshold made a top-scoring learned-researcher
|
|
1125
|
+
// match at 0.57 lose to a static match at 0.52, discarding the learned
|
|
1126
|
+
// store's output on the majority of routes).
|
|
1127
|
+
const eligibleSemantic = semanticResult.find((match) => {
|
|
1128
|
+
if (match.score <= 0.4)
|
|
1129
|
+
return false;
|
|
1130
|
+
const learned = match.intent.startsWith('learned-') || match.metadata.source === 'learned';
|
|
1131
|
+
if (!learned)
|
|
1132
|
+
return true;
|
|
1133
|
+
return Number(match.metadata.support ?? 0) >= 2
|
|
1134
|
+
&& Number(match.metadata.reliability ?? 0) >= 0.75;
|
|
1135
|
+
});
|
|
1136
|
+
if (eligibleSemantic) {
|
|
1137
|
+
const topMatch = eligibleSemantic;
|
|
1138
|
+
agents = topMatch.metadata.agents || ['coder', 'researcher'];
|
|
1139
|
+
confidence = topMatch.score;
|
|
1140
|
+
matchedPattern = topMatch.intent;
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
// Fall back to keyword matching
|
|
1144
|
+
const suggestion = suggestAgentsForTask(task);
|
|
1145
|
+
agents = suggestion.agents;
|
|
1146
|
+
confidence = suggestion.confidence;
|
|
1147
|
+
matchedPattern = 'keyword-fallback';
|
|
1148
|
+
routingMethod = 'keyword';
|
|
1149
|
+
backendInfo = 'keyword matching';
|
|
1150
|
+
}
|
|
1151
|
+
// Determine complexity
|
|
1152
|
+
const taskLower = task.toLowerCase();
|
|
1153
|
+
const complexity = taskLower.includes('complex') || taskLower.includes('architecture') || task.length > 200
|
|
1154
|
+
? 'high'
|
|
1155
|
+
: taskLower.includes('simple') || taskLower.includes('fix') || task.length < 50
|
|
1156
|
+
? 'low'
|
|
1157
|
+
: 'medium';
|
|
1158
|
+
return {
|
|
1159
|
+
task,
|
|
1160
|
+
routing: {
|
|
1161
|
+
method: routingMethod,
|
|
1162
|
+
backend: backendInfo,
|
|
1163
|
+
latencyMs: routingLatencyMs,
|
|
1164
|
+
throughput: routingLatencyMs > 0 ? `${Math.round(1000 / routingLatencyMs)} routes/s` : 'N/A',
|
|
1165
|
+
},
|
|
1166
|
+
matchedPattern,
|
|
1167
|
+
semanticMatches: semanticResult.slice(0, 3).map(r => ({
|
|
1168
|
+
pattern: r.intent,
|
|
1169
|
+
score: Math.round(r.score * 100) / 100,
|
|
1170
|
+
})),
|
|
1171
|
+
primaryAgent: {
|
|
1172
|
+
type: agents[0],
|
|
1173
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
1174
|
+
reason: routingMethod.startsWith('semantic')
|
|
1175
|
+
? `Semantic similarity to "${matchedPattern}" pattern (${Math.round(confidence * 100)}%)`
|
|
1176
|
+
: `Task contains keywords matching ${agents[0]} specialization`,
|
|
1177
|
+
},
|
|
1178
|
+
alternativeAgents: agents.slice(1).map((agent, i) => ({
|
|
1179
|
+
type: agent,
|
|
1180
|
+
confidence: Math.round((confidence - (0.1 * (i + 1))) * 100) / 100,
|
|
1181
|
+
reason: `Alternative agent for ${agent} capabilities`,
|
|
1182
|
+
})),
|
|
1183
|
+
estimatedMetrics: {
|
|
1184
|
+
successProbability: Math.round(confidence * 100) / 100,
|
|
1185
|
+
estimatedDuration: complexity === 'high' ? '2-4 hours' : complexity === 'medium' ? '30-60 min' : '10-30 min',
|
|
1186
|
+
complexity,
|
|
1187
|
+
},
|
|
1188
|
+
swarmRecommendation: agents.length > 2 ? {
|
|
1189
|
+
topology: 'hierarchical',
|
|
1190
|
+
agents,
|
|
1191
|
+
coordination: 'queen-led',
|
|
1192
|
+
} : null,
|
|
1193
|
+
// ADR-390: which embedder the semantic index + query used ('hash' when degraded).
|
|
1194
|
+
embedder: handle.embedder,
|
|
1195
|
+
...(handle.embedderReason ? { embedderReason: handle.embedderReason } : {}),
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* INTERNAL / BENCH-ONLY (ADR-391). Not a public API; may change without notice.
|
|
1200
|
+
*
|
|
1201
|
+
* Routes `task` through the same local path `hooks_route` uses (semantic index +
|
|
1202
|
+
* keyword fallback) with an explicit embedder, without the MCP layer. It skips
|
|
1203
|
+
* the two steps that are neither ADR-391 candidate A nor B: the AgentDB
|
|
1204
|
+
* pre-route (`bridgeRouteTask`, which answers first when its confidence > 0.5)
|
|
1205
|
+
* and the opt-in typesafe wrapper (CLAUDE_FLOW_ROUTER_TYPESAFE). With those
|
|
1206
|
+
* inactive, `primaryAgent` equals `hooks_route`'s `primaryAgent.type`.
|
|
1207
|
+
*
|
|
1208
|
+
* Switching `embedder` between calls rebuilds the index; benchmark in blocks.
|
|
1209
|
+
*/
|
|
1210
|
+
export async function routeTaskForBench(task, opts) {
|
|
1211
|
+
const r = await routeTaskLocal(task, opts.context, true, opts.embedder);
|
|
1212
|
+
return {
|
|
1213
|
+
primaryAgent: r.primaryAgent.type,
|
|
1214
|
+
confidence: r.primaryAgent.confidence,
|
|
1215
|
+
pattern: r.matchedPattern,
|
|
1216
|
+
embedder: r.embedder,
|
|
1217
|
+
...(r.embedderReason ? { embedderReason: r.embedderReason } : {}),
|
|
1218
|
+
method: String(r.routing?.method ?? ''),
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1152
1221
|
export const hooksMetrics = {
|
|
1153
1222
|
name: 'hooks_metrics',
|
|
1154
1223
|
description: 'View learning metrics dashboard Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router embedder (ADR-390).
|
|
3
|
+
*
|
|
4
|
+
* `hooks_route` compares a task with each agent pattern's keywords in a 384-d
|
|
5
|
+
* vector index. Historically both sides came from a character hash
|
|
6
|
+
* (`generateSimpleEmbedding`), which measures spelling, not meaning. This
|
|
7
|
+
* module lets the router use the local sentence model (all-MiniLM-L6-v2, 384-d)
|
|
8
|
+
* instead, with the hash as the fallback.
|
|
9
|
+
*
|
|
10
|
+
* Rules (ADR-390 §Decision):
|
|
11
|
+
* - The MiniLM path uses `generateLocalEmbedding` ONLY. Never the bridge-first
|
|
12
|
+
* `generateEmbedding` — that recursed without bound in #2312.
|
|
13
|
+
* - One embedder per index: if ANY text cannot be embedded by the real model
|
|
14
|
+
* (throw, backend !== 'onnx', or a non-384 vector), EVERY text in the call
|
|
15
|
+
* is embedded with the hash, and the result says so.
|
|
16
|
+
* - The default stays `hash` until ADR-391's benchmark says otherwise.
|
|
17
|
+
*
|
|
18
|
+
* Selection: `CLAUDE_FLOW_ROUTER_EMBEDDER=minilm|hash`. The router index is
|
|
19
|
+
* process-lifetime state, so the env var is read when the index is (re)built,
|
|
20
|
+
* not per CLI invocation.
|
|
21
|
+
*/
|
|
22
|
+
export type RouterEmbedderKind = 'minilm' | 'hash';
|
|
23
|
+
/** Default embedder. ADR-391 decides whether this flips to 'minilm'. */
|
|
24
|
+
export declare const DEFAULT_ROUTER_EMBEDDER: RouterEmbedderKind;
|
|
25
|
+
/** Dimension of the router index (VectorDb + SemanticRouter are built at 384). */
|
|
26
|
+
export declare const ROUTER_EMBEDDING_DIM = 384;
|
|
27
|
+
export declare const ROUTER_EMBEDDER_ENV = "CLAUDE_FLOW_ROUTER_EMBEDDER";
|
|
28
|
+
export interface RouterEmbedderSelection {
|
|
29
|
+
kind: RouterEmbedderKind;
|
|
30
|
+
/** Set when the requested value was invalid and the default was used. */
|
|
31
|
+
reason?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RouterEmbeddingResult {
|
|
34
|
+
vectors: Float32Array[];
|
|
35
|
+
/** The embedder that actually produced `vectors` (after any degradation). */
|
|
36
|
+
embedder: RouterEmbedderKind;
|
|
37
|
+
/** Why the result is `hash` when `minilm` was requested. */
|
|
38
|
+
reason?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve which embedder the router should use.
|
|
42
|
+
* Precedence: explicit override > CLAUDE_FLOW_ROUTER_EMBEDDER > DEFAULT_ROUTER_EMBEDDER.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveRouterEmbedder(override?: RouterEmbedderKind, env?: NodeJS.ProcessEnv): RouterEmbedderSelection;
|
|
45
|
+
/**
|
|
46
|
+
* Deterministic character-hash embedding (the router's historical embedder).
|
|
47
|
+
* Moved verbatim from hooks-tools.ts; do not change the math — existing routes
|
|
48
|
+
* and thresholds were calibrated against it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateSimpleEmbedding(text: string, dimension?: number): Float32Array;
|
|
51
|
+
/**
|
|
52
|
+
* Embed texts for the router. All vectors in one result come from ONE embedder.
|
|
53
|
+
* `hash` never touches the model (no load cost for default users).
|
|
54
|
+
*/
|
|
55
|
+
export declare function embedForRouter(texts: readonly string[], kind?: RouterEmbedderKind): Promise<RouterEmbeddingResult>;
|
|
56
|
+
/** Test hook: clear the MiniLM vector memo. */
|
|
57
|
+
export declare function clearRouterEmbedderCache(): void;
|
|
58
|
+
//# sourceMappingURL=router-embedder.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router embedder (ADR-390).
|
|
3
|
+
*
|
|
4
|
+
* `hooks_route` compares a task with each agent pattern's keywords in a 384-d
|
|
5
|
+
* vector index. Historically both sides came from a character hash
|
|
6
|
+
* (`generateSimpleEmbedding`), which measures spelling, not meaning. This
|
|
7
|
+
* module lets the router use the local sentence model (all-MiniLM-L6-v2, 384-d)
|
|
8
|
+
* instead, with the hash as the fallback.
|
|
9
|
+
*
|
|
10
|
+
* Rules (ADR-390 §Decision):
|
|
11
|
+
* - The MiniLM path uses `generateLocalEmbedding` ONLY. Never the bridge-first
|
|
12
|
+
* `generateEmbedding` — that recursed without bound in #2312.
|
|
13
|
+
* - One embedder per index: if ANY text cannot be embedded by the real model
|
|
14
|
+
* (throw, backend !== 'onnx', or a non-384 vector), EVERY text in the call
|
|
15
|
+
* is embedded with the hash, and the result says so.
|
|
16
|
+
* - The default stays `hash` until ADR-391's benchmark says otherwise.
|
|
17
|
+
*
|
|
18
|
+
* Selection: `CLAUDE_FLOW_ROUTER_EMBEDDER=minilm|hash`. The router index is
|
|
19
|
+
* process-lifetime state, so the env var is read when the index is (re)built,
|
|
20
|
+
* not per CLI invocation.
|
|
21
|
+
*/
|
|
22
|
+
// memory-initializer is imported lazily (as hooks-tools does elsewhere): it is
|
|
23
|
+
// heavy, and the default `hash` path must not load it at all.
|
|
24
|
+
/** Default embedder. ADR-391 decides whether this flips to 'minilm'. */
|
|
25
|
+
export const DEFAULT_ROUTER_EMBEDDER = 'hash';
|
|
26
|
+
/** Dimension of the router index (VectorDb + SemanticRouter are built at 384). */
|
|
27
|
+
export const ROUTER_EMBEDDING_DIM = 384;
|
|
28
|
+
export const ROUTER_EMBEDDER_ENV = 'CLAUDE_FLOW_ROUTER_EMBEDDER';
|
|
29
|
+
/**
|
|
30
|
+
* Resolve which embedder the router should use.
|
|
31
|
+
* Precedence: explicit override > CLAUDE_FLOW_ROUTER_EMBEDDER > DEFAULT_ROUTER_EMBEDDER.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveRouterEmbedder(override, env = process.env) {
|
|
34
|
+
if (override)
|
|
35
|
+
return { kind: override };
|
|
36
|
+
// Env-only by design (registered in scripts/audit-env-var-precedence.mjs):
|
|
37
|
+
// the router index is process-lifetime MCP state, not owned by one CLI call.
|
|
38
|
+
const raw = env.CLAUDE_FLOW_ROUTER_EMBEDDER?.trim().toLowerCase();
|
|
39
|
+
if (!raw)
|
|
40
|
+
return { kind: DEFAULT_ROUTER_EMBEDDER };
|
|
41
|
+
if (raw === 'minilm' || raw === 'hash')
|
|
42
|
+
return { kind: raw };
|
|
43
|
+
return {
|
|
44
|
+
kind: DEFAULT_ROUTER_EMBEDDER,
|
|
45
|
+
reason: `${ROUTER_EMBEDDER_ENV}=${JSON.stringify(raw)} is not 'minilm' or 'hash'; using '${DEFAULT_ROUTER_EMBEDDER}'`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic character-hash embedding (the router's historical embedder).
|
|
50
|
+
* Moved verbatim from hooks-tools.ts; do not change the math — existing routes
|
|
51
|
+
* and thresholds were calibrated against it.
|
|
52
|
+
*/
|
|
53
|
+
export function generateSimpleEmbedding(text, dimension = ROUTER_EMBEDDING_DIM) {
|
|
54
|
+
const embedding = new Float32Array(dimension);
|
|
55
|
+
const normalized = text.toLowerCase().replace(/[^a-z0-9\s]/g, '');
|
|
56
|
+
const words = normalized.split(/\s+/).filter(w => w.length > 0);
|
|
57
|
+
for (let i = 0; i < dimension; i++) {
|
|
58
|
+
let value = 0;
|
|
59
|
+
// Word-level features
|
|
60
|
+
for (let w = 0; w < words.length; w++) {
|
|
61
|
+
const word = words[w];
|
|
62
|
+
for (let c = 0; c < word.length; c++) {
|
|
63
|
+
const charCode = word.charCodeAt(c);
|
|
64
|
+
value += Math.sin((charCode * (i + 1) + w * 17 + c * 23) * 0.0137);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Character-level features
|
|
68
|
+
for (let c = 0; c < text.length; c++) {
|
|
69
|
+
value += Math.cos((text.charCodeAt(c) * (i + 1) + c * 7) * 0.0073);
|
|
70
|
+
}
|
|
71
|
+
embedding[i] = value / Math.max(1, text.length);
|
|
72
|
+
}
|
|
73
|
+
return l2Normalize(embedding);
|
|
74
|
+
}
|
|
75
|
+
function l2Normalize(v) {
|
|
76
|
+
let norm = 0;
|
|
77
|
+
for (let i = 0; i < v.length; i++)
|
|
78
|
+
norm += v[i] * v[i];
|
|
79
|
+
norm = Math.sqrt(norm);
|
|
80
|
+
if (norm > 0)
|
|
81
|
+
for (let i = 0; i < v.length; i++)
|
|
82
|
+
v[i] /= norm;
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
// Per-(embedder, text) memo. Keyword vectors don't change when the pattern set
|
|
86
|
+
// changes, so this survives router rebuilds (e.g. after saveRoutingOutcomes).
|
|
87
|
+
const MINILM_CACHE = new Map();
|
|
88
|
+
const MINILM_CACHE_MAX = 2048;
|
|
89
|
+
async function embedOneMiniLM(text) {
|
|
90
|
+
const cached = MINILM_CACHE.get(text);
|
|
91
|
+
if (cached)
|
|
92
|
+
return cached;
|
|
93
|
+
const { generateLocalEmbedding } = await import('../memory/memory-initializer.js');
|
|
94
|
+
const out = await generateLocalEmbedding(text);
|
|
95
|
+
if (out.backend !== 'onnx') {
|
|
96
|
+
throw new RouterEmbedderDegraded(`local embedder backend is '${out.backend}' (model '${out.model}'), not onnx`);
|
|
97
|
+
}
|
|
98
|
+
if (!out.embedding || out.embedding.length !== ROUTER_EMBEDDING_DIM) {
|
|
99
|
+
throw new RouterEmbedderDegraded(`local embedder returned ${out.embedding?.length ?? 0}-d vectors; router index is ${ROUTER_EMBEDDING_DIM}-d`);
|
|
100
|
+
}
|
|
101
|
+
const vec = l2Normalize(Float32Array.from(out.embedding));
|
|
102
|
+
if (MINILM_CACHE.size >= MINILM_CACHE_MAX) {
|
|
103
|
+
const oldest = MINILM_CACHE.keys().next().value;
|
|
104
|
+
if (oldest !== undefined)
|
|
105
|
+
MINILM_CACHE.delete(oldest);
|
|
106
|
+
}
|
|
107
|
+
MINILM_CACHE.set(text, vec);
|
|
108
|
+
return vec;
|
|
109
|
+
}
|
|
110
|
+
class RouterEmbedderDegraded extends Error {
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Embed texts for the router. All vectors in one result come from ONE embedder.
|
|
114
|
+
* `hash` never touches the model (no load cost for default users).
|
|
115
|
+
*/
|
|
116
|
+
export async function embedForRouter(texts, kind = resolveRouterEmbedder().kind) {
|
|
117
|
+
if (kind === 'hash') {
|
|
118
|
+
return { vectors: texts.map(t => generateSimpleEmbedding(t)), embedder: 'hash' };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const vectors = [];
|
|
122
|
+
for (const t of texts)
|
|
123
|
+
vectors.push(await embedOneMiniLM(t));
|
|
124
|
+
return { vectors, embedder: 'minilm' };
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
const why = err instanceof RouterEmbedderDegraded
|
|
128
|
+
? err.message
|
|
129
|
+
: `local embedder threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
130
|
+
return {
|
|
131
|
+
vectors: texts.map(t => generateSimpleEmbedding(t)),
|
|
132
|
+
embedder: 'hash',
|
|
133
|
+
reason: `minilm unavailable (${why}); using hash for patterns and query`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Test hook: clear the MiniLM vector memo. */
|
|
138
|
+
export function clearRouterEmbedderCache() {
|
|
139
|
+
MINILM_CACHE.clear();
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=router-embedder.js.map
|
|
File without changes
|
|
File without changes
|
|
@@ -159,14 +159,14 @@ export declare const SpawnAgentSchema: z.ZodObject<{
|
|
|
159
159
|
timeout: z.ZodOptional<z.ZodNumber>;
|
|
160
160
|
}, "strip", z.ZodTypeAny, {
|
|
161
161
|
type: "coder" | "reviewer" | "tester" | "planner" | "researcher" | "security-architect" | "security-auditor" | "memory-specialist" | "swarm-specialist" | "integration-architect" | "performance-engineer" | "core-architect" | "test-architect" | "queen-coordinator" | "project-coordinator";
|
|
162
|
-
id?: string | undefined;
|
|
163
162
|
config?: Record<string, unknown> | undefined;
|
|
164
163
|
timeout?: number | undefined;
|
|
164
|
+
id?: string | undefined;
|
|
165
165
|
}, {
|
|
166
166
|
type: "coder" | "reviewer" | "tester" | "planner" | "researcher" | "security-architect" | "security-auditor" | "memory-specialist" | "swarm-specialist" | "integration-architect" | "performance-engineer" | "core-architect" | "test-architect" | "queen-coordinator" | "project-coordinator";
|
|
167
|
-
id?: string | undefined;
|
|
168
167
|
config?: Record<string, unknown> | undefined;
|
|
169
168
|
timeout?: number | undefined;
|
|
169
|
+
id?: string | undefined;
|
|
170
170
|
}>;
|
|
171
171
|
/**
|
|
172
172
|
* Task input schema
|
|
@@ -181,13 +181,13 @@ export declare const TaskInputSchema: z.ZodObject<{
|
|
|
181
181
|
taskId: string;
|
|
182
182
|
content: string;
|
|
183
183
|
agentType: "coder" | "reviewer" | "tester" | "planner" | "researcher" | "security-architect" | "security-auditor" | "memory-specialist" | "swarm-specialist" | "integration-architect" | "performance-engineer" | "core-architect" | "test-architect" | "queen-coordinator" | "project-coordinator";
|
|
184
|
-
priority?: "
|
|
184
|
+
priority?: "critical" | "high" | "medium" | "low" | undefined;
|
|
185
185
|
metadata?: Record<string, unknown> | undefined;
|
|
186
186
|
}, {
|
|
187
187
|
taskId: string;
|
|
188
188
|
content: string;
|
|
189
189
|
agentType: "coder" | "reviewer" | "tester" | "planner" | "researcher" | "security-architect" | "security-auditor" | "memory-specialist" | "swarm-specialist" | "integration-architect" | "performance-engineer" | "core-architect" | "test-architect" | "queen-coordinator" | "project-coordinator";
|
|
190
|
-
priority?: "
|
|
190
|
+
priority?: "critical" | "high" | "medium" | "low" | undefined;
|
|
191
191
|
metadata?: Record<string, unknown> | undefined;
|
|
192
192
|
}>;
|
|
193
193
|
/**
|
|
@@ -234,16 +234,16 @@ export declare const ExecutorConfigSchema: z.ZodObject<{
|
|
|
234
234
|
cwd: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
|
|
235
235
|
allowSudo: z.ZodDefault<z.ZodBoolean>;
|
|
236
236
|
}, "strip", z.ZodTypeAny, {
|
|
237
|
-
timeout: number;
|
|
238
237
|
allowedCommands: string[];
|
|
238
|
+
timeout: number;
|
|
239
239
|
maxBuffer: number;
|
|
240
240
|
allowSudo: boolean;
|
|
241
241
|
blockedPatterns?: string[] | undefined;
|
|
242
242
|
cwd?: string | undefined;
|
|
243
243
|
}, {
|
|
244
244
|
allowedCommands: string[];
|
|
245
|
-
timeout?: number | undefined;
|
|
246
245
|
blockedPatterns?: string[] | undefined;
|
|
246
|
+
timeout?: number | undefined;
|
|
247
247
|
maxBuffer?: number | undefined;
|
|
248
248
|
cwd?: string | undefined;
|
|
249
249
|
allowSudo?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.44.0",
|
|
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",
|