codesearch-mcp 0.2.3-alpha → 0.3.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 (141) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +46 -26
  3. package/dist/app.js +5 -19
  4. package/dist/src/chunking/AstChunker.d.ts +12 -0
  5. package/dist/src/chunking/AstChunker.js +30 -0
  6. package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
  7. package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
  8. package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
  9. package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
  10. package/dist/src/chunking/FragmentMerger.d.ts +11 -0
  11. package/dist/src/chunking/FragmentMerger.js +61 -0
  12. package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
  13. package/dist/src/chunking/ImportOnlyFilter.js +14 -0
  14. package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
  15. package/dist/src/chunking/LangchainTextSplitter.js +133 -0
  16. package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
  17. package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
  18. package/dist/src/chunking/RunPacker.d.ts +12 -0
  19. package/dist/src/chunking/RunPacker.js +84 -0
  20. package/dist/src/chunking/create-chunker.d.ts +3 -0
  21. package/dist/src/chunking/create-chunker.js +15 -0
  22. package/dist/src/chunking/interfaces.d.ts +27 -0
  23. package/dist/src/chunking/interfaces.js +2 -0
  24. package/dist/src/commands/ConfigCommand.js +1 -9
  25. package/dist/src/commands/EvalCommand.d.ts +4 -3
  26. package/dist/src/commands/EvalCommand.js +85 -52
  27. package/dist/src/commands/IndexCommand.js +0 -22
  28. package/dist/src/commands/McpCommand.js +2 -8
  29. package/dist/src/commands/WebCommand.js +2 -5
  30. package/dist/src/config-error.d.ts +0 -4
  31. package/dist/src/config-error.js +0 -4
  32. package/dist/src/config-store.d.ts +1 -54
  33. package/dist/src/config-store.js +8 -69
  34. package/dist/src/config.d.ts +8 -17
  35. package/dist/src/config.js +35 -34
  36. package/dist/src/constants.d.ts +2 -28
  37. package/dist/src/constants.js +6 -28
  38. package/dist/src/container.d.ts +6 -0
  39. package/dist/src/container.js +12 -3
  40. package/dist/src/db/schema.d.ts +17 -0
  41. package/dist/src/db/schema.js +1 -6
  42. package/dist/src/declarations/DeclarationTree.d.ts +19 -0
  43. package/dist/src/declarations/DeclarationTree.js +66 -0
  44. package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
  45. package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
  46. package/dist/src/declarations/SourceLines.d.ts +37 -0
  47. package/dist/src/declarations/SourceLines.js +415 -0
  48. package/dist/src/dependency-traversal.d.ts +0 -6
  49. package/dist/src/dependency-traversal.js +0 -6
  50. package/dist/src/enriched-prompt.d.ts +0 -6
  51. package/dist/src/enriched-prompt.js +0 -6
  52. package/dist/src/error-record.d.ts +12 -0
  53. package/dist/src/error-record.js +248 -0
  54. package/dist/src/eval-corpus.d.ts +16 -0
  55. package/dist/src/eval-corpus.js +169 -0
  56. package/dist/src/eval-results.d.ts +6 -3
  57. package/dist/src/eval-results.js +309 -26
  58. package/dist/src/file-utils.d.ts +0 -5
  59. package/dist/src/file-utils.js +0 -5
  60. package/dist/src/graph.d.ts +0 -25
  61. package/dist/src/graph.js +0 -28
  62. package/dist/src/hnsw-settings.d.ts +3 -0
  63. package/dist/src/hnsw-settings.js +24 -0
  64. package/dist/src/interfaces/IAstParser.d.ts +2 -39
  65. package/dist/src/interfaces/IChunker.d.ts +3 -12
  66. package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
  67. package/dist/src/interfaces/ICorpusInspector.js +2 -0
  68. package/dist/src/interfaces/IDatabase.d.ts +2 -22
  69. package/dist/src/interfaces/IEmbedder.d.ts +0 -9
  70. package/dist/src/interfaces/ILogger.d.ts +1 -11
  71. package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
  72. package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
  73. package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
  74. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
  75. package/dist/src/interfaces/ISearcher.d.ts +16 -5
  76. package/dist/src/interrupt.js +0 -2
  77. package/dist/src/owner-path.d.ts +9 -10
  78. package/dist/src/owner-path.js +11 -23
  79. package/dist/src/project-scope.d.ts +0 -12
  80. package/dist/src/project-scope.js +0 -12
  81. package/dist/src/project-selector.d.ts +0 -5
  82. package/dist/src/project-selector.js +0 -11
  83. package/dist/src/retrieval-eval.d.ts +14 -6
  84. package/dist/src/retrieval-eval.js +345 -59
  85. package/dist/src/search/result-shaping.d.ts +4 -0
  86. package/dist/src/search/result-shaping.js +19 -0
  87. package/dist/src/services/AstParserService.d.ts +2 -62
  88. package/dist/src/services/AstParserService.js +25 -137
  89. package/dist/src/services/DatabaseService.d.ts +2 -7
  90. package/dist/src/services/DatabaseService.js +42 -29
  91. package/dist/src/services/EmbedderFactory.d.ts +0 -4
  92. package/dist/src/services/EmbedderFactory.js +0 -5
  93. package/dist/src/services/IndexerService.d.ts +0 -16
  94. package/dist/src/services/IndexerService.js +16 -45
  95. package/dist/src/services/InfinityEmbedder.d.ts +0 -21
  96. package/dist/src/services/InfinityEmbedder.js +0 -15
  97. package/dist/src/services/LoggerService.d.ts +7 -5
  98. package/dist/src/services/LoggerService.js +37 -41
  99. package/dist/src/services/McpServer.d.ts +22 -14
  100. package/dist/src/services/McpServer.js +70 -37
  101. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
  102. package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
  103. package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
  104. package/dist/src/services/NxMonorepoResolver.js +5 -56
  105. package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
  106. package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
  107. package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
  108. package/dist/src/services/ProjectDiscoveryService.js +0 -4
  109. package/dist/src/services/SearcherService.d.ts +9 -116
  110. package/dist/src/services/SearcherService.js +95 -176
  111. package/dist/src/services/VoyageEmbedder.d.ts +0 -6
  112. package/dist/src/services/VoyageEmbedder.js +0 -6
  113. package/dist/src/services/WebServerService.d.ts +1 -53
  114. package/dist/src/services/WebServerService.js +11 -57
  115. package/dist/src/services/http-retry.d.ts +0 -7
  116. package/dist/src/services/http-retry.js +1 -10
  117. package/dist/src/services/infinity-provisioning.d.ts +0 -74
  118. package/dist/src/services/infinity-provisioning.js +5 -88
  119. package/dist/src/services/legacy-ollama-index.d.ts +0 -16
  120. package/dist/src/services/legacy-ollama-index.js +0 -23
  121. package/dist/src/services/reranker.d.ts +0 -9
  122. package/dist/src/services/reranker.js +46 -12
  123. package/dist/src/setup.d.ts +11 -23
  124. package/dist/src/setup.js +98 -71
  125. package/dist/src/symbol-name.d.ts +0 -12
  126. package/dist/src/symbol-name.js +0 -13
  127. package/dist/src/symbol-rows.d.ts +0 -14
  128. package/dist/src/symbol-rows.js +0 -14
  129. package/dist/src/tool-call-log.d.ts +2 -0
  130. package/dist/src/tool-call-log.js +24 -0
  131. package/dist/src/tools/GetDependenciesTool.js +1 -1
  132. package/dist/src/tools/GetDependentsTool.js +1 -1
  133. package/dist/src/tools/SearchCodebaseTool.js +6 -3
  134. package/dist/src/tools/format-helpers.d.ts +0 -6
  135. package/dist/src/tools/format-helpers.js +87 -14
  136. package/dist/src/web/dashboard.d.ts +0 -8
  137. package/dist/src/web/dashboard.js +7 -10
  138. package/package.json +2 -2
  139. package/dist/src/services/AstChunker.d.ts +0 -42
  140. package/dist/src/services/AstChunker.js +0 -158
  141. package/docs/web-dashboard.md +0 -206
@@ -0,0 +1,21 @@
1
+ import type { Config } from '../config';
2
+ import type { ChunkPiece } from '../interfaces/IChunker';
3
+ import type { ITextSplitter, KeepWindow } from './interfaces';
4
+ export interface SplitterOptions {
5
+ chunkSize: number;
6
+ chunkOverlap: number;
7
+ separators: string[];
8
+ }
9
+ export declare class LangchainTextSplitter implements ITextSplitter {
10
+ private readonly opts;
11
+ constructor(opts: SplitterOptions);
12
+ static forJs(config: Config): LangchainTextSplitter;
13
+ static forSchema(config: Config): LangchainTextSplitter;
14
+ split(text: string, firstLine: number, keep?: KeepWindow): Promise<ChunkPiece[]>;
15
+ private splitSpan;
16
+ private static splitOn;
17
+ private static joinKept;
18
+ private merge;
19
+ private static trim;
20
+ private static newlines;
21
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LangchainTextSplitter = void 0;
4
+ const textsplitters_1 = require("@langchain/textsplitters");
5
+ class LangchainTextSplitter {
6
+ opts;
7
+ constructor(opts) {
8
+ this.opts = opts;
9
+ if (opts.chunkOverlap >= opts.chunkSize)
10
+ throw new Error('Cannot have chunkOverlap >= chunkSize');
11
+ }
12
+ static forJs(config) {
13
+ return new LangchainTextSplitter({
14
+ chunkSize: config.indexer.chunkSize,
15
+ chunkOverlap: config.indexer.chunkOverlap,
16
+ separators: textsplitters_1.RecursiveCharacterTextSplitter.getSeparatorsForLanguage('js'),
17
+ });
18
+ }
19
+ static forSchema(config) {
20
+ return new LangchainTextSplitter({
21
+ chunkSize: config.indexer.chunkSize,
22
+ chunkOverlap: config.indexer.chunkOverlap,
23
+ separators: ['\n\n', '\n', ' '],
24
+ });
25
+ }
26
+ async split(text, firstLine, keep = { from: 0, to: 0 }) {
27
+ const pieces = [];
28
+ let offset = 0;
29
+ let line = firstLine;
30
+ for (const span of this.splitSpan(text, { start: 0, end: text.length }, this.opts.separators, keep)) {
31
+ line += LangchainTextSplitter.newlines(text, offset, span.start);
32
+ offset = span.start;
33
+ const content = text.slice(span.start, span.end);
34
+ pieces.push({ content, startLine: line, endLine: line + LangchainTextSplitter.newlines(content, 0, content.length) });
35
+ }
36
+ return pieces;
37
+ }
38
+ splitSpan(text, span, separators, keep) {
39
+ const sub = text.slice(span.start, span.end);
40
+ let separator = separators[separators.length - 1];
41
+ let next;
42
+ for (let i = 0; i < separators.length; i++) {
43
+ if (separators[i] === '') {
44
+ separator = '';
45
+ break;
46
+ }
47
+ if (sub.includes(separators[i])) {
48
+ separator = separators[i];
49
+ next = separators.slice(i + 1);
50
+ break;
51
+ }
52
+ }
53
+ const out = [];
54
+ let good = [];
55
+ const splits = LangchainTextSplitter.splitOn(sub, separator, span.start);
56
+ for (const s of separator ? LangchainTextSplitter.joinKept(splits, keep) : splits) {
57
+ if (s.end - s.start < this.opts.chunkSize) {
58
+ good.push(s);
59
+ continue;
60
+ }
61
+ if (good.length) {
62
+ out.push(...this.merge(text, good));
63
+ good = [];
64
+ }
65
+ out.push(...(next ? this.splitSpan(text, s, next, keep) : [s]));
66
+ }
67
+ if (good.length)
68
+ out.push(...this.merge(text, good));
69
+ return out;
70
+ }
71
+ static splitOn(sub, separator, base) {
72
+ const parts = separator
73
+ ? sub.split(new RegExp(`(?=${separator.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')})`))
74
+ : sub.split('');
75
+ const spans = [];
76
+ let at = base;
77
+ for (const p of parts) {
78
+ if (p !== '')
79
+ spans.push({ start: at, end: at + p.length });
80
+ at += p.length;
81
+ }
82
+ return spans;
83
+ }
84
+ static joinKept(splits, keep) {
85
+ const out = [];
86
+ for (const s of splits) {
87
+ if (out.length && s.start > keep.from && s.start <= keep.to)
88
+ out[out.length - 1] = { start: out[out.length - 1].start, end: s.end };
89
+ else
90
+ out.push(s);
91
+ }
92
+ return out;
93
+ }
94
+ merge(text, splits) {
95
+ const { chunkSize, chunkOverlap } = this.opts;
96
+ const docs = [];
97
+ const current = [];
98
+ let total = 0;
99
+ const flush = () => {
100
+ const doc = LangchainTextSplitter.trim(text, { start: current[0].start, end: current[current.length - 1].end });
101
+ if (doc)
102
+ docs.push(doc);
103
+ };
104
+ for (const d of splits) {
105
+ const len = d.end - d.start;
106
+ if (total + len > chunkSize && current.length > 0) {
107
+ flush();
108
+ while (total > chunkOverlap || (total + len > chunkSize && total > 0)) {
109
+ total -= current[0].end - current[0].start;
110
+ current.shift();
111
+ }
112
+ }
113
+ current.push(d);
114
+ total += len;
115
+ }
116
+ if (current.length > 0)
117
+ flush();
118
+ return docs;
119
+ }
120
+ static trim(text, span) {
121
+ let { start, end } = span;
122
+ while (start < end && /\s/.test(text[start]))
123
+ start++;
124
+ while (end > start && /\s/.test(text[end - 1]))
125
+ end--;
126
+ return start < end ? { start, end } : null;
127
+ }
128
+ static newlines(text, from, to) {
129
+ const count = (a, b) => (text.slice(a, b).match(/\n/g) ?? []).length;
130
+ return to >= from ? count(from, to) : -count(to, from);
131
+ }
132
+ }
133
+ exports.LangchainTextSplitter = LangchainTextSplitter;
@@ -0,0 +1,9 @@
1
+ import type { DeclarationNode } from '../declarations/DeclarationTree';
2
+ import type { SourceLines } from '../declarations/SourceLines';
3
+ import type { ISplitPolicy } from './interfaces';
4
+ export declare const MIN_MEMBER_CHARS = 200;
5
+ export declare class MinSizeSplitPolicy implements ISplitPolicy {
6
+ private readonly minChars;
7
+ constructor(minChars?: number);
8
+ isSplitPoint(child: DeclarationNode, siblings: DeclarationNode[], parent: DeclarationNode | null, lines: SourceLines): boolean;
9
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MinSizeSplitPolicy = exports.MIN_MEMBER_CHARS = void 0;
4
+ exports.MIN_MEMBER_CHARS = 200;
5
+ class MinSizeSplitPolicy {
6
+ minChars;
7
+ constructor(minChars = exports.MIN_MEMBER_CHARS) {
8
+ this.minChars = minChars;
9
+ }
10
+ isSplitPoint(child, siblings, parent, lines) {
11
+ if (lines.length(child.start, child.end) < this.minChars)
12
+ return false;
13
+ if (parent && (child.start <= parent.nameLine || child.end >= parent.end))
14
+ return false;
15
+ return !siblings.some(s => s !== child && s.start <= child.end && child.start <= s.end);
16
+ }
17
+ }
18
+ exports.MinSizeSplitPolicy = MinSizeSplitPolicy;
@@ -0,0 +1,12 @@
1
+ import type { ChunkPiece } from '../interfaces/IChunker';
2
+ import type { DeclarationNode } from '../declarations/DeclarationTree';
3
+ import type { LineRange, SourceLines } from '../declarations/SourceLines';
4
+ import type { ITextSplitter } from './interfaces';
5
+ export declare class RunPacker {
6
+ private readonly splitter;
7
+ constructor(splitter: ITextSplitter);
8
+ pack(run: LineRange, children: DeclarationNode[], lines: SourceLines, chunkSize: number, characterSplit?: LineRange[], owner?: DeclarationNode | null): Promise<ChunkPiece[]>;
9
+ private static leadWindow;
10
+ static units(run: LineRange, children: DeclarationNode[]): LineRange[];
11
+ static piece(range: LineRange, lines: SourceLines): ChunkPiece[];
12
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunPacker = void 0;
4
+ class RunPacker {
5
+ splitter;
6
+ constructor(splitter) {
7
+ this.splitter = splitter;
8
+ }
9
+ async pack(run, children, lines, chunkSize, characterSplit = [], owner = null) {
10
+ const pieces = [];
11
+ let current = null;
12
+ const flush = () => {
13
+ if (current)
14
+ pieces.push(...RunPacker.piece(current, lines));
15
+ current = null;
16
+ };
17
+ for (const unit of RunPacker.units(run, children)) {
18
+ if (lines.length(unit.start, unit.end) > chunkSize) {
19
+ flush();
20
+ characterSplit.push(unit);
21
+ const keep = RunPacker.leadWindow(unit, [owner, ...children], lines, chunkSize);
22
+ pieces.push(...await this.splitter.split(lines.slice(unit.start, unit.end), unit.start, keep));
23
+ continue;
24
+ }
25
+ if (current && lines.length(current.start, unit.end) <= chunkSize) {
26
+ current.end = unit.end;
27
+ continue;
28
+ }
29
+ flush();
30
+ current = { ...unit };
31
+ }
32
+ flush();
33
+ return pieces;
34
+ }
35
+ static leadWindow(unit, nodes, lines, chunkSize) {
36
+ const lead = nodes.find(n => n !== null && n.start === unit.start && n.nameLine > unit.start);
37
+ if (!lead)
38
+ return undefined;
39
+ const to = lines.length(unit.start, lead.nameLine - 1);
40
+ if (lines.length(unit.start, lead.nameLine) < chunkSize)
41
+ return { from: 0, to };
42
+ let first = unit.start;
43
+ while (first < lead.nameLine && (lines.isBlank(first) || lines.isCommentOnly(first)))
44
+ first++;
45
+ if (first === lead.nameLine || 1 + lines.length(first, lead.nameLine) >= chunkSize)
46
+ return undefined;
47
+ return { from: lines.length(unit.start, first - 1), to };
48
+ }
49
+ static units(run, children) {
50
+ const inside = children
51
+ .filter(c => c.end >= run.start && c.start <= run.end)
52
+ .sort((a, b) => a.start - b.start);
53
+ const units = [];
54
+ let cursor = run.start;
55
+ for (let i = 0; i < inside.length;) {
56
+ const start = Math.max(inside[i].start, run.start);
57
+ let end = Math.min(inside[i].end, run.end);
58
+ let j = i + 1;
59
+ while (j < inside.length && inside[j].start <= end) {
60
+ end = Math.min(Math.max(end, inside[j].end), run.end);
61
+ j++;
62
+ }
63
+ if (cursor < start)
64
+ units.push({ start: cursor, end: start - 1 });
65
+ units.push({ start, end });
66
+ cursor = end + 1;
67
+ i = j;
68
+ }
69
+ if (cursor <= run.end)
70
+ units.push({ start: cursor, end: run.end });
71
+ return units;
72
+ }
73
+ static piece(range, lines) {
74
+ let { start, end } = range;
75
+ while (start <= end && lines.isBlank(start))
76
+ start++;
77
+ while (end >= start && lines.isBlank(end))
78
+ end--;
79
+ if (start > end)
80
+ return [];
81
+ return [{ content: lines.slice(start, end), startLine: start, endLine: end }];
82
+ }
83
+ }
84
+ exports.RunPacker = RunPacker;
@@ -0,0 +1,3 @@
1
+ import type { Config } from '../config';
2
+ import { AstChunker } from './AstChunker';
3
+ export declare function createChunker(config: Config): AstChunker;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createChunker = createChunker;
4
+ const AstChunker_1 = require("./AstChunker");
5
+ const CharacterChunkStrategy_1 = require("./CharacterChunkStrategy");
6
+ const DeclarationChunkStrategy_1 = require("./DeclarationChunkStrategy");
7
+ const FragmentMerger_1 = require("./FragmentMerger");
8
+ const ImportOnlyFilter_1 = require("./ImportOnlyFilter");
9
+ const LangchainTextSplitter_1 = require("./LangchainTextSplitter");
10
+ const MinSizeSplitPolicy_1 = require("./MinSizeSplitPolicy");
11
+ const RunPacker_1 = require("./RunPacker");
12
+ function createChunker(config) {
13
+ const jsSplitter = LangchainTextSplitter_1.LangchainTextSplitter.forJs(config);
14
+ return new AstChunker_1.AstChunker(new DeclarationChunkStrategy_1.DeclarationChunkStrategy(new MinSizeSplitPolicy_1.MinSizeSplitPolicy(), new RunPacker_1.RunPacker(jsSplitter)), new CharacterChunkStrategy_1.CharacterChunkStrategy(jsSplitter, LangchainTextSplitter_1.LangchainTextSplitter.forSchema(config)), [new ImportOnlyFilter_1.ImportOnlyFilter()], [new FragmentMerger_1.FragmentMerger()], config);
15
+ }
@@ -0,0 +1,27 @@
1
+ import type { ChunkOpts, ChunkPiece } from '../interfaces/IChunker';
2
+ import type { DeclarationNode, DeclarationTree } from '../declarations/DeclarationTree';
3
+ import type { LineRange, SourceLines } from '../declarations/SourceLines';
4
+ export interface ChunkContext {
5
+ lines: SourceLines;
6
+ declarations: DeclarationTree;
7
+ chunkSize: number;
8
+ removed: LineRange[];
9
+ splitPoints: LineRange[];
10
+ characterSplit: LineRange[];
11
+ }
12
+ export interface IChunkStrategy {
13
+ chunk(ctx: ChunkContext, opts: ChunkOpts): Promise<ChunkPiece[]>;
14
+ }
15
+ export interface ISplitPolicy {
16
+ isSplitPoint(child: DeclarationNode, siblings: DeclarationNode[], parent: DeclarationNode | null, lines: SourceLines): boolean;
17
+ }
18
+ export interface KeepWindow {
19
+ from: number;
20
+ to: number;
21
+ }
22
+ export interface ITextSplitter {
23
+ split(text: string, firstLine: number, keep?: KeepWindow): Promise<ChunkPiece[]>;
24
+ }
25
+ export interface IChunkPostProcessor {
26
+ process(pieces: ChunkPiece[], ctx: ChunkContext): ChunkPiece[];
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -72,7 +72,6 @@ class ConfigCommand {
72
72
  console.log(`No config found at ${config_store_1.CONFIG_FILE}. Run rag-setup.`);
73
73
  return;
74
74
  }
75
- // redact password for display
76
75
  const display = JSON.parse(JSON.stringify(cfg));
77
76
  if (display.db?.password !== undefined)
78
77
  display.db.password = '***';
@@ -131,24 +130,18 @@ class ConfigCommand {
131
130
  }
132
131
  }
133
132
  edit() {
134
- // Ensure config + schema exist so the editor has something to open and
135
- // resolve the schema against.
136
133
  try {
137
134
  if (!fs.existsSync(config_store_1.CONFIG_FILE)) {
138
135
  (0, config_store_1.writeConfigJson)({});
139
136
  }
140
137
  else {
141
138
  (0, config_store_1.writeSchemaFile)();
142
- // Migrate existing configs: re-add the "$schema" reference if absent
143
- // (rewrite through writeConfigJson) so editors bind to the schema.
144
- // Skip silently if the file is currently invalid — the user will fix
145
- // it in the editor and validation runs on save.
146
139
  try {
147
140
  const cfg = (0, config_store_1.readConfigJson)();
148
141
  if (cfg)
149
142
  (0, config_store_1.writeConfigJson)(cfg);
150
143
  }
151
- catch { /* invalid file — leave as-is for the user to edit */ }
144
+ catch { }
152
145
  }
153
146
  }
154
147
  catch (e) {
@@ -164,7 +157,6 @@ class ConfigCommand {
164
157
  console.error('Set $EDITOR or edit the file directly: ' + config_store_1.CONFIG_FILE);
165
158
  process.exit(1);
166
159
  }
167
- // Validate what the user saved; readConfigJson throws with a helpful message.
168
160
  try {
169
161
  (0, config_store_1.readConfigJson)();
170
162
  console.log('Config OK');
@@ -1,10 +1,11 @@
1
1
  import type { Container } from '../container';
2
2
  import type { ICommand } from './ICommand';
3
+ import type { ICorpusInspector } from '../interfaces/ICorpusInspector';
3
4
  export declare class EvalCommand implements ICommand {
4
5
  private container;
5
6
  private argv1;
6
- constructor(container: Container, argv1: string);
7
+ private corpusInspector;
8
+ private resultsDir;
9
+ constructor(container: Container, argv1: string, corpusInspector?: ICorpusInspector, resultsDir?: string);
7
10
  execute(): Promise<void>;
8
- private printSummary;
9
- private printComparison;
10
11
  }
@@ -40,10 +40,10 @@ const crypto = __importStar(require("crypto"));
40
40
  const child_process_1 = require("child_process");
41
41
  const retrieval_eval_1 = require("../retrieval-eval");
42
42
  const eval_results_1 = require("../eval-results");
43
+ const eval_corpus_1 = require("../eval-corpus");
43
44
  const DEFAULT_SUITE_DIR = path.join('benchmarks', 'eval');
44
45
  const RESULTS_DIR_NAME = path.join('benchmarks', 'results');
45
- // Resolves to the repo root in both `ts-node app.ts` (one level above `src/`) and
46
- // the compiled `dist/` layout (two levels above `dist/src/commands/`).
46
+ const KEPT_CORPUS_DIR = path.join('benchmarks', 'corpus');
47
47
  const _projectRoot = fs.existsSync(path.join(__dirname, '..', '..', 'package.json'))
48
48
  ? path.join(__dirname, '..', '..')
49
49
  : path.join(__dirname, '..', '..', '..');
@@ -58,7 +58,6 @@ function gitCommit() {
58
58
  function hashContent(text) {
59
59
  return crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
60
60
  }
61
- /** `--suite <path>` — a file or a directory of `*.json` suite files (default: `benchmarks/eval/`). */
62
61
  function parseSuiteFlag(args) {
63
62
  const i = args.indexOf('--suite');
64
63
  if (i === -1)
@@ -86,7 +85,6 @@ function parseProjectFlag(args) {
86
85
  throw new Error('--project requires a name');
87
86
  return next;
88
87
  }
89
- /** Loads one suite file, or every `*.json` file directly under a directory, merged into one. */
90
88
  function loadSuite(suitePath) {
91
89
  const resolved = path.resolve(_projectRoot, suitePath);
92
90
  const stat = fs.statSync(resolved);
@@ -94,21 +92,26 @@ function loadSuite(suitePath) {
94
92
  const files = fs.readdirSync(resolved).filter(f => f.endsWith('.json')).sort();
95
93
  const parts = files.map(f => fs.readFileSync(path.join(resolved, f), 'utf-8'));
96
94
  const suites = parts.map(text => (0, retrieval_eval_1.parseSuite)(JSON.parse(text)));
97
- return { suite: (0, retrieval_eval_1.mergeSuites)(suites), rawContent: parts.join('\n'), label: 'all' };
95
+ return { suite: (0, retrieval_eval_1.mergeSuites)(suites), corpora: (0, retrieval_eval_1.corporaOf)(suites), rawContent: parts.join('\n'), label: 'all' };
98
96
  }
99
97
  const text = fs.readFileSync(resolved, 'utf-8');
100
- return { suite: (0, retrieval_eval_1.parseSuite)(JSON.parse(text)), rawContent: text, label: path.basename(resolved, '.json') };
98
+ const suite = (0, retrieval_eval_1.parseSuite)(JSON.parse(text));
99
+ return { suite, corpora: (0, retrieval_eval_1.corporaOf)([suite]), rawContent: text, label: path.basename(resolved, '.json') };
101
100
  }
102
101
  class EvalCommand {
103
102
  container;
104
103
  argv1;
105
- constructor(container, argv1) {
104
+ corpusInspector;
105
+ resultsDir;
106
+ constructor(container, argv1, corpusInspector = new eval_corpus_1.GitCorpusInspector(), resultsDir = path.join(_projectRoot, RESULTS_DIR_NAME)) {
106
107
  this.container = container;
107
108
  this.argv1 = argv1;
109
+ this.corpusInspector = corpusInspector;
110
+ this.resultsDir = resultsDir;
108
111
  }
109
112
  async execute() {
110
113
  const { db, searcher, config } = this.container;
111
- const args = process.argv.slice(this.argv1 === 'rag-index' ? 3 : 4); // after `rag-index eval` / `index eval`
114
+ const args = process.argv.slice(this.argv1 === 'rag-index' ? 3 : 4);
112
115
  let suitePath, baselinePath, projectFilter;
113
116
  try {
114
117
  suitePath = parseSuiteFlag(args);
@@ -120,9 +123,9 @@ class EvalCommand {
120
123
  process.exitCode = 1;
121
124
  return;
122
125
  }
123
- let suite, rawContent, label;
126
+ let suite, corpora, rawContent, label;
124
127
  try {
125
- ({ suite, rawContent, label } = loadSuite(suitePath));
128
+ ({ suite, corpora, rawContent, label } = loadSuite(suitePath));
126
129
  }
127
130
  catch (err) {
128
131
  if (err instanceof retrieval_eval_1.EvalSuiteError) {
@@ -142,7 +145,30 @@ class EvalCommand {
142
145
  process.exitCode = 1;
143
146
  return;
144
147
  }
148
+ const runCorpora = corpora.filter(c => suite.cases.some(k => k.corpus === c.root));
149
+ let refused = false;
150
+ for (const corpus of runCorpora) {
151
+ let failures;
152
+ try {
153
+ failures = await (0, eval_corpus_1.checkCorpus)(corpus, path.join(config.workspaceRoot, corpus.root), path.join(_projectRoot, KEPT_CORPUS_DIR, corpus.root), this.corpusInspector);
154
+ }
155
+ catch (err) {
156
+ failures = [`cannot inspect the checkout: ${err.message}`];
157
+ }
158
+ if (failures.length === 0)
159
+ continue;
160
+ refused = true;
161
+ console.error(`\nCorpus ${corpus.root} does not match ${corpus.commit}:`);
162
+ for (const f of failures)
163
+ console.error(` - ${f}`);
164
+ }
165
+ if (refused) {
166
+ console.error('\nNo search was run and no result file was written.');
167
+ process.exitCode = 1;
168
+ return;
169
+ }
145
170
  await db.initDbWithSchema();
171
+ const projectDirs = new Map((await searcher.listProjects()).map(p => [p.serviceName, p.relPath ?? null]));
146
172
  const knownFilesCache = new Map();
147
173
  const inspect = {
148
174
  async isFileIndexed(project, file) {
@@ -155,13 +181,44 @@ class EvalCommand {
155
181
  const rows = await db.query(`SELECT DISTINCT "ownerPath" FROM code_chunk WHERE "serviceName" = $1 AND "filePath" = $2 AND "ownerPath" IS NOT NULL`, [project, file]);
156
182
  return rows.map(r => r.ownerPath);
157
183
  },
184
+ async projectDir(project) {
185
+ return projectDirs.get(project) ?? null;
186
+ },
158
187
  };
188
+ const toHits = (rows) => rows.map(r => ({ filePath: r.filePath, ownerPath: r.ownerPath ?? null }));
159
189
  const search = async (query, limit, project, threshold) => {
160
190
  const prepared = await searcher.prepareQuery(query);
161
- const results = await searcher.searchChunks(prepared.query, limit, project, threshold);
162
- return results.map(r => ({ filePath: r.filePath, ownerPath: r.ownerPath ?? null }));
191
+ const r = await searcher.searchChunksWithStages(prepared.query, limit, project, threshold, undefined, limit);
192
+ return {
193
+ pool: toHits(r.pool),
194
+ stages: r.stages.reranked
195
+ ? { vector: toHits(r.stages.vector), reranked: toHits(r.stages.reranked) }
196
+ : { vector: toHits(r.stages.vector) },
197
+ rerankFailed: r.rerankFailed,
198
+ fetchLimit: r.fetchLimit,
199
+ indexParams: r.indexParams,
200
+ };
163
201
  };
164
- const run = await new retrieval_eval_1.RetrievalEvaluator().evaluate(suite, search, inspect);
202
+ let outcome;
203
+ try {
204
+ outcome = await new retrieval_eval_1.RetrievalEvaluator().evaluate(suite, search, inspect, p => console.error((0, eval_results_1.renderProgress)(p)));
205
+ }
206
+ catch (err) {
207
+ if (!(err instanceof retrieval_eval_1.EvalSuiteError))
208
+ throw err;
209
+ console.error(`\nInvalid suite: ${err.message}`);
210
+ process.exitCode = 1;
211
+ return;
212
+ }
213
+ const { run, observed } = outcome;
214
+ const corpusRecords = [];
215
+ for (const corpus of runCorpora) {
216
+ const projects = [...projectDirs].filter(([, dir]) => dir !== null && (0, retrieval_eval_1.isUnderRoot)(dir, corpus.root)).map(([name]) => name).sort();
217
+ const started = Date.now();
218
+ const record = { ...corpus, projects, ...(await (0, eval_corpus_1.fingerprintCorpus)(db, projects)) };
219
+ console.log(`Corpus ${corpus.root}: ${record.chunks} chunks in ${projects.length} project(s), fingerprint ${record.fingerprint} (${((Date.now() - started) / 1000).toFixed(1)}s)`);
220
+ corpusRecords.push(record);
221
+ }
165
222
  const indexMeta = await db.readIndexMeta();
166
223
  const metadata = {
167
224
  suitePath,
@@ -172,60 +229,36 @@ class EvalCommand {
172
229
  indexMeta: indexMeta ? { provider: indexMeta.provider, model: indexMeta.model, dimensions: indexMeta.dimensions } : null,
173
230
  gitCommit: gitCommit(),
174
231
  timestamp: new Date().toISOString(),
232
+ conditions: {
233
+ similarityThreshold: config.search.similarityThreshold,
234
+ overfetch: config.reranker.overfetch,
235
+ candidateFloor: config.reranker.candidateFloor,
236
+ fetchLimit: observed.fetchLimit,
237
+ efSearch: observed.efSearch,
238
+ iterativeScan: observed.iterativeScan,
239
+ },
240
+ corpora: corpusRecords,
241
+ liveCases: suite.cases.filter(c => !c.corpus).length,
175
242
  };
176
243
  const resultFile = { metadata, run };
177
- this.printSummary(resultFile);
244
+ for (const line of (0, eval_results_1.renderSummary)(resultFile))
245
+ console.log(line);
178
246
  let comparison;
179
247
  if (baselinePath) {
180
248
  try {
181
249
  const baseline = (0, eval_results_1.readResultFile)(path.resolve(_projectRoot, baselinePath));
182
250
  const cmp = (0, retrieval_eval_1.compare)(baseline, resultFile);
183
251
  comparison = { baselinePath, result: cmp };
184
- this.printComparison(baselinePath, cmp);
252
+ for (const line of (0, eval_results_1.renderComparison)(baselinePath, cmp))
253
+ console.log(line);
185
254
  }
186
255
  catch (err) {
187
256
  console.error(`\nFailed to read baseline '${baselinePath}': ${err.message}`);
188
257
  process.exitCode = 1;
189
258
  }
190
259
  }
191
- const resultsDir = path.join(_projectRoot, RESULTS_DIR_NAME);
192
- const { jsonPath, mdPath } = (0, eval_results_1.writeResultFiles)(resultsDir, label, resultFile, new Date(), comparison);
260
+ const { jsonPath, mdPath } = (0, eval_results_1.writeResultFiles)(this.resultsDir, label, resultFile, new Date(), comparison);
193
261
  console.log(`\nResults written to:\n ${path.relative(_projectRoot, jsonPath)}\n ${path.relative(_projectRoot, mdPath)}`);
194
262
  }
195
- printSummary(result) {
196
- const { run, metadata } = result;
197
- console.log(`\nEvaluated ${run.cases.length} case(s) (${run.invalid.length} invalid) against ${metadata.embeddingProvider}/${metadata.embeddingModel}`);
198
- const o = run.metrics.overall;
199
- console.log(` Overall — hit@1: ${(o.hit1 * 100).toFixed(1)}% hit@5: ${(o.hit5 * 100).toFixed(1)}% MRR@10: ${o.mrr10.toFixed(4)}`);
200
- for (const category of ['identifier', 'behaviour', 'concept']) {
201
- const m = run.metrics.byCategory[category];
202
- if (m)
203
- console.log(` ${category.padEnd(10)} — hit@1: ${(m.hit1 * 100).toFixed(1)}% hit@5: ${(m.hit5 * 100).toFixed(1)}% MRR@10: ${m.mrr10.toFixed(4)} (${m.count})`);
204
- }
205
- if (run.invalid.length > 0) {
206
- console.log(`\nInvalid cases:`);
207
- for (const c of run.invalid)
208
- console.log(` ${c.id}: ${c.reason} — ${c.invalidExpects.map(e => e.file).join(', ')}`);
209
- }
210
- if (run.warnings.length > 0) {
211
- console.log(`\nWarnings (scored on remaining valid answers):`);
212
- for (const w of run.warnings)
213
- console.log(` ${w.id}: dropped ${w.droppedExpects.map(e => e.file).join(', ')}`);
214
- }
215
- }
216
- printComparison(baselinePath, cmp) {
217
- console.log(`\nComparison against ${baselinePath}:`);
218
- if (cmp.conditionWarnings.length > 0) {
219
- for (const w of cmp.conditionWarnings)
220
- console.log(` ⚠ ${w}`);
221
- }
222
- const sign = (n) => (n >= 0 ? '+' : '') + n.toFixed(4);
223
- console.log(` MRR@10: ${cmp.overall.mrr10.baseline.toFixed(4)} → ${cmp.overall.mrr10.current.toFixed(4)} (${sign(cmp.overall.mrr10.delta)})`);
224
- console.log(` hit@5: ${(cmp.overall.hit5.baseline * 100).toFixed(1)}% → ${(cmp.overall.hit5.current * 100).toFixed(1)}%`);
225
- if (cmp.hit5Lost.length > 0)
226
- console.log(` Lost hit@5: ${cmp.hit5Lost.join(', ')}`);
227
- if (cmp.hit5Gained.length > 0)
228
- console.log(` Gained hit@5: ${cmp.hit5Gained.join(', ')}`);
229
- }
230
263
  }
231
264
  exports.EvalCommand = EvalCommand;