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.
@@ -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
- // Embed all surfaces (cached by embed_hash)
316
- const embedItems = [];
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
- embedItems.push({
319
- hash: item.file.embed_hash,
320
- text: item.file.embed_input,
321
- meta: {
322
- filepath: item.file.file_path,
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
- embedItems.push({
330
- hash: s.embed_hash,
331
- text: s.embed_input,
332
- meta: {
333
- filepath: s.file_path,
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
- embedItems.push({
342
- hash: c.embed_hash,
343
- text: c.embed_input,
344
- meta: {
345
- filepath: c.file_path,
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 cached = await storage.getCachedEmbeddings(uniqueHashes);
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 storage.upsertFiles(fileRows);
523
- await storage.upsertSymbols(symbolRows);
524
- await storage.upsertChunks(chunkRows);
525
- await storage.upsertRefs(refRows);
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
- await ensureFtsIndex(chunksTable, 'string_literals', {
692
- baseTokenizer: 'simple',
693
- lowercase: true,
694
- stem: false,
695
- removeStopWords: false,
696
- maxTokenLength: 256,
697
- withPosition: true,
698
- });
699
- await ensureFtsIndex(symbolsTable, 'string_literals', {
700
- baseTokenizer: 'simple',
701
- lowercase: true,
702
- stem: false,
703
- removeStopWords: false,
704
- maxTokenLength: 256,
705
- withPosition: true,
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
- this.ftsCandidatesChunks(chunksTable, query, 'string_literals', oversample, filterClause, 'chunks.string_literals'),
726
- this.ftsCandidatesSymbols(symbolsTable, query, 'string_literals', oversample, filterClause, 'symbols.string_literals'),
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.search(query, 'fts', column).limit(limit);
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.search(query, 'fts').limit(limit);
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.search(query, 'fts', column).limit(limit);
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.search(query, 'fts', column).limit(limit);
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.search(query, 'fts', column).limit(limit);
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.vectorSearch(queryVector).column('vec_summary').limit(limit);
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.vectorSearch(queryVector).column('vec_code').limit(limit);
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.vectorSearch(queryVector).column('vec_file').limit(limit);
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)
@@ -27,6 +27,7 @@ export declare class StorageV2 {
27
27
  private openOrCreateVectorTable;
28
28
  private getTableVectorDimensions;
29
29
  close(): void;
30
+ private safeClose;
30
31
  private getDb;
31
32
  getSymbolsTable(): Table;
32
33
  getChunksTable(): Table;
@@ -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.');
@@ -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: running, warmup progress, indexing state
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.7.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:rag": "vitest run --project=rag",
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",