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
@@ -8,8 +8,11 @@ exports.checkCaseCorpora = checkCaseCorpora;
8
8
  exports.mergeSuites = mergeSuites;
9
9
  exports.signTest = signTest;
10
10
  exports.pairedChange = pairedChange;
11
+ exports.formatThresholdConditions = formatThresholdConditions;
11
12
  exports.compare = compare;
12
13
  const zod_1 = require("zod");
14
+ const constants_1 = require("./constants");
15
+ const eval_vector_index_1 = require("./eval-vector-index");
13
16
  const CATEGORIES = ['identifier', 'behaviour', 'concept'];
14
17
  exports.STAGE_ORDER = ['vector', 'reranked'];
15
18
  const STAGE_SOURCE = { reranked: 'vector' };
@@ -285,6 +288,13 @@ function runFigures(cases, stages, shortPool) {
285
288
  stageChanges: stageChangesOf(cases, stages),
286
289
  };
287
290
  }
291
+ function distinctThresholds(pairs) {
292
+ const byKey = new Map(pairs.map(p => [`${p.value}|${p.source}`, p]));
293
+ return [...byKey.values()].sort((a, b) => b.value - a.value || a.source.localeCompare(b.source));
294
+ }
295
+ function formatThresholdConditions(pairs) {
296
+ return pairs.map(p => `${(0, constants_1.formatThreshold)(p.value)} (${p.source})`).join(', ') || '—';
297
+ }
288
298
  class RetrievalEvaluator {
289
299
  async evaluate(suite, search, inspect, onCase) {
290
300
  await checkCaseCorpora(suite.cases, p => inspect.projectDir(p));
@@ -295,6 +305,7 @@ class RetrievalEvaluator {
295
305
  const fetchLimits = [];
296
306
  const efSearches = [];
297
307
  const iterativeScans = [];
308
+ const thresholds = [];
298
309
  let stages = null;
299
310
  const total = suite.cases.length;
300
311
  for (const [i, c] of suite.cases.entries()) {
@@ -334,6 +345,7 @@ class RetrievalEvaluator {
334
345
  fetchLimits.push(hits.fetchLimit);
335
346
  efSearches.push(hits.indexParams.efSearch);
336
347
  iterativeScans.push(hits.indexParams.iterativeScan);
348
+ thresholds.push(hits.threshold);
337
349
  report(result, null);
338
350
  }
339
351
  const runStages = stages ?? ['vector'];
@@ -347,6 +359,7 @@ class RetrievalEvaluator {
347
359
  fetchLimit: sortedDistinct(fetchLimits),
348
360
  efSearch: sortedDistinct(efSearches),
349
361
  iterativeScan: sortedDistinct(iterativeScans),
362
+ thresholds: distinctThresholds(thresholds),
350
363
  };
351
364
  return {
352
365
  run: {
@@ -393,8 +406,18 @@ function poolDelta(baseline, current) {
393
406
  short: poolValueDelta(baseline?.short ?? null, current.short),
394
407
  };
395
408
  }
409
+ function isThresholdCondition(v) {
410
+ return typeof v === 'object' && v !== null && 'value' in v && 'source' in v;
411
+ }
396
412
  function formatCondition(value) {
397
- return Array.isArray(value) ? `[${value.map(v => (v === null ? 'not sent' : String(v))).join(', ')}]` : String(value);
413
+ if (!Array.isArray(value))
414
+ return String(value);
415
+ if (value.length > 0 && value.every(isThresholdCondition))
416
+ return `[${formatThresholdConditions(value)}]`;
417
+ return `[${value.map(v => (v === null ? 'not sent' : String(v))).join(', ')}]`;
418
+ }
419
+ function conditionKey(value) {
420
+ return JSON.stringify(Array.isArray(value) ? value.map(v => JSON.stringify(v)).sort() : value);
398
421
  }
399
422
  function corpusWarningsOf(b, cur) {
400
423
  const warnings = [];
@@ -426,6 +449,25 @@ function corpusWarningsOf(b, cur) {
426
449
  warnings.push(live('current run', cur.liveCases));
427
450
  return warnings;
428
451
  }
452
+ function vectorIndexWarningsOf(b, cur) {
453
+ if (!b.vectorIndex || !cur.vectorIndex) {
454
+ return [...(b.vectorIndex ? [] : ['baseline records no vector index']), ...(cur.vectorIndex ? [] : ['current run records no vector index'])];
455
+ }
456
+ const warnings = [];
457
+ if (b.vectorIndex.pgvector !== cur.vectorIndex.pgvector) {
458
+ warnings.push(`pgvector differs: baseline ${b.vectorIndex.pgvector ?? 'not installed'} vs current ${cur.vectorIndex.pgvector ?? 'not installed'}`);
459
+ }
460
+ if (JSON.stringify(b.vectorIndex.indexes) !== JSON.stringify(cur.vectorIndex.indexes)) {
461
+ warnings.push(`vector index differs: baseline ${(0, eval_vector_index_1.formatIndexes)(b.vectorIndex)} vs current ${(0, eval_vector_index_1.formatIndexes)(cur.vectorIndex)}`);
462
+ }
463
+ const settings = [['hnsw.max_scan_tuples', 'maxScanTuples'], ['hnsw.scan_mem_multiplier', 'scanMemMultiplier']];
464
+ for (const [name, key] of settings) {
465
+ if (b.vectorIndex[key] !== cur.vectorIndex[key]) {
466
+ warnings.push(`${name} differs: baseline ${b.vectorIndex[key] ?? 'not defined'} vs current ${cur.vectorIndex[key] ?? 'not defined'}`);
467
+ }
468
+ }
469
+ return warnings;
470
+ }
429
471
  function conditionWarningsOf(baseline, current) {
430
472
  const warnings = [];
431
473
  const b = baseline.metadata, cur = current.metadata;
@@ -450,11 +492,12 @@ function conditionWarningsOf(baseline, current) {
450
492
  warnings.push(`search condition ${key} recorded only by the current run (${formatCondition(cc[key])})`);
451
493
  else if (!(key in cc))
452
494
  warnings.push(`search condition ${key} recorded only by the baseline (${formatCondition(bc[key])})`);
453
- else if (JSON.stringify(bc[key]) !== JSON.stringify(cc[key])) {
495
+ else if (conditionKey(bc[key]) !== conditionKey(cc[key])) {
454
496
  warnings.push(`${key} differs: baseline ${formatCondition(bc[key])} vs current ${formatCondition(cc[key])}`);
455
497
  }
456
498
  }
457
499
  }
500
+ warnings.push(...vectorIndexWarningsOf(b, cur));
458
501
  warnings.push(...corpusWarningsOf(b, cur));
459
502
  if (baseline.run.rerankFailures > 0)
460
503
  warnings.push(`baseline has ${baseline.run.rerankFailures} rerank failure(s)`);
@@ -0,0 +1,9 @@
1
+ import type { IDatabase, ILogger } from '../interfaces';
2
+ import type { CallerRow, ICallGraph, NoCallersCause } from '../interfaces/ICallGraph';
3
+ export declare class CallGraph implements ICallGraph {
4
+ private db;
5
+ private logger;
6
+ constructor(db: IDatabase, logger: ILogger);
7
+ getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
8
+ explainNoCallers(symbol: string, project: string): Promise<NoCallersCause>;
9
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallGraph = void 0;
4
+ const symbol_name_1 = require("../symbol-name");
5
+ const symbol_sql_1 = require("./symbol-sql");
6
+ const NO_CALLERS_LIST_LIMIT = 5;
7
+ class CallGraph {
8
+ db;
9
+ logger;
10
+ constructor(db, logger) {
11
+ this.db = db;
12
+ this.logger = logger;
13
+ }
14
+ async getCallers(symbol, project, calleeFile = null) {
15
+ const t0 = Date.now();
16
+ this.logger.debug('CallGraph: getCallers', { symbol, project, calleeFile });
17
+ const parsed = (0, symbol_name_1.parseCallerSymbol)(symbol);
18
+ if (!parsed)
19
+ throw new Error('Invalid symbol name');
20
+ const params = [project];
21
+ let nameFilter;
22
+ if (parsed.form === 'bare') {
23
+ params.push(parsed.name);
24
+ nameFilter = `"calleeName" = $${params.length}`;
25
+ }
26
+ else {
27
+ params.push(`${parsed.container}.${parsed.name}`);
28
+ const exactParam = params.length;
29
+ params.push(`%.${(0, symbol_name_1.escapeLike)(parsed.container)}.${(0, symbol_name_1.escapeLike)(parsed.name)}`);
30
+ const suffixParam = params.length;
31
+ nameFilter = `("calleeSymbol" = $${exactParam} OR "calleeSymbol" LIKE $${suffixParam} ESCAPE '\\')`;
32
+ }
33
+ let fileFilter = '';
34
+ if (calleeFile) {
35
+ params.push(calleeFile);
36
+ fileFilter = `AND "calleeFile" = $${params.length}`;
37
+ }
38
+ const rows = await this.db.query(`SELECT "fromFile", "fromSymbol", "calleeName", "calleeFile", line
39
+ FROM code_call
40
+ WHERE project = $1 AND ${nameFilter}
41
+ ${fileFilter}
42
+ ORDER BY "fromFile", line
43
+ LIMIT 200`, params);
44
+ this.logger.debug('CallGraph: getCallers result', { count: rows.length, ms: Date.now() - t0 });
45
+ return rows;
46
+ }
47
+ async explainNoCallers(symbol, project) {
48
+ const t0 = Date.now();
49
+ const parsed = (0, symbol_name_1.parseCallerSymbol)(symbol);
50
+ if (!parsed)
51
+ throw new Error('Invalid symbol name');
52
+ const container = parsed.form === 'qualified' ? parsed.container : null;
53
+ try {
54
+ const calls = await this.db.query(`SELECT EXISTS (SELECT 1 FROM code_call WHERE project = $1) AS present`, [project]);
55
+ if (!calls[0]?.present)
56
+ return { kind: 'no-call-graph' };
57
+ const params = [project];
58
+ const match = (0, symbol_sql_1.symbolMatch)(parsed.name, container, params);
59
+ params.push(NO_CALLERS_LIST_LIMIT + 1);
60
+ const definitions = await this.db.query(`SELECT "filePath", line, qualified
61
+ FROM (
62
+ SELECT DISTINCT "filePath", line, ${symbol_sql_1.QUALIFIED_SQL} AS qualified${match.exactRank ? `, ${match.exactRank} AS exact_rank` : ''}
63
+ FROM code_symbol
64
+ WHERE project = $1 AND ${match.filter}
65
+ ) AS definitions
66
+ ORDER BY ${match.exactRank ? 'exact_rank, ' : ''}"filePath", line, qualified COLLATE "C"
67
+ LIMIT $${params.length}`, params);
68
+ if (definitions.length > 0) {
69
+ return {
70
+ kind: 'no-recorded-calls',
71
+ definitions: definitions.slice(0, NO_CALLERS_LIST_LIMIT).map(({ qualified, filePath, line }) => ({ qualified, filePath, line })),
72
+ truncated: definitions.length > NO_CALLERS_LIST_LIMIT,
73
+ };
74
+ }
75
+ const { names, truncated } = await (0, symbol_sql_1.similarSymbolNames)(this.db, parsed.name, project, false);
76
+ return { kind: 'unknown-symbol', suggestions: names, truncated };
77
+ }
78
+ catch (err) {
79
+ this.logger.warn('CallGraph: explainNoCallers lookup failed', { err, symbol, project, ms: Date.now() - t0 });
80
+ return { kind: 'lookup-failed' };
81
+ }
82
+ }
83
+ }
84
+ exports.CallGraph = CallGraph;
@@ -0,0 +1,23 @@
1
+ import type { Config } from '../config';
2
+ import type { IDatabase, IEmbedder, ILogger } from '../interfaces';
3
+ import type { IChunkSearch, NeighborResult, NoResultsCause, SearchResult, StagedSearchResult } from '../interfaces/IChunkSearch';
4
+ import type { IndexIdentityGuard } from './IndexIdentityGuard';
5
+ export declare class ChunkSearch implements IChunkSearch {
6
+ private db;
7
+ private embedder;
8
+ private identityGuard;
9
+ private config;
10
+ private logger;
11
+ private readonly gate;
12
+ private readonly fetcher;
13
+ private readonly rerankStage;
14
+ constructor(db: IDatabase, embedder: IEmbedder, identityGuard: IndexIdentityGuard, config: Config, logger: ILogger);
15
+ prepareQuery(raw: string): Promise<{
16
+ query: string;
17
+ translated: boolean;
18
+ }>;
19
+ searchChunks(query: string, limit: number, project: string | null, threshold?: number, rerankOverride?: boolean, perFile?: number): Promise<SearchResult[]>;
20
+ searchChunksWithStages(query: string, limit: number, project: string | null, threshold?: number, rerankOverride?: boolean, perFile?: number): Promise<StagedSearchResult>;
21
+ fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
22
+ explainNoResults(project: string | null): Promise<NoResultsCause>;
23
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChunkSearch = void 0;
4
+ const result_shaping_1 = require("./result-shaping");
5
+ const search_plan_1 = require("./chunks/search-plan");
6
+ const IterativeScanGate_1 = require("./chunks/IterativeScanGate");
7
+ const CandidateFetcher_1 = require("./chunks/CandidateFetcher");
8
+ const RerankStage_1 = require("./chunks/RerankStage");
9
+ const search_metrics_1 = require("./chunks/search-metrics");
10
+ class ChunkSearch {
11
+ db;
12
+ embedder;
13
+ identityGuard;
14
+ config;
15
+ logger;
16
+ gate;
17
+ fetcher;
18
+ rerankStage;
19
+ constructor(db, embedder, identityGuard, config, logger) {
20
+ this.db = db;
21
+ this.embedder = embedder;
22
+ this.identityGuard = identityGuard;
23
+ this.config = config;
24
+ this.logger = logger;
25
+ this.gate = new IterativeScanGate_1.IterativeScanGate(db, logger);
26
+ this.fetcher = new CandidateFetcher_1.CandidateFetcher(db, this.gate);
27
+ this.rerankStage = new RerankStage_1.RerankStage(embedder, logger);
28
+ }
29
+ async prepareQuery(raw) {
30
+ return { query: raw, translated: false };
31
+ }
32
+ async searchChunks(query, limit, project, threshold, rerankOverride, perFile = result_shaping_1.DEFAULT_PER_FILE) {
33
+ const { stages } = await this.searchChunksWithStages(query, limit, project, threshold, rerankOverride, perFile);
34
+ return stages.reranked ?? stages.vector;
35
+ }
36
+ async searchChunksWithStages(query, limit, project, threshold, rerankOverride, perFile = result_shaping_1.DEFAULT_PER_FILE) {
37
+ const t0 = Date.now();
38
+ await this.identityGuard.assertIndexMatches();
39
+ const plan = (0, search_plan_1.planSearch)({ limit, perFile, threshold, rerankOverride, rerankAvailable: !!this.embedder.rerank }, this.config);
40
+ this.logger.debug('ChunkSearch: searchChunks', {
41
+ query: query.slice(0, 120), limit, fetchLimit: plan.fetchLimit, project, threshold: plan.threshold.value,
42
+ });
43
+ const tEmbed0 = Date.now();
44
+ const embedding = await this.embedder.embed(query, 'query');
45
+ const embedMs = Date.now() - tEmbed0;
46
+ const pool = await this.fetcher.fetch(embedding, plan, project);
47
+ this.logger.debug('ChunkSearch: searchChunks candidates', {
48
+ count: pool.candidates.length,
49
+ scores: pool.candidates.map(r => r.score),
50
+ files: pool.candidates.map(r => r.filePath),
51
+ ms: Date.now() - t0,
52
+ });
53
+ const vector = (0, result_shaping_1.limitPerFile)(pool.candidates, limit, perFile);
54
+ const rerank = plan.reranking ? await this.rerankStage.run(query, pool.candidates, vector, plan, t0) : undefined;
55
+ this.logger.info('search metrics', (0, search_metrics_1.searchMetrics)(query, {
56
+ fetchLimit: plan.fetchLimit, candidates: pool.candidates, results: rerank?.reranked ?? vector, poolSaturated: pool.poolSaturated, indexParams: pool.indexParams, threshold: plan.threshold,
57
+ embedMs, dbMs: pool.dbMs, rerankMs: rerank?.rerankMs ?? 0, totalMs: Date.now() - t0,
58
+ }));
59
+ return {
60
+ pool: pool.candidates,
61
+ stages: rerank ? { vector, reranked: rerank.reranked } : { vector },
62
+ rerankFailed: rerank?.rerankFailed ?? false,
63
+ fetchLimit: plan.fetchLimit,
64
+ indexParams: pool.indexParams,
65
+ threshold: plan.threshold,
66
+ };
67
+ }
68
+ async fetchNeighbors(results) {
69
+ const t0 = Date.now();
70
+ if (results.length === 0)
71
+ return [];
72
+ this.logger.debug('ChunkSearch: fetchNeighbors', { count: results.length });
73
+ const values = results.map((_, i) => `(${i}, $${i * 2 + 1}, $${i * 2 + 2}::int)`).join(',');
74
+ const params = results.flatMap(r => [r.filePath, r.startLine]);
75
+ const rows = await this.db.query(`SELECT r.idx,
76
+ prev."serviceName" AS prev_sn, prev."filePath" AS prev_fp, prev."startLine" AS prev_sl, prev.content AS prev_c, prev."ownerPath" AS prev_op,
77
+ next."serviceName" AS next_sn, next."filePath" AS next_fp, next."startLine" AS next_sl, next.content AS next_c, next."ownerPath" AS next_op
78
+ FROM (VALUES ${values}) r(idx, fp, sl)
79
+ LEFT JOIN LATERAL (
80
+ SELECT "serviceName", "filePath", "startLine", content, "ownerPath"
81
+ FROM code_chunk WHERE "filePath" = r.fp AND "startLine" < r.sl
82
+ ORDER BY "startLine" DESC LIMIT 1
83
+ ) prev ON true
84
+ LEFT JOIN LATERAL (
85
+ SELECT "serviceName", "filePath", "startLine", content, "ownerPath"
86
+ FROM code_chunk WHERE "filePath" = r.fp AND "startLine" > r.sl
87
+ ORDER BY "startLine" ASC LIMIT 1
88
+ ) next ON true
89
+ ORDER BY r.idx`, params);
90
+ const neighbors = rows.map((row, i) => ({
91
+ result: results[i],
92
+ prevChunk: row.prev_fp ? { serviceName: row.prev_sn, filePath: row.prev_fp, startLine: row.prev_sl, content: row.prev_c, ownerPath: row.prev_op ?? null } : null,
93
+ nextChunk: row.next_fp ? { serviceName: row.next_sn, filePath: row.next_fp, startLine: row.next_sl, content: row.next_c, ownerPath: row.next_op ?? null } : null,
94
+ }));
95
+ this.logger.debug('ChunkSearch: fetchNeighbors result', {
96
+ withPrev: neighbors.filter(n => n.prevChunk).length,
97
+ withNext: neighbors.filter(n => n.nextChunk).length,
98
+ ms: Date.now() - t0,
99
+ });
100
+ return neighbors;
101
+ }
102
+ async explainNoResults(project) {
103
+ try {
104
+ const rows = await this.db.query(project
105
+ ? `SELECT EXISTS (SELECT 1 FROM code_chunk WHERE "serviceName" = $1) AS present`
106
+ : `SELECT EXISTS (SELECT 1 FROM code_chunk) AS present`, project ? [project] : []);
107
+ return rows[0]?.present ? 'no-candidates' : 'empty-scope';
108
+ }
109
+ catch (err) {
110
+ this.logger.warn('ChunkSearch: explainNoResults lookup failed', { err, project });
111
+ return 'lookup-failed';
112
+ }
113
+ }
114
+ }
115
+ exports.ChunkSearch = ChunkSearch;
@@ -0,0 +1,19 @@
1
+ import type { Config } from '../config';
2
+ import type { IDatabase, ILogger } from '../interfaces';
3
+ import type { DependencyRow, GraphContext, IDependencyGraph } from '../interfaces/IDependencyGraph';
4
+ import type { SearchResult } from '../interfaces/IChunkSearch';
5
+ import type { ProjectRoots } from '../workspace/ProjectRoots';
6
+ export declare class DependencyGraph implements IDependencyGraph {
7
+ private db;
8
+ private roots;
9
+ private config;
10
+ private logger;
11
+ constructor(db: IDatabase, roots: ProjectRoots, config: Config, logger: ILogger);
12
+ getProjectEdges(): Promise<{
13
+ from: string;
14
+ to: string;
15
+ }[]>;
16
+ fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
17
+ getDependencies(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
18
+ getDependents(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
19
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DependencyGraph = void 0;
37
+ const path = __importStar(require("path"));
38
+ const dependency_traversal_1 = require("../dependency-traversal");
39
+ const index_paths_1 = require("./index-paths");
40
+ class DependencyGraph {
41
+ db;
42
+ roots;
43
+ config;
44
+ logger;
45
+ constructor(db, roots, config, logger) {
46
+ this.db = db;
47
+ this.roots = roots;
48
+ this.config = config;
49
+ this.logger = logger;
50
+ }
51
+ async getProjectEdges() {
52
+ const rows = await this.db.query(`SELECT DISTINCT project, "toFile" FROM code_dependency`);
53
+ const byRelPath = [...this.roots.entries()]
54
+ .map(([name, abs]) => ({ name, rel: path.relative(this.config.workspaceRoot, abs) }))
55
+ .sort((a, b) => b.rel.length - a.rel.length);
56
+ const ownerOf = (toFile) => {
57
+ for (const { name, rel } of byRelPath) {
58
+ if (toFile === rel || toFile.startsWith(rel + path.sep) || toFile.startsWith(rel + '/'))
59
+ return name;
60
+ }
61
+ return null;
62
+ };
63
+ const seen = new Set();
64
+ const edges = [];
65
+ for (const { project, toFile } of rows) {
66
+ const to = ownerOf(toFile);
67
+ if (!to || to === project)
68
+ continue;
69
+ const key = project + ' ' + to;
70
+ if (seen.has(key))
71
+ continue;
72
+ seen.add(key);
73
+ edges.push({ from: project, to });
74
+ }
75
+ return edges;
76
+ }
77
+ async fetchGraphContext(results) {
78
+ const t0 = Date.now();
79
+ const files = [...new Set(results.map(r => r.filePath))];
80
+ this.logger.debug('DependencyGraph: fetchGraphContext', { files: files.length });
81
+ if (files.length === 0)
82
+ return new Map();
83
+ const proportionalLimit = Math.max(2000, files.length * 100);
84
+ const values = files.map((_, i) => `($${i + 1}::text)`).join(', ');
85
+ const rows = await this.db.query(`SELECT r.fp AS "filePath", 'import' AS direction, d."toFile" AS related
86
+ FROM (VALUES ${values}) AS r(fp)
87
+ JOIN code_dependency d ON d."fromFile" = r.fp
88
+ WHERE NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('d."toFile"')}
89
+
90
+ UNION ALL
91
+
92
+ SELECT r.fp AS "filePath", 'usedby' AS direction, d."fromFile" AS related
93
+ FROM (VALUES ${values}) AS r(fp)
94
+ JOIN code_dependency d ON d."toFile" = r.fp
95
+ WHERE NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('d."fromFile"')}
96
+
97
+ LIMIT ${proportionalLimit}`, files);
98
+ const ctx = new Map();
99
+ for (const filePath of files)
100
+ ctx.set(filePath, { imports: new Set(), usedBy: new Set() });
101
+ for (const row of rows) {
102
+ const g = ctx.get(row.filePath);
103
+ if (row.direction === 'import')
104
+ g.imports.add(row.related);
105
+ else
106
+ g.usedBy.add(row.related);
107
+ }
108
+ this.logger.debug('DependencyGraph: fetchGraphContext result', { files: files.length, rows: rows.length, ms: Date.now() - t0 });
109
+ const result = new Map();
110
+ for (const [fp, g] of ctx)
111
+ result.set(fp, {
112
+ imports: [...g.imports].slice(0, 50),
113
+ usedBy: [...g.usedBy].slice(0, 50),
114
+ });
115
+ return result;
116
+ }
117
+ async getDependencies(filePath, symbol = null, transitive = false) {
118
+ const t0 = Date.now();
119
+ this.logger.debug('DependencyGraph: getDependencies', { filePath, symbol, transitive });
120
+ const params = [filePath];
121
+ let symbolFilter = '';
122
+ if (symbol) {
123
+ params.push(symbol);
124
+ symbolFilter = `AND "toSymbol" = $${params.length}`;
125
+ }
126
+ const direct = await this.db.query(`SELECT "fromFile", "fromSymbol", "toFile", "toSymbol", "depType", min("id"::text) AS "id"
127
+ FROM code_dependency
128
+ WHERE "fromFile" = $1 AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"toFile"')}
129
+ ${symbolFilter}
130
+ GROUP BY "fromFile", "fromSymbol", "toFile", "toSymbol", "depType"
131
+ ORDER BY "toFile", min("id"::text)`, params);
132
+ if (!transitive) {
133
+ this.logger.debug('DependencyGraph: getDependencies result', { direct: direct.length, ms: Date.now() - t0 });
134
+ return direct;
135
+ }
136
+ const neighbours = async (frontier) => {
137
+ const rows = await this.db.query(`SELECT DISTINCT "fromFile", "toFile" FROM code_dependency
138
+ WHERE "fromFile" = ANY($1::text[]) AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"toFile"')}`, [frontier]);
139
+ return rows.map(r => ({ from: r.fromFile, to: r.toFile }));
140
+ };
141
+ const indirect = (await (0, dependency_traversal_1.traverse)(filePath, neighbours)).map(r => ({
142
+ toFile: r.file, depth: r.depth, via: r.via,
143
+ fromFile: null, fromSymbol: null, toSymbol: null, depType: null,
144
+ }));
145
+ this.logger.debug('DependencyGraph: getDependencies result', { direct: direct.length, indirect: indirect.length, ms: Date.now() - t0 });
146
+ return [...direct, ...indirect];
147
+ }
148
+ async getDependents(filePath, symbol = null, transitive = false) {
149
+ const t0 = Date.now();
150
+ this.logger.debug('DependencyGraph: getDependents', { filePath, symbol, transitive });
151
+ const params = [filePath];
152
+ let symbolFilter = '';
153
+ if (symbol) {
154
+ params.push(symbol);
155
+ symbolFilter = `AND "toSymbol" = $${params.length}`;
156
+ }
157
+ const direct = await this.db.query(`SELECT "fromFile", "fromSymbol", "toFile", "toSymbol", "depType", min("id"::text) AS "id"
158
+ FROM code_dependency
159
+ WHERE "toFile" = $1 AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"fromFile"')}
160
+ ${symbolFilter}
161
+ GROUP BY "fromFile", "fromSymbol", "toFile", "toSymbol", "depType"
162
+ ORDER BY "fromFile", min("id"::text)`, params);
163
+ if (!transitive) {
164
+ this.logger.debug('DependencyGraph: getDependents result', { direct: direct.length, ms: Date.now() - t0 });
165
+ return direct;
166
+ }
167
+ const neighbours = async (frontier) => {
168
+ const rows = await this.db.query(`SELECT DISTINCT "fromFile", "toFile" FROM code_dependency
169
+ WHERE "toFile" = ANY($1::text[]) AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"fromFile"')}`, [frontier]);
170
+ return rows.map(r => ({ from: r.toFile, to: r.fromFile }));
171
+ };
172
+ const indirect = (await (0, dependency_traversal_1.traverse)(filePath, neighbours)).map(r => ({
173
+ fromFile: r.file, depth: r.depth, via: r.via,
174
+ toFile: null, fromSymbol: null, toSymbol: null, depType: null,
175
+ }));
176
+ this.logger.debug('DependencyGraph: getDependents result', { direct: direct.length, indirect: indirect.length, ms: Date.now() - t0 });
177
+ return [...direct, ...indirect];
178
+ }
179
+ }
180
+ exports.DependencyGraph = DependencyGraph;
@@ -0,0 +1,11 @@
1
+ import type { Config } from '../config';
2
+ import type { IDatabase, IEmbedder } from '../interfaces';
3
+ import type { IIndexIdentityGuard } from '../interfaces/IIndexIdentityGuard';
4
+ export declare class IndexIdentityGuard implements IIndexIdentityGuard {
5
+ private db;
6
+ private embedder;
7
+ private config;
8
+ constructor(db: IDatabase, embedder: IEmbedder, config: Config);
9
+ assertIndexMatches(): Promise<void>;
10
+ private compareIndexed;
11
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IndexIdentityGuard = void 0;
4
+ const legacy_ollama_index_1 = require("../services/legacy-ollama-index");
5
+ class IndexIdentityGuard {
6
+ db;
7
+ embedder;
8
+ config;
9
+ constructor(db, embedder, config) {
10
+ this.db = db;
11
+ this.embedder = embedder;
12
+ this.config = config;
13
+ }
14
+ async assertIndexMatches() {
15
+ const meta = await this.db.readIndexMeta();
16
+ if (meta) {
17
+ this.compareIndexed(meta.provider, meta.model, meta.dimensions);
18
+ return;
19
+ }
20
+ if (await (0, legacy_ollama_index_1.hasIndexedChunks)(this.db)) {
21
+ this.compareIndexed(legacy_ollama_index_1.LEGACY_OLLAMA_IDENTITY.provider, legacy_ollama_index_1.LEGACY_OLLAMA_IDENTITY.model, null);
22
+ }
23
+ }
24
+ compareIndexed(indexedProvider, indexedModel, indexedDimensions) {
25
+ const { provider, model } = this.config.embedding;
26
+ if (indexedProvider !== provider || indexedModel !== model) {
27
+ throw new Error(`Index was built with ${indexedProvider}/${indexedModel} but the config selects ${provider}/${model}. ` +
28
+ `Run \`rag-index --reindex\` to rebuild the index for the new model.`);
29
+ }
30
+ const dim = this.embedder.dimensions;
31
+ if (indexedDimensions != null && dim > 0 && dim !== indexedDimensions) {
32
+ throw new Error(`Index was built at ${indexedDimensions} dimensions but ${provider}/${model} now resolves to ${dim}. ` +
33
+ `Run \`rag-index --reindex\` to rebuild the index at the new dimension.`);
34
+ }
35
+ }
36
+ }
37
+ exports.IndexIdentityGuard = IndexIdentityGuard;
@@ -0,0 +1,11 @@
1
+ import type { IDatabase } from '../interfaces';
2
+ import type { IProjectCatalog, ProjectRow } from '../interfaces/IProjectCatalog';
3
+ import { type WorkspaceFs } from '../workspace/fs-access';
4
+ import type { ProjectRoots } from '../workspace/ProjectRoots';
5
+ export declare class ProjectCatalog implements IProjectCatalog {
6
+ private db;
7
+ private roots;
8
+ private fs;
9
+ constructor(db: IDatabase, roots: ProjectRoots, fs: WorkspaceFs);
10
+ listProjects(): Promise<ProjectRow[]>;
11
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProjectCatalog = void 0;
4
+ const file_utils_1 = require("../file-utils");
5
+ const fs_access_1 = require("../workspace/fs-access");
6
+ const EMPTY_META = { description: null, stack: null, notes: null, relations: null };
7
+ class ProjectCatalog {
8
+ db;
9
+ roots;
10
+ fs;
11
+ constructor(db, roots, fs) {
12
+ this.db = db;
13
+ this.roots = roots;
14
+ this.fs = fs;
15
+ }
16
+ async listProjects() {
17
+ const rows = await this.db.query(`SELECT "serviceName", COUNT(*)::int AS chunks
18
+ FROM code_chunk
19
+ GROUP BY "serviceName"
20
+ ORDER BY "serviceName"`);
21
+ return Promise.all(rows.map(async (r) => {
22
+ const projectRoot = this.roots.resolveProjectRoot(r.serviceName);
23
+ const meta = (0, file_utils_1.readRagMeta)(projectRoot);
24
+ let rootError = null;
25
+ try {
26
+ await this.fs.stat(projectRoot);
27
+ }
28
+ catch (err) {
29
+ rootError = (0, fs_access_1.errorCode)(err);
30
+ }
31
+ return {
32
+ ...r, path: projectRoot, relPath: this.roots.toRel(projectRoot), rootError,
33
+ ...(typeof meta === 'object' ? meta : EMPTY_META),
34
+ };
35
+ }));
36
+ }
37
+ }
38
+ exports.ProjectCatalog = ProjectCatalog;