@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.
Files changed (168) hide show
  1. package/.github/workflows/ci.yml +0 -2
  2. package/.github/workflows/claude-code-review.yml +1 -1
  3. package/CHANGELOG.md +6 -0
  4. package/README.md +1 -1
  5. package/biome.json +3 -2
  6. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -1
  7. package/build/code/chunker/tree-sitter-chunker.js +2 -12
  8. package/build/code/chunker/tree-sitter-chunker.js.map +1 -1
  9. package/build/code/indexer.d.ts.map +1 -1
  10. package/build/code/indexer.js +12 -18
  11. package/build/code/indexer.js.map +1 -1
  12. package/build/code/scanner.js +1 -1
  13. package/build/code/scanner.js.map +1 -1
  14. package/build/embeddings/cohere.d.ts +1 -1
  15. package/build/embeddings/cohere.d.ts.map +1 -1
  16. package/build/embeddings/cohere.js +2 -2
  17. package/build/embeddings/cohere.js.map +1 -1
  18. package/build/embeddings/cohere.test.js +1 -5
  19. package/build/embeddings/cohere.test.js.map +1 -1
  20. package/build/embeddings/factory.d.ts +1 -1
  21. package/build/embeddings/factory.d.ts.map +1 -1
  22. package/build/embeddings/factory.js +7 -9
  23. package/build/embeddings/factory.js.map +1 -1
  24. package/build/embeddings/factory.test.js +3 -3
  25. package/build/embeddings/factory.test.js.map +1 -1
  26. package/build/embeddings/ollama.d.ts +1 -1
  27. package/build/embeddings/ollama.d.ts.map +1 -1
  28. package/build/embeddings/ollama.js +6 -8
  29. package/build/embeddings/ollama.js.map +1 -1
  30. package/build/embeddings/ollama.test.js +2 -6
  31. package/build/embeddings/ollama.test.js.map +1 -1
  32. package/build/embeddings/openai.d.ts +1 -1
  33. package/build/embeddings/openai.d.ts.map +1 -1
  34. package/build/embeddings/openai.js +4 -7
  35. package/build/embeddings/openai.js.map +1 -1
  36. package/build/embeddings/openai.test.js +3 -12
  37. package/build/embeddings/openai.test.js.map +1 -1
  38. package/build/embeddings/sparse.test.js +12 -2
  39. package/build/embeddings/sparse.test.js.map +1 -1
  40. package/build/embeddings/voyage.d.ts +1 -1
  41. package/build/embeddings/voyage.d.ts.map +1 -1
  42. package/build/embeddings/voyage.js +2 -3
  43. package/build/embeddings/voyage.js.map +1 -1
  44. package/build/embeddings/voyage.test.js +2 -6
  45. package/build/embeddings/voyage.test.js.map +1 -1
  46. package/build/git/chunker.d.ts.map +1 -1
  47. package/build/git/chunker.js +2 -2
  48. package/build/git/chunker.js.map +1 -1
  49. package/build/git/chunker.test.js +1 -1
  50. package/build/git/chunker.test.js.map +1 -1
  51. package/build/git/extractor.d.ts.map +1 -1
  52. package/build/git/extractor.integration.test.js +9 -5
  53. package/build/git/extractor.integration.test.js.map +1 -1
  54. package/build/git/extractor.js +1 -1
  55. package/build/git/extractor.js.map +1 -1
  56. package/build/git/extractor.test.js +2 -2
  57. package/build/git/extractor.test.js.map +1 -1
  58. package/build/git/index.d.ts +4 -4
  59. package/build/git/index.d.ts.map +1 -1
  60. package/build/git/index.js +3 -3
  61. package/build/git/index.js.map +1 -1
  62. package/build/git/indexer.d.ts.map +1 -1
  63. package/build/git/indexer.js +9 -21
  64. package/build/git/indexer.js.map +1 -1
  65. package/build/git/indexer.test.js +4 -8
  66. package/build/git/indexer.test.js.map +1 -1
  67. package/build/git/sync/synchronizer.d.ts.map +1 -1
  68. package/build/git/sync/synchronizer.js.map +1 -1
  69. package/build/git/sync/synchronizer.test.js +4 -2
  70. package/build/git/sync/synchronizer.test.js.map +1 -1
  71. package/build/index.js +5 -9
  72. package/build/index.js.map +1 -1
  73. package/build/index.test.js +3 -3
  74. package/build/index.test.js.map +1 -1
  75. package/build/logger.d.ts.map +1 -1
  76. package/build/logger.js +1 -9
  77. package/build/logger.js.map +1 -1
  78. package/build/prompts/register.d.ts.map +1 -1
  79. package/build/prompts/register.js.map +1 -1
  80. package/build/qdrant/client.d.ts.map +1 -1
  81. package/build/qdrant/client.js.map +1 -1
  82. package/build/qdrant/client.test.js +10 -34
  83. package/build/qdrant/client.test.js.map +1 -1
  84. package/build/resources/index.d.ts +1 -1
  85. package/build/resources/index.d.ts.map +1 -1
  86. package/build/resources/index.js +1 -1
  87. package/build/resources/index.js.map +1 -1
  88. package/build/tools/code.d.ts.map +1 -1
  89. package/build/tools/code.js +3 -9
  90. package/build/tools/code.js.map +1 -1
  91. package/build/tools/collection.d.ts.map +1 -1
  92. package/build/tools/collection.js +1 -3
  93. package/build/tools/collection.js.map +1 -1
  94. package/build/tools/document.d.ts.map +1 -1
  95. package/build/tools/document.js +1 -1
  96. package/build/tools/document.js.map +1 -1
  97. package/build/tools/federated.d.ts.map +1 -1
  98. package/build/tools/federated.js +15 -6
  99. package/build/tools/federated.js.map +1 -1
  100. package/build/tools/federated.test.js +18 -22
  101. package/build/tools/federated.test.js.map +1 -1
  102. package/build/tools/git-history.d.ts.map +1 -1
  103. package/build/tools/git-history.js +3 -7
  104. package/build/tools/git-history.js.map +1 -1
  105. package/build/tools/index.d.ts.map +1 -1
  106. package/build/tools/index.js.map +1 -1
  107. package/build/tools/logging.d.ts.map +1 -1
  108. package/build/tools/logging.js +1 -3
  109. package/build/tools/logging.js.map +1 -1
  110. package/build/tools/logging.test.js +1 -1
  111. package/build/tools/logging.test.js.map +1 -1
  112. package/build/tools/schemas.d.ts.map +1 -1
  113. package/build/tools/schemas.js +17 -64
  114. package/build/tools/schemas.js.map +1 -1
  115. package/build/tools/search.d.ts.map +1 -1
  116. package/build/tools/search.js +1 -1
  117. package/build/tools/search.js.map +1 -1
  118. package/commitlint.config.js +12 -23
  119. package/package.json +1 -1
  120. package/scripts/verify-providers.js +12 -32
  121. package/src/code/chunker/tree-sitter-chunker.ts +9 -35
  122. package/src/code/indexer.ts +45 -107
  123. package/src/code/scanner.ts +1 -1
  124. package/src/embeddings/cohere.test.ts +17 -45
  125. package/src/embeddings/cohere.ts +10 -17
  126. package/src/embeddings/factory.test.ts +18 -18
  127. package/src/embeddings/factory.ts +18 -25
  128. package/src/embeddings/ollama.test.ts +38 -67
  129. package/src/embeddings/ollama.ts +15 -27
  130. package/src/embeddings/openai.test.ts +17 -53
  131. package/src/embeddings/openai.ts +11 -22
  132. package/src/embeddings/sparse.test.ts +12 -2
  133. package/src/embeddings/voyage.test.ts +39 -80
  134. package/src/embeddings/voyage.ts +9 -13
  135. package/src/git/chunker.test.ts +1 -1
  136. package/src/git/chunker.ts +6 -22
  137. package/src/git/extractor.integration.test.ts +12 -16
  138. package/src/git/extractor.test.ts +21 -35
  139. package/src/git/extractor.ts +14 -36
  140. package/src/git/index.ts +9 -10
  141. package/src/git/indexer.test.ts +29 -57
  142. package/src/git/indexer.ts +38 -86
  143. package/src/git/sync/synchronizer.test.ts +6 -9
  144. package/src/git/sync/synchronizer.ts +2 -5
  145. package/src/index.test.ts +7 -9
  146. package/src/index.ts +34 -80
  147. package/src/logger.ts +3 -14
  148. package/src/prompts/register.ts +3 -10
  149. package/src/qdrant/client.test.ts +63 -169
  150. package/src/qdrant/client.ts +19 -45
  151. package/src/resources/index.ts +4 -10
  152. package/src/tools/code.ts +43 -66
  153. package/src/tools/collection.ts +19 -38
  154. package/src/tools/document.ts +10 -19
  155. package/src/tools/federated.test.ts +34 -57
  156. package/src/tools/federated.ts +88 -108
  157. package/src/tools/git-history.ts +32 -60
  158. package/src/tools/index.ts +1 -4
  159. package/src/tools/logging.test.ts +10 -10
  160. package/src/tools/logging.ts +8 -18
  161. package/src/tools/schemas.ts +23 -78
  162. package/src/tools/search.ts +77 -94
  163. package/tests/code/chunker/tree-sitter-chunker.test.ts +6 -19
  164. package/tests/code/indexer.test.ts +100 -192
  165. package/tests/code/integration.test.ts +61 -117
  166. package/tests/code/scanner.test.ts +12 -39
  167. package/tests/code/sync/snapshot.test.ts +4 -14
  168. package/tests/code/sync/synchronizer.test.ts +10 -40
package/src/tools/code.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import logger from "../logger.js";
7
6
  import type { CodeIndexer } from "../code/indexer.js";
7
+ import logger from "../logger.js";
8
8
  import { withToolLogging } from "./logging.js";
9
9
  import * as schemas from "./schemas.js";
10
10
 
@@ -14,10 +14,7 @@ export interface CodeToolDependencies {
14
14
  codeIndexer: CodeIndexer;
15
15
  }
16
16
 
17
- export function registerCodeTools(
18
- server: McpServer,
19
- deps: CodeToolDependencies,
20
- ): void {
17
+ export function registerCodeTools(server: McpServer, deps: CodeToolDependencies): void {
21
18
  const { codeIndexer } = deps;
22
19
 
23
20
  // index_codebase
@@ -39,10 +36,7 @@ export function registerCodeTools(
39
36
  path,
40
37
  { forceReindex, extensions, ignorePatterns },
41
38
  (progress) => {
42
- log.debug(
43
- { phase: progress.phase, percentage: progress.percentage },
44
- progress.message,
45
- );
39
+ log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
46
40
  if (progressToken !== undefined) {
47
41
  extra.sendNotification({
48
42
  method: "notifications/progress",
@@ -54,7 +48,7 @@ export function registerCodeTools(
54
48
  },
55
49
  });
56
50
  }
57
- },
51
+ }
58
52
  );
59
53
 
60
54
  let statusMessage = `Indexed ${stats.filesIndexed}/${stats.filesScanned} files (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
@@ -69,8 +63,8 @@ export function registerCodeTools(
69
63
  content: [{ type: "text", text: statusMessage }],
70
64
  isError: stats.status === "failed",
71
65
  };
72
- },
73
- ),
66
+ }
67
+ )
74
68
  );
75
69
 
76
70
  // search_code
@@ -82,48 +76,40 @@ export function registerCodeTools(
82
76
  "Search indexed codebase using natural language queries. Returns semantically relevant code chunks with file paths and line numbers.",
83
77
  inputSchema: schemas.SearchCodeSchema,
84
78
  },
85
- withToolLogging(
86
- "search_code",
87
- async ({ path, query, limit, fileTypes, pathPattern }) => {
88
- log.info(
89
- { tool: "search_code", path, query: query.substring(0, 80) },
90
- "Tool called",
91
- );
92
- const results = await codeIndexer.searchCode(path, query, {
93
- limit,
94
- fileTypes,
95
- pathPattern,
96
- });
97
-
98
- if (results.length === 0) {
99
- return {
100
- content: [
101
- { type: "text", text: `No results found for query: "${query}"` },
102
- ],
103
- };
104
- }
105
-
106
- // Format results with file references
107
- const formattedResults = results
108
- .map(
109
- (r, idx) =>
110
- `\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
111
- `File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
112
- `Language: ${r.language}\n\n` +
113
- `${r.content}\n`,
114
- )
115
- .join("\n");
79
+ withToolLogging("search_code", async ({ path, query, limit, fileTypes, pathPattern }) => {
80
+ log.info({ tool: "search_code", path, query: query.substring(0, 80) }, "Tool called");
81
+ const results = await codeIndexer.searchCode(path, query, {
82
+ limit,
83
+ fileTypes,
84
+ pathPattern,
85
+ });
116
86
 
87
+ if (results.length === 0) {
117
88
  return {
118
- content: [
119
- {
120
- type: "text",
121
- text: `Found ${results.length} result(s):\n${formattedResults}`,
122
- },
123
- ],
89
+ content: [{ type: "text", text: `No results found for query: "${query}"` }],
124
90
  };
125
- },
126
- ),
91
+ }
92
+
93
+ // Format results with file references
94
+ const formattedResults = results
95
+ .map(
96
+ (r, idx) =>
97
+ `\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
98
+ `File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
99
+ `Language: ${r.language}\n\n` +
100
+ `${r.content}\n`
101
+ )
102
+ .join("\n");
103
+
104
+ return {
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: `Found ${results.length} result(s):\n${formattedResults}`,
109
+ },
110
+ ],
111
+ };
112
+ })
127
113
  );
128
114
 
129
115
  // reindex_changes
@@ -140,10 +126,7 @@ export function registerCodeTools(
140
126
  const progressToken = extra._meta?.progressToken;
141
127
 
142
128
  const stats = await codeIndexer.reindexChanges(path, (progress) => {
143
- log.debug(
144
- { phase: progress.phase, percentage: progress.percentage },
145
- progress.message,
146
- );
129
+ log.debug({ phase: progress.phase, percentage: progress.percentage }, progress.message);
147
130
  if (progressToken !== undefined) {
148
131
  extra.sendNotification({
149
132
  method: "notifications/progress",
@@ -164,18 +147,14 @@ export function registerCodeTools(
164
147
  message += `- Chunks added: ${stats.chunksAdded}\n`;
165
148
  message += `- Duration: ${(stats.durationMs / 1000).toFixed(1)}s`;
166
149
 
167
- if (
168
- stats.filesAdded === 0 &&
169
- stats.filesModified === 0 &&
170
- stats.filesDeleted === 0
171
- ) {
150
+ if (stats.filesAdded === 0 && stats.filesModified === 0 && stats.filesDeleted === 0) {
172
151
  message = `No changes detected. Codebase is up to date.`;
173
152
  }
174
153
 
175
154
  return {
176
155
  content: [{ type: "text", text: message }],
177
156
  };
178
- }),
157
+ })
179
158
  );
180
159
 
181
160
  // get_index_status
@@ -215,7 +194,7 @@ export function registerCodeTools(
215
194
  return {
216
195
  content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
217
196
  };
218
- }),
197
+ })
219
198
  );
220
199
 
221
200
  // clear_index
@@ -231,10 +210,8 @@ export function registerCodeTools(
231
210
  log.info({ tool: "clear_index", path }, "Tool called");
232
211
  await codeIndexer.clearIndex(path);
233
212
  return {
234
- content: [
235
- { type: "text", text: `Index cleared for codebase at "${path}".` },
236
- ],
213
+ content: [{ type: "text", text: `Index cleared for codebase at "${path}".` }],
237
214
  };
238
- }),
215
+ })
239
216
  );
240
217
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import logger from "../logger.js";
7
6
  import type { EmbeddingProvider } from "../embeddings/base.js";
7
+ import logger from "../logger.js";
8
8
  import type { QdrantManager } from "../qdrant/client.js";
9
9
  import { withToolLogging } from "./logging.js";
10
10
  import * as schemas from "./schemas.js";
@@ -16,10 +16,7 @@ export interface CollectionToolDependencies {
16
16
  embeddings: EmbeddingProvider;
17
17
  }
18
18
 
19
- export function registerCollectionTools(
20
- server: McpServer,
21
- deps: CollectionToolDependencies,
22
- ): void {
19
+ export function registerCollectionTools(server: McpServer, deps: CollectionToolDependencies): void {
23
20
  const { qdrant, embeddings } = deps;
24
21
 
25
22
  // create_collection
@@ -31,31 +28,20 @@ export function registerCollectionTools(
31
28
  "Create a new vector collection in Qdrant. The collection will be configured with the embedding provider's dimensions automatically. Set enableHybrid to true to enable hybrid search combining semantic and keyword search.",
32
29
  inputSchema: schemas.CreateCollectionSchema,
33
30
  },
34
- withToolLogging(
35
- "create_collection",
36
- async ({ name, distance, enableHybrid }) => {
37
- log.info(
38
- { tool: "create_collection", collection: name },
39
- "Tool called",
40
- );
41
- const vectorSize = embeddings.getDimensions();
42
- await qdrant.createCollection(
43
- name,
44
- vectorSize,
45
- distance,
46
- enableHybrid || false,
47
- );
31
+ withToolLogging("create_collection", async ({ name, distance, enableHybrid }) => {
32
+ log.info({ tool: "create_collection", collection: name }, "Tool called");
33
+ const vectorSize = embeddings.getDimensions();
34
+ await qdrant.createCollection(name, vectorSize, distance, enableHybrid || false);
48
35
 
49
- let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
50
- if (enableHybrid) {
51
- message += " Hybrid search is enabled for this collection.";
52
- }
36
+ let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
37
+ if (enableHybrid) {
38
+ message += " Hybrid search is enabled for this collection.";
39
+ }
53
40
 
54
- return {
55
- content: [{ type: "text", text: message }],
56
- };
57
- },
58
- ),
41
+ return {
42
+ content: [{ type: "text", text: message }],
43
+ };
44
+ })
59
45
  );
60
46
 
61
47
  // list_collections
@@ -72,7 +58,7 @@ export function registerCollectionTools(
72
58
  return {
73
59
  content: [{ type: "text", text: JSON.stringify(collections, null, 2) }],
74
60
  };
75
- }),
61
+ })
76
62
  );
77
63
 
78
64
  // get_collection_info
@@ -85,15 +71,12 @@ export function registerCollectionTools(
85
71
  inputSchema: schemas.GetCollectionInfoSchema,
86
72
  },
87
73
  withToolLogging("get_collection_info", async ({ name }) => {
88
- log.info(
89
- { tool: "get_collection_info", collection: name },
90
- "Tool called",
91
- );
74
+ log.info({ tool: "get_collection_info", collection: name }, "Tool called");
92
75
  const info = await qdrant.getCollectionInfo(name);
93
76
  return {
94
77
  content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
95
78
  };
96
- }),
79
+ })
97
80
  );
98
81
 
99
82
  // delete_collection
@@ -108,10 +91,8 @@ export function registerCollectionTools(
108
91
  log.info({ tool: "delete_collection", collection: name }, "Tool called");
109
92
  await qdrant.deleteCollection(name);
110
93
  return {
111
- content: [
112
- { type: "text", text: `Collection "${name}" deleted successfully.` },
113
- ],
94
+ content: [{ type: "text", text: `Collection "${name}" deleted successfully.` }],
114
95
  };
115
- }),
96
+ })
116
97
  );
117
98
  }
@@ -3,9 +3,9 @@
3
3
  */
4
4
 
5
5
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
- import logger from "../logger.js";
7
6
  import type { EmbeddingProvider } from "../embeddings/base.js";
8
7
  import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
8
+ import logger from "../logger.js";
9
9
  import type { QdrantManager } from "../qdrant/client.js";
10
10
  import { withToolLogging } from "./logging.js";
11
11
  import * as schemas from "./schemas.js";
@@ -17,10 +17,7 @@ export interface DocumentToolDependencies {
17
17
  embeddings: EmbeddingProvider;
18
18
  }
19
19
 
20
- export function registerDocumentTools(
21
- server: McpServer,
22
- deps: DocumentToolDependencies,
23
- ): void {
20
+ export function registerDocumentTools(server: McpServer, deps: DocumentToolDependencies): void {
24
21
  const { qdrant, embeddings } = deps;
25
22
 
26
23
  // add_documents
@@ -33,10 +30,7 @@ export function registerDocumentTools(
33
30
  inputSchema: schemas.AddDocumentsSchema,
34
31
  },
35
32
  withToolLogging("add_documents", async ({ collection, documents }) => {
36
- log.info(
37
- { tool: "add_documents", collection, count: documents.length },
38
- "Tool called",
39
- );
33
+ log.info({ tool: "add_documents", collection, count: documents.length }, "Tool called");
40
34
  // Check if collection exists and get info
41
35
  const exists = await qdrant.collectionExists(collection);
42
36
  if (!exists) {
@@ -69,7 +63,7 @@ export function registerDocumentTools(
69
63
  text: string;
70
64
  metadata?: Record<string, any>;
71
65
  },
72
- index: number,
66
+ index: number
73
67
  ) => ({
74
68
  id: doc.id,
75
69
  vector: embeddingResults[index].embedding,
@@ -78,7 +72,7 @@ export function registerDocumentTools(
78
72
  text: doc.text,
79
73
  ...doc.metadata,
80
74
  },
81
- }),
75
+ })
82
76
  );
83
77
 
84
78
  await qdrant.addPointsWithSparse(collection, points);
@@ -91,7 +85,7 @@ export function registerDocumentTools(
91
85
  text: string;
92
86
  metadata?: Record<string, any>;
93
87
  },
94
- index: number,
88
+ index: number
95
89
  ) => ({
96
90
  id: doc.id,
97
91
  vector: embeddingResults[index].embedding,
@@ -99,7 +93,7 @@ export function registerDocumentTools(
99
93
  text: doc.text,
100
94
  ...doc.metadata,
101
95
  },
102
- }),
96
+ })
103
97
  );
104
98
 
105
99
  await qdrant.addPoints(collection, points);
@@ -113,7 +107,7 @@ export function registerDocumentTools(
113
107
  },
114
108
  ],
115
109
  };
116
- }),
110
+ })
117
111
  );
118
112
 
119
113
  // delete_documents
@@ -125,10 +119,7 @@ export function registerDocumentTools(
125
119
  inputSchema: schemas.DeleteDocumentsSchema,
126
120
  },
127
121
  withToolLogging("delete_documents", async ({ collection, ids }) => {
128
- log.info(
129
- { tool: "delete_documents", collection, count: ids.length },
130
- "Tool called",
131
- );
122
+ log.info({ tool: "delete_documents", collection, count: ids.length }, "Tool called");
132
123
  await qdrant.deletePoints(collection, ids);
133
124
  return {
134
125
  content: [
@@ -138,6 +129,6 @@ export function registerDocumentTools(
138
129
  },
139
130
  ],
140
131
  };
141
- }),
132
+ })
142
133
  );
143
134
  }
@@ -1,12 +1,7 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import type { CodeSearchResult } from "../code/types.js";
3
3
  import type { GitSearchResult } from "../git/types.js";
4
- import {
5
- buildCorrelations,
6
- normalizeScores,
7
- calculateRRFScore,
8
- pathsMatch,
9
- } from "./federated.js";
4
+ import { buildCorrelations, calculateRRFScore, normalizeScores, pathsMatch } from "./federated.js";
10
5
 
11
6
  vi.mock("../logger.js", () => ({
12
7
  default: {
@@ -137,9 +132,7 @@ describe("pathsMatch", () => {
137
132
  it("should NOT match paths with different parent directories", () => {
138
133
  // This was the false positive case
139
134
  expect(pathsMatch("app/models/user.ts", "lib/user.ts")).toBe(false);
140
- expect(pathsMatch("src/auth/middleware.ts", "other/middleware.ts")).toBe(
141
- false,
142
- );
135
+ expect(pathsMatch("src/auth/middleware.ts", "other/middleware.ts")).toBe(false);
143
136
  });
144
137
 
145
138
  it("should NOT match completely different filenames", () => {
@@ -162,9 +155,7 @@ describe("pathsMatch", () => {
162
155
  });
163
156
 
164
157
  describe("buildCorrelations", () => {
165
- const createCodeResult = (
166
- overrides: Partial<CodeSearchResult> = {},
167
- ): CodeSearchResult => ({
158
+ const createCodeResult = (overrides: Partial<CodeSearchResult> = {}): CodeSearchResult => ({
168
159
  content: "function test() {}",
169
160
  filePath: "src/utils/helper.ts",
170
161
  startLine: 10,
@@ -175,9 +166,7 @@ describe("buildCorrelations", () => {
175
166
  ...overrides,
176
167
  });
177
168
 
178
- const createGitResult = (
179
- overrides: Partial<GitSearchResult> = {},
180
- ): GitSearchResult => ({
169
+ const createGitResult = (overrides: Partial<GitSearchResult> = {}): GitSearchResult => ({
181
170
  content: "Commit content",
182
171
  commitHash: "abc123def456",
183
172
  shortHash: "abc123d",
@@ -192,9 +181,7 @@ describe("buildCorrelations", () => {
192
181
 
193
182
  it("should find correlations when file paths match", () => {
194
183
  const codeResults = [createCodeResult({ filePath: "src/utils/helper.ts" })];
195
- const gitResults = [
196
- createGitResult({ files: ["src/utils/helper.ts", "src/index.ts"] }),
197
- ];
184
+ const gitResults = [createGitResult({ files: ["src/utils/helper.ts", "src/index.ts"] })];
198
185
 
199
186
  const correlations = buildCorrelations(codeResults, gitResults);
200
187
 
@@ -205,9 +192,7 @@ describe("buildCorrelations", () => {
205
192
  });
206
193
 
207
194
  it("should match partial paths (relative vs absolute)", () => {
208
- const codeResults = [
209
- createCodeResult({ filePath: "/home/user/project/src/utils/helper.ts" }),
210
- ];
195
+ const codeResults = [createCodeResult({ filePath: "/home/user/project/src/utils/helper.ts" })];
211
196
  const gitResults = [createGitResult({ files: ["src/utils/helper.ts"] })];
212
197
 
213
198
  const correlations = buildCorrelations(codeResults, gitResults);
@@ -242,12 +227,8 @@ describe("buildCorrelations", () => {
242
227
 
243
228
  expect(correlations).toHaveLength(1);
244
229
  expect(correlations[0].relatedCommits).toHaveLength(2);
245
- expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain(
246
- "abc123d",
247
- );
248
- expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain(
249
- "xyz789a",
250
- );
230
+ expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain("abc123d");
231
+ expect(correlations[0].relatedCommits.map((c) => c.shortHash)).toContain("xyz789a");
251
232
  });
252
233
 
253
234
  it("should handle multiple code results with different correlations", () => {
@@ -297,9 +278,7 @@ describe("buildCorrelations", () => {
297
278
  });
298
279
 
299
280
  it("should normalize Windows-style paths", () => {
300
- const codeResults = [
301
- createCodeResult({ filePath: "src\\utils\\helper.ts" }),
302
- ];
281
+ const codeResults = [createCodeResult({ filePath: "src\\utils\\helper.ts" })];
303
282
  const gitResults = [createGitResult({ files: ["src/utils/helper.ts"] })];
304
283
 
305
284
  const correlations = buildCorrelations(codeResults, gitResults);
@@ -379,11 +358,11 @@ describe("contextual_search integration", () => {
379
358
 
380
359
  // Get the handler for contextual_search
381
360
  const contextualSearchCall = mockServer.registerTool.mock.calls.find(
382
- (call) => call[0] === "contextual_search",
361
+ (call) => call[0] === "contextual_search"
383
362
  );
384
363
  expect(contextualSearchCall).toBeDefined();
385
364
 
386
- const handler = contextualSearchCall![2];
365
+ const handler = contextualSearchCall?.[2];
387
366
  const result = await handler({
388
367
  path: "/test/repo",
389
368
  query: "test function",
@@ -392,15 +371,13 @@ describe("contextual_search integration", () => {
392
371
  correlate: true,
393
372
  });
394
373
 
395
- expect(mockCodeIndexer.searchCode).toHaveBeenCalledWith(
396
- "/test/repo",
397
- "test function",
398
- { limit: 5 },
399
- );
374
+ expect(mockCodeIndexer.searchCode).toHaveBeenCalledWith("/test/repo", "test function", {
375
+ limit: 5,
376
+ });
400
377
  expect(mockGitHistoryIndexer.searchHistory).toHaveBeenCalledWith(
401
378
  "/test/repo",
402
379
  "test function",
403
- { limit: 5 },
380
+ { limit: 5 }
404
381
  );
405
382
  expect(result.content[0].text).toContain("Code Results");
406
383
  expect(result.content[0].text).toContain("Git History Results");
@@ -422,9 +399,9 @@ describe("contextual_search integration", () => {
422
399
  });
423
400
 
424
401
  const contextualSearchCall = mockServer.registerTool.mock.calls.find(
425
- (call) => call[0] === "contextual_search",
402
+ (call) => call[0] === "contextual_search"
426
403
  );
427
- const handler = contextualSearchCall![2];
404
+ const handler = contextualSearchCall?.[2];
428
405
  const result = await handler({
429
406
  path: "/test/repo",
430
407
  query: "test",
@@ -449,9 +426,9 @@ describe("contextual_search integration", () => {
449
426
  });
450
427
 
451
428
  const contextualSearchCall = mockServer.registerTool.mock.calls.find(
452
- (call) => call[0] === "contextual_search",
429
+ (call) => call[0] === "contextual_search"
453
430
  );
454
- const handler = contextualSearchCall![2];
431
+ const handler = contextualSearchCall?.[2];
455
432
  const result = await handler({
456
433
  path: "/test/repo",
457
434
  query: "test",
@@ -500,9 +477,9 @@ describe("contextual_search integration", () => {
500
477
  });
501
478
 
502
479
  const contextualSearchCall = mockServer.registerTool.mock.calls.find(
503
- (call) => call[0] === "contextual_search",
480
+ (call) => call[0] === "contextual_search"
504
481
  );
505
- const handler = contextualSearchCall![2];
482
+ const handler = contextualSearchCall?.[2];
506
483
  const result = await handler({
507
484
  path: "/test/repo",
508
485
  query: "test",
@@ -574,9 +551,9 @@ describe("federated_search integration", () => {
574
551
  });
575
552
 
576
553
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
577
- (call) => call[0] === "federated_search",
554
+ (call) => call[0] === "federated_search"
578
555
  );
579
- const handler = federatedSearchCall![2];
556
+ const handler = federatedSearchCall?.[2];
580
557
  const result = await handler({
581
558
  paths: ["/repo1", "/repo2"],
582
559
  query: "test function",
@@ -608,9 +585,9 @@ describe("federated_search integration", () => {
608
585
  });
609
586
 
610
587
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
611
- (call) => call[0] === "federated_search",
588
+ (call) => call[0] === "federated_search"
612
589
  );
613
- const handler = federatedSearchCall![2];
590
+ const handler = federatedSearchCall?.[2];
614
591
  const result = await handler({
615
592
  paths: ["/repo1", "/repo2"],
616
593
  query: "test",
@@ -636,9 +613,9 @@ describe("federated_search integration", () => {
636
613
  });
637
614
 
638
615
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
639
- (call) => call[0] === "federated_search",
616
+ (call) => call[0] === "federated_search"
640
617
  );
641
- const handler = federatedSearchCall![2];
618
+ const handler = federatedSearchCall?.[2];
642
619
  await handler({
643
620
  paths: ["/repo1"],
644
621
  query: "test",
@@ -664,9 +641,9 @@ describe("federated_search integration", () => {
664
641
  });
665
642
 
666
643
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
667
- (call) => call[0] === "federated_search",
644
+ (call) => call[0] === "federated_search"
668
645
  );
669
- const handler = federatedSearchCall![2];
646
+ const handler = federatedSearchCall?.[2];
670
647
  await handler({
671
648
  paths: ["/repo1"],
672
649
  query: "test",
@@ -721,9 +698,9 @@ describe("federated_search integration", () => {
721
698
  });
722
699
 
723
700
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
724
- (call) => call[0] === "federated_search",
701
+ (call) => call[0] === "federated_search"
725
702
  );
726
- const handler = federatedSearchCall![2];
703
+ const handler = federatedSearchCall?.[2];
727
704
  const result = await handler({
728
705
  paths: ["/repo1"],
729
706
  query: "test",
@@ -751,9 +728,9 @@ describe("federated_search integration", () => {
751
728
  });
752
729
 
753
730
  const federatedSearchCall = mockServer.registerTool.mock.calls.find(
754
- (call) => call[0] === "federated_search",
731
+ (call) => call[0] === "federated_search"
755
732
  );
756
- const handler = federatedSearchCall![2];
733
+ const handler = federatedSearchCall?.[2];
757
734
  const result = await handler({
758
735
  paths: ["/repo1"],
759
736
  query: "nonexistent query",