project-graph-mcp 1.5.0 → 2.1.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 (125) hide show
  1. package/README.md +171 -31
  2. package/docs/img/explorer-compact.jpg +0 -0
  3. package/docs/img/explorer-expanded.jpg +0 -0
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -1
  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/.project-graph-cache.json +1 -0
  23. package/src/compact/ai-context.js +7 -0
  24. package/src/compact/compact-migrate.js +17 -0
  25. package/src/compact/compact.js +18 -0
  26. package/src/compact/compress.js +14 -0
  27. package/src/compact/ctx-to-jsdoc.js +29 -0
  28. package/src/compact/doc-dialect.js +30 -0
  29. package/src/compact/expand.js +37 -0
  30. package/src/compact/framework-references.js +5 -0
  31. package/src/compact/instructions.js +3 -0
  32. package/src/compact/mode-config.js +8 -0
  33. package/src/compact/validate-pipeline.js +9 -0
  34. package/src/core/event-bus.js +9 -0
  35. package/src/core/filters.js +14 -0
  36. package/src/core/graph-builder.js +12 -0
  37. package/src/core/parser.js +31 -0
  38. package/src/core/workspace.js +8 -0
  39. package/src/lang/lang-go.js +17 -0
  40. package/src/lang/lang-python.js +12 -0
  41. package/src/lang/lang-sql.js +23 -0
  42. package/src/lang/lang-typescript.js +9 -0
  43. package/src/lang/lang-utils.js +4 -0
  44. package/src/mcp/mcp-server.js +17 -0
  45. package/src/mcp/tool-defs.js +3 -0
  46. package/src/mcp/tools.js +25 -0
  47. package/src/network/backend-lifecycle.js +19 -0
  48. package/src/network/backend.js +5 -0
  49. package/src/network/local-gateway.js +23 -0
  50. package/src/network/mdns.js +13 -0
  51. package/src/network/server.js +10 -0
  52. package/src/network/web-server.js +34 -0
  53. package/web/.project-graph-cache.json +1 -0
  54. package/web/app.js +17 -0
  55. package/web/components/code-block.js +3 -0
  56. package/web/components/quick-open.js +5 -0
  57. package/web/dashboard-state.js +3 -0
  58. package/web/dashboard.html +27 -0
  59. package/web/dashboard.js +8 -0
  60. package/web/highlight.js +13 -0
  61. package/web/index.html +35 -0
  62. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  63. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  64. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  65. package/web/panels/EventItem/EventItem.css.js +1 -0
  66. package/web/panels/EventItem/EventItem.js +4 -0
  67. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  69. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  70. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  72. package/web/panels/ProjectList/ProjectList.js +4 -0
  73. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  74. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  77. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  78. package/web/panels/code-viewer.js +5 -0
  79. package/web/panels/ctx-panel.js +4 -0
  80. package/web/panels/dep-graph.js +6 -0
  81. package/web/panels/file-tree.js +188 -0
  82. package/web/panels/health-panel.js +3 -0
  83. package/web/panels/live-monitor.js +3 -0
  84. package/web/state.js +17 -0
  85. package/web/style.css +157 -0
  86. package/references/symbiote-3x.md +0 -834
  87. package/src/ai-context.js +0 -113
  88. package/src/analysis-cache.js +0 -155
  89. package/src/cli-handlers.js +0 -271
  90. package/src/cli.js +0 -95
  91. package/src/compact.js +0 -207
  92. package/src/complexity.js +0 -237
  93. package/src/compress.js +0 -319
  94. package/src/ctx-to-jsdoc.js +0 -514
  95. package/src/custom-rules.js +0 -584
  96. package/src/db-analysis.js +0 -194
  97. package/src/dead-code.js +0 -468
  98. package/src/doc-dialect.js +0 -716
  99. package/src/filters.js +0 -227
  100. package/src/framework-references.js +0 -177
  101. package/src/full-analysis.js +0 -470
  102. package/src/graph-builder.js +0 -299
  103. package/src/instructions.js +0 -73
  104. package/src/jsdoc-checker.js +0 -351
  105. package/src/jsdoc-generator.js +0 -203
  106. package/src/lang-go.js +0 -285
  107. package/src/lang-python.js +0 -197
  108. package/src/lang-sql.js +0 -309
  109. package/src/lang-typescript.js +0 -190
  110. package/src/lang-utils.js +0 -124
  111. package/src/large-files.js +0 -163
  112. package/src/mcp-server.js +0 -675
  113. package/src/mode-config.js +0 -127
  114. package/src/outdated-patterns.js +0 -296
  115. package/src/parser.js +0 -662
  116. package/src/server.js +0 -28
  117. package/src/similar-functions.js +0 -279
  118. package/src/test-annotations.js +0 -323
  119. package/src/tool-defs.js +0 -793
  120. package/src/tools.js +0 -470
  121. package/src/type-checker.js +0 -188
  122. package/src/undocumented.js +0 -259
  123. package/src/workspace.js +0 -70
  124. /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
  125. /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
@@ -1,470 +0,0 @@
1
- /**
2
- * Full Analysis - Comprehensive Code Health Report
3
- * Runs all analysis tools and generates a health score
4
- *
5
- * Uses incremental caching for per-file metrics (complexity, undocumented, jsdocConsistency).
6
- * Cross-file metrics (dead code, similarity) always run dynamically.
7
- */
8
-
9
- import { readFileSync, readdirSync, statSync } from 'fs';
10
- import { join, relative, resolve } from 'path';
11
- import { getDeadCode } from './dead-code.js';
12
- import { checkUndocumentedFile } from './undocumented.js';
13
- import { getSimilarFunctions } from './similar-functions.js';
14
- import { analyzeComplexityFile } from './complexity.js';
15
- import { getLargeFiles } from './large-files.js';
16
- import { getOutdatedPatterns } from './outdated-patterns.js';
17
- import { getTableUsage } from './db-analysis.js';
18
- import { checkJSDocFile } from './jsdoc-checker.js';
19
- import { readCache, writeCache, computeContentHash, isCacheValid } from './analysis-cache.js';
20
- import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
21
- import { getWorkspaceRoot } from './workspace.js';
22
-
23
- /**
24
- * @typedef {Object} AnalysisResult
25
- * @property {Object} deadCode
26
- * @property {Object} undocumented
27
- * @property {Object} similar
28
- * @property {Object} complexity
29
- * @property {Object} largeFiles
30
- * @property {Object} outdated
31
- * @property {Object} overall
32
- */
33
-
34
- /**
35
- * Calculate health score from analysis results
36
- * @param {Object} results
37
- * @returns {{score: number, rating: string, topIssues: string[]}}
38
- */
39
- function calculateHealthScore(results) {
40
- let score = 100;
41
- const topIssues = [];
42
-
43
- // Dead code penalty: -2 per item (max -20)
44
- const deadPenalty = Math.min(results.deadCode.total * 2, 20);
45
- score -= deadPenalty;
46
- if (results.deadCode.total > 0) {
47
- topIssues.push(`${results.deadCode.total} unused functions/classes`);
48
- }
49
-
50
- // Undocumented penalty: -0.5 per item (max -15)
51
- const undocPenalty = Math.min(results.undocumented.total * 0.5, 15);
52
- score -= undocPenalty;
53
- if (results.undocumented.total > 10) {
54
- topIssues.push(`${results.undocumented.total} undocumented items`);
55
- }
56
-
57
- // Similar functions penalty: -3 per pair (max -15)
58
- const similarPenalty = Math.min(results.similar.total * 3, 15);
59
- score -= similarPenalty;
60
- if (results.similar.total > 0) {
61
- topIssues.push(`${results.similar.total} similar function pairs`);
62
- }
63
-
64
- // Complexity penalty: -5 per critical, -2 per high (max -20)
65
- const criticalCount = results.complexity.stats?.critical || 0;
66
- const highCount = results.complexity.stats?.high || 0;
67
- const complexityPenalty = Math.min(criticalCount * 5 + highCount * 2, 20);
68
- score -= complexityPenalty;
69
- if (criticalCount > 0) {
70
- topIssues.push(`${criticalCount} critical complexity functions`);
71
- }
72
-
73
- // Large files penalty: -4 per critical, -1 per warning (max -10)
74
- const largeCritical = results.largeFiles.stats?.critical || 0;
75
- const largeWarning = results.largeFiles.stats?.warning || 0;
76
- const largePenalty = Math.min(largeCritical * 4 + largeWarning * 1, 10);
77
- score -= largePenalty;
78
- if (largeCritical > 0) {
79
- topIssues.push(`${largeCritical} files need splitting`);
80
- }
81
-
82
- // Outdated patterns penalty: -3 per error, -1 per warning (max -10)
83
- const errorPatterns = results.outdated.stats?.bySeverity?.error || 0;
84
- const warningPatterns = results.outdated.stats?.bySeverity?.warning || 0;
85
- const outdatedPenalty = Math.min(errorPatterns * 3 + warningPatterns * 1, 10);
86
- score -= outdatedPenalty;
87
- if (results.outdated.redundantDeps?.length > 0) {
88
- topIssues.push(`${results.outdated.redundantDeps.length} redundant npm dependencies`);
89
- }
90
-
91
- // JSDoc consistency penalty: -2 per error, -1 per warning (max -15)
92
- if (results.jsdocConsistency) {
93
- const jsdocErrors = results.jsdocConsistency.errors || 0;
94
- const jsdocWarnings = results.jsdocConsistency.warnings || 0;
95
- const jsdocPenalty = Math.min(jsdocErrors * 2 + jsdocWarnings * 1, 15);
96
- score -= jsdocPenalty;
97
- if (jsdocErrors > 0) {
98
- topIssues.push(`${jsdocErrors} JSDoc consistency errors`);
99
- }
100
- }
101
-
102
- // Clamp score
103
- score = Math.max(0, Math.min(100, Math.round(score)));
104
-
105
- // Determine rating
106
- let rating;
107
- if (score >= 90) rating = 'excellent';
108
- else if (score >= 70) rating = 'good';
109
- else if (score >= 50) rating = 'warning';
110
- else rating = 'critical';
111
-
112
- return { score, rating, topIssues: topIssues.slice(0, 5) };
113
- }
114
-
115
- /**
116
- * Find all JS files in directory
117
- * @param {string} dir
118
- * @param {string} rootDir
119
- * @returns {string[]}
120
- */
121
- function findJSFiles(dir, rootDir = dir) {
122
- if (dir === rootDir) parseGitignore(rootDir);
123
- const files = [];
124
- try {
125
- for (const entry of readdirSync(dir)) {
126
- const fullPath = join(dir, entry);
127
- const relativePath = relative(rootDir, fullPath);
128
- const stat = statSync(fullPath);
129
- if (stat.isDirectory()) {
130
- if (!shouldExcludeDir(entry, relativePath)) {
131
- files.push(...findJSFiles(fullPath, rootDir));
132
- }
133
- } else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
134
- if (!shouldExcludeFile(entry, relativePath)) {
135
- files.push(fullPath);
136
- }
137
- }
138
- }
139
- } catch (e) { /* dir not found */ }
140
- return files;
141
- }
142
-
143
- /**
144
- * Run cacheable per-file analyses with cache support
145
- * Returns aggregated complexity, undocumented, and jsdoc results
146
- * @param {string} dir
147
- * @param {string} contextDir
148
- * @returns {{ complexity: Object[], undocumented: Object[], jsdocIssues: Object[], cacheStats: { hits: number, misses: number } }}
149
- */
150
- function runCacheableAnalyses(dir, contextDir) {
151
- const resolvedDir = resolve(dir);
152
- const wsRoot = getWorkspaceRoot();
153
- const files = findJSFiles(dir);
154
-
155
- const allComplexity = [];
156
- const allUndocumented = [];
157
- const allJsdocIssues = [];
158
- let cacheHits = 0;
159
- let cacheMisses = 0;
160
-
161
- for (const file of files) {
162
- const relPath = relative(resolvedDir, file);
163
- // Cache key: workspace-relative (src/parser.js), matches graph paths
164
- const cacheKey = relative(wsRoot, file);
165
- let code;
166
- try {
167
- code = readFileSync(file, 'utf-8');
168
- } catch (e) {
169
- continue; // File deleted between findJSFiles and read
170
- }
171
- const contentHash = computeContentHash(code);
172
-
173
- // Check cache (key: workspace-relative)
174
- const cached = readCache(contextDir, cacheKey);
175
-
176
- if (cached && isCacheValid(cached, cached.sig, contentHash, 'content')) {
177
- // Cache hit — use cached results
178
- cacheHits++;
179
- if (cached.complexity) allComplexity.push(...cached.complexity);
180
- if (cached.undocumented) allUndocumented.push(...cached.undocumented);
181
- if (cached.jsdocIssues) allJsdocIssues.push(...cached.jsdocIssues);
182
- } else {
183
- // Cache miss — compute fresh
184
- cacheMisses++;
185
- const complexity = analyzeComplexityFile(code, relPath);
186
- const undocumented = checkUndocumentedFile(code, relPath, 'tests');
187
- const jsdocIssues = checkJSDocFile(code, relPath);
188
-
189
- allComplexity.push(...complexity);
190
- allUndocumented.push(...undocumented);
191
- allJsdocIssues.push(...jsdocIssues);
192
-
193
- // Save to cache (key: workspace-relative)
194
- writeCache(contextDir, cacheKey, {
195
- sig: cached?.sig || contentHash,
196
- contentHash,
197
- complexity,
198
- undocumented,
199
- jsdocIssues,
200
- });
201
- }
202
- }
203
-
204
- return {
205
- complexity: allComplexity,
206
- undocumented: allUndocumented,
207
- jsdocIssues: allJsdocIssues,
208
- cacheStats: { hits: cacheHits, misses: cacheMisses },
209
- };
210
- }
211
-
212
- /**
213
- * Aggregate complexity items into summary format
214
- * @param {Object[]} items
215
- * @param {number} minComplexity
216
- * @returns {Object}
217
- */
218
- function aggregateComplexity(items, minComplexity = 5) {
219
- let filtered = items.filter(i => i.complexity >= minComplexity);
220
- filtered.sort((a, b) => b.complexity - a.complexity);
221
-
222
- const stats = {
223
- low: filtered.filter(i => i.rating === 'low').length,
224
- moderate: filtered.filter(i => i.rating === 'moderate').length,
225
- high: filtered.filter(i => i.rating === 'high').length,
226
- critical: filtered.filter(i => i.rating === 'critical').length,
227
- average: filtered.length > 0
228
- ? Math.round(filtered.reduce((s, i) => s + i.complexity, 0) / filtered.length * 10) / 10
229
- : 0,
230
- };
231
-
232
- return { total: filtered.length, stats, items: filtered.slice(0, 30) };
233
- }
234
-
235
- /**
236
- * Aggregate undocumented items into summary format
237
- * @param {Object[]} items
238
- * @returns {Object}
239
- */
240
- function aggregateUndocumented(items) {
241
- const byType = {
242
- class: items.filter(i => i.type === 'class').length,
243
- function: items.filter(i => i.type === 'function').length,
244
- method: items.filter(i => i.type === 'method').length,
245
- };
246
- return { total: items.length, byType, items: items.slice(0, 20) };
247
- }
248
-
249
- /**
250
- * Aggregate JSDoc issues into summary format
251
- * @param {Object[]} issues
252
- * @returns {{ issues: Object[], summary: Object }}
253
- */
254
- function aggregateJSDoc(issues) {
255
- const errors = issues.filter(i => i.severity === 'error').length;
256
- const warnings = issues.filter(i => i.severity === 'warning').length;
257
- const byFile = {};
258
- for (const issue of issues) {
259
- byFile[issue.file] = (byFile[issue.file] || 0) + 1;
260
- }
261
- return { issues, summary: { total: issues.length, errors, warnings, byFile } };
262
- }
263
-
264
- /**
265
- * Run full analysis on directory
266
- * Uses incremental cache for per-file metrics; cross-file metrics always recompute.
267
- * @param {string} dir
268
- * @param {Object} [options]
269
- * @param {boolean} [options.includeItems=false] - Include individual items
270
- * @returns {Promise<AnalysisResult>}
271
- */
272
- export async function getFullAnalysis(dir, options = {}) {
273
- const includeItems = options.includeItems || false;
274
- const resolvedDir = resolve(dir);
275
- const contextDir = join(getWorkspaceRoot(), '.context');
276
-
277
- // Run cacheable per-file analyses (complexity, undocumented, jsdoc)
278
- const cached = runCacheableAnalyses(dir, contextDir);
279
- const complexity = aggregateComplexity(cached.complexity);
280
- const undocumented = aggregateUndocumented(cached.undocumented);
281
- const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
282
-
283
- // Run cross-file analyses (always dynamic — NOT cacheable per-file)
284
- const [deadCode, similar, largeFiles, outdated, dbUsage] = await Promise.all([
285
- getDeadCode(dir).catch(() => ({ total: 0, byType: {}, items: [] })),
286
- getSimilarFunctions(dir, { threshold: 70 }).catch(() => ({ total: 0, pairs: [] })),
287
- getLargeFiles(dir).catch(() => ({ total: 0, stats: {}, items: [] })),
288
- getOutdatedPatterns(dir).catch(() => ({ codePatterns: [], redundantDeps: [], stats: { totalPatterns: 0, bySeverity: {}, byPattern: {}, redundantDeps: 0 } })),
289
- getTableUsage(dir).catch(() => ({ tables: [], totalTables: 0, totalQueries: 0 })),
290
- ]);
291
-
292
- // Calculate overall health
293
- const overall = calculateHealthScore({
294
- deadCode,
295
- undocumented,
296
- similar,
297
- complexity,
298
- largeFiles,
299
- outdated,
300
- jsdocConsistency: jsdocCheck.summary,
301
- });
302
-
303
- // Build result
304
- const result = {
305
- deadCode: {
306
- total: deadCode.total,
307
- byType: deadCode.byType,
308
- ...(includeItems && { items: deadCode.items.slice(0, 10) }),
309
- },
310
- undocumented: {
311
- total: undocumented.total,
312
- byType: undocumented.byType,
313
- ...(includeItems && { items: undocumented.items.slice(0, 10) }),
314
- },
315
- similar: {
316
- total: similar.total,
317
- ...(includeItems && { pairs: similar.pairs.slice(0, 5) }),
318
- },
319
- complexity: {
320
- total: complexity.total,
321
- stats: complexity.stats,
322
- ...(includeItems && { items: complexity.items.slice(0, 10) }),
323
- },
324
- largeFiles: {
325
- total: largeFiles.total,
326
- stats: largeFiles.stats,
327
- ...(includeItems && { items: largeFiles.items.slice(0, 10) }),
328
- },
329
- outdated: {
330
- totalPatterns: outdated.stats.totalPatterns,
331
- redundantDeps: outdated.redundantDeps,
332
- ...(includeItems && { codePatterns: outdated.codePatterns.slice(0, 10) }),
333
- },
334
- jsdocConsistency: {
335
- total: jsdocCheck.summary.total,
336
- errors: jsdocCheck.summary.errors,
337
- warnings: jsdocCheck.summary.warnings,
338
- ...(includeItems && { issues: jsdocCheck.issues.slice(0, 10) }),
339
- },
340
- cache: cached.cacheStats,
341
- overall,
342
- };
343
-
344
- // Add DB metrics if any SQL interactions found (non-scoring)
345
- if (dbUsage.totalTables > 0) {
346
- result.database = {
347
- tablesUsed: dbUsage.totalTables,
348
- totalQueries: dbUsage.totalQueries,
349
- tables: dbUsage.tables.map(t => ({
350
- name: t.table,
351
- readers: t.totalReaders,
352
- writers: t.totalWriters,
353
- })),
354
- };
355
- }
356
-
357
- return result;
358
- }
359
-
360
- /**
361
- * Quick health check — runs only cached per-file metrics, skips cross-file.
362
- * @param {string} dir - Path to scan
363
- * @returns {{healthScore: number, complexity: number, undocumented: number, jsdocIssues: number}}
364
- */
365
- export function getAnalysisSummaryOnly(dir) {
366
- const contextDir = join(getWorkspaceRoot(), '.context');
367
- const cached = runCacheableAnalyses(dir, contextDir);
368
- const complexity = aggregateComplexity(cached.complexity);
369
- const undocumented = aggregateUndocumented(cached.undocumented);
370
- const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
371
-
372
- // Reuse the same health score formula as getFullAnalysis
373
- const overall = calculateHealthScore({
374
- deadCode: { total: 0 },
375
- undocumented,
376
- similar: { total: 0 },
377
- complexity,
378
- largeFiles: { total: 0 },
379
- outdated: { stats: { totalPatterns: 0 } },
380
- jsdocConsistency: jsdocCheck.summary,
381
- });
382
-
383
- return {
384
- healthScore: overall.score,
385
- grade: overall.rating,
386
- complexity: complexity.total,
387
- undocumented: undocumented.total,
388
- jsdocIssues: jsdocCheck.summary.total,
389
- cache: cached.cacheStats,
390
- note: 'Partial score — cross-file analyses skipped for speed. Run get_full_analysis for complete health check.',
391
- };
392
- }
393
-
394
- /**
395
- * Streaming analysis — yields results as each sub-analysis completes.
396
- * Useful for large codebases where waiting for all analyses is too slow.
397
- * @param {string} dir - Path to scan
398
- * @param {Object} [options]
399
- * @param {boolean} [options.includeItems=false]
400
- * @returns {AsyncGenerator<{type: string, data: Object}>}
401
- */
402
- export async function* getFullAnalysisStreaming(dir, options = {}) {
403
- const includeItems = options.includeItems || false;
404
- const contextDir = join(getWorkspaceRoot(), '.context');
405
-
406
- // Phase 1: Cached per-file analyses (fast)
407
- const cached = runCacheableAnalyses(dir, contextDir);
408
-
409
- const complexity = aggregateComplexity(cached.complexity);
410
- yield { type: 'complexity', data: {
411
- total: complexity.total,
412
- stats: complexity.stats,
413
- ...(includeItems && { items: complexity.items.slice(0, 10) }),
414
- }};
415
-
416
- const undocumented = aggregateUndocumented(cached.undocumented);
417
- yield { type: 'undocumented', data: {
418
- total: undocumented.total,
419
- byType: undocumented.byType,
420
- ...(includeItems && { items: undocumented.items.slice(0, 10) }),
421
- }};
422
-
423
- const jsdocCheck = aggregateJSDoc(cached.jsdocIssues);
424
- yield { type: 'jsdocConsistency', data: {
425
- total: jsdocCheck.summary.total,
426
- errors: jsdocCheck.summary.errors,
427
- warnings: jsdocCheck.summary.warnings,
428
- ...(includeItems && { issues: jsdocCheck.issues.slice(0, 10) }),
429
- }};
430
-
431
- yield { type: 'cache', data: cached.cacheStats };
432
-
433
- // Phase 2: Cross-file analyses (slow, one at a time)
434
- try {
435
- const deadCode = await getDeadCode(dir);
436
- yield { type: 'deadCode', data: {
437
- total: deadCode.total,
438
- byType: deadCode.byType,
439
- ...(includeItems && { items: deadCode.items.slice(0, 10) }),
440
- }};
441
- } catch { yield { type: 'deadCode', data: { total: 0, byType: {}, error: 'analysis failed' } }; }
442
-
443
- try {
444
- const similar = await getSimilarFunctions(dir, { threshold: 70 });
445
- yield { type: 'similar', data: {
446
- total: similar.total,
447
- ...(includeItems && { pairs: similar.pairs.slice(0, 5) }),
448
- }};
449
- } catch { yield { type: 'similar', data: { total: 0, error: 'analysis failed' } }; }
450
-
451
- try {
452
- const largeFiles = await getLargeFiles(dir);
453
- yield { type: 'largeFiles', data: {
454
- total: largeFiles.total,
455
- stats: largeFiles.stats,
456
- ...(includeItems && { items: largeFiles.items.slice(0, 10) }),
457
- }};
458
- } catch { yield { type: 'largeFiles', data: { total: 0, error: 'analysis failed' } }; }
459
-
460
- try {
461
- const outdated = await getOutdatedPatterns(dir);
462
- yield { type: 'outdated', data: {
463
- totalPatterns: outdated.stats.totalPatterns,
464
- redundantDeps: outdated.redundantDeps,
465
- ...(includeItems && { codePatterns: outdated.codePatterns.slice(0, 10) }),
466
- }};
467
- } catch { yield { type: 'outdated', data: { totalPatterns: 0, error: 'analysis failed' } }; }
468
-
469
- yield { type: 'done', data: { phases: 2, timestamp: new Date().toISOString() } };
470
- }