viberag 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/useCommands.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -4
- package/dist/client/types.d.ts +6 -0
- package/dist/daemon/lib/chunker/index.js +32 -20
- package/dist/daemon/lib/telemetry/client.d.ts +1 -1
- package/dist/daemon/lib/telemetry/client.js +1 -1
- package/dist/daemon/lib/telemetry/privacy-policy.d.ts +1 -1
- package/dist/daemon/lib/telemetry/privacy-policy.js +1 -1
- package/dist/daemon/lib/telemetry/sentry.d.ts +10 -4
- package/dist/daemon/lib/telemetry/sentry.js +53 -3
- package/dist/daemon/lib/user-settings.js +1 -1
- package/dist/daemon/owner.d.ts +23 -0
- package/dist/daemon/owner.js +106 -2
- package/dist/daemon/services/memory-monitor-sentry.d.ts +32 -0
- package/dist/daemon/services/memory-monitor-sentry.js +200 -0
- package/dist/daemon/services/memory-monitor.d.ts +136 -0
- package/dist/daemon/services/memory-monitor.js +509 -0
- package/dist/daemon/services/v2/indexing.js +113 -46
- package/dist/daemon/services/v2/search/engine.js +138 -26
- package/dist/daemon/services/v2/storage/index.d.ts +1 -0
- package/dist/daemon/services/v2/storage/index.js +18 -0
- package/dist/mcp/server.js +3 -2
- package/package.json +9 -2
- package/scripts/memory/README.md +172 -0
- package/scripts/memory/isolate-intent-growth.mjs +348 -0
- package/scripts/memory/out/.gitkeep +1 -0
- package/scripts/memory/profile.mjs +188 -0
- package/scripts/memory/stress-search-growth.mjs +560 -0
- package/scripts/memory/watch-reindex-stress.mjs +683 -0
|
@@ -28,6 +28,7 @@ import { extractV2FromFile, } from './extract/extract.js';
|
|
|
28
28
|
import { StorageV2 } from './storage/index.js';
|
|
29
29
|
import { checkV2IndexCompatibility, loadV2Manifest, saveV2Manifest, V2ReindexRequiredError, V2_SCHEMA_VERSION, } from './manifest.js';
|
|
30
30
|
import { TypedEmitter, } from '../types.js';
|
|
31
|
+
const PERSIST_BATCH_SIZE = 500;
|
|
31
32
|
let globalV2IndexPromise = null;
|
|
32
33
|
export class IndexingServiceV2 extends TypedEmitter {
|
|
33
34
|
constructor(projectRoot, options) {
|
|
@@ -152,6 +153,9 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
152
153
|
const { force = false } = options;
|
|
153
154
|
this.suppressEvents = false;
|
|
154
155
|
const startTimeMs = Date.now();
|
|
156
|
+
const extracted = [];
|
|
157
|
+
const uniqueByHash = new Map();
|
|
158
|
+
const cached = new Map();
|
|
155
159
|
const stats = {
|
|
156
160
|
filesScanned: 0,
|
|
157
161
|
filesIndexed: 0,
|
|
@@ -288,7 +292,6 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
288
292
|
}
|
|
289
293
|
// Extract deterministic facts
|
|
290
294
|
this.emitIndexProgress('chunk', 'Extracting symbols/chunks/files', 0, filesToProcess.length, 'files');
|
|
291
|
-
const extracted = [];
|
|
292
295
|
let extractedFiles = 0;
|
|
293
296
|
for (const filePath of filesToProcess) {
|
|
294
297
|
throwIfAborted(this.abortSignal, 'Indexing cancelled');
|
|
@@ -312,54 +315,42 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
312
315
|
}
|
|
313
316
|
}
|
|
314
317
|
throwIfAborted(this.abortSignal, 'Indexing cancelled');
|
|
315
|
-
//
|
|
316
|
-
const
|
|
318
|
+
// Dedupe embedding surfaces by hash (cache key)
|
|
319
|
+
const addEmbedInput = (hash, text, meta) => {
|
|
320
|
+
if (!uniqueByHash.has(hash)) {
|
|
321
|
+
uniqueByHash.set(hash, { text, meta });
|
|
322
|
+
}
|
|
323
|
+
};
|
|
317
324
|
for (const item of extracted) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
startLine: 1,
|
|
324
|
-
endLine: 1,
|
|
325
|
-
size: item.file.embed_input.length,
|
|
326
|
-
},
|
|
325
|
+
addEmbedInput(item.file.embed_hash, item.file.embed_input, {
|
|
326
|
+
filepath: item.file.file_path,
|
|
327
|
+
startLine: 1,
|
|
328
|
+
endLine: 1,
|
|
329
|
+
size: item.file.embed_input.length,
|
|
327
330
|
});
|
|
328
331
|
for (const s of item.symbols) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
startLine: s.start_line,
|
|
335
|
-
endLine: s.end_line,
|
|
336
|
-
size: s.embed_input.length,
|
|
337
|
-
},
|
|
332
|
+
addEmbedInput(s.embed_hash, s.embed_input, {
|
|
333
|
+
filepath: s.file_path,
|
|
334
|
+
startLine: s.start_line,
|
|
335
|
+
endLine: s.end_line,
|
|
336
|
+
size: s.embed_input.length,
|
|
338
337
|
});
|
|
339
338
|
}
|
|
340
339
|
for (const c of item.chunks) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
startLine: c.start_line,
|
|
347
|
-
endLine: c.end_line,
|
|
348
|
-
size: c.embed_input.length,
|
|
349
|
-
},
|
|
340
|
+
addEmbedInput(c.embed_hash, c.embed_input, {
|
|
341
|
+
filepath: c.file_path,
|
|
342
|
+
startLine: c.start_line,
|
|
343
|
+
endLine: c.end_line,
|
|
344
|
+
size: c.embed_input.length,
|
|
350
345
|
});
|
|
351
346
|
}
|
|
352
347
|
}
|
|
353
|
-
// Dedupe by hash (cache key)
|
|
354
|
-
const uniqueByHash = new Map();
|
|
355
|
-
for (const item of embedItems) {
|
|
356
|
-
if (!uniqueByHash.has(item.hash)) {
|
|
357
|
-
uniqueByHash.set(item.hash, { text: item.text, meta: item.meta });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
348
|
const uniqueHashes = [...uniqueByHash.keys()];
|
|
361
349
|
this.emitIndexProgress('embed', 'Checking embedding cache', 0, uniqueHashes.length, 'chunks');
|
|
362
|
-
const
|
|
350
|
+
const cachedEmbeddings = await storage.getCachedEmbeddings(uniqueHashes);
|
|
351
|
+
for (const [hash, vector] of cachedEmbeddings) {
|
|
352
|
+
cached.set(hash, vector);
|
|
353
|
+
}
|
|
363
354
|
const cacheHits = uniqueHashes.filter(h => cached.has(h));
|
|
364
355
|
stats.embeddingsCached += cacheHits.length;
|
|
365
356
|
const misses = uniqueHashes.filter(h => !cached.has(h));
|
|
@@ -408,6 +399,8 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
408
399
|
this.emitIndexProgress('embed', 'Embedding surfaces', embeddedSoFar, uniqueHashes.length, 'chunks');
|
|
409
400
|
await Promise.all(batchTasks.map(task => batchLimit(task)));
|
|
410
401
|
}
|
|
402
|
+
// Release raw embedding inputs once vectors are available.
|
|
403
|
+
uniqueByHash.clear();
|
|
411
404
|
throwIfAborted(this.abortSignal, 'Indexing cancelled');
|
|
412
405
|
// Persist (upsert) rows
|
|
413
406
|
this.emitIndexProgress('persist', 'Writing tables', 0, 0, null);
|
|
@@ -415,6 +408,77 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
415
408
|
const symbolRows = [];
|
|
416
409
|
const chunkRows = [];
|
|
417
410
|
const refRows = [];
|
|
411
|
+
const clearPersistBatch = () => {
|
|
412
|
+
fileRows.length = 0;
|
|
413
|
+
symbolRows.length = 0;
|
|
414
|
+
chunkRows.length = 0;
|
|
415
|
+
refRows.length = 0;
|
|
416
|
+
};
|
|
417
|
+
const collectPersistBatchFilePaths = () => {
|
|
418
|
+
const paths = new Set();
|
|
419
|
+
const collect = (rows) => {
|
|
420
|
+
for (const row of rows) {
|
|
421
|
+
const filePath = row['file_path'];
|
|
422
|
+
if (typeof filePath === 'string') {
|
|
423
|
+
paths.add(filePath);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
collect(fileRows);
|
|
428
|
+
collect(symbolRows);
|
|
429
|
+
collect(chunkRows);
|
|
430
|
+
collect(refRows);
|
|
431
|
+
return [...paths];
|
|
432
|
+
};
|
|
433
|
+
const rollbackPersistBatch = async (filePaths) => {
|
|
434
|
+
if (filePaths.length === 0) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
this.log('warn', `Persist batch failed; rolling back ${filePaths.length} file(s).`);
|
|
438
|
+
for (const filePath of filePaths) {
|
|
439
|
+
try {
|
|
440
|
+
await storage.deleteAllRowsForFile(filePath);
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
444
|
+
this.log('warn', `Rollback failed for ${filePath}: ${message}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const flushPersistBatch = async (force = false) => {
|
|
449
|
+
// Keep persisted row buffers bounded so large repositories do not
|
|
450
|
+
// retain all rows in memory until the very end of indexing.
|
|
451
|
+
const shouldFlush = force ||
|
|
452
|
+
fileRows.length >= PERSIST_BATCH_SIZE ||
|
|
453
|
+
symbolRows.length >= PERSIST_BATCH_SIZE ||
|
|
454
|
+
chunkRows.length >= PERSIST_BATCH_SIZE ||
|
|
455
|
+
refRows.length >= PERSIST_BATCH_SIZE;
|
|
456
|
+
if (!shouldFlush)
|
|
457
|
+
return;
|
|
458
|
+
const fileCount = fileRows.length;
|
|
459
|
+
const symbolCount = symbolRows.length;
|
|
460
|
+
const chunkCount = chunkRows.length;
|
|
461
|
+
const refCount = refRows.length;
|
|
462
|
+
const batchFilePaths = collectPersistBatchFilePaths();
|
|
463
|
+
try {
|
|
464
|
+
await storage.upsertFiles(fileRows);
|
|
465
|
+
await storage.upsertSymbols(symbolRows);
|
|
466
|
+
await storage.upsertChunks(chunkRows);
|
|
467
|
+
await storage.upsertRefs(refRows);
|
|
468
|
+
stats.fileRowsUpserted += fileCount;
|
|
469
|
+
stats.symbolRowsUpserted += symbolCount;
|
|
470
|
+
stats.chunkRowsUpserted += chunkCount;
|
|
471
|
+
stats.refRowsUpserted += refCount;
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
await rollbackPersistBatch(batchFilePaths);
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
finally {
|
|
478
|
+
// Always release row buffers, even if persist fails mid-batch.
|
|
479
|
+
clearPersistBatch();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
418
482
|
for (const item of extracted) {
|
|
419
483
|
const vecFile = cached.get(item.file.embed_hash);
|
|
420
484
|
if (!vecFile) {
|
|
@@ -518,15 +582,12 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
518
582
|
imported_name: r.imported_name,
|
|
519
583
|
});
|
|
520
584
|
}
|
|
585
|
+
await flushPersistBatch();
|
|
521
586
|
}
|
|
522
|
-
await
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
stats.fileRowsUpserted += fileRows.length;
|
|
527
|
-
stats.symbolRowsUpserted += symbolRows.length;
|
|
528
|
-
stats.chunkRowsUpserted += chunkRows.length;
|
|
529
|
-
stats.refRowsUpserted += refRows.length;
|
|
587
|
+
await flushPersistBatch(true);
|
|
588
|
+
// Explicitly release large temporary structures before finalize phase.
|
|
589
|
+
cached.clear();
|
|
590
|
+
extracted.length = 0;
|
|
530
591
|
const totalSymbols = await storage.getSymbolsTable().countRows();
|
|
531
592
|
const totalChunks = await storage.getChunksTable().countRows();
|
|
532
593
|
const totalRefs = await storage.getRefsTable().countRows();
|
|
@@ -570,6 +631,12 @@ export class IndexingServiceV2 extends TypedEmitter {
|
|
|
570
631
|
});
|
|
571
632
|
throw error;
|
|
572
633
|
}
|
|
634
|
+
finally {
|
|
635
|
+
// Best-effort cleanup of large temporary structures for both success and failure paths.
|
|
636
|
+
uniqueByHash.clear();
|
|
637
|
+
cached.clear();
|
|
638
|
+
extracted.length = 0;
|
|
639
|
+
}
|
|
573
640
|
}
|
|
574
641
|
async initialize() {
|
|
575
642
|
if (this.config)
|
|
@@ -688,22 +688,31 @@ export class SearchEngineV2 {
|
|
|
688
688
|
this.getChunksTable(),
|
|
689
689
|
this.getSymbolsTable(),
|
|
690
690
|
]);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
691
|
+
const stringLiteralQuery = toStringLiteralExactTextQuery(query);
|
|
692
|
+
if (stringLiteralQuery) {
|
|
693
|
+
await ensureFtsIndex(chunksTable, 'string_literals', {
|
|
694
|
+
baseTokenizer: 'simple',
|
|
695
|
+
lowercase: true,
|
|
696
|
+
stem: false,
|
|
697
|
+
removeStopWords: false,
|
|
698
|
+
maxTokenLength: 256,
|
|
699
|
+
withPosition: true,
|
|
700
|
+
});
|
|
701
|
+
await ensureFtsIndex(symbolsTable, 'string_literals', {
|
|
702
|
+
baseTokenizer: 'simple',
|
|
703
|
+
lowercase: true,
|
|
704
|
+
stem: false,
|
|
705
|
+
removeStopWords: false,
|
|
706
|
+
maxTokenLength: 256,
|
|
707
|
+
withPosition: true,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// LanceDB string_literals FTS can exhibit pathological native-memory
|
|
712
|
+
// growth for broad multi-token queries at high limits. Fall back to
|
|
713
|
+
// code_text channels for these queries.
|
|
714
|
+
this.log('warn', 'Exact-text query has multiple tokens; skipping string_literals channel to avoid high-memory path.');
|
|
715
|
+
}
|
|
707
716
|
await ensureFtsIndex(chunksTable, 'code_text', {
|
|
708
717
|
baseTokenizer: 'simple',
|
|
709
718
|
lowercase: true,
|
|
@@ -722,8 +731,12 @@ export class SearchEngineV2 {
|
|
|
722
731
|
});
|
|
723
732
|
const oversample = Math.min(200, Math.max(k * 8, 40));
|
|
724
733
|
const [chunkStringHits, symbolStringHits, chunkHits, symbolHits] = await Promise.all([
|
|
725
|
-
|
|
726
|
-
|
|
734
|
+
stringLiteralQuery
|
|
735
|
+
? this.ftsCandidatesChunks(chunksTable, stringLiteralQuery, 'string_literals', oversample, filterClause, 'chunks.string_literals')
|
|
736
|
+
: Promise.resolve([]),
|
|
737
|
+
stringLiteralQuery
|
|
738
|
+
? this.ftsCandidatesSymbols(symbolsTable, stringLiteralQuery, 'string_literals', oversample, filterClause, 'symbols.string_literals')
|
|
739
|
+
: Promise.resolve([]),
|
|
727
740
|
this.ftsCandidatesChunks(chunksTable, query, 'code_text', oversample, filterClause, 'chunks.code_text'),
|
|
728
741
|
this.ftsCandidatesSymbols(symbolsTable, query, 'code_text', oversample, filterClause, 'symbols.code_text'),
|
|
729
742
|
]);
|
|
@@ -826,7 +839,21 @@ export class SearchEngineV2 {
|
|
|
826
839
|
return reranked.slice(0, k).map(c => toHit(c, explain));
|
|
827
840
|
}
|
|
828
841
|
async ftsCandidatesSymbols(table, query, column, limit, filterClause, source) {
|
|
829
|
-
let q = table
|
|
842
|
+
let q = table
|
|
843
|
+
.search(query, 'fts', column)
|
|
844
|
+
.limit(limit)
|
|
845
|
+
.select([
|
|
846
|
+
'symbol_id',
|
|
847
|
+
'file_path',
|
|
848
|
+
'start_line',
|
|
849
|
+
'end_line',
|
|
850
|
+
'qualname',
|
|
851
|
+
'symbol_name',
|
|
852
|
+
'signature',
|
|
853
|
+
'code_text',
|
|
854
|
+
'is_exported',
|
|
855
|
+
'_score',
|
|
856
|
+
]);
|
|
830
857
|
if (filterClause)
|
|
831
858
|
q = q.where(filterClause);
|
|
832
859
|
const rows = await q.toArray();
|
|
@@ -854,7 +881,21 @@ export class SearchEngineV2 {
|
|
|
854
881
|
});
|
|
855
882
|
}
|
|
856
883
|
async ftsCandidatesSymbolsFullTextQuery(table, query, limit, filterClause, source) {
|
|
857
|
-
let q = table
|
|
884
|
+
let q = table
|
|
885
|
+
.search(query, 'fts')
|
|
886
|
+
.limit(limit)
|
|
887
|
+
.select([
|
|
888
|
+
'symbol_id',
|
|
889
|
+
'file_path',
|
|
890
|
+
'start_line',
|
|
891
|
+
'end_line',
|
|
892
|
+
'qualname',
|
|
893
|
+
'symbol_name',
|
|
894
|
+
'signature',
|
|
895
|
+
'code_text',
|
|
896
|
+
'is_exported',
|
|
897
|
+
'_score',
|
|
898
|
+
]);
|
|
858
899
|
if (filterClause)
|
|
859
900
|
q = q.where(filterClause);
|
|
860
901
|
const rows = await q.toArray();
|
|
@@ -882,7 +923,18 @@ export class SearchEngineV2 {
|
|
|
882
923
|
});
|
|
883
924
|
}
|
|
884
925
|
async ftsCandidatesChunks(table, query, column, limit, filterClause, source) {
|
|
885
|
-
let q = table
|
|
926
|
+
let q = table
|
|
927
|
+
.search(query, 'fts', column)
|
|
928
|
+
.limit(limit)
|
|
929
|
+
.select([
|
|
930
|
+
'chunk_id',
|
|
931
|
+
'file_path',
|
|
932
|
+
'start_line',
|
|
933
|
+
'end_line',
|
|
934
|
+
'chunk_kind',
|
|
935
|
+
'code_text',
|
|
936
|
+
'_score',
|
|
937
|
+
]);
|
|
886
938
|
if (filterClause)
|
|
887
939
|
q = q.where(filterClause);
|
|
888
940
|
const rows = await q.toArray();
|
|
@@ -908,7 +960,10 @@ export class SearchEngineV2 {
|
|
|
908
960
|
});
|
|
909
961
|
}
|
|
910
962
|
async ftsCandidatesFiles(table, query, column, limit, filterClause, source) {
|
|
911
|
-
let q = table
|
|
963
|
+
let q = table
|
|
964
|
+
.search(query, 'fts', column)
|
|
965
|
+
.limit(limit)
|
|
966
|
+
.select(['file_id', 'file_path', 'file_summary_text', '_score']);
|
|
912
967
|
if (filterClause)
|
|
913
968
|
q = q.where(filterClause);
|
|
914
969
|
const rows = await q.toArray();
|
|
@@ -935,7 +990,23 @@ export class SearchEngineV2 {
|
|
|
935
990
|
});
|
|
936
991
|
}
|
|
937
992
|
async ftsCandidatesRefs(table, query, column, limit, filterClause, source) {
|
|
938
|
-
let q = table
|
|
993
|
+
let q = table
|
|
994
|
+
.search(query, 'fts', column)
|
|
995
|
+
.limit(limit)
|
|
996
|
+
.select([
|
|
997
|
+
'ref_id',
|
|
998
|
+
'file_path',
|
|
999
|
+
'start_line',
|
|
1000
|
+
'end_line',
|
|
1001
|
+
'start_byte',
|
|
1002
|
+
'end_byte',
|
|
1003
|
+
'ref_kind',
|
|
1004
|
+
'token_texts',
|
|
1005
|
+
'context_snippet',
|
|
1006
|
+
'module_name',
|
|
1007
|
+
'imported_name',
|
|
1008
|
+
'_score',
|
|
1009
|
+
]);
|
|
939
1010
|
if (filterClause)
|
|
940
1011
|
q = q.where(filterClause);
|
|
941
1012
|
const rows = await q.toArray();
|
|
@@ -977,7 +1048,22 @@ export class SearchEngineV2 {
|
|
|
977
1048
|
});
|
|
978
1049
|
}
|
|
979
1050
|
async vectorCandidatesSymbols(table, queryVector, limit, filterClause, source) {
|
|
980
|
-
let q = table
|
|
1051
|
+
let q = table
|
|
1052
|
+
.vectorSearch(queryVector)
|
|
1053
|
+
.column('vec_summary')
|
|
1054
|
+
.limit(limit)
|
|
1055
|
+
.select([
|
|
1056
|
+
'symbol_id',
|
|
1057
|
+
'file_path',
|
|
1058
|
+
'start_line',
|
|
1059
|
+
'end_line',
|
|
1060
|
+
'qualname',
|
|
1061
|
+
'symbol_name',
|
|
1062
|
+
'signature',
|
|
1063
|
+
'code_text',
|
|
1064
|
+
'is_exported',
|
|
1065
|
+
'_distance',
|
|
1066
|
+
]);
|
|
981
1067
|
if (filterClause)
|
|
982
1068
|
q = q.where(filterClause);
|
|
983
1069
|
const rows = await q.toArray();
|
|
@@ -1000,7 +1086,19 @@ export class SearchEngineV2 {
|
|
|
1000
1086
|
});
|
|
1001
1087
|
}
|
|
1002
1088
|
async vectorCandidatesChunks(table, queryVector, limit, filterClause, source) {
|
|
1003
|
-
let q = table
|
|
1089
|
+
let q = table
|
|
1090
|
+
.vectorSearch(queryVector)
|
|
1091
|
+
.column('vec_code')
|
|
1092
|
+
.limit(limit)
|
|
1093
|
+
.select([
|
|
1094
|
+
'chunk_id',
|
|
1095
|
+
'file_path',
|
|
1096
|
+
'start_line',
|
|
1097
|
+
'end_line',
|
|
1098
|
+
'chunk_kind',
|
|
1099
|
+
'code_text',
|
|
1100
|
+
'_distance',
|
|
1101
|
+
]);
|
|
1004
1102
|
if (filterClause)
|
|
1005
1103
|
q = q.where(filterClause);
|
|
1006
1104
|
const rows = await q.toArray();
|
|
@@ -1021,7 +1119,11 @@ export class SearchEngineV2 {
|
|
|
1021
1119
|
});
|
|
1022
1120
|
}
|
|
1023
1121
|
async vectorCandidatesFiles(table, queryVector, limit, filterClause, source) {
|
|
1024
|
-
let q = table
|
|
1122
|
+
let q = table
|
|
1123
|
+
.vectorSearch(queryVector)
|
|
1124
|
+
.column('vec_file')
|
|
1125
|
+
.limit(limit)
|
|
1126
|
+
.select(['file_id', 'file_path', 'file_summary_text', '_distance']);
|
|
1025
1127
|
if (filterClause)
|
|
1026
1128
|
q = q.where(filterClause);
|
|
1027
1129
|
const rows = await q.toArray();
|
|
@@ -1240,6 +1342,16 @@ function routeIntent(query) {
|
|
|
1240
1342
|
}
|
|
1241
1343
|
return 'concept';
|
|
1242
1344
|
}
|
|
1345
|
+
function toStringLiteralExactTextQuery(query) {
|
|
1346
|
+
const trimmed = query.trim();
|
|
1347
|
+
if (!trimmed)
|
|
1348
|
+
return null;
|
|
1349
|
+
// Multi-token exact-text queries at high limits can trigger pathological
|
|
1350
|
+
// native-memory growth in LanceDB string_literals FTS. Use string literal
|
|
1351
|
+
// channels only for focused single-token probes.
|
|
1352
|
+
const tokens = trimmed.split(/\s+/g).filter(Boolean);
|
|
1353
|
+
return tokens.length === 1 ? trimmed : null;
|
|
1354
|
+
}
|
|
1243
1355
|
function buildDefinitionFuzzyPlan(query) {
|
|
1244
1356
|
const normalized = normalizeDefinitionFuzzyQuery(query);
|
|
1245
1357
|
if (!normalized)
|
|
@@ -114,6 +114,14 @@ export class StorageV2 {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
close() {
|
|
117
|
+
// LanceDB tables/connection own native resources; close explicitly instead
|
|
118
|
+
// of relying on GC finalizers during long-lived daemon lifecycles.
|
|
119
|
+
this.safeClose(this.cacheTable);
|
|
120
|
+
this.safeClose(this.refsTable);
|
|
121
|
+
this.safeClose(this.filesTable);
|
|
122
|
+
this.safeClose(this.chunksTable);
|
|
123
|
+
this.safeClose(this.symbolsTable);
|
|
124
|
+
this.safeClose(this.db);
|
|
117
125
|
this.db = null;
|
|
118
126
|
this.symbolsTable = null;
|
|
119
127
|
this.chunksTable = null;
|
|
@@ -121,6 +129,16 @@ export class StorageV2 {
|
|
|
121
129
|
this.refsTable = null;
|
|
122
130
|
this.cacheTable = null;
|
|
123
131
|
}
|
|
132
|
+
safeClose(target) {
|
|
133
|
+
if (!target)
|
|
134
|
+
return;
|
|
135
|
+
try {
|
|
136
|
+
target.close();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Ignore close-time errors to avoid masking shutdown paths.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
124
142
|
getDb() {
|
|
125
143
|
if (!this.db) {
|
|
126
144
|
throw new Error('Database not connected. Call connect() first.');
|
package/dist/mcp/server.js
CHANGED
|
@@ -747,7 +747,7 @@ NOTE: Usually automatic via file watcher. Only call manually if needed.`,
|
|
|
747
747
|
}),
|
|
748
748
|
execute: async (args) => {
|
|
749
749
|
await ensureInitialized(projectRoot);
|
|
750
|
-
const stats = await client.index({ force: args.force });
|
|
750
|
+
const stats = await client.index({ force: args.force }, 180000);
|
|
751
751
|
return JSON.stringify(stats);
|
|
752
752
|
},
|
|
753
753
|
});
|
|
@@ -823,7 +823,7 @@ RETURNS:
|
|
|
823
823
|
- Initialization status and setup instructions (if not initialized)
|
|
824
824
|
- Index compatibility (may indicate reindex needed)
|
|
825
825
|
- Index stats: file/symbol/chunk counts
|
|
826
|
-
- Daemon status:
|
|
826
|
+
- Daemon status: memory snapshot, warmup progress, indexing state
|
|
827
827
|
|
|
828
828
|
CALL THIS FIRST if unsure whether VibeRAG is ready to use.`,
|
|
829
829
|
parameters: z.object({}),
|
|
@@ -989,6 +989,7 @@ NOTE: If telemetry is disabled, this is a no-op.`,
|
|
|
989
989
|
}
|
|
990
990
|
function formatDaemonStatusSummary(status) {
|
|
991
991
|
return {
|
|
992
|
+
memory: status.memory,
|
|
992
993
|
warmup: {
|
|
993
994
|
status: status.warmupStatus,
|
|
994
995
|
elapsedMs: status.warmupElapsedMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viberag",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Local code RAG for AI coding assistants - semantic search via MCP server",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"keywords": [
|
|
@@ -42,12 +42,19 @@
|
|
|
42
42
|
"dev": "tsc --watch",
|
|
43
43
|
"test": "prettier --check . && eslint . && npm run build && vitest run",
|
|
44
44
|
"test:fast": "vitest run --project=fast",
|
|
45
|
-
"test:
|
|
45
|
+
"test:daemon": "vitest run --project=daemon",
|
|
46
|
+
"test:rag": "npm run test:daemon",
|
|
47
|
+
"test:memory": "vitest run --project=daemon source/daemon/__tests__/*memory-regression.test.ts",
|
|
48
|
+
"test:windows-smoke": "vitest run --project=fast source/cli/__tests__/mcp-setup.test.ts source/cli/__tests__/mcp-setup-global.test.ts source/daemon/__tests__/grammar-smoke.test.ts source/daemon/__tests__/user-settings.test.ts source/mcp/__tests__/mcp-uninitialized-smoke.test.ts",
|
|
46
49
|
"test:smoke": "vitest run --testNamePattern='Grammar Smoke'",
|
|
47
50
|
"lint": "eslint .",
|
|
48
51
|
"lint:fix": "eslint . --fix",
|
|
49
52
|
"format": "prettier --write .",
|
|
50
53
|
"bake:telemetry": "node scripts/bake-telemetry-keys-local.js",
|
|
54
|
+
"memory:profile": "npm run build && node --expose-gc scripts/memory/profile.mjs",
|
|
55
|
+
"memory:isolate": "npm run build && node scripts/memory/isolate-intent-growth.mjs",
|
|
56
|
+
"memory:stress": "npm run build && node scripts/memory/stress-search-growth.mjs",
|
|
57
|
+
"memory:watch-stress": "npm run build && node scripts/memory/watch-reindex-stress.mjs",
|
|
51
58
|
"build:telemetry": "npm run build && npm run bake:telemetry",
|
|
52
59
|
"prepare": "husky",
|
|
53
60
|
"prepack": "npm run build && node scripts/bake-telemetry-keys.js",
|