claude-flow 3.6.4 → 3.6.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.6.4",
3
+ "version": "3.6.6",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -8,47 +8,90 @@ import { validateIdentifier, validateText } from './validate-input.js';
8
8
  // Session registry for multi-session support
9
9
  const browserSessions = new Map();
10
10
  /**
11
- * Execute agent-browser CLI command
11
+ * Execute agent-browser CLI command.
12
+ * Tries global agent-browser first, falls back to npx if ENOENT.
12
13
  */
13
14
  async function execBrowserCommand(args, session = 'default') {
14
15
  const { execFileSync } = await import('child_process');
16
+ const fullArgs = ['--session', session, '--json', ...args];
17
+ let result;
15
18
  try {
16
- const fullArgs = ['--session', session, '--json', ...args];
17
- const result = execFileSync('agent-browser', fullArgs, {
19
+ result = execFileSync('agent-browser', fullArgs, {
18
20
  encoding: 'utf-8',
19
21
  timeout: 30000,
20
22
  });
21
- let data;
22
- try {
23
- data = JSON.parse(result);
24
- }
25
- catch {
26
- data = result.trim();
23
+ }
24
+ catch (error) {
25
+ const err = error;
26
+ if (err.code === 'ENOENT') {
27
+ try {
28
+ result = execFileSync('npx', ['--yes', 'agent-browser', ...fullArgs], {
29
+ encoding: 'utf-8',
30
+ timeout: 60000,
31
+ });
32
+ }
33
+ catch (npxError) {
34
+ const npxErr = npxError;
35
+ return {
36
+ content: [{
37
+ type: 'text',
38
+ text: JSON.stringify({
39
+ success: false,
40
+ error: npxErr.code === 'ENOENT'
41
+ ? 'Neither agent-browser nor npx found. Install with: npm i -g agent-browser'
42
+ : npxErr instanceof Error ? npxErr.message : String(npxError),
43
+ }),
44
+ }],
45
+ isError: true,
46
+ };
47
+ }
27
48
  }
28
- // Update session activity
29
- const sessionInfo = browserSessions.get(session);
30
- if (sessionInfo) {
31
- sessionInfo.lastActivity = new Date().toISOString();
49
+ else {
50
+ return {
51
+ content: [{
52
+ type: 'text',
53
+ text: JSON.stringify({
54
+ success: false,
55
+ error: err instanceof Error ? err.message : String(error),
56
+ }),
57
+ }],
58
+ isError: true,
59
+ };
32
60
  }
33
- return {
34
- content: [{
35
- type: 'text',
36
- text: JSON.stringify(data, null, 2),
37
- }],
38
- };
39
61
  }
40
- catch (error) {
41
- return {
42
- content: [{
43
- type: 'text',
44
- text: JSON.stringify({
45
- success: false,
46
- error: error instanceof Error ? error.message : String(error),
47
- }),
48
- }],
49
- isError: true,
50
- };
62
+ let data;
63
+ try {
64
+ data = JSON.parse(result);
65
+ }
66
+ catch {
67
+ data = result.trim();
51
68
  }
69
+ const sessionInfo = browserSessions.get(session);
70
+ if (sessionInfo) {
71
+ sessionInfo.lastActivity = new Date().toISOString();
72
+ }
73
+ return {
74
+ content: [{
75
+ type: 'text',
76
+ text: JSON.stringify(data, null, 2),
77
+ }],
78
+ };
79
+ }
80
+ /**
81
+ * Detect if Linux needs --no-sandbox for Chrome.
82
+ */
83
+ function needsNoSandbox() {
84
+ try {
85
+ const { readFileSync, existsSync } = require('fs');
86
+ if (process.platform !== 'linux')
87
+ return false;
88
+ const clonePath = '/proc/sys/kernel/unprivileged_userns_clone';
89
+ if (existsSync(clonePath)) {
90
+ return readFileSync(clonePath, 'utf-8').trim() === '0';
91
+ }
92
+ }
93
+ catch { /* not Linux or can't read */ }
94
+ return false;
52
95
  }
53
96
  /**
54
97
  * Browser MCP Tools
@@ -72,6 +115,11 @@ export const browserTools = [
72
115
  enum: ['load', 'domcontentloaded', 'networkidle'],
73
116
  description: 'Wait condition',
74
117
  },
118
+ args: {
119
+ type: 'array',
120
+ items: { type: 'string' },
121
+ description: 'Additional Chrome launch args (e.g., ["--no-sandbox", "--disable-dev-shm-usage"])',
122
+ },
75
123
  },
76
124
  required: ['url'],
77
125
  },
@@ -85,9 +133,15 @@ export const browserTools = [
85
133
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
86
134
  }
87
135
  const { url, session, waitUntil } = input;
136
+ const launchArgs = input.args || [];
137
+ if (needsNoSandbox() && !launchArgs.includes('--no-sandbox')) {
138
+ launchArgs.push('--no-sandbox');
139
+ }
88
140
  const args = ['open', url];
89
141
  if (waitUntil)
90
142
  args.push('--wait-until', waitUntil);
143
+ for (const a of launchArgs)
144
+ args.push('--arg', a);
91
145
  // Create session if new
92
146
  const sessionId = session || 'default';
93
147
  if (!browserSessions.has(sessionId)) {
@@ -369,13 +423,13 @@ export const browserTools = [
369
423
  },
370
424
  {
371
425
  name: 'browser_hover',
372
- description: 'Hover over an element',
426
+ description: 'Hover over an element using ref (@e1) or CSS selector',
373
427
  category: 'browser',
374
428
  tags: ['interaction'],
375
429
  inputSchema: {
376
430
  type: 'object',
377
431
  properties: {
378
- target: { type: 'string', description: 'Element ref or CSS selector' },
432
+ target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
379
433
  session: { type: 'string', description: 'Session ID' },
380
434
  },
381
435
  required: ['target'],
@@ -412,7 +412,7 @@ export const embeddingsTools = [
412
412
  query,
413
413
  limit: topK,
414
414
  threshold,
415
- namespace: namespace || 'default'
415
+ namespace: namespace || 'all'
416
416
  });
417
417
  const searchTime = (performance.now() - startTime).toFixed(2);
418
418
  return {
@@ -428,7 +428,7 @@ export const embeddingsTools = [
428
428
  model: config.model,
429
429
  topK,
430
430
  threshold,
431
- namespace: namespace || 'default',
431
+ namespace: namespace || 'all',
432
432
  searchTime: `${searchTime}ms`,
433
433
  indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
434
434
  resultCount: searchResult.results.length
@@ -446,7 +446,7 @@ export const embeddingsTools = [
446
446
  model: config.model,
447
447
  topK,
448
448
  threshold,
449
- namespace: namespace || 'default',
449
+ namespace: namespace || 'all',
450
450
  searchTime: `${searchTime}ms`,
451
451
  indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
452
452
  },
@@ -804,5 +804,74 @@ export const embeddingsTools = [
804
804
  };
805
805
  },
806
806
  },
807
+ // --- RaBitQ 1-bit quantized vector index ---
808
+ {
809
+ name: 'embeddings_rabitq_build',
810
+ description: 'Build RaBitQ 1-bit quantized index from stored embeddings (32× compression). Pre-filters candidates via Hamming scan before exact rerank.',
811
+ category: 'embeddings',
812
+ inputSchema: {
813
+ type: 'object',
814
+ properties: {
815
+ force: { type: 'boolean', description: 'Force rebuild even if index exists' },
816
+ },
817
+ },
818
+ handler: async (params) => {
819
+ const { buildRabitqIndex } = await import('../memory/rabitq-index.js');
820
+ return buildRabitqIndex({ force: params.force });
821
+ },
822
+ },
823
+ {
824
+ name: 'embeddings_rabitq_search',
825
+ description: 'Search via RaBitQ quantized index (fast Hamming scan). Returns candidate IDs for reranking.',
826
+ category: 'embeddings',
827
+ inputSchema: {
828
+ type: 'object',
829
+ properties: {
830
+ query: { type: 'string', description: 'Search query text' },
831
+ k: { type: 'number', description: 'Number of results (default: 10)' },
832
+ namespace: { type: 'string', description: 'Filter by namespace' },
833
+ },
834
+ required: ['query'],
835
+ },
836
+ handler: async (params) => {
837
+ const { validateText: vt } = await import('./validate-input.js');
838
+ const v = vt(params.query, 'query');
839
+ if (!v.valid)
840
+ return { success: false, error: v.error };
841
+ const { searchRabitq } = await import('../memory/rabitq-index.js');
842
+ const { generateEmbedding } = await import('../memory/memory-initializer.js');
843
+ const queryEmb = await generateEmbedding(params.query);
844
+ const results = await searchRabitq(queryEmb.embedding, {
845
+ k: params.k || 10,
846
+ namespace: params.namespace,
847
+ });
848
+ if (!results) {
849
+ return { success: false, error: 'RaBitQ index not built. Call embeddings_rabitq_build first.' };
850
+ }
851
+ return {
852
+ success: true,
853
+ results: results.map(r => ({
854
+ id: r.id.substring(0, 12),
855
+ key: r.key,
856
+ namespace: r.namespace,
857
+ distance: Math.round(r.distance * 10000) / 10000,
858
+ })),
859
+ count: results.length,
860
+ };
861
+ },
862
+ },
863
+ {
864
+ name: 'embeddings_rabitq_status',
865
+ description: 'Get RaBitQ quantized index status — availability, vector count, compression ratio',
866
+ category: 'embeddings',
867
+ inputSchema: {
868
+ type: 'object',
869
+ properties: {},
870
+ },
871
+ handler: async () => {
872
+ const { getRabitqStatus } = await import('../memory/rabitq-index.js');
873
+ return { success: true, ...getRabitqStatus() };
874
+ },
875
+ },
807
876
  ];
808
877
  //# sourceMappingURL=embeddings-tools.js.map
@@ -2479,20 +2479,25 @@ export const hooksPatternStore = {
2479
2479
  }
2480
2480
  const success = reasoningResult?.success || storeResult.success;
2481
2481
  const controller = reasoningResult?.controller || (storeResult.success ? 'bridge-store' : 'none');
2482
+ const hasEmbedding = !!storeResult.embedding || controller === 'reasoningBank' || controller === 'bridge-fallback';
2482
2483
  return {
2483
2484
  patternId: reasoningResult?.patternId || storeResult.id || patternId,
2484
2485
  pattern,
2485
2486
  type,
2486
2487
  confidence,
2487
2488
  indexed: success,
2488
- hnswIndexed: success && (!!storeResult.embedding || controller === 'reasoningBank'),
2489
+ hnswIndexed: success && hasEmbedding,
2489
2490
  embedding: storeResult.embedding,
2490
2491
  timestamp,
2491
2492
  controller,
2492
- implementation: controller === 'reasoningBank' ? 'reasoning-bank-controller' : (storeResult.success ? 'real-hnsw-indexed' : 'memory-only'),
2493
+ implementation: (controller === 'reasoningBank' || controller === 'bridge-fallback')
2494
+ ? 'reasoning-bank-controller'
2495
+ : (storeResult.success ? 'real-hnsw-indexed' : 'memory-only'),
2493
2496
  note: controller === 'reasoningBank'
2494
2497
  ? 'Pattern stored via ReasoningBank controller with HNSW indexing'
2495
- : (storeResult.success ? 'Pattern stored with vector embedding for semantic search' : (storeResult.error || 'Store function unavailable')),
2498
+ : controller === 'bridge-fallback'
2499
+ ? 'Pattern stored via bridge with embedding and HNSW indexing'
2500
+ : (storeResult.success ? 'Pattern stored with vector embedding for semantic search' : (storeResult.error || 'Store function unavailable')),
2496
2501
  };
2497
2502
  },
2498
2503
  };
@@ -37,6 +37,7 @@ export declare function bridgeStoreEntry(options: {
37
37
  dimensions: number;
38
38
  model: string;
39
39
  };
40
+ rawEmbedding?: number[];
40
41
  guarded?: boolean;
41
42
  cached?: boolean;
42
43
  attested?: boolean;
@@ -404,4 +405,19 @@ export declare function bridgeContextSynthesize(params: {
404
405
  export declare function bridgeSemanticRoute(params: {
405
406
  input: string;
406
407
  }): Promise<any>;
408
+ /**
409
+ * Export all embeddings from the bridge's better-sqlite3 connection.
410
+ * Used by RaBitQ to build its index from the same data that memory_store writes.
411
+ * Returns null if bridge is unavailable (caller falls back to sql.js).
412
+ */
413
+ export declare function bridgeGetAllEmbeddings(options?: {
414
+ dimensions?: number;
415
+ limit?: number;
416
+ dbPath?: string;
417
+ }): Promise<Array<{
418
+ id: string;
419
+ key: string;
420
+ namespace: string;
421
+ embedding: number[];
422
+ }> | null>;
407
423
  //# sourceMappingURL=memory-bridge.d.ts.map
@@ -381,6 +381,7 @@ export async function bridgeStoreEntry(options) {
381
381
  success: true,
382
382
  id,
383
383
  embedding: embeddingJson ? { dimensions, model } : undefined,
384
+ rawEmbedding: embeddingJson ? JSON.parse(embeddingJson) : undefined,
384
385
  guarded: true,
385
386
  cached: true,
386
387
  attested: true,
@@ -460,10 +461,11 @@ export async function bridgeSearchEntries(options) {
460
461
  bm25ScoreVal = Math.min(bm25ScoreVal / 10, 1.0);
461
462
  }
462
463
  // Reciprocal rank fusion: combine semantic and BM25
463
- // Weight: 0.7 semantic + 0.3 BM25 (semantic preferred when embeddings available)
464
- const score = queryEmbedding
464
+ // Weight: 0.7 semantic + 0.3 BM25 when both embeddings present
465
+ // Fall back to BM25-only when either query or row lacks an embedding
466
+ const score = semanticScore > 0
465
467
  ? (0.7 * semanticScore + 0.3 * bm25ScoreVal)
466
- : bm25ScoreVal; // BM25-only when no embeddings
468
+ : bm25ScoreVal;
467
469
  if (score >= threshold) {
468
470
  // Phase 4: ExplainableRecall provenance
469
471
  const provenance = queryEmbedding
@@ -971,15 +973,31 @@ export async function bridgeStorePattern(options) {
971
973
  return { success: true, patternId, controller: 'reasoningBank' };
972
974
  }
973
975
  // Fallback: store via bridge SQL
976
+ const patternValue = JSON.stringify({ pattern: options.pattern, type: options.type, confidence: options.confidence, metadata: options.metadata });
974
977
  const result = await bridgeStoreEntry({
975
978
  key: patternId,
976
- value: JSON.stringify({ pattern: options.pattern, type: options.type, confidence: options.confidence, metadata: options.metadata }),
979
+ value: patternValue,
977
980
  namespace: 'pattern',
978
981
  generateEmbeddingFlag: true,
979
982
  tags: [options.type, 'reasoning-pattern'],
980
983
  dbPath: options.dbPath,
981
984
  });
982
- return result ? { success: true, patternId: result.id, controller: 'bridge-fallback' } : null;
985
+ if (!result)
986
+ return null;
987
+ // Add to HNSW index for fast semantic search (bridgeStoreEntry stores SQL only)
988
+ if (result.rawEmbedding) {
989
+ try {
990
+ const { addToHNSWIndex } = await import('./memory-initializer.js');
991
+ await addToHNSWIndex(result.id, result.rawEmbedding, {
992
+ id: result.id,
993
+ key: patternId,
994
+ namespace: 'pattern',
995
+ content: patternValue,
996
+ });
997
+ }
998
+ catch { /* HNSW is best-effort */ }
999
+ }
1000
+ return { success: true, patternId: result.id, controller: 'bridge-fallback' };
983
1001
  }
984
1002
  catch {
985
1003
  return null;
@@ -1536,6 +1554,51 @@ export async function bridgeSemanticRoute(params) {
1536
1554
  return { route: null, error: e.message };
1537
1555
  }
1538
1556
  }
1557
+ // ===== RaBitQ data export =====
1558
+ /**
1559
+ * Export all embeddings from the bridge's better-sqlite3 connection.
1560
+ * Used by RaBitQ to build its index from the same data that memory_store writes.
1561
+ * Returns null if bridge is unavailable (caller falls back to sql.js).
1562
+ */
1563
+ export async function bridgeGetAllEmbeddings(options) {
1564
+ const registry = await getRegistry(options?.dbPath);
1565
+ if (!registry)
1566
+ return null;
1567
+ const ctx = getDb(registry);
1568
+ if (!ctx)
1569
+ return null;
1570
+ try {
1571
+ const dims = options?.dimensions ?? 384;
1572
+ const maxRows = options?.limit ?? 50000;
1573
+ const rows = ctx.db.prepare(`
1574
+ SELECT id, key, namespace, embedding
1575
+ FROM memory_entries
1576
+ WHERE status = 'active' AND embedding IS NOT NULL
1577
+ LIMIT ?
1578
+ `).all(maxRows);
1579
+ const results = [];
1580
+ for (const row of rows) {
1581
+ if (!row.embedding)
1582
+ continue;
1583
+ try {
1584
+ const emb = JSON.parse(row.embedding);
1585
+ if (emb.length !== dims)
1586
+ continue;
1587
+ results.push({
1588
+ id: String(row.id),
1589
+ key: row.key || String(row.id),
1590
+ namespace: row.namespace || 'default',
1591
+ embedding: emb,
1592
+ });
1593
+ }
1594
+ catch { /* skip invalid */ }
1595
+ }
1596
+ return results;
1597
+ }
1598
+ catch {
1599
+ return null;
1600
+ }
1601
+ }
1539
1602
  // ===== Utility =====
1540
1603
  function cosineSim(a, b) {
1541
1604
  if (!a || !b || a.length === 0 || b.length === 0)
@@ -1689,8 +1689,19 @@ export async function storeEntry(options) {
1689
1689
  const bridge = await getBridge();
1690
1690
  if (bridge) {
1691
1691
  const bridgeResult = await bridge.bridgeStoreEntry(options);
1692
- if (bridgeResult)
1692
+ if (bridgeResult) {
1693
+ // Keep HNSW index in sync with bridge-stored entries
1694
+ if (bridgeResult.rawEmbedding && bridgeResult.success) {
1695
+ const ns = options.namespace || 'default';
1696
+ await addToHNSWIndex(bridgeResult.id, bridgeResult.rawEmbedding, {
1697
+ id: bridgeResult.id,
1698
+ key: options.key,
1699
+ namespace: ns,
1700
+ content: options.value,
1701
+ }).catch(() => { });
1702
+ }
1693
1703
  return bridgeResult;
1704
+ }
1694
1705
  }
1695
1706
  // Fallback: raw sql.js
1696
1707
  const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
@@ -1799,7 +1810,51 @@ export async function searchEntries(options) {
1799
1810
  // Generate query embedding
1800
1811
  const queryEmb = await generateEmbedding(query);
1801
1812
  const queryEmbedding = queryEmb.embedding;
1802
- // Try HNSW search first (150x faster)
1813
+ // Try RaBitQ pre-filter first (32× compressed Hamming scan)
1814
+ try {
1815
+ const { searchRabitq } = await import('./rabitq-index.js');
1816
+ const rabitqCandidates = await searchRabitq(queryEmbedding, { k: limit * 2, namespace: effectiveNamespace });
1817
+ if (rabitqCandidates && rabitqCandidates.length > 0) {
1818
+ // Rerank candidates with exact cosine similarity from SQLite
1819
+ const initSqlJs = (await import('sql.js')).default;
1820
+ const SQL = await initSqlJs();
1821
+ const fileBuffer = fs.readFileSync(dbPath);
1822
+ const db = new SQL.Database(fileBuffer);
1823
+ const reranked = [];
1824
+ for (const candidate of rabitqCandidates) {
1825
+ const stmt = db.prepare('SELECT content, embedding FROM memory_entries WHERE id = ? AND status = ?');
1826
+ stmt.bind([candidate.id, 'active']);
1827
+ if (stmt.step()) {
1828
+ const [content, embeddingJson] = stmt.get();
1829
+ let score = 0;
1830
+ if (embeddingJson) {
1831
+ try {
1832
+ const embedding = JSON.parse(embeddingJson);
1833
+ score = cosineSim(queryEmbedding, embedding);
1834
+ }
1835
+ catch { /* skip */ }
1836
+ }
1837
+ if (score >= threshold) {
1838
+ reranked.push({
1839
+ id: candidate.id.substring(0, 12),
1840
+ key: candidate.key || candidate.id.substring(0, 15),
1841
+ content: (content || '').substring(0, 60) + ((content || '').length > 60 ? '...' : ''),
1842
+ score,
1843
+ namespace: candidate.namespace,
1844
+ });
1845
+ }
1846
+ }
1847
+ stmt.free();
1848
+ }
1849
+ db.close();
1850
+ if (reranked.length > 0) {
1851
+ reranked.sort((a, b) => b.score - a.score);
1852
+ return { success: true, results: reranked.slice(0, limit), searchTime: Date.now() - startTime };
1853
+ }
1854
+ }
1855
+ }
1856
+ catch { /* RaBitQ unavailable, fall through */ }
1857
+ // Try HNSW search (150x faster than brute-force)
1803
1858
  const hnswResults = await searchHNSWIndex(queryEmbedding, { k: limit, namespace: effectiveNamespace });
1804
1859
  if (hnswResults && hnswResults.length > 0) {
1805
1860
  // Filter by threshold
@@ -0,0 +1,60 @@
1
+ /**
2
+ * RaBitQ Index — 1-bit quantized vector pre-filter (32× compression)
3
+ *
4
+ * Wraps @ruvector/rabitq-wasm to provide Hamming-scan pre-filtering
5
+ * over quantized embeddings. Candidates are reranked with exact cosine
6
+ * similarity from the full-precision source (HNSW or SQLite).
7
+ *
8
+ * Lifecycle:
9
+ * 1. build() — bulk-load all embeddings from SQLite into the WASM index
10
+ * 2. search() — fast Hamming scan → candidate ids → caller reranks
11
+ * 3. rebuild() — called when entry count drifts >20% from last build
12
+ */
13
+ /**
14
+ * Build or rebuild the RaBitQ index from SQLite embeddings.
15
+ * Returns entry count or 0 if RaBitQ is unavailable.
16
+ */
17
+ export declare function buildRabitqIndex(options?: {
18
+ dbPath?: string;
19
+ dimensions?: number;
20
+ force?: boolean;
21
+ }): Promise<{
22
+ success: boolean;
23
+ vectorCount: number;
24
+ dimensions: number;
25
+ compressionRatio: number;
26
+ buildTimeMs: number;
27
+ wasmVersion?: string;
28
+ error?: string;
29
+ }>;
30
+ /**
31
+ * Search the RaBitQ index for candidate IDs.
32
+ * Returns null if index not built or unavailable.
33
+ * Caller is responsible for reranking with exact similarity.
34
+ */
35
+ export declare function searchRabitq(queryEmbedding: number[], options?: {
36
+ k?: number;
37
+ namespace?: string;
38
+ }): Promise<Array<{
39
+ id: string;
40
+ key: string;
41
+ namespace: string;
42
+ distance: number;
43
+ position: number;
44
+ }> | null>;
45
+ /**
46
+ * Check if the RaBitQ index needs rebuilding.
47
+ */
48
+ export declare function shouldRebuildRabitq(currentEntryCount: number): Promise<boolean>;
49
+ /**
50
+ * Get RaBitQ index status.
51
+ */
52
+ export declare function getRabitqStatus(): {
53
+ available: boolean;
54
+ initialized: boolean;
55
+ vectorCount: number;
56
+ dimensions: number;
57
+ builtAt: number | null;
58
+ compressionRatio: number;
59
+ };
60
+ //# sourceMappingURL=rabitq-index.d.ts.map
@@ -0,0 +1,242 @@
1
+ /**
2
+ * RaBitQ Index — 1-bit quantized vector pre-filter (32× compression)
3
+ *
4
+ * Wraps @ruvector/rabitq-wasm to provide Hamming-scan pre-filtering
5
+ * over quantized embeddings. Candidates are reranked with exact cosine
6
+ * similarity from the full-precision source (HNSW or SQLite).
7
+ *
8
+ * Lifecycle:
9
+ * 1. build() — bulk-load all embeddings from SQLite into the WASM index
10
+ * 2. search() — fast Hamming scan → candidate ids → caller reranks
11
+ * 3. rebuild() — called when entry count drifts >20% from last build
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ const RABITQ_SEED = 42n;
16
+ const RABITQ_RERANK_FACTOR = 20;
17
+ const REBUILD_DRIFT_THRESHOLD = 0.2; // rebuild when count drifts >20%
18
+ let rabitqState = null;
19
+ let rabitqInitializing = false;
20
+ async function loadRabitqModule() {
21
+ try {
22
+ const mod = await import('@ruvector/rabitq-wasm');
23
+ // Node.js: use initSync with the WASM bytes
24
+ const { createRequire } = await import('module');
25
+ const require = createRequire(import.meta.url);
26
+ const wasmPath = require.resolve('@ruvector/rabitq-wasm/ruvector_rabitq_wasm_bg.wasm');
27
+ const wasmBytes = fs.readFileSync(wasmPath);
28
+ mod.initSync({ module: wasmBytes });
29
+ return {
30
+ RabitqIndex: mod.RabitqIndex,
31
+ initSync: mod.initSync,
32
+ version: mod.version,
33
+ };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Build or rebuild the RaBitQ index from SQLite embeddings.
41
+ * Returns entry count or 0 if RaBitQ is unavailable.
42
+ */
43
+ export async function buildRabitqIndex(options) {
44
+ if (rabitqInitializing) {
45
+ return { success: false, vectorCount: 0, dimensions: 0, compressionRatio: 0, buildTimeMs: 0, error: 'Build already in progress' };
46
+ }
47
+ rabitqInitializing = true;
48
+ const startTime = Date.now();
49
+ try {
50
+ const mod = await loadRabitqModule();
51
+ if (!mod) {
52
+ rabitqInitializing = false;
53
+ return { success: false, vectorCount: 0, dimensions: 0, compressionRatio: 0, buildTimeMs: 0, error: '@ruvector/rabitq-wasm not available' };
54
+ }
55
+ const dimensions = options?.dimensions ?? 384;
56
+ const swarmDir = path.resolve(process.cwd(), '.swarm');
57
+ const dbPath = options?.dbPath ? path.resolve(options.dbPath) : path.join(swarmDir, 'memory.db');
58
+ const entries = [];
59
+ const vectors = [];
60
+ // Try bridge first (reads via better-sqlite3, sees WAL data)
61
+ let usedBridge = false;
62
+ try {
63
+ const { bridgeGetAllEmbeddings } = await import('./memory-bridge.js');
64
+ const bridgeRows = await bridgeGetAllEmbeddings({ dimensions, dbPath: options?.dbPath });
65
+ if (bridgeRows && bridgeRows.length > 0) {
66
+ for (const row of bridgeRows) {
67
+ entries.push({ id: row.id, key: row.key, namespace: row.namespace });
68
+ vectors.push(...row.embedding);
69
+ }
70
+ usedBridge = true;
71
+ }
72
+ }
73
+ catch { /* bridge unavailable, fall through */ }
74
+ // Fallback: read .swarm/memory.db via sql.js
75
+ if (!usedBridge) {
76
+ if (!fs.existsSync(dbPath)) {
77
+ rabitqInitializing = false;
78
+ return { success: false, vectorCount: 0, dimensions, compressionRatio: 0, buildTimeMs: 0, error: 'Database not found' };
79
+ }
80
+ const initSqlJs = (await import('sql.js')).default;
81
+ const SQL = await initSqlJs();
82
+ const fileBuffer = fs.readFileSync(dbPath);
83
+ const db = new SQL.Database(fileBuffer);
84
+ const result = db.exec(`
85
+ SELECT id, key, namespace, embedding
86
+ FROM memory_entries
87
+ WHERE status = 'active' AND embedding IS NOT NULL
88
+ LIMIT 50000
89
+ `);
90
+ if (result[0]?.values) {
91
+ for (const row of result[0].values) {
92
+ const [id, key, ns, embeddingJson] = row;
93
+ if (!embeddingJson)
94
+ continue;
95
+ try {
96
+ const embedding = JSON.parse(embeddingJson);
97
+ if (embedding.length !== dimensions)
98
+ continue;
99
+ entries.push({ id: String(id), key: key || String(id), namespace: ns || 'default' });
100
+ vectors.push(...embedding);
101
+ }
102
+ catch {
103
+ // skip invalid
104
+ }
105
+ }
106
+ }
107
+ db.close();
108
+ }
109
+ if (entries.length < 2) {
110
+ rabitqInitializing = false;
111
+ return { success: false, vectorCount: entries.length, dimensions, compressionRatio: 0, buildTimeMs: Date.now() - startTime, error: 'Need at least 2 vectors to build RaBitQ index' };
112
+ }
113
+ // Build the RaBitQ index
114
+ const flatVectors = new Float32Array(vectors);
115
+ const index = mod.RabitqIndex.build(flatVectors, dimensions, RABITQ_SEED, RABITQ_RERANK_FACTOR);
116
+ // Free old index if exists
117
+ if (rabitqState?.index) {
118
+ try {
119
+ rabitqState.index.free();
120
+ }
121
+ catch { /* already freed */ }
122
+ }
123
+ rabitqState = {
124
+ index,
125
+ entries,
126
+ dimensions,
127
+ builtAt: Date.now(),
128
+ vectorCount: entries.length,
129
+ };
130
+ // Persist metadata for fast reload hint
131
+ try {
132
+ const metaPath = path.join(swarmDir, 'rabitq.meta.json');
133
+ fs.writeFileSync(metaPath, JSON.stringify({
134
+ vectorCount: entries.length,
135
+ dimensions,
136
+ builtAt: rabitqState.builtAt,
137
+ wasmVersion: mod.version(),
138
+ }));
139
+ }
140
+ catch { /* best-effort */ }
141
+ const rawBytes = entries.length * dimensions * 4; // f32 = 4 bytes
142
+ const quantizedBytes = entries.length * Math.ceil(dimensions / 8); // 1 bit per dim
143
+ const compressionRatio = rawBytes / Math.max(quantizedBytes, 1);
144
+ rabitqInitializing = false;
145
+ return {
146
+ success: true,
147
+ vectorCount: entries.length,
148
+ dimensions,
149
+ compressionRatio: Math.round(compressionRatio * 10) / 10,
150
+ buildTimeMs: Date.now() - startTime,
151
+ wasmVersion: mod.version(),
152
+ };
153
+ }
154
+ catch (error) {
155
+ rabitqInitializing = false;
156
+ return {
157
+ success: false,
158
+ vectorCount: 0,
159
+ dimensions: 0,
160
+ compressionRatio: 0,
161
+ buildTimeMs: Date.now() - startTime,
162
+ error: error instanceof Error ? error.message : String(error),
163
+ };
164
+ }
165
+ }
166
+ /**
167
+ * Search the RaBitQ index for candidate IDs.
168
+ * Returns null if index not built or unavailable.
169
+ * Caller is responsible for reranking with exact similarity.
170
+ */
171
+ export async function searchRabitq(queryEmbedding, options) {
172
+ if (!rabitqState?.index)
173
+ return null;
174
+ try {
175
+ const query = new Float32Array(queryEmbedding);
176
+ if (query.length !== rabitqState.dimensions)
177
+ return null;
178
+ const k = options?.k ?? 10;
179
+ // Get more candidates than needed for namespace filtering + rerank
180
+ const expandedK = Math.min(k * 3, rabitqState.vectorCount);
181
+ const rawResults = rabitqState.index.search(query, expandedK);
182
+ const results = [];
183
+ for (const hit of rawResults) {
184
+ const pos = hit.id; // row index from build()
185
+ const entry = rabitqState.entries[pos];
186
+ if (!entry)
187
+ continue;
188
+ // Namespace filter
189
+ if (options?.namespace && options.namespace !== 'all' && entry.namespace !== options.namespace) {
190
+ continue;
191
+ }
192
+ results.push({
193
+ id: entry.id,
194
+ key: entry.key,
195
+ namespace: entry.namespace,
196
+ distance: hit.distance,
197
+ position: pos,
198
+ });
199
+ // Free WASM SearchResult to prevent leak
200
+ try {
201
+ hit.free();
202
+ }
203
+ catch { /* already freed */ }
204
+ if (results.length >= k)
205
+ break;
206
+ }
207
+ // Free remaining SearchResults
208
+ for (const hit of rawResults) {
209
+ try {
210
+ hit.free();
211
+ }
212
+ catch { /* already freed or used */ }
213
+ }
214
+ return results;
215
+ }
216
+ catch {
217
+ return null;
218
+ }
219
+ }
220
+ /**
221
+ * Check if the RaBitQ index needs rebuilding.
222
+ */
223
+ export async function shouldRebuildRabitq(currentEntryCount) {
224
+ if (!rabitqState)
225
+ return currentEntryCount >= 10; // Build if we have enough vectors
226
+ const drift = Math.abs(currentEntryCount - rabitqState.vectorCount) / Math.max(rabitqState.vectorCount, 1);
227
+ return drift > REBUILD_DRIFT_THRESHOLD;
228
+ }
229
+ /**
230
+ * Get RaBitQ index status.
231
+ */
232
+ export function getRabitqStatus() {
233
+ return {
234
+ available: rabitqState !== null,
235
+ initialized: rabitqState !== null,
236
+ vectorCount: rabitqState?.vectorCount ?? 0,
237
+ dimensions: rabitqState?.dimensions ?? 384,
238
+ builtAt: rabitqState?.builtAt ?? null,
239
+ compressionRatio: rabitqState ? Math.round((rabitqState.dimensions * 4) / Math.ceil(rabitqState.dimensions / 8) * 10) / 10 : 0,
240
+ };
241
+ }
242
+ //# sourceMappingURL=rabitq-index.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.6.4",
3
+ "version": "3.6.6",
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",
@@ -97,23 +97,24 @@
97
97
  "@claude-flow/mcp": "^3.0.0-alpha.8",
98
98
  "@claude-flow/shared": "^3.0.0-alpha.7",
99
99
  "@noble/ed25519": "^2.1.0",
100
+ "@ruvector/rabitq-wasm": "^0.1.0",
100
101
  "semver": "^7.6.0"
101
102
  },
102
103
  "optionalDependencies": {
103
104
  "@claude-flow/aidefence": "^3.0.2",
104
- "@claude-flow/security": "^3.0.0-alpha.1",
105
105
  "@claude-flow/codex": "^3.0.0-alpha.8",
106
106
  "@claude-flow/embeddings": "^3.0.0-alpha.12",
107
107
  "@claude-flow/guidance": "^3.0.0-alpha.1",
108
108
  "@claude-flow/memory": "^3.0.0-alpha.12",
109
109
  "@claude-flow/plugin-gastown-bridge": "^0.1.3",
110
+ "@claude-flow/security": "^3.0.0-alpha.1",
110
111
  "@ruvector/attention": "^0.1.32",
111
112
  "@ruvector/attention-darwin-arm64": "0.1.32",
113
+ "@ruvector/diskann": "^0.1.0",
112
114
  "@ruvector/learning-wasm": "^0.1.29",
113
115
  "@ruvector/router": "^0.1.30",
114
116
  "@ruvector/ruvllm-wasm": "^2.0.2",
115
117
  "@ruvector/rvagent-wasm": "^0.1.0",
116
- "@ruvector/diskann": "^0.1.0",
117
118
  "@ruvector/sona": "^0.1.5",
118
119
  "agentdb": "^3.0.0-alpha.11",
119
120
  "agentic-flow": "^3.0.0-alpha.1"