claude-flow 3.42.4 → 3.43.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/.proven-config-version +1 -0
- package/.claude/helpers/router.cjs +66 -23
- package/.claude/proven-config.json +42 -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 +3 -3
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +22 -1
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +9 -3
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +11 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +4 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +44 -11
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +42 -0
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +77 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +123 -41
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +53 -11
- package/v3/@claude-flow/cli/dist/src/ruvector/typesafe-router.d.ts +119 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/typesafe-router.js +245 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +40 -11
- package/v3/@claude-flow/cli/package.json +5 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Claude Flow Agent Router
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Static keyword router that suggests an agent for a task description.
|
|
6
|
+
* NOTE: This is *not* a learned model. It is a heuristic table; "confidence"
|
|
7
|
+
* is reported as a heuristic prior, not a calibrated probability.
|
|
8
|
+
*
|
|
9
|
+
* #2257 fix: patterns are now word-boundary-anchored so short tokens like
|
|
10
|
+
* `cd`, `ci`, `ui`, `add`, `structure` no longer match inside unrelated
|
|
11
|
+
* words (`decision`, `infrastructure`, `address`, `addendum`). Default
|
|
12
|
+
* confidence dropped from 0.8 to 0.6 to better reflect that this is a
|
|
13
|
+
* static heuristic, not a learned classifier.
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
16
|
const AGENT_CAPABILITIES = {
|
|
@@ -15,41 +24,73 @@ const AGENT_CAPABILITIES = {
|
|
|
15
24
|
devops: ['ci-cd', 'docker', 'deployment', 'infrastructure'],
|
|
16
25
|
};
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
}
|
|
27
|
+
// Each entry is an array of tokens. Tokens are alternation-friendly:
|
|
28
|
+
// - multi-word phrases ("unit test") match as phrases
|
|
29
|
+
// - single tokens are wrapped with \b … \b word boundaries at match time
|
|
30
|
+
const TASK_PATTERNS = [
|
|
31
|
+
// Code patterns
|
|
32
|
+
{ tokens: ['implement', 'create', 'build', 'add', 'write code', 'refactor', 'debug'], agent: 'coder' },
|
|
33
|
+
{ tokens: ['test', 'tests', 'testing', 'spec', 'specs', 'coverage', 'unit test', 'integration test'], agent: 'tester' },
|
|
34
|
+
{ tokens: ['review', 'audit', 'check', 'validate', 'security'], agent: 'reviewer' },
|
|
35
|
+
{ tokens: ['research', 'find', 'search', 'documentation', 'explore'], agent: 'researcher' },
|
|
36
|
+
{ tokens: ['design', 'architect', 'architecture', 'structure', 'plan'], agent: 'architect' },
|
|
37
|
+
|
|
38
|
+
// Domain patterns
|
|
39
|
+
{ tokens: ['api', 'endpoint', 'server', 'backend', 'database'], agent: 'backend-dev' },
|
|
40
|
+
{ tokens: ['ui', 'frontend', 'component', 'react', 'css', 'style'], agent: 'frontend-dev' },
|
|
41
|
+
// 'cd' / 'ci' are kept but require word boundaries via \b — they will not
|
|
42
|
+
// match inside "decision" or "specific". 'cd' as a literal command in a
|
|
43
|
+
// task description ("set up cd pipeline") still matches.
|
|
44
|
+
{ tokens: ['deploy', 'docker', 'ci', 'cd', 'ci/cd', 'pipeline', 'infrastructure', 'devops'], agent: 'devops' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function escapeRegex(s) {
|
|
48
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build an anchored alternation regex from a token list.
|
|
52
|
+
// - multi-word phrases (containing whitespace or '/') match literally
|
|
53
|
+
// - single tokens are wrapped with \b boundaries so 'cd' won't match "decide"
|
|
54
|
+
function buildPattern(tokens) {
|
|
55
|
+
const alternatives = tokens.map((tok) => {
|
|
56
|
+
const escaped = escapeRegex(tok.toLowerCase());
|
|
57
|
+
if (/\s|\//.test(tok)) {
|
|
58
|
+
// Phrase: whitespace/'/' on each side acts as a natural boundary
|
|
59
|
+
return escaped;
|
|
60
|
+
}
|
|
61
|
+
return `\\b${escaped}\\b`;
|
|
62
|
+
});
|
|
63
|
+
return new RegExp(`(?:${alternatives.join('|')})`, 'i');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const COMPILED_PATTERNS = TASK_PATTERNS.map((entry) => ({
|
|
67
|
+
agent: entry.agent,
|
|
68
|
+
tokens: entry.tokens,
|
|
69
|
+
regex: buildPattern(entry.tokens),
|
|
70
|
+
}));
|
|
28
71
|
|
|
29
72
|
function routeTask(task) {
|
|
30
|
-
const taskLower = task.toLowerCase();
|
|
73
|
+
const taskLower = String(task ?? '').toLowerCase();
|
|
31
74
|
|
|
32
|
-
for (const
|
|
33
|
-
|
|
34
|
-
if (regex.test(taskLower)) {
|
|
75
|
+
for (const entry of COMPILED_PATTERNS) {
|
|
76
|
+
if (entry.regex.test(taskLower)) {
|
|
35
77
|
return {
|
|
36
|
-
agent,
|
|
37
|
-
|
|
38
|
-
|
|
78
|
+
agent: entry.agent,
|
|
79
|
+
// Heuristic prior, not a learned probability — see file header.
|
|
80
|
+
confidence: 0.6,
|
|
81
|
+
reason: `Matched keyword(s) from: ${entry.tokens.join('|')}`,
|
|
39
82
|
};
|
|
40
83
|
}
|
|
41
84
|
}
|
|
42
85
|
|
|
43
86
|
return {
|
|
44
87
|
agent: 'coder',
|
|
45
|
-
confidence: 0.
|
|
46
|
-
reason: 'Default routing - no specific
|
|
88
|
+
confidence: 0.3,
|
|
89
|
+
reason: 'Default routing - no specific keyword matched',
|
|
47
90
|
};
|
|
48
91
|
}
|
|
49
92
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// CLI - only run when executed directly
|
|
93
|
+
// CLI
|
|
53
94
|
if (require.main === module) {
|
|
54
95
|
const task = process.argv.slice(2).join(' ');
|
|
55
96
|
if (task) {
|
|
@@ -60,3 +101,5 @@ if (require.main === module) {
|
|
|
60
101
|
console.log('\nAvailable agents:', Object.keys(AGENT_CAPABILITIES).join(', '));
|
|
61
102
|
}
|
|
62
103
|
}
|
|
104
|
+
|
|
105
|
+
module.exports = { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS, buildPattern };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adoptedAt": 1790175486477,
|
|
3
|
+
"championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
|
4
|
+
"manifest": {
|
|
5
|
+
"schema": "ruflo.proven-config/v1",
|
|
6
|
+
"policy": {
|
|
7
|
+
"ref": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
|
|
8
|
+
"value": {
|
|
9
|
+
"alpha": 0.3,
|
|
10
|
+
"subjectWeight": 1,
|
|
11
|
+
"mmrLambda": 0.5,
|
|
12
|
+
"bodyWeight": 1.5,
|
|
13
|
+
"typePenaltyFactor": 0.5
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"layer": "framework/node-cli",
|
|
17
|
+
"compatibility": {
|
|
18
|
+
"ruflo": ">=3.24.0"
|
|
19
|
+
},
|
|
20
|
+
"benchmark": {
|
|
21
|
+
"corpus": "ADR-081-labelled-v1",
|
|
22
|
+
"corpusHash": "sha256:2f700b5c363e20a3bd88ce2bc9b87bbbbaa61732c6177894c6ec37890f888982"
|
|
23
|
+
},
|
|
24
|
+
"receipt": {
|
|
25
|
+
"heldOutDelta": 0.07381404928570845,
|
|
26
|
+
"redblue": "PASS",
|
|
27
|
+
"drift": 0,
|
|
28
|
+
"canary": {
|
|
29
|
+
"rollbackRate": 0,
|
|
30
|
+
"latencyP95": 244.612458000076,
|
|
31
|
+
"costPerTask": 0
|
|
32
|
+
},
|
|
33
|
+
"receiptCoverage": 1
|
|
34
|
+
},
|
|
35
|
+
"platform": [
|
|
36
|
+
"linux",
|
|
37
|
+
"macOS",
|
|
38
|
+
"windows"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"previous": ""
|
|
42
|
+
}
|
|
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;
|
|
162
163
|
config?: Record<string, unknown> | undefined;
|
|
163
164
|
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;
|
|
167
168
|
config?: Record<string, unknown> | undefined;
|
|
168
169
|
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?: "low" | "medium" | "high" | "critical" | 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?: "low" | "medium" | "high" | "critical" | 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
|
-
allowedCommands: string[];
|
|
238
237
|
timeout: number;
|
|
238
|
+
allowedCommands: string[];
|
|
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
|
-
blockedPatterns?: string[] | undefined;
|
|
246
245
|
timeout?: number | undefined;
|
|
246
|
+
blockedPatterns?: string[] | 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",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.43.0",
|
|
4
4
|
"workspaces": [
|
|
5
5
|
"v3/@claude-flow/codex",
|
|
6
6
|
"v3/@claude-flow/mcp",
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
"@claude-flow/plugin-gastown-bridge": "^0.1.3",
|
|
99
99
|
"@ruvector/attention": "^0.1.3",
|
|
100
100
|
"@ruvector/core": "^0.1.30",
|
|
101
|
-
"@ruvector/router": "^0.1.
|
|
102
|
-
"@ruvector/router-linux-x64-gnu": "^0.1.
|
|
101
|
+
"@ruvector/router": "^0.1.31",
|
|
102
|
+
"@ruvector/router-linux-x64-gnu": "^0.1.31",
|
|
103
103
|
"@ruvector/sona": "^0.1.5",
|
|
104
104
|
"agentdb": "^3.0.0-alpha.17",
|
|
105
105
|
"agentic-flow": "^2.0.14",
|
|
@@ -2059,6 +2059,26 @@ async function checkMetaharness() {
|
|
|
2059
2059
|
};
|
|
2060
2060
|
}
|
|
2061
2061
|
}
|
|
2062
|
+
// Opt-in @ruvector/typesafe task router (optional peer). `--component typesafe` only.
|
|
2063
|
+
async function checkTypesafeRouter() {
|
|
2064
|
+
const name = '@ruvector/typesafe router';
|
|
2065
|
+
const { readTypesafeConfig } = await import('../ruvector/typesafe-router.js');
|
|
2066
|
+
const cfg = readTypesafeConfig();
|
|
2067
|
+
const gate = cfg.enabled ? `enabled (${cfg.embedder === 'hash' ? 'hash embedder, uncalibrated' : 'onnx embedder'})` : 'disabled (set CLAUDE_FLOW_ROUTER_TYPESAFE=1)';
|
|
2068
|
+
try {
|
|
2069
|
+
const { createRequire } = await import('module');
|
|
2070
|
+
const pj = createRequire(import.meta.url)('@ruvector/typesafe/package.json');
|
|
2071
|
+
return { name, status: 'pass', message: `v${pj.version ?? '?'} installed; ${gate}` };
|
|
2072
|
+
}
|
|
2073
|
+
catch {
|
|
2074
|
+
return {
|
|
2075
|
+
name,
|
|
2076
|
+
status: cfg.enabled ? 'warn' : 'pass',
|
|
2077
|
+
message: `Not installed; ${gate} — hooks_route uses the built-in router`,
|
|
2078
|
+
...(cfg.enabled ? { fix: 'npm install @ruvector/typesafe # optional peer' } : {}),
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2062
2082
|
async function checkClaudeCode() {
|
|
2063
2083
|
try {
|
|
2064
2084
|
const version = await runCommand('claude --version');
|
|
@@ -2252,7 +2272,7 @@ export const doctorCommand = {
|
|
|
2252
2272
|
{
|
|
2253
2273
|
name: 'component',
|
|
2254
2274
|
short: 'c',
|
|
2255
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, metaharness)',
|
|
2275
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, metaharness)',
|
|
2256
2276
|
type: 'string'
|
|
2257
2277
|
},
|
|
2258
2278
|
{
|
|
@@ -2435,6 +2455,7 @@ export const doctorCommand = {
|
|
|
2435
2455
|
// a user would actually debug them (is it installed? running? exposed?).
|
|
2436
2456
|
'proxy': [checkProxySponsoredConsent, checkProxyBinary, checkProxyProcess, checkProxyBindAddress],
|
|
2437
2457
|
'auth': checkAuth, // ADR-306
|
|
2458
|
+
'typesafe': checkTypesafeRouter, // opt-in @ruvector/typesafe task router
|
|
2438
2459
|
};
|
|
2439
2460
|
let checksToRun = allChecks;
|
|
2440
2461
|
if (component && componentMap[component]) {
|
|
@@ -1811,6 +1811,12 @@ const postTaskCommand = {
|
|
|
1811
1811
|
}
|
|
1812
1812
|
output.writeln();
|
|
1813
1813
|
output.printSuccess(`Task outcome recorded: ${success ? 'SUCCESS' : 'FAILED'}`);
|
|
1814
|
+
// #3353: only show observed learning results; say so when degraded.
|
|
1815
|
+
const lu = result.learningUpdates;
|
|
1816
|
+
if (lu.available === false) {
|
|
1817
|
+
output.writeln();
|
|
1818
|
+
output.printWarning(`Learning degraded: ${lu.reason ?? 'feedback not recorded'} — no pattern updates were observed`);
|
|
1819
|
+
}
|
|
1814
1820
|
output.writeln();
|
|
1815
1821
|
output.writeln(output.bold('Learning Updates'));
|
|
1816
1822
|
output.printTable({
|
|
@@ -1819,10 +1825,10 @@ const postTaskCommand = {
|
|
|
1819
1825
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
1820
1826
|
],
|
|
1821
1827
|
data: [
|
|
1822
|
-
{ metric: 'Patterns Updated', value:
|
|
1823
|
-
{ metric: 'New Patterns', value:
|
|
1828
|
+
{ metric: 'Patterns Updated', value: lu.patternsUpdated },
|
|
1829
|
+
{ metric: 'New Patterns', value: lu.newPatterns ?? 'unknown' },
|
|
1830
|
+
{ metric: 'Trajectory Recorded', value: result.trajectory ? (result.trajectory.recorded ? 'yes' : 'no') : 'unknown' },
|
|
1824
1831
|
{ metric: 'Duration', value: `${(result.duration / 1000).toFixed(1)}s` },
|
|
1825
|
-
{ metric: 'Trajectory ID', value: result.learningUpdates.trajectoryId }
|
|
1826
1832
|
]
|
|
1827
1833
|
});
|
|
1828
1834
|
return { success: true, data: result };
|
|
@@ -283,7 +283,7 @@ const AGENT_CAPABILITIES = {
|
|
|
283
283
|
// the whitespace acts as a natural boundary.
|
|
284
284
|
const TASK_PATTERNS = [
|
|
285
285
|
{ tokens: ['implement', 'create', 'build', 'add', 'write code', 'refactor', 'debug'], agent: 'coder' },
|
|
286
|
-
{ tokens: ['test', 'tests', 'spec', 'coverage', 'unit test', 'integration test'], agent: 'tester' },
|
|
286
|
+
{ tokens: ['test', 'tests', 'testing', 'spec', 'specs', 'coverage', 'unit test', 'integration test'], agent: 'tester' },
|
|
287
287
|
{ tokens: ['review', 'audit', 'check', 'validate', 'security'], agent: 'reviewer' },
|
|
288
288
|
{ tokens: ['research', 'find', 'search', 'documentation', 'explore'], agent: 'researcher' },
|
|
289
289
|
{ tokens: ['design', 'architect', 'architecture', 'structure', 'plan'], agent: 'architect' },
|
|
@@ -329,15 +329,16 @@ function routeTask(task) {
|
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
// CLI
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (task) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
} else {
|
|
339
|
-
|
|
340
|
-
|
|
332
|
+
// CLI — only when executed directly, not when require()d by hook-handler.cjs
|
|
333
|
+
if (require.main === module) {
|
|
334
|
+
const task = process.argv.slice(2).join(' ');
|
|
335
|
+
if (task) {
|
|
336
|
+
const result = routeTask(task);
|
|
337
|
+
console.log(JSON.stringify(result, null, 2));
|
|
338
|
+
} else {
|
|
339
|
+
console.log('Usage: router.js <task description>');
|
|
340
|
+
console.log('\\nAvailable agents:', Object.keys(AGENT_CAPABILITIES).join(', '));
|
|
341
|
+
}
|
|
341
342
|
}
|
|
342
343
|
|
|
343
344
|
module.exports = { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS, buildPattern };
|
|
@@ -125,7 +125,7 @@ export const CAPABILITY_DOMAINS = [
|
|
|
125
125
|
{
|
|
126
126
|
id: 'guidance',
|
|
127
127
|
name: 'Capability Guidance',
|
|
128
|
-
prefixes: ['guidance_'],
|
|
128
|
+
prefixes: ['guidance_', 'seraphina_'],
|
|
129
129
|
description: 'Live capability inventory, task routing, workflow guidance, and system discovery.',
|
|
130
130
|
taskSignals: ['guidance', 'discover', 'capability', 'what can ruflo do'],
|
|
131
131
|
commands: ['guidance compile', 'guidance retrieve', 'guidance gates', 'guidance optimize'],
|
|
@@ -430,7 +430,7 @@ export const CAPABILITY_DOMAINS = [
|
|
|
430
430
|
id: 'business-collaboration',
|
|
431
431
|
name: 'AgentBBS & Business Pods',
|
|
432
432
|
// Metadata classification only; execution remains behind the existing loadAgentbbs guard.
|
|
433
|
-
prefixes: ['agentbbs_', 'business_pod_', 'federation_bbs_'],
|
|
433
|
+
prefixes: ['agentbbs_', 'business_pod_', 'federation_bbs_', 'x_federation_'],
|
|
434
434
|
description: 'Federated business rooms, domain-affinity routing, and business-pod validation.',
|
|
435
435
|
taskSignals: ['business pod', 'bbs', 'room', 'domain affinity'],
|
|
436
436
|
commands: [],
|
|
@@ -551,6 +551,8 @@ const TOOL_OWNERSHIP = [
|
|
|
551
551
|
{ prefixes: ['testgen_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-testgen' },
|
|
552
552
|
{ prefixes: ['managed_agent_', 'wasm_agent_', 'wasm_gallery_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-agent' },
|
|
553
553
|
{ prefixes: ['guidance_'], packageOwner: '@claude-flow/guidance', pluginOwner: 'ruflo-core' },
|
|
554
|
+
{ prefixes: ['seraphina_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-core' },
|
|
555
|
+
{ prefixes: ['x_federation_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-x-gateway' },
|
|
554
556
|
];
|
|
555
557
|
function ownershipForTool(toolName, definition) {
|
|
556
558
|
const explicit = TOOL_OWNERSHIP.find((entry) => entry.prefixes.some((prefix) => toolName.startsWith(prefix)));
|
|
@@ -13,6 +13,11 @@ import { type MCPTool } from './types.js';
|
|
|
13
13
|
* the tag names untouched.
|
|
14
14
|
*/
|
|
15
15
|
export declare function scrubReasoningBlocks(text: string): string;
|
|
16
|
+
/** Exported for tests. */
|
|
17
|
+
export declare function suggestAgentsForTask(task: string): {
|
|
18
|
+
agents: string[];
|
|
19
|
+
confidence: number;
|
|
20
|
+
};
|
|
16
21
|
export declare const hooksPreEdit: MCPTool;
|
|
17
22
|
export declare const hooksPostEdit: MCPTool;
|
|
18
23
|
export declare const hooksPreCommand: MCPTool;
|
|
@@ -9,6 +9,7 @@ import { getProjectCwd } from './types.js';
|
|
|
9
9
|
import { validateIdentifier, validateText, validatePath } from './validate-input.js';
|
|
10
10
|
import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
|
|
11
11
|
import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
|
|
12
|
+
import { applyTypesafeRouting, getTypesafeRouter } from '../ruvector/typesafe-router.js';
|
|
12
13
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
13
14
|
let searchEntriesFn = null;
|
|
14
15
|
/**
|
|
@@ -269,6 +270,10 @@ const TASK_PATTERNS = {
|
|
|
269
270
|
agents: ['memory-specialist', 'architect', 'coder'],
|
|
270
271
|
},
|
|
271
272
|
};
|
|
273
|
+
/** Wrap hooks_route so the opt-in typesafe router (src/ruvector/typesafe-router.ts) can override the legacy pick. */
|
|
274
|
+
function withTypesafeRouting(legacy) {
|
|
275
|
+
return async (params) => applyTypesafeRouting(params, (await legacy(params)), TASK_PATTERNS, getTypesafeRouter());
|
|
276
|
+
}
|
|
272
277
|
/**
|
|
273
278
|
* Get the semantic router with environment detection.
|
|
274
279
|
* Tries native VectorDb first (HNSW, 16k routes/s), falls back to pure JS (47k routes/s cosine).
|
|
@@ -621,11 +626,21 @@ function suggestAgentsForFile(filePath) {
|
|
|
621
626
|
}
|
|
622
627
|
return AGENT_PATTERNS[ext] || ['coder', 'architect'];
|
|
623
628
|
}
|
|
624
|
-
|
|
625
|
-
|
|
629
|
+
// Whole-word matchers for KEYWORD_PATTERNS. A bare `includes()` matched
|
|
630
|
+
// substrings: 'test' hit "latest" (tester @ 0.95), 'auth' hit "author",
|
|
631
|
+
// 'fix' hit "prefix", 'api' hit "capitalize". Single words get \b anchors plus
|
|
632
|
+
// simple inflections (tests, testing, fixes, deployed); phrases containing
|
|
633
|
+
// whitespace or '/' (e.g. 'ci/cd') match literally between word boundaries.
|
|
634
|
+
const KEYWORD_MATCHERS = Object.entries(KEYWORD_PATTERNS).map(([keyword, result]) => {
|
|
635
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
636
|
+
const body = /[\s/]/.test(keyword) ? escaped : `${escaped}(?:s|es|ing|ed)?`;
|
|
637
|
+
return { regex: new RegExp(`\\b${body}\\b`, 'i'), result };
|
|
638
|
+
});
|
|
639
|
+
/** Exported for tests. */
|
|
640
|
+
export function suggestAgentsForTask(task) {
|
|
626
641
|
// Check static keyword patterns first
|
|
627
|
-
for (const
|
|
628
|
-
if (
|
|
642
|
+
for (const { regex, result } of KEYWORD_MATCHERS) {
|
|
643
|
+
if (regex.test(task)) {
|
|
629
644
|
return result;
|
|
630
645
|
}
|
|
631
646
|
}
|
|
@@ -950,7 +965,8 @@ export const hooksRoute = {
|
|
|
950
965
|
},
|
|
951
966
|
required: ['task'],
|
|
952
967
|
},
|
|
953
|
-
|
|
968
|
+
// Opt-in @ruvector/typesafe augmentation (CLAUDE_FLOW_ROUTER_TYPESAFE=1); returns the legacy result unchanged when unset.
|
|
969
|
+
handler: withTypesafeRouting(async (params) => {
|
|
954
970
|
const task = params.task;
|
|
955
971
|
const context = params.context;
|
|
956
972
|
const useSemanticRouter = params.useSemanticRouter !== false;
|
|
@@ -1131,7 +1147,7 @@ export const hooksRoute = {
|
|
|
1131
1147
|
coordination: 'queen-led',
|
|
1132
1148
|
} : null,
|
|
1133
1149
|
};
|
|
1134
|
-
},
|
|
1150
|
+
}),
|
|
1135
1151
|
};
|
|
1136
1152
|
export const hooksMetrics = {
|
|
1137
1153
|
name: 'hooks_metrics',
|
|
@@ -1439,10 +1455,12 @@ export const hooksPostTask = {
|
|
|
1439
1455
|
catch {
|
|
1440
1456
|
// Non-fatal
|
|
1441
1457
|
}
|
|
1442
|
-
// Record trajectory via intelligence module (SONA + ReasoningBank)
|
|
1458
|
+
// Record trajectory via intelligence module (SONA + ReasoningBank).
|
|
1459
|
+
// #3353: keep the observed result instead of discarding it.
|
|
1460
|
+
let trajectoryRecorded = false;
|
|
1443
1461
|
try {
|
|
1444
1462
|
const intelligence = await import('../memory/intelligence.js');
|
|
1445
|
-
await intelligence.recordTrajectory([{ type: 'result', content: params.task || taskId, metadata: { success, agent, quality }, timestamp: Date.now() }], success ? 'success' : 'failure');
|
|
1463
|
+
trajectoryRecorded = (await intelligence.recordTrajectory([{ type: 'result', content: params.task || taskId, metadata: { success, agent, quality }, timestamp: Date.now() }], success ? 'success' : 'failure')) === true;
|
|
1446
1464
|
}
|
|
1447
1465
|
catch {
|
|
1448
1466
|
// Intelligence module not available — non-fatal
|
|
@@ -1582,17 +1600,32 @@ export const hooksPostTask = {
|
|
|
1582
1600
|
writeFileSync(storePath, JSON.stringify(store, null, 2), 'utf-8');
|
|
1583
1601
|
}
|
|
1584
1602
|
catch { /* non-critical */ }
|
|
1603
|
+
// #3353: report only observed learning results. The previous
|
|
1604
|
+
// `feedbackResult?.updated || (success ? 2 : 1)` / `newPatterns: success ? 1 : 0`
|
|
1605
|
+
// invented counts whenever the feedback controller was unavailable (and the
|
|
1606
|
+
// `||` turned an observed 0 into 2). No path reports pattern *creation*, so
|
|
1607
|
+
// newPatterns is null (unknown) rather than a guess; the trajectory has no
|
|
1608
|
+
// real id to surface, so trajectoryId is null.
|
|
1609
|
+
const feedbackRecorded = feedbackResult?.success === true;
|
|
1610
|
+
const learningAvailable = feedbackRecorded;
|
|
1585
1611
|
return {
|
|
1586
1612
|
taskId,
|
|
1587
1613
|
success,
|
|
1588
1614
|
duration,
|
|
1589
1615
|
learningUpdates: {
|
|
1590
|
-
patternsUpdated: feedbackResult?.updated
|
|
1591
|
-
newPatterns:
|
|
1592
|
-
trajectoryId:
|
|
1616
|
+
patternsUpdated: feedbackRecorded ? (feedbackResult?.updated ?? 0) : 0,
|
|
1617
|
+
newPatterns: null,
|
|
1618
|
+
trajectoryId: null,
|
|
1593
1619
|
controller: feedbackResult?.controller || 'none',
|
|
1594
1620
|
outcomePersisted,
|
|
1621
|
+
available: learningAvailable,
|
|
1622
|
+
...(learningAvailable ? {} : {
|
|
1623
|
+
reason: feedbackResult
|
|
1624
|
+
? `feedback controller '${feedbackResult.controller}' did not record the outcome`
|
|
1625
|
+
: 'feedback controller unavailable',
|
|
1626
|
+
}),
|
|
1595
1627
|
},
|
|
1628
|
+
trajectory: { recorded: trajectoryRecorded },
|
|
1596
1629
|
quality,
|
|
1597
1630
|
pheromone,
|
|
1598
1631
|
feedback: feedbackResult ? {
|
|
@@ -61,6 +61,26 @@ function validateMemoryInput(key, value, query, namespace) {
|
|
|
61
61
|
throw new Error('Namespace contains disallowed characters');
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* #3374 — presence check for a schema-`required` string parameter.
|
|
66
|
+
*
|
|
67
|
+
* `validateMemoryInput` above is a bounds-and-charset validator: every branch
|
|
68
|
+
* is truthiness-guarded, so an omitted parameter passes it silently. Nothing
|
|
69
|
+
* else enforces `inputSchema.required` for these tools, so without this an
|
|
70
|
+
* omitted `query` travelled down to `generateHashEmbedding`'s
|
|
71
|
+
* `text.toLowerCase()` and came back as an unrelated TypeError.
|
|
72
|
+
*/
|
|
73
|
+
const MISSING_REQUIRED_PARAM = 'MISSING_REQUIRED_PARAM';
|
|
74
|
+
function missingRequiredString(input, param, tool) {
|
|
75
|
+
const v = input[param];
|
|
76
|
+
if (typeof v === 'string' && v.length > 0)
|
|
77
|
+
return null;
|
|
78
|
+
const got = v === undefined ? 'it was omitted' : v === '' ? 'it was an empty string' : `got ${v === null ? 'null' : typeof v}`;
|
|
79
|
+
return {
|
|
80
|
+
error: `${tool}: required parameter "${param}" must be a non-empty string (${got})`,
|
|
81
|
+
code: MISSING_REQUIRED_PARAM,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
64
84
|
// #1884 — sanitize a key produced from arbitrary input (markdown headings,
|
|
65
85
|
// frontmatter names, file names) so it survives validateMemoryInput on the
|
|
66
86
|
// read/delete path. Replaces every dangerous char with `_`. Truncates to
|
|
@@ -360,6 +380,10 @@ export const memoryTools = [
|
|
|
360
380
|
required: ['key', 'value'],
|
|
361
381
|
},
|
|
362
382
|
handler: async (input) => {
|
|
383
|
+
const missingKey = missingRequiredString(input, 'key', 'memory_store');
|
|
384
|
+
if (missingKey) {
|
|
385
|
+
return { success: false, key: input.key, stored: false, hasEmbedding: false, ...missingKey };
|
|
386
|
+
}
|
|
363
387
|
await ensureInitialized();
|
|
364
388
|
const { storeEntry } = await getMemoryFunctions();
|
|
365
389
|
const key = input.key;
|
|
@@ -408,6 +432,8 @@ export const memoryTools = [
|
|
|
408
432
|
backend: await describeBackend(),
|
|
409
433
|
storeTime: `${duration.toFixed(2)}ms`,
|
|
410
434
|
error: result.error,
|
|
435
|
+
// #3325: why hasEmbedding is false, when the bridge could not embed.
|
|
436
|
+
...(result.embeddingError ? { embeddingError: result.embeddingError } : {}),
|
|
411
437
|
};
|
|
412
438
|
}
|
|
413
439
|
catch (error) {
|
|
@@ -432,6 +458,10 @@ export const memoryTools = [
|
|
|
432
458
|
required: ['key'],
|
|
433
459
|
},
|
|
434
460
|
handler: async (input) => {
|
|
461
|
+
const missingKey = missingRequiredString(input, 'key', 'memory_retrieve');
|
|
462
|
+
if (missingKey) {
|
|
463
|
+
return { key: input.key, namespace: input.namespace, value: null, found: false, ...missingKey };
|
|
464
|
+
}
|
|
435
465
|
await ensureInitialized();
|
|
436
466
|
const { getEntry } = await getMemoryFunctions();
|
|
437
467
|
const key = input.key;
|
|
@@ -500,6 +530,10 @@ export const memoryTools = [
|
|
|
500
530
|
required: ['query'],
|
|
501
531
|
},
|
|
502
532
|
handler: async (input) => {
|
|
533
|
+
const missingQuery = missingRequiredString(input, 'query', 'memory_search');
|
|
534
|
+
if (missingQuery) {
|
|
535
|
+
return { query: input.query, results: [], total: 0, ...missingQuery };
|
|
536
|
+
}
|
|
503
537
|
await ensureInitialized();
|
|
504
538
|
const { searchEntries } = await getMemoryFunctions();
|
|
505
539
|
const query = input.query;
|
|
@@ -664,6 +698,10 @@ export const memoryTools = [
|
|
|
664
698
|
required: ['key'],
|
|
665
699
|
},
|
|
666
700
|
handler: async (input) => {
|
|
701
|
+
const missingKey = missingRequiredString(input, 'key', 'memory_delete');
|
|
702
|
+
if (missingKey) {
|
|
703
|
+
return { success: false, key: input.key, namespace: input.namespace, deleted: false, ...missingKey };
|
|
704
|
+
}
|
|
667
705
|
await ensureInitialized();
|
|
668
706
|
const { deleteEntry } = await getMemoryFunctions();
|
|
669
707
|
const key = input.key;
|
|
@@ -1145,6 +1183,10 @@ export const memoryTools = [
|
|
|
1145
1183
|
required: ['query'],
|
|
1146
1184
|
},
|
|
1147
1185
|
handler: async (input) => {
|
|
1186
|
+
const missingQuery = missingRequiredString(input, 'query', 'memory_search_unified');
|
|
1187
|
+
if (missingQuery) {
|
|
1188
|
+
return { success: false, query: input.query, results: [], total: 0, ...missingQuery };
|
|
1189
|
+
}
|
|
1148
1190
|
await ensureInitialized();
|
|
1149
1191
|
const { searchEntries, listEntries } = await getMemoryFunctions();
|
|
1150
1192
|
validateMemoryInput(undefined, undefined, input.query);
|
|
@@ -29,6 +29,16 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @module v3/cli/memory/graph-edge-writer
|
|
31
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* Checkpoint and close the cached handle if it is open (optionally only if it
|
|
34
|
+
* is open on `dbPath`). Returns true if a handle was released. Never throws.
|
|
35
|
+
*
|
|
36
|
+
* busy_timeout is dropped to 0 first so the TRUNCATE checkpoint cannot stall
|
|
37
|
+
* the event loop behind another connection: if another native connection is
|
|
38
|
+
* attached the sidecars stay anyway (it owns them), so a busy checkpoint loses
|
|
39
|
+
* nothing; if this is the only connection, the checkpoint is never busy.
|
|
40
|
+
*/
|
|
41
|
+
export declare function releaseBridgeDb(dbPath?: string): boolean;
|
|
32
42
|
/**
|
|
33
43
|
* Return the better-sqlite3 Database instance for graph_edges writes.
|
|
34
44
|
* Creates the graph_edges table if it is absent (idempotent).
|