claude-flow 3.56.8 → 3.56.10
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/hook-handler.cjs +109 -23
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/proven-config.json +42 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +1 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +3 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
- package/node_modules/@claude-flow/codex/dist/index.js +1 -1
- package/node_modules/@claude-flow/codex/package.json +1 -1
- 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/node_modules/@claude-flow/security/dist/policy/envelope.js +6 -6
- package/node_modules/@claude-flow/security/dist/policy/envelope.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +5 -5
- package/v3/@claude-flow/cli/dist/src/autopilot-state.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/autopilot-state.js +36 -8
- package/v3/@claude-flow/cli/dist/src/command-resolution.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/command-resolution.js +25 -0
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +17 -17
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +40 -29
- package/v3/@claude-flow/cli/dist/src/commands/claims.js +6 -8
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +86 -31
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +14 -2
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.js +61 -3
- package/v3/@claude-flow/cli/dist/src/commands/security.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +81 -35
- package/v3/@claude-flow/cli/dist/src/commands/tui.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/commands/tui.js +29 -3
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +24 -3
- package/v3/@claude-flow/cli/dist/src/funnel/local-signals.js +2 -1
- package/v3/@claude-flow/cli/dist/src/index.js +6 -42
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +109 -23
- package/v3/@claude-flow/cli/dist/src/init/types.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +45 -22
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/v3/@claude-flow/cli/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +70 -20
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +28 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/security-tools.js +55 -17
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +5 -1
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +105 -29
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +10 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +2 -1
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.d.ts +54 -0
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.js +37 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.js +59 -2
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/v3/@claude-flow/cli/dist/src/parser.js +32 -22
- package/v3/@claude-flow/cli/dist/src/production/monitoring.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/production/monitoring.js +19 -6
- package/v3/@claude-flow/cli/dist/src/production/rate-limiter.js +5 -4
- package/v3/@claude-flow/cli/dist/src/production/retry.js +3 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.js +27 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +202 -69
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +3 -3
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +7 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +37 -0
- package/v3/@claude-flow/cli/dist/src/transfer/store/discovery.js +16 -17
- package/v3/@claude-flow/cli/dist/src/transfer/store/types.d.ts +4 -0
- package/v3/@claude-flow/cli/package.json +7 -7
- package/v3/@claude-flow/shared/dist/resilience/rate-limiter.js +3 -3
- package/v3/@claude-flow/shared/dist/resilience/retry.js +3 -1
- package/v3/@claude-flow/shared/package.json +1 -1
|
@@ -80,6 +80,21 @@ async function bridgeUnavailableResult() {
|
|
|
80
80
|
hint: 'Check Node version + try `ruflo memory init` to initialize manually.',
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
// #4135 / #4161: graph reads (agentdb_graph-query, agentdb_graph-pathfinder) open the
|
|
84
|
+
// graph_edges database; under encryption at rest that file is an RFE1 blob they cannot
|
|
85
|
+
// open. A recorded edge is then unreadable even though the write reports success, so the
|
|
86
|
+
// write says so instead of letting a caller assume it can read the edge back.
|
|
87
|
+
async function unreadableEdgeWarning() {
|
|
88
|
+
try {
|
|
89
|
+
const { isBridgeDbEncryptedAtRest } = await getGraphEdgeWriter();
|
|
90
|
+
if (!isBridgeDbEncryptedAtRest())
|
|
91
|
+
return undefined;
|
|
92
|
+
return 'Edge recorded, but graph reads (agentdb_graph-query, agentdb_graph-pathfinder) cannot open the graph_edges database because it is encrypted at rest (RFE1), so this edge cannot be read back yet. Your data is intact (#2889, #4135).';
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
83
98
|
let memInitMod = null;
|
|
84
99
|
async function getMemInit() {
|
|
85
100
|
if (!memInitMod)
|
|
@@ -554,7 +569,9 @@ export const agentdbCausalEdge = {
|
|
|
554
569
|
// Also record in AgentDB bridge for compatibility
|
|
555
570
|
const bridge = await getBridge();
|
|
556
571
|
await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined }).catch(() => { });
|
|
557
|
-
|
|
572
|
+
const unreadable = await unreadableEdgeWarning();
|
|
573
|
+
const warning = [legacyWarning, unreadable].filter(Boolean).join(' ');
|
|
574
|
+
return { ...graphResult, _graphNodeBackend: true, ...(unreadable && { readable: false }), ...(warning && { warning }) };
|
|
558
575
|
}
|
|
559
576
|
}
|
|
560
577
|
}
|
|
@@ -567,7 +584,9 @@ export const agentdbCausalEdge = {
|
|
|
567
584
|
weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined,
|
|
568
585
|
});
|
|
569
586
|
const baseResult = result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
|
|
570
|
-
|
|
587
|
+
const unreadable = baseResult.success === false ? undefined : await unreadableEdgeWarning();
|
|
588
|
+
const warning = [legacyWarning, unreadable].filter(Boolean).join(' ');
|
|
589
|
+
return warning ? { ...baseResult, ...(unreadable && { readable: false }), warning } : baseResult;
|
|
571
590
|
}
|
|
572
591
|
catch (error) {
|
|
573
592
|
return { success: false, error: sanitizeError(error) };
|
|
@@ -1157,6 +1176,10 @@ export const agentdbGraphQuery = {
|
|
|
1157
1176
|
catch (error) {
|
|
1158
1177
|
return { success: false, error: `Retained SQL graph query failed: ${sanitizeError(error)}`, mode, nodeId };
|
|
1159
1178
|
}
|
|
1179
|
+
// #4135: name the real reason when the retained store is encrypted at rest.
|
|
1180
|
+
const encryptedAtRest = await getGraphEdgeWriter().then((w) => w.isBridgeDbEncryptedAtRest()).catch(() => false);
|
|
1181
|
+
if (encryptedAtRest)
|
|
1182
|
+
return { success: false, ...(await bridgeUnavailableResult()), mode, nodeId };
|
|
1160
1183
|
return { success: false, error: 'Retained SQL graph_edges backend is unavailable for k-hop query', mode, nodeId };
|
|
1161
1184
|
}
|
|
1162
1185
|
// ── semantic mode ────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known-miss registry for aidefence_learn (#4147).
|
|
3
|
+
*
|
|
4
|
+
* The detector's learned patterns are written by `learnFromDetection`, but
|
|
5
|
+
* they are only created from threats the detector already found, and
|
|
6
|
+
* `detect()` never reads them back. A threat the detector MISSED (a false
|
|
7
|
+
* negative, the case feedback matters most for) therefore had nowhere to go.
|
|
8
|
+
*
|
|
9
|
+
* This registry stores a SHA-256 of the whitespace/case-normalised input so a
|
|
10
|
+
* reported miss is flagged next time the identical text is scanned. It is an
|
|
11
|
+
* exact-match list, not generalisation: the raw text is never stored.
|
|
12
|
+
*/
|
|
13
|
+
export interface KnownMiss {
|
|
14
|
+
hash: string;
|
|
15
|
+
threatType: string;
|
|
16
|
+
reportedAt: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function hashInput(input: string): string;
|
|
19
|
+
/** Returns true when the miss was newly recorded, false when it was already known. */
|
|
20
|
+
export declare function recordKnownMiss(input: string, threatType: string, cwd?: string): boolean;
|
|
21
|
+
export declare function findKnownMiss(input: string, cwd?: string): KnownMiss | undefined;
|
|
22
|
+
//# sourceMappingURL=aidefence-known-misses.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known-miss registry for aidefence_learn (#4147).
|
|
3
|
+
*
|
|
4
|
+
* The detector's learned patterns are written by `learnFromDetection`, but
|
|
5
|
+
* they are only created from threats the detector already found, and
|
|
6
|
+
* `detect()` never reads them back. A threat the detector MISSED (a false
|
|
7
|
+
* negative, the case feedback matters most for) therefore had nowhere to go.
|
|
8
|
+
*
|
|
9
|
+
* This registry stores a SHA-256 of the whitespace/case-normalised input so a
|
|
10
|
+
* reported miss is flagged next time the identical text is scanned. It is an
|
|
11
|
+
* exact-match list, not generalisation: the raw text is never stored.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
const MAX_ENTRIES = 500;
|
|
17
|
+
function storePath(cwd) {
|
|
18
|
+
return join(cwd, '.claude-flow', 'aidefence-known-misses.json');
|
|
19
|
+
}
|
|
20
|
+
export function hashInput(input) {
|
|
21
|
+
const normalised = input.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
22
|
+
return createHash('sha256').update(normalised).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
function load(cwd) {
|
|
25
|
+
try {
|
|
26
|
+
const p = storePath(cwd);
|
|
27
|
+
if (!existsSync(p))
|
|
28
|
+
return [];
|
|
29
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
30
|
+
return Array.isArray(parsed) ? parsed.filter((e) => e && typeof e.hash === 'string') : [];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Returns true when the miss was newly recorded, false when it was already known. */
|
|
37
|
+
export function recordKnownMiss(input, threatType, cwd = process.cwd()) {
|
|
38
|
+
const hash = hashInput(input);
|
|
39
|
+
const entries = load(cwd);
|
|
40
|
+
if (entries.some((e) => e.hash === hash))
|
|
41
|
+
return false;
|
|
42
|
+
entries.push({ hash, threatType, reportedAt: new Date().toISOString() });
|
|
43
|
+
const dir = join(cwd, '.claude-flow');
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
const tmp = `${storePath(cwd)}.${process.pid}.tmp`;
|
|
46
|
+
writeFileSync(tmp, JSON.stringify(entries.slice(-MAX_ENTRIES), null, 2), { mode: 0o600 });
|
|
47
|
+
renameSync(tmp, storePath(cwd));
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
export function findKnownMiss(input, cwd = process.cwd()) {
|
|
51
|
+
const entries = load(cwd);
|
|
52
|
+
if (entries.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
const hash = hashInput(input);
|
|
55
|
+
return entries.find((e) => e.hash === hash);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=aidefence-known-misses.js.map
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Provides diff analysis and classification via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { validateGitRef, validatePath } from './validate-input.js';
|
|
6
|
-
import { analyzeDiff, assessFileRisk, assessOverallRisk,
|
|
6
|
+
import { analyzeDiff, assessFileRisk, assessOverallRisk, suggestReviewers, getGitDiffNumstat, } from '../ruvector/diff-classifier.js';
|
|
7
7
|
/**
|
|
8
8
|
* Diff Analysis Tool
|
|
9
9
|
* Analyzes git diffs for change risk assessment and classification
|
|
10
10
|
*/
|
|
11
11
|
export const analyzeDiffTool = {
|
|
12
12
|
name: 'analyze_diff',
|
|
13
|
-
description: '
|
|
13
|
+
description: 'Heuristically analyze git diff paths, changed lines, and one-commit subjects for risk and classification. Use when preparing a review that needs a quick evidence-based summary; inspect the diff for final judgment.',
|
|
14
14
|
category: 'analyze',
|
|
15
15
|
tags: ['diff', 'risk', 'classification', 'git'],
|
|
16
16
|
inputSchema: {
|
|
@@ -85,7 +85,7 @@ export const analyzeDiffTool = {
|
|
|
85
85
|
*/
|
|
86
86
|
export const diffRiskTool = {
|
|
87
87
|
name: 'analyze_diff-risk',
|
|
88
|
-
description: '
|
|
88
|
+
description: 'Heuristically assess git diff risk from changed file paths, sizes, and status. Use when triaging which files merit closer human review; this is not a security guarantee.',
|
|
89
89
|
category: 'analyze',
|
|
90
90
|
tags: ['diff', 'risk', 'git'],
|
|
91
91
|
inputSchema: {
|
|
@@ -130,7 +130,7 @@ export const diffRiskTool = {
|
|
|
130
130
|
*/
|
|
131
131
|
export const diffClassifyTool = {
|
|
132
132
|
name: 'analyze_diff-classify',
|
|
133
|
-
description: '
|
|
133
|
+
description: 'Heuristically classify a git diff from changed lines, file paths, and a single-commit subject when available. Use when you need a coarse change category; inspect the diff to confirm.',
|
|
134
134
|
category: 'analyze',
|
|
135
135
|
tags: ['diff', 'classification', 'git'],
|
|
136
136
|
inputSchema: {
|
|
@@ -151,12 +151,11 @@ export const diffClassifyTool = {
|
|
|
151
151
|
}
|
|
152
152
|
const ref = params.ref || 'HEAD';
|
|
153
153
|
try {
|
|
154
|
-
const
|
|
155
|
-
const classification = classifyDiff(files);
|
|
154
|
+
const result = await analyzeDiff({ ref });
|
|
156
155
|
return {
|
|
157
156
|
ref,
|
|
158
|
-
classification,
|
|
159
|
-
files: files.length,
|
|
157
|
+
classification: result.classification,
|
|
158
|
+
files: result.files.length,
|
|
160
159
|
};
|
|
161
160
|
}
|
|
162
161
|
catch (error) {
|
|
@@ -174,7 +173,7 @@ export const diffClassifyTool = {
|
|
|
174
173
|
*/
|
|
175
174
|
export const diffReviewersTool = {
|
|
176
175
|
name: 'analyze_diff-reviewers',
|
|
177
|
-
description: 'Suggest
|
|
176
|
+
description: 'Suggest reviewer roles from git diff paths and risk; does not identify people or read CODEOWNERS. Use when triaging expertise for a change, then consult CODEOWNERS to assign people.',
|
|
178
177
|
category: 'analyze',
|
|
179
178
|
tags: ['diff', 'reviewers', 'git'],
|
|
180
179
|
inputSchema: {
|
|
@@ -225,7 +224,7 @@ export const diffReviewersTool = {
|
|
|
225
224
|
*/
|
|
226
225
|
export const fileRiskTool = {
|
|
227
226
|
name: 'analyze_file-risk',
|
|
228
|
-
description: '
|
|
227
|
+
description: 'Heuristically assess risk for a file path and optional change metadata. Use when triaging a changed file for focused review; inspect the source for final judgment.',
|
|
229
228
|
category: 'analyze',
|
|
230
229
|
tags: ['file', 'risk'],
|
|
231
230
|
inputSchema: {
|
|
@@ -280,7 +279,7 @@ export const fileRiskTool = {
|
|
|
280
279
|
*/
|
|
281
280
|
export const diffStatsTool = {
|
|
282
281
|
name: 'analyze_diff-stats',
|
|
283
|
-
description: '
|
|
282
|
+
description: 'Count changed files, additions, and deletions in a git diff. Use when you need a quick size summary before inspecting the full patch.',
|
|
284
283
|
category: 'analyze',
|
|
285
284
|
tags: ['diff', 'stats', 'git'],
|
|
286
285
|
inputSchema: {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module @claude-flow/cli/mcp-tools/autopilot
|
|
9
9
|
*/
|
|
10
10
|
import { validateText } from './validate-input.js';
|
|
11
|
-
import { loadState, saveState, appendLog, loadLog, discoverTasks, isTerminal, tryLoadLearning, validateNumber, validateConfiguredTaskSources, VALID_TASK_SOURCES, } from '../autopilot-state.js';
|
|
11
|
+
import { loadState, saveState, appendLog, loadLog, discoverTasks, isTerminal, tryLoadLearning, getLearningLoadError, validateNumber, validateConfiguredTaskSources, VALID_TASK_SOURCES, } from '../autopilot-state.js';
|
|
12
12
|
function ok(data) {
|
|
13
13
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
14
14
|
}
|
|
@@ -175,7 +175,7 @@ const autopilotLearn = {
|
|
|
175
175
|
]);
|
|
176
176
|
return ok({ metrics, patterns });
|
|
177
177
|
}
|
|
178
|
-
return ok({ available: false, reason: 'AgentDB/AutopilotLearning not initialized', patterns: [] });
|
|
178
|
+
return ok({ available: false, reason: getLearningLoadError() ?? 'AgentDB/AutopilotLearning not initialized', patterns: [] });
|
|
179
179
|
},
|
|
180
180
|
};
|
|
181
181
|
const autopilotHistory = {
|
|
@@ -85,9 +85,13 @@
|
|
|
85
85
|
* @module @claude-flow/cli/mcp-tools/browser-intent
|
|
86
86
|
*/
|
|
87
87
|
import { createRequire } from 'node:module';
|
|
88
|
-
import { createServer } from 'node:http';
|
|
89
88
|
import { readFileSync, existsSync } from 'node:fs';
|
|
90
89
|
import { dirname, join } from 'node:path';
|
|
90
|
+
// Lazy, CommonJS-style load of node:http: an ESM `import … from 'node:http'`
|
|
91
|
+
// materialises every export of the builtin, which trips http's lazy undici
|
|
92
|
+
// getters (~2.5 MB heap / ~6 MB RSS) at MCP startup even when no server is
|
|
93
|
+
// ever started. require() of the builtin does not touch those getters.
|
|
94
|
+
const loadHttp = () => createRequire(import.meta.url)('node:http');
|
|
91
95
|
import { validateText, validateIdentifier } from './validate-input.js';
|
|
92
96
|
import { execBrowserCommand } from './browser-tools.js';
|
|
93
97
|
import { storeEntry } from '../memory/memory-initializer.js';
|
|
@@ -175,7 +179,7 @@ export function resolvePageAgentLLMConfig(env = process.env) {
|
|
|
175
179
|
export const PLACEHOLDER_API_KEY = 'ruflo-proxied-key';
|
|
176
180
|
export function startLocalLLMProxy(real) {
|
|
177
181
|
return new Promise((resolve, reject) => {
|
|
178
|
-
const server = createServer((req, res) => {
|
|
182
|
+
const server = loadHttp().createServer((req, res) => {
|
|
179
183
|
const target = `${real.baseURL.replace(/\/+$/, '')}${req.url ?? ''}`;
|
|
180
184
|
const chunks = [];
|
|
181
185
|
req.on('data', (c) => chunks.push(c));
|
|
@@ -93,16 +93,28 @@ function toPoincare(euclidean, curvature) {
|
|
|
93
93
|
const factor = Math.tanh(sqrtC * norm / 2) / (sqrtC * norm + 1e-15);
|
|
94
94
|
return euclidean.map(x => x * factor);
|
|
95
95
|
}
|
|
96
|
-
//
|
|
96
|
+
// #4159: a point is usable only strictly inside the ball of radius 1/sqrt(c).
|
|
97
|
+
function insidePoincareBall(p, curvature) {
|
|
98
|
+
const c = Math.abs(curvature);
|
|
99
|
+
return c * p.reduce((sum, x) => sum + x * x, 0) < 1;
|
|
100
|
+
}
|
|
101
|
+
// Poincaré distance, d_c(a,b) = acosh(1 + 2c|a-b|^2 / ((1-c|a|^2)(1-c|b|^2))) / sqrt(c).
|
|
102
|
+
// Returns NaN for a point on or outside the ball (the formula is undefined there);
|
|
103
|
+
// callers must check Number.isFinite before reporting it.
|
|
97
104
|
function poincareDistance(a, b, curvature) {
|
|
98
105
|
const c = Math.abs(curvature);
|
|
106
|
+
if (a.length !== b.length || !insidePoincareBall(a, curvature) || !insidePoincareBall(b, curvature)) {
|
|
107
|
+
return NaN;
|
|
108
|
+
}
|
|
99
109
|
const diffSq = a.reduce((sum, _, i) => sum + (a[i] - b[i]) ** 2, 0);
|
|
100
110
|
const normASq = a.reduce((sum, x) => sum + x * x, 0);
|
|
101
111
|
const normBSq = b.reduce((sum, x) => sum + x * x, 0);
|
|
102
|
-
const denom = (1 - normASq) * (1 - normBSq);
|
|
103
|
-
const delta = 2 * diffSq /
|
|
112
|
+
const denom = (1 - c * normASq) * (1 - c * normBSq);
|
|
113
|
+
const delta = (2 * c * diffSq) / denom;
|
|
104
114
|
return (1 / Math.sqrt(c)) * Math.acosh(1 + delta);
|
|
105
115
|
}
|
|
116
|
+
const HYPERBOLIC_RADIUS_NOTE = 'convert is an exponential map at the origin: it preserves the input norm ordering, so unit-norm text embeddings all land at the same radius. ' +
|
|
117
|
+
'It does not encode hierarchy; depth/tree structure has to come from the hierarchy itself (for example depth chosen as the radius, or embeddings trained on the tree edges).';
|
|
106
118
|
// Cosine similarity
|
|
107
119
|
function cosineSimilarity(a, b) {
|
|
108
120
|
const dot = a.reduce((sum, _, i) => sum + a[i] * b[i], 0);
|
|
@@ -339,6 +351,9 @@ export const embeddingsTools = [
|
|
|
339
351
|
const poinc1 = toPoincare(emb1, config.hyperbolic.curvature);
|
|
340
352
|
const poinc2 = toPoincare(emb2, config.hyperbolic.curvature);
|
|
341
353
|
distance = poincareDistance(poinc1, poinc2, config.hyperbolic.curvature);
|
|
354
|
+
if (!Number.isFinite(distance)) {
|
|
355
|
+
return { success: false, error: 'Hyperbolic distance is undefined for these inputs (a converted point reached the ball boundary).' };
|
|
356
|
+
}
|
|
342
357
|
similarity = 1 / (1 + distance);
|
|
343
358
|
break;
|
|
344
359
|
case 'euclidean':
|
|
@@ -739,6 +754,7 @@ export const embeddingsTools = [
|
|
|
739
754
|
poincare,
|
|
740
755
|
curvature,
|
|
741
756
|
poincareNorm: Math.sqrt(poincare.reduce((sum, x) => sum + x * x, 0)),
|
|
757
|
+
note: HYPERBOLIC_RADIUS_NOTE,
|
|
742
758
|
};
|
|
743
759
|
case 'distance':
|
|
744
760
|
const emb1 = input.embedding1;
|
|
@@ -747,6 +763,13 @@ export const embeddingsTools = [
|
|
|
747
763
|
return { success: false, error: 'embedding1 and embedding2 required for distance action' };
|
|
748
764
|
}
|
|
749
765
|
const dist = poincareDistance(emb1, emb2, curvature);
|
|
766
|
+
if (!Number.isFinite(dist)) {
|
|
767
|
+
return {
|
|
768
|
+
success: false,
|
|
769
|
+
error: 'Poincaré distance needs two points of equal length strictly inside the ball (|x| < 1/sqrt(c)); got a point on or outside it, or mismatched lengths. Run convert on Euclidean vectors first.',
|
|
770
|
+
curvature,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
750
773
|
return {
|
|
751
774
|
success: true,
|
|
752
775
|
action: 'distance',
|
|
@@ -780,11 +803,10 @@ export const embeddingsTools = [
|
|
|
780
803
|
epsilon: config.hyperbolic.epsilon,
|
|
781
804
|
maxNorm: config.hyperbolic.maxNorm,
|
|
782
805
|
},
|
|
806
|
+
note: HYPERBOLIC_RADIUS_NOTE,
|
|
783
807
|
benefits: [
|
|
784
|
-
'
|
|
785
|
-
'
|
|
786
|
-
'Preserves tree-like structures',
|
|
787
|
-
'Natural for taxonomy embeddings',
|
|
808
|
+
'Exponential capacity in low dimensions when the points are placed by hierarchy (for example by depth)',
|
|
809
|
+
'Natural geometry for taxonomy embeddings that are trained or constructed for it',
|
|
788
810
|
],
|
|
789
811
|
};
|
|
790
812
|
}
|
|
@@ -3253,6 +3253,8 @@ export const hooksTrajectoryEnd = {
|
|
|
3253
3253
|
learned: result.learned,
|
|
3254
3254
|
patternKey: result.patternKey,
|
|
3255
3255
|
confidence: result.confidence,
|
|
3256
|
+
reinforcedNeighbour: result.reinforcedNeighbour,
|
|
3257
|
+
routingGate: result.routingGate,
|
|
3256
3258
|
};
|
|
3257
3259
|
}
|
|
3258
3260
|
catch {
|
|
@@ -3416,6 +3418,8 @@ export const hooksTrajectoryEnd = {
|
|
|
3416
3418
|
sonaUpdate: sonaResult.learned,
|
|
3417
3419
|
sonaPatternKey: sonaResult.patternKey || undefined,
|
|
3418
3420
|
sonaConfidence: sonaResult.confidence || undefined,
|
|
3421
|
+
sonaReinforcedNeighbour: sonaResult.reinforcedNeighbour || undefined,
|
|
3422
|
+
sonaRoutingGate: sonaResult.routingGate,
|
|
3419
3423
|
ewcConsolidation: ewcResult.consolidated,
|
|
3420
3424
|
ewcPenalty: ewcResult.penalty || undefined,
|
|
3421
3425
|
patternsExtracted,
|
|
@@ -14,6 +14,7 @@ import { homedir } from 'os';
|
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import { createHash } from 'crypto';
|
|
16
16
|
import { validateIdentifier } from './validate-input.js';
|
|
17
|
+
import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.js';
|
|
17
18
|
// #1604: Align with memory-initializer.ts — single source of truth is .swarm/memory.db
|
|
18
19
|
const MEMORY_DIR = '.swarm';
|
|
19
20
|
const LEGACY_MEMORY_FILE = 'store.json';
|
|
@@ -231,6 +232,19 @@ async function getMemoryFunctions() {
|
|
|
231
232
|
checkMemoryInitialization,
|
|
232
233
|
};
|
|
233
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Path of the CLI's own store (.swarm/memory.db or CLAUDE_FLOW_DB_PATH), or
|
|
237
|
+
* undefined when the memory module cannot say (#4148). Never throws.
|
|
238
|
+
*/
|
|
239
|
+
async function resolveCliStorePath() {
|
|
240
|
+
try {
|
|
241
|
+
const mod = await import('../memory/memory-initializer.js');
|
|
242
|
+
return typeof mod.resolveDbPath === 'function' ? mod.resolveDbPath() : undefined;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
234
248
|
/**
|
|
235
249
|
* #2922: every `backend:` field in this file's tool responses used to be the
|
|
236
250
|
* hardcoded literal `'sql.js + HNSW'` regardless of which search path was
|
|
@@ -538,7 +552,7 @@ export const memoryTools = [
|
|
|
538
552
|
query: { type: 'string', description: 'Search query (semantic similarity)' },
|
|
539
553
|
namespace: { type: 'string', description: 'Namespace to search (default: all namespaces — omit to search across every namespace)' },
|
|
540
554
|
limit: { type: 'number', description: 'Maximum results (default: 10)' },
|
|
541
|
-
threshold: { type: 'number', description: 'Minimum raw retrieval relevance 0-1 for candidate admission, applied per query before SmartRetrieval ranking; not a floor on rankingScore (default: 0.
|
|
555
|
+
threshold: { type: 'number', description: 'Minimum raw retrieval relevance 0-1 for candidate admission, applied per query before SmartRetrieval ranking; not a floor on rankingScore (default: 0.2; calibrated for the bundled embedding model, pass 0 to see every match)' },
|
|
542
556
|
smart: { type: 'boolean', description: 'Enable SmartRetrieval — query expansion, RRF fusion, recency boost, MMR diversity; preserves raw similarity and adds rankingScore (default: false)' },
|
|
543
557
|
provenance_filter: {
|
|
544
558
|
type: 'array',
|
|
@@ -567,7 +581,8 @@ export const memoryTools = [
|
|
|
567
581
|
// in the search layer actually fires.
|
|
568
582
|
const namespace = input.namespace;
|
|
569
583
|
const limit = input.limit ?? 10;
|
|
570
|
-
const threshold = input.threshold ??
|
|
584
|
+
const threshold = input.threshold ?? DEFAULT_SEARCH_THRESHOLD;
|
|
585
|
+
const thresholdWasDefault = input.threshold === undefined || input.threshold === null;
|
|
571
586
|
validateMemoryInput(undefined, undefined, query, namespace);
|
|
572
587
|
const startTime = performance.now();
|
|
573
588
|
try {
|
|
@@ -638,10 +653,14 @@ export const memoryTools = [
|
|
|
638
653
|
provenanceType: r.provenanceType,
|
|
639
654
|
};
|
|
640
655
|
});
|
|
656
|
+
const smartNearMisses = results.length === 0 && thresholdWasDefault
|
|
657
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
658
|
+
: undefined;
|
|
641
659
|
return {
|
|
642
660
|
query,
|
|
643
661
|
results,
|
|
644
662
|
total: results.length,
|
|
663
|
+
...(smartNearMisses ?? {}),
|
|
645
664
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
646
665
|
backend: 'SmartRetrieval (RRF + MMR + Recency)',
|
|
647
666
|
stats: smartResult.stats,
|
|
@@ -685,10 +704,14 @@ export const memoryTools = [
|
|
|
685
704
|
provenanceType: r.provenanceType,
|
|
686
705
|
};
|
|
687
706
|
});
|
|
707
|
+
const nearMissInfo = results.length === 0 && thresholdWasDefault
|
|
708
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
709
|
+
: undefined;
|
|
688
710
|
return {
|
|
689
711
|
query,
|
|
690
712
|
results,
|
|
691
713
|
total: results.length,
|
|
714
|
+
...(nearMissInfo ?? {}),
|
|
692
715
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
693
716
|
backend: 'HNSW + sql.js',
|
|
694
717
|
...(smartFallbackReason ? { smartFallback: smartFallbackReason } : {}),
|
|
@@ -1497,25 +1520,50 @@ export const memoryTools = [
|
|
|
1497
1520
|
if (!v.valid)
|
|
1498
1521
|
throw new Error(v.error);
|
|
1499
1522
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1523
|
+
const readStore = async (dbPath) => {
|
|
1524
|
+
const out = [];
|
|
1525
|
+
let total;
|
|
1526
|
+
do {
|
|
1527
|
+
const page = await listEntries({ limit: 10000, offset: out.length, namespace, includeContent: true, ...(dbPath ? { dbPath } : {}) });
|
|
1528
|
+
if (!page.success)
|
|
1529
|
+
return { error: page.error || 'Could not read memory for export' };
|
|
1530
|
+
total ??= page.total;
|
|
1531
|
+
if (page.entries.length === 0 && out.length < total) {
|
|
1532
|
+
return { error: `Incomplete memory export: read ${out.length} of ${total} entries` };
|
|
1533
|
+
}
|
|
1534
|
+
out.push(...page.entries);
|
|
1535
|
+
} while (out.length < total);
|
|
1536
|
+
return { entries: out };
|
|
1537
|
+
};
|
|
1538
|
+
const primary = await readStore();
|
|
1539
|
+
if ('error' in primary)
|
|
1540
|
+
return { error: primary.error };
|
|
1541
|
+
// #4148: the default reader is the AgentDB bridge, but the CLI's own store
|
|
1542
|
+
// (.swarm/memory.db, possibly encrypted) can hold rows the bridge never
|
|
1543
|
+
// sees, so an export silently covered one store and reported success. Read
|
|
1544
|
+
// the CLI store too and merge, newest copy of a (namespace, key) winning.
|
|
1545
|
+
const warnings = [];
|
|
1546
|
+
const stores = [{ store: 'default', entries: primary.entries.length }];
|
|
1547
|
+
const merged = new Map();
|
|
1548
|
+
const keep = (e) => {
|
|
1549
|
+
const id = `${e.namespace}\u0000${e.key}`;
|
|
1550
|
+
const have = merged.get(id);
|
|
1551
|
+
if (!have || String(e.updatedAt) > String(have.updatedAt))
|
|
1552
|
+
merged.set(id, e);
|
|
1553
|
+
};
|
|
1554
|
+
primary.entries.forEach(keep);
|
|
1555
|
+
const cliStore = await resolveCliStorePath();
|
|
1556
|
+
if (cliStore && existsSync(cliStore)) {
|
|
1557
|
+
const secondary = await readStore(cliStore);
|
|
1558
|
+
if ('error' in secondary) {
|
|
1559
|
+
warnings.push(`Could not read the CLI store ${cliStore} (${secondary.error}); the export covers the AgentDB store only.`);
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
stores.push({ store: cliStore, entries: secondary.entries.length });
|
|
1563
|
+
secondary.entries.forEach(keep);
|
|
1515
1564
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
const all = { entries };
|
|
1565
|
+
}
|
|
1566
|
+
const all = { entries: [...merged.values()] };
|
|
1519
1567
|
const payload = {
|
|
1520
1568
|
schema: 'ruflo-memory-export/v1',
|
|
1521
1569
|
exportedAt: new Date().toISOString(),
|
|
@@ -1542,6 +1590,8 @@ export const memoryTools = [
|
|
|
1542
1590
|
outputPath,
|
|
1543
1591
|
format: input.format || 'json',
|
|
1544
1592
|
exported: { entries: all.entries.length, vectors: vectorsWithEmb, patterns: 0 },
|
|
1593
|
+
stores,
|
|
1594
|
+
...(warnings.length ? { warnings } : {}),
|
|
1545
1595
|
fileSize: `${Buffer.byteLength(JSON.stringify(payload))}B`,
|
|
1546
1596
|
note: input.format === 'csv' ? 'CSV not implemented yet — wrote JSON' : undefined,
|
|
1547
1597
|
};
|
|
@@ -452,8 +452,24 @@ export const neuralTools = [
|
|
|
452
452
|
patternsStored++;
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
|
+
// #4156: nothing stored means nothing was trained. Reporting success and "ready" made
|
|
456
|
+
// callers (the neural-train skill) believe the pattern store had been filled.
|
|
457
|
+
if (patternsStored === 0) {
|
|
458
|
+
model.status = 'untrained';
|
|
459
|
+
model.accuracy = 0;
|
|
460
|
+
saveNeuralStore(store);
|
|
461
|
+
return {
|
|
462
|
+
success: false,
|
|
463
|
+
status: 'empty',
|
|
464
|
+
modelId,
|
|
465
|
+
type: modelType,
|
|
466
|
+
patternsStored: 0,
|
|
467
|
+
totalPatterns: Object.keys(store.patterns).length,
|
|
468
|
+
error: 'neural_train stored nothing: pass `data` (an array of strings or {text, label} entries). It does not read trajectories, SONA or the ReasoningBank: those (hooks_intelligence_*) are a separate store that neural_predict and neural_patterns do not see.',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
455
471
|
model.status = 'ready';
|
|
456
|
-
model.accuracy =
|
|
472
|
+
model.accuracy = 1.0; // accuracy = data stored, not simulated
|
|
457
473
|
model.trainedAt = new Date().toISOString();
|
|
458
474
|
saveNeuralStore(store);
|
|
459
475
|
return {
|
|
@@ -484,6 +500,7 @@ export const neuralTools = [
|
|
|
484
500
|
modelId: { type: 'string', description: 'Model ID to use' },
|
|
485
501
|
input: { type: 'string', description: 'Input text or data' },
|
|
486
502
|
topK: { type: 'number', description: 'Number of top predictions' },
|
|
503
|
+
minSimilarity: { type: 'number', description: 'Minimum cosine similarity for a stored pattern to count as a match (default 0.5). A softmax over a single candidate is always 1.0, so without a floor any input matched the only stored pattern with confidence 1' },
|
|
487
504
|
},
|
|
488
505
|
required: ['input'],
|
|
489
506
|
},
|
|
@@ -502,6 +519,7 @@ export const neuralTools = [
|
|
|
502
519
|
const modelId = input.modelId;
|
|
503
520
|
const inputText = input.input;
|
|
504
521
|
const topK = input.topK || 3;
|
|
522
|
+
const minSimilarity = typeof input.minSimilarity === 'number' ? input.minSimilarity : 0.5;
|
|
505
523
|
// Find model or use default
|
|
506
524
|
const model = modelId ? store.models[modelId] : Object.values(store.models).find(m => m.status === 'ready');
|
|
507
525
|
if (model && model.status !== 'ready') {
|
|
@@ -519,9 +537,10 @@ export const neuralTools = [
|
|
|
519
537
|
// to 1, and we surface enough metadata to trust the result.
|
|
520
538
|
const storedPatterns = Object.values(store.patterns);
|
|
521
539
|
let predictions;
|
|
540
|
+
let bestSimilarity = null;
|
|
522
541
|
if (storedPatterns.length > 0) {
|
|
523
542
|
// Step 1: k-NN with cosine
|
|
524
|
-
|
|
543
|
+
let scored = storedPatterns
|
|
525
544
|
.map(p => {
|
|
526
545
|
const sim = cosineSimilarity(embedding, p.embedding);
|
|
527
546
|
return {
|
|
@@ -530,8 +549,10 @@ export const neuralTools = [
|
|
|
530
549
|
cosineSimilarity: sim,
|
|
531
550
|
};
|
|
532
551
|
})
|
|
533
|
-
.sort((a, b) => b.cosineSimilarity - a.cosineSimilarity)
|
|
534
|
-
|
|
552
|
+
.sort((a, b) => b.cosineSimilarity - a.cosineSimilarity);
|
|
553
|
+
bestSimilarity = scored[0]?.cosineSimilarity ?? null;
|
|
554
|
+
// #4156: only patterns that reach the similarity floor are candidates.
|
|
555
|
+
scored = scored.filter((c) => c.cosineSimilarity >= minSimilarity).slice(0, topK);
|
|
535
556
|
// Step 2: temperature-softmax over the top-K so confidence sums to 1.
|
|
536
557
|
// Temperature 0.1 sharpens differences between similar candidates.
|
|
537
558
|
const tau = 0.1;
|
|
@@ -569,8 +590,11 @@ export const neuralTools = [
|
|
|
569
590
|
embedding: embedding.slice(0, 8), // Preview of embedding
|
|
570
591
|
embeddingDims: embedding.length,
|
|
571
592
|
latency,
|
|
593
|
+
...(bestSimilarity !== null ? { bestSimilarity: Number(bestSimilarity.toFixed(4)), minSimilarity } : {}),
|
|
572
594
|
...(storedPatterns.length === 0 ? {
|
|
573
595
|
_note: 'No patterns stored. Train with neural_train(modelType, trainingData) before predicting.',
|
|
596
|
+
} : predictions.length === 0 ? {
|
|
597
|
+
_note: `No stored pattern reached the similarity floor (best ${bestSimilarity === null ? 'n/a' : bestSimilarity.toFixed(3)}, floor ${minSimilarity}); pass minSimilarity to lower it.`,
|
|
574
598
|
} : {}),
|
|
575
599
|
};
|
|
576
600
|
},
|
|
@@ -43,7 +43,13 @@ export const ruvllmWasmTools = [
|
|
|
43
43
|
nativeBackend = {
|
|
44
44
|
available: iStats._ruvllmBackend === 'active',
|
|
45
45
|
coordinator: iStats._ruvllmBackend || 'unavailable',
|
|
46
|
+
// `trajectories` is the in-process SonaCoordinator buffer — it is
|
|
47
|
+
// 0 in any process that did not itself record (e.g. this MCP
|
|
48
|
+
// server while the daemon / hook processes do the recording).
|
|
46
49
|
trajectories: iStats._ruvllmTrajectories || 0,
|
|
50
|
+
// Project-persisted total across all processes
|
|
51
|
+
// (.claude-flow/neural/stats.json).
|
|
52
|
+
trajectoriesRecorded: iStats.trajectoriesRecorded || 0,
|
|
47
53
|
contrastiveTrainer: sStats._contrastiveTrainer !== 'unavailable' ? 'active' : 'unavailable',
|
|
48
54
|
trainingBackend: iStats._trainingBackend || 'unknown',
|
|
49
55
|
};
|