Skip to main content
Glama
rodhayl
by rodhayl

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
NameRequiredDescriptionDefault
rootDirNoRoot directory to analyze (default: workspace root)
minSimilarityNoMinimum similarity for duplicate detection (default: 0.85)
includeTypesNoTypes of analysis to include (default: all)

Implementation Reference

  • 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,
        };
      }
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rodhayl/mcpLocalHelper'

If you have feedback or need assistance with the MCP directory API, please join our Discord server