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.
Files changed (83) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +20 -0
  3. package/dist/app.js +34 -10
  4. package/dist/src/commands/ConfigCommand.d.ts +1 -0
  5. package/dist/src/commands/ConfigCommand.js +84 -4
  6. package/dist/src/commands/IndexCommand.js +22 -7
  7. package/dist/src/commands/McpCommand.js +4 -3
  8. package/dist/src/commands/WebCommand.d.ts +8 -0
  9. package/dist/src/commands/WebCommand.js +56 -0
  10. package/dist/src/config-store.d.ts +43 -0
  11. package/dist/src/config-store.js +92 -27
  12. package/dist/src/config.js +16 -17
  13. package/dist/src/constants.d.ts +2 -1
  14. package/dist/src/constants.js +6 -4
  15. package/dist/src/container.d.ts +10 -6
  16. package/dist/src/container.js +6 -3
  17. package/dist/src/graph.d.ts +60 -0
  18. package/dist/src/graph.js +239 -120
  19. package/dist/src/help.js +15 -1
  20. package/dist/src/interfaces/IAstParser.d.ts +6 -3
  21. package/dist/src/interfaces/IDatabase.d.ts +13 -0
  22. package/dist/src/interfaces/IDatabase.js +2 -0
  23. package/dist/src/interfaces/IEmbedder.d.ts +1 -0
  24. package/dist/src/interfaces/ILogger.d.ts +10 -0
  25. package/dist/src/interfaces/ILogger.js +2 -0
  26. package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
  27. package/dist/src/interfaces/IMcpToolHandler.js +2 -0
  28. package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
  29. package/dist/src/interfaces/IMonorepoResolver.js +2 -0
  30. package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
  31. package/dist/src/interfaces/IProjectDiscovery.js +2 -0
  32. package/dist/src/interfaces/ISearcher.d.ts +36 -0
  33. package/dist/src/interfaces/ISearcher.js +2 -0
  34. package/dist/src/interfaces/index.d.ts +7 -0
  35. package/dist/src/interfaces/index.js +2 -0
  36. package/dist/src/project-selector.d.ts +1 -1
  37. package/dist/src/services/AstParserService.d.ts +4 -6
  38. package/dist/src/services/AstParserService.js +29 -13
  39. package/dist/src/services/DatabaseService.d.ts +2 -1
  40. package/dist/src/services/IndexerService.d.ts +6 -8
  41. package/dist/src/services/IndexerService.js +5 -8
  42. package/dist/src/services/LoggerService.d.ts +2 -1
  43. package/dist/src/services/McpServer.d.ts +16 -7
  44. package/dist/src/services/McpServer.js +54 -367
  45. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
  46. package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
  47. package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
  48. package/dist/src/services/NxMonorepoResolver.js +308 -0
  49. package/dist/src/services/OllamaEmbedder.d.ts +5 -2
  50. package/dist/src/services/OllamaEmbedder.js +9 -2
  51. package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
  52. package/dist/src/services/ProjectDiscoveryService.js +52 -0
  53. package/dist/src/services/SearcherService.d.ts +39 -6
  54. package/dist/src/services/SearcherService.js +151 -40
  55. package/dist/src/services/WebServerService.d.ts +72 -0
  56. package/dist/src/services/WebServerService.js +247 -0
  57. package/dist/src/setup.js +7 -7
  58. package/dist/src/tools/FindSymbolTool.d.ts +8 -0
  59. package/dist/src/tools/FindSymbolTool.js +45 -0
  60. package/dist/src/tools/GetCallersTool.d.ts +8 -0
  61. package/dist/src/tools/GetCallersTool.js +42 -0
  62. package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
  63. package/dist/src/tools/GetDependenciesTool.js +38 -0
  64. package/dist/src/tools/GetDependentsTool.d.ts +8 -0
  65. package/dist/src/tools/GetDependentsTool.js +57 -0
  66. package/dist/src/tools/ListFilesTool.d.ts +8 -0
  67. package/dist/src/tools/ListFilesTool.js +39 -0
  68. package/dist/src/tools/ListProjectsTool.d.ts +8 -0
  69. package/dist/src/tools/ListProjectsTool.js +44 -0
  70. package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
  71. package/dist/src/tools/ReadFileRangeTool.js +44 -0
  72. package/dist/src/tools/ReadFileTool.d.ts +8 -0
  73. package/dist/src/tools/ReadFileTool.js +42 -0
  74. package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
  75. package/dist/src/tools/SearchCodebaseTool.js +81 -0
  76. package/dist/src/tools/format-helpers.d.ts +8 -0
  77. package/dist/src/tools/format-helpers.js +57 -0
  78. package/dist/src/tools/index.d.ts +9 -0
  79. package/dist/src/tools/index.js +21 -0
  80. package/dist/src/web/dashboard.d.ts +9 -0
  81. package/dist/src/web/dashboard.js +337 -0
  82. package/docs/web-dashboard.md +187 -0
  83. 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 escHtml(s) {
54
- return s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
55
- }
56
- function printConsole(nodes, edges, workspacePath) {
57
- if (edges.length === 0)
58
- return;
59
- console.log('\n=== Project Relations ===');
60
- const grouped = new Map();
61
- for (const e of edges) {
62
- if (!grouped.has(e.from))
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 [label, labelEdges] of byLabel) {
74
- const targets = labelEdges.map(e => e.to).join(', ');
75
- console.log(` ${from.padEnd(30)} → ${targets.padEnd(30)} [${label}]`);
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
- console.log(`\n Full graph: file://${path.join(workspacePath, 'project-graph.html')}`);
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
- function buildHtml(nodes, edges) {
81
- const visNodes = nodes.map(n => ({
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
- if (!known.has(e.to)) {
90
- visNodes.push({ id: e.to, label: e.to, title: e.to, color: '#E8A838' });
91
- known.add(e.to);
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 visEdges = edges.map((e, i) => ({ id: i, from: e.from, to: e.to, label: e.label, arrows: 'to' }));
95
- const allLabels = [...new Set(edges.map(e => e.label))].sort();
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
- #header p { font-size: 12px; color: #888; }
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>${nodes.length} projects · ${edges.length} edges · ${new Date().toLocaleString()}</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:#5a5a7a"></span>no .ragmeta.json</div>
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
- ${visNodes.map(n => `<button class="proj-btn" data-node="${escHtml(n.id)}"><span class="proj-dot" style="background:${escHtml(n.color)}"></span>${escHtml(n.label)}</button>`).join('\n ')}
244
+ <div id="proj-list-body"></div>
153
245
  </div>
154
246
  </div>
155
247
  </div>
156
248
  <script>
157
- const allEdges = ${JSON.stringify(visEdges)};
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 network = new vis.Network(
162
- document.getElementById('network'),
163
- { nodes: nodesDs, edges: edgesDs },
164
- {
165
- nodes: {
166
- shape: 'box',
167
- font: { color: '#fff', size: 13 },
168
- borderWidth: 2,
169
- shadow: true,
170
- margin: { top: 8, bottom: 8, left: 12, right: 12 },
171
- },
172
- edges: {
173
- font: { color: '#aaa', size: 11, align: 'middle', strokeWidth: 0, background: '#1a1a2e' },
174
- color: { color: '#4A90D9', highlight: '#7eb8f7', hover: '#7eb8f7' },
175
- smooth: { type: 'curvedCW', roundness: 0.2 },
176
- arrows: { to: { scaleFactor: 0.6 } },
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
- const activeLabels = new Set(${JSON.stringify(allLabels)});
206
- const hiddenNodes = new Set();
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
- document.querySelectorAll('.filter-btn').forEach(btn => {
217
- btn.addEventListener('click', () => {
218
- const label = btn.dataset.label;
219
- if (activeLabels.has(label)) { activeLabels.delete(label); btn.classList.add('off'); }
220
- else { activeLabels.add(label); btn.classList.remove('off'); }
221
- refreshEdges();
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
- document.querySelectorAll('.proj-btn').forEach(btn => {
226
- btn.addEventListener('click', () => {
227
- const id = btn.dataset.node;
228
- if (hiddenNodes.has(id)) {
229
- hiddenNodes.delete(id);
230
- nodesDs.update({ id, hidden: false });
231
- btn.classList.remove('off');
232
- } else {
233
- hiddenNodes.add(id);
234
- nodesDs.update({ id, hidden: true });
235
- btn.classList.add('off');
236
- }
237
- refreshEdges();
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 — reads meta once, prints console summary and writes HTML
245
- function generateProjectGraph(projects, workspacePath) {
246
- const EMPTY = { description: null, stack: null, notes: null, relations: null };
247
- const nodes = projects.map(p => {
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(nodes, edges), 'utf-8');
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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -2,4 +2,5 @@ export interface IEmbedder {
2
2
  embed(text: string): Promise<number[]>;
3
3
  translate?(text: string, model: string): Promise<string>;
4
4
  rerank?(query: string, documents: string[], topN: number): Promise<number[]>;
5
+ listModels?(): Promise<string[]>;
5
6
  }
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });