codesearch-mcp 0.3.0-alpha → 0.4.0-alpha

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 (224) hide show
  1. package/CHANGELOG.md +59 -6
  2. package/LICENSE +661 -21
  3. package/README.md +33 -17
  4. package/dist/app.js +13 -20
  5. package/dist/src/ast/CallExtractor.d.ts +8 -0
  6. package/dist/src/ast/CallExtractor.js +68 -0
  7. package/dist/src/ast/GraphqlNameExtractor.d.ts +22 -0
  8. package/dist/src/ast/GraphqlNameExtractor.js +240 -0
  9. package/dist/src/ast/ImportEdgeExtractor.d.ts +11 -0
  10. package/dist/src/ast/ImportEdgeExtractor.js +129 -0
  11. package/dist/src/ast/IndexedNodeResolver.d.ts +14 -0
  12. package/dist/src/ast/IndexedNodeResolver.js +217 -0
  13. package/dist/src/ast/MemberExtractor.d.ts +8 -0
  14. package/dist/src/ast/MemberExtractor.js +129 -0
  15. package/dist/src/ast/TopLevelSymbolExtractor.d.ts +5 -0
  16. package/dist/src/ast/TopLevelSymbolExtractor.js +86 -0
  17. package/dist/src/ast/graphql-grammar.d.ts +24 -0
  18. package/dist/src/ast/graphql-grammar.js +93 -0
  19. package/dist/src/ast/node-helpers.d.ts +11 -0
  20. package/dist/src/ast/node-helpers.js +55 -0
  21. package/dist/src/commands/EvalCommand.d.ts +7 -3
  22. package/dist/src/commands/EvalCommand.js +36 -158
  23. package/dist/src/commands/IndexCommand.d.ts +10 -4
  24. package/dist/src/commands/IndexCommand.js +52 -124
  25. package/dist/src/commands/McpCommand.d.ts +6 -3
  26. package/dist/src/commands/McpCommand.js +19 -14
  27. package/dist/src/commands/WebCommand.d.ts +9 -3
  28. package/dist/src/commands/WebCommand.js +17 -8
  29. package/dist/src/commands/eval/baseline-comparison.d.ts +6 -0
  30. package/dist/src/commands/eval/baseline-comparison.js +54 -0
  31. package/dist/src/commands/eval/eval-args.d.ts +6 -0
  32. package/dist/src/commands/eval/eval-args.js +71 -0
  33. package/dist/src/commands/eval/eval-corpora.d.ts +6 -0
  34. package/dist/src/commands/eval/eval-corpora.js +76 -0
  35. package/dist/src/commands/eval/eval-metadata.d.ts +17 -0
  36. package/dist/src/commands/eval/eval-metadata.js +75 -0
  37. package/dist/src/commands/eval/index-inspector.d.ts +3 -0
  38. package/dist/src/commands/eval/index-inspector.js +21 -0
  39. package/dist/src/commands/eval/staged-search.d.ts +3 -0
  40. package/dist/src/commands/eval/staged-search.js +20 -0
  41. package/dist/src/commands/eval/suite-loader.d.ts +8 -0
  42. package/dist/src/commands/eval/suite-loader.js +52 -0
  43. package/dist/src/commands/index/backend-probe.d.ts +2 -0
  44. package/dist/src/commands/index/backend-probe.js +15 -0
  45. package/dist/src/commands/index/embedding-setup.d.ts +3 -0
  46. package/dist/src/commands/index/embedding-setup.js +16 -0
  47. package/dist/src/commands/index/index-summary.d.ts +2 -0
  48. package/dist/src/commands/index/index-summary.js +22 -0
  49. package/dist/src/commands/index/legacy-ollama-guard.d.ts +3 -0
  50. package/dist/src/commands/index/legacy-ollama-guard.js +23 -0
  51. package/dist/src/commands/index/prune-step.d.ts +3 -0
  52. package/dist/src/commands/index/prune-step.js +22 -0
  53. package/dist/src/commands/index/scan-targets.d.ts +8 -0
  54. package/dist/src/commands/index/scan-targets.js +107 -0
  55. package/dist/src/config-store.d.ts +0 -1
  56. package/dist/src/config-store.js +3 -4
  57. package/dist/src/config.d.ts +1 -1
  58. package/dist/src/config.js +1 -1
  59. package/dist/src/constants.d.ts +15 -0
  60. package/dist/src/constants.js +34 -6
  61. package/dist/src/container.d.ts +4 -2
  62. package/dist/src/container.js +7 -5
  63. package/dist/src/db/drizzle-types.d.ts +7 -0
  64. package/dist/src/db/index.d.ts +1 -0
  65. package/dist/src/db/schema-steps.d.ts +8 -0
  66. package/dist/src/db/schema-steps.js +105 -0
  67. package/dist/src/declarations/DeclarationTree.d.ts +1 -1
  68. package/dist/src/eval-results.js +16 -2
  69. package/dist/src/eval-vector-index.d.ts +5 -0
  70. package/dist/src/eval-vector-index.js +60 -0
  71. package/dist/src/help.js +9 -1
  72. package/dist/src/index-args.d.ts +13 -0
  73. package/dist/src/index-args.js +48 -0
  74. package/dist/src/indexing/AnalysisBatcher.d.ts +12 -0
  75. package/dist/src/indexing/AnalysisBatcher.js +28 -0
  76. package/dist/src/indexing/ChunkEmbedder.d.ts +24 -0
  77. package/dist/src/indexing/ChunkEmbedder.js +37 -0
  78. package/dist/src/indexing/ConsoleScanReporter.d.ts +10 -0
  79. package/dist/src/indexing/ConsoleScanReporter.js +73 -0
  80. package/dist/src/indexing/EmbeddingSchemaPreparer.d.ts +14 -0
  81. package/dist/src/indexing/EmbeddingSchemaPreparer.js +66 -0
  82. package/dist/src/indexing/FileIngestor.d.ts +22 -0
  83. package/dist/src/indexing/FileIngestor.js +116 -0
  84. package/dist/src/indexing/IndexStore.d.ts +24 -0
  85. package/dist/src/indexing/IndexStore.js +69 -0
  86. package/dist/src/indexing/StaleFilePruner.d.ts +8 -0
  87. package/dist/src/indexing/StaleFilePruner.js +55 -0
  88. package/dist/src/indexing/analysis-rows.d.ts +4 -0
  89. package/dist/src/indexing/analysis-rows.js +65 -0
  90. package/dist/src/indexing/project-notices.d.ts +2 -0
  91. package/dist/src/indexing/project-notices.js +63 -0
  92. package/dist/src/interfaces/IAstParser.d.ts +2 -1
  93. package/dist/src/interfaces/ICallGraph.d.ts +28 -0
  94. package/dist/src/interfaces/ICallGraph.js +2 -0
  95. package/dist/src/interfaces/IChunkSearch.d.ts +42 -0
  96. package/dist/src/interfaces/IChunkSearch.js +2 -0
  97. package/dist/src/interfaces/IDatabase.d.ts +6 -2
  98. package/dist/src/interfaces/IDependencyGraph.d.ts +24 -0
  99. package/dist/src/interfaces/IDependencyGraph.js +2 -0
  100. package/dist/src/interfaces/IIndexIdentityGuard.d.ts +3 -0
  101. package/dist/src/interfaces/IIndexIdentityGuard.js +2 -0
  102. package/dist/src/interfaces/IIndexedNodeResolver.d.ts +10 -0
  103. package/dist/src/interfaces/IIndexedNodeResolver.js +2 -0
  104. package/dist/src/interfaces/IMcpReadiness.d.ts +12 -0
  105. package/dist/src/interfaces/IMcpReadiness.js +2 -0
  106. package/dist/src/interfaces/IProjectCatalog.d.ts +13 -0
  107. package/dist/src/interfaces/IProjectCatalog.js +2 -0
  108. package/dist/src/interfaces/IProjectRoots.d.ts +3 -0
  109. package/dist/src/interfaces/IProjectRoots.js +2 -0
  110. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +21 -2
  111. package/dist/src/interfaces/IScanReporter.d.ts +13 -0
  112. package/dist/src/interfaces/IScanReporter.js +2 -0
  113. package/dist/src/interfaces/ISymbolLookup.d.ts +49 -0
  114. package/dist/src/interfaces/ISymbolLookup.js +2 -0
  115. package/dist/src/interfaces/IToolPaths.d.ts +62 -0
  116. package/dist/src/interfaces/IToolPaths.js +2 -0
  117. package/dist/src/interfaces/IWorkspaceFiles.d.ts +28 -0
  118. package/dist/src/interfaces/IWorkspaceFiles.js +2 -0
  119. package/dist/src/interfaces/SymbolKind.d.ts +1 -0
  120. package/dist/src/interfaces/SymbolKind.js +2 -0
  121. package/dist/src/interfaces/index.d.ts +12 -1
  122. package/dist/src/project-scope.d.ts +1 -0
  123. package/dist/src/project-scope.js +5 -0
  124. package/dist/src/retrieval-eval.d.ts +2 -1
  125. package/dist/src/retrieval-eval.js +45 -2
  126. package/dist/src/search/CallGraph.d.ts +9 -0
  127. package/dist/src/search/CallGraph.js +84 -0
  128. package/dist/src/search/ChunkSearch.d.ts +23 -0
  129. package/dist/src/search/ChunkSearch.js +115 -0
  130. package/dist/src/search/DependencyGraph.d.ts +19 -0
  131. package/dist/src/search/DependencyGraph.js +180 -0
  132. package/dist/src/search/IndexIdentityGuard.d.ts +11 -0
  133. package/dist/src/search/IndexIdentityGuard.js +37 -0
  134. package/dist/src/search/ProjectCatalog.d.ts +11 -0
  135. package/dist/src/search/ProjectCatalog.js +38 -0
  136. package/dist/src/search/SymbolLookup.d.ts +24 -0
  137. package/dist/src/search/SymbolLookup.js +97 -0
  138. package/dist/src/search/chunks/CandidateFetcher.d.ts +19 -0
  139. package/dist/src/search/chunks/CandidateFetcher.js +40 -0
  140. package/dist/src/search/chunks/IterativeScanGate.d.ts +10 -0
  141. package/dist/src/search/chunks/IterativeScanGate.js +42 -0
  142. package/dist/src/search/chunks/RerankStage.d.ts +15 -0
  143. package/dist/src/search/chunks/RerankStage.js +42 -0
  144. package/dist/src/search/chunks/search-metrics.d.ts +18 -0
  145. package/dist/src/search/chunks/search-metrics.js +32 -0
  146. package/dist/src/search/chunks/search-plan.d.ts +18 -0
  147. package/dist/src/search/chunks/search-plan.js +19 -0
  148. package/dist/src/search/create-search-services.d.ts +26 -0
  149. package/dist/src/search/create-search-services.js +29 -0
  150. package/dist/src/search/index-paths.d.ts +1 -0
  151. package/dist/src/search/index-paths.js +5 -0
  152. package/dist/src/search/symbol-sql.d.ts +13 -0
  153. package/dist/src/search/symbol-sql.js +66 -0
  154. package/dist/src/search/symbols/SymbolGrepFallback.d.ts +19 -0
  155. package/dist/src/search/symbols/SymbolGrepFallback.js +106 -0
  156. package/dist/src/search/symbols/SymbolIndexQuery.d.ts +14 -0
  157. package/dist/src/search/symbols/SymbolIndexQuery.js +52 -0
  158. package/dist/src/search/symbols/SymbolTextSearch.d.ts +13 -0
  159. package/dist/src/search/symbols/SymbolTextSearch.js +84 -0
  160. package/dist/src/search/symbols/TargetColumnGate.d.ts +7 -0
  161. package/dist/src/search/symbols/TargetColumnGate.js +23 -0
  162. package/dist/src/search/symbols/find-symbol-constants.d.ts +4 -0
  163. package/dist/src/search/symbols/find-symbol-constants.js +8 -0
  164. package/dist/src/search/symbols/grep-args.d.ts +3 -0
  165. package/dist/src/search/symbols/grep-args.js +7 -0
  166. package/dist/src/search/symbols/grep-patterns.d.ts +4 -0
  167. package/dist/src/search/symbols/grep-patterns.js +60 -0
  168. package/dist/src/services/AstParserService.d.ts +8 -44
  169. package/dist/src/services/AstParserService.js +19 -938
  170. package/dist/src/services/DatabaseService.d.ts +8 -10
  171. package/dist/src/services/DatabaseService.js +29 -86
  172. package/dist/src/services/IndexerService.d.ts +7 -10
  173. package/dist/src/services/IndexerService.js +36 -255
  174. package/dist/src/services/McpReadiness.d.ts +18 -0
  175. package/dist/src/services/McpReadiness.js +73 -0
  176. package/dist/src/services/McpServer.d.ts +32 -9
  177. package/dist/src/services/McpServer.js +78 -41
  178. package/dist/src/services/WebServerService.d.ts +4 -3
  179. package/dist/src/services/WebServerService.js +16 -8
  180. package/dist/src/setup.d.ts +16 -4
  181. package/dist/src/setup.js +115 -40
  182. package/dist/src/symbol-name.d.ts +1 -0
  183. package/dist/src/symbol-name.js +16 -12
  184. package/dist/src/tools/FindSymbolTool.d.ts +17 -7
  185. package/dist/src/tools/FindSymbolTool.js +54 -35
  186. package/dist/src/tools/GetCallersTool.d.ts +16 -8
  187. package/dist/src/tools/GetCallersTool.js +65 -35
  188. package/dist/src/tools/GetDependenciesTool.d.ts +16 -8
  189. package/dist/src/tools/GetDependenciesTool.js +35 -28
  190. package/dist/src/tools/GetDependentsTool.d.ts +20 -8
  191. package/dist/src/tools/GetDependentsTool.js +47 -33
  192. package/dist/src/tools/ListFilesTool.d.ts +16 -8
  193. package/dist/src/tools/ListFilesTool.js +20 -26
  194. package/dist/src/tools/ListProjectsTool.d.ts +11 -7
  195. package/dist/src/tools/ListProjectsTool.js +13 -11
  196. package/dist/src/tools/McpTool.d.ts +17 -0
  197. package/dist/src/tools/McpTool.js +28 -0
  198. package/dist/src/tools/ReadFileRangeTool.d.ts +16 -8
  199. package/dist/src/tools/ReadFileRangeTool.js +23 -25
  200. package/dist/src/tools/ReadFileTool.d.ts +15 -8
  201. package/dist/src/tools/ReadFileTool.js +19 -24
  202. package/dist/src/tools/SearchCodebaseTool.d.ts +22 -10
  203. package/dist/src/tools/SearchCodebaseTool.js +69 -51
  204. package/dist/src/tools/format-helpers.d.ts +9 -3
  205. package/dist/src/tools/format-helpers.js +59 -14
  206. package/dist/src/tools/tool-args.d.ts +38 -0
  207. package/dist/src/tools/tool-args.js +95 -0
  208. package/dist/src/workspace/GrepRunner.d.ts +15 -0
  209. package/dist/src/workspace/GrepRunner.js +104 -0
  210. package/dist/src/workspace/ProjectRoots.d.ts +22 -0
  211. package/dist/src/workspace/ProjectRoots.js +102 -0
  212. package/dist/src/workspace/ToolPaths.d.ts +31 -0
  213. package/dist/src/workspace/ToolPaths.js +312 -0
  214. package/dist/src/workspace/WorkspaceFiles.d.ts +19 -0
  215. package/dist/src/workspace/WorkspaceFiles.js +285 -0
  216. package/dist/src/workspace/fs-access.d.ts +9 -0
  217. package/dist/src/workspace/fs-access.js +47 -0
  218. package/dist/src/workspace/path-helpers.d.ts +3 -0
  219. package/dist/src/workspace/path-helpers.js +43 -0
  220. package/package.json +4 -3
  221. package/dist/src/interfaces/ISearcher.d.ts +0 -46
  222. package/dist/src/services/SearcherService.d.ts +0 -235
  223. package/dist/src/services/SearcherService.js +0 -1118
  224. /package/dist/src/{interfaces/ISearcher.js → db/drizzle-types.js} +0 -0
@@ -1,76 +1,94 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SearchCodebaseTool = void 0;
4
+ const zod_1 = require("zod");
4
5
  const format_helpers_1 = require("./format-helpers");
6
+ const McpTool_1 = require("./McpTool");
7
+ const tool_args_1 = require("./tool-args");
5
8
  const result_shaping_1 = require("../search/result-shaping");
6
- class SearchCodebaseTool {
7
- searcher;
8
- defaultLimit;
9
- maxLimit;
10
- defaultThreshold;
11
- logger;
12
- constructor(searcher, defaultLimit, maxLimit, defaultThreshold, logger) {
13
- this.searcher = searcher;
14
- this.defaultLimit = defaultLimit;
15
- this.maxLimit = maxLimit;
16
- this.defaultThreshold = defaultThreshold;
17
- this.logger = logger;
9
+ const constants_1 = require("../constants");
10
+ function searchArgs(defaultLimit, maxLimit, defaultThreshold) {
11
+ const shown = (0, constants_1.formatThreshold)(defaultThreshold);
12
+ const stricter = defaultThreshold > 0 ? ' Use a stricter value a little below the default for precise technical queries.' : '';
13
+ const raise = defaultThreshold < 1 ? ' Raise it (up to 1.0) when a search finds nothing.' : '';
14
+ return zod_1.z.object({
15
+ query: (0, tool_args_1.requiredText)(),
16
+ limit: (0, tool_args_1.intInRange)(`Max results (default ${defaultLimit}, max ${maxLimit})`, { min: 1, max: maxLimit, fallback: defaultLimit }),
17
+ project: (0, tool_args_1.optionalText)('Filter by project name (optional). Use list_projects to see available names.'),
18
+ include_neighbors: (0, tool_args_1.flag)('Also include the chunk before and after each result from the same file (Small-to-Big retrieval). Useful when results look truncated.'),
19
+ include_graph_context: (0, tool_args_1.flag)('Append direct imports and dependents list to each result. Shows "Imports: X · Y" and "Used by: Z" without extra tool calls.'),
20
+ threshold: (0, tool_args_1.numberInRange)(`Max cosine distance (0–1). Lower = stricter. Default: ${shown}.${stricter}${raise}`, { min: 0, max: 1 }),
21
+ per_file: (0, tool_args_1.intInRange)(`Max results from one file (integer ≥ 1, default ${result_shaping_1.DEFAULT_PER_FILE}). Skipped results are backfilled from the next candidates. Raise it when one file is expected to hold several answers (e.g. several methods of one class).`, { min: 1, fallback: result_shaping_1.DEFAULT_PER_FILE }),
22
+ rerank: (0, tool_args_1.flag)('Override whether this call reranks, regardless of config. false skips reranking (use for "what does X do" / "where is X" lookups described without an exact name — reranking measurably hurts these; if you know the exact symbol name, use find_symbol instead). true forces reranking on if a reranker is configured (no-op otherwise). Omit to use config.reranker.enabled (fine for conceptual/architectural queries, where reranking is roughly neutral).'),
23
+ });
24
+ }
25
+ const DISTANCE_NOTE = '> Lower distance = closer match. Results may be reranked, so their order need not follow the distance.';
26
+ function renderNoResults(cause, project, threshold) {
27
+ const first = 'No relevant results found.';
28
+ switch (cause) {
29
+ case 'empty-scope':
30
+ return `${first}\n${project
31
+ ? `Project \`${project}\` has no indexed chunks. Check the name with \`list_projects\`.`
32
+ : 'The index holds no chunks. Run `rag-index`.'}`;
33
+ case 'no-candidates': {
34
+ const steps = ['rephrase the query', 'use `find_symbol` for a known name'];
35
+ if (project)
36
+ steps.push('drop `project`');
37
+ if (threshold < 1)
38
+ steps.push('pass a higher `threshold`');
39
+ const last = steps.pop();
40
+ return `${first}\nNo chunk ${project ? `of project \`${project}\` ` : ''}came within the distance threshold, which does not show the code is absent. Next: ${steps.join(', ')}, or ${last}.`;
41
+ }
42
+ case 'lookup-failed':
43
+ return `${first}\nThe cause could not be determined, so this does not show the code is absent.`;
18
44
  }
19
- definition() {
20
- return {
45
+ }
46
+ class SearchCodebaseTool extends McpTool_1.McpTool {
47
+ chunks;
48
+ dependencies;
49
+ defaultThreshold;
50
+ constructor(chunks, dependencies, defaultLimit, maxLimit, defaultThreshold, logger) {
51
+ super({
21
52
  name: 'search_codebase',
22
- description: 'Search local project codebase by semantic similarity. Each result shows File, Project, an optional Symbol breadcrumb (owning declaration, e.g. `IndexerService.ingestFile`), Line, the code, and Relevance.',
23
- inputSchema: {
24
- type: 'object',
25
- properties: {
26
- query: { type: 'string' },
27
- limit: { type: 'number', description: `Max results (default ${this.defaultLimit}, max ${this.maxLimit})` },
28
- project: { type: 'string', description: 'Filter by project name (optional). Use list_projects to see available names.' },
29
- include_neighbors: { type: 'boolean', description: 'Also include the chunk before and after each result from the same file (Small-to-Big retrieval). Useful when results look truncated.' },
30
- include_graph_context: { type: 'boolean', description: 'Append direct imports and dependents list to each result. Shows "Imports: X · Y" and "Used by: Z" without extra tool calls.' },
31
- threshold: { type: 'number', description: `Max cosine distance (0–1). Lower = stricter. Default: ${this.defaultThreshold}. Use 0.40–0.45 for precise technical queries, 0.55–0.60 for broad exploration.` },
32
- per_file: { type: 'number', description: `Max results from one file (integer ≥ 1, default ${result_shaping_1.DEFAULT_PER_FILE}). Skipped results are backfilled from the next candidates. Raise it when one file is expected to hold several answers (e.g. several methods of one class).` },
33
- rerank: { type: 'boolean', description: 'Override whether this call reranks, regardless of config. false skips reranking (use for "what does X do" / "where is X" lookups described without an exact name — reranking measurably hurts these; if you know the exact symbol name, use find_symbol instead). true forces reranking on if a reranker is configured (no-op otherwise). Omit to use config.reranker.enabled (fine for conceptual/architectural queries, where reranking is roughly neutral).' },
34
- },
35
- required: ['query'],
36
- },
37
- };
53
+ description: 'Search local project codebase by semantic similarity. Each result shows File, Project, an optional Symbol breadcrumb (owning declaration, e.g. `IndexerService.ingestFile`), Line, the code, and Distance (cosine distance: lower is closer).',
54
+ args: searchArgs(defaultLimit, maxLimit, defaultThreshold),
55
+ }, logger);
56
+ this.chunks = chunks;
57
+ this.dependencies = dependencies;
58
+ this.defaultThreshold = defaultThreshold;
38
59
  }
39
- async handle(args) {
60
+ async run(args) {
40
61
  const t0 = Date.now();
41
- const raw = String(args.query);
42
- const limit = Math.min(Math.max(1, Number(args.limit) || this.defaultLimit), this.maxLimit);
43
- const project = args.project ? String(args.project) : null;
44
- const includeNeighbors = Boolean(args.include_neighbors);
45
- const includeGraphContext = Boolean(args.include_graph_context);
46
- const rawThreshold = args.threshold;
47
- const parsedThreshold = Number(rawThreshold);
48
- const threshold = rawThreshold != null && !isNaN(parsedThreshold)
49
- ? Math.min(1, Math.max(0, parsedThreshold))
50
- : undefined;
51
- const rerank = typeof args.rerank === 'boolean' ? args.rerank : undefined;
52
- const perFile = Number.isInteger(args.per_file) && args.per_file >= 1 ? args.per_file : result_shaping_1.DEFAULT_PER_FILE;
53
- const { query, translated } = await this.searcher.prepareQuery(raw);
54
- const results = await this.searcher.searchChunks(query, limit, project, threshold, rerank, perFile);
62
+ const raw = args.query;
63
+ const limit = args.limit;
64
+ const project = args.project ?? null;
65
+ const includeNeighbors = args.include_neighbors ?? false;
66
+ const includeGraphContext = args.include_graph_context ?? false;
67
+ const threshold = args.threshold;
68
+ const rerank = args.rerank;
69
+ const perFile = args.per_file;
70
+ const { query, translated } = await this.chunks.prepareQuery(raw);
71
+ const results = await this.chunks.searchChunks(query, limit, project, threshold, rerank, perFile);
55
72
  this.logger.debug('SearchCodebaseTool: handle', {
56
73
  query: raw.slice(0, 120), project, threshold: threshold ?? this.defaultThreshold,
57
74
  limit, perFile, translated, count: results.length, ms: Date.now() - t0,
58
75
  });
59
76
  if (results.length === 0) {
60
- return (0, format_helpers_1.textResponse)('No relevant results found.');
77
+ const cause = await this.chunks.explainNoResults(project);
78
+ return (0, format_helpers_1.textResponse)(renderNoResults(cause, project, threshold ?? this.defaultThreshold));
61
79
  }
62
- const header = translated ? `> Query translated: "${raw}" → "${query}"\n\n` : '';
80
+ const header = `${translated ? `> Query translated: "${raw}" → "${query}"\n` : ''}${DISTANCE_NOTE}\n\n`;
63
81
  let graphCtx = null;
64
82
  if (includeGraphContext) {
65
- graphCtx = await this.searcher.fetchGraphContext(results);
83
+ graphCtx = await this.dependencies.fetchGraphContext(results);
66
84
  }
67
85
  if (includeNeighbors) {
68
- const withNeighbors = await this.searcher.fetchNeighbors(results);
86
+ const withNeighbors = await this.chunks.fetchNeighbors(results);
69
87
  const blocks = withNeighbors.map(({ result, prevChunk, nextChunk }) => {
70
88
  const parts = [];
71
89
  if (prevChunk)
72
90
  parts.push((0, format_helpers_1.formatChunk)(prevChunk, 'prev chunk'));
73
- parts.push(`${(0, format_helpers_1.formatChunk)(result)}\n**Relevance:** ${result.score}${(0, format_helpers_1.formatGraphContext)(graphCtx?.get(result.filePath))}`);
91
+ parts.push(`${(0, format_helpers_1.formatChunk)(result)}\n**Distance:** ${result.score}${(0, format_helpers_1.formatGraphContext)(graphCtx?.get(result.filePath))}`);
74
92
  if (nextChunk)
75
93
  parts.push((0, format_helpers_1.formatChunk)(nextChunk, 'next chunk'));
76
94
  return parts.join('\n\n');
@@ -78,7 +96,7 @@ class SearchCodebaseTool {
78
96
  return (0, format_helpers_1.textResponse)(header + blocks.join('\n\n---\n\n'));
79
97
  }
80
98
  const text = results
81
- .map(r => `${(0, format_helpers_1.formatChunk)(r)}\n**Relevance:** ${r.score}${(0, format_helpers_1.formatGraphContext)(graphCtx?.get(r.filePath))}`)
99
+ .map(r => `${(0, format_helpers_1.formatChunk)(r)}\n**Distance:** ${r.score}${(0, format_helpers_1.formatGraphContext)(graphCtx?.get(r.filePath))}`)
82
100
  .join('\n\n---\n\n');
83
101
  return (0, format_helpers_1.textResponse)(header + text);
84
102
  }
@@ -1,12 +1,18 @@
1
- import type { ChunkRow, DependencyRow, GraphContext, FileRangeResult, PathFailure, PathRefusal } from '../services/SearcherService';
1
+ import type { ChunkRow } from '../interfaces/IChunkSearch';
2
+ import type { DependencyRow, GraphContext } from '../interfaces/IDependencyGraph';
3
+ import type { FileRangeResult } from '../interfaces/IWorkspaceFiles';
4
+ import type { PathFailure, PathRefusal } from '../interfaces/IToolPaths';
2
5
  import type { ToolResponse } from '../interfaces/IMcpToolHandler';
3
6
  import type { ILogger } from '../interfaces/ILogger';
4
7
  export declare function textResponse(text: string): ToolResponse;
5
8
  export declare function errorResponse(text: string): ToolResponse;
6
9
  export declare function formatChunk(r: ChunkRow, label?: string): string;
7
10
  export declare function formatGraphContext(ctx: GraphContext | undefined): string;
8
- export declare function formatDependencies(filePath: string, rows: DependencyRow[], transitive: boolean): string;
9
- export declare function formatDependents(filePath: string, rows: DependencyRow[], transitive: boolean): string;
11
+ export declare function formatDependencies(filePath: string, rows: DependencyRow[], transitive: boolean, symbol?: string | null): string;
12
+ export declare function formatDependents(filePath: string, rows: DependencyRow[], transitive: boolean, symbol?: string | null): string;
13
+ export declare function formatTransitiveSection(rows: DependencyRow[], symbol: string | null, side: 'dependencies' | 'dependents'): string;
14
+ export declare function formatDependenciesFilterMiss(filePath: string, symbol: string, unfiltered: DependencyRow[]): string;
15
+ export declare function formatDependentsFilterMiss(filePath: string, symbol: string, unfiltered: DependencyRow[]): string;
10
16
  export type PathKind = 'file' | 'directory';
11
17
  export declare const PATH_EXAMPLE: Record<PathKind, string>;
12
18
  export declare function pathArgDescription(kind?: PathKind): string;
@@ -7,6 +7,9 @@ exports.formatChunk = formatChunk;
7
7
  exports.formatGraphContext = formatGraphContext;
8
8
  exports.formatDependencies = formatDependencies;
9
9
  exports.formatDependents = formatDependents;
10
+ exports.formatTransitiveSection = formatTransitiveSection;
11
+ exports.formatDependenciesFilterMiss = formatDependenciesFilterMiss;
12
+ exports.formatDependentsFilterMiss = formatDependentsFilterMiss;
10
13
  exports.pathArgDescription = pathArgDescription;
11
14
  exports.pathErrorText = pathErrorText;
12
15
  exports.pathErrorResponse = pathErrorResponse;
@@ -101,7 +104,13 @@ function groupByFile(rows, key) {
101
104
  function directHeader(fileCount) {
102
105
  return `**Direct:** ${fileCount} file${fileCount === 1 ? '' : 's'}`;
103
106
  }
104
- function formatDependencies(filePath, rows, transitive) {
107
+ function transitiveLines(indirect, symbol, line) {
108
+ const note = symbol ? ' *(not filtered by `symbol`, which applies to direct imports only)*' : '';
109
+ return [`**Transitive:**${note}`, ...indirect.map(line)];
110
+ }
111
+ const dependencyLine = (r) => ` → \`${r.toFile}\` (depth ${r.depth}, via \`${r.via}\`)`;
112
+ const dependentLine = (r) => ` ← \`${r.fromFile}\` (depth ${r.depth}, via \`${r.via}\`)`;
113
+ function formatDependencies(filePath, rows, transitive, symbol = null) {
105
114
  const direct = rows.filter(r => !transitive || (r.depth ?? 1) === 1);
106
115
  const indirect = transitive ? rows.filter(r => (r.depth ?? 1) > 1) : [];
107
116
  const groups = groupByFile(direct, 'toFile');
@@ -110,15 +119,11 @@ function formatDependencies(filePath, rows, transitive) {
110
119
  const names = groupNames(groupRows);
111
120
  lines.push(` → \`${toFile}\`${names ? ` — ${names}` : ''}`);
112
121
  }
113
- if (indirect.length > 0) {
114
- lines.push('\n**Transitive:**');
115
- for (const r of indirect) {
116
- lines.push(` → \`${r.toFile}\` (depth ${r.depth}, via \`${r.via}\`)`);
117
- }
118
- }
122
+ if (indirect.length > 0)
123
+ lines.push('', ...transitiveLines(indirect, symbol, dependencyLine));
119
124
  return lines.join('\n');
120
125
  }
121
- function formatDependents(filePath, rows, transitive) {
126
+ function formatDependents(filePath, rows, transitive, symbol = null) {
122
127
  const direct = rows.filter(r => !transitive || (r.depth ?? 1) === 1);
123
128
  const indirect = transitive ? rows.filter(r => (r.depth ?? 1) > 1) : [];
124
129
  const groups = groupByFile(direct, 'fromFile');
@@ -131,14 +136,53 @@ function formatDependents(filePath, rows, transitive) {
131
136
  line += ` · in ${inPart}`;
132
137
  lines.push(line);
133
138
  }
134
- if (indirect.length > 0) {
135
- lines.push('\n**Transitive:**');
136
- for (const r of indirect) {
137
- lines.push(` ← \`${r.fromFile}\` (depth ${r.depth}, via \`${r.via}\`)`);
138
- }
139
- }
139
+ if (indirect.length > 0)
140
+ lines.push('', ...transitiveLines(indirect, symbol, dependentLine));
140
141
  return lines.join('\n');
141
142
  }
143
+ function formatTransitiveSection(rows, symbol, side) {
144
+ const indirect = rows.filter(r => (r.depth ?? 1) > 1);
145
+ if (indirect.length === 0)
146
+ return '';
147
+ return transitiveLines(indirect, symbol, side === 'dependencies' ? dependencyLine : dependentLine).join('\n');
148
+ }
149
+ function filterMissSummary(rows, key) {
150
+ const groups = groupByFile(rows, key);
151
+ const names = [];
152
+ for (const r of rows) {
153
+ if (r.toSymbol !== null && !names.includes(r.toSymbol))
154
+ names.push(r.toSymbol);
155
+ }
156
+ const nameless = [...groups.values()].filter(g => g.some(r => r.toSymbol === null)).length;
157
+ return { files: groups.size, names: capped(names, MAX_NAMES), nameless };
158
+ }
159
+ function filterMissTail(nameless) {
160
+ const lines = [];
161
+ if (nameless > 0) {
162
+ lines.push(`${nameless} of them ${nameless === 1 ? 'uses' : 'use'} a default, namespace, side-effect or \`export *\` import, which the \`symbol\` filter cannot match.`);
163
+ }
164
+ lines.push('Retry without `symbol` to list them.');
165
+ return lines;
166
+ }
167
+ function formatDependenciesFilterMiss(filePath, symbol, unfiltered) {
168
+ const { files, names, nameless } = filterMissSummary(unfiltered, 'toFile');
169
+ const importing = names ? `, importing: ${names}` : '';
170
+ return [
171
+ `No import of \`${symbol}\` found in: ${filePath}`,
172
+ `It has ${files} direct ${files === 1 ? 'dependency' : 'dependencies'}${importing}.`,
173
+ ...filterMissTail(nameless),
174
+ ].join('\n');
175
+ }
176
+ function formatDependentsFilterMiss(filePath, symbol, unfiltered) {
177
+ const { files, names, nameless } = filterMissSummary(unfiltered, 'fromFile');
178
+ const using = names ? `, using: ${names}` : '';
179
+ return [
180
+ `**Dependents of** \`${filePath}\``,
181
+ '',
182
+ `**Direct:** No direct import of \`${symbol}\`. ${files} ${files === 1 ? 'file imports' : 'files import'} this file${using}.`,
183
+ ...filterMissTail(nameless),
184
+ ].join('\n');
185
+ }
142
186
  exports.PATH_EXAMPLE = { file: 'my-app/src/index.ts', directory: 'my-app/src' };
143
187
  function pathArgDescription(kind = 'file') {
144
188
  const root = kind === 'directory' ? ', "." for the workspace root' : '';
@@ -184,6 +228,7 @@ function pathErrorText(argName, input, relPath, err, kind = 'file') {
184
228
  return `Not indexed: ${relPath}\n${notIndexedExplanation(err.explanation)}`;
185
229
  case 'not_a_file': return `Not a file: ${relPath}`;
186
230
  case 'not_a_directory': return `Not a directory: ${relPath}`;
231
+ case 'binary': return `Binary file: ${relPath}`;
187
232
  case 'unreadable': return `Cannot read: ${relPath} (${err.code})`;
188
233
  case 'past_end': return `${relPath} has ${err.lineCount} lines; start_line ${err.startLine} is past the end`;
189
234
  case 'bad_range': return `end_line ${err.endLine} is before start_line ${err.startLine}`;
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import type { PathKind } from './format-helpers';
3
+ export declare function requiredText(description?: string): z.ZodType<string>;
4
+ export declare function optionalText(description?: string): z.ZodType<string | undefined>;
5
+ export declare function pathArg(description: string, kind?: PathKind): z.ZodType<string>;
6
+ export declare function optionalPathArg(description: string): z.ZodType<string | undefined>;
7
+ interface IntRange {
8
+ min?: number;
9
+ max?: number;
10
+ }
11
+ export declare function intInRange(description: string, range: IntRange & {
12
+ fallback: number;
13
+ }): z.ZodType<number>;
14
+ export declare function intInRange(description: string, range?: IntRange): z.ZodType<number | undefined>;
15
+ export declare function numberInRange(description: string, range: {
16
+ min: number;
17
+ max: number;
18
+ }): z.ZodType<number | undefined>;
19
+ export declare function flag(description: string): z.ZodType<boolean | undefined>;
20
+ export declare function oneOf<T extends string>(description: string, values: readonly T[], fallback: T): z.ZodType<T>;
21
+ export declare function toInputSchema(schema: z.ZodObject): Record<string, unknown>;
22
+ export type ArgFailure = {
23
+ kind: 'missing-path';
24
+ arg: string;
25
+ pathKind: PathKind;
26
+ } | {
27
+ kind: 'invalid';
28
+ text: string;
29
+ };
30
+ export type ParsedArgs<T> = {
31
+ ok: true;
32
+ args: T;
33
+ } | {
34
+ ok: false;
35
+ failure: ArgFailure;
36
+ };
37
+ export declare function parseToolArgs<S extends z.ZodObject>(schema: S, raw: Record<string, unknown>): ParsedArgs<z.output<S>>;
38
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requiredText = requiredText;
4
+ exports.optionalText = optionalText;
5
+ exports.pathArg = pathArg;
6
+ exports.optionalPathArg = optionalPathArg;
7
+ exports.intInRange = intInRange;
8
+ exports.numberInRange = numberInRange;
9
+ exports.flag = flag;
10
+ exports.oneOf = oneOf;
11
+ exports.toInputSchema = toInputSchema;
12
+ exports.parseToolArgs = parseToolArgs;
13
+ const zod_1 = require("zod");
14
+ const requiredPaths = zod_1.z.registry();
15
+ const isBlank = (value) => value == null || (typeof value === 'string' && !value.trim());
16
+ const blankToUndefined = (value) => (isBlank(value) ? undefined : value);
17
+ const emptyPathToUndefined = (value) => (value === '' ? undefined : value ?? undefined);
18
+ const readNumber = (value) => {
19
+ const present = blankToUndefined(value);
20
+ return typeof present === 'string' ? Number(present) : present;
21
+ };
22
+ const readFlag = (value) => {
23
+ const present = blankToUndefined(value);
24
+ if (present === 'true')
25
+ return true;
26
+ if (present === 'false')
27
+ return false;
28
+ return present;
29
+ };
30
+ const clamp = (value, min = -Infinity, max = Infinity) => Math.min(Math.max(min, value), max);
31
+ function described(schema, description) {
32
+ return description === undefined ? schema : schema.describe(description);
33
+ }
34
+ function requiredText(description) {
35
+ return described(zod_1.z.preprocess(blankToUndefined, zod_1.z.string()), description);
36
+ }
37
+ function optionalText(description) {
38
+ return described(zod_1.z.preprocess(blankToUndefined, zod_1.z.string().optional()), description);
39
+ }
40
+ function pathArg(description, kind = 'file') {
41
+ const schema = zod_1.z.preprocess(emptyPathToUndefined, zod_1.z.string()).describe(description);
42
+ requiredPaths.add(schema, { pathKind: kind });
43
+ return schema;
44
+ }
45
+ function optionalPathArg(description) {
46
+ return zod_1.z.preprocess(emptyPathToUndefined, zod_1.z.string().optional()).describe(description);
47
+ }
48
+ function intInRange(description, range = {}) {
49
+ const { min, max, fallback } = range;
50
+ const inRange = zod_1.z.number().optional().transform(value => {
51
+ const picked = value === undefined ? fallback : Math.trunc(value);
52
+ return picked === undefined ? undefined : clamp(picked, min, max);
53
+ });
54
+ return zod_1.z.preprocess(readNumber, inRange).describe(description);
55
+ }
56
+ function numberInRange(description, range) {
57
+ const inRange = zod_1.z.number().optional().transform(value => value === undefined ? undefined : clamp(value, range.min, range.max));
58
+ return zod_1.z.preprocess(readNumber, inRange).describe(description);
59
+ }
60
+ function flag(description) {
61
+ return zod_1.z.preprocess(readFlag, zod_1.z.boolean().optional()).describe(description);
62
+ }
63
+ function oneOf(description, values, fallback) {
64
+ const member = zod_1.z.enum(values).optional().transform(value => (value ?? fallback));
65
+ return zod_1.z.preprocess(blankToUndefined, member).describe(description);
66
+ }
67
+ function toInputSchema(schema) {
68
+ const { $schema: _, ...inputSchema } = zod_1.z.toJSONSchema(schema, { io: 'input' });
69
+ return inputSchema;
70
+ }
71
+ function expectation(issue) {
72
+ return issue.code === 'invalid_value'
73
+ ? `one of ${issue.values.join(', ')}`
74
+ : `a ${issue.expected}`;
75
+ }
76
+ function parseToolArgs(schema, raw) {
77
+ const parsed = schema.safeParse(raw);
78
+ if (parsed.success)
79
+ return { ok: true, args: parsed.data };
80
+ const lines = [];
81
+ for (const [arg, argSchema] of Object.entries(schema.shape)) {
82
+ const issue = parsed.error.issues.find(i => i.path[0] === arg);
83
+ if (!issue)
84
+ continue;
85
+ if (!isBlank(raw[arg])) {
86
+ lines.push(`Invalid ${arg}: expected ${expectation(issue)}`);
87
+ continue;
88
+ }
89
+ const path = requiredPaths.get(argSchema);
90
+ if (path)
91
+ return { ok: false, failure: { kind: 'missing-path', arg, pathKind: path.pathKind } };
92
+ lines.push(`Missing ${arg}`);
93
+ }
94
+ return { ok: false, failure: { kind: 'invalid', text: lines.join('\n') } };
95
+ }
@@ -0,0 +1,15 @@
1
+ import type { ILogger } from '../interfaces';
2
+ export interface GrepRun {
3
+ ok: boolean;
4
+ output: string | null;
5
+ failure: string | null;
6
+ }
7
+ export declare class GrepRunner {
8
+ private logger;
9
+ private readonly liveGreps;
10
+ private readonly killLiveGreps;
11
+ constructor(logger: ILogger);
12
+ run(args: string[]): Promise<GrepRun>;
13
+ private trackGrep;
14
+ private untrackGrep;
15
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GrepRunner = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const fs_access_1 = require("./fs-access");
6
+ const GREP_TIMEOUT_MS = 60_000;
7
+ const FAILURE_MAX_CHARS = 200;
8
+ const STDERR_MAX_BYTES = 4096;
9
+ const capFailure = (text) => {
10
+ const capped = text.slice(0, FAILURE_MAX_CHARS);
11
+ return /[\uD800-\uDBFF]$/.test(capped) ? capped.slice(0, -1) : capped;
12
+ };
13
+ class GrepRunner {
14
+ logger;
15
+ liveGreps = new Set();
16
+ killLiveGreps = () => {
17
+ for (const proc of this.liveGreps)
18
+ proc.kill('SIGKILL');
19
+ };
20
+ constructor(logger) {
21
+ this.logger = logger;
22
+ }
23
+ run(args) {
24
+ return new Promise((resolve) => {
25
+ let proc;
26
+ try {
27
+ proc = (0, child_process_1.spawn)('grep', args, { stdio: ['ignore', 'pipe', 'pipe'] });
28
+ }
29
+ catch (err) {
30
+ this.logger.error('GrepRunner: grep spawn failed', { err });
31
+ resolve({ ok: false, output: null, failure: capFailure((0, fs_access_1.errorCode)(err)) });
32
+ return;
33
+ }
34
+ this.trackGrep(proc);
35
+ const chunks = [];
36
+ let totalBytes = 0;
37
+ const MAX_BUFFER = 10 * 1024 * 1024;
38
+ const printed = () => (chunks.length === 0 ? null : Buffer.concat(chunks).toString('utf-8'));
39
+ const stderrChunks = [];
40
+ let stderrBytes = 0;
41
+ const stderrLine = () => {
42
+ const text = Buffer.concat(stderrChunks).toString('utf-8');
43
+ const line = text.split('\n').map(l => l.trim()).find(l => l !== '');
44
+ return line ?? null;
45
+ };
46
+ const timer = setTimeout(() => {
47
+ this.logger.warn('GrepRunner: grep timed out', { ms: GREP_TIMEOUT_MS });
48
+ finish({ ok: false, output: printed(), failure: `timed out after ${GREP_TIMEOUT_MS / 1000} seconds` });
49
+ proc.kill('SIGKILL');
50
+ proc.stdout?.destroy();
51
+ proc.stderr?.destroy();
52
+ }, GREP_TIMEOUT_MS);
53
+ timer.unref();
54
+ let settled = false;
55
+ const finish = (result) => {
56
+ settled = true;
57
+ clearTimeout(timer);
58
+ this.untrackGrep(proc);
59
+ resolve(result);
60
+ };
61
+ proc.stdout?.on('data', (chunk) => {
62
+ totalBytes += chunk.length;
63
+ if (totalBytes <= MAX_BUFFER) {
64
+ chunks.push(chunk);
65
+ }
66
+ });
67
+ proc.stderr?.on('data', (chunk) => {
68
+ if (stderrBytes >= STDERR_MAX_BYTES)
69
+ return;
70
+ stderrChunks.push(chunk);
71
+ stderrBytes += chunk.length;
72
+ });
73
+ proc.on('error', (err) => {
74
+ if (settled)
75
+ return;
76
+ this.logger.error('GrepRunner: grep spawn failed', { err });
77
+ finish({ ok: false, output: null, failure: capFailure((0, fs_access_1.errorCode)(err)) });
78
+ });
79
+ proc.on('close', (code, signal) => {
80
+ if (settled)
81
+ return;
82
+ const output = printed();
83
+ if (code === 0 || code === 1) {
84
+ finish({ ok: true, output, failure: null });
85
+ return;
86
+ }
87
+ this.logger.warn('GrepRunner: grep exited with code', { code, signal });
88
+ const cause = stderrLine() ?? (code !== null ? `exit status ${code}` : `signal ${signal}`);
89
+ finish({ ok: false, output, failure: capFailure(cause) });
90
+ });
91
+ });
92
+ }
93
+ trackGrep(proc) {
94
+ if (this.liveGreps.size === 0)
95
+ process.on('exit', this.killLiveGreps);
96
+ this.liveGreps.add(proc);
97
+ }
98
+ untrackGrep(proc) {
99
+ this.liveGreps.delete(proc);
100
+ if (this.liveGreps.size === 0)
101
+ process.off('exit', this.killLiveGreps);
102
+ }
103
+ }
104
+ exports.GrepRunner = GrepRunner;
@@ -0,0 +1,22 @@
1
+ import type { Config } from '../config';
2
+ import type { IProjectDiscovery } from '../interfaces';
3
+ import type { IProjectRoots } from '../interfaces/IProjectRoots';
4
+ export declare class ProjectRoots implements IProjectRoots {
5
+ private discovery;
6
+ private config;
7
+ private rootMap?;
8
+ constructor(discovery: IProjectDiscovery, config: Config);
9
+ private projectRootMap;
10
+ entries(): IterableIterator<[string, string]>;
11
+ refresh(): void;
12
+ resolveProjectRoot(name: string): string;
13
+ toRel(absPath: string): string;
14
+ toAbs(relPath: string): string;
15
+ workspaceRoot(): string;
16
+ findProjectRootByName(name: string): string | null;
17
+ findProjectRoot(match: (rel: string) => boolean): {
18
+ name: string;
19
+ rel: string;
20
+ } | null;
21
+ fallbackRoot(relPath: string): string | null;
22
+ }