codesearch-mcp 0.2.2-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 +65 -1
- package/README.md +60 -33
- package/dist/app.js +46 -22
- 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 +11 -0
- package/dist/src/commands/EvalCommand.js +264 -0
- package/dist/src/commands/IndexCommand.js +70 -66
- 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/index.d.ts +1 -1
- package/dist/src/db/index.js +1 -2
- package/dist/src/db/schema.d.ts +22 -29
- package/dist/src/db/schema.js +3 -14
- 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 +10 -0
- package/dist/src/dependency-traversal.js +39 -0
- package/dist/src/enriched-prompt.d.ts +0 -5
- package/dist/src/enriched-prompt.js +0 -5
- 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 +15 -0
- package/dist/src/eval-results.js +397 -0
- package/dist/src/file-utils.d.ts +10 -0
- package/dist/src/file-utils.js +19 -1
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/help.js +4 -1
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/interfaces/IAstParser.d.ts +5 -30
- 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 -36
- 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 +216 -0
- package/dist/src/interfaces/IRetrievalEvaluator.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +26 -20
- package/dist/src/interfaces/index.d.ts +1 -0
- 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 +7 -0
- package/dist/src/project-scope.js +37 -0
- package/dist/src/project-selector.d.ts +1 -0
- package/dist/src/project-selector.js +13 -6
- package/dist/src/retrieval-eval.d.ts +24 -0
- package/dist/src/retrieval-eval.js +500 -0
- 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 +28 -23
- package/dist/src/services/AstParserService.js +391 -98
- package/dist/src/services/DatabaseService.d.ts +2 -19
- package/dist/src/services/DatabaseService.js +44 -81
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +2 -10
- package/dist/src/services/IndexerService.js +29 -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 +25 -10
- package/dist/src/services/McpServer.js +101 -64
- 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 +1 -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 +127 -70
- package/dist/src/services/SearcherService.js +526 -305
- 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 +2 -11
- package/dist/src/symbol-rows.js +5 -13
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- 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 +7 -2
- package/dist/src/tools/format-helpers.d.ts +10 -1
- package/dist/src/tools/format-helpers.js +145 -8
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/package.json +10 -9
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- package/docs/web-dashboard.md +0 -206
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesearch-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-alpha",
|
|
4
4
|
"description": "Local semantic codebase search via MCP — indexes projects into pgvector, exposes search to Claude",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist/",
|
|
31
31
|
"docker/",
|
|
32
|
-
"docs/",
|
|
32
|
+
"docs/architecture.svg",
|
|
33
33
|
"README.md",
|
|
34
34
|
"CHANGELOG.md"
|
|
35
35
|
],
|
|
@@ -42,23 +42,24 @@
|
|
|
42
42
|
"start:web": "ts-node app.ts web",
|
|
43
43
|
"index:workspace": "ts-node app.ts index",
|
|
44
44
|
"index:reindex": "ts-node app.ts index --reindex",
|
|
45
|
+
"index:project": "ts-node app.ts index --project",
|
|
45
46
|
"typecheck": "tsc --noEmit",
|
|
46
47
|
"test": "vitest run",
|
|
47
48
|
"test:watch": "vitest",
|
|
48
49
|
"verify": "tsc --noEmit && vitest run"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@inquirer/prompts": "^8.
|
|
52
|
-
"@langchain/textsplitters": "^1.0.
|
|
53
|
-
"@modelcontextprotocol/sdk": "
|
|
52
|
+
"@inquirer/prompts": "^8.7.3",
|
|
53
|
+
"@langchain/textsplitters": "^1.0.2",
|
|
54
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
54
55
|
"cli-progress": "^3.12.0",
|
|
55
56
|
"drizzle-orm": "^0.45.2",
|
|
56
57
|
"ignore": "^7.0.5",
|
|
57
|
-
"pg": "^8.
|
|
58
|
-
"pino": "^10.
|
|
58
|
+
"pg": "^8.23.1",
|
|
59
|
+
"pino": "^10.4.0",
|
|
59
60
|
"pino-roll": "^4.0.0",
|
|
60
61
|
"ts-morph": "^28.0.0",
|
|
61
|
-
"zod": "^4.
|
|
62
|
+
"zod": "^4.6.7"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@types/cli-progress": "^3.11.6",
|
|
@@ -66,6 +67,6 @@
|
|
|
66
67
|
"@types/pg": "^8.20.0",
|
|
67
68
|
"ts-node": "^10.9.2",
|
|
68
69
|
"typescript": "^5.6.0",
|
|
69
|
-
"vitest": "^
|
|
70
|
+
"vitest": "^5.0.5"
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Config } from '../config';
|
|
2
|
-
import type { IChunker, ChunkPiece, ChunkOpts } from '../interfaces/IChunker';
|
|
3
|
-
/**
|
|
4
|
-
* Splits TS/JS source into embeddable pieces aligned to top-level declaration
|
|
5
|
-
* ranges (AST strategy). Falls back to the character-based recursive splitter
|
|
6
|
-
* for schema files, files without symbol analysis, or when strategy is
|
|
7
|
-
* 'character' — that path is byte-for-byte the previous behavior.
|
|
8
|
-
*/
|
|
9
|
-
export declare class AstChunker implements IChunker {
|
|
10
|
-
private config;
|
|
11
|
-
private readonly jsSplitter;
|
|
12
|
-
private readonly schemaSplitter;
|
|
13
|
-
private readonly chunkSize;
|
|
14
|
-
constructor(config: Config);
|
|
15
|
-
chunk(code: string, opts: ChunkOpts): Promise<ChunkPiece[]>;
|
|
16
|
-
private produce;
|
|
17
|
-
/**
|
|
18
|
-
* True iff every significant line (blank and comment lines stripped) belongs
|
|
19
|
-
* to an `import` / re-export statement and at least one is an import or
|
|
20
|
-
* re-export keyword — so the chunk carries no value/type declaration and is
|
|
21
|
-
* never a search answer. Any `export const|let|var|function|class|interface|
|
|
22
|
-
* enum|namespace|default|declare|async function|type X = …` disqualifies
|
|
23
|
-
* exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
|
|
24
|
-
* (re-export → drop) from `export type X = …` (declaration → keep).
|
|
25
|
-
*/
|
|
26
|
-
static isImportOrReexportOnly(content: string): boolean;
|
|
27
|
-
/** Character-splitter path for a whole file; line numbers from splitter metadata. */
|
|
28
|
-
private splitWholeFile;
|
|
29
|
-
private chunkByAst;
|
|
30
|
-
/**
|
|
31
|
-
* Build disjoint, sorted line intervals from symbol ranges. Overlapping /
|
|
32
|
-
* nested ranges merge into the outermost (a merged region covers any symbol
|
|
33
|
-
* inside it). Ranges are clamped to the file's line count.
|
|
34
|
-
*/
|
|
35
|
-
private static buildIntervals;
|
|
36
|
-
/**
|
|
37
|
-
* Emit pieces for a 1-based inclusive line region. Whitespace-only gaps are
|
|
38
|
-
* dropped; regions longer than chunkSize are sub-split with the char splitter,
|
|
39
|
-
* with sub-piece line numbers offset back to absolute file lines.
|
|
40
|
-
*/
|
|
41
|
-
private regionPieces;
|
|
42
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AstChunker = void 0;
|
|
4
|
-
const textsplitters_1 = require("@langchain/textsplitters");
|
|
5
|
-
/**
|
|
6
|
-
* Splits TS/JS source into embeddable pieces aligned to top-level declaration
|
|
7
|
-
* ranges (AST strategy). Falls back to the character-based recursive splitter
|
|
8
|
-
* for schema files, files without symbol analysis, or when strategy is
|
|
9
|
-
* 'character' — that path is byte-for-byte the previous behavior.
|
|
10
|
-
*/
|
|
11
|
-
class AstChunker {
|
|
12
|
-
config;
|
|
13
|
-
jsSplitter;
|
|
14
|
-
schemaSplitter;
|
|
15
|
-
chunkSize;
|
|
16
|
-
constructor(config) {
|
|
17
|
-
this.config = config;
|
|
18
|
-
this.chunkSize = config.indexer.chunkSize;
|
|
19
|
-
this.jsSplitter = textsplitters_1.RecursiveCharacterTextSplitter.fromLanguage('js', {
|
|
20
|
-
chunkSize: config.indexer.chunkSize,
|
|
21
|
-
chunkOverlap: config.indexer.chunkOverlap,
|
|
22
|
-
});
|
|
23
|
-
this.schemaSplitter = new textsplitters_1.RecursiveCharacterTextSplitter({
|
|
24
|
-
chunkSize: config.indexer.chunkSize,
|
|
25
|
-
chunkOverlap: config.indexer.chunkOverlap,
|
|
26
|
-
separators: ['\n\n', '\n', ' '],
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
async chunk(code, opts) {
|
|
30
|
-
const pieces = await this.produce(code, opts);
|
|
31
|
-
// Single post-chunk pass: drop pieces whose only significant content is
|
|
32
|
-
// import / re-export statements. Both chunk sources — AST "gap" regions and
|
|
33
|
-
// the whole-file character path for barrel files — can emit these, so we
|
|
34
|
-
// filter once over the final pieces. Dependency edges are untouched (they
|
|
35
|
-
// come from analyzeProject → code_dependency, not from chunk text).
|
|
36
|
-
return pieces.filter(p => !AstChunker.isImportOrReexportOnly(p.content));
|
|
37
|
-
}
|
|
38
|
-
async produce(code, opts) {
|
|
39
|
-
if (opts.isSchemaFile) {
|
|
40
|
-
return this.splitWholeFile(code, this.schemaSplitter);
|
|
41
|
-
}
|
|
42
|
-
if (opts.strategy === 'character' || opts.symbols.length === 0) {
|
|
43
|
-
return this.splitWholeFile(code, this.jsSplitter);
|
|
44
|
-
}
|
|
45
|
-
return this.chunkByAst(code, opts.symbols);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* True iff every significant line (blank and comment lines stripped) belongs
|
|
49
|
-
* to an `import` / re-export statement and at least one is an import or
|
|
50
|
-
* re-export keyword — so the chunk carries no value/type declaration and is
|
|
51
|
-
* never a search answer. Any `export const|let|var|function|class|interface|
|
|
52
|
-
* enum|namespace|default|declare|async function|type X = …` disqualifies
|
|
53
|
-
* exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
|
|
54
|
-
* (re-export → drop) from `export type X = …` (declaration → keep).
|
|
55
|
-
*/
|
|
56
|
-
static isImportOrReexportOnly(content) {
|
|
57
|
-
const lines = content
|
|
58
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
|
|
59
|
-
.split('\n')
|
|
60
|
-
.map(l => l.trim())
|
|
61
|
-
.filter(l => l !== '' && !l.startsWith('//') && !l.startsWith('*'));
|
|
62
|
-
if (lines.length === 0)
|
|
63
|
-
return false;
|
|
64
|
-
const IMPORT = /^import\b/;
|
|
65
|
-
const REEXPORT = /^export\s+(\*|type\s+\{|\{)/; // export * | export type { | export {
|
|
66
|
-
const DISQUALIFY = /^export\s+(default|async\b|function\b|const\b|let\b|var\b|class\b|abstract\b|interface\b|enum\b|namespace\b|declare\b|type\s+[A-Za-z_$])/;
|
|
67
|
-
const CONTINUATION = /^(\}?\s*from\s+['"][^'"]+['"];?|[{}]|(type\s+)?[A-Za-z_$][\w$]*(\s+as\s+[A-Za-z_$][\w$]*)?,?|['"][^'"]*['"];?)$/;
|
|
68
|
-
let keywords = 0;
|
|
69
|
-
for (const line of lines) {
|
|
70
|
-
if (DISQUALIFY.test(line))
|
|
71
|
-
return false;
|
|
72
|
-
if (IMPORT.test(line) || REEXPORT.test(line)) {
|
|
73
|
-
keywords++;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (CONTINUATION.test(line))
|
|
77
|
-
continue;
|
|
78
|
-
return false; // unrecognized line → real code, keep the chunk
|
|
79
|
-
}
|
|
80
|
-
return keywords >= 1;
|
|
81
|
-
}
|
|
82
|
-
/** Character-splitter path for a whole file; line numbers from splitter metadata. */
|
|
83
|
-
async splitWholeFile(code, splitter) {
|
|
84
|
-
const docs = await splitter.createDocuments([code]);
|
|
85
|
-
return docs.map((d) => {
|
|
86
|
-
const from = (d.metadata?.loc?.lines?.from ?? 1);
|
|
87
|
-
const to = (d.metadata?.loc?.lines?.to ?? from);
|
|
88
|
-
return { content: d.pageContent, startLine: from, endLine: to };
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
async chunkByAst(code, symbols) {
|
|
92
|
-
const lines = code.split('\n');
|
|
93
|
-
const total = lines.length;
|
|
94
|
-
const intervals = AstChunker.buildIntervals(symbols, total);
|
|
95
|
-
const pieces = [];
|
|
96
|
-
let cursor = 1;
|
|
97
|
-
for (const iv of intervals) {
|
|
98
|
-
if (cursor < iv.start) {
|
|
99
|
-
pieces.push(...await this.regionPieces(lines, cursor, iv.start - 1, true));
|
|
100
|
-
}
|
|
101
|
-
pieces.push(...await this.regionPieces(lines, iv.start, iv.end, false));
|
|
102
|
-
cursor = iv.end + 1;
|
|
103
|
-
}
|
|
104
|
-
if (cursor <= total) {
|
|
105
|
-
pieces.push(...await this.regionPieces(lines, cursor, total, true));
|
|
106
|
-
}
|
|
107
|
-
return pieces;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Build disjoint, sorted line intervals from symbol ranges. Overlapping /
|
|
111
|
-
* nested ranges merge into the outermost (a merged region covers any symbol
|
|
112
|
-
* inside it). Ranges are clamped to the file's line count.
|
|
113
|
-
*/
|
|
114
|
-
static buildIntervals(symbols, total) {
|
|
115
|
-
const raw = symbols
|
|
116
|
-
.filter(s => s.line != null && s.line >= 1 && s.line <= total)
|
|
117
|
-
.map(s => ({
|
|
118
|
-
start: s.line,
|
|
119
|
-
end: Math.min(Math.max(s.endLine ?? s.line, s.line), total),
|
|
120
|
-
}))
|
|
121
|
-
.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
122
|
-
const merged = [];
|
|
123
|
-
for (const iv of raw) {
|
|
124
|
-
const last = merged[merged.length - 1];
|
|
125
|
-
if (last && iv.start <= last.end) {
|
|
126
|
-
last.end = Math.max(last.end, iv.end);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
merged.push({ ...iv });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return merged;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Emit pieces for a 1-based inclusive line region. Whitespace-only gaps are
|
|
136
|
-
* dropped; regions longer than chunkSize are sub-split with the char splitter,
|
|
137
|
-
* with sub-piece line numbers offset back to absolute file lines.
|
|
138
|
-
*/
|
|
139
|
-
async regionPieces(lines, start, end, isGap) {
|
|
140
|
-
const text = lines.slice(start - 1, end).join('\n');
|
|
141
|
-
if (isGap && text.trim() === '')
|
|
142
|
-
return [];
|
|
143
|
-
if (text.length <= this.chunkSize) {
|
|
144
|
-
return [{ content: text, startLine: start, endLine: end }];
|
|
145
|
-
}
|
|
146
|
-
const docs = await this.jsSplitter.createDocuments([text]);
|
|
147
|
-
return docs.map((d) => {
|
|
148
|
-
const from = (d.metadata?.loc?.lines?.from ?? 1);
|
|
149
|
-
const to = (d.metadata?.loc?.lines?.to ?? from);
|
|
150
|
-
return {
|
|
151
|
-
content: d.pageContent,
|
|
152
|
-
startLine: start + from - 1,
|
|
153
|
-
endLine: start + to - 1,
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
exports.AstChunker = AstChunker;
|
package/docs/web-dashboard.md
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
# Web dashboard (`rag-web`)
|
|
2
|
-
|
|
3
|
-
A local web UI for configuring and inspecting the RAG server. It provides a
|
|
4
|
-
schema-driven config editor, an overview of indexed projects, and an interactive
|
|
5
|
-
project-dependency graph — all served from a small built-in HTTP server with no
|
|
6
|
-
external framework and no new dependencies (`node:http`).
|
|
7
|
-
|
|
8
|
-
The dashboard is a thin presentation layer over primitives that already exist:
|
|
9
|
-
config I/O and validation (`src/config-store.ts`), project data
|
|
10
|
-
(`SearcherService.listProjects`), and the graph builder (`src/graph.ts`). It adds
|
|
11
|
-
no business logic of its own beyond request routing and a password-preservation
|
|
12
|
-
merge.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Running
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
rag-web # http://127.0.0.1:4319
|
|
20
|
-
rag-web --port 8080 # custom port
|
|
21
|
-
npm run start:web # dev mode (ts-node, no build)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
If the `rag-web` binary is not on your `PATH`, either run it from the project
|
|
25
|
-
(`node dist/app.js web`) or register it once with `npm link`.
|
|
26
|
-
|
|
27
|
-
Keep the terminal open — the server runs until you press `Ctrl+C`. The browser
|
|
28
|
-
can only reach the dashboard while the process is alive.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Security model
|
|
33
|
-
|
|
34
|
-
- **Binds to `127.0.0.1` only.** The server is never exposed on the network;
|
|
35
|
-
it is a single-user local dev tool, so there is no authentication.
|
|
36
|
-
- **The DB password is never sent to the browser.** `GET /api/config` returns it
|
|
37
|
-
masked as `********`. On save, if the field is still the mask, the stored
|
|
38
|
-
password is preserved (`mergeConfigForSave`); a genuinely changed value is
|
|
39
|
-
written through.
|
|
40
|
-
- **No arbitrary file serving.** Only the known routes below respond; there is no
|
|
41
|
-
static file handler, so no path-traversal surface.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Requirements & failure modes
|
|
46
|
-
|
|
47
|
-
- Requires a valid `config.json` — `rag-web` goes through the normal container
|
|
48
|
-
bootstrap and `workspaceRoot` guard. To repair a broken config, use
|
|
49
|
-
`rag-config edit` (CLI) instead.
|
|
50
|
-
- A failed **DB** connection is non-fatal: the config editor still works; the
|
|
51
|
-
Projects and Graph tabs show an in-page error.
|
|
52
|
-
- A failed **embedding backend** connection only affects model suggestions (see
|
|
53
|
-
*Field options*); everything else works.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## UI tabs
|
|
58
|
-
|
|
59
|
-
### Config
|
|
60
|
-
A form generated from the config JSON Schema (`buildJsonSchema()` — the same
|
|
61
|
-
schema that powers editor autocomplete on `config.json`). Fields are grouped by
|
|
62
|
-
section and typed automatically:
|
|
63
|
-
|
|
64
|
-
| Schema shape | Rendered as |
|
|
65
|
-
|---|---|
|
|
66
|
-
| `boolean` | checkbox |
|
|
67
|
-
| `enum` (e.g. `logging.level`) | `<select>` |
|
|
68
|
-
| `integer` / `number` | number input (with sensible `min`/`max`/`step`) |
|
|
69
|
-
| `string` | text input |
|
|
70
|
-
| field in `/api/field-options`, `strict` | `<select>` limited to the list |
|
|
71
|
-
| field in `/api/field-options`, non-strict | text input + `<datalist>` (combobox) |
|
|
72
|
-
|
|
73
|
-
Two affordances make the effective configuration obvious:
|
|
74
|
-
|
|
75
|
-
- **Defaults.** Any key left unset shows `↪ using default: <value>` (amber) and
|
|
76
|
-
the applied value as the input placeholder. A key that *is* set shows a muted
|
|
77
|
-
`default: <value>` for reference. Source: `GET /api/defaults`
|
|
78
|
-
(`CONFIG_DEFAULTS`).
|
|
79
|
-
- **Field options.** Fields whose valid values are constrained get a dropdown
|
|
80
|
-
instead of free text. Source: `GET /api/field-options`. See below.
|
|
81
|
-
|
|
82
|
-
Saving validates through `parseConfig` and, on failure, shows the validation
|
|
83
|
-
message and highlights the offending fields; on success the config is written
|
|
84
|
-
(with the `$schema` reference preserved) and the form reloads.
|
|
85
|
-
|
|
86
|
-
### Projects
|
|
87
|
-
Cards for each indexed project from `SearcherService.listProjects()` — name,
|
|
88
|
-
chunk count, and any `.ragmeta.json` metadata (stack, description, relations).
|
|
89
|
-
|
|
90
|
-
### Graph
|
|
91
|
-
The **full standalone graph page embedded in an `<iframe>`** (`src="/graph"`),
|
|
92
|
-
loaded lazily on first open. `GET /graph` serves the exact same HTML as the
|
|
93
|
-
generated `project-graph.html` (`buildHtml()` in `src/graph.ts`), so the whole
|
|
94
|
-
sidebar comes for free: the node-color legend, the edge-type filter buttons, and
|
|
95
|
-
the project toggle list, plus hover highlighting and auto-fit. The dashboard
|
|
96
|
-
does **not** re-implement vis-network — reusing one page keeps the two graph
|
|
97
|
-
views identical. `GET /api/graph` still returns the raw `{ nodes, edges }` JSON
|
|
98
|
-
(`buildGraphData()`) for programmatic use.
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Field options (model dropdowns)
|
|
103
|
-
|
|
104
|
-
Some settings have a constrained set of valid values, so the form offers a list
|
|
105
|
-
rather than free text. `GET /api/field-options` returns
|
|
106
|
-
`{ [dottedKey]: { values: string[], strict: boolean } }`:
|
|
107
|
-
|
|
108
|
-
- **`embedding.model`, `reranker.model` — non-strict.** `embedding.model`
|
|
109
|
-
suggests `MODEL_REGISTRY` (`src/constants.ts`) entries plus any model the
|
|
110
|
-
backend currently serves; `reranker.model` suggests only served models. Both
|
|
111
|
-
allow free text — a model outside the registry still works (its dimension is
|
|
112
|
-
probed at index time). Served models come from `IEmbedder.listModels()`,
|
|
113
|
-
implemented only by `InfinityEmbedder` (2-second-limited `/models` listing);
|
|
114
|
-
if the backend is unreachable, or the provider doesn't implement `listModels`
|
|
115
|
-
(`openai-compatible`, `voyage`), the served-model list is empty and the field
|
|
116
|
-
degrades to registry suggestions only (or plain text for `reranker.model`).
|
|
117
|
-
|
|
118
|
-
A strict `<select>` preserves an unknown current value as an extra option, so
|
|
119
|
-
saving never silently drops a value that isn't in the list.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## HTTP API
|
|
124
|
-
|
|
125
|
-
All routes are served from `127.0.0.1:<port>`.
|
|
126
|
-
|
|
127
|
-
| Method | Path | Response |
|
|
128
|
-
|---|---|---|
|
|
129
|
-
| `GET` | `/` | Dashboard HTML (self-contained) |
|
|
130
|
-
| `GET` | `/api/schema` | JSON Schema of the config (`buildJsonSchema()`) |
|
|
131
|
-
| `GET` | `/api/defaults` | `CONFIG_DEFAULTS` — dotted key → default value |
|
|
132
|
-
| `GET` | `/api/field-options` | dotted key → `{ values, strict }` |
|
|
133
|
-
| `GET` | `/api/config` | Current config, `db.password` masked |
|
|
134
|
-
| `PUT` | `/api/config` | Save. Body = config JSON. `200 {ok:true, backend:{action,message}}` or `400 {error}`. For `embedding.provider: infinity`, `backend.action` reports whether the Infinity container needed a background recreate (`started`/`already-running`/`not-needed`/`manual-required`/`failed`) — see *Infinity backend recreate* below |
|
|
135
|
-
| `GET` | `/api/backend-status` | `{ state, message, startedAt, finishedAt }` — progress of the most recent background Infinity recreate this server process started |
|
|
136
|
-
| `GET` | `/api/projects` | `SearcherService.listProjects()` |
|
|
137
|
-
| `GET` | `/api/graph` | `{ nodes, edges }` JSON (`buildGraphData()`) |
|
|
138
|
-
| `GET` | `/graph` | Full interactive graph page (`buildHtml()`), embedded by the Graph tab's iframe |
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Infinity backend recreate
|
|
143
|
-
|
|
144
|
-
When a save's `embedding.provider` is `infinity`, the server computes the
|
|
145
|
-
required model set from the **saved** config (freshly re-read from disk, not
|
|
146
|
-
the embedder the process started with) and compares it against what the
|
|
147
|
-
backend currently serves. If a managed container needs to be started or
|
|
148
|
-
recreated to serve the new set, that runs as an in-process background task —
|
|
149
|
-
the `PUT` responds immediately (`action: "started"`) without waiting for it.
|
|
150
|
-
Poll `GET /api/backend-status` for progress (`state: "recreating"` →
|
|
151
|
-
`"ready"`/`"failed"`). Only one `docker compose` run is active at a time; a
|
|
152
|
-
save that arrives while a recreate is already in flight for a managed backend
|
|
153
|
-
responds `already-running`, and at most one follow-up run applies the latest
|
|
154
|
-
such save once the in-flight one finishes. An external (unmanaged) backend
|
|
155
|
-
missing a required model never triggers Docker — the response's `message`
|
|
156
|
-
carries manual `--model-id`/launch instructions instead.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Architecture
|
|
161
|
-
|
|
162
|
-
| File | Responsibility |
|
|
163
|
-
|---|---|
|
|
164
|
-
| `src/commands/WebCommand.ts` | CLI entry. `db.initDb()`, starts the server, keeps `execute()` pending until `SIGINT`/`SIGTERM` (mirrors `McpCommand`), then `server.stop()`. Parses `--port` (default `4319`). |
|
|
165
|
-
| `src/services/WebServerService.ts` | `WebServer(searcher, config, logger, embedder, deps?)` — `node:http` server, routing, the API handlers, and the background Infinity recreate + `/api/backend-status` state. Exports the pure `mergeConfigForSave()` and the `FieldOption` type. |
|
|
166
|
-
| `src/services/infinity-provisioning.ts` | The readiness/recreate primitive the background task delegates to (shared with `InfinityEmbedder` and `rag-setup`) — classification, compose-file rewrite, `docker compose` invocation, readiness polling. |
|
|
167
|
-
| `src/web/dashboard.ts` | `renderDashboardHtml()` — the entire page (HTML + CSS + JS) as one self-contained string, mirroring `graph.ts:buildHtml`. |
|
|
168
|
-
|
|
169
|
-
Reused primitives (no duplication):
|
|
170
|
-
|
|
171
|
-
- `readConfigJson` / `parseConfig` / `writeConfigJson` / `buildJsonSchema` /
|
|
172
|
-
`CONFIG_DEFAULTS` — `src/config-store.ts`
|
|
173
|
-
- `buildGraphData` — `src/graph.ts`
|
|
174
|
-
- `listProjects` — `src/services/SearcherService.ts`
|
|
175
|
-
- `MODEL_REGISTRY` — `src/constants.ts`
|
|
176
|
-
- `evaluateBackend` / `ensureModelSetServed` / `requiredModelSet` — `src/services/infinity-provisioning.ts`
|
|
177
|
-
|
|
178
|
-
Wiring: `app.ts` detects the `web` mode and dispatches
|
|
179
|
-
`new WebCommand(container, effectiveArgs)`. The command uses the shared
|
|
180
|
-
`Container` services (`db`, `searcher`, `config`, `logger`) — no container
|
|
181
|
-
changes were needed.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Extending
|
|
186
|
-
|
|
187
|
-
- **Constrain another field to a list** — add an entry to `fieldOptions()` in
|
|
188
|
-
`WebServerService.ts` (`{ values, strict }`). The form renders it automatically
|
|
189
|
-
(strict → dropdown, non-strict → combobox).
|
|
190
|
-
- **Add a tab / endpoint** — add a route in `WebServer.handle`, a fetch + render
|
|
191
|
-
in `dashboard.ts`, and a tab button. Keep handlers thin — delegate to a service.
|
|
192
|
-
- **Actions (e.g. trigger reindex)** — deferred by design. This is heavier (a
|
|
193
|
-
long-running process with live progress, likely via SSE, plus concurrency
|
|
194
|
-
control) and belongs in its own change rather than the read/config-edit shell.
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Keep in sync
|
|
199
|
-
|
|
200
|
-
- `CONFIG_DEFAULTS` (`config-store.ts`) is the **single source of truth** for
|
|
201
|
-
built-in defaults: `resolvedSchema` in `config.ts` references it in its
|
|
202
|
-
`.default(...)` calls, so the runtime default and the dashboard-displayed
|
|
203
|
-
default cannot diverge — no manual sync needed. (A unit test still asserts
|
|
204
|
-
every key is a valid config key.)
|
|
205
|
-
- `MODEL_REGISTRY` (`constants.ts`) is advisory, not exhaustive — a model
|
|
206
|
-
outside it still works via the runtime dimension probe at index time.
|