code-graph-context 2.0.1 → 2.3.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 +221 -2
- package/dist/constants.js +167 -0
- package/dist/core/config/fairsquare-framework-schema.js +9 -7
- package/dist/core/config/schema.js +41 -2
- package/dist/core/embeddings/natural-language-to-cypher.service.js +166 -110
- package/dist/core/parsers/typescript-parser.js +1039 -742
- package/dist/core/parsers/workspace-parser.js +175 -193
- package/dist/core/utils/code-normalizer.js +299 -0
- package/dist/core/utils/file-change-detection.js +17 -2
- package/dist/core/utils/file-utils.js +40 -5
- package/dist/core/utils/graph-factory.js +161 -0
- package/dist/core/utils/shared-utils.js +79 -0
- package/dist/core/workspace/workspace-detector.js +59 -5
- package/dist/mcp/constants.js +261 -8
- package/dist/mcp/handlers/graph-generator.handler.js +1 -0
- package/dist/mcp/handlers/incremental-parse.handler.js +22 -6
- package/dist/mcp/handlers/parallel-import.handler.js +136 -0
- package/dist/mcp/handlers/streaming-import.handler.js +14 -59
- package/dist/mcp/mcp.server.js +77 -2
- package/dist/mcp/services/job-manager.js +5 -8
- package/dist/mcp/services/watch-manager.js +64 -25
- package/dist/mcp/tools/detect-dead-code.tool.js +413 -0
- package/dist/mcp/tools/detect-duplicate-code.tool.js +450 -0
- package/dist/mcp/tools/hello.tool.js +16 -2
- package/dist/mcp/tools/impact-analysis.tool.js +20 -4
- package/dist/mcp/tools/index.js +37 -0
- package/dist/mcp/tools/parse-typescript-project.tool.js +15 -14
- package/dist/mcp/tools/swarm-cleanup.tool.js +157 -0
- package/dist/mcp/tools/swarm-constants.js +35 -0
- package/dist/mcp/tools/swarm-pheromone.tool.js +196 -0
- package/dist/mcp/tools/swarm-sense.tool.js +212 -0
- package/dist/mcp/workers/chunk-worker-pool.js +196 -0
- package/dist/mcp/workers/chunk-worker.types.js +4 -0
- package/dist/mcp/workers/chunk.worker.js +89 -0
- package/dist/mcp/workers/parse-coordinator.js +183 -0
- package/dist/mcp/workers/worker.pool.js +54 -0
- package/dist/storage/neo4j/neo4j.service.js +198 -14
- package/package.json +1 -1
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect Dead Code Tool
|
|
3
|
+
* Identifies potentially unused code in the codebase
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { toNumber, isUIComponent, isPackageExport, isExcludedByPattern, getShortPath, } from '../../core/utils/shared-utils.js';
|
|
7
|
+
import { Neo4jService, QUERIES } from '../../storage/neo4j/neo4j.service.js';
|
|
8
|
+
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
9
|
+
import { createErrorResponse, createSuccessResponse, debugLog, resolveProjectIdOrError } from '../utils.js';
|
|
10
|
+
// Default file patterns to exclude
|
|
11
|
+
const DEFAULT_ENTRY_POINT_FILE_PATTERNS = [
|
|
12
|
+
// Common entry points
|
|
13
|
+
'main.ts',
|
|
14
|
+
'app.ts',
|
|
15
|
+
'index.ts',
|
|
16
|
+
// NestJS
|
|
17
|
+
'*.module.ts',
|
|
18
|
+
'*.controller.ts',
|
|
19
|
+
// Fastify / Express
|
|
20
|
+
'*.routes.ts',
|
|
21
|
+
'*.router.ts',
|
|
22
|
+
'*.handler.ts',
|
|
23
|
+
'server.ts',
|
|
24
|
+
// Next.js / React frameworks (file-based routing)
|
|
25
|
+
'page.tsx',
|
|
26
|
+
'page.ts',
|
|
27
|
+
'layout.tsx',
|
|
28
|
+
'layout.ts',
|
|
29
|
+
'route.tsx',
|
|
30
|
+
'route.ts',
|
|
31
|
+
'loading.tsx',
|
|
32
|
+
'error.tsx',
|
|
33
|
+
'not-found.tsx',
|
|
34
|
+
'template.tsx',
|
|
35
|
+
'default.tsx',
|
|
36
|
+
// Remix
|
|
37
|
+
'root.tsx',
|
|
38
|
+
// Astro
|
|
39
|
+
'*.astro',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Determine confidence level based on code characteristics.
|
|
43
|
+
* Returns both the level and a human-readable explanation.
|
|
44
|
+
*/
|
|
45
|
+
const determineConfidence = (item) => {
|
|
46
|
+
// HIGH: Exported but definitively never imported
|
|
47
|
+
if (item.isExported && item.reason.includes('never imported')) {
|
|
48
|
+
return { level: 'HIGH', reason: 'Exported but never imported anywhere' };
|
|
49
|
+
}
|
|
50
|
+
// MEDIUM: Private with no internal calls
|
|
51
|
+
if (item.visibility === 'private') {
|
|
52
|
+
return { level: 'MEDIUM', reason: 'Private method with no internal callers' };
|
|
53
|
+
}
|
|
54
|
+
// LOW: Could be used dynamically
|
|
55
|
+
return { level: 'LOW', reason: 'Could be used via dynamic references' };
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Calculate risk level based on dead code count.
|
|
59
|
+
*/
|
|
60
|
+
const getRiskLevel = (totalCount, highCount) => {
|
|
61
|
+
if (highCount >= 20 || totalCount >= 50)
|
|
62
|
+
return 'CRITICAL';
|
|
63
|
+
if (highCount >= 10 || totalCount >= 25)
|
|
64
|
+
return 'HIGH';
|
|
65
|
+
if (highCount >= 5 || totalCount >= 10)
|
|
66
|
+
return 'MEDIUM';
|
|
67
|
+
return 'LOW';
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Check if confidence meets minimum threshold.
|
|
71
|
+
*/
|
|
72
|
+
const shouldInclude = (confidence, minConfidence) => {
|
|
73
|
+
const levels = ['LOW', 'MEDIUM', 'HIGH'];
|
|
74
|
+
return levels.indexOf(confidence) >= levels.indexOf(minConfidence);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Check if semantic type is excluded.
|
|
78
|
+
*/
|
|
79
|
+
const isExcludedBySemanticType = (semanticType, excludeTypes) => {
|
|
80
|
+
return semanticType != null && excludeTypes.includes(semanticType);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Determine category of dead code item based on file path.
|
|
84
|
+
*/
|
|
85
|
+
const determineCategory = (filePath) => {
|
|
86
|
+
if (isUIComponent(filePath))
|
|
87
|
+
return 'ui-component';
|
|
88
|
+
if (isPackageExport(filePath))
|
|
89
|
+
return 'library-export';
|
|
90
|
+
return 'internal-unused';
|
|
91
|
+
};
|
|
92
|
+
export const createDetectDeadCodeTool = (server) => {
|
|
93
|
+
server.registerTool(TOOL_NAMES.detectDeadCode, {
|
|
94
|
+
title: TOOL_METADATA[TOOL_NAMES.detectDeadCode].title,
|
|
95
|
+
description: TOOL_METADATA[TOOL_NAMES.detectDeadCode].description,
|
|
96
|
+
inputSchema: {
|
|
97
|
+
projectId: z.string().describe('Project ID, name, or path (e.g., "backend" or "proj_a1b2c3d4e5f6")'),
|
|
98
|
+
excludePatterns: z
|
|
99
|
+
.array(z.string())
|
|
100
|
+
.optional()
|
|
101
|
+
.describe('Additional file patterns to exclude as entry points (e.g., ["*.config.ts", "*.seed.ts"])'),
|
|
102
|
+
excludeSemanticTypes: z
|
|
103
|
+
.array(z.string())
|
|
104
|
+
.optional()
|
|
105
|
+
.describe('Additional semantic types to exclude (e.g., ["EntityClass", "DTOClass"])'),
|
|
106
|
+
includeEntryPoints: z
|
|
107
|
+
.boolean()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe('Include excluded entry points in a separate audit section for review (default: true). ' +
|
|
110
|
+
'Entry points are always excluded from main results.')
|
|
111
|
+
.default(true),
|
|
112
|
+
minConfidence: z
|
|
113
|
+
.enum(['LOW', 'MEDIUM', 'HIGH'])
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('Minimum confidence level to include in results (default: LOW)')
|
|
116
|
+
.default('LOW'),
|
|
117
|
+
summaryOnly: z
|
|
118
|
+
.boolean()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Return only summary statistics without full dead code list (default: false)')
|
|
121
|
+
.default(false),
|
|
122
|
+
limit: z
|
|
123
|
+
.number()
|
|
124
|
+
.int()
|
|
125
|
+
.min(1)
|
|
126
|
+
.max(500)
|
|
127
|
+
.optional()
|
|
128
|
+
.describe('Maximum number of dead code items to return per page (default: 100)')
|
|
129
|
+
.default(100),
|
|
130
|
+
offset: z
|
|
131
|
+
.number()
|
|
132
|
+
.int()
|
|
133
|
+
.min(0)
|
|
134
|
+
.optional()
|
|
135
|
+
.describe('Number of items to skip for pagination (default: 0)')
|
|
136
|
+
.default(0),
|
|
137
|
+
filterCategory: z
|
|
138
|
+
.enum(['library-export', 'ui-component', 'internal-unused', 'all'])
|
|
139
|
+
.optional()
|
|
140
|
+
.describe('Filter by category: library-export, ui-component, internal-unused, or all (default: all)')
|
|
141
|
+
.default('all'),
|
|
142
|
+
excludeLibraryExports: z
|
|
143
|
+
.boolean()
|
|
144
|
+
.optional()
|
|
145
|
+
.describe('Exclude all items from packages/* directories (default: false)')
|
|
146
|
+
.default(false),
|
|
147
|
+
excludeCoreTypes: z
|
|
148
|
+
.array(z.string())
|
|
149
|
+
.optional()
|
|
150
|
+
.describe('Exclude specific core types from results (e.g., ["InterfaceDeclaration", "TypeAliasDeclaration"] to skip type definitions)')
|
|
151
|
+
.default([]),
|
|
152
|
+
},
|
|
153
|
+
}, async ({ projectId, excludePatterns = [], excludeSemanticTypes = [], includeEntryPoints = true, minConfidence = 'LOW', summaryOnly = false, limit = 100, offset = 0, filterCategory = 'all', excludeLibraryExports = false, excludeCoreTypes = [], }) => {
|
|
154
|
+
const neo4jService = new Neo4jService();
|
|
155
|
+
try {
|
|
156
|
+
// Resolve project ID
|
|
157
|
+
const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
|
|
158
|
+
if (!projectResult.success)
|
|
159
|
+
return projectResult.error;
|
|
160
|
+
const resolvedProjectId = projectResult.projectId;
|
|
161
|
+
await debugLog('Dead code detection started', {
|
|
162
|
+
projectId: resolvedProjectId,
|
|
163
|
+
excludePatterns,
|
|
164
|
+
excludeSemanticTypes,
|
|
165
|
+
minConfidence,
|
|
166
|
+
});
|
|
167
|
+
// Query project's actual semantic types (data-driven, per-project detection)
|
|
168
|
+
const semanticTypesResult = (await neo4jService.run(QUERIES.GET_PROJECT_SEMANTIC_TYPES, {
|
|
169
|
+
projectId: resolvedProjectId,
|
|
170
|
+
}));
|
|
171
|
+
const projectSemanticTypes = semanticTypesResult.map((r) => r.semanticType);
|
|
172
|
+
// Combine project semantic types with user-provided exclusions
|
|
173
|
+
const allExcludeSemanticTypes = [...projectSemanticTypes, ...excludeSemanticTypes];
|
|
174
|
+
const allExcludePatterns = [...DEFAULT_ENTRY_POINT_FILE_PATTERNS, ...excludePatterns];
|
|
175
|
+
// Run all queries in parallel for better performance
|
|
176
|
+
const [unreferencedExports, uncalledPrivateMethods, unreferencedInterfaces, entryPointsResult] = await Promise.all([
|
|
177
|
+
// 1. Find unreferenced exports
|
|
178
|
+
neo4jService.run(QUERIES.FIND_UNREFERENCED_EXPORTS, {
|
|
179
|
+
projectId: resolvedProjectId,
|
|
180
|
+
}),
|
|
181
|
+
// 2. Find uncalled private methods
|
|
182
|
+
neo4jService.run(QUERIES.FIND_UNCALLED_PRIVATE_METHODS, {
|
|
183
|
+
projectId: resolvedProjectId,
|
|
184
|
+
}),
|
|
185
|
+
// 3. Find unreferenced interfaces
|
|
186
|
+
neo4jService.run(QUERIES.FIND_UNREFERENCED_INTERFACES, {
|
|
187
|
+
projectId: resolvedProjectId,
|
|
188
|
+
}),
|
|
189
|
+
// 4. Get framework entry points for exclusion/audit (using project's semantic types)
|
|
190
|
+
neo4jService.run(QUERIES.GET_FRAMEWORK_ENTRY_POINTS, {
|
|
191
|
+
projectId: resolvedProjectId,
|
|
192
|
+
semanticTypes: allExcludeSemanticTypes,
|
|
193
|
+
}),
|
|
194
|
+
]);
|
|
195
|
+
// Create set of entry point IDs for filtering
|
|
196
|
+
const entryPointIds = new Set(entryPointsResult.map((r) => r.nodeId));
|
|
197
|
+
// Process and filter results
|
|
198
|
+
const deadCodeItems = [];
|
|
199
|
+
// Process unreferenced exports
|
|
200
|
+
for (const item of unreferencedExports) {
|
|
201
|
+
if (entryPointIds.has(item.nodeId))
|
|
202
|
+
continue;
|
|
203
|
+
if (isExcludedByPattern(item.filePath, allExcludePatterns))
|
|
204
|
+
continue;
|
|
205
|
+
if (isExcludedBySemanticType(item.semanticType, allExcludeSemanticTypes))
|
|
206
|
+
continue;
|
|
207
|
+
const confidence = determineConfidence({
|
|
208
|
+
isExported: true,
|
|
209
|
+
coreType: item.coreType,
|
|
210
|
+
reason: item.reason,
|
|
211
|
+
});
|
|
212
|
+
if (shouldInclude(confidence.level, minConfidence)) {
|
|
213
|
+
const category = determineCategory(item.filePath);
|
|
214
|
+
deadCodeItems.push({
|
|
215
|
+
nodeId: item.nodeId,
|
|
216
|
+
name: item.name,
|
|
217
|
+
type: item.coreType,
|
|
218
|
+
coreType: item.coreType,
|
|
219
|
+
semanticType: item.semanticType ?? null,
|
|
220
|
+
filePath: item.filePath,
|
|
221
|
+
lineNumber: toNumber(item.lineNumber),
|
|
222
|
+
confidence: confidence.level,
|
|
223
|
+
confidenceReason: confidence.reason,
|
|
224
|
+
category,
|
|
225
|
+
reason: item.reason,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Process uncalled private methods
|
|
230
|
+
for (const item of uncalledPrivateMethods) {
|
|
231
|
+
// Apply same exclusion checks as other dead code types
|
|
232
|
+
if (entryPointIds.has(item.nodeId))
|
|
233
|
+
continue;
|
|
234
|
+
if (isExcludedByPattern(item.filePath, allExcludePatterns))
|
|
235
|
+
continue;
|
|
236
|
+
if (isExcludedBySemanticType(item.semanticType, allExcludeSemanticTypes))
|
|
237
|
+
continue;
|
|
238
|
+
const confidence = determineConfidence({
|
|
239
|
+
isExported: false,
|
|
240
|
+
visibility: 'private',
|
|
241
|
+
coreType: item.coreType,
|
|
242
|
+
reason: item.reason,
|
|
243
|
+
});
|
|
244
|
+
if (shouldInclude(confidence.level, minConfidence)) {
|
|
245
|
+
const category = determineCategory(item.filePath);
|
|
246
|
+
deadCodeItems.push({
|
|
247
|
+
nodeId: item.nodeId,
|
|
248
|
+
name: item.name,
|
|
249
|
+
type: item.coreType,
|
|
250
|
+
coreType: item.coreType,
|
|
251
|
+
semanticType: item.semanticType ?? null,
|
|
252
|
+
filePath: item.filePath,
|
|
253
|
+
lineNumber: toNumber(item.lineNumber),
|
|
254
|
+
confidence: confidence.level,
|
|
255
|
+
confidenceReason: confidence.reason,
|
|
256
|
+
category,
|
|
257
|
+
reason: item.reason,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Process unreferenced interfaces
|
|
262
|
+
for (const item of unreferencedInterfaces) {
|
|
263
|
+
if (entryPointIds.has(item.nodeId))
|
|
264
|
+
continue;
|
|
265
|
+
if (isExcludedByPattern(item.filePath, allExcludePatterns))
|
|
266
|
+
continue;
|
|
267
|
+
const confidence = determineConfidence({
|
|
268
|
+
isExported: true,
|
|
269
|
+
coreType: item.coreType,
|
|
270
|
+
reason: item.reason,
|
|
271
|
+
});
|
|
272
|
+
if (shouldInclude(confidence.level, minConfidence)) {
|
|
273
|
+
const category = determineCategory(item.filePath);
|
|
274
|
+
deadCodeItems.push({
|
|
275
|
+
nodeId: item.nodeId,
|
|
276
|
+
name: item.name,
|
|
277
|
+
type: item.coreType,
|
|
278
|
+
coreType: item.coreType,
|
|
279
|
+
semanticType: item.semanticType ?? null,
|
|
280
|
+
filePath: item.filePath,
|
|
281
|
+
lineNumber: toNumber(item.lineNumber),
|
|
282
|
+
confidence: confidence.level,
|
|
283
|
+
confidenceReason: confidence.reason,
|
|
284
|
+
category,
|
|
285
|
+
reason: item.reason,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Apply exclusion filters
|
|
290
|
+
let filteredItems = deadCodeItems;
|
|
291
|
+
// Exclude library exports if requested
|
|
292
|
+
if (excludeLibraryExports) {
|
|
293
|
+
filteredItems = filteredItems.filter((i) => i.category !== 'library-export');
|
|
294
|
+
}
|
|
295
|
+
// Exclude specific core types if requested
|
|
296
|
+
if (excludeCoreTypes.length > 0) {
|
|
297
|
+
filteredItems = filteredItems.filter((i) => !excludeCoreTypes.includes(i.coreType));
|
|
298
|
+
}
|
|
299
|
+
// Apply category filter if specified
|
|
300
|
+
if (filterCategory !== 'all') {
|
|
301
|
+
filteredItems = filteredItems.filter((i) => i.category === filterCategory);
|
|
302
|
+
}
|
|
303
|
+
// Calculate statistics on ALL items (before filtering)
|
|
304
|
+
const byConfidence = {
|
|
305
|
+
HIGH: deadCodeItems.filter((i) => i.confidence === 'HIGH').length,
|
|
306
|
+
MEDIUM: deadCodeItems.filter((i) => i.confidence === 'MEDIUM').length,
|
|
307
|
+
LOW: deadCodeItems.filter((i) => i.confidence === 'LOW').length,
|
|
308
|
+
};
|
|
309
|
+
const byCategory = {
|
|
310
|
+
'library-export': deadCodeItems.filter((i) => i.category === 'library-export').length,
|
|
311
|
+
'ui-component': deadCodeItems.filter((i) => i.category === 'ui-component').length,
|
|
312
|
+
'internal-unused': deadCodeItems.filter((i) => i.category === 'internal-unused').length,
|
|
313
|
+
};
|
|
314
|
+
const byType = {};
|
|
315
|
+
for (const item of deadCodeItems) {
|
|
316
|
+
byType[item.type] = (byType[item.type] ?? 0) + 1;
|
|
317
|
+
}
|
|
318
|
+
// Use filtered items for affected files and output
|
|
319
|
+
const affectedFiles = [...new Set(filteredItems.map((i) => i.filePath))].sort();
|
|
320
|
+
const riskLevel = getRiskLevel(filteredItems.length, byConfidence.HIGH);
|
|
321
|
+
// Build entry points list for audit
|
|
322
|
+
const excludedEntryPoints = includeEntryPoints
|
|
323
|
+
? entryPointsResult.map((r) => ({
|
|
324
|
+
nodeId: r.nodeId,
|
|
325
|
+
name: r.name,
|
|
326
|
+
type: r.coreType ?? 'Unknown',
|
|
327
|
+
semanticType: r.semanticType ?? null,
|
|
328
|
+
filePath: r.filePath,
|
|
329
|
+
}))
|
|
330
|
+
: [];
|
|
331
|
+
// Build summary based on filter
|
|
332
|
+
const filterSuffix = filterCategory !== 'all' ? ` (filtered to ${filterCategory})` : '';
|
|
333
|
+
const summary = filteredItems.length === 0
|
|
334
|
+
? 'No potentially dead code found' + filterSuffix
|
|
335
|
+
: `Found ${filteredItems.length} potentially dead code items across ${affectedFiles.length} files` +
|
|
336
|
+
filterSuffix;
|
|
337
|
+
// Count entry points (always available)
|
|
338
|
+
const excludedEntryPointsCount = entryPointsResult.length;
|
|
339
|
+
// Compute top files by dead code count (used in both modes)
|
|
340
|
+
const fileDeadCodeCounts = {};
|
|
341
|
+
for (const item of filteredItems) {
|
|
342
|
+
const shortPath = getShortPath(item.filePath);
|
|
343
|
+
fileDeadCodeCounts[shortPath] = (fileDeadCodeCounts[shortPath] ?? 0) + 1;
|
|
344
|
+
}
|
|
345
|
+
const topFilesByDeadCode = Object.entries(fileDeadCodeCounts)
|
|
346
|
+
.sort((a, b) => b[1] - a[1])
|
|
347
|
+
.slice(0, 20)
|
|
348
|
+
.map(([file, count]) => ({ file, count }));
|
|
349
|
+
// Build result based on summaryOnly flag
|
|
350
|
+
let result;
|
|
351
|
+
if (summaryOnly) {
|
|
352
|
+
// Summary mode: statistics only, no full arrays
|
|
353
|
+
result = {
|
|
354
|
+
summary,
|
|
355
|
+
riskLevel,
|
|
356
|
+
totalCount: filteredItems.length,
|
|
357
|
+
totalBeforeFilter: deadCodeItems.length,
|
|
358
|
+
byConfidence,
|
|
359
|
+
byCategory,
|
|
360
|
+
byType,
|
|
361
|
+
affectedFiles,
|
|
362
|
+
topFilesByDeadCode,
|
|
363
|
+
excludedEntryPointsCount,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
// Paginated mode: apply limit/offset
|
|
368
|
+
const paginatedItems = filteredItems.slice(offset, offset + limit);
|
|
369
|
+
const hasMore = offset + limit < filteredItems.length;
|
|
370
|
+
result = {
|
|
371
|
+
summary,
|
|
372
|
+
riskLevel,
|
|
373
|
+
totalCount: filteredItems.length,
|
|
374
|
+
totalBeforeFilter: deadCodeItems.length,
|
|
375
|
+
byConfidence,
|
|
376
|
+
byCategory,
|
|
377
|
+
byType,
|
|
378
|
+
topFilesByDeadCode,
|
|
379
|
+
deadCode: paginatedItems,
|
|
380
|
+
pagination: {
|
|
381
|
+
offset,
|
|
382
|
+
limit,
|
|
383
|
+
returned: paginatedItems.length,
|
|
384
|
+
hasMore,
|
|
385
|
+
},
|
|
386
|
+
excludedEntryPointsCount,
|
|
387
|
+
// Only include full entry points array on first page
|
|
388
|
+
...(offset === 0 && includeEntryPoints ? { excludedEntryPoints } : {}),
|
|
389
|
+
affectedFiles,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
await debugLog('Dead code detection complete', {
|
|
393
|
+
projectId: resolvedProjectId,
|
|
394
|
+
totalCount: deadCodeItems.length,
|
|
395
|
+
filteredCount: filteredItems.length,
|
|
396
|
+
filterCategory,
|
|
397
|
+
riskLevel,
|
|
398
|
+
summaryOnly,
|
|
399
|
+
offset,
|
|
400
|
+
limit,
|
|
401
|
+
});
|
|
402
|
+
return createSuccessResponse(JSON.stringify(result, null, 2));
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
console.error('Dead code detection error:', error);
|
|
406
|
+
await debugLog('Dead code detection error', { projectId, error });
|
|
407
|
+
return createErrorResponse(error);
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
await neo4jService.close();
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
};
|