codesearch-mcp 0.2.1-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.
- package/CHANGELOG.md +40 -0
- package/README.md +15 -8
- package/dist/app.js +45 -7
- package/dist/src/commands/EvalCommand.d.ts +10 -0
- package/dist/src/commands/EvalCommand.js +231 -0
- package/dist/src/commands/IndexCommand.js +79 -53
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +1 -2
- package/dist/src/db/schema.d.ts +5 -29
- package/dist/src/db/schema.js +2 -8
- package/dist/src/dependency-traversal.d.ts +16 -0
- package/dist/src/dependency-traversal.js +45 -0
- package/dist/src/enriched-prompt.d.ts +3 -2
- package/dist/src/enriched-prompt.js +3 -2
- package/dist/src/eval-results.d.ts +12 -0
- package/dist/src/eval-results.js +114 -0
- package/dist/src/file-utils.d.ts +15 -0
- package/dist/src/file-utils.js +24 -1
- package/dist/src/help.js +4 -1
- package/dist/src/interfaces/IAstParser.d.ts +13 -1
- package/dist/src/interfaces/IDatabase.d.ts +0 -14
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +94 -0
- package/dist/src/interfaces/IRetrievalEvaluator.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +11 -16
- package/dist/src/interfaces/index.d.ts +1 -0
- package/dist/src/project-scope.d.ts +19 -0
- package/dist/src/project-scope.js +49 -0
- package/dist/src/project-selector.d.ts +6 -0
- package/dist/src/project-selector.js +18 -0
- package/dist/src/retrieval-eval.d.ts +16 -0
- package/dist/src/retrieval-eval.js +214 -0
- package/dist/src/services/AstParserService.d.ts +66 -1
- package/dist/src/services/AstParserService.js +432 -28
- package/dist/src/services/DatabaseService.d.ts +0 -12
- package/dist/src/services/DatabaseService.js +2 -52
- package/dist/src/services/IndexerService.d.ts +8 -0
- package/dist/src/services/IndexerService.js +24 -11
- package/dist/src/services/McpServer.d.ts +7 -0
- package/dist/src/services/McpServer.js +42 -38
- package/dist/src/services/OpenAICompatibleEmbedder.js +2 -1
- package/dist/src/services/SearcherService.d.ts +189 -25
- package/dist/src/services/SearcherService.js +505 -203
- package/dist/src/symbol-rows.d.ts +14 -9
- package/dist/src/symbol-rows.js +17 -11
- package/dist/src/tools/FindSymbolTool.js +14 -5
- package/dist/src/tools/GetCallersTool.d.ts +3 -1
- package/dist/src/tools/GetCallersTool.js +16 -6
- package/dist/src/tools/GetDependenciesTool.d.ts +3 -1
- package/dist/src/tools/GetDependenciesTool.js +15 -8
- package/dist/src/tools/GetDependentsTool.d.ts +3 -1
- package/dist/src/tools/GetDependentsTool.js +31 -16
- package/dist/src/tools/ListFilesTool.d.ts +3 -1
- package/dist/src/tools/ListFilesTool.js +14 -12
- package/dist/src/tools/ListProjectsTool.js +5 -1
- package/dist/src/tools/ReadFileRangeTool.d.ts +3 -1
- package/dist/src/tools/ReadFileRangeTool.js +12 -11
- package/dist/src/tools/ReadFileTool.d.ts +3 -1
- package/dist/src/tools/ReadFileTool.js +12 -10
- package/dist/src/tools/SearchCodebaseTool.js +3 -1
- package/dist/src/tools/format-helpers.d.ts +16 -1
- package/dist/src/tools/format-helpers.js +64 -0
- package/package.json +9 -8
package/dist/src/db/schema.d.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
479
|
+
enumValues: [string, ...string[]];
|
|
504
480
|
baseColumn: never;
|
|
505
481
|
identity: undefined;
|
|
506
482
|
generated: undefined;
|
package/dist/src/db/schema.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.indexMeta = exports.codeCall = exports.
|
|
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
|
|
6
|
-
*
|
|
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
|
|
65
|
-
*
|
|
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
|
+
}
|
package/dist/src/file-utils.d.ts
CHANGED
|
@@ -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[];
|
package/dist/src/file-utils.js
CHANGED
|
@@ -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 (
|
|
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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SearchResult, NeighborResult, GraphContext, DependencyRow, ProjectRow,
|
|
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,
|
|
21
|
-
getDependents(filePath: string,
|
|
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,
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
+
}
|