@mhalder/qdrant-mcp-server 1.4.0 → 1.6.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.
Files changed (92) hide show
  1. package/.codecov.yml +16 -0
  2. package/.github/workflows/claude-code-review.yml +6 -5
  3. package/.releaserc.json +8 -1
  4. package/CHANGELOG.md +34 -0
  5. package/README.md +259 -9
  6. package/build/code/chunker/base.d.ts +19 -0
  7. package/build/code/chunker/base.d.ts.map +1 -0
  8. package/build/code/chunker/base.js +5 -0
  9. package/build/code/chunker/base.js.map +1 -0
  10. package/build/code/chunker/character-chunker.d.ts +22 -0
  11. package/build/code/chunker/character-chunker.d.ts.map +1 -0
  12. package/build/code/chunker/character-chunker.js +111 -0
  13. package/build/code/chunker/character-chunker.js.map +1 -0
  14. package/build/code/chunker/tree-sitter-chunker.d.ts +29 -0
  15. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -0
  16. package/build/code/chunker/tree-sitter-chunker.js +213 -0
  17. package/build/code/chunker/tree-sitter-chunker.js.map +1 -0
  18. package/build/code/config.d.ts +11 -0
  19. package/build/code/config.d.ts.map +1 -0
  20. package/build/code/config.js +145 -0
  21. package/build/code/config.js.map +1 -0
  22. package/build/code/indexer.d.ts +42 -0
  23. package/build/code/indexer.d.ts.map +1 -0
  24. package/build/code/indexer.js +508 -0
  25. package/build/code/indexer.js.map +1 -0
  26. package/build/code/metadata.d.ts +32 -0
  27. package/build/code/metadata.d.ts.map +1 -0
  28. package/build/code/metadata.js +128 -0
  29. package/build/code/metadata.js.map +1 -0
  30. package/build/code/scanner.d.ts +35 -0
  31. package/build/code/scanner.d.ts.map +1 -0
  32. package/build/code/scanner.js +108 -0
  33. package/build/code/scanner.js.map +1 -0
  34. package/build/code/sync/merkle.d.ts +45 -0
  35. package/build/code/sync/merkle.d.ts.map +1 -0
  36. package/build/code/sync/merkle.js +116 -0
  37. package/build/code/sync/merkle.js.map +1 -0
  38. package/build/code/sync/snapshot.d.ts +41 -0
  39. package/build/code/sync/snapshot.d.ts.map +1 -0
  40. package/build/code/sync/snapshot.js +91 -0
  41. package/build/code/sync/snapshot.js.map +1 -0
  42. package/build/code/sync/synchronizer.d.ts +53 -0
  43. package/build/code/sync/synchronizer.d.ts.map +1 -0
  44. package/build/code/sync/synchronizer.js +132 -0
  45. package/build/code/sync/synchronizer.js.map +1 -0
  46. package/build/code/types.d.ts +98 -0
  47. package/build/code/types.d.ts.map +1 -0
  48. package/build/code/types.js +5 -0
  49. package/build/code/types.js.map +1 -0
  50. package/build/index.js +252 -1
  51. package/build/index.js.map +1 -1
  52. package/build/qdrant/client.d.ts +1 -1
  53. package/build/qdrant/client.d.ts.map +1 -1
  54. package/build/qdrant/client.js +2 -2
  55. package/build/qdrant/client.js.map +1 -1
  56. package/build/qdrant/client.test.js +16 -0
  57. package/build/qdrant/client.test.js.map +1 -1
  58. package/examples/code-search/README.md +271 -0
  59. package/package.json +15 -2
  60. package/src/code/chunker/base.ts +22 -0
  61. package/src/code/chunker/character-chunker.ts +131 -0
  62. package/src/code/chunker/tree-sitter-chunker.ts +250 -0
  63. package/src/code/config.ts +156 -0
  64. package/src/code/indexer.ts +613 -0
  65. package/src/code/metadata.ts +153 -0
  66. package/src/code/scanner.ts +124 -0
  67. package/src/code/sync/merkle.ts +136 -0
  68. package/src/code/sync/snapshot.ts +110 -0
  69. package/src/code/sync/synchronizer.ts +154 -0
  70. package/src/code/types.ts +117 -0
  71. package/src/index.ts +298 -1
  72. package/src/qdrant/client.test.ts +20 -0
  73. package/src/qdrant/client.ts +2 -2
  74. package/tests/code/chunker/character-chunker.test.ts +141 -0
  75. package/tests/code/chunker/tree-sitter-chunker.test.ts +275 -0
  76. package/tests/code/fixtures/sample-py/calculator.py +32 -0
  77. package/tests/code/fixtures/sample-ts/async-operations.ts +120 -0
  78. package/tests/code/fixtures/sample-ts/auth.ts +31 -0
  79. package/tests/code/fixtures/sample-ts/config.ts +52 -0
  80. package/tests/code/fixtures/sample-ts/database.ts +50 -0
  81. package/tests/code/fixtures/sample-ts/index.ts +39 -0
  82. package/tests/code/fixtures/sample-ts/types-advanced.ts +132 -0
  83. package/tests/code/fixtures/sample-ts/utils.ts +105 -0
  84. package/tests/code/fixtures/sample-ts/validator.ts +169 -0
  85. package/tests/code/indexer.test.ts +828 -0
  86. package/tests/code/integration.test.ts +708 -0
  87. package/tests/code/metadata.test.ts +457 -0
  88. package/tests/code/scanner.test.ts +131 -0
  89. package/tests/code/sync/merkle.test.ts +406 -0
  90. package/tests/code/sync/snapshot.test.ts +360 -0
  91. package/tests/code/sync/synchronizer.test.ts +501 -0
  92. package/vitest.config.ts +1 -0
@@ -0,0 +1,501 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { homedir, tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { FileSynchronizer } from "../../../src/code/sync/synchronizer.js";
6
+
7
+ describe("FileSynchronizer", () => {
8
+ let tempDir: string;
9
+ let codebaseDir: string;
10
+ let synchronizer: FileSynchronizer;
11
+ let collectionName: string;
12
+
13
+ beforeEach(async () => {
14
+ // Create temporary directories for testing
15
+ tempDir = join(
16
+ tmpdir(),
17
+ `qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}`
18
+ );
19
+ codebaseDir = join(tempDir, "codebase");
20
+ await fs.mkdir(codebaseDir, { recursive: true });
21
+
22
+ // Use unique collection name to avoid conflicts between test runs
23
+ collectionName = `test-collection-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
24
+ synchronizer = new FileSynchronizer(codebaseDir, collectionName);
25
+ });
26
+
27
+ afterEach(async () => {
28
+ // Clean up temporary directory
29
+ try {
30
+ await fs.rm(tempDir, { recursive: true, force: true });
31
+ } catch (_error) {
32
+ // Ignore cleanup errors
33
+ }
34
+
35
+ // Clean up snapshot file
36
+ try {
37
+ const snapshotPath = join(homedir(), ".qdrant-mcp", "snapshots", `${collectionName}.json`);
38
+ await fs.rm(snapshotPath, { force: true });
39
+ } catch (_error) {
40
+ // Ignore cleanup errors
41
+ }
42
+ });
43
+
44
+ describe("initialize", () => {
45
+ it("should return false when no snapshot exists", async () => {
46
+ const hasSnapshot = await synchronizer.initialize();
47
+ expect(hasSnapshot).toBe(false);
48
+ });
49
+
50
+ it("should return true when snapshot exists", async () => {
51
+ // Create some files and update snapshot
52
+ await createFile(codebaseDir, "file1.ts", "content1");
53
+ await synchronizer.updateSnapshot(["file1.ts"]);
54
+
55
+ // Create a new synchronizer and initialize
56
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
57
+ const hasSnapshot = await newSync.initialize();
58
+
59
+ expect(hasSnapshot).toBe(true);
60
+ });
61
+
62
+ it("should load previous file hashes", async () => {
63
+ await createFile(codebaseDir, "file1.ts", "content1");
64
+ await createFile(codebaseDir, "file2.ts", "content2");
65
+
66
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
67
+
68
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
69
+ await newSync.initialize();
70
+
71
+ const changes = await newSync.detectChanges(["file1.ts", "file2.ts"]);
72
+
73
+ expect(changes.added).toEqual([]);
74
+ expect(changes.modified).toEqual([]);
75
+ expect(changes.deleted).toEqual([]);
76
+ });
77
+ });
78
+
79
+ describe("updateSnapshot", () => {
80
+ it("should save snapshot with file hashes", async () => {
81
+ await createFile(codebaseDir, "file1.ts", "content1");
82
+ await createFile(codebaseDir, "file2.ts", "content2");
83
+
84
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
85
+
86
+ const newSync = new FileSynchronizer(codebaseDir, collectionName, tempDir);
87
+ const hasSnapshot = await newSync.initialize();
88
+
89
+ expect(hasSnapshot).toBe(true);
90
+ });
91
+
92
+ it("should handle empty file list", async () => {
93
+ await synchronizer.updateSnapshot([]);
94
+
95
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
96
+ const hasSnapshot = await newSync.initialize();
97
+
98
+ expect(hasSnapshot).toBe(true);
99
+ });
100
+
101
+ it("should overwrite previous snapshot", async () => {
102
+ await createFile(codebaseDir, "file1.ts", "content1");
103
+ await synchronizer.updateSnapshot(["file1.ts"]);
104
+
105
+ await createFile(codebaseDir, "file2.ts", "content2");
106
+ await synchronizer.updateSnapshot(["file2.ts"]);
107
+
108
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
109
+ await newSync.initialize();
110
+
111
+ const changes = await newSync.detectChanges(["file2.ts"]);
112
+
113
+ expect(changes.added).toEqual([]);
114
+ expect(changes.modified).toEqual([]);
115
+ expect(changes.deleted).toEqual([]);
116
+ });
117
+
118
+ it("should handle relative and absolute paths", async () => {
119
+ await createFile(codebaseDir, "file.ts", "content");
120
+
121
+ await synchronizer.updateSnapshot(["file.ts"]);
122
+ await synchronizer.updateSnapshot([join(codebaseDir, "file.ts")]);
123
+
124
+ // Both should work without duplicates
125
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
126
+ await newSync.initialize();
127
+
128
+ const changes = await newSync.detectChanges(["file.ts"]);
129
+ expect(changes.modified).toEqual([]);
130
+ });
131
+ });
132
+
133
+ describe("detectChanges", () => {
134
+ it("should detect added files", async () => {
135
+ await synchronizer.initialize();
136
+ await synchronizer.updateSnapshot([]);
137
+
138
+ await createFile(codebaseDir, "file1.ts", "content1");
139
+ await createFile(codebaseDir, "file2.ts", "content2");
140
+
141
+ const changes = await synchronizer.detectChanges(["file1.ts", "file2.ts"]);
142
+
143
+ expect(changes.added).toContain("file1.ts");
144
+ expect(changes.added).toContain("file2.ts");
145
+ expect(changes.modified).toEqual([]);
146
+ expect(changes.deleted).toEqual([]);
147
+ });
148
+
149
+ it("should detect deleted files", async () => {
150
+ await createFile(codebaseDir, "file1.ts", "content1");
151
+ await createFile(codebaseDir, "file2.ts", "content2");
152
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
153
+
154
+ await fs.unlink(join(codebaseDir, "file2.ts"));
155
+
156
+ const changes = await synchronizer.detectChanges(["file1.ts"]);
157
+
158
+ expect(changes.added).toEqual([]);
159
+ expect(changes.modified).toEqual([]);
160
+ expect(changes.deleted).toEqual(["file2.ts"]);
161
+ });
162
+
163
+ it("should detect modified files", async () => {
164
+ await createFile(codebaseDir, "file1.ts", "original content");
165
+ await synchronizer.updateSnapshot(["file1.ts"]);
166
+
167
+ await createFile(codebaseDir, "file1.ts", "modified content");
168
+
169
+ const changes = await synchronizer.detectChanges(["file1.ts"]);
170
+
171
+ expect(changes.added).toEqual([]);
172
+ expect(changes.modified).toEqual(["file1.ts"]);
173
+ expect(changes.deleted).toEqual([]);
174
+ });
175
+
176
+ it("should detect mixed changes", async () => {
177
+ await createFile(codebaseDir, "file1.ts", "content1");
178
+ await createFile(codebaseDir, "file2.ts", "content2");
179
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
180
+
181
+ // Modify file1
182
+ await createFile(codebaseDir, "file1.ts", "modified content1");
183
+
184
+ // Add file3
185
+ await createFile(codebaseDir, "file3.ts", "content3");
186
+
187
+ // Delete file2
188
+
189
+ const changes = await synchronizer.detectChanges(["file1.ts", "file3.ts"]);
190
+
191
+ expect(changes.added).toEqual(["file3.ts"]);
192
+ expect(changes.modified).toEqual(["file1.ts"]);
193
+ expect(changes.deleted).toEqual(["file2.ts"]);
194
+ });
195
+
196
+ it("should detect no changes when files unchanged", async () => {
197
+ await createFile(codebaseDir, "file1.ts", "content1");
198
+ await synchronizer.updateSnapshot(["file1.ts"]);
199
+
200
+ const changes = await synchronizer.detectChanges(["file1.ts"]);
201
+
202
+ expect(changes.added).toEqual([]);
203
+ expect(changes.modified).toEqual([]);
204
+ expect(changes.deleted).toEqual([]);
205
+ });
206
+
207
+ it("should consider all files as added when no snapshot exists", async () => {
208
+ await createFile(codebaseDir, "file1.ts", "content1");
209
+ await createFile(codebaseDir, "file2.ts", "content2");
210
+
211
+ const changes = await synchronizer.detectChanges(["file1.ts", "file2.ts"]);
212
+
213
+ expect(changes.added).toContain("file1.ts");
214
+ expect(changes.added).toContain("file2.ts");
215
+ expect(changes.modified).toEqual([]);
216
+ expect(changes.deleted).toEqual([]);
217
+ });
218
+ });
219
+
220
+ describe("deleteSnapshot", () => {
221
+ it("should delete existing snapshot", async () => {
222
+ await createFile(codebaseDir, "file.ts", "content");
223
+ await synchronizer.updateSnapshot(["file.ts"]);
224
+
225
+ await synchronizer.deleteSnapshot();
226
+
227
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
228
+ const hasSnapshot = await newSync.initialize();
229
+
230
+ expect(hasSnapshot).toBe(false);
231
+ });
232
+
233
+ it("should handle deleting non-existent snapshot", async () => {
234
+ await expect(synchronizer.deleteSnapshot()).resolves.not.toThrow();
235
+ });
236
+
237
+ it("should allow operations after deletion", async () => {
238
+ await createFile(codebaseDir, "file1.ts", "content1");
239
+ await synchronizer.updateSnapshot(["file1.ts"]);
240
+
241
+ await synchronizer.deleteSnapshot();
242
+
243
+ await createFile(codebaseDir, "file2.ts", "content2");
244
+ await synchronizer.updateSnapshot(["file2.ts"]);
245
+
246
+ const newSync = new FileSynchronizer(codebaseDir, collectionName);
247
+ const hasSnapshot = await newSync.initialize();
248
+
249
+ expect(hasSnapshot).toBe(true);
250
+ });
251
+ });
252
+
253
+ describe("edge cases", () => {
254
+ it("should handle files with same content different names", async () => {
255
+ const sameContent = "identical content";
256
+ await createFile(codebaseDir, "file1.ts", sameContent);
257
+ await createFile(codebaseDir, "file2.ts", sameContent);
258
+
259
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
260
+
261
+ const changes = await synchronizer.detectChanges(["file1.ts", "file2.ts"]);
262
+
263
+ expect(changes.added).toEqual([]);
264
+ expect(changes.modified).toEqual([]);
265
+ expect(changes.deleted).toEqual([]);
266
+ });
267
+
268
+ it("should handle binary files", async () => {
269
+ const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0xff]);
270
+ await fs.writeFile(join(codebaseDir, "binary.dat"), binaryContent);
271
+
272
+ await synchronizer.updateSnapshot(["binary.dat"]);
273
+
274
+ const changes = await synchronizer.detectChanges(["binary.dat"]);
275
+
276
+ expect(changes.modified).toEqual([]);
277
+ });
278
+
279
+ it("should handle very large files", async () => {
280
+ const largeContent = "x".repeat(1024 * 1024); // 1MB
281
+ await createFile(codebaseDir, "large.ts", largeContent);
282
+
283
+ await synchronizer.updateSnapshot(["large.ts"]);
284
+
285
+ const changes = await synchronizer.detectChanges(["large.ts"]);
286
+
287
+ expect(changes.modified).toEqual([]);
288
+ });
289
+
290
+ it("should handle files with special characters in names", async () => {
291
+ await createFile(codebaseDir, "file with spaces.ts", "content");
292
+ await createFile(codebaseDir, "file-with-dashes.ts", "content");
293
+ await createFile(codebaseDir, "file_with_underscores.ts", "content");
294
+
295
+ await synchronizer.updateSnapshot([
296
+ "file with spaces.ts",
297
+ "file-with-dashes.ts",
298
+ "file_with_underscores.ts",
299
+ ]);
300
+
301
+ const changes = await synchronizer.detectChanges([
302
+ "file with spaces.ts",
303
+ "file-with-dashes.ts",
304
+ "file_with_underscores.ts",
305
+ ]);
306
+
307
+ expect(changes.modified).toEqual([]);
308
+ });
309
+
310
+ it("should handle nested directory structures", async () => {
311
+ await fs.mkdir(join(codebaseDir, "src", "components"), {
312
+ recursive: true,
313
+ });
314
+ await createFile(codebaseDir, "src/components/Button.tsx", "content");
315
+
316
+ await synchronizer.updateSnapshot(["src/components/Button.tsx"]);
317
+
318
+ const changes = await synchronizer.detectChanges(["src/components/Button.tsx"]);
319
+
320
+ expect(changes.modified).toEqual([]);
321
+ });
322
+
323
+ it("should handle empty files", async () => {
324
+ await createFile(codebaseDir, "empty.ts", "");
325
+
326
+ await synchronizer.updateSnapshot(["empty.ts"]);
327
+
328
+ const changes = await synchronizer.detectChanges(["empty.ts"]);
329
+
330
+ expect(changes.modified).toEqual([]);
331
+ });
332
+
333
+ it("should detect when empty file becomes non-empty", async () => {
334
+ await createFile(codebaseDir, "file.ts", "");
335
+ await synchronizer.updateSnapshot(["file.ts"]);
336
+
337
+ await createFile(codebaseDir, "file.ts", "now has content");
338
+
339
+ const changes = await synchronizer.detectChanges(["file.ts"]);
340
+
341
+ expect(changes.modified).toEqual(["file.ts"]);
342
+ });
343
+
344
+ it("should handle many files efficiently", async () => {
345
+ const files: string[] = [];
346
+
347
+ for (let i = 0; i < 100; i++) {
348
+ const filename = `file${i}.ts`;
349
+ await createFile(codebaseDir, filename, `content ${i}`);
350
+ files.push(filename);
351
+ }
352
+
353
+ await synchronizer.updateSnapshot(files);
354
+
355
+ const changes = await synchronizer.detectChanges(files);
356
+
357
+ expect(changes.added).toEqual([]);
358
+ expect(changes.modified).toEqual([]);
359
+ expect(changes.deleted).toEqual([]);
360
+ });
361
+ });
362
+
363
+ describe("hash calculation", () => {
364
+ it("should produce same hash for identical content", async () => {
365
+ const content = "test content";
366
+ await createFile(codebaseDir, "file1.ts", content);
367
+ await synchronizer.updateSnapshot(["file1.ts"]);
368
+
369
+ // Delete and recreate with same content
370
+ await fs.unlink(join(codebaseDir, "file1.ts"));
371
+ await createFile(codebaseDir, "file1.ts", content);
372
+
373
+ const changes = await synchronizer.detectChanges(["file1.ts"]);
374
+
375
+ expect(changes.modified).toEqual([]);
376
+ });
377
+
378
+ it("should produce different hash for different content", async () => {
379
+ await createFile(codebaseDir, "file.ts", "original");
380
+ await synchronizer.updateSnapshot(["file.ts"]);
381
+
382
+ await createFile(codebaseDir, "file.ts", "modified");
383
+
384
+ const changes = await synchronizer.detectChanges(["file.ts"]);
385
+
386
+ expect(changes.modified).toEqual(["file.ts"]);
387
+ });
388
+
389
+ it("should be sensitive to whitespace changes", async () => {
390
+ await createFile(codebaseDir, "file.ts", "content");
391
+ await synchronizer.updateSnapshot(["file.ts"]);
392
+
393
+ await createFile(codebaseDir, "file.ts", "content ");
394
+
395
+ const changes = await synchronizer.detectChanges(["file.ts"]);
396
+
397
+ expect(changes.modified).toEqual(["file.ts"]);
398
+ });
399
+
400
+ it("should be sensitive to line ending changes", async () => {
401
+ await createFile(codebaseDir, "file.ts", "line1\nline2");
402
+ await synchronizer.updateSnapshot(["file.ts"]);
403
+
404
+ await createFile(codebaseDir, "file.ts", "line1\r\nline2");
405
+
406
+ const changes = await synchronizer.detectChanges(["file.ts"]);
407
+
408
+ expect(changes.modified).toEqual(["file.ts"]);
409
+ });
410
+ });
411
+
412
+ describe("getSnapshotAge", () => {
413
+ it("should return snapshot age in milliseconds", async () => {
414
+ await createFile(codebaseDir, "file.ts", "content");
415
+ await synchronizer.updateSnapshot(["file.ts"]);
416
+
417
+ // Wait a bit to ensure some time passes
418
+ await new Promise((resolve) => setTimeout(resolve, 10));
419
+
420
+ const age = await synchronizer.getSnapshotAge();
421
+
422
+ expect(age).not.toBeNull();
423
+ expect(age).toBeGreaterThanOrEqual(10);
424
+ });
425
+
426
+ it("should return null when no snapshot exists", async () => {
427
+ const age = await synchronizer.getSnapshotAge();
428
+
429
+ expect(age).toBeNull();
430
+ });
431
+
432
+ it("should return age after snapshot creation", async () => {
433
+ await createFile(codebaseDir, "file.ts", "content");
434
+ await synchronizer.updateSnapshot(["file.ts"]);
435
+
436
+ const age = await synchronizer.getSnapshotAge();
437
+
438
+ expect(age).not.toBeNull();
439
+ expect(age).toBeGreaterThanOrEqual(0);
440
+ });
441
+ });
442
+
443
+ describe("needsReindex", () => {
444
+ it("should return true when no previous tree exists", async () => {
445
+ await createFile(codebaseDir, "file.ts", "content");
446
+
447
+ const needsReindex = await synchronizer.needsReindex(["file.ts"]);
448
+
449
+ expect(needsReindex).toBe(true);
450
+ });
451
+
452
+ it("should return false when files are unchanged", async () => {
453
+ await createFile(codebaseDir, "file.ts", "content");
454
+ await synchronizer.updateSnapshot(["file.ts"]);
455
+
456
+ const needsReindex = await synchronizer.needsReindex(["file.ts"]);
457
+
458
+ expect(needsReindex).toBe(false);
459
+ });
460
+
461
+ it("should return true when files have changed", async () => {
462
+ await createFile(codebaseDir, "file.ts", "original content");
463
+ await synchronizer.updateSnapshot(["file.ts"]);
464
+
465
+ await createFile(codebaseDir, "file.ts", "modified content");
466
+
467
+ const needsReindex = await synchronizer.needsReindex(["file.ts"]);
468
+
469
+ expect(needsReindex).toBe(true);
470
+ });
471
+
472
+ it("should return true when files are added", async () => {
473
+ await createFile(codebaseDir, "file1.ts", "content1");
474
+ await synchronizer.updateSnapshot(["file1.ts"]);
475
+
476
+ await createFile(codebaseDir, "file2.ts", "content2");
477
+
478
+ const needsReindex = await synchronizer.needsReindex(["file1.ts", "file2.ts"]);
479
+
480
+ expect(needsReindex).toBe(true);
481
+ });
482
+
483
+ it("should return true when files are deleted", async () => {
484
+ await createFile(codebaseDir, "file1.ts", "content1");
485
+ await createFile(codebaseDir, "file2.ts", "content2");
486
+ await synchronizer.updateSnapshot(["file1.ts", "file2.ts"]);
487
+
488
+ const needsReindex = await synchronizer.needsReindex(["file1.ts"]);
489
+
490
+ expect(needsReindex).toBe(true);
491
+ });
492
+ });
493
+ });
494
+
495
+ // Helper function to create files in the test codebase
496
+ async function createFile(baseDir: string, relativePath: string, content: string): Promise<void> {
497
+ const fullPath = join(baseDir, relativePath);
498
+ const dir = join(fullPath, "..");
499
+ await fs.mkdir(dir, { recursive: true });
500
+ await fs.writeFile(fullPath, content, "utf-8");
501
+ }
package/vitest.config.ts CHANGED
@@ -17,6 +17,7 @@ export default defineConfig({
17
17
  "commitlint.config.js",
18
18
  "src/index.ts",
19
19
  "scripts/**",
20
+ "tests/**/fixtures/**",
20
21
  ],
21
22
  thresholds: {
22
23
  "src/qdrant/client.ts": {