@mhalder/qdrant-mcp-server 3.3.3 → 3.3.4
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/.github/workflows/ci.yml +0 -2
- package/.github/workflows/claude-code-review.yml +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/biome.json +3 -2
- package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
- package/build/code/chunker/tree-sitter-chunker.js +2 -12
- package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +12 -18
- package/build/code/indexer.js.map +1 -1
- package/build/code/scanner.js +1 -1
- package/build/code/scanner.js.map +1 -1
- package/build/embeddings/cohere.d.ts +1 -1
- package/build/embeddings/cohere.d.ts.map +1 -1
- package/build/embeddings/cohere.js +2 -2
- package/build/embeddings/cohere.js.map +1 -1
- package/build/embeddings/cohere.test.js +1 -5
- package/build/embeddings/cohere.test.js.map +1 -1
- package/build/embeddings/factory.d.ts +1 -1
- package/build/embeddings/factory.d.ts.map +1 -1
- package/build/embeddings/factory.js +7 -9
- package/build/embeddings/factory.js.map +1 -1
- package/build/embeddings/factory.test.js +3 -3
- package/build/embeddings/factory.test.js.map +1 -1
- package/build/embeddings/ollama.d.ts +1 -1
- package/build/embeddings/ollama.d.ts.map +1 -1
- package/build/embeddings/ollama.js +6 -8
- package/build/embeddings/ollama.js.map +1 -1
- package/build/embeddings/ollama.test.js +2 -6
- package/build/embeddings/ollama.test.js.map +1 -1
- package/build/embeddings/openai.d.ts +1 -1
- package/build/embeddings/openai.d.ts.map +1 -1
- package/build/embeddings/openai.js +4 -7
- package/build/embeddings/openai.js.map +1 -1
- package/build/embeddings/openai.test.js +3 -12
- package/build/embeddings/openai.test.js.map +1 -1
- package/build/embeddings/sparse.test.js +12 -2
- package/build/embeddings/sparse.test.js.map +1 -1
- package/build/embeddings/voyage.d.ts +1 -1
- package/build/embeddings/voyage.d.ts.map +1 -1
- package/build/embeddings/voyage.js +2 -3
- package/build/embeddings/voyage.js.map +1 -1
- package/build/embeddings/voyage.test.js +2 -6
- package/build/embeddings/voyage.test.js.map +1 -1
- package/build/git/chunker.d.ts.map +1 -1
- package/build/git/chunker.js +2 -2
- package/build/git/chunker.js.map +1 -1
- package/build/git/chunker.test.js +1 -1
- package/build/git/chunker.test.js.map +1 -1
- package/build/git/extractor.d.ts.map +1 -1
- package/build/git/extractor.integration.test.js +9 -5
- package/build/git/extractor.integration.test.js.map +1 -1
- package/build/git/extractor.js +1 -1
- package/build/git/extractor.js.map +1 -1
- package/build/git/extractor.test.js +2 -2
- package/build/git/extractor.test.js.map +1 -1
- package/build/git/index.d.ts +4 -4
- package/build/git/index.d.ts.map +1 -1
- package/build/git/index.js +3 -3
- package/build/git/index.js.map +1 -1
- package/build/git/indexer.d.ts.map +1 -1
- package/build/git/indexer.js +9 -21
- package/build/git/indexer.js.map +1 -1
- package/build/git/indexer.test.js +4 -8
- package/build/git/indexer.test.js.map +1 -1
- package/build/git/sync/synchronizer.d.ts.map +1 -1
- package/build/git/sync/synchronizer.js.map +1 -1
- package/build/git/sync/synchronizer.test.js +4 -2
- package/build/git/sync/synchronizer.test.js.map +1 -1
- package/build/index.js +5 -9
- package/build/index.js.map +1 -1
- package/build/index.test.js +3 -3
- package/build/index.test.js.map +1 -1
- package/build/logger.d.ts.map +1 -1
- package/build/logger.js +1 -9
- package/build/logger.js.map +1 -1
- package/build/prompts/register.d.ts.map +1 -1
- package/build/prompts/register.js.map +1 -1
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +10 -34
- package/build/qdrant/client.test.js.map +1 -1
- package/build/resources/index.d.ts +1 -1
- package/build/resources/index.d.ts.map +1 -1
- package/build/resources/index.js +1 -1
- package/build/resources/index.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +3 -9
- package/build/tools/code.js.map +1 -1
- package/build/tools/collection.d.ts.map +1 -1
- package/build/tools/collection.js +1 -3
- package/build/tools/collection.js.map +1 -1
- package/build/tools/document.d.ts.map +1 -1
- package/build/tools/document.js +1 -1
- package/build/tools/document.js.map +1 -1
- package/build/tools/federated.d.ts.map +1 -1
- package/build/tools/federated.js +15 -6
- package/build/tools/federated.js.map +1 -1
- package/build/tools/federated.test.js +18 -22
- package/build/tools/federated.test.js.map +1 -1
- package/build/tools/git-history.d.ts.map +1 -1
- package/build/tools/git-history.js +3 -7
- package/build/tools/git-history.js.map +1 -1
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +1 -3
- package/build/tools/logging.js.map +1 -1
- package/build/tools/logging.test.js +1 -1
- package/build/tools/logging.test.js.map +1 -1
- package/build/tools/schemas.d.ts.map +1 -1
- package/build/tools/schemas.js +17 -64
- package/build/tools/schemas.js.map +1 -1
- package/build/tools/search.d.ts.map +1 -1
- package/build/tools/search.js +1 -1
- package/build/tools/search.js.map +1 -1
- package/commitlint.config.js +12 -23
- package/package.json +1 -1
- package/scripts/verify-providers.js +12 -32
- package/src/code/chunker/tree-sitter-chunker.ts +9 -35
- package/src/code/indexer.ts +45 -107
- package/src/code/scanner.ts +1 -1
- package/src/embeddings/cohere.test.ts +17 -45
- package/src/embeddings/cohere.ts +10 -17
- package/src/embeddings/factory.test.ts +18 -18
- package/src/embeddings/factory.ts +18 -25
- package/src/embeddings/ollama.test.ts +38 -67
- package/src/embeddings/ollama.ts +15 -27
- package/src/embeddings/openai.test.ts +17 -53
- package/src/embeddings/openai.ts +11 -22
- package/src/embeddings/sparse.test.ts +12 -2
- package/src/embeddings/voyage.test.ts +39 -80
- package/src/embeddings/voyage.ts +9 -13
- package/src/git/chunker.test.ts +1 -1
- package/src/git/chunker.ts +6 -22
- package/src/git/extractor.integration.test.ts +12 -16
- package/src/git/extractor.test.ts +21 -35
- package/src/git/extractor.ts +14 -36
- package/src/git/index.ts +9 -10
- package/src/git/indexer.test.ts +29 -57
- package/src/git/indexer.ts +38 -86
- package/src/git/sync/synchronizer.test.ts +6 -9
- package/src/git/sync/synchronizer.ts +2 -5
- package/src/index.test.ts +7 -9
- package/src/index.ts +34 -80
- package/src/logger.ts +3 -14
- package/src/prompts/register.ts +3 -10
- package/src/qdrant/client.test.ts +63 -169
- package/src/qdrant/client.ts +19 -45
- package/src/resources/index.ts +4 -10
- package/src/tools/code.ts +43 -66
- package/src/tools/collection.ts +19 -38
- package/src/tools/document.ts +10 -19
- package/src/tools/federated.test.ts +34 -57
- package/src/tools/federated.ts +88 -108
- package/src/tools/git-history.ts +32 -60
- package/src/tools/index.ts +1 -4
- package/src/tools/logging.test.ts +10 -10
- package/src/tools/logging.ts +8 -18
- package/src/tools/schemas.ts +23 -78
- package/src/tools/search.ts +77 -94
- package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
- package/tests/code/indexer.test.ts +100 -192
- package/tests/code/integration.test.ts +61 -117
- package/tests/code/scanner.test.ts +12 -39
- package/tests/code/sync/snapshot.test.ts +4 -14
- package/tests/code/sync/synchronizer.test.ts +10 -40
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* These tests run against real git repositories (not mocked)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { describe, it, expect, beforeAll } from "vitest";
|
|
7
|
-
import { GitExtractor } from "./extractor.js";
|
|
8
|
-
import { DEFAULT_GIT_CONFIG } from "./config.js";
|
|
9
|
-
import type { GitConfig } from "./types.js";
|
|
10
6
|
import { execFile } from "node:child_process";
|
|
11
7
|
import { promisify } from "node:util";
|
|
8
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
9
|
+
import { DEFAULT_GIT_CONFIG } from "./config.js";
|
|
10
|
+
import { GitExtractor } from "./extractor.js";
|
|
11
|
+
import type { GitConfig } from "./types.js";
|
|
12
12
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
|
|
@@ -93,11 +93,9 @@ describe("GitExtractor Integration Tests", () => {
|
|
|
93
93
|
|
|
94
94
|
it("should return correct commit count matching git rev-list", async () => {
|
|
95
95
|
// Get expected count from git directly
|
|
96
|
-
const { stdout } = await execFileAsync(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{ cwd: repoPath },
|
|
100
|
-
);
|
|
96
|
+
const { stdout } = await execFileAsync("git", ["rev-list", "--count", "-n", "50", "HEAD"], {
|
|
97
|
+
cwd: repoPath,
|
|
98
|
+
});
|
|
101
99
|
const expectedCount = Math.min(parseInt(stdout.trim(), 10), 50);
|
|
102
100
|
|
|
103
101
|
// Get commits via extractor
|
|
@@ -112,7 +110,7 @@ describe("GitExtractor Integration Tests", () => {
|
|
|
112
110
|
const { stdout: logOutput } = await execFileAsync(
|
|
113
111
|
"git",
|
|
114
112
|
["log", "--oneline", "--shortstat", "-n", "10", "HEAD"],
|
|
115
|
-
{ cwd: repoPath }
|
|
113
|
+
{ cwd: repoPath }
|
|
116
114
|
);
|
|
117
115
|
|
|
118
116
|
// If there are commits with files changed, verify our extractor gets them
|
|
@@ -199,11 +197,9 @@ describe("GitExtractor Integration Tests", () => {
|
|
|
199
197
|
const count = await extractor.getCommitCount();
|
|
200
198
|
|
|
201
199
|
// Verify against git rev-list
|
|
202
|
-
const { stdout } = await execFileAsync(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{ cwd: repoPath },
|
|
206
|
-
);
|
|
200
|
+
const { stdout } = await execFileAsync("git", ["rev-list", "--count", "HEAD"], {
|
|
201
|
+
cwd: repoPath,
|
|
202
|
+
});
|
|
207
203
|
expect(count).toBe(parseInt(stdout.trim(), 10));
|
|
208
204
|
});
|
|
209
205
|
|
|
@@ -218,7 +214,7 @@ describe("GitExtractor Integration Tests", () => {
|
|
|
218
214
|
const { stdout } = await execFileAsync(
|
|
219
215
|
"git",
|
|
220
216
|
["rev-list", "--count", `${sinceHash}..HEAD`],
|
|
221
|
-
{ cwd: repoPath }
|
|
217
|
+
{ cwd: repoPath }
|
|
222
218
|
);
|
|
223
219
|
expect(count).toBe(parseInt(stdout.trim(), 10));
|
|
224
220
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GitExtractor, normalizeRemoteUrl } from "./extractor.js";
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
2
|
import { DEFAULT_GIT_CONFIG, GIT_LOG_COMMIT_DELIMITER } from "./config.js";
|
|
3
|
+
import { GitExtractor, normalizeRemoteUrl } from "./extractor.js";
|
|
4
4
|
import type { GitConfig } from "./types.js";
|
|
5
5
|
|
|
6
6
|
// Mock child_process with promisify-compatible execFile
|
|
@@ -35,7 +35,7 @@ describe("GitExtractor", () => {
|
|
|
35
35
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
36
36
|
"git",
|
|
37
37
|
["rev-parse", "--git-dir"],
|
|
38
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
38
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
39
39
|
);
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -61,7 +61,7 @@ describe("GitExtractor", () => {
|
|
|
61
61
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
62
62
|
"git",
|
|
63
63
|
["rev-parse", "HEAD"],
|
|
64
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
64
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
65
65
|
);
|
|
66
66
|
});
|
|
67
67
|
});
|
|
@@ -76,7 +76,7 @@ describe("GitExtractor", () => {
|
|
|
76
76
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
77
77
|
"git",
|
|
78
78
|
["rev-list", "--count", "HEAD"],
|
|
79
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
79
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -89,7 +89,7 @@ describe("GitExtractor", () => {
|
|
|
89
89
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
90
90
|
"git",
|
|
91
91
|
["rev-list", "--count", "abc123..HEAD"],
|
|
92
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
92
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
93
93
|
);
|
|
94
94
|
});
|
|
95
95
|
});
|
|
@@ -202,7 +202,7 @@ describe("GitExtractor", () => {
|
|
|
202
202
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
203
203
|
"git",
|
|
204
204
|
expect.arrayContaining(["-n100"]),
|
|
205
|
-
expect.any(Object)
|
|
205
|
+
expect.any(Object)
|
|
206
206
|
);
|
|
207
207
|
});
|
|
208
208
|
|
|
@@ -214,7 +214,7 @@ describe("GitExtractor", () => {
|
|
|
214
214
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
215
215
|
"git",
|
|
216
216
|
expect.arrayContaining(["abc123..HEAD"]),
|
|
217
|
-
expect.any(Object)
|
|
217
|
+
expect.any(Object)
|
|
218
218
|
);
|
|
219
219
|
});
|
|
220
220
|
|
|
@@ -226,7 +226,7 @@ describe("GitExtractor", () => {
|
|
|
226
226
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
227
227
|
"git",
|
|
228
228
|
expect.arrayContaining(["--since=2024-01-01"]),
|
|
229
|
-
expect.any(Object)
|
|
229
|
+
expect.any(Object)
|
|
230
230
|
);
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -287,7 +287,7 @@ diff --git a/file.ts b/file.ts
|
|
|
287
287
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
288
288
|
"git",
|
|
289
289
|
["show", "--no-color", "-p", "abc123"],
|
|
290
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
290
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
291
291
|
);
|
|
292
292
|
});
|
|
293
293
|
|
|
@@ -326,14 +326,12 @@ diff --git a/file.ts b/file.ts
|
|
|
326
326
|
expect(mockExecFile).toHaveBeenCalledWith(
|
|
327
327
|
"git",
|
|
328
328
|
["remote", "get-url", "origin"],
|
|
329
|
-
expect.objectContaining({ cwd: "/test/repo" })
|
|
329
|
+
expect.objectContaining({ cwd: "/test/repo" })
|
|
330
330
|
);
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
it("should return empty string when no remote configured", async () => {
|
|
334
|
-
mockExecFile.mockRejectedValue(
|
|
335
|
-
new Error("fatal: No such remote 'origin'"),
|
|
336
|
-
);
|
|
334
|
+
mockExecFile.mockRejectedValue(new Error("fatal: No such remote 'origin'"));
|
|
337
335
|
|
|
338
336
|
const result = await extractor.getRemoteUrl();
|
|
339
337
|
|
|
@@ -355,22 +353,16 @@ diff --git a/file.ts b/file.ts
|
|
|
355
353
|
|
|
356
354
|
describe("normalizeRemoteUrl", () => {
|
|
357
355
|
it("should normalize SSH URL", () => {
|
|
358
|
-
expect(normalizeRemoteUrl("git@github.com:user/repo.git")).toBe(
|
|
359
|
-
"user/repo",
|
|
360
|
-
);
|
|
356
|
+
expect(normalizeRemoteUrl("git@github.com:user/repo.git")).toBe("user/repo");
|
|
361
357
|
});
|
|
362
358
|
|
|
363
359
|
it("should normalize HTTPS URL", () => {
|
|
364
|
-
expect(normalizeRemoteUrl("https://github.com/user/repo.git")).toBe(
|
|
365
|
-
"user/repo",
|
|
366
|
-
);
|
|
360
|
+
expect(normalizeRemoteUrl("https://github.com/user/repo.git")).toBe("user/repo");
|
|
367
361
|
});
|
|
368
362
|
|
|
369
363
|
it("should handle URL without .git suffix", () => {
|
|
370
364
|
expect(normalizeRemoteUrl("git@github.com:user/repo")).toBe("user/repo");
|
|
371
|
-
expect(normalizeRemoteUrl("https://github.com/user/repo")).toBe(
|
|
372
|
-
"user/repo",
|
|
373
|
-
);
|
|
365
|
+
expect(normalizeRemoteUrl("https://github.com/user/repo")).toBe("user/repo");
|
|
374
366
|
});
|
|
375
367
|
|
|
376
368
|
it("should return empty string for empty input", () => {
|
|
@@ -378,26 +370,20 @@ describe("normalizeRemoteUrl", () => {
|
|
|
378
370
|
});
|
|
379
371
|
|
|
380
372
|
it("should handle GitLab SSH URL", () => {
|
|
381
|
-
expect(normalizeRemoteUrl("git@gitlab.com:group/project.git")).toBe(
|
|
382
|
-
"group/project",
|
|
383
|
-
);
|
|
373
|
+
expect(normalizeRemoteUrl("git@gitlab.com:group/project.git")).toBe("group/project");
|
|
384
374
|
});
|
|
385
375
|
|
|
386
376
|
it("should handle Bitbucket SSH URL", () => {
|
|
387
|
-
expect(normalizeRemoteUrl("git@bitbucket.org:team/repo.git")).toBe(
|
|
388
|
-
"team/repo",
|
|
389
|
-
);
|
|
377
|
+
expect(normalizeRemoteUrl("git@bitbucket.org:team/repo.git")).toBe("team/repo");
|
|
390
378
|
});
|
|
391
379
|
|
|
392
380
|
it("should handle HTTP URL (not HTTPS)", () => {
|
|
393
|
-
expect(normalizeRemoteUrl("http://github.com/user/repo.git")).toBe(
|
|
394
|
-
"user/repo",
|
|
395
|
-
);
|
|
381
|
+
expect(normalizeRemoteUrl("http://github.com/user/repo.git")).toBe("user/repo");
|
|
396
382
|
});
|
|
397
383
|
|
|
398
384
|
it("should handle nested paths", () => {
|
|
399
|
-
expect(
|
|
400
|
-
|
|
401
|
-
)
|
|
385
|
+
expect(normalizeRemoteUrl("https://github.com/org/group/subgroup/repo.git")).toBe(
|
|
386
|
+
"org/group/subgroup/repo"
|
|
387
|
+
);
|
|
402
388
|
});
|
|
403
389
|
});
|
package/src/git/extractor.ts
CHANGED
|
@@ -5,11 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import {
|
|
9
|
-
GIT_LOG_COMMIT_DELIMITER,
|
|
10
|
-
GIT_LOG_FORMAT,
|
|
11
|
-
GIT_MAX_BUFFER,
|
|
12
|
-
} from "./config.js";
|
|
8
|
+
import { GIT_LOG_COMMIT_DELIMITER, GIT_LOG_FORMAT, GIT_MAX_BUFFER } from "./config.js";
|
|
13
9
|
import type { GitConfig, GitExtractOptions, RawCommit } from "./types.js";
|
|
14
10
|
|
|
15
11
|
const execFileAsync = promisify(execFile);
|
|
@@ -34,7 +30,7 @@ export function normalizeRemoteUrl(url: string): string {
|
|
|
34
30
|
export class GitExtractor {
|
|
35
31
|
constructor(
|
|
36
32
|
private repoPath: string,
|
|
37
|
-
private config: GitConfig
|
|
33
|
+
private config: GitConfig
|
|
38
34
|
) {}
|
|
39
35
|
|
|
40
36
|
/**
|
|
@@ -70,15 +66,11 @@ export class GitExtractor {
|
|
|
70
66
|
*/
|
|
71
67
|
async getRemoteUrl(): Promise<string> {
|
|
72
68
|
try {
|
|
73
|
-
const { stdout } = await execFileAsync(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
maxBuffer: GIT_MAX_BUFFER,
|
|
79
|
-
timeout: this.config.gitTimeout,
|
|
80
|
-
},
|
|
81
|
-
);
|
|
69
|
+
const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
|
|
70
|
+
cwd: this.repoPath,
|
|
71
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
72
|
+
timeout: this.config.gitTimeout,
|
|
73
|
+
});
|
|
82
74
|
return stdout.trim();
|
|
83
75
|
} catch {
|
|
84
76
|
return ""; // No remote configured
|
|
@@ -144,15 +136,11 @@ export class GitExtractor {
|
|
|
144
136
|
*/
|
|
145
137
|
async getCommitDiff(commitHash: string): Promise<string> {
|
|
146
138
|
try {
|
|
147
|
-
const { stdout } = await execFileAsync(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
maxBuffer: GIT_MAX_BUFFER,
|
|
153
|
-
timeout: this.config.gitTimeout,
|
|
154
|
-
},
|
|
155
|
-
);
|
|
139
|
+
const { stdout } = await execFileAsync("git", ["show", "--no-color", "-p", commitHash], {
|
|
140
|
+
cwd: this.repoPath,
|
|
141
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
142
|
+
timeout: this.config.gitTimeout,
|
|
143
|
+
});
|
|
156
144
|
|
|
157
145
|
// Truncate diff if it exceeds maxDiffSize
|
|
158
146
|
if (stdout.length > this.config.maxDiffSize) {
|
|
@@ -204,15 +192,7 @@ export class GitExtractor {
|
|
|
204
192
|
|
|
205
193
|
if (parts.length < 6) return null;
|
|
206
194
|
|
|
207
|
-
const [
|
|
208
|
-
hash,
|
|
209
|
-
shortHash,
|
|
210
|
-
author,
|
|
211
|
-
authorEmail,
|
|
212
|
-
dateStr,
|
|
213
|
-
subject,
|
|
214
|
-
...bodyParts
|
|
215
|
-
] = parts;
|
|
195
|
+
const [hash, shortHash, author, authorEmail, dateStr, subject, ...bodyParts] = parts;
|
|
216
196
|
|
|
217
197
|
// Parse files and stats from numstat output
|
|
218
198
|
const { files, insertions, deletions } = this.parseNumstat(lines.slice(1));
|
|
@@ -266,9 +246,7 @@ export class GitExtractor {
|
|
|
266
246
|
if (filename.includes(" => ")) {
|
|
267
247
|
const renameParts = filename.match(/(.+)\{(.+) => (.+)\}(.+)?/);
|
|
268
248
|
if (renameParts) {
|
|
269
|
-
files.push(
|
|
270
|
-
`${renameParts[1]}${renameParts[3]}${renameParts[4] || ""}`,
|
|
271
|
-
);
|
|
249
|
+
files.push(`${renameParts[1]}${renameParts[3]}${renameParts[4] || ""}`);
|
|
272
250
|
} else {
|
|
273
251
|
const simpleRename = filename.split(" => ");
|
|
274
252
|
files.push(simpleRename[1] || filename);
|
package/src/git/index.ts
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* Git history indexing module - barrel export
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
export { CommitChunker } from "./chunker.js";
|
|
6
|
+
|
|
7
|
+
// Config
|
|
8
|
+
export { COMMIT_TYPE_PATTERNS, DEFAULT_GIT_CONFIG } from "./config.js";
|
|
9
|
+
export { GitExtractor } from "./extractor.js";
|
|
10
|
+
// Main classes
|
|
11
|
+
export { GitHistoryIndexer } from "./indexer.js";
|
|
12
|
+
export { GitSynchronizer } from "./sync/synchronizer.js";
|
|
5
13
|
// Types
|
|
6
14
|
export type {
|
|
7
15
|
CommitChunk,
|
|
@@ -9,10 +17,10 @@ export type {
|
|
|
9
17
|
GitChangeStats,
|
|
10
18
|
GitConfig,
|
|
11
19
|
GitExtractOptions,
|
|
20
|
+
GitIndexingStatus,
|
|
12
21
|
GitIndexOptions,
|
|
13
22
|
GitIndexStats,
|
|
14
23
|
GitIndexStatus,
|
|
15
|
-
GitIndexingStatus,
|
|
16
24
|
GitProgressCallback,
|
|
17
25
|
GitProgressUpdate,
|
|
18
26
|
GitSearchOptions,
|
|
@@ -20,12 +28,3 @@ export type {
|
|
|
20
28
|
GitSnapshot,
|
|
21
29
|
RawCommit,
|
|
22
30
|
} from "./types.js";
|
|
23
|
-
|
|
24
|
-
// Config
|
|
25
|
-
export { DEFAULT_GIT_CONFIG, COMMIT_TYPE_PATTERNS } from "./config.js";
|
|
26
|
-
|
|
27
|
-
// Main classes
|
|
28
|
-
export { GitHistoryIndexer } from "./indexer.js";
|
|
29
|
-
export { GitExtractor } from "./extractor.js";
|
|
30
|
-
export { CommitChunker } from "./chunker.js";
|
|
31
|
-
export { GitSynchronizer } from "./sync/synchronizer.js";
|
package/src/git/indexer.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GitHistoryIndexer } from "./indexer.js";
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
2
|
import { DEFAULT_GIT_CONFIG } from "./config.js";
|
|
3
|
+
import { GitHistoryIndexer } from "./indexer.js";
|
|
4
4
|
import type { GitConfig } from "./types.js";
|
|
5
5
|
|
|
6
6
|
// Create mock instances
|
|
@@ -100,9 +100,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
100
100
|
// Reset mock instances
|
|
101
101
|
mockExtractorInstance.validateRepository.mockResolvedValue(true);
|
|
102
102
|
mockExtractorInstance.getLatestCommitHash.mockResolvedValue("abc123def456");
|
|
103
|
-
mockExtractorInstance.getRemoteUrl.mockResolvedValue(
|
|
104
|
-
"git@github.com:test/repo.git",
|
|
105
|
-
);
|
|
103
|
+
mockExtractorInstance.getRemoteUrl.mockResolvedValue("git@github.com:test/repo.git");
|
|
106
104
|
mockExtractorInstance.getCommits.mockResolvedValue([]);
|
|
107
105
|
mockExtractorInstance.getCommitDiff.mockResolvedValue("");
|
|
108
106
|
|
|
@@ -138,9 +136,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
138
136
|
collectionExists: vi.fn().mockResolvedValue(false),
|
|
139
137
|
createCollection: vi.fn().mockResolvedValue(undefined),
|
|
140
138
|
deleteCollection: vi.fn().mockResolvedValue(undefined),
|
|
141
|
-
getCollectionInfo: vi
|
|
142
|
-
.fn()
|
|
143
|
-
.mockResolvedValue({ pointsCount: 0, hybridEnabled: false }),
|
|
139
|
+
getCollectionInfo: vi.fn().mockResolvedValue({ pointsCount: 0, hybridEnabled: false }),
|
|
144
140
|
addPoints: vi.fn().mockResolvedValue(undefined),
|
|
145
141
|
addPointsWithSparse: vi.fn().mockResolvedValue(undefined),
|
|
146
142
|
search: vi.fn().mockResolvedValue([]),
|
|
@@ -151,9 +147,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
151
147
|
mockEmbeddings = {
|
|
152
148
|
getDimensions: vi.fn().mockReturnValue(768),
|
|
153
149
|
embed: vi.fn().mockResolvedValue({ embedding: Array(768).fill(0.5) }),
|
|
154
|
-
embedBatch: vi
|
|
155
|
-
.fn()
|
|
156
|
-
.mockResolvedValue([{ embedding: Array(768).fill(0.5) }]),
|
|
150
|
+
embedBatch: vi.fn().mockResolvedValue([{ embedding: Array(768).fill(0.5) }]),
|
|
157
151
|
};
|
|
158
152
|
|
|
159
153
|
indexer = new GitHistoryIndexer(mockQdrant, mockEmbeddings, config);
|
|
@@ -196,9 +190,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
196
190
|
const stats = await indexer.indexHistory("/not/a/repo");
|
|
197
191
|
|
|
198
192
|
expect(stats.status).toBe("failed");
|
|
199
|
-
expect(
|
|
200
|
-
stats.errors?.some((e) => e.includes("Not a valid git repository")),
|
|
201
|
-
).toBe(true);
|
|
193
|
+
expect(stats.errors?.some((e) => e.includes("Not a valid git repository"))).toBe(true);
|
|
202
194
|
});
|
|
203
195
|
|
|
204
196
|
it("should handle empty repository", async () => {
|
|
@@ -253,7 +245,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
253
245
|
expect(progressCallback).toHaveBeenCalledWith(
|
|
254
246
|
expect.objectContaining({
|
|
255
247
|
phase: expect.stringMatching(/extracting|chunking|embedding|storing/),
|
|
256
|
-
})
|
|
248
|
+
})
|
|
257
249
|
);
|
|
258
250
|
});
|
|
259
251
|
});
|
|
@@ -291,9 +283,9 @@ describe("GitHistoryIndexer", () => {
|
|
|
291
283
|
it("should throw error when history not indexed", async () => {
|
|
292
284
|
mockQdrant.collectionExists.mockResolvedValue(false);
|
|
293
285
|
|
|
294
|
-
await expect(
|
|
295
|
-
|
|
296
|
-
)
|
|
286
|
+
await expect(indexer.searchHistory("/test/repo", "query")).rejects.toThrow(
|
|
287
|
+
"Git history not indexed"
|
|
288
|
+
);
|
|
297
289
|
});
|
|
298
290
|
|
|
299
291
|
it("should apply commit type filter", async () => {
|
|
@@ -314,7 +306,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
314
306
|
match: { any: ["fix", "feat"] },
|
|
315
307
|
}),
|
|
316
308
|
]),
|
|
317
|
-
})
|
|
309
|
+
})
|
|
318
310
|
);
|
|
319
311
|
});
|
|
320
312
|
|
|
@@ -341,7 +333,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
341
333
|
range: { lte: "2024-12-31" },
|
|
342
334
|
}),
|
|
343
335
|
]),
|
|
344
|
-
})
|
|
336
|
+
})
|
|
345
337
|
);
|
|
346
338
|
});
|
|
347
339
|
|
|
@@ -448,7 +440,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
448
440
|
mockSynchronizerInstance.initialize.mockResolvedValue(false);
|
|
449
441
|
|
|
450
442
|
await expect(indexer.indexNewCommits("/test/repo")).rejects.toThrow(
|
|
451
|
-
"No previous snapshot found"
|
|
443
|
+
"No previous snapshot found"
|
|
452
444
|
);
|
|
453
445
|
});
|
|
454
446
|
|
|
@@ -484,7 +476,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
484
476
|
it("should ignore snapshot deletion errors", async () => {
|
|
485
477
|
mockQdrant.collectionExists.mockResolvedValue(true);
|
|
486
478
|
mockSynchronizerInstance.deleteSnapshot.mockRejectedValue(
|
|
487
|
-
new Error("Snapshot deletion failed")
|
|
479
|
+
new Error("Snapshot deletion failed")
|
|
488
480
|
);
|
|
489
481
|
|
|
490
482
|
await expect(indexer.clearIndex("/test/repo")).resolves.not.toThrow();
|
|
@@ -568,7 +560,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
568
560
|
]),
|
|
569
561
|
}),
|
|
570
562
|
]),
|
|
571
|
-
})
|
|
563
|
+
})
|
|
572
564
|
);
|
|
573
565
|
});
|
|
574
566
|
});
|
|
@@ -593,9 +585,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
593
585
|
mockExtractorInstance.validateRepository.mockResolvedValue(true);
|
|
594
586
|
mockExtractorInstance.getLatestCommitHash.mockResolvedValue("abc123");
|
|
595
587
|
mockExtractorInstance.getCommits.mockResolvedValue(mockCommits);
|
|
596
|
-
mockExtractorInstance.getCommitDiff.mockRejectedValue(
|
|
597
|
-
new Error("Diff extraction failed"),
|
|
598
|
-
);
|
|
588
|
+
mockExtractorInstance.getCommitDiff.mockRejectedValue(new Error("Diff extraction failed"));
|
|
599
589
|
|
|
600
590
|
const stats = await indexer.indexHistory("/test/repo");
|
|
601
591
|
|
|
@@ -623,9 +613,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
623
613
|
mockExtractorInstance.getLatestCommitHash.mockResolvedValue("abc123");
|
|
624
614
|
mockExtractorInstance.getCommits.mockResolvedValue(mockCommits);
|
|
625
615
|
mockExtractorInstance.getCommitDiff.mockResolvedValue("");
|
|
626
|
-
mockEmbeddings.embedBatch.mockRejectedValue(
|
|
627
|
-
new Error("Embedding API error"),
|
|
628
|
-
);
|
|
616
|
+
mockEmbeddings.embedBatch.mockRejectedValue(new Error("Embedding API error"));
|
|
629
617
|
|
|
630
618
|
const stats = await indexer.indexHistory("/test/repo");
|
|
631
619
|
|
|
@@ -653,9 +641,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
653
641
|
mockExtractorInstance.getLatestCommitHash.mockResolvedValue("abc123");
|
|
654
642
|
mockExtractorInstance.getCommits.mockResolvedValue(mockCommits);
|
|
655
643
|
mockExtractorInstance.getCommitDiff.mockResolvedValue("");
|
|
656
|
-
mockSynchronizerInstance.updateSnapshot.mockRejectedValue(
|
|
657
|
-
new Error("Snapshot write failed"),
|
|
658
|
-
);
|
|
644
|
+
mockSynchronizerInstance.updateSnapshot.mockRejectedValue(new Error("Snapshot write failed"));
|
|
659
645
|
|
|
660
646
|
const stats = await indexer.indexHistory("/test/repo");
|
|
661
647
|
|
|
@@ -684,11 +670,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
684
670
|
...DEFAULT_GIT_CONFIG,
|
|
685
671
|
enableHybridSearch: true,
|
|
686
672
|
};
|
|
687
|
-
const hybridIndexer = new GitHistoryIndexer(
|
|
688
|
-
mockQdrant,
|
|
689
|
-
mockEmbeddings,
|
|
690
|
-
hybridConfig,
|
|
691
|
-
);
|
|
673
|
+
const hybridIndexer = new GitHistoryIndexer(mockQdrant, mockEmbeddings, hybridConfig);
|
|
692
674
|
|
|
693
675
|
const mockCommits = [
|
|
694
676
|
{
|
|
@@ -720,7 +702,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
720
702
|
expect.any(String),
|
|
721
703
|
768,
|
|
722
704
|
"Cosine",
|
|
723
|
-
true
|
|
705
|
+
true
|
|
724
706
|
);
|
|
725
707
|
expect(mockQdrant.addPointsWithSparse).toHaveBeenCalled();
|
|
726
708
|
});
|
|
@@ -758,7 +740,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
758
740
|
mockQdrant.collectionExists.mockResolvedValue(false);
|
|
759
741
|
|
|
760
742
|
await expect(indexer.indexNewCommits("/test/repo")).rejects.toThrow(
|
|
761
|
-
"Git history not indexed"
|
|
743
|
+
"Git history not indexed"
|
|
762
744
|
);
|
|
763
745
|
});
|
|
764
746
|
|
|
@@ -768,7 +750,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
768
750
|
mockSynchronizerInstance.getLastCommitHash.mockReturnValue(null);
|
|
769
751
|
|
|
770
752
|
await expect(indexer.indexNewCommits("/test/repo")).rejects.toThrow(
|
|
771
|
-
"Invalid snapshot: no last commit hash"
|
|
753
|
+
"Invalid snapshot: no last commit hash"
|
|
772
754
|
);
|
|
773
755
|
});
|
|
774
756
|
|
|
@@ -805,7 +787,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
805
787
|
expect(progressCallback).toHaveBeenCalledWith(
|
|
806
788
|
expect.objectContaining({
|
|
807
789
|
phase: expect.stringMatching(/extracting|chunking|embedding|storing/),
|
|
808
|
-
})
|
|
790
|
+
})
|
|
809
791
|
);
|
|
810
792
|
});
|
|
811
793
|
|
|
@@ -814,11 +796,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
814
796
|
...DEFAULT_GIT_CONFIG,
|
|
815
797
|
enableHybridSearch: true,
|
|
816
798
|
};
|
|
817
|
-
const hybridIndexer = new GitHistoryIndexer(
|
|
818
|
-
mockQdrant,
|
|
819
|
-
mockEmbeddings,
|
|
820
|
-
hybridConfig,
|
|
821
|
-
);
|
|
799
|
+
const hybridIndexer = new GitHistoryIndexer(mockQdrant, mockEmbeddings, hybridConfig);
|
|
822
800
|
|
|
823
801
|
mockQdrant.collectionExists.mockResolvedValue(true);
|
|
824
802
|
|
|
@@ -900,9 +878,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
900
878
|
mockSynchronizerInstance.initialize.mockResolvedValue(true);
|
|
901
879
|
mockSynchronizerInstance.getCommitsIndexed.mockReturnValue(100);
|
|
902
880
|
mockSynchronizerInstance.getLastCommitHash.mockReturnValue("latest123");
|
|
903
|
-
mockSynchronizerInstance.getLastIndexedAt.mockReturnValue(
|
|
904
|
-
new Date("2024-01-15T10:00:00Z"),
|
|
905
|
-
);
|
|
881
|
+
mockSynchronizerInstance.getLastIndexedAt.mockReturnValue(new Date("2024-01-15T10:00:00Z"));
|
|
906
882
|
|
|
907
883
|
const status = await indexer.getIndexStatus("/test/repo");
|
|
908
884
|
|
|
@@ -945,9 +921,9 @@ describe("GitHistoryIndexer", () => {
|
|
|
945
921
|
indexer.searchHistory("/test/repo", "query", {
|
|
946
922
|
dateFrom: "2024-12-31",
|
|
947
923
|
dateTo: "2024-01-01",
|
|
948
|
-
})
|
|
924
|
+
})
|
|
949
925
|
).rejects.toThrow(
|
|
950
|
-
"Invalid date range: dateFrom (2024-12-31) must be before dateTo (2024-01-01)"
|
|
926
|
+
"Invalid date range: dateFrom (2024-12-31) must be before dateTo (2024-01-01)"
|
|
951
927
|
);
|
|
952
928
|
});
|
|
953
929
|
|
|
@@ -1076,9 +1052,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
1076
1052
|
mockChunkerInstance.generateChunkId.mockReturnValue("chunk-1");
|
|
1077
1053
|
|
|
1078
1054
|
// All retries fail
|
|
1079
|
-
mockEmbeddings.embedBatch.mockRejectedValue(
|
|
1080
|
-
new Error("Persistent error"),
|
|
1081
|
-
);
|
|
1055
|
+
mockEmbeddings.embedBatch.mockRejectedValue(new Error("Persistent error"));
|
|
1082
1056
|
|
|
1083
1057
|
mockQdrant.collectionExists.mockResolvedValue(false);
|
|
1084
1058
|
mockQdrant.getCollectionInfo.mockResolvedValue({ hybridEnabled: false });
|
|
@@ -1087,9 +1061,7 @@ describe("GitHistoryIndexer", () => {
|
|
|
1087
1061
|
|
|
1088
1062
|
expect(stats.status).toBe("partial");
|
|
1089
1063
|
expect(stats.errors).toBeDefined();
|
|
1090
|
-
expect(stats.errors?.some((e) => e.includes("after 3 attempts"))).toBe(
|
|
1091
|
-
true,
|
|
1092
|
-
);
|
|
1064
|
+
expect(stats.errors?.some((e) => e.includes("after 3 attempts"))).toBe(true);
|
|
1093
1065
|
});
|
|
1094
1066
|
});
|
|
1095
1067
|
});
|