project-graph-mcp 1.3.0 → 2.0.0

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 (113) hide show
  1. package/README.md +223 -17
  2. package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +87 -30
  3. package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +23 -8
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -0
  6. package/src/analysis/analysis-cache.js +7 -0
  7. package/src/analysis/complexity.js +14 -0
  8. package/src/analysis/custom-rules.js +36 -0
  9. package/src/analysis/db-analysis.js +9 -0
  10. package/src/analysis/dead-code.js +19 -0
  11. package/src/analysis/full-analysis.js +18 -0
  12. package/src/analysis/jsdoc-checker.js +24 -0
  13. package/src/analysis/jsdoc-generator.js +10 -0
  14. package/src/analysis/large-files.js +11 -0
  15. package/src/analysis/outdated-patterns.js +12 -0
  16. package/src/analysis/similar-functions.js +16 -0
  17. package/src/analysis/test-annotations.js +21 -0
  18. package/src/analysis/type-checker.js +8 -0
  19. package/src/analysis/undocumented.js +14 -0
  20. package/src/cli/cli-handlers.js +4 -0
  21. package/src/cli/cli.js +5 -0
  22. package/src/compact/ai-context.js +7 -0
  23. package/src/compact/compact.js +18 -0
  24. package/src/compact/compress.js +13 -0
  25. package/src/compact/ctx-to-jsdoc.js +29 -0
  26. package/src/compact/doc-dialect.js +30 -0
  27. package/src/compact/expand.js +37 -0
  28. package/src/compact/framework-references.js +5 -0
  29. package/src/compact/instructions.js +3 -0
  30. package/src/compact/mode-config.js +8 -0
  31. package/src/compact/validate-pipeline.js +9 -0
  32. package/src/core/event-bus.js +9 -0
  33. package/src/core/filters.js +14 -0
  34. package/src/core/graph-builder.js +12 -0
  35. package/src/core/parser.js +31 -0
  36. package/src/core/workspace.js +8 -0
  37. package/src/lang/lang-go.js +17 -0
  38. package/src/lang/lang-python.js +12 -0
  39. package/src/lang/lang-sql.js +23 -0
  40. package/src/lang/lang-typescript.js +9 -0
  41. package/src/lang/lang-utils.js +4 -0
  42. package/src/mcp/mcp-server.js +17 -0
  43. package/src/mcp/tool-defs.js +3 -0
  44. package/src/mcp/tools.js +25 -0
  45. package/src/network/backend-lifecycle.js +19 -0
  46. package/src/network/backend.js +5 -0
  47. package/src/network/local-gateway.js +23 -0
  48. package/src/network/mdns.js +13 -0
  49. package/src/network/server.js +10 -0
  50. package/src/network/web-server.js +34 -0
  51. package/vendor/terser.mjs +49 -0
  52. package/web/.project-graph-cache.json +1 -0
  53. package/web/app.js +16 -0
  54. package/web/components/code-block.js +3 -0
  55. package/web/components/quick-open.js +5 -0
  56. package/web/dashboard-state.js +3 -0
  57. package/web/dashboard.html +27 -0
  58. package/web/dashboard.js +8 -0
  59. package/web/highlight.js +13 -0
  60. package/web/index.html +35 -0
  61. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  62. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  63. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  64. package/web/panels/EventItem/EventItem.css.js +1 -0
  65. package/web/panels/EventItem/EventItem.js +4 -0
  66. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  67. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  69. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  70. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.js +4 -0
  72. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  73. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  74. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  77. package/web/panels/code-viewer.js +5 -0
  78. package/web/panels/ctx-panel.js +4 -0
  79. package/web/panels/dep-graph.js +6 -0
  80. package/web/panels/file-tree.js +188 -0
  81. package/web/panels/health-panel.js +3 -0
  82. package/web/panels/live-monitor.js +3 -0
  83. package/web/state.js +17 -0
  84. package/web/style.css +157 -0
  85. package/references/symbiote-3x.md +0 -834
  86. package/src/cli-handlers.js +0 -140
  87. package/src/cli.js +0 -83
  88. package/src/complexity.js +0 -223
  89. package/src/custom-rules.js +0 -583
  90. package/src/db-analysis.js +0 -194
  91. package/src/dead-code.js +0 -468
  92. package/src/filters.js +0 -227
  93. package/src/framework-references.js +0 -177
  94. package/src/full-analysis.js +0 -174
  95. package/src/graph-builder.js +0 -299
  96. package/src/instructions.js +0 -175
  97. package/src/jsdoc-generator.js +0 -214
  98. package/src/lang-go.js +0 -285
  99. package/src/lang-python.js +0 -197
  100. package/src/lang-sql.js +0 -309
  101. package/src/lang-typescript.js +0 -190
  102. package/src/lang-utils.js +0 -124
  103. package/src/large-files.js +0 -162
  104. package/src/mcp-server.js +0 -468
  105. package/src/outdated-patterns.js +0 -295
  106. package/src/parser.js +0 -452
  107. package/src/server.js +0 -28
  108. package/src/similar-functions.js +0 -278
  109. package/src/test-annotations.js +0 -301
  110. package/src/tool-defs.js +0 -525
  111. package/src/tools.js +0 -470
  112. package/src/undocumented.js +0 -260
  113. package/src/workspace.js +0 -70
package/src/tools.js DELETED
@@ -1,470 +0,0 @@
1
- /**
2
- * MCP Tools for Project Graph
3
- */
4
-
5
- import { parseProject, parseFile, findJSFiles } from './parser.js';
6
- import { buildGraph, createSkeleton } from './graph-builder.js';
7
- import { readFileSync, statSync, writeFileSync, existsSync, unlinkSync } from 'fs';
8
- import { execSync } from 'child_process';
9
- import { join } from 'path';
10
-
11
- /** @type {import('./graph-builder.js').Graph|null} */
12
- let cachedGraph = null;
13
-
14
- /** @type {string|null} */
15
- let cachedPath = null;
16
-
17
- /** @type {Map<string, number>} file path -> mtimeMs */
18
- let cachedMtimes = new Map();
19
-
20
- /**
21
- * Save cache to disk
22
- * @param {string} path
23
- * @param {import('./graph-builder.js').Graph} graph
24
- */
25
- function saveDiskCache(path, graph) {
26
- try {
27
- const cachePath = join(path, '.project-graph-cache.json');
28
- const cacheData = {
29
- version: 1,
30
- path: path,
31
- mtimes: Object.fromEntries(cachedMtimes),
32
- graph: graph
33
- };
34
- writeFileSync(cachePath, JSON.stringify(cacheData), 'utf-8');
35
- } catch (e) {
36
- // Ignore cache save errors
37
- }
38
- }
39
-
40
- /**
41
- * Load cache from disk
42
- * @param {string} path
43
- * @returns {boolean} true if cache was successfully loaded and is valid
44
- */
45
- function loadDiskCache(path) {
46
- try {
47
- const cachePath = join(path, '.project-graph-cache.json');
48
- if (!existsSync(cachePath)) return false;
49
-
50
- const content = readFileSync(cachePath, 'utf-8');
51
- const data = JSON.parse(content);
52
-
53
- if (data.version !== 1 || data.path !== path) return false;
54
-
55
- cachedMtimes.clear();
56
- for (const [file, mtime] of Object.entries(data.mtimes)) {
57
- cachedMtimes.set(file, mtime);
58
- }
59
-
60
- cachedGraph = data.graph;
61
- cachedPath = path;
62
-
63
- const changed = detectChanges(path);
64
- if (changed) {
65
- cachedGraph = null;
66
- cachedPath = null;
67
- cachedMtimes.clear();
68
- return false;
69
- }
70
-
71
- return true;
72
- } catch (e) {
73
- return false;
74
- }
75
- }
76
-
77
- /**
78
- * Get or build graph with smart mtime-based caching.
79
- * On first call: full parse + build.
80
- * On subsequent calls: check file mtimes, rebuild only if changes detected.
81
- * @param {string} path
82
- * @returns {Promise<import('./graph-builder.js').Graph>}
83
- */
84
- async function getGraph(path) {
85
- // Different path = full rebuild
86
- if (cachedGraph && cachedPath === path) {
87
- // Check for file changes via mtime
88
- const changed = detectChanges(path);
89
- if (!changed) {
90
- return cachedGraph;
91
- }
92
- // Files changed - full rebuild (incremental would need graph-builder changes)
93
- } else if (!cachedGraph) {
94
- if (loadDiskCache(path)) {
95
- return cachedGraph;
96
- }
97
- }
98
-
99
- const parsed = await parseProject(path);
100
- cachedGraph = buildGraph(parsed);
101
- cachedPath = path;
102
-
103
- // Snapshot mtimes for all parsed files
104
- snapshotMtimes(path);
105
- saveDiskCache(path, cachedGraph);
106
-
107
- return cachedGraph;
108
- }
109
-
110
- /**
111
- * Detect if any JS files changed since last snapshot.
112
- * Checks: new files, deleted files, modified files (via mtimeMs).
113
- * @param {string} path
114
- * @returns {boolean} true if changes detected
115
- */
116
- function detectChanges(path) {
117
- if (cachedMtimes.size === 0) return true;
118
-
119
- try {
120
- const currentFiles = findJSFiles(path);
121
- const currentSet = new Set(currentFiles);
122
- const cachedSet = new Set(cachedMtimes.keys());
123
-
124
- // New or deleted files
125
- if (currentFiles.length !== cachedMtimes.size) return true;
126
- for (const f of currentFiles) {
127
- if (!cachedSet.has(f)) return true;
128
- }
129
- for (const f of cachedSet) {
130
- if (!currentSet.has(f)) return true;
131
- }
132
-
133
- // Check mtimes
134
- for (const file of currentFiles) {
135
- try {
136
- const mtime = statSync(file).mtimeMs;
137
- if (mtime !== cachedMtimes.get(file)) return true;
138
- } catch {
139
- return true; // File gone or unreadable
140
- }
141
- }
142
-
143
- return false;
144
- } catch {
145
- return true; // Safety: rebuild on error
146
- }
147
- }
148
-
149
- /**
150
- * Snapshot current mtimes for all JS files in path.
151
- * @param {string} path
152
- */
153
- function snapshotMtimes(path) {
154
- cachedMtimes.clear();
155
- try {
156
- const files = findJSFiles(path);
157
- for (const file of files) {
158
- try {
159
- cachedMtimes.set(file, statSync(file).mtimeMs);
160
- } catch {
161
- // Skip unreadable files
162
- }
163
- }
164
- } catch {
165
- // Ignore errors
166
- }
167
- }
168
-
169
- /**
170
- * Get compact project skeleton
171
- * @param {string} path
172
- * @returns {Promise<Object>}
173
- */
174
- export async function getSkeleton(path) {
175
- const graph = await getGraph(path);
176
- return createSkeleton(graph);
177
- }
178
-
179
- /**
180
- * Get enriched focus zone based on recent activity
181
- * @param {Object} options
182
- * @param {string[]} [options.recentFiles]
183
- * @param {boolean} [options.useGitDiff]
184
- * @returns {Promise<Object>}
185
- */
186
- export async function getFocusZone(options = {}) {
187
- const path = options.path || 'src/components';
188
- const graph = await getGraph(path);
189
-
190
- let focusFiles = options.recentFiles || [];
191
-
192
- // Auto-detect from git diff
193
- if (options.useGitDiff) {
194
- try {
195
- const diff = execSync('git diff --name-only HEAD~5', { encoding: 'utf-8' });
196
- focusFiles = diff.split('\n').filter(f => f.endsWith('.js'));
197
- } catch (e) {
198
- // Git not available or not a repo
199
- }
200
- }
201
-
202
- const expanded = {};
203
-
204
- for (const file of focusFiles) {
205
- // Find classes in this file
206
- const content = readFileSync(file, 'utf-8');
207
- const parsed = await parseFile(content, file);
208
-
209
- for (const cls of parsed.classes) {
210
- const shortName = graph.legend[cls.name];
211
- if (shortName && graph.nodes[shortName]) {
212
- expanded[shortName] = {
213
- ...graph.nodes[shortName],
214
- methods: cls.methods,
215
- properties: cls.properties,
216
- file: cls.file,
217
- line: cls.line,
218
- };
219
- }
220
- }
221
- }
222
-
223
- return {
224
- focusFiles,
225
- expanded,
226
- expandable: Object.keys(graph.nodes).filter(k => !expanded[k]),
227
- };
228
- }
229
-
230
- /**
231
- * Expand a symbol to full details
232
- * @param {string} symbol - Minified symbol like 'SN' or 'SN.tP'
233
- * @returns {Promise<Object>}
234
- */
235
- export async function expand(symbol) {
236
- const path = cachedPath || 'src/components';
237
- const graph = await getGraph(path);
238
-
239
- const [nodeKey, methodKey] = symbol.split('.');
240
- const fullName = graph.reverseLegend[nodeKey];
241
-
242
- if (!fullName) {
243
- return { error: `Unknown symbol: ${symbol}. Run get_skeleton on your project first, then use symbols from the L (Legend) field.` };
244
- }
245
-
246
- // Find the source file
247
- const parsed = await parseProject(path);
248
- const cls = parsed.classes.find(c => c.name === fullName);
249
- const fn = parsed.functions.find(f => f.name === fullName);
250
-
251
- if (!cls && !fn) {
252
- return { error: `Symbol not found: ${fullName}` };
253
- }
254
-
255
- if (fn && !methodKey) {
256
- return {
257
- symbol,
258
- fullName,
259
- type: 'function',
260
- file: fn.file,
261
- line: fn.line,
262
- exported: fn.exported,
263
- calls: fn.calls,
264
- };
265
- }
266
-
267
- // If method specified, extract method code
268
- if (methodKey && cls) {
269
- const methodName = graph.reverseLegend[methodKey] || methodKey;
270
- const content = readFileSync(cls.file, 'utf-8');
271
- const methodCode = extractMethod(content, methodName);
272
-
273
- return {
274
- symbol,
275
- fullName: `${fullName}.${methodName}`,
276
- file: cls.file,
277
- line: cls.line, // TODO: get actual method line
278
- code: methodCode,
279
- };
280
- }
281
-
282
- // Return full class info
283
- return {
284
- symbol,
285
- fullName,
286
- file: cls.file,
287
- line: cls.line,
288
- extends: cls.extends,
289
- methods: cls.methods,
290
- properties: cls.properties,
291
- calls: cls.calls,
292
- };
293
- }
294
-
295
- /**
296
- * Get dependency tree for a symbol
297
- * @param {string} symbol
298
- * @returns {Promise<Object>}
299
- */
300
- export async function deps(symbol) {
301
- const path = cachedPath || 'src/components';
302
- const graph = await getGraph(path);
303
-
304
- const node = graph.nodes[symbol];
305
- if (!node) {
306
- return { error: `Unknown symbol: ${symbol}. Run get_skeleton on your project first, then use symbols from the L (Legend) field.` };
307
- }
308
-
309
- // Find incoming edges (usedBy)
310
- const usedBy = graph.edges
311
- .filter(e => e[2].startsWith(symbol))
312
- .map(e => e[0]);
313
-
314
- // Find outgoing edges (calls)
315
- const calls = graph.edges
316
- .filter(e => e[0] === symbol)
317
- .map(e => e[2]);
318
-
319
- return {
320
- symbol,
321
- imports: node.i || [],
322
- usedBy: [...new Set(usedBy)],
323
- calls: [...new Set(calls)],
324
- };
325
- }
326
-
327
- /**
328
- * Find all usages of a symbol
329
- * @param {string} symbol
330
- * @returns {Promise<Array>}
331
- */
332
- export async function usages(symbol) {
333
- const path = cachedPath || 'src/components';
334
- const graph = await getGraph(path);
335
- const parsed = await parseProject(path);
336
-
337
- const fullName = graph.reverseLegend[symbol] || symbol;
338
- const results = [];
339
-
340
- for (const cls of parsed.classes) {
341
- if (cls.calls?.includes(fullName) || cls.calls?.some(c => c.includes(fullName))) {
342
- results.push({
343
- file: cls.file,
344
- line: cls.line,
345
- context: `${cls.name} calls ${fullName}`,
346
- });
347
- }
348
- }
349
-
350
- return results;
351
- }
352
-
353
- /**
354
- * Extract method code from file content
355
- * @param {string} content
356
- * @param {string} methodName
357
- * @returns {string}
358
- */
359
- function extractMethod(content, methodName) {
360
- const regex = new RegExp(`((?:\\/\\*\\*[\\s\\S]*?\\*\\/\\s*)?)(?:async\\s+)?${methodName}\\s*\\([^)]*\\)\\s*{`, 'g');
361
- const match = regex.exec(content);
362
-
363
- if (!match) return '';
364
-
365
- const start = match.index;
366
- let depth = 0;
367
- let i = match.index + match[0].length - 1;
368
-
369
- while (i < content.length) {
370
- if (content[i] === '{') depth++;
371
- else if (content[i] === '}') {
372
- depth--;
373
- if (depth === 0) {
374
- return content.slice(start, i + 1);
375
- }
376
- }
377
- i++;
378
- }
379
-
380
- return content.slice(start);
381
- }
382
-
383
- /**
384
- * Find call chain from one symbol to another
385
- * @param {Object} options
386
- * @param {string} options.from - Starting symbol (full or minified)
387
- * @param {string} options.to - Target symbol (full or minified)
388
- * @param {string} [options.path] - Project path
389
- * @returns {Promise<string[]|Object>}
390
- */
391
- export async function getCallChain(options = {}) {
392
- const { from, to, path } = options;
393
- if (!from || !to) {
394
- return { error: 'Both "from" and "to" parameters are required' };
395
- }
396
-
397
- const projectPath = path || cachedPath || 'src/components';
398
- const graph = await getGraph(projectPath);
399
-
400
- const fromSym = graph.legend[from] || from;
401
- const toSym = graph.legend[to] || to;
402
-
403
- // Build adjacency list for fast lookup
404
- const adj = {};
405
- for (const [caller, _, target] of graph.edges) {
406
- if (!adj[caller]) adj[caller] = [];
407
- adj[caller].push(target);
408
- }
409
-
410
- // Queue stores { current: string, path: string[] }
411
- const queue = [{ current: fromSym, path: [fromSym] }];
412
- const visitedNodes = new Set();
413
- const expandedBases = new Set();
414
- visitedNodes.add(fromSym);
415
-
416
- while (queue.length > 0) {
417
- const { current, path: currentPath } = queue.shift();
418
-
419
- const currentBase = current.split('.')[0];
420
- const currentMethod = current.split('.')[1];
421
-
422
- if (current === toSym || currentBase === toSym || currentMethod === toSym) {
423
- const fullPath = currentPath.map(sym => {
424
- const parts = sym.split('.');
425
- const base = graph.reverseLegend[parts[0]] || parts[0];
426
- if (parts.length === 2) {
427
- const method = graph.reverseLegend[parts[1]] || parts[1];
428
- return `${base}.${method}`;
429
- }
430
- return base;
431
- });
432
- return fullPath;
433
- }
434
-
435
- if (expandedBases.has(currentBase)) {
436
- continue;
437
- }
438
- expandedBases.add(currentBase);
439
-
440
- const neighbors = adj[currentBase] || [];
441
- for (const neighbor of neighbors) {
442
- if (!visitedNodes.has(neighbor)) {
443
- visitedNodes.add(neighbor);
444
- queue.push({
445
- current: neighbor,
446
- path: [...currentPath, neighbor]
447
- });
448
- }
449
- }
450
- }
451
-
452
- return { error: `No call path found from "${from}" to "${to}"` };
453
- }
454
-
455
- /**
456
- * Invalidate cache
457
- */
458
- export function invalidateCache() {
459
- if (cachedPath) {
460
- try {
461
- const cachePath = join(cachedPath, '.project-graph-cache.json');
462
- if (existsSync(cachePath)) {
463
- unlinkSync(cachePath);
464
- }
465
- } catch (e) {}
466
- }
467
- cachedGraph = null;
468
- cachedPath = null;
469
- cachedMtimes.clear();
470
- }