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
@@ -1,260 +0,0 @@
1
- /**
2
- * Undocumented Code Finder (AST-based)
3
- * Finds methods/functions missing JSDoc annotations using Acorn AST parser
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
-
12
- /**
13
- * @typedef {Object} UndocumentedItem
14
- * @property {string} name - ClassName.methodName or functionName
15
- * @property {string} type - 'method' | 'function' | 'class'
16
- * @property {string} file
17
- * @property {number} line
18
- * @property {string} reason - What's missing
19
- */
20
-
21
- /**
22
- * Find all JS files in directory
23
- * @param {string} dir
24
- * @param {string} rootDir
25
- * @returns {string[]}
26
- */
27
- function findJSFiles(dir, rootDir = dir) {
28
- if (dir === rootDir) {
29
- parseGitignore(rootDir);
30
- }
31
-
32
- const files = [];
33
-
34
- try {
35
- for (const entry of readdirSync(dir)) {
36
- const fullPath = join(dir, entry);
37
- const relativePath = relative(rootDir, fullPath);
38
- const stat = statSync(fullPath);
39
-
40
- if (stat.isDirectory()) {
41
- if (!shouldExcludeDir(entry, relativePath)) {
42
- files.push(...findJSFiles(fullPath, rootDir));
43
- }
44
- } else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
45
- if (!shouldExcludeFile(entry, relativePath)) {
46
- files.push(fullPath);
47
- }
48
- }
49
- }
50
- } catch (e) {
51
- // Directory not found
52
- }
53
-
54
- return files;
55
- }
56
-
57
- /**
58
- * Extract JSDoc comments from code with their positions
59
- * @param {string} code
60
- * @returns {Array<{text: string, endLine: number}>}
61
- */
62
- function extractComments(code) {
63
- const comments = [];
64
- const regex = /\/\*\*[\s\S]*?\*\//g;
65
- let match;
66
-
67
- while ((match = regex.exec(code)) !== null) {
68
- const endLine = code.slice(0, match.index + match[0].length).split('\n').length;
69
- comments.push({ text: match[0], endLine });
70
- }
71
-
72
- return comments;
73
- }
74
-
75
- /**
76
- * Find JSDoc comment before a target line
77
- * @param {Array<{text: string, endLine: number}>} comments
78
- * @param {number} targetLine
79
- * @returns {string|null}
80
- */
81
- function findJSDocBefore(comments, targetLine) {
82
- for (const comment of comments) {
83
- const gap = targetLine - comment.endLine;
84
- if (gap >= 0 && gap <= 2) {
85
- return comment.text;
86
- }
87
- }
88
- return null;
89
- }
90
-
91
- /**
92
- * Check what's missing from JSDoc based on level
93
- * @param {string|null} jsdoc
94
- * @param {'tests'|'params'|'all'} level
95
- * @returns {string[]}
96
- */
97
- function checkMissing(jsdoc, level) {
98
- const missing = [];
99
-
100
- if (!jsdoc) {
101
- if (level === 'all') missing.push('description');
102
- if (level === 'params' || level === 'all') missing.push('@param', '@returns');
103
- if (level === 'tests' || level === 'params' || level === 'all') missing.push('@test', '@expect');
104
- return missing;
105
- }
106
-
107
- if (level === 'tests' || level === 'params' || level === 'all') {
108
- if (!jsdoc.includes('@test')) missing.push('@test');
109
- if (!jsdoc.includes('@expect')) missing.push('@expect');
110
- }
111
-
112
- if (level === 'params' || level === 'all') {
113
- if (!jsdoc.includes('@param')) missing.push('@param');
114
- if (!jsdoc.includes('@returns') && !jsdoc.includes('@return')) missing.push('@returns');
115
- }
116
-
117
- return missing;
118
- }
119
-
120
- /** Skip list for methods */
121
- const SKIP_METHODS = [
122
- 'constructor', 'connectedCallback', 'disconnectedCallback',
123
- 'attributeChangedCallback', 'renderCallback',
124
- ];
125
-
126
- /**
127
- * Parse file using AST and find undocumented items
128
- * @param {string} code
129
- * @param {string} filePath
130
- * @param {'tests'|'params'|'all'} level
131
- * @returns {UndocumentedItem[]}
132
- */
133
- function parseFile(code, filePath, level) {
134
- const results = [];
135
-
136
- let ast;
137
- try {
138
- ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
139
- } catch (e) {
140
- return results;
141
- }
142
-
143
- const comments = extractComments(code);
144
-
145
- walk.simple(ast, {
146
- ClassDeclaration(node) {
147
- const className = node.id?.name || 'Anonymous';
148
-
149
- // Check class itself (only for 'all' level)
150
- if (level === 'all') {
151
- const classJsdoc = findJSDocBefore(comments, node.loc.start.line);
152
- if (!classJsdoc) {
153
- results.push({
154
- name: className,
155
- type: 'class',
156
- file: filePath,
157
- line: node.loc.start.line,
158
- reason: 'No JSDoc',
159
- });
160
- }
161
- }
162
-
163
- // Check methods
164
- for (const element of node.body.body) {
165
- if (element.type === 'MethodDefinition') {
166
- const methodName = element.key.name || element.key.value;
167
-
168
- // Skip: constructor, private, getters/setters, lifecycle
169
- if (element.kind === 'get' || element.kind === 'set') continue;
170
- if (methodName?.startsWith('_')) continue;
171
- if (SKIP_METHODS.includes(methodName)) continue;
172
-
173
- const jsdoc = findJSDocBefore(comments, element.loc.start.line);
174
- const missing = checkMissing(jsdoc, level);
175
-
176
- if (missing.length > 0) {
177
- results.push({
178
- name: `${className}.${methodName}`,
179
- type: 'method',
180
- file: filePath,
181
- line: element.loc.start.line,
182
- reason: missing.join(', '),
183
- });
184
- }
185
- }
186
- }
187
- },
188
-
189
- FunctionDeclaration(node) {
190
- if (!node.id) return;
191
- const funcName = node.id.name;
192
-
193
- // Skip private functions
194
- if (funcName.startsWith('_')) return;
195
-
196
- const jsdoc = findJSDocBefore(comments, node.loc.start.line);
197
- const missing = checkMissing(jsdoc, level);
198
-
199
- if (missing.length > 0) {
200
- results.push({
201
- name: funcName,
202
- type: 'function',
203
- file: filePath,
204
- line: node.loc.start.line,
205
- reason: missing.join(', '),
206
- });
207
- }
208
- },
209
- });
210
-
211
- return results;
212
- }
213
-
214
- /**
215
- * Get undocumented items from directory
216
- * @param {string} dir
217
- * @param {'tests'|'params'|'all'} level
218
- * @returns {UndocumentedItem[]}
219
- */
220
- export function getUndocumented(dir, level = 'tests') {
221
- const resolvedDir = resolve(dir);
222
- const files = findJSFiles(dir);
223
- const results = [];
224
-
225
- for (const file of files) {
226
- const content = readFileSync(file, 'utf-8');
227
- const items = parseFile(content, relative(resolvedDir, file), level);
228
- results.push(...items);
229
- }
230
-
231
- return results;
232
- }
233
-
234
- /**
235
- * Get summary of undocumented items
236
- * @param {string} dir
237
- * @param {'tests'|'params'|'all'} level
238
- * @returns {Object}
239
- */
240
- export function getUndocumentedSummary(dir, level = 'tests') {
241
- const items = getUndocumented(dir, level);
242
-
243
- const byType = {
244
- class: items.filter(i => i.type === 'class').length,
245
- function: items.filter(i => i.type === 'function').length,
246
- method: items.filter(i => i.type === 'method').length,
247
- };
248
-
249
- const byReason = {};
250
- for (const item of items) {
251
- byReason[item.reason] = (byReason[item.reason] || 0) + 1;
252
- }
253
-
254
- return {
255
- total: items.length,
256
- byType,
257
- byReason,
258
- items: items.slice(0, 20),
259
- };
260
- }
package/src/workspace.js DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * Workspace Root Resolution
3
- *
4
- * Resolves relative paths against the correct workspace root.
5
- * Priority: MCP initialize roots → --workspace arg → PROJECT_ROOT env → process.cwd()
6
- */
7
-
8
- import { resolve, isAbsolute } from 'path';
9
-
10
- /** @type {string|null} */
11
- let workspaceRoot = null;
12
-
13
- // Auto-detect --workspace arg at module load
14
- const wsArg = process.argv.find(a => a.startsWith('--workspace='));
15
- if (wsArg) {
16
- workspaceRoot = wsArg.split('=')[1];
17
- console.error(`[project-graph] Workspace from arg: ${workspaceRoot}`);
18
- }
19
-
20
- /**
21
- * Set workspace root from MCP initialize roots
22
- * @param {Array<{uri: string, name?: string}>} roots
23
- */
24
- export function setRoots(roots) {
25
- if (roots && roots.length > 0) {
26
- let uri = roots[0].uri;
27
- // Strip file:// protocol if present
28
- if (uri.startsWith('file://')) {
29
- uri = uri.slice(7);
30
- }
31
- workspaceRoot = uri;
32
- console.error(`[project-graph] Workspace root: ${workspaceRoot}`);
33
- }
34
- }
35
-
36
- /**
37
- * Get current workspace root
38
- * @returns {string}
39
- */
40
- export function getWorkspaceRoot() {
41
- if (workspaceRoot) {
42
- return workspaceRoot;
43
- }
44
- if (process.env.PROJECT_ROOT) {
45
- return process.env.PROJECT_ROOT;
46
- }
47
- return process.cwd();
48
- }
49
-
50
- /**
51
- * Resolve a path argument against workspace root.
52
- * Absolute paths are returned as-is.
53
- * Relative paths are resolved against the workspace root.
54
- * @param {string} path
55
- * @returns {string}
56
- */
57
- export function resolvePath(inputPath) {
58
- if (!inputPath) {
59
- return getWorkspaceRoot();
60
- }
61
- const root = getWorkspaceRoot();
62
- const resolved = isAbsolute(inputPath) ? inputPath : resolve(root, inputPath);
63
-
64
- // Prevent path traversal — resolved path must stay within workspace
65
- if (!resolved.startsWith(root)) {
66
- throw new Error(`Path traversal blocked: '${inputPath}' resolves outside workspace root '${root}'`);
67
- }
68
-
69
- return resolved;
70
- }