project-graph-mcp 1.5.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 (121) hide show
  1. package/README.md +128 -8
  2. package/package.json +12 -8
  3. package/src/.project-graph-cache.json +1 -1
  4. package/src/analysis/analysis-cache.js +7 -0
  5. package/src/analysis/complexity.js +14 -0
  6. package/src/analysis/custom-rules.js +36 -0
  7. package/src/analysis/db-analysis.js +9 -0
  8. package/src/analysis/dead-code.js +19 -0
  9. package/src/analysis/full-analysis.js +18 -0
  10. package/src/analysis/jsdoc-checker.js +24 -0
  11. package/src/analysis/jsdoc-generator.js +10 -0
  12. package/src/analysis/large-files.js +11 -0
  13. package/src/analysis/outdated-patterns.js +12 -0
  14. package/src/analysis/similar-functions.js +16 -0
  15. package/src/analysis/test-annotations.js +21 -0
  16. package/src/analysis/type-checker.js +8 -0
  17. package/src/analysis/undocumented.js +14 -0
  18. package/src/cli/cli-handlers.js +4 -0
  19. package/src/cli/cli.js +5 -0
  20. package/src/compact/ai-context.js +7 -0
  21. package/src/compact/compact.js +18 -0
  22. package/src/compact/compress.js +13 -0
  23. package/src/compact/ctx-to-jsdoc.js +29 -0
  24. package/src/compact/doc-dialect.js +30 -0
  25. package/src/compact/expand.js +37 -0
  26. package/src/compact/framework-references.js +5 -0
  27. package/src/compact/instructions.js +3 -0
  28. package/src/compact/mode-config.js +8 -0
  29. package/src/compact/validate-pipeline.js +9 -0
  30. package/src/core/event-bus.js +9 -0
  31. package/src/core/filters.js +14 -0
  32. package/src/core/graph-builder.js +12 -0
  33. package/src/core/parser.js +31 -0
  34. package/src/core/workspace.js +8 -0
  35. package/src/lang/lang-go.js +17 -0
  36. package/src/lang/lang-python.js +12 -0
  37. package/src/lang/lang-sql.js +23 -0
  38. package/src/lang/lang-typescript.js +9 -0
  39. package/src/lang/lang-utils.js +4 -0
  40. package/src/mcp/mcp-server.js +17 -0
  41. package/src/mcp/tool-defs.js +3 -0
  42. package/src/mcp/tools.js +25 -0
  43. package/src/network/backend-lifecycle.js +19 -0
  44. package/src/network/backend.js +5 -0
  45. package/src/network/local-gateway.js +23 -0
  46. package/src/network/mdns.js +13 -0
  47. package/src/network/server.js +10 -0
  48. package/src/network/web-server.js +34 -0
  49. package/web/.project-graph-cache.json +1 -0
  50. package/web/app.js +16 -0
  51. package/web/components/code-block.js +3 -0
  52. package/web/components/quick-open.js +5 -0
  53. package/web/dashboard-state.js +3 -0
  54. package/web/dashboard.html +27 -0
  55. package/web/dashboard.js +8 -0
  56. package/web/highlight.js +13 -0
  57. package/web/index.html +35 -0
  58. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  59. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  60. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  61. package/web/panels/EventItem/EventItem.css.js +1 -0
  62. package/web/panels/EventItem/EventItem.js +4 -0
  63. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  64. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  65. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  66. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  67. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  68. package/web/panels/ProjectList/ProjectList.js +4 -0
  69. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  70. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  71. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  72. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  73. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  74. package/web/panels/code-viewer.js +5 -0
  75. package/web/panels/ctx-panel.js +4 -0
  76. package/web/panels/dep-graph.js +6 -0
  77. package/web/panels/file-tree.js +188 -0
  78. package/web/panels/health-panel.js +3 -0
  79. package/web/panels/live-monitor.js +3 -0
  80. package/web/state.js +17 -0
  81. package/web/style.css +157 -0
  82. package/references/symbiote-3x.md +0 -834
  83. package/src/ai-context.js +0 -113
  84. package/src/analysis-cache.js +0 -155
  85. package/src/cli-handlers.js +0 -271
  86. package/src/cli.js +0 -95
  87. package/src/compact.js +0 -207
  88. package/src/complexity.js +0 -237
  89. package/src/compress.js +0 -319
  90. package/src/ctx-to-jsdoc.js +0 -514
  91. package/src/custom-rules.js +0 -584
  92. package/src/db-analysis.js +0 -194
  93. package/src/dead-code.js +0 -468
  94. package/src/doc-dialect.js +0 -716
  95. package/src/filters.js +0 -227
  96. package/src/framework-references.js +0 -177
  97. package/src/full-analysis.js +0 -470
  98. package/src/graph-builder.js +0 -299
  99. package/src/instructions.js +0 -73
  100. package/src/jsdoc-checker.js +0 -351
  101. package/src/jsdoc-generator.js +0 -203
  102. package/src/lang-go.js +0 -285
  103. package/src/lang-python.js +0 -197
  104. package/src/lang-sql.js +0 -309
  105. package/src/lang-typescript.js +0 -190
  106. package/src/lang-utils.js +0 -124
  107. package/src/large-files.js +0 -163
  108. package/src/mcp-server.js +0 -675
  109. package/src/mode-config.js +0 -127
  110. package/src/outdated-patterns.js +0 -296
  111. package/src/parser.js +0 -662
  112. package/src/server.js +0 -28
  113. package/src/similar-functions.js +0 -279
  114. package/src/test-annotations.js +0 -323
  115. package/src/tool-defs.js +0 -793
  116. package/src/tools.js +0 -470
  117. package/src/type-checker.js +0 -188
  118. package/src/undocumented.js +0 -259
  119. package/src/workspace.js +0 -70
  120. /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
  121. /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
package/src/compact.js DELETED
@@ -1,207 +0,0 @@
1
- /**
2
- * Compact/Beautify — Project-wide code compression and expansion
3
- *
4
- * Converts JS files between compact (minified, no comments) and
5
- * beautified (formatted, readable) forms. Both preserve all names
6
- * (mangle: false) — only whitespace and comments are affected.
7
- *
8
- * Types and documentation live in .ctx files, not in source code.
9
- */
10
-
11
- import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
12
- import { join, extname, relative } from 'path';
13
- import { minify } from '../vendor/terser.mjs';
14
-
15
- const SUPPORTED = new Set(['.js', '.mjs']);
16
- const SKIP_DIRS = new Set(['node_modules', '.git', 'vendor', '.context', 'dev-docs', '.agent', '.agents']);
17
-
18
- /**
19
- * Walk directory for JS files
20
- * @param {string} dir
21
- * @param {string} rootDir
22
- * @returns {string[]} Absolute paths
23
- */
24
- function walkJSFiles(dir, rootDir = dir) {
25
- const results = [];
26
- try {
27
- for (const entry of readdirSync(dir)) {
28
- if (entry.startsWith('.') && entry !== '.') continue;
29
- const full = join(dir, entry);
30
- const stat = statSync(full);
31
- if (stat.isDirectory()) {
32
- if (!SKIP_DIRS.has(entry)) {
33
- results.push(...walkJSFiles(full, rootDir));
34
- }
35
- } else if (SUPPORTED.has(extname(entry).toLowerCase())) {
36
- results.push(full);
37
- }
38
- }
39
- } catch { /* skip unreadable */ }
40
- return results;
41
- }
42
-
43
- /**
44
- * Compact a single file — minify with preserved names
45
- * @param {string} filePath
46
- * @returns {Promise<{original: number, compacted: number}>}
47
- */
48
- async function compactFile(filePath) {
49
- const source = readFileSync(filePath, 'utf-8');
50
- const original = source.length;
51
-
52
- if (!source.trim()) return { original: 0, compacted: 0 };
53
-
54
- const result = await minify(source, {
55
- compress: {
56
- dead_code: true,
57
- drop_console: false,
58
- passes: 2,
59
- },
60
- mangle: false,
61
- module: true,
62
- output: {
63
- beautify: false,
64
- comments: false,
65
- semicolons: true,
66
- },
67
- });
68
-
69
- if (result.error) throw result.error;
70
-
71
- writeFileSync(filePath, result.code, 'utf-8');
72
- return { original, compacted: result.code.length };
73
- }
74
-
75
- /**
76
- * Beautify a single file — format with readable output
77
- * @param {string} filePath
78
- * @returns {Promise<{original: number, beautified: number}>}
79
- */
80
- async function beautifyFile(filePath) {
81
- const source = readFileSync(filePath, 'utf-8');
82
- const original = source.length;
83
-
84
- if (!source.trim()) return { original: 0, beautified: 0 };
85
-
86
- const result = await minify(source, {
87
- compress: false,
88
- mangle: false,
89
- module: true,
90
- output: {
91
- beautify: true,
92
- comments: false,
93
- indent_level: 2,
94
- semicolons: true,
95
- },
96
- });
97
-
98
- if (result.error) throw result.error;
99
-
100
- writeFileSync(filePath, result.code + '\n', 'utf-8');
101
- return { original, beautified: result.code.length };
102
- }
103
-
104
- /**
105
- * Compact all JS files in a directory
106
- * @param {string} dir - Directory to compact
107
- * @param {Object} [options]
108
- * @param {boolean} [options.dryRun=false] - Preview without writing
109
- * @returns {Promise<{files: number, originalBytes: number, compactedBytes: number, savings: string}>}
110
- */
111
- export async function compactProject(dir, options = {}) {
112
- const { dryRun = false } = options;
113
- const files = walkJSFiles(dir);
114
- let totalOriginal = 0;
115
- let totalCompacted = 0;
116
- const processed = [];
117
- const errors = [];
118
-
119
- for (const filePath of files) {
120
- const rel = relative(dir, filePath);
121
- try {
122
- const source = readFileSync(filePath, 'utf-8');
123
- totalOriginal += source.length;
124
-
125
- if (!dryRun) {
126
- const { compacted } = await compactFile(filePath);
127
- totalCompacted += compacted;
128
- } else {
129
- const result = await minify(source, {
130
- compress: { dead_code: true, drop_console: false, passes: 2 },
131
- mangle: false,
132
- module: true,
133
- output: { beautify: false, comments: false },
134
- });
135
- totalCompacted += result.code?.length || source.length;
136
- }
137
-
138
- processed.push(rel);
139
- } catch (e) {
140
- errors.push({ file: rel, error: e.message });
141
- }
142
- }
143
-
144
- const savings = totalOriginal > 0
145
- ? Math.round((1 - totalCompacted / totalOriginal) * 100)
146
- : 0;
147
-
148
- return {
149
- files: processed.length,
150
- fileList: processed,
151
- originalBytes: totalOriginal,
152
- compactedBytes: totalCompacted,
153
- savings: `${savings}%`,
154
- errors: errors.length > 0 ? errors : undefined,
155
- dryRun,
156
- };
157
- }
158
-
159
- /**
160
- * Beautify all JS files in a directory
161
- * @param {string} dir - Directory to beautify
162
- * @param {Object} [options]
163
- * @param {boolean} [options.dryRun=false] - Preview without writing
164
- * @returns {Promise<{files: number, originalBytes: number, beautifiedBytes: number}>}
165
- */
166
- export async function expandProject(dir, options = {}) {
167
- const { dryRun = false } = options;
168
- const files = walkJSFiles(dir);
169
- let totalOriginal = 0;
170
- let totalBeautified = 0;
171
- const processed = [];
172
- const errors = [];
173
-
174
- for (const filePath of files) {
175
- const rel = relative(dir, filePath);
176
- try {
177
- const source = readFileSync(filePath, 'utf-8');
178
- totalOriginal += source.length;
179
-
180
- if (!dryRun) {
181
- const { beautified } = await beautifyFile(filePath);
182
- totalBeautified += beautified;
183
- } else {
184
- const result = await minify(source, {
185
- compress: false,
186
- mangle: false,
187
- module: true,
188
- output: { beautify: true, comments: false, indent_level: 2 },
189
- });
190
- totalBeautified += result.code?.length || source.length;
191
- }
192
-
193
- processed.push(rel);
194
- } catch (e) {
195
- errors.push({ file: rel, error: e.message });
196
- }
197
- }
198
-
199
- return {
200
- files: processed.length,
201
- fileList: processed,
202
- originalBytes: totalOriginal,
203
- beautifiedBytes: totalBeautified,
204
- errors: errors.length > 0 ? errors : undefined,
205
- dryRun,
206
- };
207
- }
package/src/complexity.js DELETED
@@ -1,237 +0,0 @@
1
- /**
2
- * Cyclomatic Complexity Analyzer
3
- * Measures function complexity based on decision points
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} ComplexityItem
14
- * @property {string} name
15
- * @property {string} type - 'function' | 'method'
16
- * @property {string} file
17
- * @property {number} line
18
- * @property {number} complexity - Cyclomatic complexity score
19
- * @property {string} rating - 'low' | 'moderate' | 'high' | 'critical'
20
- */
21
-
22
- /**
23
- * Find all JS files
24
- * @param {string} dir
25
- * @param {string} rootDir
26
- * @returns {string[]}
27
- */
28
- function findJSFiles(dir, rootDir = dir) {
29
- if (dir === rootDir) parseGitignore(rootDir);
30
- const files = [];
31
-
32
- try {
33
- for (const entry of readdirSync(dir)) {
34
- const fullPath = join(dir, entry);
35
- const relativePath = relative(rootDir, fullPath);
36
- const stat = statSync(fullPath);
37
-
38
- if (stat.isDirectory()) {
39
- if (!shouldExcludeDir(entry, relativePath)) {
40
- files.push(...findJSFiles(fullPath, rootDir));
41
- }
42
- } else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
43
- if (!shouldExcludeFile(entry, relativePath)) {
44
- files.push(fullPath);
45
- }
46
- }
47
- }
48
- } catch (e) { }
49
-
50
- return files;
51
- }
52
-
53
- /**
54
- * Calculate complexity of a function body
55
- * @param {Object} body
56
- * @returns {number}
57
- */
58
- function calculateComplexity(body) {
59
- let complexity = 1; // Base complexity
60
-
61
- walk.simple(body, {
62
- // Branching
63
- IfStatement() { complexity++; },
64
- ConditionalExpression() { complexity++; }, // ternary
65
-
66
- // Loops
67
- ForStatement() { complexity++; },
68
- ForOfStatement() { complexity++; },
69
- ForInStatement() { complexity++; },
70
- WhileStatement() { complexity++; },
71
- DoWhileStatement() { complexity++; },
72
-
73
- // Switch cases
74
- SwitchCase(node) {
75
- if (node.test) complexity++; // Skip default case
76
- },
77
-
78
- // Logical operators
79
- LogicalExpression(node) {
80
- if (node.operator === '&&' || node.operator === '||') {
81
- complexity++;
82
- }
83
- },
84
-
85
- // Nullish coalescing
86
- BinaryExpression(node) {
87
- if (node.operator === '??') {
88
- complexity++;
89
- }
90
- },
91
-
92
- // Error handling
93
- CatchClause() { complexity++; },
94
- });
95
-
96
- return complexity;
97
- }
98
-
99
- /**
100
- * Get rating from complexity score
101
- * @param {number} complexity
102
- * @returns {string}
103
- */
104
- function getRating(complexity) {
105
- if (complexity <= 5) return 'low';
106
- if (complexity <= 10) return 'moderate';
107
- if (complexity <= 20) return 'high';
108
- return 'critical';
109
- }
110
-
111
- /**
112
- * Analyze complexity of a single file (per-file export for cache integration)
113
- * @param {string} code - File source code
114
- * @param {string} relPath - Relative path for reporting
115
- * @returns {ComplexityItem[]}
116
- */
117
- export function analyzeComplexityFile(code, relPath) {
118
- const items = [];
119
-
120
- let ast;
121
- try {
122
- ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
123
- } catch (e) {
124
- return items;
125
- }
126
-
127
- walk.simple(ast, {
128
- FunctionDeclaration(node) {
129
- if (!node.id) return;
130
- const complexity = calculateComplexity(node.body);
131
- items.push({
132
- name: node.id.name,
133
- type: 'function',
134
- file: relPath,
135
- line: node.loc.start.line,
136
- complexity,
137
- rating: getRating(complexity),
138
- });
139
- },
140
-
141
- ArrowFunctionExpression(node) {
142
- if (node.body.type !== 'BlockStatement') return;
143
- const complexity = calculateComplexity(node.body);
144
- if (complexity > 5) {
145
- items.push({
146
- name: '(arrow)',
147
- type: 'function',
148
- file: relPath,
149
- line: node.loc.start.line,
150
- complexity,
151
- rating: getRating(complexity),
152
- });
153
- }
154
- },
155
-
156
- MethodDefinition(node) {
157
- if (node.kind !== 'method') return;
158
- const name = node.key.name || node.key.value;
159
- const complexity = calculateComplexity(node.value.body);
160
- items.push({
161
- name,
162
- type: 'method',
163
- file: relPath,
164
- line: node.loc.start.line,
165
- complexity,
166
- rating: getRating(complexity),
167
- });
168
- },
169
- });
170
-
171
- return items;
172
- }
173
-
174
- /**
175
- * Analyze complexity of file (internal, reads from disk)
176
- * @param {string} filePath
177
- * @param {string} rootDir
178
- * @returns {ComplexityItem[]}
179
- */
180
- function analyzeFile(filePath, rootDir) {
181
- let code;
182
- try {
183
- code = readFileSync(filePath, 'utf-8');
184
- } catch (e) {
185
- return []; // File deleted between findJSFiles and read
186
- }
187
- const relPath = relative(rootDir, filePath);
188
- return analyzeComplexityFile(code, relPath);
189
- }
190
-
191
- /**
192
- * Get complexity analysis for directory
193
- * @param {string} dir
194
- * @param {Object} [options]
195
- * @param {number} [options.minComplexity=1] - Minimum complexity to include
196
- * @param {boolean} [options.onlyProblematic=false] - Only show high/critical
197
- * @returns {Promise<{total: number, stats: Object, items: ComplexityItem[]}>}
198
- */
199
- export async function getComplexity(dir, options = {}) {
200
- const minComplexity = options.minComplexity || 1;
201
- const onlyProblematic = options.onlyProblematic || false;
202
- const resolvedDir = resolve(dir);
203
-
204
- const files = findJSFiles(dir);
205
- let allItems = [];
206
-
207
- for (const file of files) {
208
- allItems.push(...analyzeFile(file, resolvedDir));
209
- }
210
-
211
- // Filter
212
- allItems = allItems.filter(item => {
213
- if (item.complexity < minComplexity) return false;
214
- if (onlyProblematic && (item.rating === 'low' || item.rating === 'moderate')) return false;
215
- return true;
216
- });
217
-
218
- // Sort by complexity descending
219
- allItems.sort((a, b) => b.complexity - a.complexity);
220
-
221
- // Calculate stats
222
- const stats = {
223
- low: allItems.filter(i => i.rating === 'low').length,
224
- moderate: allItems.filter(i => i.rating === 'moderate').length,
225
- high: allItems.filter(i => i.rating === 'high').length,
226
- critical: allItems.filter(i => i.rating === 'critical').length,
227
- average: allItems.length > 0
228
- ? Math.round(allItems.reduce((s, i) => s + i.complexity, 0) / allItems.length * 10) / 10
229
- : 0,
230
- };
231
-
232
- return {
233
- total: allItems.length,
234
- stats,
235
- items: allItems.slice(0, 30),
236
- };
237
- }