@mhalder/qdrant-mcp-server 2.1.1 → 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.
- package/CHANGELOG.md +6 -0
- package/README.md +3 -2
- package/build/code/indexer.d.ts +5 -0
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +116 -14
- package/build/code/indexer.js.map +1 -1
- package/build/code/types.d.ts +4 -0
- package/build/code/types.d.ts.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +11 -1
- package/build/tools/code.js.map +1 -1
- package/examples/code-search/README.md +19 -4
- package/package.json +1 -1
- package/src/code/indexer.ts +186 -38
- package/src/code/types.ts +5 -0
- package/src/tools/code.ts +12 -1
- package/tests/code/indexer.test.ts +412 -74
- 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:
|
|
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
|
-
|
|
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(
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
398
|
-
|
|
399
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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, {
|
|
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(
|
|
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, "..");
|