codesearch-mcp 0.2.2-alpha → 0.2.3-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 (62) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +15 -8
  3. package/dist/app.js +45 -7
  4. package/dist/src/commands/EvalCommand.d.ts +10 -0
  5. package/dist/src/commands/EvalCommand.js +231 -0
  6. package/dist/src/commands/IndexCommand.js +79 -53
  7. package/dist/src/db/index.d.ts +1 -1
  8. package/dist/src/db/index.js +1 -2
  9. package/dist/src/db/schema.d.ts +5 -29
  10. package/dist/src/db/schema.js +2 -8
  11. package/dist/src/dependency-traversal.d.ts +16 -0
  12. package/dist/src/dependency-traversal.js +45 -0
  13. package/dist/src/enriched-prompt.d.ts +3 -2
  14. package/dist/src/enriched-prompt.js +3 -2
  15. package/dist/src/eval-results.d.ts +12 -0
  16. package/dist/src/eval-results.js +114 -0
  17. package/dist/src/file-utils.d.ts +15 -0
  18. package/dist/src/file-utils.js +24 -1
  19. package/dist/src/help.js +4 -1
  20. package/dist/src/interfaces/IAstParser.d.ts +13 -1
  21. package/dist/src/interfaces/IDatabase.d.ts +0 -14
  22. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +94 -0
  23. package/dist/src/interfaces/IRetrievalEvaluator.js +2 -0
  24. package/dist/src/interfaces/ISearcher.d.ts +11 -16
  25. package/dist/src/interfaces/index.d.ts +1 -0
  26. package/dist/src/project-scope.d.ts +19 -0
  27. package/dist/src/project-scope.js +49 -0
  28. package/dist/src/project-selector.d.ts +6 -0
  29. package/dist/src/project-selector.js +18 -0
  30. package/dist/src/retrieval-eval.d.ts +16 -0
  31. package/dist/src/retrieval-eval.js +214 -0
  32. package/dist/src/services/AstParserService.d.ts +66 -1
  33. package/dist/src/services/AstParserService.js +430 -25
  34. package/dist/src/services/DatabaseService.d.ts +0 -12
  35. package/dist/src/services/DatabaseService.js +2 -52
  36. package/dist/src/services/IndexerService.d.ts +8 -0
  37. package/dist/src/services/IndexerService.js +24 -11
  38. package/dist/src/services/McpServer.d.ts +7 -0
  39. package/dist/src/services/McpServer.js +42 -38
  40. package/dist/src/services/OpenAICompatibleEmbedder.js +2 -1
  41. package/dist/src/services/SearcherService.d.ts +189 -25
  42. package/dist/src/services/SearcherService.js +505 -203
  43. package/dist/src/symbol-rows.d.ts +14 -9
  44. package/dist/src/symbol-rows.js +17 -11
  45. package/dist/src/tools/FindSymbolTool.js +14 -5
  46. package/dist/src/tools/GetCallersTool.d.ts +3 -1
  47. package/dist/src/tools/GetCallersTool.js +16 -6
  48. package/dist/src/tools/GetDependenciesTool.d.ts +3 -1
  49. package/dist/src/tools/GetDependenciesTool.js +15 -8
  50. package/dist/src/tools/GetDependentsTool.d.ts +3 -1
  51. package/dist/src/tools/GetDependentsTool.js +31 -16
  52. package/dist/src/tools/ListFilesTool.d.ts +3 -1
  53. package/dist/src/tools/ListFilesTool.js +14 -12
  54. package/dist/src/tools/ListProjectsTool.js +5 -1
  55. package/dist/src/tools/ReadFileRangeTool.d.ts +3 -1
  56. package/dist/src/tools/ReadFileRangeTool.js +12 -11
  57. package/dist/src/tools/ReadFileTool.d.ts +3 -1
  58. package/dist/src/tools/ReadFileTool.js +12 -10
  59. package/dist/src/tools/SearchCodebaseTool.js +3 -1
  60. package/dist/src/tools/format-helpers.d.ts +16 -1
  61. package/dist/src/tools/format-helpers.js +64 -0
  62. package/package.json +9 -8
@@ -464,43 +464,19 @@ export declare const codeSymbol: import("drizzle-orm/pg-core").PgTableWithColumn
464
464
  identity: undefined;
465
465
  generated: undefined;
466
466
  }, {}, {}>;
467
- };
468
- dialect: "pg";
469
- }>;
470
- export declare const symbolIndexVersion: import("drizzle-orm/pg-core").PgTableWithColumns<{
471
- name: "symbol_index_version";
472
- schema: undefined;
473
- columns: {
474
- project: import("drizzle-orm/pg-core").PgColumn<{
475
- name: "project";
476
- tableName: "symbol_index_version";
467
+ target: import("drizzle-orm/pg-core").PgColumn<{
468
+ name: "target";
469
+ tableName: "code_symbol";
477
470
  dataType: "string";
478
471
  columnType: "PgText";
479
472
  data: string;
480
473
  driverParam: string;
481
- notNull: true;
482
- hasDefault: false;
483
- isPrimaryKey: true;
484
- isAutoincrement: false;
485
- hasRuntimeDefault: false;
486
- enumValues: [string, ...string[]];
487
- baseColumn: never;
488
- identity: undefined;
489
- generated: undefined;
490
- }, {}, {}>;
491
- version: import("drizzle-orm/pg-core").PgColumn<{
492
- name: "version";
493
- tableName: "symbol_index_version";
494
- dataType: "number";
495
- columnType: "PgInteger";
496
- data: number;
497
- driverParam: string | number;
498
- notNull: true;
474
+ notNull: false;
499
475
  hasDefault: false;
500
476
  isPrimaryKey: false;
501
477
  isAutoincrement: false;
502
478
  hasRuntimeDefault: false;
503
- enumValues: undefined;
479
+ enumValues: [string, ...string[]];
504
480
  baseColumn: never;
505
481
  identity: undefined;
506
482
  generated: undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.indexMeta = exports.codeCall = exports.symbolIndexVersion = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
3
+ exports.indexMeta = exports.codeCall = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
4
4
  const pg_core_1 = require("drizzle-orm/pg-core");
5
5
  // NOTE: the `dimensions` literal below is nominal only. `dataType()` is invoked
6
6
  // solely by drizzle-kit DDL generation, which this project does not use — the
@@ -40,13 +40,7 @@ exports.codeSymbol = (0, pg_core_1.pgTable)('code_symbol', {
40
40
  col: (0, pg_core_1.integer)('col'),
41
41
  container: (0, pg_core_1.text)('container'),
42
42
  isStatic: (0, pg_core_1.boolean)('isStatic').notNull().default(false),
43
- });
44
- // Per-project marker of the symbol/call-graph row format. Not one of the four
45
- // index tables (see the `index-consistency` pruning requirement) — a project
46
- // with only a marker row is not "indexed" and is never pruned for it.
47
- exports.symbolIndexVersion = (0, pg_core_1.pgTable)('symbol_index_version', {
48
- project: (0, pg_core_1.text)('project').primaryKey(),
49
- version: (0, pg_core_1.integer)('version').notNull(),
43
+ target: (0, pg_core_1.text)('target'),
50
44
  });
51
45
  exports.codeCall = (0, pg_core_1.pgTable)('code_call', {
52
46
  id: (0, pg_core_1.uuid)('id').primaryKey().defaultRandom(),
@@ -0,0 +1,16 @@
1
+ /** One edge in the traversal direction: forward (fromFile → toFile) for dependencies, backward for dependents. */
2
+ export interface Edge {
3
+ from: string;
4
+ to: string;
5
+ }
6
+ export interface IndirectRow {
7
+ file: string;
8
+ depth: number;
9
+ via: string;
10
+ }
11
+ /**
12
+ * Breadth-first, one `neighbours` call per depth level. Each reachable file is expanded
13
+ * at most once; `via` ties within a level are broken by byte order, not call order.
14
+ * Does not catch — a rejecting `neighbours` call rejects the whole traversal.
15
+ */
16
+ export declare function traverse(root: string, neighbours: (frontier: string[]) => Promise<Edge[]>, maxDepth?: number): Promise<IndirectRow[]>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.traverse = traverse;
4
+ /** UTF-8 byte order (`COLLATE "C"`), not the platform locale or UTF-16 code-unit order. */
5
+ function byteCompare(a, b) {
6
+ return Buffer.compare(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));
7
+ }
8
+ /**
9
+ * Breadth-first, one `neighbours` call per depth level. Each reachable file is expanded
10
+ * at most once; `via` ties within a level are broken by byte order, not call order.
11
+ * Does not catch — a rejecting `neighbours` call rejects the whole traversal.
12
+ */
13
+ async function traverse(root, neighbours, maxDepth = 20) {
14
+ const visited = new Map();
15
+ visited.set(root, 0);
16
+ const via = new Map();
17
+ let frontier = [root];
18
+ let depth = 1;
19
+ while (frontier.length > 0 && depth <= maxDepth) {
20
+ const edges = await neighbours(frontier);
21
+ const bestVia = new Map();
22
+ for (const edge of edges) {
23
+ if (visited.has(edge.to))
24
+ continue;
25
+ const current = bestVia.get(edge.to);
26
+ if (current === undefined || byteCompare(edge.from, current) < 0)
27
+ bestVia.set(edge.to, edge.from);
28
+ }
29
+ const next = [];
30
+ for (const [to, from] of bestVia) {
31
+ visited.set(to, depth);
32
+ via.set(to, from);
33
+ next.push(to);
34
+ }
35
+ frontier = next;
36
+ depth += 1;
37
+ }
38
+ const rows = [];
39
+ for (const [file, d] of visited) {
40
+ if (d >= 2)
41
+ rows.push({ file, depth: d, via: via.get(file) });
42
+ }
43
+ rows.sort((a, b) => a.depth - b.depth || byteCompare(a.file, b.file));
44
+ return rows;
45
+ }
@@ -2,7 +2,8 @@ import type { ChunkPiece } from './interfaces/IChunker';
2
2
  import type { DepEdge, SymbolEntry } from './interfaces/IAstParser';
3
3
  /**
4
4
  * Build the embedding-input prompt for one chunk. `symbols` is the file's
5
- * top-level declarations only (never members) — the prompt is unaffected by
6
- * this change (see the `symbol-index-members` non-goal).
5
+ * top-level declarations only (never members) — the prompt-owner-line
6
+ * experiment measured adding members and an owner line and found neither met
7
+ * the required MRR@10 gain, so this stays the shipped prompt.
7
8
  */
8
9
  export declare function getEnrichedPrompt(piece: ChunkPiece, relPath: string, serviceName: string, edges: DepEdge[], symbols: SymbolEntry[]): string;
@@ -61,8 +61,9 @@ function inferRole(relPath) {
61
61
  }
62
62
  /**
63
63
  * Build the embedding-input prompt for one chunk. `symbols` is the file's
64
- * top-level declarations only (never members) — the prompt is unaffected by
65
- * this change (see the `symbol-index-members` non-goal).
64
+ * top-level declarations only (never members) — the prompt-owner-line
65
+ * experiment measured adding members and an owner line and found neither met
66
+ * the required MRR@10 gain, so this stays the shipped prompt.
66
67
  */
67
68
  function getEnrichedPrompt(piece, relPath, serviceName, edges, symbols) {
68
69
  const chunkStart = piece.startLine;
@@ -0,0 +1,12 @@
1
+ import type { EvalResultFile, CompareResult } from './interfaces/IRetrievalEvaluator';
2
+ /** `yyyy-mm-dd_hh-mm` in local time, matching the existing `benchmarks/results/` naming. */
3
+ export declare function timestampSlug(date: Date): string;
4
+ /** Writes the JSON + Markdown result files for one eval run; returns their paths. */
5
+ export declare function writeResultFiles(resultsDir: string, suiteLabel: string, result: EvalResultFile, now: Date, comparison?: {
6
+ baselinePath: string;
7
+ result: CompareResult;
8
+ }): {
9
+ jsonPath: string;
10
+ mdPath: string;
11
+ };
12
+ export declare function readResultFile(filePath: string): EvalResultFile;
@@ -0,0 +1,114 @@
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.timestampSlug = timestampSlug;
37
+ exports.writeResultFiles = writeResultFiles;
38
+ exports.readResultFile = readResultFile;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ function pct(n) {
42
+ return `${(n * 100).toFixed(1)}%`;
43
+ }
44
+ function metricsRow(label, m) {
45
+ return `| ${label} | ${m.count} | ${pct(m.hit1)} | ${pct(m.hit5)} | ${m.mrr10.toFixed(4)} |`;
46
+ }
47
+ function deltaRow(label, d) {
48
+ const sign = (n) => (n >= 0 ? '+' : '') + n.toFixed(4);
49
+ return `| ${label} | ${d.count.current} | ${pct(d.hit1.current)} (${sign(d.hit1.delta)}) | ${pct(d.hit5.current)} (${sign(d.hit5.delta)}) | ${d.mrr10.current.toFixed(4)} (${sign(d.mrr10.delta)}) |`;
50
+ }
51
+ function renderMarkdown(result, comparison) {
52
+ const { metadata, run } = result;
53
+ const lines = [];
54
+ lines.push('# Retrieval Evaluation Report', '');
55
+ lines.push(`**Suite:** ${metadata.suitePath} (hash \`${metadata.suiteHash}\`)`);
56
+ lines.push(`**Model:** ${metadata.embeddingProvider}/${metadata.embeddingModel} | **Reranker:** ${metadata.rerankerModel ?? 'none'}`);
57
+ lines.push(`**Commit:** ${metadata.gitCommit} | **Timestamp:** ${metadata.timestamp}`);
58
+ if (metadata.indexMeta) {
59
+ lines.push(`**Index:** ${metadata.indexMeta.provider}/${metadata.indexMeta.model} (${metadata.indexMeta.dimensions} dims)`);
60
+ }
61
+ lines.push('', '## Metrics', '', '| Category | Cases | hit@1 | hit@5 | MRR@10 |', '|---|---|---|---|---|');
62
+ lines.push(metricsRow('Overall', run.metrics.overall));
63
+ for (const category of ['identifier', 'behaviour', 'concept']) {
64
+ const m = run.metrics.byCategory[category];
65
+ if (m)
66
+ lines.push(metricsRow(category, m));
67
+ }
68
+ if (run.invalid.length > 0) {
69
+ lines.push('', '## Invalid cases', '', '| Case | Reason |', '|---|---|');
70
+ for (const c of run.invalid)
71
+ lines.push(`| ${c.id} | ${c.reason}: ${c.invalidExpects.map(e => e.file).join(', ')} |`);
72
+ }
73
+ if (run.warnings.length > 0) {
74
+ lines.push('', '## Warnings (partially invalid)', '', '| Case | Dropped |', '|---|---|');
75
+ for (const w of run.warnings)
76
+ lines.push(`| ${w.id} | ${w.droppedExpects.map(e => e.file).join(', ')} |`);
77
+ }
78
+ if (comparison) {
79
+ lines.push('', `## Comparison against ${comparison.baselinePath}`, '');
80
+ if (comparison.result.conditionWarnings.length > 0) {
81
+ lines.push('> ⚠ ' + comparison.result.conditionWarnings.join('; '), '');
82
+ }
83
+ lines.push('| Category | Cases | hit@1 (Δ) | hit@5 (Δ) | MRR@10 (Δ) |', '|---|---|---|---|---|');
84
+ lines.push(deltaRow('Overall', comparison.result.overall));
85
+ for (const category of ['identifier', 'behaviour', 'concept']) {
86
+ const d = comparison.result.byCategory[category];
87
+ if (d)
88
+ lines.push(deltaRow(category, d));
89
+ }
90
+ if (comparison.result.hit5Lost.length > 0)
91
+ lines.push('', `**Lost hit@5:** ${comparison.result.hit5Lost.join(', ')}`);
92
+ if (comparison.result.hit5Gained.length > 0)
93
+ lines.push('', `**Gained hit@5:** ${comparison.result.hit5Gained.join(', ')}`);
94
+ }
95
+ return lines.join('\n') + '\n';
96
+ }
97
+ /** `yyyy-mm-dd_hh-mm` in local time, matching the existing `benchmarks/results/` naming. */
98
+ function timestampSlug(date) {
99
+ const pad = (n) => String(n).padStart(2, '0');
100
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}`;
101
+ }
102
+ /** Writes the JSON + Markdown result files for one eval run; returns their paths. */
103
+ function writeResultFiles(resultsDir, suiteLabel, result, now, comparison) {
104
+ fs.mkdirSync(resultsDir, { recursive: true });
105
+ const base = `eval_${suiteLabel}_${timestampSlug(now)}`;
106
+ const jsonPath = path.join(resultsDir, `${base}.json`);
107
+ const mdPath = path.join(resultsDir, `${base}.md`);
108
+ fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2) + '\n', 'utf-8');
109
+ fs.writeFileSync(mdPath, renderMarkdown(result, comparison), 'utf-8');
110
+ return { jsonPath, mdPath };
111
+ }
112
+ function readResultFile(filePath) {
113
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
114
+ }
@@ -10,4 +10,19 @@ export type RagMetaResult = ProjectMeta | 'missing' | 'invalid';
10
10
  export declare function readRagMeta(projectDir: string): RagMetaResult;
11
11
  export declare const IGNORE_DIRS: Set<string>;
12
12
  export declare function loadIgnore(projectDir: string): ReturnType<typeof ignore>;
13
+ export declare function isIndexableFileName(name: string): boolean;
14
+ export type IndexingExclusion = {
15
+ kind: 'type';
16
+ } | {
17
+ kind: 'ignored_dir';
18
+ segment: string;
19
+ } | {
20
+ kind: 'ragignore';
21
+ };
22
+ /**
23
+ * Why `collectFiles` would skip the file at `relPath` (relative to the project
24
+ * root, `/`-separated), or null when it would collect it. Checks mirror
25
+ * `collectFiles`: file name, `IGNORE_DIRS` segments, `.ragignore` on every prefix.
26
+ */
27
+ export declare function indexingExclusion(relPath: string, ig: ReturnType<typeof ignore>): IndexingExclusion | null;
13
28
  export declare function collectFiles(dir: string, ig: ReturnType<typeof ignore>, rootDir: string): string[];
@@ -39,6 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.IGNORE_DIRS = void 0;
40
40
  exports.readRagMeta = readRagMeta;
41
41
  exports.loadIgnore = loadIgnore;
42
+ exports.isIndexableFileName = isIndexableFileName;
43
+ exports.indexingExclusion = indexingExclusion;
42
44
  exports.collectFiles = collectFiles;
43
45
  const fs = __importStar(require("fs"));
44
46
  const path = __importStar(require("path"));
@@ -75,6 +77,27 @@ function loadIgnore(projectDir) {
75
77
  }
76
78
  return ig;
77
79
  }
80
+ function isIndexableFileName(name) {
81
+ return SOURCE_EXTENSIONS.test(name) && !IGNORE_EXTENSIONS.test(name);
82
+ }
83
+ /**
84
+ * Why `collectFiles` would skip the file at `relPath` (relative to the project
85
+ * root, `/`-separated), or null when it would collect it. Checks mirror
86
+ * `collectFiles`: file name, `IGNORE_DIRS` segments, `.ragignore` on every prefix.
87
+ */
88
+ function indexingExclusion(relPath, ig) {
89
+ const segments = relPath.split('/');
90
+ if (!isIndexableFileName(segments[segments.length - 1]))
91
+ return { kind: 'type' };
92
+ const ignoredDir = segments.slice(0, -1).find(s => exports.IGNORE_DIRS.has(s));
93
+ if (ignoredDir)
94
+ return { kind: 'ignored_dir', segment: ignoredDir };
95
+ for (let i = 1; i <= segments.length; i++) {
96
+ if (ig.ignores(segments.slice(0, i).join('/')))
97
+ return { kind: 'ragignore' };
98
+ }
99
+ return null;
100
+ }
78
101
  function collectFiles(dir, ig, rootDir) {
79
102
  const result = [];
80
103
  let entries;
@@ -94,7 +117,7 @@ function collectFiles(dir, ig, rootDir) {
94
117
  if (!exports.IGNORE_DIRS.has(file))
95
118
  result.push(...collectFiles(fullPath, ig, rootDir));
96
119
  }
97
- else if (SOURCE_EXTENSIONS.test(file) && !IGNORE_EXTENSIONS.test(file)) {
120
+ else if (isIndexableFileName(file)) {
98
121
  result.push(fullPath);
99
122
  }
100
123
  }
package/dist/src/help.js CHANGED
@@ -13,6 +13,9 @@ function printHelp(command) {
13
13
  console.log('Options:');
14
14
  console.log(' --reindex Force re-index all files (ignores cached mtimes)');
15
15
  console.log(' --select Interactively select which projects to index');
16
+ console.log(' --project [name] Scope this run to one discovered project or monorepo unit');
17
+ console.log(' by exact name, bypassing selection and pruning; omit the');
18
+ console.log(' name for an interactive single-choice prompt');
16
19
  console.log(' -h, --help Show this help message');
17
20
  break;
18
21
  case 'rag-mcp':
@@ -72,7 +75,7 @@ function printHelp(command) {
72
75
  console.log(' -h, --help Show this help message');
73
76
  break;
74
77
  default:
75
- console.log('Usage: rag-index [--reindex] [--select]');
78
+ console.log('Usage: rag-index [--reindex] [--select] [--project [name]]');
76
79
  console.log(' rag-mcp');
77
80
  console.log(' rag-setup');
78
81
  console.log(' rag-config <show|get|set|reset|edit>');
@@ -6,7 +6,7 @@ export interface DepEdge {
6
6
  toSymbol: string | null;
7
7
  depType: string;
8
8
  }
9
- export type SymbolKind = 'function' | 'class' | 'interface' | 'type' | 'variable' | 'method' | 'get_accessor' | 'set_accessor' | 'constructor' | 'function_property' | 'method_signature';
9
+ export type SymbolKind = 'function' | 'class' | 'interface' | 'type' | 'variable' | 'method' | 'get_accessor' | 'set_accessor' | 'constructor' | 'function_property' | 'method_signature' | 'graphql_query' | 'graphql_mutation' | 'graphql_subscription' | 'graphql_field' | 'graphql_type';
10
10
  export interface SymbolEntry {
11
11
  project: string;
12
12
  filePath: string;
@@ -19,6 +19,11 @@ export interface SymbolEntry {
19
19
  /** Dotted path of enclosing named scopes, outermost first; null for top-level declarations. */
20
20
  container: string | null;
21
21
  isStatic: boolean;
22
+ /**
23
+ * Qualified TypeScript name of the decorated declaration, set only on
24
+ * `graphql-symbols` rows (`graphqlNames`); null on every `symbols`/`members` entry.
25
+ */
26
+ target?: string | null;
22
27
  /**
23
28
  * Whether this declaration node has a body (vs. an overload signature).
24
29
  * Internal — used only to pick an overload set's representative row before
@@ -60,6 +65,13 @@ export interface FileAnalysis {
60
65
  calls: CallEdge[];
61
66
  /** Ownership ranges (decorator-inclusive) for every indexed declaration except `method_signature`. */
62
67
  ownerRanges?: OwnerRange[];
68
+ /**
69
+ * GraphQL-named rows (`@Query`/`@Mutation`/`@Subscription`/`@ResolveField`/
70
+ * `@ObjectType`/`@InputType`/`@InterfaceType` of `@nestjs/graphql`). Written to
71
+ * `code_symbol` alongside `symbols`/`members`; never fed to the chunker, the
72
+ * prompt, `buildOwnerRanges` or `extractCalls` (see `graphql-symbols`).
73
+ */
74
+ graphqlNames: SymbolEntry[];
63
75
  }
64
76
  export interface AnalyzeProjectOpts {
65
77
  skipDependencyResolution?: boolean;
@@ -41,18 +41,4 @@ export interface IDatabase {
41
41
  listIndexedProjects(): Promise<string[]>;
42
42
  /** Delete all rows for `name` from the four index tables in one transaction. */
43
43
  deleteProject(name: string): Promise<void>;
44
- /** Every project with at least one row in `code_chunk` or `code_symbol` — the
45
- * symbol/call-graph lookup scope (distinct from `listIndexedProjects`, which
46
- * also covers `code_call`/`code_dependency`). */
47
- listProjectsWithIndexRows(): Promise<string[]>;
48
- /**
49
- * Among `projectNames` (or every project with rows when null), those that
50
- * are outdated: at least one row in `code_chunk`/`code_symbol` and a
51
- * `symbol_index_version` below the current format version (missing row = 1).
52
- * A missing `symbol_index_version` table counts as "no marker rows" for every
53
- * project. Any other database error propagates.
54
- */
55
- listOutdatedProjects(projectNames: string[] | null): Promise<string[]>;
56
- /** Upsert a project's `symbol_index_version` marker to the current format version. */
57
- upsertSymbolIndexVersion(project: string): Promise<void>;
58
44
  }
@@ -0,0 +1,94 @@
1
+ export type EvalCategory = 'identifier' | 'behaviour' | 'concept';
2
+ export interface ExpectedAnswer {
3
+ file: string;
4
+ owner?: string;
5
+ }
6
+ export interface EvalCase {
7
+ id: string;
8
+ project: string;
9
+ category: EvalCategory;
10
+ query: string;
11
+ expect: ExpectedAnswer[];
12
+ threshold?: number;
13
+ }
14
+ export interface EvalSuite {
15
+ version: 1;
16
+ cases: EvalCase[];
17
+ }
18
+ export interface SearchHit {
19
+ filePath: string;
20
+ ownerPath: string | null;
21
+ }
22
+ export type SearchFn = (query: string, limit: number, project: string | null, threshold?: number) => Promise<SearchHit[]>;
23
+ /** Index-inspection callbacks used to validate a case's expected answers before scoring. */
24
+ export interface IndexInspector {
25
+ isFileIndexed(project: string, file: string): Promise<boolean>;
26
+ ownerPathsOfFile(project: string, file: string): Promise<string[]>;
27
+ }
28
+ export interface Metrics {
29
+ count: number;
30
+ hit1: number;
31
+ hit5: number;
32
+ mrr10: number;
33
+ }
34
+ export interface CaseResult {
35
+ id: string;
36
+ project: string;
37
+ category: EvalCategory;
38
+ query: string;
39
+ /** 1-based position of the first matching result within the top 10, null when unranked. */
40
+ rank: number | null;
41
+ matched: SearchHit | null;
42
+ }
43
+ export interface InvalidCase {
44
+ id: string;
45
+ reason: string;
46
+ invalidExpects: ExpectedAnswer[];
47
+ }
48
+ export interface PartialWarning {
49
+ id: string;
50
+ droppedExpects: ExpectedAnswer[];
51
+ }
52
+ export interface EvalRunResult {
53
+ cases: CaseResult[];
54
+ invalid: InvalidCase[];
55
+ warnings: PartialWarning[];
56
+ metrics: {
57
+ overall: Metrics;
58
+ byCategory: Partial<Record<EvalCategory, Metrics>>;
59
+ };
60
+ }
61
+ export interface IRetrievalEvaluator {
62
+ evaluate(suite: EvalSuite, search: SearchFn, inspect: IndexInspector): Promise<EvalRunResult>;
63
+ }
64
+ export interface ResultMetadata {
65
+ suitePath: string;
66
+ suiteHash: string;
67
+ embeddingProvider: string;
68
+ embeddingModel: string;
69
+ rerankerModel: string | null;
70
+ indexMeta: {
71
+ provider: string;
72
+ model: string;
73
+ dimensions: number;
74
+ } | null;
75
+ gitCommit: string;
76
+ timestamp: string;
77
+ }
78
+ export interface EvalResultFile {
79
+ metadata: ResultMetadata;
80
+ run: EvalRunResult;
81
+ }
82
+ export interface MetricDelta {
83
+ baseline: number;
84
+ current: number;
85
+ delta: number;
86
+ }
87
+ export type MetricsDelta = Record<keyof Metrics, MetricDelta>;
88
+ export interface CompareResult {
89
+ overall: MetricsDelta;
90
+ byCategory: Partial<Record<EvalCategory, MetricsDelta>>;
91
+ hit5Gained: string[];
92
+ hit5Lost: string[];
93
+ conditionWarnings: string[];
94
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,4 @@
1
- import type { SearchResult, NeighborResult, GraphContext, DependencyRow, ProjectRow, GrepMatch, SymbolKind, SymbolHit, CallerRow, FileRangeResult } from '../services/SearcherService';
1
+ import type { SearchResult, NeighborResult, GraphContext, DependencyRow, ProjectRow, SymbolKind, SymbolHit, CallerRow, FileRangeResult, ListFilesResult, GrepSymbolResult, ResolvedToolPath, PathFailure } from '../services/SearcherService';
2
2
  /**
3
3
  * Searcher abstraction — McpServer depends on this interface
4
4
  * rather than the concrete SearcherService.
@@ -14,27 +14,22 @@ export interface ISearcher {
14
14
  from: string;
15
15
  to: string;
16
16
  }[]>;
17
- searchChunks(query: string, limit: number, project: string | null, threshold?: number): Promise<SearchResult[]>;
17
+ searchChunks(query: string, limit: number, project: string | null, threshold?: number, rerankOverride?: boolean): Promise<SearchResult[]>;
18
18
  fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
19
19
  fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
20
- getDependencies(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
21
- getDependents(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
20
+ getDependencies(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
21
+ getDependents(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
22
22
  getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
23
- grepSymbol(symbol: string, project: string, excludeFile: string): Promise<{
24
- matches: GrepMatch[];
25
- truncated: boolean;
26
- }>;
23
+ grepSymbol(symbol: string, rootDir: string, excludeFile: string): Promise<GrepSymbolResult>;
27
24
  findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
28
25
  hits: SymbolHit[];
29
26
  truncated: boolean;
30
27
  grepFailed: boolean;
31
28
  }>;
32
- /** One-line warning when a project in scope is outdated (see `SearcherService.outdatedNotice`), else null. */
33
- outdatedNotice(project: string | null): Promise<string | null>;
34
- listFiles(relativePath: string, project: string, depth: number, pattern: string | null): {
35
- tree: string;
36
- error?: string;
37
- };
38
- readFileAbsolute(absPath: string, maxLines: number): Promise<FileRangeResult>;
39
- readFileRange(filePath: string, startLine: number, endLine: number): Promise<FileRangeResult>;
29
+ listFiles(relPath: string, depth: number, pattern: string | null): Promise<ListFilesResult>;
30
+ readFile(relPath: string, maxLines: number): Promise<FileRangeResult>;
31
+ readFileRange(relPath: string, startLine: number, endLine: number): Promise<FileRangeResult>;
32
+ resolveToolPath(input: string | null | undefined): Promise<ResolvedToolPath>;
33
+ checkGraphPath(relPath: string): Promise<PathFailure | null>;
34
+ fallbackRoot(relPath: string): string | null;
40
35
  }
@@ -6,3 +6,4 @@ export type { IDatabase } from './IDatabase';
6
6
  export type { ISearcher } from './ISearcher';
7
7
  export type { IProjectDiscovery, DiscoveredProject } from './IProjectDiscovery';
8
8
  export type { IMcpToolHandler, ToolDefinition, ToolResponse } from './IMcpToolHandler';
9
+ export type { EvalCategory, ExpectedAnswer, EvalCase, EvalSuite, SearchHit, SearchFn, IndexInspector, Metrics, CaseResult, InvalidCase, PartialWarning, EvalRunResult, IRetrievalEvaluator, ResultMetadata, EvalResultFile, MetricDelta, MetricsDelta, CompareResult, } from './IRetrievalEvaluator';
@@ -0,0 +1,19 @@
1
+ import type { DiscoveredProject } from './interfaces';
2
+ export interface ResolveProjectNamesResult {
3
+ targets: DiscoveredProject[];
4
+ notFound: string[];
5
+ }
6
+ /**
7
+ * Resolve one or more `--project`-style names against discovered units: a
8
+ * unit name expands to all of its children, a leaf name resolves to itself.
9
+ * Units are tried before leaves so a top-level project named the same as a
10
+ * nested-monorepo child's bare name can never shadow it. Targets are
11
+ * deduplicated by leaf name and ordered by first-resolving input name.
12
+ */
13
+ export declare function resolveProjectNames(names: string[], units: DiscoveredProject[]): ResolveProjectNamesResult;
14
+ /**
15
+ * Every name `--project` would accept as a match, in discovery order: each
16
+ * unit's own name, immediately followed by its expanded leaf names (an
17
+ * ordinary project — no children — contributes only its own name once).
18
+ */
19
+ export declare function listAvailableNames(units: DiscoveredProject[]): string[];
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveProjectNames = resolveProjectNames;
4
+ exports.listAvailableNames = listAvailableNames;
5
+ const MonorepoAwareDiscoveryService_1 = require("./services/MonorepoAwareDiscoveryService");
6
+ /**
7
+ * Resolve one or more `--project`-style names against discovered units: a
8
+ * unit name expands to all of its children, a leaf name resolves to itself.
9
+ * Units are tried before leaves so a top-level project named the same as a
10
+ * nested-monorepo child's bare name can never shadow it. Targets are
11
+ * deduplicated by leaf name and ordered by first-resolving input name.
12
+ */
13
+ function resolveProjectNames(names, units) {
14
+ const leaves = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(units);
15
+ const targets = [];
16
+ const seen = new Set();
17
+ const notFound = [];
18
+ for (const name of names) {
19
+ const unit = units.find(u => u.name === name);
20
+ const matches = unit ? (0, MonorepoAwareDiscoveryService_1.toLeafProjects)([unit]) : [leaves.find(l => l.name === name)].filter((p) => p !== undefined);
21
+ if (matches.length === 0) {
22
+ notFound.push(name);
23
+ continue;
24
+ }
25
+ for (const leaf of matches) {
26
+ if (seen.has(leaf.name))
27
+ continue;
28
+ seen.add(leaf.name);
29
+ targets.push(leaf);
30
+ }
31
+ }
32
+ return { targets, notFound };
33
+ }
34
+ /**
35
+ * Every name `--project` would accept as a match, in discovery order: each
36
+ * unit's own name, immediately followed by its expanded leaf names (an
37
+ * ordinary project — no children — contributes only its own name once).
38
+ */
39
+ function listAvailableNames(units) {
40
+ const names = [];
41
+ for (const unit of units) {
42
+ names.push(unit.name);
43
+ if (unit.children && unit.children.length > 0) {
44
+ for (const child of unit.children)
45
+ names.push(child.name);
46
+ }
47
+ }
48
+ return names;
49
+ }