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.
- package/README.md +128 -8
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +13 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +16 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/ai-context.js +0 -113
- package/src/analysis-cache.js +0 -155
- package/src/cli-handlers.js +0 -271
- package/src/cli.js +0 -95
- package/src/compact.js +0 -207
- package/src/complexity.js +0 -237
- package/src/compress.js +0 -319
- package/src/ctx-to-jsdoc.js +0 -514
- package/src/custom-rules.js +0 -584
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/doc-dialect.js +0 -716
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -470
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -73
- package/src/jsdoc-checker.js +0 -351
- package/src/jsdoc-generator.js +0 -203
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -163
- package/src/mcp-server.js +0 -675
- package/src/mode-config.js +0 -127
- package/src/outdated-patterns.js +0 -296
- package/src/parser.js +0 -662
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -279
- package/src/test-annotations.js +0 -323
- package/src/tool-defs.js +0 -793
- package/src/tools.js +0 -470
- package/src/type-checker.js +0 -188
- package/src/undocumented.js +0 -259
- package/src/workspace.js +0 -70
- /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
- /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
|
-
}
|