@mhalder/qdrant-mcp-server 2.1.0 → 2.1.2

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +14 -2
  3. package/README.md +3 -2
  4. package/build/code/indexer.d.ts +5 -0
  5. package/build/code/indexer.d.ts.map +1 -1
  6. package/build/code/indexer.js +116 -14
  7. package/build/code/indexer.js.map +1 -1
  8. package/build/code/types.d.ts +4 -0
  9. package/build/code/types.d.ts.map +1 -1
  10. package/build/index.js +28 -831
  11. package/build/index.js.map +1 -1
  12. package/build/prompts/register.d.ts +10 -0
  13. package/build/prompts/register.d.ts.map +1 -0
  14. package/build/prompts/register.js +50 -0
  15. package/build/prompts/register.js.map +1 -0
  16. package/build/resources/index.d.ts +10 -0
  17. package/build/resources/index.d.ts.map +1 -0
  18. package/build/resources/index.js +60 -0
  19. package/build/resources/index.js.map +1 -0
  20. package/build/tools/code.d.ts +10 -0
  21. package/build/tools/code.d.ts.map +1 -0
  22. package/build/tools/code.js +132 -0
  23. package/build/tools/code.js.map +1 -0
  24. package/build/tools/collection.d.ts +12 -0
  25. package/build/tools/collection.d.ts.map +1 -0
  26. package/build/tools/collection.js +59 -0
  27. package/build/tools/collection.js.map +1 -0
  28. package/build/tools/document.d.ts +12 -0
  29. package/build/tools/document.d.ts.map +1 -0
  30. package/build/tools/document.js +84 -0
  31. package/build/tools/document.js.map +1 -0
  32. package/build/tools/index.d.ts +18 -0
  33. package/build/tools/index.d.ts.map +1 -0
  34. package/build/tools/index.js +30 -0
  35. package/build/tools/index.js.map +1 -0
  36. package/build/tools/schemas.d.ts +75 -0
  37. package/build/tools/schemas.d.ts.map +1 -0
  38. package/build/tools/schemas.js +114 -0
  39. package/build/tools/schemas.js.map +1 -0
  40. package/build/tools/search.d.ts +12 -0
  41. package/build/tools/search.d.ts.map +1 -0
  42. package/build/tools/search.js +79 -0
  43. package/build/tools/search.js.map +1 -0
  44. package/examples/code-search/README.md +19 -4
  45. package/package.json +1 -1
  46. package/src/code/indexer.ts +186 -38
  47. package/src/code/types.ts +5 -0
  48. package/src/index.ts +26 -983
  49. package/src/prompts/register.ts +71 -0
  50. package/src/resources/index.ts +79 -0
  51. package/src/tools/code.ts +195 -0
  52. package/src/tools/collection.ts +100 -0
  53. package/src/tools/document.ts +113 -0
  54. package/src/tools/index.ts +48 -0
  55. package/src/tools/schemas.ts +130 -0
  56. package/src/tools/search.ts +122 -0
  57. package/tests/code/indexer.test.ts +412 -74
  58. package/tests/code/integration.test.ts +239 -54
@@ -19,8 +19,8 @@ class MockQdrantManager implements Partial<QdrantManager> {
19
19
  async createCollection(
20
20
  name: string,
21
21
  vectorSize: number,
22
- distance: string,
23
- enableHybrid?: boolean
22
+ distance: "Cosine" | "Euclid" | "Dot" = "Cosine",
23
+ enableHybrid?: boolean,
24
24
  ): Promise<void> {
25
25
  this.collections.set(name, {
26
26
  vectorSize,
@@ -37,10 +37,16 @@ class MockQdrantManager implements Partial<QdrantManager> {
37
37
 
38
38
  async addPoints(collectionName: string, points: any[]): Promise<void> {
39
39
  const existing = this.points.get(collectionName) || [];
40
- this.points.set(collectionName, [...existing, ...points]);
40
+ // Upsert: remove existing points with same ID, then add new ones
41
+ const newIds = new Set(points.map((p) => p.id));
42
+ const filtered = existing.filter((p) => !newIds.has(p.id));
43
+ this.points.set(collectionName, [...filtered, ...points]);
41
44
  }
42
45
 
43
- async addPointsWithSparse(collectionName: string, points: any[]): Promise<void> {
46
+ async addPointsWithSparse(
47
+ collectionName: string,
48
+ points: any[],
49
+ ): Promise<void> {
44
50
  await this.addPoints(collectionName, points);
45
51
  }
46
52
 
@@ -48,7 +54,7 @@ class MockQdrantManager implements Partial<QdrantManager> {
48
54
  collectionName: string,
49
55
  _vector: number[],
50
56
  limit: number,
51
- filter?: any
57
+ filter?: any,
52
58
  ): Promise<any[]> {
53
59
  const points = this.points.get(collectionName) || [];
54
60
  let filtered = points;
@@ -57,7 +63,9 @@ class MockQdrantManager implements Partial<QdrantManager> {
57
63
  if (filter?.must) {
58
64
  for (const condition of filter.must) {
59
65
  if (condition.key === "fileExtension") {
60
- filtered = filtered.filter((p) => condition.match.any.includes(p.payload.fileExtension));
66
+ filtered = filtered.filter((p) =>
67
+ condition.match.any.includes(p.payload.fileExtension),
68
+ );
61
69
  }
62
70
  }
63
71
  }
@@ -74,7 +82,7 @@ class MockQdrantManager implements Partial<QdrantManager> {
74
82
  vector: number[],
75
83
  _sparseVector: any,
76
84
  limit: number,
77
- filter?: any
85
+ filter?: any,
78
86
  ): Promise<any[]> {
79
87
  // Hybrid search returns similar results with slight boost
80
88
  const results = await this.search(collectionName, vector, limit, filter);
@@ -90,6 +98,21 @@ class MockQdrantManager implements Partial<QdrantManager> {
90
98
  vectorSize: collection?.vectorSize || 384,
91
99
  };
92
100
  }
101
+
102
+ async getPoint(
103
+ collectionName: string,
104
+ id: string | number,
105
+ ): Promise<{ id: string | number; payload?: Record<string, any> } | null> {
106
+ const points = this.points.get(collectionName) || [];
107
+ const point = points.find((p) => p.id === id);
108
+ if (!point) {
109
+ return null;
110
+ }
111
+ return {
112
+ id: point.id,
113
+ payload: point.payload,
114
+ };
115
+ }
93
116
  }
94
117
 
95
118
  class MockEmbeddingProvider implements EmbeddingProvider {
@@ -97,14 +120,24 @@ class MockEmbeddingProvider implements EmbeddingProvider {
97
120
  return 384;
98
121
  }
99
122
 
100
- async embed(text: string): Promise<{ embedding: number[] }> {
123
+ getModel(): string {
124
+ return "mock-model";
125
+ }
126
+
127
+ async embed(
128
+ text: string,
129
+ ): Promise<{ embedding: number[]; dimensions: number }> {
101
130
  // Simple hash-based mock embedding
102
- const hash = text.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
131
+ const hash = text
132
+ .split("")
133
+ .reduce((acc, char) => acc + char.charCodeAt(0), 0);
103
134
  const base = (hash % 100) / 100;
104
- return { embedding: new Array(384).fill(base) };
135
+ return { embedding: new Array(384).fill(base), dimensions: 384 };
105
136
  }
106
137
 
107
- async embedBatch(texts: string[]): Promise<Array<{ embedding: number[] }>> {
138
+ async embedBatch(
139
+ texts: string[],
140
+ ): Promise<Array<{ embedding: number[]; dimensions: number }>> {
108
141
  return Promise.all(texts.map((text) => this.embed(text)));
109
142
  }
110
143
  }
@@ -120,7 +153,7 @@ describe("CodeIndexer Integration Tests", () => {
120
153
  beforeEach(async () => {
121
154
  tempDir = join(
122
155
  tmpdir(),
123
- `qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}`
156
+ `qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}`,
124
157
  );
125
158
  codebaseDir = join(tempDir, "codebase");
126
159
  await fs.mkdir(codebaseDir, { recursive: true });
@@ -161,7 +194,7 @@ export class AuthService {
161
194
  return { token: 'jwt-token' };
162
195
  }
163
196
  }
164
- `
197
+ `,
165
198
  );
166
199
 
167
200
  await createTestFile(
@@ -173,7 +206,7 @@ export class RegistrationService {
173
206
  return { id: '123', email: user.email };
174
207
  }
175
208
  }
176
- `
209
+ `,
177
210
  );
178
211
 
179
212
  await createTestFile(
@@ -183,7 +216,7 @@ export class RegistrationService {
183
216
  export function validateEmail(email: string): boolean {
184
217
  return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
185
218
  }
186
- `
219
+ `,
187
220
  );
188
221
 
189
222
  // Index the codebase
@@ -195,7 +228,10 @@ export function validateEmail(email: string): boolean {
195
228
  expect(indexStats.status).toBe("completed");
196
229
 
197
230
  // Search for authentication-related code
198
- const authResults = await indexer.searchCode(codebaseDir, "authentication login");
231
+ const authResults = await indexer.searchCode(
232
+ codebaseDir,
233
+ "authentication login",
234
+ );
199
235
 
200
236
  expect(authResults.length).toBeGreaterThan(0);
201
237
  expect(authResults[0].language).toBe("typescript");
@@ -215,7 +251,7 @@ export function validateEmail(email: string): boolean {
215
251
  import express from 'express';
216
252
  const app = express();
217
253
  app.listen(3000);
218
- `
254
+ `,
219
255
  );
220
256
 
221
257
  await createTestFile(
@@ -224,7 +260,7 @@ app.listen(3000);
224
260
  `
225
261
  const API_URL = 'http://localhost:3000';
226
262
  fetch(API_URL).then(res => res.json());
227
- `
263
+ `,
228
264
  );
229
265
 
230
266
  await createTestFile(
@@ -233,7 +269,7 @@ fetch(API_URL).then(res => res.json());
233
269
  `
234
270
  def process_data(data):
235
271
  return [x * 2 for x in data]
236
- `
272
+ `,
237
273
  );
238
274
 
239
275
  const stats = await indexer.indexCodebase(codebaseDir);
@@ -259,7 +295,7 @@ console.log('First file loaded successfully');
259
295
  function init(): string {
260
296
  console.log('Initializing system');
261
297
  return 'ready';
262
- }`
298
+ }`,
263
299
  );
264
300
  await createTestFile(
265
301
  codebaseDir,
@@ -269,7 +305,7 @@ console.log('Second file loaded successfully');
269
305
  function start(): string {
270
306
  console.log('Starting application');
271
307
  return 'started';
272
- }`
308
+ }`,
273
309
  );
274
310
 
275
311
  const initialStats = await indexer.indexCodebase(codebaseDir);
@@ -291,7 +327,7 @@ export function process(): string {
291
327
  return status;
292
328
  }
293
329
 
294
- export const thirdValue = 3;`
330
+ export const thirdValue = 3;`,
295
331
  );
296
332
 
297
333
  // Incremental update
@@ -310,7 +346,7 @@ export const thirdValue = 3;`
310
346
  await createTestFile(
311
347
  codebaseDir,
312
348
  "config.ts",
313
- "export const DEBUG_MODE = false;\nconsole.log('Debug mode off');"
349
+ "export const DEBUG_MODE = false;\nconsole.log('Debug mode off');",
314
350
  );
315
351
 
316
352
  await indexer.indexCodebase(codebaseDir);
@@ -319,7 +355,7 @@ export const thirdValue = 3;`
319
355
  await createTestFile(
320
356
  codebaseDir,
321
357
  "config.ts",
322
- "export const DEBUG_MODE = true;\nconsole.log('Debug mode on');"
358
+ "export const DEBUG_MODE = true;\nconsole.log('Debug mode on');",
323
359
  );
324
360
 
325
361
  const updateStats = await indexer.reindexChanges(codebaseDir);
@@ -332,12 +368,12 @@ export const thirdValue = 3;`
332
368
  await createTestFile(
333
369
  codebaseDir,
334
370
  "temp.ts",
335
- "export const tempValue = true;\nconsole.log('Temporary file created');\nfunction cleanup() { return null; }"
371
+ "export const tempValue = true;\nconsole.log('Temporary file created');\nfunction cleanup() { return null; }",
336
372
  );
337
373
  await createTestFile(
338
374
  codebaseDir,
339
375
  "keep.ts",
340
- "export const keepValue = true;\nconsole.log('Permanent file stays');\nfunction maintain() { return true; }"
376
+ "export const keepValue = true;\nconsole.log('Permanent file stays');\nfunction maintain() { return true; }",
341
377
  );
342
378
 
343
379
  await indexer.indexCodebase(codebaseDir);
@@ -356,17 +392,17 @@ export const thirdValue = 3;`
356
392
  await createTestFile(
357
393
  codebaseDir,
358
394
  "file1.ts",
359
- "export const alpha = 1;\nconsole.log('Alpha file');"
395
+ "export const alpha = 1;\nconsole.log('Alpha file');",
360
396
  );
361
397
  await createTestFile(
362
398
  codebaseDir,
363
399
  "file2.ts",
364
- "export const beta = 2;\nconsole.log('Beta file');"
400
+ "export const beta = 2;\nconsole.log('Beta file');",
365
401
  );
366
402
  await createTestFile(
367
403
  codebaseDir,
368
404
  "file3.ts",
369
- "export const gamma = 3;\nconsole.log('Gamma file');"
405
+ "export const gamma = 3;\nconsole.log('Gamma file');",
370
406
  );
371
407
 
372
408
  await indexer.indexCodebase(codebaseDir);
@@ -375,12 +411,12 @@ export const thirdValue = 3;`
375
411
  await createTestFile(
376
412
  codebaseDir,
377
413
  "file1.ts",
378
- "export const alpha = 100;\nconsole.log('Alpha modified');"
414
+ "export const alpha = 100;\nconsole.log('Alpha modified');",
379
415
  ); // Modified
380
416
  await createTestFile(
381
417
  codebaseDir,
382
418
  "file4.ts",
383
- "export const delta = 4;\nconsole.log('Delta file added');"
419
+ "export const delta = 4;\nconsole.log('Delta file added');",
384
420
  ); // Added
385
421
  await fs.unlink(join(codebaseDir, "file3.ts")); // Deleted
386
422
 
@@ -394,9 +430,21 @@ export const thirdValue = 3;`
394
430
 
395
431
  describe("Search filtering and options", () => {
396
432
  beforeEach(async () => {
397
- await createTestFile(codebaseDir, "users.ts", "export class UserService {}");
398
- await createTestFile(codebaseDir, "auth.ts", "export class AuthService {}");
399
- await createTestFile(codebaseDir, "utils.js", "export function helper() {}");
433
+ await createTestFile(
434
+ codebaseDir,
435
+ "users.ts",
436
+ "export class UserService {}",
437
+ );
438
+ await createTestFile(
439
+ codebaseDir,
440
+ "auth.ts",
441
+ "export class AuthService {}",
442
+ );
443
+ await createTestFile(
444
+ codebaseDir,
445
+ "utils.js",
446
+ "export function helper() {}",
447
+ );
400
448
  await createTestFile(codebaseDir, "data.py", "class DataProcessor: pass");
401
449
 
402
450
  await indexer.indexCodebase(codebaseDir);
@@ -431,7 +479,11 @@ export const thirdValue = 3;`
431
479
  });
432
480
 
433
481
  it("should support path pattern filtering", async () => {
434
- await createTestFile(codebaseDir, "src/api/endpoints.ts", "export const API = {}");
482
+ await createTestFile(
483
+ codebaseDir,
484
+ "src/api/endpoints.ts",
485
+ "export const API = {}",
486
+ );
435
487
  await indexer.indexCodebase(codebaseDir, { forceReindex: true });
436
488
 
437
489
  const results = await indexer.searchCode(codebaseDir, "export", {
@@ -445,24 +497,35 @@ export const thirdValue = 3;`
445
497
  describe("Hybrid search workflow", () => {
446
498
  it("should enable and use hybrid search", async () => {
447
499
  const hybridConfig = { ...config, enableHybridSearch: true };
448
- const hybridIndexer = new CodeIndexer(qdrant as any, embeddings, hybridConfig);
500
+ const hybridIndexer = new CodeIndexer(
501
+ qdrant as any,
502
+ embeddings,
503
+ hybridConfig,
504
+ );
449
505
 
450
506
  await createTestFile(
451
507
  codebaseDir,
452
508
  "search.ts",
453
- "function performSearch(query: string) { return results; }"
509
+ "function performSearch(query: string) { return results; }",
454
510
  );
455
511
 
456
512
  await hybridIndexer.indexCodebase(codebaseDir);
457
513
 
458
- const results = await hybridIndexer.searchCode(codebaseDir, "search query");
514
+ const results = await hybridIndexer.searchCode(
515
+ codebaseDir,
516
+ "search query",
517
+ );
459
518
 
460
519
  expect(results.length).toBeGreaterThan(0);
461
520
  });
462
521
 
463
522
  it("should fallback to standard search if hybrid not available", async () => {
464
523
  const hybridConfig = { ...config, enableHybridSearch: true };
465
- const hybridIndexer = new CodeIndexer(qdrant as any, embeddings, hybridConfig);
524
+ const hybridIndexer = new CodeIndexer(
525
+ qdrant as any,
526
+ embeddings,
527
+ hybridConfig,
528
+ );
466
529
 
467
530
  // Index without hybrid
468
531
  await createTestFile(
@@ -473,7 +536,7 @@ console.log('Test value configured successfully');
473
536
  function validate(): boolean {
474
537
  console.log('Validating test value');
475
538
  return testValue === true;
476
- }`
539
+ }`,
477
540
  );
478
541
  await indexer.indexCodebase(codebaseDir);
479
542
 
@@ -491,7 +554,7 @@ function validate(): boolean {
491
554
  await createTestFile(
492
555
  codebaseDir,
493
556
  `module${i}.ts`,
494
- `export function func${i}() { return ${i}; }`
557
+ `export function func${i}() { return ${i}; }`,
495
558
  );
496
559
  }
497
560
 
@@ -521,12 +584,12 @@ function validate(): boolean {
521
584
  await createTestFile(
522
585
  codebaseDir,
523
586
  "valid.ts",
524
- "export const validValue = true;\nconsole.log('Valid file');"
587
+ "export const validValue = true;\nconsole.log('Valid file');",
525
588
  );
526
589
  await createTestFile(
527
590
  codebaseDir,
528
591
  "secrets.ts",
529
- 'export const apiKey = "sk_test_FAKE_KEY_FOR_TESTING_NOT_REAL_KEY";\nconsole.log("Secrets file");'
592
+ 'export const apiKey = "sk_test_FAKE_KEY_FOR_TESTING_NOT_REAL_KEY";\nconsole.log("Secrets file");',
530
593
  );
531
594
 
532
595
  const stats = await indexer.indexCodebase(codebaseDir);
@@ -541,14 +604,16 @@ function validate(): boolean {
541
604
  await createTestFile(
542
605
  codebaseDir,
543
606
  "test.ts",
544
- "export const testData = true;\nconsole.log('Test data loaded');"
607
+ "export const testData = true;\nconsole.log('Test data loaded');",
545
608
  );
546
609
 
547
610
  const stats1 = await indexer.indexCodebase(codebaseDir);
548
611
  expect(stats1.status).toBe("completed");
549
612
 
550
613
  // Force re-index
551
- const stats2 = await indexer.indexCodebase(codebaseDir, { forceReindex: true });
614
+ const stats2 = await indexer.indexCodebase(codebaseDir, {
615
+ forceReindex: true,
616
+ });
552
617
  expect(stats2.status).toBe("completed");
553
618
  });
554
619
  });
@@ -561,11 +626,13 @@ function validate(): boolean {
561
626
 
562
627
  let status = await indexer.getIndexStatus(codebaseDir);
563
628
  expect(status.isIndexed).toBe(true);
629
+ expect(status.status).toBe("indexed");
564
630
 
565
631
  await indexer.clearIndex(codebaseDir);
566
632
 
567
633
  status = await indexer.getIndexStatus(codebaseDir);
568
634
  expect(status.isIndexed).toBe(false);
635
+ expect(status.status).toBe("not_indexed");
569
636
 
570
637
  // Re-index
571
638
  const stats = await indexer.indexCodebase(codebaseDir);
@@ -573,6 +640,120 @@ function validate(): boolean {
573
640
 
574
641
  status = await indexer.getIndexStatus(codebaseDir);
575
642
  expect(status.isIndexed).toBe(true);
643
+ expect(status.status).toBe("indexed");
644
+ });
645
+ });
646
+
647
+ describe("Index status states", () => {
648
+ it("should transition through all status states during indexing lifecycle", async () => {
649
+ // Initial state: not_indexed
650
+ let status = await indexer.getIndexStatus(codebaseDir);
651
+ expect(status.status).toBe("not_indexed");
652
+ expect(status.isIndexed).toBe(false);
653
+
654
+ // Create files and index
655
+ for (let i = 0; i < 3; i++) {
656
+ await createTestFile(
657
+ codebaseDir,
658
+ `component${i}.ts`,
659
+ `export class Component${i} {\n private value = ${i};\n render() {\n console.log('Rendering component ${i}');\n return this.value;\n }\n}`,
660
+ );
661
+ }
662
+
663
+ // Track status during indexing
664
+ let sawIndexingStatus = false;
665
+ await indexer.indexCodebase(codebaseDir, undefined, async (progress) => {
666
+ if (progress.phase === "embedding" && !sawIndexingStatus) {
667
+ const midStatus = await indexer.getIndexStatus(codebaseDir);
668
+ if (midStatus.status === "indexing") {
669
+ sawIndexingStatus = true;
670
+ }
671
+ }
672
+ });
673
+
674
+ // Final state: indexed
675
+ status = await indexer.getIndexStatus(codebaseDir);
676
+ expect(status.status).toBe("indexed");
677
+ expect(status.isIndexed).toBe(true);
678
+ expect(status.collectionName).toBeDefined();
679
+ expect(status.chunksCount).toBeGreaterThanOrEqual(0);
680
+ });
681
+
682
+ it("should include lastUpdated timestamp in indexed status", async () => {
683
+ await createTestFile(
684
+ codebaseDir,
685
+ "timestamped.ts",
686
+ "export const timestamp = Date.now();\nconsole.log('Timestamp test');\nfunction getTime() { return timestamp; }",
687
+ );
688
+
689
+ const beforeIndexing = new Date();
690
+ await indexer.indexCodebase(codebaseDir);
691
+ const afterIndexing = new Date();
692
+
693
+ const status = await indexer.getIndexStatus(codebaseDir);
694
+
695
+ expect(status.status).toBe("indexed");
696
+ expect(status.lastUpdated).toBeDefined();
697
+ expect(status.lastUpdated).toBeInstanceOf(Date);
698
+ expect(status.lastUpdated!.getTime()).toBeGreaterThanOrEqual(
699
+ beforeIndexing.getTime(),
700
+ );
701
+ expect(status.lastUpdated!.getTime()).toBeLessThanOrEqual(
702
+ afterIndexing.getTime(),
703
+ );
704
+ });
705
+
706
+ it("should correctly count chunks excluding metadata point", async () => {
707
+ // Create several files to generate multiple chunks
708
+ for (let i = 0; i < 5; i++) {
709
+ await createTestFile(
710
+ codebaseDir,
711
+ `service${i}.ts`,
712
+ `export class Service${i} {\n async process(input: string): Promise<string> {\n console.log('Processing in service ${i}:', input);\n const result = input.toUpperCase();\n return result;\n }\n async validate(data: any): Promise<boolean> {\n console.log('Validating in service ${i}');\n return data !== null;\n }\n}`,
713
+ );
714
+ }
715
+
716
+ const stats = await indexer.indexCodebase(codebaseDir);
717
+ const status = await indexer.getIndexStatus(codebaseDir);
718
+
719
+ // The chunks count in status should match what was indexed
720
+ // Note: The actual count depends on chunking algorithm, but should be consistent
721
+ expect(status.chunksCount).toBeDefined();
722
+ expect(typeof status.chunksCount).toBe("number");
723
+ // chunksCount should be close to chunksCreated (accounting for metadata point)
724
+ expect(status.chunksCount).toBeGreaterThanOrEqual(0);
725
+ });
726
+
727
+ it("should maintain indexed status after force reindex", async () => {
728
+ await createTestFile(
729
+ codebaseDir,
730
+ "reindexable.ts",
731
+ "export const version = 1;\nconsole.log('Version:', version);\nfunction getVersion() { return version; }",
732
+ );
733
+
734
+ // Initial index
735
+ await indexer.indexCodebase(codebaseDir);
736
+ let status = await indexer.getIndexStatus(codebaseDir);
737
+ expect(status.status).toBe("indexed");
738
+ const firstTimestamp = status.lastUpdated;
739
+
740
+ // Wait to ensure different timestamp
741
+ await new Promise((resolve) => setTimeout(resolve, 10));
742
+
743
+ // Force reindex
744
+ await indexer.indexCodebase(codebaseDir, { forceReindex: true });
745
+ status = await indexer.getIndexStatus(codebaseDir);
746
+
747
+ expect(status.status).toBe("indexed");
748
+ expect(status.isIndexed).toBe(true);
749
+ expect(status.lastUpdated).toBeDefined();
750
+
751
+ // Timestamp should be updated
752
+ if (firstTimestamp && status.lastUpdated) {
753
+ expect(status.lastUpdated.getTime()).toBeGreaterThanOrEqual(
754
+ firstTimestamp.getTime(),
755
+ );
756
+ }
576
757
  });
577
758
  });
578
759
 
@@ -582,7 +763,7 @@ function validate(): boolean {
582
763
  await createTestFile(
583
764
  codebaseDir,
584
765
  `file${i}.ts`,
585
- `export const value${i} = ${i};\nconsole.log('File ${i} loaded successfully');\nfunction process${i}() { return value${i} * 2; }`
766
+ `export const value${i} = ${i};\nconsole.log('File ${i} loaded successfully');\nfunction process${i}() { return value${i} * 2; }`,
586
767
  );
587
768
  }
588
769
 
@@ -603,14 +784,14 @@ function validate(): boolean {
603
784
  await createTestFile(
604
785
  codebaseDir,
605
786
  "file1.ts",
606
- "export const initial = 1;\nconsole.log('Initial file');"
787
+ "export const initial = 1;\nconsole.log('Initial file');",
607
788
  );
608
789
  await indexer.indexCodebase(codebaseDir);
609
790
 
610
791
  await createTestFile(
611
792
  codebaseDir,
612
793
  "file2.ts",
613
- "export const additional = 2;\nconsole.log('Additional file');"
794
+ "export const additional = 2;\nconsole.log('Additional file');",
614
795
  );
615
796
 
616
797
  const progressUpdates: string[] = [];
@@ -627,13 +808,17 @@ function validate(): boolean {
627
808
  describe("Hybrid search with incremental updates", () => {
628
809
  it("should use hybrid search during reindexChanges", async () => {
629
810
  const hybridConfig = { ...config, enableHybridSearch: true };
630
- const hybridIndexer = new CodeIndexer(qdrant as any, embeddings, hybridConfig);
811
+ const hybridIndexer = new CodeIndexer(
812
+ qdrant as any,
813
+ embeddings,
814
+ hybridConfig,
815
+ );
631
816
 
632
817
  // Initial indexing with hybrid search
633
818
  await createTestFile(
634
819
  codebaseDir,
635
820
  "initial.ts",
636
- "export const initial = 1;\nconsole.log('Initial file');"
821
+ "export const initial = 1;\nconsole.log('Initial file');",
637
822
  );
638
823
  await hybridIndexer.indexCodebase(codebaseDir);
639
824
 
@@ -657,7 +842,7 @@ export class DataProcessor {
657
842
  process(input: string): string {
658
843
  return input.trim();
659
844
  }
660
- }`
845
+ }`,
661
846
  );
662
847
 
663
848
  // Reindex with hybrid search - this should cover lines 540-545
@@ -674,7 +859,7 @@ export class DataProcessor {
674
859
  await createTestFile(
675
860
  codebaseDir,
676
861
  "file1.ts",
677
- "export const value = 1;\nconsole.log('File 1');"
862
+ "export const value = 1;\nconsole.log('File 1');",
678
863
  );
679
864
  await indexer.indexCodebase(codebaseDir);
680
865
 
@@ -682,7 +867,7 @@ export class DataProcessor {
682
867
  await createTestFile(
683
868
  codebaseDir,
684
869
  "file2.ts",
685
- "export const value2 = 2;\nconsole.log('File 2');"
870
+ "export const value2 = 2;\nconsole.log('File 2');",
686
871
  );
687
872
 
688
873
  // This should not throw even if there are processing errors
@@ -699,7 +884,7 @@ export class DataProcessor {
699
884
  async function createTestFile(
700
885
  baseDir: string,
701
886
  relativePath: string,
702
- content: string
887
+ content: string,
703
888
  ): Promise<void> {
704
889
  const fullPath = join(baseDir, relativePath);
705
890
  const dir = join(fullPath, "..");