codesearch-mcp 0.0.17-alpha.1 → 0.0.18-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 +25 -0
- package/README.md +20 -0
- package/dist/app.js +34 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +22 -7
- package/dist/src/commands/McpCommand.js +4 -3
- package/dist/src/commands/WebCommand.d.ts +8 -0
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +43 -0
- package/dist/src/config-store.js +92 -27
- package/dist/src/config.js +16 -17
- package/dist/src/constants.d.ts +2 -1
- package/dist/src/constants.js +6 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +6 -3
- package/dist/src/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +15 -1
- package/dist/src/interfaces/IAstParser.d.ts +6 -3
- package/dist/src/interfaces/IDatabase.d.ts +13 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +1 -0
- 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 +36 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +7 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstParserService.d.ts +4 -6
- package/dist/src/services/AstParserService.js +29 -13
- package/dist/src/services/DatabaseService.d.ts +2 -1
- package/dist/src/services/IndexerService.d.ts +6 -8
- package/dist/src/services/IndexerService.js +5 -8
- 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 +5 -2
- package/dist/src/services/OllamaEmbedder.js +9 -2
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/services/ProjectDiscoveryService.js +52 -0
- package/dist/src/services/SearcherService.d.ts +39 -6
- package/dist/src/services/SearcherService.js +151 -40
- package/dist/src/services/WebServerService.d.ts +72 -0
- package/dist/src/services/WebServerService.js +247 -0
- package/dist/src/setup.js +7 -7
- 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 +57 -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 +337 -0
- package/docs/web-dashboard.md +187 -0
- package/package.json +5 -2
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,6 +46,7 @@ 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');
|
|
@@ -57,11 +58,24 @@ function printHelp(command) {
|
|
|
57
58
|
console.log(' logging.level, logging.dir');
|
|
58
59
|
console.log(' workspaceRoot');
|
|
59
60
|
break;
|
|
61
|
+
case 'rag-web':
|
|
62
|
+
case 'web':
|
|
63
|
+
console.log('Usage: rag-web [--port N]');
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log('Start a local web dashboard (config editor, project overview,');
|
|
66
|
+
console.log('dependency graph) on http://127.0.0.1:<port>.');
|
|
67
|
+
console.log('Requires a valid config.json — repair a broken one with rag-config edit.');
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log('Options:');
|
|
70
|
+
console.log(' --port N Listen port (default 4319)');
|
|
71
|
+
console.log(' -h, --help Show this help message');
|
|
72
|
+
break;
|
|
60
73
|
default:
|
|
61
74
|
console.log('Usage: rag-index [--reindex] [--select]');
|
|
62
75
|
console.log(' rag-mcp');
|
|
63
76
|
console.log(' rag-setup');
|
|
64
|
-
console.log(' rag-config <show|get|set|reset>');
|
|
77
|
+
console.log(' rag-config <show|get|set|reset|edit>');
|
|
78
|
+
console.log(' rag-web [--port N]');
|
|
65
79
|
console.log('');
|
|
66
80
|
console.log('Run any command with -h or --help for details.');
|
|
67
81
|
}
|
|
@@ -31,13 +31,16 @@ export interface FileAnalysis {
|
|
|
31
31
|
symbols: SymbolEntry[];
|
|
32
32
|
calls: CallEdge[];
|
|
33
33
|
}
|
|
34
|
+
export interface AnalyzeProjectOpts {
|
|
35
|
+
skipDependencyResolution?: boolean;
|
|
36
|
+
/** Path to root tsconfig (e.g. tsconfig.base.json) for workspace-wide path aliases */
|
|
37
|
+
rootTsConfigPath?: string;
|
|
38
|
+
}
|
|
34
39
|
export interface IAstParser {
|
|
35
40
|
/**
|
|
36
41
|
* @deprecated Use analyzeProject instead for project-wide context.
|
|
37
42
|
*/
|
|
38
43
|
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>>;
|
|
44
|
+
analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?: AnalyzeProjectOpts): Promise<Map<string, FileAnalysis>>;
|
|
42
45
|
clearCache(projectRoot?: string): void;
|
|
43
46
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DrizzleDb, DrizzleTx } from '../services/DatabaseService';
|
|
2
|
+
/**
|
|
3
|
+
* Database abstraction — services depend on this interface
|
|
4
|
+
* rather than the concrete DatabaseService.
|
|
5
|
+
*/
|
|
6
|
+
export interface IDatabase {
|
|
7
|
+
readonly orm: DrizzleDb;
|
|
8
|
+
query<T = any>(sql: string, params?: any[]): Promise<T>;
|
|
9
|
+
transaction<T>(fn: (tx: DrizzleTx) => Promise<T>): Promise<T>;
|
|
10
|
+
initDb(): Promise<void>;
|
|
11
|
+
initDbWithSchema(): Promise<void>;
|
|
12
|
+
destroy(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a discovered project within a monorepo workspace.
|
|
3
|
+
*/
|
|
4
|
+
export interface MonorepoProject {
|
|
5
|
+
/** Project name (e.g. "my-app", "shared-utils") */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Absolute path to the project root */
|
|
8
|
+
path: string;
|
|
9
|
+
/** npm scope + package name (e.g. "@scope/my-app") — null if no package.json or no name */
|
|
10
|
+
packageName: string | null;
|
|
11
|
+
/** "app" | "lib" — derived from project.json or directory convention */
|
|
12
|
+
type: 'app' | 'lib';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Monorepo resolver — detects and resolves workspace structure.
|
|
16
|
+
* Implementations handle specific monorepo tooling (Nx, Turborepo, etc.)
|
|
17
|
+
*/
|
|
18
|
+
export interface IMonorepoResolver {
|
|
19
|
+
/**
|
|
20
|
+
* Detect whether the given root directory is a monorepo workspace.
|
|
21
|
+
*/
|
|
22
|
+
isMonorepo(rootDir: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Discover all projects (apps + libs) in the monorepo.
|
|
25
|
+
*/
|
|
26
|
+
discoverProjects(rootDir: string): MonorepoProject[];
|
|
27
|
+
/**
|
|
28
|
+
* Get the root tsconfig path that contains workspace-wide path aliases.
|
|
29
|
+
* For Nx this is typically tsconfig.base.json at the workspace root.
|
|
30
|
+
* The AST parser uses this to resolve cross-project imports (@scope/*).
|
|
31
|
+
*/
|
|
32
|
+
getRootTsConfigPath(rootDir: string): string | null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DiscoveredProject {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
/** npm scope + package name (e.g. "@scope/my-app") — only set for monorepo projects */
|
|
5
|
+
packageName?: string | null;
|
|
6
|
+
/** "app" | "lib" — only set for monorepo projects */
|
|
7
|
+
type?: 'app' | 'lib';
|
|
8
|
+
/**
|
|
9
|
+
* Path to the workspace-wide tsconfig (e.g. tsconfig.base.json) that holds
|
|
10
|
+
* cross-project path aliases. Set for monorepo projects; undefined for flat
|
|
11
|
+
* workspaces (where each project uses its own local tsconfig.json).
|
|
12
|
+
*/
|
|
13
|
+
rootTsConfigPath?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* When a top-level directory is itself a nested monorepo (e.g. an Nx repo
|
|
16
|
+
* under a flat workspaceRoot), this holds its expanded sub-projects. The
|
|
17
|
+
* repo stays a single selectable unit; `toLeafProjects` expands `children`
|
|
18
|
+
* into the actual projects to index. Absent/undefined for ordinary projects.
|
|
19
|
+
*/
|
|
20
|
+
children?: DiscoveredProject[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Project discovery abstraction — decouples project enumeration
|
|
24
|
+
* from the indexing service (Single Responsibility Principle).
|
|
25
|
+
*/
|
|
26
|
+
export interface IProjectDiscovery {
|
|
27
|
+
discover(rootDir: string): DiscoveredProject[];
|
|
28
|
+
}
|