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
JavaScript
/**
* @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 };