@mhalder/qdrant-mcp-server 1.4.0 → 1.5.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 (82) hide show
  1. package/.codecov.yml +16 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +236 -9
  4. package/build/code/chunker/base.d.ts +19 -0
  5. package/build/code/chunker/base.d.ts.map +1 -0
  6. package/build/code/chunker/base.js +5 -0
  7. package/build/code/chunker/base.js.map +1 -0
  8. package/build/code/chunker/character-chunker.d.ts +22 -0
  9. package/build/code/chunker/character-chunker.d.ts.map +1 -0
  10. package/build/code/chunker/character-chunker.js +111 -0
  11. package/build/code/chunker/character-chunker.js.map +1 -0
  12. package/build/code/chunker/tree-sitter-chunker.d.ts +29 -0
  13. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -0
  14. package/build/code/chunker/tree-sitter-chunker.js +213 -0
  15. package/build/code/chunker/tree-sitter-chunker.js.map +1 -0
  16. package/build/code/config.d.ts +11 -0
  17. package/build/code/config.d.ts.map +1 -0
  18. package/build/code/config.js +145 -0
  19. package/build/code/config.js.map +1 -0
  20. package/build/code/indexer.d.ts +42 -0
  21. package/build/code/indexer.d.ts.map +1 -0
  22. package/build/code/indexer.js +508 -0
  23. package/build/code/indexer.js.map +1 -0
  24. package/build/code/metadata.d.ts +32 -0
  25. package/build/code/metadata.d.ts.map +1 -0
  26. package/build/code/metadata.js +128 -0
  27. package/build/code/metadata.js.map +1 -0
  28. package/build/code/scanner.d.ts +35 -0
  29. package/build/code/scanner.d.ts.map +1 -0
  30. package/build/code/scanner.js +108 -0
  31. package/build/code/scanner.js.map +1 -0
  32. package/build/code/sync/merkle.d.ts +45 -0
  33. package/build/code/sync/merkle.d.ts.map +1 -0
  34. package/build/code/sync/merkle.js +116 -0
  35. package/build/code/sync/merkle.js.map +1 -0
  36. package/build/code/sync/snapshot.d.ts +41 -0
  37. package/build/code/sync/snapshot.d.ts.map +1 -0
  38. package/build/code/sync/snapshot.js +91 -0
  39. package/build/code/sync/snapshot.js.map +1 -0
  40. package/build/code/sync/synchronizer.d.ts +53 -0
  41. package/build/code/sync/synchronizer.d.ts.map +1 -0
  42. package/build/code/sync/synchronizer.js +132 -0
  43. package/build/code/sync/synchronizer.js.map +1 -0
  44. package/build/code/types.d.ts +98 -0
  45. package/build/code/types.d.ts.map +1 -0
  46. package/build/code/types.js +5 -0
  47. package/build/code/types.js.map +1 -0
  48. package/build/index.js +250 -0
  49. package/build/index.js.map +1 -1
  50. package/examples/code-search/README.md +271 -0
  51. package/package.json +13 -1
  52. package/src/code/chunker/base.ts +22 -0
  53. package/src/code/chunker/character-chunker.ts +131 -0
  54. package/src/code/chunker/tree-sitter-chunker.ts +250 -0
  55. package/src/code/config.ts +156 -0
  56. package/src/code/indexer.ts +613 -0
  57. package/src/code/metadata.ts +153 -0
  58. package/src/code/scanner.ts +124 -0
  59. package/src/code/sync/merkle.ts +136 -0
  60. package/src/code/sync/snapshot.ts +110 -0
  61. package/src/code/sync/synchronizer.ts +154 -0
  62. package/src/code/types.ts +117 -0
  63. package/src/index.ts +296 -0
  64. package/tests/code/chunker/character-chunker.test.ts +141 -0
  65. package/tests/code/chunker/tree-sitter-chunker.test.ts +275 -0
  66. package/tests/code/fixtures/sample-py/calculator.py +32 -0
  67. package/tests/code/fixtures/sample-ts/async-operations.ts +120 -0
  68. package/tests/code/fixtures/sample-ts/auth.ts +31 -0
  69. package/tests/code/fixtures/sample-ts/config.ts +52 -0
  70. package/tests/code/fixtures/sample-ts/database.ts +50 -0
  71. package/tests/code/fixtures/sample-ts/index.ts +39 -0
  72. package/tests/code/fixtures/sample-ts/types-advanced.ts +132 -0
  73. package/tests/code/fixtures/sample-ts/utils.ts +105 -0
  74. package/tests/code/fixtures/sample-ts/validator.ts +169 -0
  75. package/tests/code/indexer.test.ts +828 -0
  76. package/tests/code/integration.test.ts +708 -0
  77. package/tests/code/metadata.test.ts +457 -0
  78. package/tests/code/scanner.test.ts +131 -0
  79. package/tests/code/sync/merkle.test.ts +406 -0
  80. package/tests/code/sync/snapshot.test.ts +360 -0
  81. package/tests/code/sync/synchronizer.test.ts +501 -0
  82. package/vitest.config.ts +1 -0
@@ -0,0 +1,360 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { MerkleTree } from "../../../src/code/sync/merkle.js";
6
+ import { SnapshotManager } from "../../../src/code/sync/snapshot.js";
7
+
8
+ describe("SnapshotManager", () => {
9
+ let tempDir: string;
10
+ let snapshotManager: SnapshotManager;
11
+ let collectionName: string;
12
+
13
+ beforeEach(async () => {
14
+ // Create a temporary directory for test snapshots
15
+ tempDir = join(
16
+ tmpdir(),
17
+ `qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}`
18
+ );
19
+ await fs.mkdir(tempDir, { recursive: true });
20
+
21
+ collectionName = "test-collection";
22
+ const snapshotPath = join(tempDir, `${collectionName}.json`);
23
+ snapshotManager = new SnapshotManager(snapshotPath);
24
+ });
25
+
26
+ afterEach(async () => {
27
+ // Clean up temporary directory
28
+ try {
29
+ await fs.rm(tempDir, { recursive: true, force: true });
30
+ } catch (_error) {
31
+ // Ignore cleanup errors
32
+ }
33
+ });
34
+
35
+ describe("save", () => {
36
+ it("should save snapshot with file hashes and merkle tree", async () => {
37
+ const fileHashes = new Map([
38
+ ["file1.ts", "hash1"],
39
+ ["file2.ts", "hash2"],
40
+ ]);
41
+
42
+ const tree = new MerkleTree();
43
+ tree.build(fileHashes);
44
+
45
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
46
+
47
+ const snapshotPath = join(tempDir, `${collectionName}.json`);
48
+ const exists = await fs
49
+ .access(snapshotPath)
50
+ .then(() => true)
51
+ .catch(() => false);
52
+
53
+ expect(exists).toBe(true);
54
+ });
55
+
56
+ it("should save correct snapshot data", async () => {
57
+ const fileHashes = new Map([
58
+ ["file1.ts", "hash1"],
59
+ ["file2.ts", "hash2"],
60
+ ]);
61
+
62
+ const tree = new MerkleTree();
63
+ tree.build(fileHashes);
64
+
65
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
66
+
67
+ const loaded = await snapshotManager.load();
68
+
69
+ expect(loaded).toBeDefined();
70
+ expect(loaded?.codebasePath).toBe("/test/codebase");
71
+ expect(loaded?.fileHashes.size).toBe(2);
72
+ expect(loaded?.fileHashes.get("file1.ts")).toBe("hash1");
73
+ expect(loaded?.fileHashes.get("file2.ts")).toBe("hash2");
74
+ });
75
+
76
+ it("should overwrite existing snapshot", async () => {
77
+ const fileHashes1 = new Map([["file1.ts", "hash1"]]);
78
+ const tree1 = new MerkleTree();
79
+ tree1.build(fileHashes1);
80
+
81
+ await snapshotManager.save("/test/codebase1", fileHashes1, tree1);
82
+
83
+ const fileHashes2 = new Map([["file2.ts", "hash2"]]);
84
+ const tree2 = new MerkleTree();
85
+ tree2.build(fileHashes2);
86
+
87
+ await snapshotManager.save("/test/codebase2", fileHashes2, tree2);
88
+
89
+ const loaded = await snapshotManager.load();
90
+
91
+ expect(loaded?.codebasePath).toBe("/test/codebase2");
92
+ expect(loaded?.fileHashes.size).toBe(1);
93
+ expect(loaded?.fileHashes.get("file2.ts")).toBe("hash2");
94
+ });
95
+
96
+ it("should create snapshot directory if it doesn't exist", async () => {
97
+ const nonExistentDir = join(tempDir, "nested", "path");
98
+ const snapshotPath = join(nonExistentDir, `${collectionName}.json`);
99
+ const manager = new SnapshotManager(snapshotPath);
100
+
101
+ const fileHashes = new Map([["file.ts", "hash"]]);
102
+ const tree = new MerkleTree();
103
+ tree.build(fileHashes);
104
+
105
+ await manager.save("/test/codebase", fileHashes, tree);
106
+
107
+ const exists = await fs
108
+ .access(snapshotPath)
109
+ .then(() => true)
110
+ .catch(() => false);
111
+
112
+ expect(exists).toBe(true);
113
+ });
114
+
115
+ it("should handle empty file hashes", async () => {
116
+ const fileHashes = new Map<string, string>();
117
+ const tree = new MerkleTree();
118
+ tree.build(fileHashes);
119
+
120
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
121
+
122
+ const loaded = await snapshotManager.load();
123
+
124
+ expect(loaded?.fileHashes.size).toBe(0);
125
+ });
126
+
127
+ it("should preserve timestamp", async () => {
128
+ const fileHashes = new Map([["file.ts", "hash"]]);
129
+ const tree = new MerkleTree();
130
+ tree.build(fileHashes);
131
+
132
+ const beforeSave = Date.now();
133
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
134
+ const afterSave = Date.now();
135
+
136
+ const loaded = await snapshotManager.load();
137
+
138
+ expect(loaded?.timestamp).toBeGreaterThanOrEqual(beforeSave);
139
+ expect(loaded?.timestamp).toBeLessThanOrEqual(afterSave);
140
+ });
141
+ });
142
+
143
+ describe("load", () => {
144
+ it("should return null if snapshot doesn't exist", async () => {
145
+ const loaded = await snapshotManager.load();
146
+ expect(loaded).toBeNull();
147
+ });
148
+
149
+ it("should load previously saved snapshot", async () => {
150
+ const fileHashes = new Map([
151
+ ["file1.ts", "hash1"],
152
+ ["file2.ts", "hash2"],
153
+ ["file3.ts", "hash3"],
154
+ ]);
155
+
156
+ const tree = new MerkleTree();
157
+ tree.build(fileHashes);
158
+
159
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
160
+
161
+ const loaded = await snapshotManager.load();
162
+
163
+ expect(loaded).toBeDefined();
164
+ expect(loaded?.codebasePath).toBe("/test/codebase");
165
+ expect(loaded?.fileHashes.size).toBe(3);
166
+ expect(loaded?.merkleTree.getRootHash()).toBe(tree.getRootHash());
167
+ });
168
+
169
+ it("should handle corrupted snapshot file", async () => {
170
+ const snapshotPath = join(tempDir, `${collectionName}.json`);
171
+ await fs.writeFile(snapshotPath, "{ invalid json", "utf-8");
172
+
173
+ const loaded = await snapshotManager.load();
174
+
175
+ expect(loaded).toBeNull();
176
+ });
177
+
178
+ it("should handle missing fields in snapshot", async () => {
179
+ const snapshotPath = join(tempDir, `${collectionName}.json`);
180
+ await fs.writeFile(snapshotPath, JSON.stringify({ codebasePath: "/test" }), "utf-8");
181
+
182
+ const loaded = await snapshotManager.load();
183
+
184
+ // Should handle gracefully, might return null or partial data
185
+ expect(loaded).toBeDefined();
186
+ });
187
+
188
+ it("should reconstruct merkle tree from serialized data", async () => {
189
+ const fileHashes = new Map([
190
+ ["file1.ts", "hash1"],
191
+ ["file2.ts", "hash2"],
192
+ ]);
193
+
194
+ const tree = new MerkleTree();
195
+ tree.build(fileHashes);
196
+
197
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
198
+
199
+ const loaded = await snapshotManager.load();
200
+
201
+ expect(loaded?.merkleTree).toBeDefined();
202
+ expect(loaded?.merkleTree.getRootHash()).toBe(tree.getRootHash());
203
+ });
204
+ });
205
+
206
+ describe("exists", () => {
207
+ it("should return false when snapshot doesn't exist", async () => {
208
+ const exists = await snapshotManager.exists();
209
+ expect(exists).toBe(false);
210
+ });
211
+
212
+ it("should return true when snapshot exists", async () => {
213
+ const fileHashes = new Map([["file.ts", "hash"]]);
214
+ const tree = new MerkleTree();
215
+ tree.build(fileHashes);
216
+
217
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
218
+
219
+ const exists = await snapshotManager.exists();
220
+ expect(exists).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe("delete", () => {
225
+ it("should delete existing snapshot", async () => {
226
+ const fileHashes = new Map([["file.ts", "hash"]]);
227
+ const tree = new MerkleTree();
228
+ tree.build(fileHashes);
229
+
230
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
231
+
232
+ expect(await snapshotManager.exists()).toBe(true);
233
+
234
+ await snapshotManager.delete();
235
+
236
+ expect(await snapshotManager.exists()).toBe(false);
237
+ });
238
+
239
+ it("should handle deleting non-existent snapshot", async () => {
240
+ await expect(snapshotManager.delete()).resolves.not.toThrow();
241
+ });
242
+
243
+ it("should allow saving after deletion", async () => {
244
+ const fileHashes1 = new Map([["file1.ts", "hash1"]]);
245
+ const tree1 = new MerkleTree();
246
+ tree1.build(fileHashes1);
247
+
248
+ await snapshotManager.save("/test/codebase1", fileHashes1, tree1);
249
+ await snapshotManager.delete();
250
+
251
+ const fileHashes2 = new Map([["file2.ts", "hash2"]]);
252
+ const tree2 = new MerkleTree();
253
+ tree2.build(fileHashes2);
254
+
255
+ await snapshotManager.save("/test/codebase2", fileHashes2, tree2);
256
+
257
+ const loaded = await snapshotManager.load();
258
+ expect(loaded?.codebasePath).toBe("/test/codebase2");
259
+ });
260
+ });
261
+
262
+ describe("edge cases", () => {
263
+ it("should handle large number of files", async () => {
264
+ const fileHashes = new Map<string, string>();
265
+ for (let i = 0; i < 1000; i++) {
266
+ fileHashes.set(`file${i}.ts`, `hash${i}`);
267
+ }
268
+
269
+ const tree = new MerkleTree();
270
+ tree.build(fileHashes);
271
+
272
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
273
+
274
+ const loaded = await snapshotManager.load();
275
+
276
+ expect(loaded?.fileHashes.size).toBe(1000);
277
+ });
278
+
279
+ it("should handle special characters in paths", async () => {
280
+ const fileHashes = new Map([
281
+ ["/path/with spaces/file.ts", "hash1"],
282
+ ["/path/with-dashes/file.ts", "hash2"],
283
+ ["/path/with_underscores/file.ts", "hash3"],
284
+ ]);
285
+
286
+ const tree = new MerkleTree();
287
+ tree.build(fileHashes);
288
+
289
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
290
+
291
+ const loaded = await snapshotManager.load();
292
+
293
+ expect(loaded?.fileHashes.size).toBe(3);
294
+ });
295
+
296
+ it("should handle collection names with special characters", async () => {
297
+ const specialName = "collection-with-dashes_and_underscores";
298
+ const snapshotPath = join(tempDir, `${specialName}.json`);
299
+ const manager = new SnapshotManager(snapshotPath);
300
+
301
+ const fileHashes = new Map([["file.ts", "hash"]]);
302
+ const tree = new MerkleTree();
303
+ tree.build(fileHashes);
304
+
305
+ await manager.save("/test/codebase", fileHashes, tree);
306
+
307
+ const loaded = await manager.load();
308
+ expect(loaded).toBeDefined();
309
+ });
310
+
311
+ it("should handle very long codebase paths", async () => {
312
+ const longPath = `${"/very/long/path/".repeat(50)}codebase`;
313
+
314
+ const fileHashes = new Map([["file.ts", "hash"]]);
315
+ const tree = new MerkleTree();
316
+ tree.build(fileHashes);
317
+
318
+ await snapshotManager.save(longPath, fileHashes, tree);
319
+
320
+ const loaded = await snapshotManager.load();
321
+ expect(loaded?.codebasePath).toBe(longPath);
322
+ });
323
+ });
324
+
325
+ describe("concurrent operations", () => {
326
+ it("should handle rapid save operations", async () => {
327
+ const promises = [];
328
+
329
+ for (let i = 0; i < 10; i++) {
330
+ const fileHashes = new Map([[`file${i}.ts`, `hash${i}`]]);
331
+ const tree = new MerkleTree();
332
+ tree.build(fileHashes);
333
+
334
+ promises.push(snapshotManager.save(`/test/codebase${i}`, fileHashes, tree));
335
+ }
336
+
337
+ await Promise.all(promises);
338
+
339
+ // Last save should win
340
+ const loaded = await snapshotManager.load();
341
+ expect(loaded).toBeDefined();
342
+ });
343
+
344
+ it("should handle concurrent save and load", async () => {
345
+ const fileHashes = new Map([["file.ts", "hash"]]);
346
+ const tree = new MerkleTree();
347
+ tree.build(fileHashes);
348
+
349
+ await snapshotManager.save("/test/codebase", fileHashes, tree);
350
+
351
+ const promises = [snapshotManager.load(), snapshotManager.load(), snapshotManager.load()];
352
+
353
+ const results = await Promise.all(promises);
354
+
355
+ results.forEach((result) => {
356
+ expect(result).toBeDefined();
357
+ });
358
+ });
359
+ });
360
+ });