codesearch-mcp 0.0.17-alpha.1 → 0.1.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 +48 -0
- package/README.md +95 -6
- package/dist/app.js +70 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +35 -7
- package/dist/src/commands/McpCommand.js +9 -4
- package/dist/src/commands/{SetupCommand.d.ts → WebCommand.d.ts} +3 -2
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +85 -4
- package/dist/src/config-store.js +157 -32
- package/dist/src/config.d.ts +32 -8
- package/dist/src/config.js +93 -30
- package/dist/src/constants.d.ts +35 -0
- package/dist/src/constants.js +57 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +10 -5
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +111 -0
- package/dist/src/db/schema.js +15 -1
- package/dist/src/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +20 -5
- package/dist/src/interfaces/IAstParser.d.ts +20 -3
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +27 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +15 -2
- package/dist/src/interfaces/ILogger.d.ts +10 -0
- package/dist/src/interfaces/ILogger.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
- package/dist/src/interfaces/IMcpToolHandler.js +2 -0
- package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
- package/dist/src/interfaces/IMonorepoResolver.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
- package/dist/src/interfaces/IProjectDiscovery.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +37 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +8 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstChunker.d.ts +31 -0
- package/dist/src/services/AstChunker.js +114 -0
- package/dist/src/services/AstParserService.d.ts +10 -6
- package/dist/src/services/AstParserService.js +60 -15
- package/dist/src/services/DatabaseService.d.ts +14 -1
- package/dist/src/services/DatabaseService.js +70 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +29 -0
- package/dist/src/services/IndexerService.d.ts +22 -12
- package/dist/src/services/IndexerService.js +106 -46
- package/dist/src/services/InfinityEmbedder.d.ts +79 -0
- package/dist/src/services/InfinityEmbedder.js +199 -0
- package/dist/src/services/LoggerService.d.ts +2 -1
- package/dist/src/services/McpServer.d.ts +16 -7
- package/dist/src/services/McpServer.js +54 -367
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
- package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
- package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
- package/dist/src/services/NxMonorepoResolver.js +308 -0
- package/dist/src/services/OllamaEmbedder.d.ts +19 -5
- package/dist/src/services/OllamaEmbedder.js +91 -36
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/{env-loader.js → services/ProjectDiscoveryService.js} +16 -5
- package/dist/src/services/SearcherService.d.ts +46 -7
- package/dist/src/services/SearcherService.js +183 -53
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +77 -0
- package/dist/src/services/WebServerService.js +267 -0
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +6 -0
- package/dist/src/setup.js +249 -31
- package/dist/src/tools/FindSymbolTool.d.ts +8 -0
- package/dist/src/tools/FindSymbolTool.js +45 -0
- package/dist/src/tools/GetCallersTool.d.ts +8 -0
- package/dist/src/tools/GetCallersTool.js +42 -0
- package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
- package/dist/src/tools/GetDependenciesTool.js +38 -0
- package/dist/src/tools/GetDependentsTool.d.ts +8 -0
- package/dist/src/tools/GetDependentsTool.js +57 -0
- package/dist/src/tools/ListFilesTool.d.ts +8 -0
- package/dist/src/tools/ListFilesTool.js +39 -0
- package/dist/src/tools/ListProjectsTool.d.ts +8 -0
- package/dist/src/tools/ListProjectsTool.js +44 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
- package/dist/src/tools/ReadFileRangeTool.js +44 -0
- package/dist/src/tools/ReadFileTool.d.ts +8 -0
- package/dist/src/tools/ReadFileTool.js +42 -0
- package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
- package/dist/src/tools/SearchCodebaseTool.js +81 -0
- package/dist/src/tools/format-helpers.d.ts +8 -0
- package/dist/src/tools/format-helpers.js +58 -0
- package/dist/src/tools/index.d.ts +9 -0
- package/dist/src/tools/index.js +21 -0
- package/dist/src/web/dashboard.d.ts +9 -0
- package/dist/src/web/dashboard.js +342 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +25 -0
- package/docs/web-dashboard.md +187 -0
- package/package.json +25 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- package/dist/src/commands/SetupCommand.js +0 -14
- package/dist/src/db/data-source.d.ts +0 -2
- package/dist/src/db/data-source.js +0 -13
- package/dist/src/db/entities/code-chunk.entity.d.ts +0 -9
- package/dist/src/db/entities/code-chunk.entity.js +0 -54
- package/dist/src/db/entities/code-dependency.entity.d.ts +0 -9
- package/dist/src/db/entities/code-dependency.entity.js +0 -54
- package/dist/src/db/entities/code-symbol.entity.d.ts +0 -10
- package/dist/src/db/entities/code-symbol.entity.js +0 -59
- package/dist/src/db/init.d.ts +0 -2
- package/dist/src/db/init.js +0 -62
- package/dist/src/env-loader.d.ts +0 -1
- package/dist/src/indexer.d.ts +0 -17
- package/dist/src/indexer.js +0 -242
- package/dist/src/mcp.d.ts +0 -1
- package/dist/src/mcp.js +0 -403
- package/dist/src/ollama.d.ts +0 -2
- package/dist/src/ollama.js +0 -6
- package/dist/src/searcher.d.ts +0 -74
- package/dist/src/searcher.js +0 -446
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
package/dist/src/graph.js
CHANGED
|
@@ -33,10 +33,80 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildGraphData = buildGraphData;
|
|
37
|
+
exports.buildGraphViews = buildGraphViews;
|
|
38
|
+
exports.buildHtml = buildHtml;
|
|
36
39
|
exports.generateProjectGraph = generateProjectGraph;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
39
42
|
const file_utils_1 = require("./file-utils");
|
|
43
|
+
const EMPTY_META = { description: null, stack: null, notes: null, relations: null };
|
|
44
|
+
/**
|
|
45
|
+
* Build the project graph data (nodes + relation edges) from a project list.
|
|
46
|
+
* Pure apart from reading each project's `.ragmeta.json`. Shared by the console
|
|
47
|
+
* graph, the graph views, and the web dashboard's `/api/graph`.
|
|
48
|
+
*/
|
|
49
|
+
function buildGraphData(projects) {
|
|
50
|
+
const nodes = projects.map(p => {
|
|
51
|
+
const meta = (0, file_utils_1.readRagMeta)(p.path);
|
|
52
|
+
return { name: p.name, ...(typeof meta === 'object' ? meta : EMPTY_META) };
|
|
53
|
+
});
|
|
54
|
+
return { nodes, edges: buildEdges(nodes) };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Split a project list into graph tabs. A project whose name contains a `/`
|
|
58
|
+
* belongs to a nested Nx repo (`<repo>/<sub-project>`); everything else is a
|
|
59
|
+
* flat top-level project.
|
|
60
|
+
*
|
|
61
|
+
* - **Workspace** tab: flat projects + one node per Nx repo (collapsed to a
|
|
62
|
+
* single `nxRepo` node); edges from `.ragmeta.json` relations.
|
|
63
|
+
* - **One tab per Nx repo**: its sub-projects as nodes, with edges between them
|
|
64
|
+
* taken from real import dependencies (`projectEdges`, from `code_dependency`).
|
|
65
|
+
*
|
|
66
|
+
* Returns the Workspace view first, then repo views sorted by name.
|
|
67
|
+
*/
|
|
68
|
+
function buildGraphViews(projects, projectEdges, workspaceRoot) {
|
|
69
|
+
const flat = [];
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const p of projects) {
|
|
72
|
+
const slash = p.name.indexOf('/');
|
|
73
|
+
if (slash > 0) {
|
|
74
|
+
const repo = p.name.slice(0, slash);
|
|
75
|
+
let list = groups.get(repo);
|
|
76
|
+
if (!list) {
|
|
77
|
+
list = [];
|
|
78
|
+
groups.set(repo, list);
|
|
79
|
+
}
|
|
80
|
+
list.push(p);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
flat.push(p);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const repoNames = [...groups.keys()].sort();
|
|
87
|
+
// Workspace: flat projects + one collapsed node per repo (reads repo-root ragmeta).
|
|
88
|
+
const workspaceProjects = [
|
|
89
|
+
...flat,
|
|
90
|
+
...repoNames.map(repo => ({ name: repo, path: path.join(workspaceRoot, repo) })),
|
|
91
|
+
];
|
|
92
|
+
const ws = buildGraphData(workspaceProjects);
|
|
93
|
+
const repoSet = new Set(repoNames);
|
|
94
|
+
for (const n of ws.nodes)
|
|
95
|
+
if (repoSet.has(n.name))
|
|
96
|
+
n.nxRepo = true;
|
|
97
|
+
const workspace = { name: 'Workspace', nodes: ws.nodes, edges: ws.edges };
|
|
98
|
+
// Per-repo: sub-project nodes + intra-repo import edges.
|
|
99
|
+
const repoViews = repoNames.map(repo => {
|
|
100
|
+
const subs = groups.get(repo);
|
|
101
|
+
const { nodes } = buildGraphData(subs);
|
|
102
|
+
const names = new Set(subs.map(s => s.name));
|
|
103
|
+
const edges = projectEdges
|
|
104
|
+
.filter(e => names.has(e.from) && names.has(e.to))
|
|
105
|
+
.map(e => ({ from: e.from, to: e.to, label: 'imports' }));
|
|
106
|
+
return { name: repo, nodes, edges };
|
|
107
|
+
});
|
|
108
|
+
return [workspace, ...repoViews];
|
|
109
|
+
}
|
|
40
110
|
function buildEdges(nodes) {
|
|
41
111
|
const edges = [];
|
|
42
112
|
for (const node of nodes) {
|
|
@@ -50,49 +120,68 @@ function buildEdges(nodes) {
|
|
|
50
120
|
}
|
|
51
121
|
return edges;
|
|
52
122
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
grouped.set(e.from, []);
|
|
64
|
-
grouped.get(e.from).push(e);
|
|
65
|
-
}
|
|
66
|
-
for (const [from, fromEdges] of grouped) {
|
|
67
|
-
const byLabel = new Map();
|
|
68
|
-
for (const e of fromEdges) {
|
|
69
|
-
if (!byLabel.has(e.label))
|
|
70
|
-
byLabel.set(e.label, []);
|
|
71
|
-
byLabel.get(e.label).push(e);
|
|
123
|
+
function printConsole(views, workspacePath) {
|
|
124
|
+
const workspace = views[0];
|
|
125
|
+
const repoViews = views.slice(1);
|
|
126
|
+
if (workspace.edges.length > 0) {
|
|
127
|
+
console.log('\n=== Project Relations ===');
|
|
128
|
+
const grouped = new Map();
|
|
129
|
+
for (const e of workspace.edges) {
|
|
130
|
+
if (!grouped.has(e.from))
|
|
131
|
+
grouped.set(e.from, []);
|
|
132
|
+
grouped.get(e.from).push(e);
|
|
72
133
|
}
|
|
73
|
-
for (const [
|
|
74
|
-
const
|
|
75
|
-
|
|
134
|
+
for (const [from, fromEdges] of grouped) {
|
|
135
|
+
const byLabel = new Map();
|
|
136
|
+
for (const e of fromEdges) {
|
|
137
|
+
if (!byLabel.has(e.label))
|
|
138
|
+
byLabel.set(e.label, []);
|
|
139
|
+
byLabel.get(e.label).push(e);
|
|
140
|
+
}
|
|
141
|
+
for (const [label, labelEdges] of byLabel) {
|
|
142
|
+
const targets = labelEdges.map(e => e.to).join(', ');
|
|
143
|
+
console.log(` ${from.padEnd(30)} → ${targets.padEnd(30)} [${label}]`);
|
|
144
|
+
}
|
|
76
145
|
}
|
|
77
146
|
}
|
|
78
|
-
|
|
147
|
+
for (const v of repoViews) {
|
|
148
|
+
console.log(` Nx repo ${v.name.padEnd(24)} ${v.nodes.length} projects · ${v.edges.length} internal edges`);
|
|
149
|
+
}
|
|
150
|
+
if (workspace.edges.length > 0 || repoViews.length > 0) {
|
|
151
|
+
console.log(`\n Full graph: file://${path.join(workspacePath, 'project-graph.html')}`);
|
|
152
|
+
}
|
|
79
153
|
}
|
|
80
|
-
|
|
81
|
-
|
|
154
|
+
/** Prepare a view for vis-network: colored nodes (+ synthetic nodes for unknown edge targets) and indexed edges. */
|
|
155
|
+
function viewToVis(view) {
|
|
156
|
+
const nodes = view.nodes.map(n => ({
|
|
82
157
|
id: n.name,
|
|
83
158
|
label: n.name,
|
|
84
159
|
title: [n.description, n.stack].filter(Boolean).join('\n') || n.name,
|
|
85
|
-
color: n.relations ? '#4A90D9' : '#A8C7E8',
|
|
160
|
+
color: n.nxRepo ? '#B57EDC' : (n.relations ? '#4A90D9' : '#A8C7E8'),
|
|
161
|
+
...(n.nxRepo ? { borderWidth: 3 } : {}),
|
|
86
162
|
}));
|
|
87
|
-
const known = new Set(nodes.map(n => n.name));
|
|
88
|
-
for (const e of edges) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
163
|
+
const known = new Set(view.nodes.map(n => n.name));
|
|
164
|
+
for (const e of view.edges) {
|
|
165
|
+
for (const end of [e.from, e.to]) {
|
|
166
|
+
if (!known.has(end)) {
|
|
167
|
+
nodes.push({ id: end, label: end, title: end, color: '#E8A838' });
|
|
168
|
+
known.add(end);
|
|
169
|
+
}
|
|
92
170
|
}
|
|
93
171
|
}
|
|
94
|
-
const
|
|
95
|
-
const
|
|
172
|
+
const edges = view.edges.map((e, i) => ({ id: i, from: e.from, to: e.to, label: e.label, arrows: 'to' }));
|
|
173
|
+
const labels = [...new Set(view.edges.map(e => e.label))].sort();
|
|
174
|
+
return { name: view.name, nodes, edges, labels };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Render the full interactive graph page (vis-network + inner tabs, edge-type
|
|
178
|
+
* filters, legend, project toggle list). Used for the standalone
|
|
179
|
+
* `project-graph.html` and served by the web dashboard at `GET /graph` (embedded
|
|
180
|
+
* via iframe) so both share one implementation. Tab 0 is the Workspace view;
|
|
181
|
+
* double-clicking an Nx-repo node opens that repo's tab.
|
|
182
|
+
*/
|
|
183
|
+
function buildHtml(views) {
|
|
184
|
+
const visViews = views.map(viewToVis);
|
|
96
185
|
return `<!DOCTYPE html>
|
|
97
186
|
<html lang="en">
|
|
98
187
|
<head>
|
|
@@ -104,7 +193,10 @@ function buildHtml(nodes, edges) {
|
|
|
104
193
|
body { font-family: system-ui, sans-serif; background: #1a1a2e; color: #eee; display: flex; flex-direction: column; height: 100vh; }
|
|
105
194
|
#header { padding: 14px 20px; background: #16213e; border-bottom: 1px solid #0f3460; display: flex; align-items: center; gap: 20px; flex-shrink: 0; }
|
|
106
195
|
#header h1 { font-size: 16px; font-weight: 600; white-space: nowrap; }
|
|
107
|
-
#
|
|
196
|
+
#count { font-size: 12px; color: #888; }
|
|
197
|
+
#view-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
198
|
+
.view-tab { padding: 5px 12px; border-radius: 8px; border: 1px solid #2a3f6a; background: transparent; color: #88aabb; font-size: 12px; cursor: pointer; }
|
|
199
|
+
.view-tab.active { background: #4A90D9; border-color: #4A90D9; color: #fff; }
|
|
108
200
|
#filters { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
|
|
109
201
|
.filter-btn {
|
|
110
202
|
padding: 4px 10px; border-radius: 12px; border: 1px solid #4A90D9;
|
|
@@ -133,77 +225,50 @@ function buildHtml(nodes, edges) {
|
|
|
133
225
|
<div id="header">
|
|
134
226
|
<div>
|
|
135
227
|
<h1>Project Graph</h1>
|
|
136
|
-
<p
|
|
137
|
-
</div>
|
|
138
|
-
<div id="filters">
|
|
139
|
-
${allLabels.map(l => `<button class="filter-btn" data-label="${escHtml(l)}">${escHtml(l)}</button>`).join('\n ')}
|
|
228
|
+
<p id="count"></p>
|
|
140
229
|
</div>
|
|
230
|
+
<div id="view-tabs"></div>
|
|
231
|
+
<div id="filters"></div>
|
|
141
232
|
</div>
|
|
142
233
|
<div id="main">
|
|
143
234
|
<div id="network"></div>
|
|
144
235
|
<div id="legend">
|
|
145
236
|
<h3>Nodes</h3>
|
|
237
|
+
<div class="entry"><span class="dot" style="background:#B57EDC"></span>Nx repo (double-click to open)</div>
|
|
146
238
|
<div class="entry"><span class="dot" style="background:#4A90D9"></span>has .ragmeta.json</div>
|
|
147
|
-
<div class="entry"><span class="dot" style="background:#
|
|
239
|
+
<div class="entry"><span class="dot" style="background:#A8C7E8"></span>no .ragmeta.json</div>
|
|
148
240
|
<div class="entry"><span class="dot" style="background:#E8A838"></span>not indexed</div>
|
|
149
|
-
<div id="hint">Drag to rearrange.<br>Edge buttons — toggle type.<br>Projects — toggle node.</div>
|
|
241
|
+
<div id="hint">Drag to rearrange.<br>Edge buttons — toggle type.<br>Projects — toggle node.<br>Double-click an Nx repo to open it.</div>
|
|
150
242
|
<div id="project-list">
|
|
151
243
|
<h3>Projects</h3>
|
|
152
|
-
|
|
244
|
+
<div id="proj-list-body"></div>
|
|
153
245
|
</div>
|
|
154
246
|
</div>
|
|
155
247
|
</div>
|
|
156
248
|
<script>
|
|
157
|
-
const
|
|
158
|
-
const nodesDs = new vis.DataSet(${JSON.stringify(visNodes)});
|
|
159
|
-
const edgesDs = new vis.DataSet(allEdges);
|
|
249
|
+
const VIEWS = ${JSON.stringify(visViews)};
|
|
160
250
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
selectionWidth: 2,
|
|
178
|
-
},
|
|
179
|
-
physics: {
|
|
180
|
-
solver: 'barnesHut',
|
|
181
|
-
barnesHut: {
|
|
182
|
-
gravitationalConstant: -18000,
|
|
183
|
-
springConstant: 0.025,
|
|
184
|
-
springLength: 280,
|
|
185
|
-
damping: 0.25,
|
|
186
|
-
},
|
|
187
|
-
stabilization: { iterations: 400, updateInterval: 25 },
|
|
188
|
-
},
|
|
189
|
-
interaction: { hover: true, tooltipDelay: 80, dragNodes: true },
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
network.on('stabilizationIterationsDone', () => {
|
|
194
|
-
network.fit({ animation: { duration: 600, easingFunction: 'easeInOutQuad' } });
|
|
195
|
-
setTimeout(() => network.setOptions({ physics: { enabled: false } }), 650);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
network.on('hoverEdge', ({ edge }) => {
|
|
199
|
-
edgesDs.update({ id: edge, font: { color: '#fff', size: 16, background: '#16213e', strokeWidth: 0 } });
|
|
200
|
-
});
|
|
201
|
-
network.on('blurEdge', ({ edge }) => {
|
|
202
|
-
edgesDs.update({ id: edge, font: { color: '#aaa', size: 11, background: '#1a1a2e', strokeWidth: 0 } });
|
|
203
|
-
});
|
|
251
|
+
const NET_OPTIONS = {
|
|
252
|
+
nodes: { shape: 'box', font: { color: '#fff', size: 13 }, borderWidth: 2, shadow: true, margin: { top: 8, bottom: 8, left: 12, right: 12 } },
|
|
253
|
+
edges: {
|
|
254
|
+
font: { color: '#aaa', size: 11, align: 'middle', strokeWidth: 0, background: '#1a1a2e' },
|
|
255
|
+
color: { color: '#4A90D9', highlight: '#7eb8f7', hover: '#7eb8f7' },
|
|
256
|
+
smooth: { type: 'curvedCW', roundness: 0.2 },
|
|
257
|
+
arrows: { to: { scaleFactor: 0.6 } },
|
|
258
|
+
selectionWidth: 2,
|
|
259
|
+
},
|
|
260
|
+
physics: {
|
|
261
|
+
solver: 'barnesHut',
|
|
262
|
+
barnesHut: { gravitationalConstant: -18000, springConstant: 0.025, springLength: 280, damping: 0.25 },
|
|
263
|
+
stabilization: { iterations: 400, updateInterval: 25 },
|
|
264
|
+
},
|
|
265
|
+
interaction: { hover: true, tooltipDelay: 80, dragNodes: true },
|
|
266
|
+
};
|
|
204
267
|
|
|
205
|
-
|
|
206
|
-
|
|
268
|
+
let active = -1;
|
|
269
|
+
let network = null;
|
|
270
|
+
let nodesDs = null, edgesDs = null, allEdges = [];
|
|
271
|
+
let activeLabels = new Set(), hiddenNodes = new Set();
|
|
207
272
|
|
|
208
273
|
function refreshEdges() {
|
|
209
274
|
const visible = allEdges.filter(e =>
|
|
@@ -213,46 +278,100 @@ function buildHtml(nodes, edges) {
|
|
|
213
278
|
edgesDs.add(visible);
|
|
214
279
|
}
|
|
215
280
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
281
|
+
function renderTabs() {
|
|
282
|
+
const bar = document.getElementById('view-tabs');
|
|
283
|
+
bar.innerHTML = '';
|
|
284
|
+
if (VIEWS.length < 2) return; // only Workspace — no tab bar needed
|
|
285
|
+
VIEWS.forEach((v, i) => {
|
|
286
|
+
const b = document.createElement('button');
|
|
287
|
+
b.className = 'view-tab' + (i === active ? ' active' : '');
|
|
288
|
+
b.textContent = v.name;
|
|
289
|
+
b.addEventListener('click', () => selectView(i));
|
|
290
|
+
bar.appendChild(b);
|
|
222
291
|
});
|
|
223
|
-
}
|
|
292
|
+
}
|
|
224
293
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
294
|
+
function renderFilters() {
|
|
295
|
+
const f = document.getElementById('filters');
|
|
296
|
+
f.innerHTML = '';
|
|
297
|
+
VIEWS[active].labels.forEach(label => {
|
|
298
|
+
const b = document.createElement('button');
|
|
299
|
+
b.className = 'filter-btn';
|
|
300
|
+
b.textContent = label;
|
|
301
|
+
b.addEventListener('click', () => {
|
|
302
|
+
if (activeLabels.has(label)) { activeLabels.delete(label); b.classList.add('off'); }
|
|
303
|
+
else { activeLabels.add(label); b.classList.remove('off'); }
|
|
304
|
+
refreshEdges();
|
|
305
|
+
});
|
|
306
|
+
f.appendChild(b);
|
|
238
307
|
});
|
|
239
|
-
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function renderProjectList() {
|
|
311
|
+
const body = document.getElementById('proj-list-body');
|
|
312
|
+
body.innerHTML = '';
|
|
313
|
+
VIEWS[active].nodes.forEach(n => {
|
|
314
|
+
const b = document.createElement('button');
|
|
315
|
+
b.className = 'proj-btn';
|
|
316
|
+
const dot = document.createElement('span');
|
|
317
|
+
dot.className = 'proj-dot';
|
|
318
|
+
dot.style.background = n.color;
|
|
319
|
+
b.appendChild(dot);
|
|
320
|
+
b.appendChild(document.createTextNode(n.label));
|
|
321
|
+
b.addEventListener('click', () => {
|
|
322
|
+
if (hiddenNodes.has(n.id)) { hiddenNodes.delete(n.id); nodesDs.update({ id: n.id, hidden: false }); b.classList.remove('off'); }
|
|
323
|
+
else { hiddenNodes.add(n.id); nodesDs.update({ id: n.id, hidden: true }); b.classList.add('off'); }
|
|
324
|
+
refreshEdges();
|
|
325
|
+
});
|
|
326
|
+
body.appendChild(b);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function selectView(i) {
|
|
331
|
+
active = i;
|
|
332
|
+
const v = VIEWS[i];
|
|
333
|
+
activeLabels = new Set(v.labels);
|
|
334
|
+
hiddenNodes = new Set();
|
|
335
|
+
allEdges = v.edges;
|
|
336
|
+
nodesDs = new vis.DataSet(v.nodes);
|
|
337
|
+
edgesDs = new vis.DataSet(v.edges);
|
|
338
|
+
|
|
339
|
+
if (!network) {
|
|
340
|
+
network = new vis.Network(document.getElementById('network'), { nodes: nodesDs, edges: edgesDs }, NET_OPTIONS);
|
|
341
|
+
network.on('stabilizationIterationsDone', () => {
|
|
342
|
+
network.fit({ animation: { duration: 600, easingFunction: 'easeInOutQuad' } });
|
|
343
|
+
setTimeout(() => network.setOptions({ physics: { enabled: false } }), 650);
|
|
344
|
+
});
|
|
345
|
+
network.on('hoverEdge', ({ edge }) => edgesDs.update({ id: edge, font: { color: '#fff', size: 16, background: '#16213e', strokeWidth: 0 } }));
|
|
346
|
+
network.on('blurEdge', ({ edge }) => edgesDs.update({ id: edge, font: { color: '#aaa', size: 11, background: '#1a1a2e', strokeWidth: 0 } }));
|
|
347
|
+
network.on('doubleClick', params => {
|
|
348
|
+
if (!params.nodes.length) return;
|
|
349
|
+
const idx = VIEWS.findIndex(vv => vv.name === params.nodes[0]);
|
|
350
|
+
if (idx >= 0 && idx !== active) selectView(idx);
|
|
351
|
+
});
|
|
352
|
+
} else {
|
|
353
|
+
network.setOptions({ physics: { enabled: true } });
|
|
354
|
+
network.setData({ nodes: nodesDs, edges: edgesDs });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
document.getElementById('count').textContent = v.nodes.length + ' nodes · ' + v.edges.length + ' edges';
|
|
358
|
+
renderTabs();
|
|
359
|
+
renderFilters();
|
|
360
|
+
renderProjectList();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
selectView(0);
|
|
240
364
|
</script>
|
|
241
365
|
</body>
|
|
242
366
|
</html>`;
|
|
243
367
|
}
|
|
244
|
-
// Single entry point —
|
|
245
|
-
function generateProjectGraph(projects, workspacePath) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
const meta = (0, file_utils_1.readRagMeta)(p.path);
|
|
249
|
-
return { name: p.name, ...(typeof meta === 'object' ? meta : EMPTY) };
|
|
250
|
-
});
|
|
251
|
-
const edges = buildEdges(nodes);
|
|
252
|
-
printConsole(nodes, edges, workspacePath);
|
|
368
|
+
// Single entry point — builds the tabbed views, prints a console summary, writes the HTML.
|
|
369
|
+
function generateProjectGraph(projects, projectEdges, workspacePath) {
|
|
370
|
+
const views = buildGraphViews(projects, projectEdges, workspacePath);
|
|
371
|
+
printConsole(views, workspacePath);
|
|
253
372
|
const htmlPath = path.join(workspacePath, 'project-graph.html');
|
|
254
373
|
try {
|
|
255
|
-
fs.writeFileSync(htmlPath, buildHtml(
|
|
374
|
+
fs.writeFileSync(htmlPath, buildHtml(views), 'utf-8');
|
|
256
375
|
}
|
|
257
376
|
catch (err) {
|
|
258
377
|
console.error(` ⚠ Could not write project-graph.html: ${err.message}`);
|
package/dist/src/help.js
CHANGED
|
@@ -46,22 +46,37 @@ function printHelp(command) {
|
|
|
46
46
|
console.log(' get <key> Print a single value');
|
|
47
47
|
console.log(' set <key> <value> Set a value');
|
|
48
48
|
console.log(' reset <key> Remove key (falls back to built-in default)');
|
|
49
|
+
console.log(' edit Open config in $EDITOR (with schema validation)');
|
|
49
50
|
console.log('');
|
|
50
51
|
console.log('Keys:');
|
|
51
52
|
console.log(' db.host, db.port, db.user, db.password, db.name');
|
|
52
|
-
console.log('
|
|
53
|
+
console.log(' embedding.provider, embedding.baseURL, embedding.apiKey, embedding.model,');
|
|
54
|
+
console.log(' embedding.dimensions, embedding.batchSize');
|
|
53
55
|
console.log(' indexing.chunkSize, indexing.chunkOverlap, indexing.embedBatchSize');
|
|
54
56
|
console.log(' search.similarityThreshold, search.defaultLimit, search.maxLimit');
|
|
55
|
-
console.log('
|
|
56
|
-
console.log(' reranker.
|
|
57
|
+
console.log(' reranker.enabled, reranker.provider, reranker.baseURL, reranker.apiKey,');
|
|
58
|
+
console.log(' reranker.model, reranker.url, reranker.overfetch');
|
|
57
59
|
console.log(' logging.level, logging.dir');
|
|
58
|
-
console.log(' workspaceRoot');
|
|
60
|
+
console.log(' workspaceRoot (API keys: use env:VAR_NAME → ~/.config/codesearch-mcp/.env)');
|
|
61
|
+
break;
|
|
62
|
+
case 'rag-web':
|
|
63
|
+
case 'web':
|
|
64
|
+
console.log('Usage: rag-web [--port N]');
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log('Start a local web dashboard (config editor, project overview,');
|
|
67
|
+
console.log('dependency graph) on http://127.0.0.1:<port>.');
|
|
68
|
+
console.log('Requires a valid config.json — repair a broken one with rag-config edit.');
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log('Options:');
|
|
71
|
+
console.log(' --port N Listen port (default 4319)');
|
|
72
|
+
console.log(' -h, --help Show this help message');
|
|
59
73
|
break;
|
|
60
74
|
default:
|
|
61
75
|
console.log('Usage: rag-index [--reindex] [--select]');
|
|
62
76
|
console.log(' rag-mcp');
|
|
63
77
|
console.log(' rag-setup');
|
|
64
|
-
console.log(' rag-config <show|get|set|reset>');
|
|
78
|
+
console.log(' rag-config <show|get|set|reset|edit>');
|
|
79
|
+
console.log(' rag-web [--port N]');
|
|
65
80
|
console.log('');
|
|
66
81
|
console.log('Run any command with -h or --help for details.');
|
|
67
82
|
}
|
|
@@ -26,18 +26,35 @@ export interface CallEdge {
|
|
|
26
26
|
line: number | null;
|
|
27
27
|
col: number | null;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Line range of a named scope used only to compute a chunk's owning declaration
|
|
31
|
+
* (`ownerPath`). Method/constructor/accessor ranges carry their enclosing class
|
|
32
|
+
* as `container`; top-level declarations use `container: null`. These are NOT
|
|
33
|
+
* persisted to `code_symbol` — they exist only for chunk ownership.
|
|
34
|
+
*/
|
|
35
|
+
export interface OwnerRange {
|
|
36
|
+
name: string;
|
|
37
|
+
container: string | null;
|
|
38
|
+
line: number;
|
|
39
|
+
endLine: number;
|
|
40
|
+
}
|
|
29
41
|
export interface FileAnalysis {
|
|
30
42
|
edges: DepEdge[];
|
|
31
43
|
symbols: SymbolEntry[];
|
|
32
44
|
calls: CallEdge[];
|
|
45
|
+
/** Method/accessor/constructor ranges for owner-path resolution (Phase 2). */
|
|
46
|
+
ownerRanges?: OwnerRange[];
|
|
47
|
+
}
|
|
48
|
+
export interface AnalyzeProjectOpts {
|
|
49
|
+
skipDependencyResolution?: boolean;
|
|
50
|
+
/** Path to root tsconfig (e.g. tsconfig.base.json) for workspace-wide path aliases */
|
|
51
|
+
rootTsConfigPath?: string;
|
|
33
52
|
}
|
|
34
53
|
export interface IAstParser {
|
|
35
54
|
/**
|
|
36
55
|
* @deprecated Use analyzeProject instead for project-wide context.
|
|
37
56
|
*/
|
|
38
57
|
analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
|
|
39
|
-
analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?:
|
|
40
|
-
skipDependencyResolution?: boolean;
|
|
41
|
-
}): Promise<Map<string, FileAnalysis>>;
|
|
58
|
+
analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?: AnalyzeProjectOpts): Promise<Map<string, FileAnalysis>>;
|
|
42
59
|
clearCache(projectRoot?: string): void;
|
|
43
60
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SymbolEntry } from './IAstParser';
|
|
2
|
+
/** One embeddable unit of source with its 1-based inclusive line range. */
|
|
3
|
+
export interface ChunkPiece {
|
|
4
|
+
content: string;
|
|
5
|
+
startLine: number;
|
|
6
|
+
endLine: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ChunkOpts {
|
|
9
|
+
/** 'ast' = symbol-aligned boundaries; 'character' = fixed-size recursive splitter. */
|
|
10
|
+
strategy: 'ast' | 'character';
|
|
11
|
+
/** Schema files (.prisma/.graphql/.gql) always use the schema separator splitter. */
|
|
12
|
+
isSchemaFile: boolean;
|
|
13
|
+
/** Top-level symbol ranges for the file; may be empty (no analysis available). */
|
|
14
|
+
symbols: SymbolEntry[];
|
|
15
|
+
}
|
|
16
|
+
export interface IChunker {
|
|
17
|
+
/**
|
|
18
|
+
* Split source into embeddable pieces. AST strategy aligns boundaries to
|
|
19
|
+
* top-level declaration ranges and never emits a piece longer than the
|
|
20
|
+
* configured chunk size; falls back to the character splitter for schema
|
|
21
|
+
* files, empty-symbol files, or when strategy is 'character'.
|
|
22
|
+
*/
|
|
23
|
+
chunk(code: string, opts: ChunkOpts): Promise<ChunkPiece[]>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DrizzleDb, DrizzleTx } from '../services/DatabaseService';
|
|
2
|
+
/** Single-row record of which embedding model built the current index. */
|
|
3
|
+
export interface IndexMeta {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
dimensions: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Database abstraction — services depend on this interface
|
|
10
|
+
* rather than the concrete DatabaseService.
|
|
11
|
+
*/
|
|
12
|
+
export interface IDatabase {
|
|
13
|
+
readonly orm: DrizzleDb;
|
|
14
|
+
query<T = any>(sql: string, params?: any[]): Promise<T>;
|
|
15
|
+
transaction<T>(fn: (tx: DrizzleTx) => Promise<T>): Promise<T>;
|
|
16
|
+
initDb(): Promise<void>;
|
|
17
|
+
initDbWithSchema(): Promise<void>;
|
|
18
|
+
destroy(): Promise<void>;
|
|
19
|
+
/** Read the single `index_meta` row, or null when absent (table missing → null). */
|
|
20
|
+
readIndexMeta(): Promise<IndexMeta | null>;
|
|
21
|
+
/** Upsert the single `index_meta` row. */
|
|
22
|
+
writeIndexMeta(meta: IndexMeta): Promise<void>;
|
|
23
|
+
/** Create `code_chunk` (+ HNSW/service indexes) at `dimensions` if absent. */
|
|
24
|
+
ensureChunkTable(dimensions: number): Promise<void>;
|
|
25
|
+
/** Drop and recreate `code_chunk` at `dimensions` (dimension change). */
|
|
26
|
+
recreateChunkTable(dimensions: number): Promise<void>;
|
|
27
|
+
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
export type InputType = 'query' | 'document';
|
|
1
2
|
export interface IEmbedder {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The vector width this embedder produces, when known without a probe:
|
|
5
|
+
* `embedding.dimensions` override → model registry, else `0` for models whose
|
|
6
|
+
* width is only knowable by embedding once (see `resolveDimension`). Read by
|
|
7
|
+
* `SearcherService.assertIndexMatches` to catch a dimension change on the
|
|
8
|
+
* search side (skipped when `0`). Indexing resolves the true width via
|
|
9
|
+
* `resolveDimension` (which probes when needed), not this field.
|
|
10
|
+
*/
|
|
11
|
+
readonly dimensions: number;
|
|
12
|
+
embed(text: string, inputType?: InputType): Promise<number[]>;
|
|
13
|
+
/** One vector per input, in input order. The indexing path. */
|
|
14
|
+
embedBatch(texts: string[], inputType?: InputType): Promise<number[][]>;
|
|
4
15
|
rerank?(query: string, documents: string[], topN: number): Promise<number[]>;
|
|
16
|
+
listModels?(): Promise<string[]>;
|
|
17
|
+
ensureModelAvailable?(): Promise<void>;
|
|
5
18
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger abstraction — allows services to depend on a contract
|
|
3
|
+
* rather than the concrete pino-based LoggerService.
|
|
4
|
+
*/
|
|
5
|
+
export interface ILogger {
|
|
6
|
+
debug(msg: string, data?: Record<string, unknown>): void;
|
|
7
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
8
|
+
warn(msg: string, data?: Record<string, unknown>): void;
|
|
9
|
+
error(msg: string, data?: Record<string, unknown>): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Handler interface — each tool implements this contract.
|
|
3
|
+
* Follows the Open/Closed Principle: new tools are added by implementing
|
|
4
|
+
* this interface and registering them, not by modifying McpServer.
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolDefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolResponse {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
content: Array<{
|
|
15
|
+
type: 'text';
|
|
16
|
+
text: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export interface IMcpToolHandler {
|
|
20
|
+
definition(): ToolDefinition;
|
|
21
|
+
handle(args: Record<string, unknown>): Promise<ToolResponse>;
|
|
22
|
+
}
|