UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

324 lines (274 loc) • 11.9 kB
#!/usr/bin/env node /** * @file Command-line interface for Single Responsibility Principle (SRP) violation analysis and code organization assessment * @description Single responsibility: Provide interactive CLI for identifying SRP violations and code organization issues * * This CLI tool serves as the SRP analysis interface for the AgentSqripts platform, * detecting files that violate the Single Responsibility Principle through multiple exports, * excessive length, mixed functionality patterns, and poor cohesion indicators. It implements * configurable scoring thresholds, detailed violation analysis, and actionable recommendations * to support systematic code organization improvements. * * Design rationale: * - SRP-focused analysis enables systematic code organization improvements * - Multi-factor violation detection provides comprehensive assessment beyond simple metrics * - Configurable score thresholds allow teams to adjust sensitivity based on project needs * - Detailed violation reporting identifies specific patterns requiring refactoring attention * - Graduated severity levels help prioritize refactoring efforts for maximum organizational impact * * SRP violation detection scope: * - Multiple export patterns indicating files handling multiple responsibilities * - File length analysis identifying classes and modules that have grown beyond manageable size * - Mixed keyword clusters suggesting different concern areas within single files * - Function and method count analysis revealing excessive functionality concentration * - Cohesion analysis identifying loosely related functionality grouped inappropriately */ const { analyzeSRPViolations, analyzeSingleFile } = require('../lib/srp-violations/analyzeSRPViolations'); const fs = require('fs'); const path = require('path'); const { getProcessArgs } = require('../lib/utils/processHelpers'); const { shouldShowHelp } = require('../lib/utils/cliHelpers'); const { createHelpFunction } = require('./lib/helpFormatter'); const { parseArgs: sharedParseArgs } = require('./lib/argumentParser'); const { outputJson: sharedOutputJson } = require('./lib/jsonOutputFormatter'); /** * Display help information */ const showHelp = createHelpFunction({ command: 'analyze-srp.js', description: 'Analyzes files for Single Responsibility Principle (SRP) violations by detecting\nmultiple exports, long files, and mixed functionality patterns.', options: [ { flag: '--extensions <exts>', description: 'Comma-separated list of file extensions (default: .js,.ts,.jsx,.tsx)' }, { flag: '--output-format <fmt>', description: 'Output format: json, summary, detailed (default: summary)' }, { flag: '--min-score <score>', description: 'Minimum violation score to report (default: 1)' }, { flag: '--mode <mode>', description: 'Analysis mode: file, project (default: auto-detect)' }, { flag: '--help', description: 'Show this help message' } ], examples: [ 'node analyze-srp.js .', 'node analyze-srp.js --mode file src/component.js', 'node analyze-srp.js --output-format json --min-score 3 src/', 'node analyze-srp.js --extensions .js,.ts --output-format detailed .' ], output: ' The tool identifies files that likely violate SRP by checking for:\n - Multiple exports (indicating multiple responsibilities)\n - Long files (>300 lines often do too much)\n - Mixed keyword clusters (different concern areas)\n - Excessive function/method counts', sections: { 'SEVERITY LEVELS': ' šŸ” LOW (1-2): Minor SRP concerns\n āš ļø MEDIUM (3-4): Notable SRP violations\n 🚨 HIGH (5-7): Significant SRP violations\n 🚨 CRITICAL (8+): Severe SRP violations requiring immediate attention' } }); // Old parseArgs function removed - using shared argument parser /** * Format analysis results for display */ function formatResults(results, format, isFile = false) { switch (format) { case 'json': return JSON.stringify(results, null, 2); case 'detailed': if (isFile) { return formatSingleFileDetailed(results); } else { return formatProjectDetailed(results); } case 'summary': default: if (isFile) { return formatSingleFileSummary(results); } else { return formatProjectSummary(results); } } } /** * Format single file results - summary */ function formatSingleFileSummary(result) { if (!result) { return 'āŒ Unable to analyze file'; } const emoji = result.severity === 'CRITICAL' ? '🚨' : result.severity === 'HIGH' ? 'āš ļø' : result.severity === 'MEDIUM' ? 'āš ļø' : 'šŸ”'; let output = `šŸ” SRP ANALYSIS FOR: ${result.file}\n\n`; output += `${emoji} Violation Score: ${result.score}\n`; output += `šŸ“Š Severity Level: ${result.severity}\n\n`; if (result.violations.length > 0) { output += `āš ļø Violations Detected:\n`; result.violations.forEach(violation => { output += ` • ${violation}\n`; }); output += '\n'; } if (result.recommendations.length > 0) { output += `šŸ’” Recommendations:\n`; result.recommendations.forEach(rec => { output += ` • ${rec}\n`; }); output += '\n'; } output += `šŸ“ˆ File Metrics:\n`; output += ` Lines of Code: ${result.details.lineCount}\n`; output += ` Export Count: ${result.details.exportCount}\n`; output += ` Function Count: ${result.details.functionCount}\n`; if (result.details.foundClusters.length > 0) { output += ` Responsibility Areas: ${result.details.foundClusters.map(c => c.clusterType).join(', ')}\n`; } return output; } /** * Format single file results - detailed */ function formatSingleFileDetailed(result) { let output = formatSingleFileSummary(result); if (result.details.foundClusters.length > 0) { output += `\nšŸ” Detected Responsibility Clusters:\n`; result.details.foundClusters.forEach(cluster => { output += ` • ${cluster.clusterType}: ${cluster.keywords.join(', ')}\n`; }); } return output; } /** * Format project results - summary */ function formatProjectSummary(results) { let output = `šŸ” SRP VIOLATION ANALYSIS SUMMARY\n\n`; output += `šŸ“Š Project Overview:\n`; output += ` Total Files Analyzed: ${results.summary.totalFiles}\n`; output += ` Files with Violations: ${results.summary.violatingFiles}\n`; output += ` Violation Rate: ${results.summary.violationRate}%\n`; output += ` Average Violation Score: ${results.summary.averageScore}\n\n`; output += `āš ļø Severity Breakdown:\n`; output += ` 🚨 Critical: ${results.summary.criticalViolations} files\n`; output += ` āš ļø High: ${results.summary.highViolations} files\n\n`; if (results.violations.length > 0) { output += `🚨 Top Violating Files:\n`; results.violations.slice(0, 10).forEach(violation => { const emoji = violation.severity === 'CRITICAL' ? '🚨' : violation.severity === 'HIGH' ? 'āš ļø' : violation.severity === 'MEDIUM' ? 'āš ļø' : 'šŸ”'; output += ` ${emoji} ${violation.file} → score: ${violation.score} (${violation.severity})\n`; }); if (results.violations.length > 10) { output += ` ... and ${results.violations.length - 10} more files\n`; } output += '\n'; } if (results.recommendations.length > 0) { output += `šŸ¤– AI Agent Recommendations:\n`; results.recommendations.forEach(rec => { output += ` • ${rec}\n`; }); output += '\n'; } output += `ā±ļø Analysis completed in ${results.analysisTime}ms\n`; output += `šŸ“… Timestamp: ${new Date(results.timestamp).toLocaleString()}\n\n`; output += `šŸ“ Note: High scores indicate files with multiple functions.\n`; output += ` Scores are information, not mandates. Consider whether functions\n`; output += ` belong together before refactoring. See lib/srp-violations/USAGE-GUIDANCE.md\n`; return output; } /** * Format project results - detailed */ function formatProjectDetailed(results) { let output = formatProjectSummary(results); if (results.violations.length > 0) { output += `\nšŸ“‹ Detailed Violation Analysis:\n\n`; results.violations.forEach((violation, index) => { if (index < 20) { // Limit detailed output to prevent overwhelming display output += formatSingleFileSummary(violation); output += `\n${'='.repeat(60)}\n\n`; } }); if (results.violations.length > 20) { output += `... ${results.violations.length - 20} more violations (use JSON output for complete details)\n`; } } return output; } /** * Main execution function */ async function main() { try { const args = getProcessArgs(); if (shouldShowHelp(args)) { showHelp(); return; } const { options: parsedOptions, targetPath, error } = sharedParseArgs(args, { defaults: { extensions: ['.js', '.ts', '.jsx', '.tsx'], outputFormat: 'summary', minScore: 1, mode: 'auto' }, flags: { extensions: { type: 'list' }, outputFormat: { type: 'string', validate: (v) => ['json', 'summary', 'detailed'].includes(v) }, minScore: { type: 'number', validate: (v) => v >= 0 }, mode: { type: 'string', validate: (v) => ['file', 'project', 'auto'].includes(v) } } }); if (error) { const { logAnalysisError } = require('../lib/utils/errorMessages'); logAnalysisError('SRP', error); process.exit(1); } const options = parsedOptions; // Check if target exists try { await fs.promises.access(targetPath); } catch (error) { const { logError } = require('../lib/utils/errorMessages'); logError(`Path '${targetPath}' does not exist`, new Error('ENOENT')); process.exit(1); } // Determine analysis mode const stat = await fs.promises.stat(targetPath); const isFile = stat.isFile(); const mode = options.mode === 'auto' ? (isFile ? 'file' : 'project') : options.mode; console.log(`šŸ” SRP violation analysis for: ${targetPath}`); console.log(`šŸ“Š Mode: ${mode}`); console.log(`āš ļø Minimum score: ${options.minScore}`); console.log(''); let results; if (mode === 'file' || isFile) { // Single file analysis results = await analyzeSingleFile(targetPath, { keywordClusters: undefined // Use defaults }); if (!results) { const { logError } = require('../lib/utils/errorMessages'); logError('Unable to analyze file', new Error('Analysis failed')); process.exit(1); } if (results.score < options.minScore) { console.log('āœ… No significant SRP violations detected'); process.exit(0); } console.log(formatResults(results, options.outputFormat, true)); } else { // Project analysis results = await analyzeSRPViolations(targetPath, { extensions: options.extensions, minScore: options.minScore, includeDetails: options.outputFormat !== 'summary' }); if (results.violations.length === 0) { console.log('āœ… No SRP violations detected above the minimum threshold'); process.exit(0); } console.log(formatResults(results, options.outputFormat, false)); } } catch (error) { const { logAnalysisError } = require('../lib/utils/errorMessages'); logAnalysisError('SRP', error); process.exit(1); } } // Run the CLI if (require.main === module) { main().catch(error => { console.error("Fatal error:", error); process.exit(1); }); } module.exports = { main, formatResults };