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.
- package/CHANGELOG.md +31 -1
- package/README.md +46 -26
- package/dist/app.js +5 -19
- package/dist/src/chunking/AstChunker.d.ts +12 -0
- package/dist/src/chunking/AstChunker.js +30 -0
- package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
- package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
- package/dist/src/chunking/FragmentMerger.d.ts +11 -0
- package/dist/src/chunking/FragmentMerger.js +61 -0
- package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
- package/dist/src/chunking/ImportOnlyFilter.js +14 -0
- package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
- package/dist/src/chunking/LangchainTextSplitter.js +133 -0
- package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
- package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
- package/dist/src/chunking/RunPacker.d.ts +12 -0
- package/dist/src/chunking/RunPacker.js +84 -0
- package/dist/src/chunking/create-chunker.d.ts +3 -0
- package/dist/src/chunking/create-chunker.js +15 -0
- package/dist/src/chunking/interfaces.d.ts +27 -0
- package/dist/src/chunking/interfaces.js +2 -0
- package/dist/src/commands/ConfigCommand.js +1 -9
- package/dist/src/commands/EvalCommand.d.ts +4 -3
- package/dist/src/commands/EvalCommand.js +85 -52
- package/dist/src/commands/IndexCommand.js +0 -22
- package/dist/src/commands/McpCommand.js +2 -8
- package/dist/src/commands/WebCommand.js +2 -5
- package/dist/src/config-error.d.ts +0 -4
- package/dist/src/config-error.js +0 -4
- package/dist/src/config-store.d.ts +1 -54
- package/dist/src/config-store.js +8 -69
- package/dist/src/config.d.ts +8 -17
- package/dist/src/config.js +35 -34
- package/dist/src/constants.d.ts +2 -28
- package/dist/src/constants.js +6 -28
- package/dist/src/container.d.ts +6 -0
- package/dist/src/container.js +12 -3
- package/dist/src/db/schema.d.ts +17 -0
- package/dist/src/db/schema.js +1 -6
- package/dist/src/declarations/DeclarationTree.d.ts +19 -0
- package/dist/src/declarations/DeclarationTree.js +66 -0
- package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
- package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
- package/dist/src/declarations/SourceLines.d.ts +37 -0
- package/dist/src/declarations/SourceLines.js +415 -0
- package/dist/src/dependency-traversal.d.ts +0 -6
- package/dist/src/dependency-traversal.js +0 -6
- package/dist/src/enriched-prompt.d.ts +0 -6
- package/dist/src/enriched-prompt.js +0 -6
- package/dist/src/error-record.d.ts +12 -0
- package/dist/src/error-record.js +248 -0
- package/dist/src/eval-corpus.d.ts +16 -0
- package/dist/src/eval-corpus.js +169 -0
- package/dist/src/eval-results.d.ts +6 -3
- package/dist/src/eval-results.js +309 -26
- package/dist/src/file-utils.d.ts +0 -5
- package/dist/src/file-utils.js +0 -5
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/interfaces/IAstParser.d.ts +2 -39
- package/dist/src/interfaces/IChunker.d.ts +3 -12
- package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
- package/dist/src/interfaces/ICorpusInspector.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +2 -22
- package/dist/src/interfaces/IEmbedder.d.ts +0 -9
- package/dist/src/interfaces/ILogger.d.ts +1 -11
- package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
- package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
- package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
- package/dist/src/interfaces/ISearcher.d.ts +16 -5
- package/dist/src/interrupt.js +0 -2
- package/dist/src/owner-path.d.ts +9 -10
- package/dist/src/owner-path.js +11 -23
- package/dist/src/project-scope.d.ts +0 -12
- package/dist/src/project-scope.js +0 -12
- package/dist/src/project-selector.d.ts +0 -5
- package/dist/src/project-selector.js +0 -11
- package/dist/src/retrieval-eval.d.ts +14 -6
- package/dist/src/retrieval-eval.js +345 -59
- package/dist/src/search/result-shaping.d.ts +4 -0
- package/dist/src/search/result-shaping.js +19 -0
- package/dist/src/services/AstParserService.d.ts +2 -62
- package/dist/src/services/AstParserService.js +25 -137
- package/dist/src/services/DatabaseService.d.ts +2 -7
- package/dist/src/services/DatabaseService.js +42 -29
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +0 -16
- package/dist/src/services/IndexerService.js +16 -45
- package/dist/src/services/InfinityEmbedder.d.ts +0 -21
- package/dist/src/services/InfinityEmbedder.js +0 -15
- package/dist/src/services/LoggerService.d.ts +7 -5
- package/dist/src/services/LoggerService.js +37 -41
- package/dist/src/services/McpServer.d.ts +22 -14
- package/dist/src/services/McpServer.js +70 -37
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
- package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
- package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
- package/dist/src/services/NxMonorepoResolver.js +5 -56
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
- package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
- package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
- package/dist/src/services/ProjectDiscoveryService.js +0 -4
- package/dist/src/services/SearcherService.d.ts +9 -116
- package/dist/src/services/SearcherService.js +95 -176
- package/dist/src/services/VoyageEmbedder.d.ts +0 -6
- package/dist/src/services/VoyageEmbedder.js +0 -6
- package/dist/src/services/WebServerService.d.ts +1 -53
- package/dist/src/services/WebServerService.js +11 -57
- package/dist/src/services/http-retry.d.ts +0 -7
- package/dist/src/services/http-retry.js +1 -10
- package/dist/src/services/infinity-provisioning.d.ts +0 -74
- package/dist/src/services/infinity-provisioning.js +5 -88
- package/dist/src/services/legacy-ollama-index.d.ts +0 -16
- package/dist/src/services/legacy-ollama-index.js +0 -23
- package/dist/src/services/reranker.d.ts +0 -9
- package/dist/src/services/reranker.js +46 -12
- package/dist/src/setup.d.ts +11 -23
- package/dist/src/setup.js +98 -71
- package/dist/src/symbol-name.d.ts +0 -12
- package/dist/src/symbol-name.js +0 -13
- package/dist/src/symbol-rows.d.ts +0 -14
- package/dist/src/symbol-rows.js +0 -14
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- package/dist/src/tools/GetDependenciesTool.js +1 -1
- package/dist/src/tools/GetDependentsTool.js +1 -1
- package/dist/src/tools/SearchCodebaseTool.js +6 -3
- package/dist/src/tools/format-helpers.d.ts +0 -6
- package/dist/src/tools/format-helpers.js +87 -14
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/package.json +2 -2
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- 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,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
|
+
}
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
|
162
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|