code_quality_analyzer
Analyze code quality by detecting duplicates, measuring complexity, identifying security issues, and finding code smells to improve maintainability.
Instructions
Run multi-signal quality checks: duplicates, complexity, smells, security.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rootDir | No | Root directory to analyze (default: workspace root) | |
| minSimilarity | No | Minimum similarity for duplicate detection (default: 0.85) | |
| includeTypes | No | Types of analysis to include (default: all) |
Implementation Reference
- src/tools/code-analysis.ts:989-1224 (handler)The main implementation of `code_quality_analyzer` tool. It analyzes code for duplicates, complexity, security issues, and code smells within a given workspace directory.
codeQualityAnalyzer(options?: { rootDir?: string; minSimilarity?: number; includeTypes?: Array<'duplicates' | 'complexity' | 'security' | 'dead_code' | 'smells'>; timeoutMs?: number; maxFiles?: number; }): CodeQualityResult { const startedAtMs = Date.now(); const timeoutMs = Math.max(1000, options?.timeoutMs ?? 30000); const deadlineMs = startedAtMs + timeoutMs; const timedOut = () => Date.now() > deadlineMs; const rootDir = options?.rootDir ?? this.config.getDefaultWorkspaceRoot(); // minSimilarity is available for future similarity threshold customization const _minSimilarity = options?.minSimilarity ?? 0.85; void _minSimilarity; // Reserved for future use const includeTypes = options?.includeTypes ?? [ 'duplicates', 'complexity', 'security', 'smells', ]; const issues: CodeQualityIssue[] = []; const root = this.config.resolveWorkspacePath(rootDir); if (!this.config.isPathAllowed(root)) { throw new Error(`Access denied: Path '${rootDir}' is not in the allowlist`); } const maxFiles = Math.max(1, options?.maxFiles ?? 5000); const files = walkDirectory(root, maxFiles); const warnings: string[] = []; let partial = false; const analyzedFiles = new Set<string>(); const supportedExts = new Set(['.ts', '.tsx', '.js', '.jsx', '.py']); const sourceFiles = files.filter((f) => supportedExts.has(extname(f.path).toLowerCase())); // Duplicates analysis if (includeTypes.includes('duplicates')) { const timeLeft = Math.max(0, deadlineMs - Date.now()); const dupBudgetMs = Math.min(15000, Math.floor(timeLeft * 0.6)); if (dupBudgetMs < 500) { warnings.push('Skipped duplicate-code analysis due to low remaining time budget.'); partial = true; } else { const duplicates = this.duplicateCodeFinder({ rootDir, minLines: 6, kTokens: 20, maxReports: 50, // Keep duplicate detection bounded; winnowing is CPU-heavy on large repos. maxFiles: Math.min(maxFiles, 1500), timeoutMs: dupBudgetMs, }); for (const group of duplicates) { if (group.occurrences.length >= 2) { issues.push({ type: 'duplicate_code', severity: group.occurrences.length > 3 ? 'high' : 'medium', file: group.occurrences[0].path, line: group.occurrences[0].start_line, message: `Duplicate code found in ${group.occurrences.length} locations`, suggestion: 'Consider extracting to a shared function', }); } } } } // Complexity analysis if (includeTypes.includes('complexity')) { for (const file of sourceFiles) { if (timedOut()) { partial = true; warnings.push('Stopped early due to timeout while running complexity analysis.'); break; } const content = readTextFile(file.path); if (!content) continue; analyzedFiles.add(file.path); const ext = extname(file.path).toLowerCase(); let functions: FunctionInfo[] = []; if (ext === '.py') { functions = extractFunctionsFromPython(content); } else { functions = extractFunctionsFromTS(content, file.path); } for (const func of functions) { const lines = func.end - func.start + 1; if (lines > 100) { issues.push({ type: 'complexity', severity: lines > 200 ? 'high' : 'medium', file: relative(root, file.path), line: func.start, message: `Function '${func.name}' is ${lines} lines long`, suggestion: 'Consider breaking into smaller functions', }); } } } } // Security analysis if (includeTypes.includes('security')) { const securityPatterns = [ { pattern: /eval\s*\(/g, name: 'eval usage', severity: 'high' as const }, { pattern: /innerHTML\s*=/g, name: 'innerHTML assignment', severity: 'medium' as const }, { pattern: /password\s*[:=]\s*["'][^"']+["']/gi, name: 'hardcoded password', severity: 'critical' as const, }, { pattern: /api[_-]?key\s*[:=]\s*["'][^"']+["']/gi, name: 'hardcoded API key', severity: 'critical' as const, }, { pattern: /secret\s*[:=]\s*["'][^"']+["']/gi, name: 'hardcoded secret', severity: 'critical' as const, }, ]; for (const file of sourceFiles) { if (timedOut()) { partial = true; warnings.push('Stopped early due to timeout while running security pattern analysis.'); break; } const content = readTextFile(file.path); if (!content) continue; analyzedFiles.add(file.path); const lines = content.split('\n'); for (const { pattern, name, severity } of securityPatterns) { pattern.lastIndex = 0; for (let i = 0; i < lines.length; i++) { if (pattern.test(lines[i])) { issues.push({ type: 'security', severity, file: relative(root, file.path), line: i + 1, message: `Potential security issue: ${name}`, suggestion: 'Review and remediate security concern', }); } pattern.lastIndex = 0; } } } } // Code smells if (includeTypes.includes('smells')) { for (const file of sourceFiles) { if (timedOut()) { partial = true; warnings.push('Stopped early due to timeout while running smell analysis.'); break; } const content = readTextFile(file.path); if (!content) continue; analyzedFiles.add(file.path); const lines = content.split('\n'); // Long lines for (let i = 0; i < lines.length; i++) { if (lines[i].length > 150) { issues.push({ type: 'smell', severity: 'low', file: relative(root, file.path), line: i + 1, message: `Line is ${lines[i].length} characters long`, suggestion: 'Consider breaking into multiple lines', }); } } // TODO/FIXME/HACK comments const todoPattern = /\b(TODO|FIXME|HACK|XXX)\b/i; for (let i = 0; i < lines.length; i++) { if (todoPattern.test(lines[i])) { issues.push({ type: 'smell', severity: 'low', file: relative(root, file.path), line: i + 1, message: 'Contains TODO/FIXME/HACK comment', suggestion: 'Address the technical debt', }); } } } } if (includeTypes.includes('dead_code')) { // Not yet implemented: keep the public API stable while being explicit. warnings.push( "dead_code analysis is not implemented yet; omit 'dead_code' from includeTypes to suppress this warning." ); partial = true; } // Calculate summary const byType: Record<string, number> = {}; const bySeverity: Record<string, number> = {}; for (const issue of issues) { byType[issue.type] = (byType[issue.type] ?? 0) + 1; bySeverity[issue.severity] = (bySeverity[issue.severity] ?? 0) + 1; } return { total_issues: issues.length, by_type: byType, by_severity: bySeverity, issues: issues.slice(0, 200), // Limit output partial: partial || warnings.length > 0, elapsedMs: Date.now() - startedAtMs, filesDiscovered: files.length, filesAnalyzed: analyzedFiles.size, warnings: warnings.length > 0 ? warnings : undefined, }; } }