claude-flow 3.63.1 → 3.63.3
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.json +1 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/sampling.js +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/server.js +2 -0
- package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/guard.d.ts +17 -0
- package/node_modules/@claude-flow/mcp/dist/transport/guard.d.ts.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/guard.js +43 -0
- package/node_modules/@claude-flow/mcp/dist/transport/guard.js.map +1 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +2 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +15 -0
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts +4 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.js +4 -0
- package/node_modules/@claude-flow/mcp/dist/transport/websocket.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/types.d.ts +2 -0
- package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/inbound-dispatcher.d.ts +0 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/inbound-dispatcher.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/inbound-dispatcher.js +11 -2
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/inbound-dispatcher.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +3 -3
- package/v3/@claude-flow/cli/README.md +63 -179
- package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
- package/v3/@claude-flow/cli/dist/src/commands/config.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.js +36 -8
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +120 -120
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +193 -193
- package/v3/@claude-flow/cli/dist/src/services/headless-worker-executor.js +1 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +0 -3
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -4
- package/v3/@claude-flow/cli/package.json +3 -3
- package/.claude/helpers/.helpers-version +0 -1
- package/.claude/helpers/helpers.manifest.json +0 -13
- package/.claude/settings.local.json +0 -8
- package/v3/@claude-flow/cli/dist/src/ruvector/diskann-backend.d.ts +0 -78
- package/v3/@claude-flow/cli/dist/src/ruvector/diskann-backend.js +0 -310
- package/v3/@claude-flow/shared/dist/events/event-store.test.d.ts +0 -8
- package/v3/@claude-flow/shared/dist/events/event-store.test.js +0 -293
- package/v3/@claude-flow/shared/dist/hooks/hooks.test.d.ts +0 -9
- package/v3/@claude-flow/shared/dist/hooks/hooks.test.js +0 -322
- package/v3/@claude-flow/shared/dist/hooks/verify-exports.test.d.ts +0 -9
- package/v3/@claude-flow/shared/dist/hooks/verify-exports.test.js +0 -93
|
@@ -94,7 +94,7 @@ Provide a JSON report with:
|
|
|
94
94
|
sandbox: 'strict',
|
|
95
95
|
model: 'haiku',
|
|
96
96
|
outputFormat: 'json',
|
|
97
|
-
contextPatterns: ['**/*.ts', '**/*.js', '
|
|
97
|
+
contextPatterns: ['**/*.ts', '**/*.js', '**/package.json'],
|
|
98
98
|
timeoutMs: 5 * 60 * 1000,
|
|
99
99
|
},
|
|
100
100
|
},
|
|
@@ -71,9 +71,6 @@ export interface DaemonConfig {
|
|
|
71
71
|
}
|
|
72
72
|
/** Parse only worker types the daemon can actually schedule. */
|
|
73
73
|
export declare function parseEnabledWorkers(value: string): WorkerType[];
|
|
74
|
-
/**
|
|
75
|
-
* Worker Daemon - Manages background workers with Node.js
|
|
76
|
-
*/
|
|
77
74
|
export declare class WorkerDaemon extends EventEmitter {
|
|
78
75
|
private config;
|
|
79
76
|
private workers;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { ConfigFileManager } from './config-file-manager.js';
|
|
14
14
|
import { EventEmitter } from 'events';
|
|
15
15
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, unlinkSync } from 'fs';
|
|
16
|
-
import { cpus } from 'os';
|
|
16
|
+
import { cpus, homedir } from 'os';
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
19
19
|
import { writeFileAtomic } from '../fs-secure.js';
|
|
@@ -108,6 +108,19 @@ function readEnvSecsAsMs(name, defaultMs) {
|
|
|
108
108
|
/**
|
|
109
109
|
* Worker Daemon - Manages background workers with Node.js
|
|
110
110
|
*/
|
|
111
|
+
/** `daemon.aiWorkers.enabled` from the USER-level config only (never the project's). */
|
|
112
|
+
function readUserAiWorkersOptIn() {
|
|
113
|
+
try {
|
|
114
|
+
const raw = JSON.parse(readFileSync(join(homedir(), '.claude-flow', 'config.json'), 'utf-8'));
|
|
115
|
+
const nested = raw?.daemon?.aiWorkers?.enabled;
|
|
116
|
+
const dotted = raw?.['daemon.aiWorkers.enabled'];
|
|
117
|
+
const v = nested ?? dotted;
|
|
118
|
+
return typeof v === 'boolean' ? v : undefined;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
111
124
|
export class WorkerDaemon extends EventEmitter {
|
|
112
125
|
config;
|
|
113
126
|
workers = new Map();
|
|
@@ -203,9 +216,15 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
203
216
|
// #2661 — AI workers are opt-in: flag > config.json > env > OFF.
|
|
204
217
|
// Deliberately NOT restored from daemon-state.json (initializeWorkerStates
|
|
205
218
|
// whitelist) so a stale state file can never resurrect consent.
|
|
219
|
+
// SECURITY: the project's own config file is repo content and may come
|
|
220
|
+
// from a clone, so it can never switch AI workers on. Only the flag, the
|
|
221
|
+
// env var, or the user-level ~/.claude-flow/config.json can.
|
|
222
|
+
// A project file may still say `false` (it can only make this safer).
|
|
206
223
|
aiWorkersEnabled: config?.aiWorkersEnabled
|
|
207
|
-
?? fileConfig.aiWorkersEnabled
|
|
208
|
-
?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1')
|
|
224
|
+
?? (fileConfig.aiWorkersEnabled === false ? false : undefined)
|
|
225
|
+
?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1' ? true : undefined)
|
|
226
|
+
?? readUserAiWorkersOptIn()
|
|
227
|
+
?? false,
|
|
209
228
|
workers: (config?.workers ?? DEFAULT_WORKERS).map(worker => ({ ...worker })),
|
|
210
229
|
enabledWorkers: config?.enabledWorkers,
|
|
211
230
|
};
|
|
@@ -250,7 +269,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
250
269
|
// every worker runs its $0 local path, and a default install produces
|
|
251
270
|
// zero autonomous Claude launches regardless of worktree count.
|
|
252
271
|
if (!this.config.aiWorkersEnabled) {
|
|
253
|
-
this.log('info', 'AI workers disabled (default) - all workers run local-only. Enable with `daemon start --headless`, daemon.aiWorkers.enabled=true, or RUFLO_DAEMON_AI_WORKERS=1 (#2661)');
|
|
272
|
+
this.log('info', 'AI workers disabled (default) - all workers run local-only. Enable with `daemon start --headless`, daemon.aiWorkers.enabled=true in ~/.claude-flow/config.json (a project config cannot enable it), or RUFLO_DAEMON_AI_WORKERS=1 (#2661)');
|
|
254
273
|
return;
|
|
255
274
|
}
|
|
256
275
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.63.
|
|
3
|
+
"version": "3.63.3",
|
|
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",
|
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
"@electron/asar": "^4.3.1",
|
|
103
103
|
"@claude-flow/cli-core": "3.7.5",
|
|
104
104
|
"@claude-flow/codex": "3.0.5",
|
|
105
|
-
"@claude-flow/mcp": "3.1.
|
|
105
|
+
"@claude-flow/mcp": "3.1.3",
|
|
106
106
|
"@claude-flow/neural": "3.0.0",
|
|
107
|
-
"@claude-flow/plugin-agent-federation": "1.0.
|
|
107
|
+
"@claude-flow/plugin-agent-federation": "1.0.2",
|
|
108
108
|
"@claude-flow/security": "3.0.5",
|
|
109
109
|
"@claude-flow/shared": "3.0.5",
|
|
110
110
|
"@iarna/toml": "^2.2.5",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.0.0
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"manifest": {
|
|
3
|
-
"version": "3.32.24",
|
|
4
|
-
"files": {
|
|
5
|
-
"auto-memory-hook.mjs": "68be7e9a9eba7bf9c4e8a230db7bf61a243b965639f8504842799d6c6ca28762",
|
|
6
|
-
"hook-handler.cjs": "50ea92a72651bdc95634f7588d56a5870963168eef5226f66ed14af3b47c8d9a",
|
|
7
|
-
"intelligence.cjs": "bd1f8e4b034944aee1df0391dc47ac2e8cc4b3aa542c65407a59d49620bbf76b",
|
|
8
|
-
"statusline.cjs": "d2a0eac56d1267d8dbed2d70b09feb592299469196ec4b215909f2b052144882"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"signature": "Vi6dza9CfZgo25+eTMJ81fUjsNtQQfUZUZS61MxpBTlilSBTo88cNCCy4ZNC7OfH2r2JOYS7HqqYotf0/AJ9DA==",
|
|
12
|
-
"algorithm": "ed25519"
|
|
13
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiskANN Vector Search Backend
|
|
3
|
-
*
|
|
4
|
-
* SSD-friendly approximate nearest neighbor search using Vamana graph.
|
|
5
|
-
* Falls back gracefully to HNSW (@ruvector/router VectorDb) or
|
|
6
|
-
* pure-JS cosine similarity when DiskANN is unavailable.
|
|
7
|
-
*
|
|
8
|
-
* @module v3/cli/ruvector/diskann-backend
|
|
9
|
-
*/
|
|
10
|
-
export interface DiskAnnConfig {
|
|
11
|
-
dim: number;
|
|
12
|
-
maxDegree?: number;
|
|
13
|
-
buildBeam?: number;
|
|
14
|
-
searchBeam?: number;
|
|
15
|
-
alpha?: number;
|
|
16
|
-
pqSubspaces?: number;
|
|
17
|
-
storagePath?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface SearchResult {
|
|
20
|
-
id: string;
|
|
21
|
-
distance: number;
|
|
22
|
-
score: number;
|
|
23
|
-
}
|
|
24
|
-
export type VectorBackend = 'diskann' | 'hnsw' | 'cosine-js';
|
|
25
|
-
/**
|
|
26
|
-
* Check if @ruvector/diskann is available
|
|
27
|
-
*/
|
|
28
|
-
export declare function isDiskAnnAvailable(): Promise<boolean>;
|
|
29
|
-
/**
|
|
30
|
-
* Create or get a DiskANN index instance
|
|
31
|
-
*/
|
|
32
|
-
export declare function getDiskAnnIndex(config: DiskAnnConfig): Promise<{
|
|
33
|
-
index: any;
|
|
34
|
-
backend: VectorBackend;
|
|
35
|
-
}>;
|
|
36
|
-
/**
|
|
37
|
-
* Get the active backend name
|
|
38
|
-
*/
|
|
39
|
-
export declare function getActiveBackend(): VectorBackend;
|
|
40
|
-
/**
|
|
41
|
-
* Reset the index (for testing)
|
|
42
|
-
*/
|
|
43
|
-
export declare function resetIndex(): void;
|
|
44
|
-
/**
|
|
45
|
-
* Insert a vector into the active backend
|
|
46
|
-
*/
|
|
47
|
-
export declare function insertVector(id: string, vector: Float32Array, config?: DiskAnnConfig): Promise<{
|
|
48
|
-
backend: VectorBackend;
|
|
49
|
-
}>;
|
|
50
|
-
/**
|
|
51
|
-
* Build the index (required for DiskANN before search)
|
|
52
|
-
*/
|
|
53
|
-
export declare function buildIndex(config?: DiskAnnConfig): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* Search for k nearest neighbors
|
|
56
|
-
*/
|
|
57
|
-
export declare function searchVectors(query: Float32Array, k: number, config?: DiskAnnConfig): Promise<SearchResult[]>;
|
|
58
|
-
export interface BenchmarkResult {
|
|
59
|
-
backend: VectorBackend;
|
|
60
|
-
dim: number;
|
|
61
|
-
vectorCount: number;
|
|
62
|
-
insertTimeMs: number;
|
|
63
|
-
buildTimeMs: number;
|
|
64
|
-
searchTimeMs: number;
|
|
65
|
-
searchesPerSecond: number;
|
|
66
|
-
recall: number;
|
|
67
|
-
memoryMB: number;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Run a benchmark comparing available backends
|
|
71
|
-
*/
|
|
72
|
-
export declare function benchmark(opts?: {
|
|
73
|
-
dim?: number;
|
|
74
|
-
vectorCount?: number;
|
|
75
|
-
k?: number;
|
|
76
|
-
queries?: number;
|
|
77
|
-
}): Promise<BenchmarkResult[]>;
|
|
78
|
-
//# sourceMappingURL=diskann-backend.d.ts.map
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiskANN Vector Search Backend
|
|
3
|
-
*
|
|
4
|
-
* SSD-friendly approximate nearest neighbor search using Vamana graph.
|
|
5
|
-
* Falls back gracefully to HNSW (@ruvector/router VectorDb) or
|
|
6
|
-
* pure-JS cosine similarity when DiskANN is unavailable.
|
|
7
|
-
*
|
|
8
|
-
* @module v3/cli/ruvector/diskann-backend
|
|
9
|
-
*/
|
|
10
|
-
// ===== Lazy loading =====
|
|
11
|
-
let diskannInstance = null;
|
|
12
|
-
let diskannAvailable = null;
|
|
13
|
-
let activeBackend = 'cosine-js';
|
|
14
|
-
/**
|
|
15
|
-
* Check if @ruvector/diskann is available
|
|
16
|
-
*/
|
|
17
|
-
export async function isDiskAnnAvailable() {
|
|
18
|
-
if (diskannAvailable !== null)
|
|
19
|
-
return diskannAvailable;
|
|
20
|
-
try {
|
|
21
|
-
const { createRequire } = await import('module');
|
|
22
|
-
const require2 = createRequire(import.meta.url);
|
|
23
|
-
const mod = require2('@ruvector/diskann');
|
|
24
|
-
diskannAvailable = typeof mod.DiskAnn === 'function';
|
|
25
|
-
return diskannAvailable;
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
diskannAvailable = false;
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Create or get a DiskANN index instance
|
|
34
|
-
*/
|
|
35
|
-
export async function getDiskAnnIndex(config) {
|
|
36
|
-
if (diskannInstance)
|
|
37
|
-
return { index: diskannInstance, backend: activeBackend };
|
|
38
|
-
// Try DiskANN first
|
|
39
|
-
if (await isDiskAnnAvailable()) {
|
|
40
|
-
try {
|
|
41
|
-
const { createRequire } = await import('module');
|
|
42
|
-
const require2 = createRequire(import.meta.url);
|
|
43
|
-
const { DiskAnn } = require2('@ruvector/diskann');
|
|
44
|
-
const index = new DiskAnn({
|
|
45
|
-
dim: config.dim,
|
|
46
|
-
maxDegree: config.maxDegree ?? 64,
|
|
47
|
-
buildBeam: config.buildBeam ?? 128,
|
|
48
|
-
searchBeam: config.searchBeam ?? 64,
|
|
49
|
-
alpha: config.alpha ?? 1.2,
|
|
50
|
-
pqSubspaces: config.pqSubspaces ?? 0,
|
|
51
|
-
storagePath: config.storagePath,
|
|
52
|
-
});
|
|
53
|
-
diskannInstance = index;
|
|
54
|
-
activeBackend = 'diskann';
|
|
55
|
-
return { index, backend: 'diskann' };
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Fall through
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Try HNSW (@ruvector/router VectorDb) as fallback
|
|
62
|
-
try {
|
|
63
|
-
const { createRequire } = await import('module');
|
|
64
|
-
const require2 = createRequire(import.meta.url);
|
|
65
|
-
const router = require2('@ruvector/router');
|
|
66
|
-
if (router.VectorDb && router.DistanceMetric) {
|
|
67
|
-
const index = new router.VectorDb({
|
|
68
|
-
dimensions: config.dim,
|
|
69
|
-
distanceMetric: router.DistanceMetric.Cosine,
|
|
70
|
-
hnswM: 16,
|
|
71
|
-
hnswEfConstruction: 200,
|
|
72
|
-
hnswEfSearch: 100,
|
|
73
|
-
});
|
|
74
|
-
diskannInstance = index;
|
|
75
|
-
activeBackend = 'hnsw';
|
|
76
|
-
return { index, backend: 'hnsw' };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// Fall through
|
|
81
|
-
}
|
|
82
|
-
// Pure JS fallback
|
|
83
|
-
const jsIndex = createJsFallbackIndex(config.dim);
|
|
84
|
-
diskannInstance = jsIndex;
|
|
85
|
-
activeBackend = 'cosine-js';
|
|
86
|
-
return { index: jsIndex, backend: 'cosine-js' };
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Get the active backend name
|
|
90
|
-
*/
|
|
91
|
-
export function getActiveBackend() {
|
|
92
|
-
return activeBackend;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Reset the index (for testing)
|
|
96
|
-
*/
|
|
97
|
-
export function resetIndex() {
|
|
98
|
-
diskannInstance = null;
|
|
99
|
-
diskannAvailable = null;
|
|
100
|
-
activeBackend = 'cosine-js';
|
|
101
|
-
}
|
|
102
|
-
// ===== Unified search interface =====
|
|
103
|
-
/**
|
|
104
|
-
* Insert a vector into the active backend
|
|
105
|
-
*/
|
|
106
|
-
export async function insertVector(id, vector, config = { dim: 384 }) {
|
|
107
|
-
const { index, backend } = await getDiskAnnIndex(config);
|
|
108
|
-
if (backend === 'diskann') {
|
|
109
|
-
index.insert(id, vector);
|
|
110
|
-
}
|
|
111
|
-
else if (backend === 'hnsw') {
|
|
112
|
-
index.insert(id, vector);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
index.insert(id, vector);
|
|
116
|
-
}
|
|
117
|
-
return { backend };
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Build the index (required for DiskANN before search)
|
|
121
|
-
*/
|
|
122
|
-
export async function buildIndex(config = { dim: 384 }) {
|
|
123
|
-
const { index, backend } = await getDiskAnnIndex(config);
|
|
124
|
-
if (backend === 'diskann' && typeof index.build === 'function') {
|
|
125
|
-
index.build();
|
|
126
|
-
}
|
|
127
|
-
// HNSW and JS fallback don't need explicit build
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Search for k nearest neighbors
|
|
131
|
-
*/
|
|
132
|
-
export async function searchVectors(query, k, config = { dim: 384 }) {
|
|
133
|
-
const { index, backend } = await getDiskAnnIndex(config);
|
|
134
|
-
if (backend === 'diskann') {
|
|
135
|
-
const results = index.search(query, k);
|
|
136
|
-
return results.map((r) => ({
|
|
137
|
-
id: r.id,
|
|
138
|
-
distance: r.distance,
|
|
139
|
-
score: 1 / (1 + r.distance), // Convert L2 distance to similarity
|
|
140
|
-
}));
|
|
141
|
-
}
|
|
142
|
-
if (backend === 'hnsw') {
|
|
143
|
-
const results = index.search(query, k);
|
|
144
|
-
return results.map((r) => ({
|
|
145
|
-
id: r.id,
|
|
146
|
-
distance: r.score, // VectorDb returns distance as 'score'
|
|
147
|
-
score: 1 / (1 + r.score),
|
|
148
|
-
}));
|
|
149
|
-
}
|
|
150
|
-
// JS fallback
|
|
151
|
-
return index.search(query, k);
|
|
152
|
-
}
|
|
153
|
-
// ===== Pure JS fallback =====
|
|
154
|
-
function createJsFallbackIndex(dim) {
|
|
155
|
-
const vectors = new Map();
|
|
156
|
-
return {
|
|
157
|
-
insert(id, vector) {
|
|
158
|
-
vectors.set(id, new Float32Array(vector));
|
|
159
|
-
},
|
|
160
|
-
search(query, k) {
|
|
161
|
-
const results = [];
|
|
162
|
-
for (const [id, vec] of vectors) {
|
|
163
|
-
let dot = 0, normQ = 0, normV = 0;
|
|
164
|
-
for (let i = 0; i < dim; i++) {
|
|
165
|
-
dot += query[i] * vec[i];
|
|
166
|
-
normQ += query[i] * query[i];
|
|
167
|
-
normV += vec[i] * vec[i];
|
|
168
|
-
}
|
|
169
|
-
const cosine = dot / (Math.sqrt(normQ) * Math.sqrt(normV) || 1);
|
|
170
|
-
const distance = 1 - cosine;
|
|
171
|
-
results.push({ id, distance, score: cosine });
|
|
172
|
-
}
|
|
173
|
-
return results.sort((a, b) => a.distance - b.distance).slice(0, k);
|
|
174
|
-
},
|
|
175
|
-
count() { return vectors.size; },
|
|
176
|
-
delete(id) { return vectors.delete(id); },
|
|
177
|
-
build() { },
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Run a benchmark comparing available backends
|
|
182
|
-
*/
|
|
183
|
-
export async function benchmark(opts = {}) {
|
|
184
|
-
const dim = opts.dim ?? 384;
|
|
185
|
-
const n = opts.vectorCount ?? 1000;
|
|
186
|
-
const k = opts.k ?? 10;
|
|
187
|
-
const queryCount = opts.queries ?? 100;
|
|
188
|
-
const results = [];
|
|
189
|
-
// Generate random vectors
|
|
190
|
-
const vectors = [];
|
|
191
|
-
for (let i = 0; i < n; i++) {
|
|
192
|
-
const v = new Float32Array(dim);
|
|
193
|
-
for (let j = 0; j < dim; j++)
|
|
194
|
-
v[j] = Math.random() * 2 - 1;
|
|
195
|
-
// Normalize
|
|
196
|
-
let norm = 0;
|
|
197
|
-
for (let j = 0; j < dim; j++)
|
|
198
|
-
norm += v[j] * v[j];
|
|
199
|
-
norm = Math.sqrt(norm);
|
|
200
|
-
for (let j = 0; j < dim; j++)
|
|
201
|
-
v[j] /= norm;
|
|
202
|
-
vectors.push([`vec-${i}`, v]);
|
|
203
|
-
}
|
|
204
|
-
// Generate query vectors
|
|
205
|
-
const queries = [];
|
|
206
|
-
for (let i = 0; i < queryCount; i++) {
|
|
207
|
-
const q = new Float32Array(dim);
|
|
208
|
-
for (let j = 0; j < dim; j++)
|
|
209
|
-
q[j] = Math.random() * 2 - 1;
|
|
210
|
-
let norm = 0;
|
|
211
|
-
for (let j = 0; j < dim; j++)
|
|
212
|
-
norm += q[j] * q[j];
|
|
213
|
-
norm = Math.sqrt(norm);
|
|
214
|
-
for (let j = 0; j < dim; j++)
|
|
215
|
-
q[j] /= norm;
|
|
216
|
-
queries.push(q);
|
|
217
|
-
}
|
|
218
|
-
// Brute force ground truth
|
|
219
|
-
function bruteForceSearch(query) {
|
|
220
|
-
const scores = [];
|
|
221
|
-
for (const [id, vec] of vectors) {
|
|
222
|
-
let dist = 0;
|
|
223
|
-
for (let j = 0; j < dim; j++) {
|
|
224
|
-
const d = query[j] - vec[j];
|
|
225
|
-
dist += d * d;
|
|
226
|
-
}
|
|
227
|
-
scores.push({ id, dist });
|
|
228
|
-
}
|
|
229
|
-
scores.sort((a, b) => a.dist - b.dist);
|
|
230
|
-
return scores.slice(0, k).map(s => s.id);
|
|
231
|
-
}
|
|
232
|
-
const groundTruth = queries.map(q => bruteForceSearch(q));
|
|
233
|
-
// Test each available backend
|
|
234
|
-
for (const backendName of ['diskann', 'hnsw', 'cosine-js']) {
|
|
235
|
-
resetIndex();
|
|
236
|
-
try {
|
|
237
|
-
let index;
|
|
238
|
-
const memBefore = process.memoryUsage().heapUsed;
|
|
239
|
-
if (backendName === 'diskann') {
|
|
240
|
-
if (!(await isDiskAnnAvailable()))
|
|
241
|
-
continue;
|
|
242
|
-
const { createRequire } = await import('module');
|
|
243
|
-
const require2 = createRequire(import.meta.url);
|
|
244
|
-
const { DiskAnn } = require2('@ruvector/diskann');
|
|
245
|
-
index = new DiskAnn({ dim, maxDegree: 64, buildBeam: 128, searchBeam: 64 });
|
|
246
|
-
}
|
|
247
|
-
else if (backendName === 'hnsw') {
|
|
248
|
-
try {
|
|
249
|
-
const { createRequire } = await import('module');
|
|
250
|
-
const require2 = createRequire(import.meta.url);
|
|
251
|
-
const router = require2('@ruvector/router');
|
|
252
|
-
if (!router.VectorDb)
|
|
253
|
-
continue;
|
|
254
|
-
index = new router.VectorDb({ dimensions: dim, distanceMetric: router.DistanceMetric.Cosine });
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
index = createJsFallbackIndex(dim);
|
|
262
|
-
}
|
|
263
|
-
// Insert
|
|
264
|
-
const insertStart = performance.now();
|
|
265
|
-
for (const [id, vec] of vectors) {
|
|
266
|
-
index.insert(id, vec);
|
|
267
|
-
}
|
|
268
|
-
const insertTime = performance.now() - insertStart;
|
|
269
|
-
// Build
|
|
270
|
-
const buildStart = performance.now();
|
|
271
|
-
if (typeof index.build === 'function')
|
|
272
|
-
index.build();
|
|
273
|
-
const buildTime = performance.now() - buildStart;
|
|
274
|
-
// Search
|
|
275
|
-
const searchStart = performance.now();
|
|
276
|
-
const searchResults = [];
|
|
277
|
-
for (const q of queries) {
|
|
278
|
-
const r = index.search(q, k);
|
|
279
|
-
searchResults.push(r.map((x) => x.id));
|
|
280
|
-
}
|
|
281
|
-
const searchTime = performance.now() - searchStart;
|
|
282
|
-
// Recall vs ground truth
|
|
283
|
-
let totalRecall = 0;
|
|
284
|
-
for (let i = 0; i < queryCount; i++) {
|
|
285
|
-
const truth = new Set(groundTruth[i]);
|
|
286
|
-
const found = searchResults[i].filter(id => truth.has(id)).length;
|
|
287
|
-
totalRecall += found / k;
|
|
288
|
-
}
|
|
289
|
-
const recall = totalRecall / queryCount;
|
|
290
|
-
const memAfter = process.memoryUsage().heapUsed;
|
|
291
|
-
results.push({
|
|
292
|
-
backend: backendName,
|
|
293
|
-
dim,
|
|
294
|
-
vectorCount: n,
|
|
295
|
-
insertTimeMs: Math.round(insertTime * 100) / 100,
|
|
296
|
-
buildTimeMs: Math.round(buildTime * 100) / 100,
|
|
297
|
-
searchTimeMs: Math.round(searchTime * 100) / 100,
|
|
298
|
-
searchesPerSecond: Math.round(queryCount / (searchTime / 1000)),
|
|
299
|
-
recall: Math.round(recall * 1000) / 1000,
|
|
300
|
-
memoryMB: Math.round((memAfter - memBefore) / 1024 / 1024 * 100) / 100,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
// Backend failed, skip
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
resetIndex();
|
|
308
|
-
return results;
|
|
309
|
-
}
|
|
310
|
-
//# sourceMappingURL=diskann-backend.js.map
|