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/filters.js DELETED
@@ -1,227 +0,0 @@
1
- /**
2
- * Filter Configuration for Project Graph
3
- * Manages excludes, includes, and gitignore parsing
4
- */
5
-
6
- import { readFileSync, existsSync } from 'fs';
7
- import { join, relative } from 'path';
8
-
9
- /**
10
- * Default directories to exclude
11
- */
12
- const DEFAULT_EXCLUDES = [
13
- 'node_modules',
14
- 'dist',
15
- 'build',
16
- 'coverage',
17
- '.next',
18
- '.nuxt',
19
- '.output',
20
- '__pycache__',
21
- '.cache',
22
- '.turbo',
23
- 'out',
24
- ];
25
-
26
- /**
27
- * Default file patterns to exclude
28
- */
29
- const DEFAULT_EXCLUDE_PATTERNS = [
30
- '*.test.js',
31
- '*.spec.js',
32
- '*.min.js',
33
- '*.bundle.js',
34
- '*.d.ts',
35
- '.project-graph-cache.json',
36
- ];
37
-
38
- // Current filter configuration (mutable via MCP)
39
- let config = {
40
- excludeDirs: [...DEFAULT_EXCLUDES],
41
- excludePatterns: [...DEFAULT_EXCLUDE_PATTERNS],
42
- includeHidden: false,
43
- useGitignore: true,
44
- gitignorePatterns: [],
45
- };
46
-
47
- /**
48
- * Get current filter configuration
49
- * @returns {Object}
50
- */
51
- export function getFilters() {
52
- return { ...config };
53
- }
54
-
55
- /**
56
- * Update filter configuration
57
- * @param {Object} updates
58
- * @returns {Object}
59
- */
60
- export function setFilters(updates) {
61
- if (updates.excludeDirs !== undefined) {
62
- config.excludeDirs = updates.excludeDirs;
63
- }
64
- if (updates.excludePatterns !== undefined) {
65
- config.excludePatterns = updates.excludePatterns;
66
- }
67
- if (updates.includeHidden !== undefined) {
68
- config.includeHidden = updates.includeHidden;
69
- }
70
- if (updates.useGitignore !== undefined) {
71
- config.useGitignore = updates.useGitignore;
72
- }
73
- return getFilters();
74
- }
75
-
76
- /**
77
- * Add directories to exclude list
78
- * @param {string[]} dirs
79
- * @returns {Object}
80
- */
81
- export function addExcludes(dirs) {
82
- config.excludeDirs = [...new Set([...config.excludeDirs, ...dirs])];
83
- return getFilters();
84
- }
85
-
86
- /**
87
- * Remove directories from exclude list
88
- * @param {string[]} dirs
89
- * @returns {Object}
90
- */
91
- export function removeExcludes(dirs) {
92
- config.excludeDirs = config.excludeDirs.filter(d => !dirs.includes(d));
93
- return getFilters();
94
- }
95
-
96
- /**
97
- * Reset filters to defaults
98
- * @returns {Object}
99
- */
100
- export function resetFilters() {
101
- config = {
102
- excludeDirs: [...DEFAULT_EXCLUDES],
103
- excludePatterns: [...DEFAULT_EXCLUDE_PATTERNS],
104
- includeHidden: false,
105
- useGitignore: true,
106
- gitignorePatterns: [],
107
- };
108
- return getFilters();
109
- }
110
-
111
- /**
112
- * Parse .gitignore file
113
- * @param {string} rootDir
114
- * @returns {string[]}
115
- */
116
- export function parseGitignore(rootDir) {
117
- const gitignorePath = join(rootDir, '.gitignore');
118
-
119
- if (!existsSync(gitignorePath)) {
120
- return [];
121
- }
122
-
123
- try {
124
- const content = readFileSync(gitignorePath, 'utf-8');
125
- const patterns = content
126
- .split('\n')
127
- .map(line => line.trim())
128
- .filter(line => line && !line.startsWith('#'))
129
- .map(line => line.replace(/\/$/, '')); // Remove trailing slashes
130
-
131
- config.gitignorePatterns = patterns;
132
- return patterns;
133
- } catch (e) {
134
- return [];
135
- }
136
- }
137
-
138
- /**
139
- * Check if a directory should be excluded
140
- * @param {string} dirName - Directory name (not path)
141
- * @param {string} relativePath - Relative path from root
142
- * @returns {boolean}
143
- */
144
- export function shouldExcludeDir(dirName, relativePath = '') {
145
- // Check hidden directories
146
- if (!config.includeHidden && dirName.startsWith('.')) {
147
- return true;
148
- }
149
-
150
- // Check default excludes
151
- if (config.excludeDirs.includes(dirName)) {
152
- return true;
153
- }
154
-
155
- // Check gitignore patterns
156
- if (config.useGitignore) {
157
- for (const pattern of config.gitignorePatterns) {
158
- if (matchGitignorePattern(pattern, dirName, relativePath)) {
159
- return true;
160
- }
161
- }
162
- }
163
-
164
- return false;
165
- }
166
-
167
- /**
168
- * Check if a file should be excluded
169
- * @param {string} fileName
170
- * @param {string} relativePath
171
- * @returns {boolean}
172
- */
173
- export function shouldExcludeFile(fileName, relativePath = '') {
174
- // Check exclude patterns
175
- for (const pattern of config.excludePatterns) {
176
- if (matchWildcard(pattern, fileName)) {
177
- return true;
178
- }
179
- }
180
-
181
- // Check gitignore patterns
182
- if (config.useGitignore) {
183
- for (const pattern of config.gitignorePatterns) {
184
- if (matchGitignorePattern(pattern, fileName, relativePath)) {
185
- return true;
186
- }
187
- }
188
- }
189
-
190
- return false;
191
- }
192
-
193
- /**
194
- * Match simple wildcard pattern (*.js, *.test.js)
195
- * @param {string} pattern
196
- * @param {string} str
197
- * @returns {boolean}
198
- */
199
- function matchWildcard(pattern, str) {
200
- const regex = pattern
201
- .replace(/\./g, '\\.')
202
- .replace(/\*/g, '.*');
203
- return new RegExp(`^${regex}$`).test(str);
204
- }
205
-
206
- /**
207
- * Match gitignore pattern
208
- * @param {string} pattern
209
- * @param {string} name
210
- * @param {string} relativePath
211
- * @returns {boolean}
212
- */
213
- function matchGitignorePattern(pattern, name, relativePath) {
214
- // Simple matching: exact name or wildcard
215
- if (pattern === name) return true;
216
-
217
- // Pattern with wildcard
218
- if (pattern.includes('*')) {
219
- return matchWildcard(pattern, name);
220
- }
221
-
222
- // Pattern in path
223
- const fullPath = relativePath ? `${relativePath}/${name}` : name;
224
- if (fullPath.includes(pattern)) return true;
225
-
226
- return false;
227
- }
@@ -1,177 +0,0 @@
1
- /**
2
- * Framework Reference System
3
- * Loads framework-specific AI references from GitHub (with caching) or local files
4
- */
5
-
6
- import { readFileSync, readdirSync, existsSync, writeFileSync } from 'fs';
7
- import { join, basename, dirname } from 'path';
8
- import { fileURLToPath } from 'url';
9
- import { detectProjectRuleSets } from './custom-rules.js';
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const REFERENCES_DIR = join(__dirname, '..', 'references');
13
-
14
- /**
15
- * Remote sources for framework references
16
- * Maps reference name to raw GitHub URL
17
- */
18
- const REMOTE_SOURCES = {
19
- 'symbiote-3x': 'https://raw.githubusercontent.com/symbiotejs/symbiote.js/main/AI_REFERENCE.md',
20
- };
21
-
22
- /** @type {Map<string, {content: string, fetchedAt: number}>} */
23
- const cache = new Map();
24
-
25
- /** Cache TTL: 1 hour */
26
- const CACHE_TTL = 60 * 60 * 1000;
27
-
28
- /**
29
- * Fetch reference from GitHub with caching
30
- * Falls back to local file if fetch fails
31
- * @param {string} name - Reference name
32
- * @returns {Promise<{content: string, source: string}>}
33
- */
34
- async function fetchReference(name) {
35
- const url = REMOTE_SOURCES[name];
36
- const localPath = join(REFERENCES_DIR, `${name}.md`);
37
-
38
- // Check in-memory cache
39
- const cached = cache.get(name);
40
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
41
- return { content: cached.content, source: 'cache' };
42
- }
43
-
44
- // Try fetching from GitHub
45
- if (url) {
46
- try {
47
- const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
48
- if (response.ok) {
49
- const content = await response.text();
50
- cache.set(name, { content, fetchedAt: Date.now() });
51
-
52
- // Update local file as backup
53
- try {
54
- writeFileSync(localPath, content, 'utf-8');
55
- } catch (e) {
56
- // Write failure is non-critical
57
- }
58
-
59
- return { content, source: `github (${url})` };
60
- }
61
- } catch (e) {
62
- // Fetch failed — fall back to local
63
- }
64
- }
65
-
66
- // Fall back to local file
67
- if (existsSync(localPath)) {
68
- const content = readFileSync(localPath, 'utf-8');
69
- cache.set(name, { content, fetchedAt: Date.now() });
70
- return { content, source: 'local' };
71
- }
72
-
73
- return { content: '', source: 'not_found' };
74
- }
75
-
76
- /**
77
- * Map ruleset names to reference names
78
- */
79
- const RULESET_TO_REFERENCE = {
80
- 'symbiote-3x': 'symbiote-3x',
81
- 'symbiote-2x': 'symbiote-3x',
82
- };
83
-
84
- /**
85
- * List available framework references (local + remote)
86
- * @returns {string[]}
87
- */
88
- function listAvailable() {
89
- const names = new Set(Object.keys(REMOTE_SOURCES));
90
-
91
- if (existsSync(REFERENCES_DIR)) {
92
- for (const f of readdirSync(REFERENCES_DIR)) {
93
- if (f.endsWith('.md')) {
94
- names.add(basename(f, '.md'));
95
- }
96
- }
97
- }
98
-
99
- return [...names];
100
- }
101
-
102
- /**
103
- * Get framework reference content
104
- * @param {Object} options
105
- * @param {string} [options.framework] - Explicit framework name
106
- * @param {string} [options.path] - Project path for auto-detection
107
- * @returns {Promise<Object>}
108
- */
109
- export async function getFrameworkReference(options = {}) {
110
- const available = listAvailable();
111
-
112
- // Explicit framework requested
113
- if (options.framework) {
114
- if (!available.includes(options.framework)) {
115
- return {
116
- error: `Framework reference '${options.framework}' not found`,
117
- available,
118
- };
119
- }
120
-
121
- const { content, source } = await fetchReference(options.framework);
122
- if (!content) {
123
- return { error: `Failed to load reference '${options.framework}'`, available };
124
- }
125
-
126
- return {
127
- framework: options.framework,
128
- source,
129
- lines: content.split('\n').length,
130
- content,
131
- };
132
- }
133
-
134
- // Auto-detect from project path
135
- if (options.path) {
136
- const { detected, reasons } = detectProjectRuleSets(options.path);
137
-
138
- const matchedRefs = [];
139
- for (const ruleset of detected) {
140
- const refName = RULESET_TO_REFERENCE[ruleset];
141
- if (refName && available.includes(refName) && !matchedRefs.includes(refName)) {
142
- matchedRefs.push(refName);
143
- }
144
- }
145
-
146
- if (matchedRefs.length === 0) {
147
- return {
148
- error: 'No framework references found for this project',
149
- detected,
150
- reasons,
151
- available,
152
- };
153
- }
154
-
155
- const results = await Promise.all(matchedRefs.map(fetchReference));
156
- const contents = results.map(r => r.content).filter(Boolean);
157
- const sources = results.map(r => r.source);
158
-
159
- return {
160
- frameworks: matchedRefs,
161
- sources,
162
- detected: { rulesets: detected, reasons },
163
- lines: contents.reduce((sum, c) => sum + c.split('\n').length, 0),
164
- content: contents.join('\n\n---\n\n'),
165
- };
166
- }
167
-
168
- // No framework specified — list available
169
- return {
170
- error: 'Specify framework name or path for auto-detection',
171
- available: available.map(name => ({
172
- name,
173
- remote: !!REMOTE_SOURCES[name],
174
- url: REMOTE_SOURCES[name] ?? null,
175
- })),
176
- };
177
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Full Analysis - Comprehensive Code Health Report
3
- * Runs all analysis tools and generates a health score
4
- */
5
-
6
- import { getDeadCode } from './dead-code.js';
7
- import { getUndocumentedSummary } from './undocumented.js';
8
- import { getSimilarFunctions } from './similar-functions.js';
9
- import { getComplexity } from './complexity.js';
10
- import { getLargeFiles } from './large-files.js';
11
- import { getOutdatedPatterns } from './outdated-patterns.js';
12
- import { getTableUsage } from './db-analysis.js';
13
-
14
- /**
15
- * @typedef {Object} AnalysisResult
16
- * @property {Object} deadCode
17
- * @property {Object} undocumented
18
- * @property {Object} similar
19
- * @property {Object} complexity
20
- * @property {Object} largeFiles
21
- * @property {Object} outdated
22
- * @property {Object} overall
23
- */
24
-
25
- /**
26
- * Calculate health score from analysis results
27
- * @param {Object} results
28
- * @returns {{score: number, rating: string, topIssues: string[]}}
29
- */
30
- function calculateHealthScore(results) {
31
- let score = 100;
32
- const topIssues = [];
33
-
34
- // Dead code penalty: -2 per item (max -20)
35
- const deadPenalty = Math.min(results.deadCode.total * 2, 20);
36
- score -= deadPenalty;
37
- if (results.deadCode.total > 0) {
38
- topIssues.push(`${results.deadCode.total} unused functions/classes`);
39
- }
40
-
41
- // Undocumented penalty: -0.5 per item (max -15)
42
- const undocPenalty = Math.min(results.undocumented.total * 0.5, 15);
43
- score -= undocPenalty;
44
- if (results.undocumented.total > 10) {
45
- topIssues.push(`${results.undocumented.total} undocumented items`);
46
- }
47
-
48
- // Similar functions penalty: -3 per pair (max -15)
49
- const similarPenalty = Math.min(results.similar.total * 3, 15);
50
- score -= similarPenalty;
51
- if (results.similar.total > 0) {
52
- topIssues.push(`${results.similar.total} similar function pairs`);
53
- }
54
-
55
- // Complexity penalty: -5 per critical, -2 per high (max -20)
56
- const criticalCount = results.complexity.stats?.critical || 0;
57
- const highCount = results.complexity.stats?.high || 0;
58
- const complexityPenalty = Math.min(criticalCount * 5 + highCount * 2, 20);
59
- score -= complexityPenalty;
60
- if (criticalCount > 0) {
61
- topIssues.push(`${criticalCount} critical complexity functions`);
62
- }
63
-
64
- // Large files penalty: -4 per critical, -1 per warning (max -10)
65
- const largeCritical = results.largeFiles.stats?.critical || 0;
66
- const largeWarning = results.largeFiles.stats?.warning || 0;
67
- const largePenalty = Math.min(largeCritical * 4 + largeWarning * 1, 10);
68
- score -= largePenalty;
69
- if (largeCritical > 0) {
70
- topIssues.push(`${largeCritical} files need splitting`);
71
- }
72
-
73
- // Outdated patterns penalty: -3 per error, -1 per warning (max -10)
74
- const errorPatterns = results.outdated.stats?.bySeverity?.error || 0;
75
- const warningPatterns = results.outdated.stats?.bySeverity?.warning || 0;
76
- const outdatedPenalty = Math.min(errorPatterns * 3 + warningPatterns * 1, 10);
77
- score -= outdatedPenalty;
78
- if (results.outdated.redundantDeps.length > 0) {
79
- topIssues.push(`${results.outdated.redundantDeps.length} redundant npm dependencies`);
80
- }
81
-
82
- // Clamp score
83
- score = Math.max(0, Math.min(100, Math.round(score)));
84
-
85
- // Determine rating
86
- let rating;
87
- if (score >= 90) rating = 'excellent';
88
- else if (score >= 70) rating = 'good';
89
- else if (score >= 50) rating = 'warning';
90
- else rating = 'critical';
91
-
92
- return { score, rating, topIssues: topIssues.slice(0, 5) };
93
- }
94
-
95
- /**
96
- * Run full analysis on directory
97
- * @param {string} dir
98
- * @param {Object} [options]
99
- * @param {boolean} [options.includeItems=false] - Include individual items
100
- * @returns {Promise<AnalysisResult>}
101
- */
102
- export async function getFullAnalysis(dir, options = {}) {
103
- const includeItems = options.includeItems || false;
104
-
105
- // Run all analyses in parallel
106
- const [deadCode, undocumented, similar, complexity, largeFiles, outdated, dbUsage] = await Promise.all([
107
- getDeadCode(dir),
108
- getUndocumentedSummary(dir, 'tests'),
109
- getSimilarFunctions(dir, { threshold: 70 }),
110
- getComplexity(dir, { minComplexity: 5 }),
111
- getLargeFiles(dir),
112
- getOutdatedPatterns(dir),
113
- getTableUsage(dir).catch(() => ({ tables: [], totalTables: 0, totalQueries: 0 })),
114
- ]);
115
-
116
- // Calculate overall health
117
- const overall = calculateHealthScore({
118
- deadCode,
119
- undocumented,
120
- similar,
121
- complexity,
122
- largeFiles,
123
- outdated,
124
- });
125
-
126
- // Build result
127
- const result = {
128
- deadCode: {
129
- total: deadCode.total,
130
- byType: deadCode.byType,
131
- ...(includeItems && { items: deadCode.items.slice(0, 10) }),
132
- },
133
- undocumented: {
134
- total: undocumented.total,
135
- byType: undocumented.byType,
136
- ...(includeItems && { items: undocumented.items.slice(0, 10) }),
137
- },
138
- similar: {
139
- total: similar.total,
140
- ...(includeItems && { pairs: similar.pairs.slice(0, 5) }),
141
- },
142
- complexity: {
143
- total: complexity.total,
144
- stats: complexity.stats,
145
- ...(includeItems && { items: complexity.items.slice(0, 10) }),
146
- },
147
- largeFiles: {
148
- total: largeFiles.total,
149
- stats: largeFiles.stats,
150
- ...(includeItems && { items: largeFiles.items.slice(0, 10) }),
151
- },
152
- outdated: {
153
- totalPatterns: outdated.stats.totalPatterns,
154
- redundantDeps: outdated.redundantDeps,
155
- ...(includeItems && { codePatterns: outdated.codePatterns.slice(0, 10) }),
156
- },
157
- overall,
158
- };
159
-
160
- // Add DB metrics if any SQL interactions found (non-scoring)
161
- if (dbUsage.totalTables > 0) {
162
- result.database = {
163
- tablesUsed: dbUsage.totalTables,
164
- totalQueries: dbUsage.totalQueries,
165
- tables: dbUsage.tables.map(t => ({
166
- name: t.table,
167
- readers: t.totalReaders,
168
- writers: t.totalWriters,
169
- })),
170
- };
171
- }
172
-
173
- return result;
174
- }