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/parser.js DELETED
@@ -1,452 +0,0 @@
1
- /**
2
- * AST Parser for JavaScript files using Acorn
3
- * Extracts classes, functions, methods, properties, imports, calls, and SQL queries
4
- */
5
-
6
- import { readFileSync, readdirSync, statSync } from 'fs';
7
- import { join, relative, resolve } from 'path';
8
- import { parse } from '../vendor/acorn.mjs';
9
- import * as walk from '../vendor/walk.mjs';
10
- import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
11
- import { parseTypeScript } from './lang-typescript.js';
12
- import { parsePython } from './lang-python.js';
13
- import { parseGo } from './lang-go.js';
14
- import { parseSQL, extractSQLFromString, isSQLString } from './lang-sql.js';
15
-
16
- /** Supported source file extensions */
17
- const SOURCE_EXTENSIONS = ['.js', '.ts', '.tsx', '.py', '.go', '.sql'];
18
-
19
- /**
20
- * @typedef {Object} ClassInfo
21
- * @property {string} name
22
- * @property {string} [extends]
23
- * @property {string[]} methods
24
- * @property {string[]} properties
25
- * @property {string[]} calls
26
- * @property {string[]} [dbReads] - Tables read by SQL queries
27
- * @property {string[]} [dbWrites] - Tables written by SQL queries
28
- * @property {string} file
29
- * @property {number} line
30
- */
31
-
32
- /**
33
- * @typedef {Object} FunctionInfo
34
- * @property {string} name
35
- * @property {boolean} exported
36
- * @property {string[]} calls
37
- * @property {string[]} [dbReads] - Tables read by SQL queries
38
- * @property {string[]} [dbWrites] - Tables written by SQL queries
39
- * @property {string} file
40
- * @property {number} line
41
- */
42
-
43
- /**
44
- * @typedef {Object} ParseResult
45
- * @property {string[]} files
46
- * @property {ClassInfo[]} classes
47
- * @property {FunctionInfo[]} functions
48
- * @property {string[]} imports
49
- * @property {string[]} exports
50
- */
51
-
52
- /**
53
- * Parse a JavaScript file content using AST
54
- * @param {string} code
55
- * @param {string} filename
56
- * @returns {Promise<ParseResult>}
57
- */
58
- export async function parseFile(code, filename) {
59
- const result = {
60
- file: filename,
61
- classes: [],
62
- functions: [],
63
- imports: [],
64
- exports: [],
65
- };
66
-
67
- let ast;
68
- try {
69
- ast = parse(code, {
70
- ecmaVersion: 'latest',
71
- sourceType: 'module',
72
- locations: true,
73
- });
74
- } catch (e) {
75
- // If parsing fails, return empty result
76
- console.warn(`Parse error in ${filename}:`, e.message);
77
- return result;
78
- }
79
-
80
- // Track exported names
81
- const exportedNames = new Set();
82
-
83
- // Walk the AST
84
- walk.simple(ast, {
85
- // Import declarations
86
- ImportDeclaration(node) {
87
- for (const spec of node.specifiers) {
88
- if (spec.type === 'ImportDefaultSpecifier') {
89
- result.imports.push(spec.local.name);
90
- } else if (spec.type === 'ImportSpecifier') {
91
- result.imports.push(spec.imported.name);
92
- }
93
- }
94
- },
95
-
96
- // Export declarations
97
- ExportNamedDeclaration(node) {
98
- if (node.declaration) {
99
- if (node.declaration.id) {
100
- exportedNames.add(node.declaration.id.name);
101
- } else if (node.declaration.declarations) {
102
- for (const decl of node.declaration.declarations) {
103
- exportedNames.add(decl.id.name);
104
- }
105
- }
106
- }
107
- if (node.specifiers) {
108
- for (const spec of node.specifiers) {
109
- exportedNames.add(spec.exported.name);
110
- }
111
- }
112
- },
113
-
114
- ExportDefaultDeclaration(node) {
115
- if (node.declaration && node.declaration.id) {
116
- exportedNames.add(node.declaration.id.name);
117
- }
118
- },
119
-
120
- // Class declarations
121
- ClassDeclaration(node) {
122
- const classInfo = {
123
- name: node.id.name,
124
- extends: node.superClass ? node.superClass.name : null,
125
- methods: [],
126
- properties: [],
127
- calls: [],
128
- dbReads: [],
129
- dbWrites: [],
130
- file: filename,
131
- line: node.loc.start.line,
132
- };
133
-
134
- // Extract methods and properties from class body
135
- for (const element of node.body.body) {
136
- if (element.type === 'MethodDefinition' && element.key.name !== 'constructor') {
137
- classInfo.methods.push(element.key.name);
138
-
139
- // Extract calls and SQL from method body
140
- extractCallsAndSQL(element.value.body, classInfo.calls, classInfo.dbReads, classInfo.dbWrites);
141
- } else if (element.type === 'PropertyDefinition') {
142
- const propName = element.key.name;
143
-
144
- // Check for init$ object properties
145
- if (propName === 'init$' && element.value && element.value.type === 'ObjectExpression') {
146
- for (const prop of element.value.properties) {
147
- if (prop.key && prop.key.name) {
148
- classInfo.properties.push(prop.key.name);
149
- }
150
- }
151
- }
152
- }
153
- }
154
-
155
- result.classes.push(classInfo);
156
- },
157
-
158
- // Standalone function declarations
159
- FunctionDeclaration(node) {
160
- if (node.id) {
161
- const funcInfo = {
162
- name: node.id.name,
163
- exported: false, // Will be updated later
164
- calls: [],
165
- dbReads: [],
166
- dbWrites: [],
167
- file: filename,
168
- line: node.loc.start.line,
169
- };
170
-
171
- extractCallsAndSQL(node.body, funcInfo.calls, funcInfo.dbReads, funcInfo.dbWrites);
172
- result.functions.push(funcInfo);
173
- }
174
- },
175
- });
176
-
177
- // Mark exported functions
178
- for (const func of result.functions) {
179
- func.exported = exportedNames.has(func.name);
180
- }
181
-
182
- // Collect exports
183
- result.exports = [...exportedNames];
184
-
185
- return result;
186
- }
187
-
188
- /** DB client method names that accept SQL as first argument */
189
- const DB_METHODS = new Set(['query', 'execute', 'raw', 'exec', 'queryFile', 'none', 'one', 'many', 'any', 'oneOrNone', 'manyOrNone', 'result']);
190
-
191
- /**
192
- * Extract method calls AND SQL queries from AST node in a single walk.
193
- * Combines what was previously two separate walk.simple() calls.
194
- * @param {Object} node
195
- * @param {string[]} calls
196
- * @param {string[]} [dbReads]
197
- * @param {string[]} [dbWrites]
198
- */
199
- function extractCallsAndSQL(node, calls, dbReads, dbWrites) {
200
- if (!node) return;
201
-
202
- walk.simple(node, {
203
- CallExpression(callNode) {
204
- const callee = callNode.callee;
205
-
206
- // === Call extraction ===
207
- if (callee.type === 'MemberExpression') {
208
- const object = callee.object;
209
- const property = callee.property;
210
-
211
- if (property.type === 'Identifier') {
212
- if (object.type === 'Identifier') {
213
- const call = `${object.name}.${property.name}`;
214
- if (!calls.includes(call)) calls.push(call);
215
- } else if (object.type === 'MemberExpression' && object.property.type === 'Identifier') {
216
- const call = `${object.property.name}.${property.name}`;
217
- if (!calls.includes(call)) calls.push(call);
218
- } else if (object.type === 'ThisExpression') {
219
- const call = property.name;
220
- if (!calls.includes(call)) calls.push(call);
221
- }
222
- }
223
- } else if (callee.type === 'Identifier') {
224
- const call = callee.name;
225
- if (!calls.includes(call)) calls.push(call);
226
- }
227
-
228
- // === SQL extraction from DB client calls ===
229
- if (dbReads && dbWrites) {
230
- const methodName = getCallMethodName(callNode);
231
- if (methodName && DB_METHODS.has(methodName) && callNode.arguments.length > 0) {
232
- const sqlStr = extractStringValue(callNode.arguments[0]);
233
- if (sqlStr && isSQLString(sqlStr)) {
234
- const ext = extractSQLFromString(sqlStr);
235
- ext.reads.forEach(t => { if (!dbReads.includes(t)) dbReads.push(t); });
236
- ext.writes.forEach(t => { if (!dbWrites.includes(t)) dbWrites.push(t); });
237
- }
238
- }
239
- }
240
- },
241
-
242
- // === SQL: Tagged templates ===
243
- TaggedTemplateExpression(tagNode) {
244
- if (!dbReads || !dbWrites) return;
245
- const tagName = getTagName(tagNode.tag);
246
- if (tagName && /sql/i.test(tagName)) {
247
- const sqlStr = templateToString(tagNode.quasi);
248
- if (sqlStr) {
249
- const ext = extractSQLFromString(sqlStr);
250
- ext.reads.forEach(t => { if (!dbReads.includes(t)) dbReads.push(t); });
251
- ext.writes.forEach(t => { if (!dbWrites.includes(t)) dbWrites.push(t); });
252
- }
253
- }
254
- },
255
-
256
- // === SQL: Standalone template literals ===
257
- TemplateLiteral(tplNode) {
258
- if (!dbReads || !dbWrites) return;
259
- const sqlStr = templateToString(tplNode);
260
- if (sqlStr && isSQLString(sqlStr)) {
261
- const ext = extractSQLFromString(sqlStr);
262
- ext.reads.forEach(t => { if (!dbReads.includes(t)) dbReads.push(t); });
263
- ext.writes.forEach(t => { if (!dbWrites.includes(t)) dbWrites.push(t); });
264
- }
265
- },
266
-
267
- // === SQL: String literals ===
268
- Literal(litNode) {
269
- if (!dbReads || !dbWrites) return;
270
- if (typeof litNode.value === 'string' && isSQLString(litNode.value)) {
271
- const ext = extractSQLFromString(litNode.value);
272
- ext.reads.forEach(t => { if (!dbReads.includes(t)) dbReads.push(t); });
273
- ext.writes.forEach(t => { if (!dbWrites.includes(t)) dbWrites.push(t); });
274
- }
275
- },
276
- });
277
- }
278
-
279
- /**
280
- * Get tag name from tagged template expression.
281
- * Handles: sql`...`, Prisma.sql`...`, db.sql`...`
282
- * @param {Object} tag - AST node
283
- * @returns {string|null}
284
- */
285
- function getTagName(tag) {
286
- if (tag.type === 'Identifier') return tag.name;
287
- if (tag.type === 'MemberExpression' && tag.property.type === 'Identifier') {
288
- return tag.property.name;
289
- }
290
- return null;
291
- }
292
-
293
- /**
294
- * Get method name from a CallExpression callee.
295
- * @param {Object} callNode
296
- * @returns {string|null}
297
- */
298
- function getCallMethodName(callNode) {
299
- const callee = callNode.callee;
300
- if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
301
- return callee.property.name;
302
- }
303
- return null;
304
- }
305
-
306
- /**
307
- * Extract string value from AST node (Literal or TemplateLiteral).
308
- * For templates with expressions, substitutes $N placeholders.
309
- * @param {Object} node
310
- * @returns {string|null}
311
- */
312
- function extractStringValue(node) {
313
- if (!node) return null;
314
- if (node.type === 'Literal' && typeof node.value === 'string') {
315
- return node.value;
316
- }
317
- if (node.type === 'TemplateLiteral') {
318
- return templateToString(node);
319
- }
320
- return null;
321
- }
322
-
323
- /**
324
- * Convert TemplateLiteral AST node to string.
325
- * Expressions are replaced with $N placeholders.
326
- * @param {Object} tplNode
327
- * @returns {string}
328
- */
329
- function templateToString(tplNode) {
330
- if (!tplNode || !tplNode.quasis) return '';
331
- let result = '';
332
- for (let i = 0; i < tplNode.quasis.length; i++) {
333
- result += tplNode.quasis[i].value.cooked || tplNode.quasis[i].value.raw || '';
334
- if (i < tplNode.expressions?.length) {
335
- result += '$' + (i + 1);
336
- }
337
- }
338
- return result;
339
- }
340
-
341
- /**
342
- * Parse all JS files in a directory
343
- * @param {string} dir
344
- * @returns {Promise<ParseResult>}
345
- */
346
- export async function parseProject(dir) {
347
- const result = {
348
- files: [],
349
- classes: [],
350
- functions: [],
351
- imports: [],
352
- exports: [],
353
- tables: [],
354
- };
355
-
356
- const resolvedDir = resolve(dir);
357
- const files = findJSFiles(dir);
358
-
359
- for (const file of files) {
360
- const content = readFileSync(file, 'utf-8');
361
- const relPath = relative(resolvedDir, file);
362
- const parsed = await parseFileByExtension(content, relPath);
363
-
364
- result.files.push(relPath);
365
- result.classes.push(...parsed.classes);
366
- result.functions.push(...parsed.functions);
367
- result.imports.push(...parsed.imports);
368
- result.exports.push(...parsed.exports);
369
- if (parsed.tables?.length) {
370
- result.tables.push(...parsed.tables);
371
- }
372
- }
373
-
374
- // Dedupe imports/exports
375
- result.imports = [...new Set(result.imports)];
376
- result.exports = [...new Set(result.exports)];
377
-
378
- return result;
379
- }
380
-
381
- /**
382
- * Route file to appropriate parser based on extension.
383
- * @param {string} code
384
- * @param {string} filename
385
- * @returns {Promise<ParseResult>}
386
- */
387
- async function parseFileByExtension(code, filename) {
388
- if (filename.endsWith('.sql')) {
389
- return parseSQL(code, filename);
390
- }
391
- if (filename.endsWith('.py')) {
392
- return parsePython(code, filename);
393
- }
394
- if (filename.endsWith('.go')) {
395
- return parseGo(code, filename);
396
- }
397
- if (filename.endsWith('.ts') || filename.endsWith('.tsx')) {
398
- return parseTypeScript(code, filename);
399
- }
400
- // Default: JS via Acorn
401
- return parseFile(code, filename);
402
- }
403
-
404
- /**
405
- * Check if file is a supported source file.
406
- * @param {string} filename
407
- * @returns {boolean}
408
- */
409
- function isSourceFile(filename) {
410
- // Exclude Symbiote.js presentation files
411
- if (filename.endsWith('.css.js') || filename.endsWith('.tpl.js')) {
412
- return false;
413
- }
414
- return SOURCE_EXTENSIONS.some(ext => filename.endsWith(ext));
415
- }
416
-
417
- /**
418
- * Find all JS files recursively (uses filter configuration)
419
- * @param {string} dir
420
- * @param {string} [rootDir] - Root directory for relative path calculation
421
- * @returns {string[]}
422
- */
423
- export function findJSFiles(dir, rootDir = dir) {
424
- // Parse gitignore on first call
425
- if (dir === rootDir) {
426
- parseGitignore(rootDir);
427
- }
428
-
429
- const files = [];
430
-
431
- try {
432
- for (const entry of readdirSync(dir)) {
433
- const fullPath = join(dir, entry);
434
- const stat = statSync(fullPath);
435
- const relativePath = relative(rootDir, dir);
436
-
437
- if (stat.isDirectory()) {
438
- if (!shouldExcludeDir(entry, relativePath)) {
439
- files.push(...findJSFiles(fullPath, rootDir));
440
- }
441
- } else if (isSourceFile(entry)) {
442
- if (!shouldExcludeFile(entry, relativePath)) {
443
- files.push(fullPath);
444
- }
445
- }
446
- }
447
- } catch (e) {
448
- console.warn(`Cannot read directory ${dir}:`, e.message);
449
- }
450
-
451
- return files;
452
- }
package/src/server.js DELETED
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Entry Point for Project Graph MCP
4
- *
5
- * Decides whether to run in CLI mode or MCP Server mode (stdio)
6
- * Usage:
7
- * npx project-graph-mcp -> stdio server
8
- * npx project-graph-mcp <cmd> [args] -> CLI execution
9
- */
10
-
11
- import { startStdioServer } from './mcp-server.js';
12
- import { runCLI } from './cli.js';
13
-
14
- // Main execution logic
15
- // We check endsWith('server.js') to verify this is the main module being run
16
- if (process.argv[1] && (process.argv[1].endsWith('server.js') || process.argv[1].endsWith('project-graph-mcp'))) {
17
- const [, , command, ...args] = process.argv;
18
-
19
- if (command) {
20
- // CLI mode
21
- runCLI(command, args);
22
- } else {
23
- // MCP stdio mode
24
- // Use stderr for logs so stdout remains clean for JSON-RPC
25
- console.error('Starting Project Graph MCP (stdio)...');
26
- startStdioServer();
27
- }
28
- }