Skip to main content
Glama
audit-completion.js24 kB
/** * Comprehensive Audit Completion Tool * Performs complete repository audit and generates readiness report */ import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join } from 'path'; export const auditCompletionTool = { name: 'audit_completion', description: 'Performs comprehensive repository audit and generates complete readiness report for version bumps and releases', inputSchema: { type: 'object', properties: { projectPath: { type: 'string', description: 'Path to the project directory to audit (defaults to current directory)', default: '.', }, auditType: { type: 'string', enum: ['release', 'security', 'quality', 'complete'], description: 'Type of audit to perform', default: 'complete', }, generateReport: { type: 'boolean', description: 'Generate detailed audit report file', default: true, }, fixIssues: { type: 'boolean', description: 'Automatically fix issues where possible', default: false, }, targetVersion: { type: 'string', description: 'Target version for release readiness check (e.g., "2.1.5")', }, strictMode: { type: 'boolean', description: 'Enable strict mode for production-ready validation', default: true, }, }, }, }; export async function handleAuditCompletion(args) { const { projectPath = '.', auditType = 'complete', generateReport = true, fixIssues = false, targetVersion, strictMode = true, } = args.params || args; try { const auditResults = await performComprehensiveAudit({ projectPath, auditType, fixIssues, targetVersion, strictMode, }); if (generateReport) { await generateAuditReport(auditResults, projectPath); } return { content: [ { type: 'text', text: formatAuditResults(auditResults), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify( { error: `Audit completion failed: ${error.message}`, timestamp: new Date().toISOString(), }, null, 2, ), }, ], }; } } async function performComprehensiveAudit({ projectPath, auditType, fixIssues, targetVersion, strictMode, }) { const results = { timestamp: new Date().toISOString(), projectPath, auditType, targetVersion, strictMode, overall: { score: 0, status: 'unknown', readyForRelease: false, criticalIssues: 0, warnings: 0, recommendations: [], }, sections: {}, }; // 1. Version Consistency Check if (auditType === 'complete' || auditType === 'release') { results.sections.versionConsistency = await auditVersionConsistency( projectPath, targetVersion, fixIssues, ); } // 2. Security Audit if (auditType === 'complete' || auditType === 'security') { results.sections.security = await auditSecurity(projectPath, strictMode); } // 3. Code Quality Audit if (auditType === 'complete' || auditType === 'quality') { results.sections.codeQuality = await auditCodeQuality(projectPath, strictMode); } // 4. Test Coverage and Status if (auditType === 'complete' || auditType === 'quality') { results.sections.testing = await auditTesting(projectPath); } // 5. Documentation Audit if (auditType === 'complete') { results.sections.documentation = await auditDocumentation(projectPath); } // 6. CI/CD and Release Readiness if (auditType === 'complete' || auditType === 'release') { results.sections.cicd = await auditCICD(projectPath); } // 7. Dependencies and Licensing if (auditType === 'complete' || auditType === 'security') { results.sections.dependencies = await auditDependencies(projectPath); } // 8. Git State and Repository Health if (auditType === 'complete' || auditType === 'release') { results.sections.repository = await auditRepository(projectPath); } // Calculate overall score and status calculateOverallScore(results); return results; } async function auditVersionConsistency(projectPath, targetVersion, fixIssues) { const result = { score: 0, status: 'fail', issues: [], fixes: [], }; try { const packageJsonPath = join(projectPath, 'package.json'); const readmePath = join(projectPath, 'README.md'); let packageJson = {}; let readmeContent = ''; let gitTags = []; // Read package.json if (existsSync(packageJsonPath)) { packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); } else { result.issues.push('package.json not found'); return result; } // Read README if (existsSync(readmePath)) { readmeContent = readFileSync(readmePath, 'utf8'); } // Get git tags try { const gitTagOutput = execSync('git tag --sort=-version:refname', { cwd: projectPath, encoding: 'utf8', }); gitTags = gitTagOutput .trim() .split('\n') .filter((tag) => tag); } catch (error) { result.issues.push('Failed to read git tags'); } const packageVersion = packageJson.version; const latestTag = gitTags[0]; // Extract version from README badge const versionBadgeMatch = readmeContent.match(/version-([^-]+)-/); const readmeVersion = versionBadgeMatch ? versionBadgeMatch[1] : null; // Check consistency let consistentVersions = 0; const totalChecks = 3; if (packageVersion) { result.packageVersion = packageVersion; consistentVersions++; } else { result.issues.push('No version in package.json'); } if (readmeVersion) { result.readmeVersion = readmeVersion; if (readmeVersion === packageVersion) { consistentVersions++; } else { result.issues.push( `README version (${readmeVersion}) doesn't match package.json (${packageVersion})`, ); if (fixIssues && packageVersion) { // Fix README version const newReadmeContent = readmeContent.replace( /version-[^-]+-/, `version-${packageVersion}-`, ); writeFileSync(readmePath, newReadmeContent); result.fixes.push(`Updated README version to ${packageVersion}`); } } } else { result.issues.push('No version badge found in README'); } if (latestTag) { result.latestTag = latestTag; const tagVersion = latestTag.replace(/^v/, ''); if (tagVersion === packageVersion) { consistentVersions++; } else { result.issues.push( `Latest git tag (${latestTag}) doesn't match package.json (${packageVersion})`, ); } } else { result.issues.push('No git tags found'); } // Check target version if (targetVersion) { result.targetVersion = targetVersion; if (packageVersion !== targetVersion) { result.issues.push( `Current version (${packageVersion}) doesn't match target (${targetVersion})`, ); if (fixIssues) { // Update package.json version packageJson.version = targetVersion; writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); result.fixes.push(`Updated package.json version to ${targetVersion}`); } } } result.score = Math.round((consistentVersions / totalChecks) * 100); result.status = result.score >= 80 ? 'pass' : result.score >= 60 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`Version consistency check failed: ${error.message}`); } return result; } async function auditSecurity(projectPath, strictMode) { const result = { score: 100, status: 'pass', vulnerabilities: [], issues: [], }; try { // Run security audit try { const auditOutput = execSync('bun audit --json', { cwd: projectPath, encoding: 'utf8', }); const auditData = JSON.parse(auditOutput); if (auditData.vulnerabilities && auditData.vulnerabilities.length > 0) { result.vulnerabilities = auditData.vulnerabilities; result.score = Math.max(0, 100 - auditData.vulnerabilities.length * 10); result.status = auditData.vulnerabilities.some((v) => v.severity === 'critical') ? 'fail' : 'warning'; } } catch (error) { // Try npm audit as fallback try { const npmAuditOutput = execSync('npm audit --json', { cwd: projectPath, encoding: 'utf8', }); const npmAuditData = JSON.parse(npmAuditOutput); if (npmAuditData.vulnerabilities) { const vulnCount = Object.keys(npmAuditData.vulnerabilities).length; if (vulnCount > 0) { result.vulnerabilities = Object.values(npmAuditData.vulnerabilities); result.score = Math.max(0, 100 - vulnCount * 10); result.status = 'warning'; } } } catch (npmError) { result.issues.push('Security audit failed - no package manager available'); result.score = strictMode ? 0 : 80; result.status = strictMode ? 'fail' : 'warning'; } } } catch (error) { result.issues.push(`Security audit failed: ${error.message}`); result.score = strictMode ? 0 : 50; result.status = 'fail'; } return result; } async function auditCodeQuality(projectPath, strictMode) { const result = { score: 0, status: 'unknown', linting: { passed: false, errors: 0, warnings: 0 }, formatting: { consistent: false }, issues: [], }; try { // Check for linting try { execSync('bun run lint', { cwd: projectPath, stdio: 'pipe' }); result.linting.passed = true; result.score += 50; } catch (error) { const output = error.stdout?.toString() || error.stderr?.toString() || ''; const errorMatch = output.match(/(\d+) error/); const warningMatch = output.match(/(\d+) warning/); result.linting.errors = errorMatch ? parseInt(errorMatch[1]) : 0; result.linting.warnings = warningMatch ? parseInt(warningMatch[1]) : 0; if (result.linting.errors === 0) { result.score += 40; } else if (result.linting.errors < 5) { result.score += 20; } } // Check for type checking (if TypeScript) try { execSync('bun run type-check', { cwd: projectPath, stdio: 'pipe' }); result.score += 30; } catch (error) { // TypeScript errors found or no type-check script result.issues.push('Type checking failed or not configured'); if (strictMode) { result.score = Math.max(0, result.score - 20); } } // Basic file structure checks const requiredFiles = ['package.json', 'README.md', 'LICENSE']; const existingFiles = requiredFiles.filter((file) => existsSync(join(projectPath, file))); result.score += (existingFiles.length / requiredFiles.length) * 20; result.status = result.score >= 80 ? 'pass' : result.score >= 60 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`Code quality audit failed: ${error.message}`); result.status = 'fail'; } return result; } async function auditTesting(projectPath) { const result = { score: 0, status: 'unknown', testsExist: false, testsPassing: false, passRate: 0, totalTests: 0, passingTests: 0, issues: [], }; try { // Check if tests exist const packageJsonPath = join(projectPath, 'package.json'); if (existsSync(packageJsonPath)) { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); if (packageJson.scripts && packageJson.scripts.test) { result.testsExist = true; result.score += 30; // Run tests try { const testOutput = execSync('bun test', { cwd: projectPath, encoding: 'utf8', stdio: 'pipe', }); // Parse test results const passMatch = testOutput.match(/(\d+) pass/); const failMatch = testOutput.match(/(\d+) fail/); result.passingTests = passMatch ? parseInt(passMatch[1]) : 0; const failingTests = failMatch ? parseInt(failMatch[1]) : 0; result.totalTests = result.passingTests + failingTests; if (result.totalTests > 0) { result.passRate = (result.passingTests / result.totalTests) * 100; result.testsPassing = result.passRate === 100; if (result.passRate >= 95) { result.score += 70; } else if (result.passRate >= 80) { result.score += 50; } else if (result.passRate >= 60) { result.score += 30; } else { result.score += 10; } } } catch (error) { result.issues.push('Tests failed to run or some tests are failing'); result.score += 20; // Some credit for having tests } } else { result.issues.push('No test script found in package.json'); } } result.status = result.score >= 80 ? 'pass' : result.score >= 60 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`Testing audit failed: ${error.message}`); result.status = 'fail'; } return result; } async function auditDocumentation(projectPath) { const result = { score: 0, status: 'unknown', files: {}, issues: [], }; try { const docFiles = [ { name: 'README.md', weight: 40, required: true }, { name: 'LICENSE', weight: 20, required: true }, { name: 'CHANGELOG.md', weight: 15, required: false }, { name: 'CONTRIBUTING.md', weight: 10, required: false }, { name: 'docs/', weight: 15, required: false, isDir: true }, ]; for (const docFile of docFiles) { const filePath = join(projectPath, docFile.name); const exists = existsSync(filePath); result.files[docFile.name] = { exists, required: docFile.required, weight: docFile.weight, }; if (exists) { result.score += docFile.weight; if (docFile.name === 'README.md') { // Check README quality const content = readFileSync(filePath, 'utf8'); if (content.length < 500) { result.issues.push('README.md is quite short - consider adding more details'); result.score -= 10; } if (!content.includes('## Installation') && !content.includes('# Installation')) { result.issues.push('README.md missing installation instructions'); result.score -= 5; } } } else if (docFile.required) { result.issues.push(`Required file ${docFile.name} is missing`); result.score -= docFile.weight / 2; } } result.status = result.score >= 80 ? 'pass' : result.score >= 60 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`Documentation audit failed: ${error.message}`); result.status = 'fail'; } return result; } async function auditCICD(projectPath) { const result = { score: 0, status: 'unknown', workflows: [], issues: [], }; try { const githubWorkflowsPath = join(projectPath, '.github', 'workflows'); if (existsSync(githubWorkflowsPath)) { const workflowFiles = execSync('ls', { cwd: githubWorkflowsPath, encoding: 'utf8', }) .trim() .split('\n') .filter((f) => f.endsWith('.yml') || f.endsWith('.yaml')); result.workflows = workflowFiles; result.score += Math.min(workflowFiles.length * 20, 80); // Check for essential workflows const hasCI = workflowFiles.some((f) => f.includes('ci') || f.includes('test')); const hasRelease = workflowFiles.some((f) => f.includes('release') || f.includes('publish')); if (hasCI) result.score += 10; if (hasRelease) result.score += 10; if (!hasCI) result.issues.push('No CI workflow found'); if (!hasRelease) result.issues.push('No release workflow found'); } else { result.issues.push('No GitHub workflows found'); } result.status = result.score >= 80 ? 'pass' : result.score >= 40 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`CI/CD audit failed: ${error.message}`); result.status = 'fail'; } return result; } async function auditDependencies(projectPath) { const result = { score: 100, status: 'pass', outdated: [], issues: [], }; try { // Check for outdated dependencies try { const outdatedOutput = execSync('bun outdated --json', { cwd: projectPath, encoding: 'utf8', }); const outdatedData = JSON.parse(outdatedOutput); if (outdatedData && Object.keys(outdatedData).length > 0) { result.outdated = Object.keys(outdatedData); result.score = Math.max(50, 100 - result.outdated.length * 5); result.status = result.outdated.length > 10 ? 'warning' : 'pass'; } } catch (error) { // Outdated check failed, but not critical result.issues.push('Could not check for outdated dependencies'); result.score = 90; } } catch (error) { result.issues.push(`Dependencies audit failed: ${error.message}`); result.status = 'warning'; result.score = 70; } return result; } async function auditRepository(projectPath) { const result = { score: 0, status: 'unknown', gitStatus: 'unknown', uncommittedFiles: [], issues: [], }; try { // Check git status try { const gitStatusOutput = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf8', }); if (gitStatusOutput.trim()) { result.uncommittedFiles = gitStatusOutput.trim().split('\n'); result.gitStatus = 'dirty'; result.issues.push(`${result.uncommittedFiles.length} uncommitted files found`); result.score = 60; } else { result.gitStatus = 'clean'; result.score = 100; } } catch (error) { result.issues.push('Not a git repository or git command failed'); result.score = 50; } result.status = result.score >= 80 ? 'pass' : result.score >= 60 ? 'warning' : 'fail'; } catch (error) { result.issues.push(`Repository audit failed: ${error.message}`); result.status = 'fail'; } return result; } function calculateOverallScore(results) { const sections = results.sections; const weights = { versionConsistency: 20, security: 25, codeQuality: 20, testing: 15, documentation: 10, cicd: 5, dependencies: 3, repository: 2, }; let totalScore = 0; let totalWeight = 0; let criticalIssues = 0; let warnings = 0; for (const [sectionName, sectionResult] of Object.entries(sections)) { if (sectionResult && typeof sectionResult.score === 'number') { const weight = weights[sectionName] || 1; totalScore += sectionResult.score * weight; totalWeight += weight; if (sectionResult.status === 'fail') { criticalIssues++; } else if (sectionResult.status === 'warning') { warnings++; } } } results.overall.score = totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0; results.overall.criticalIssues = criticalIssues; results.overall.warnings = warnings; // Determine overall status if (criticalIssues > 0 || results.overall.score < 60) { results.overall.status = 'fail'; results.overall.readyForRelease = false; } else if (warnings > 2 || results.overall.score < 80) { results.overall.status = 'warning'; results.overall.readyForRelease = false; } else { results.overall.status = 'pass'; results.overall.readyForRelease = true; } // Generate recommendations generateRecommendations(results); } function generateRecommendations(results) { const recommendations = []; if (results.sections.versionConsistency?.score < 80) { recommendations.push( '🔴 CRITICAL: Fix version consistency across package.json, README, and git tags', ); } if (results.sections.security?.vulnerabilities?.length > 0) { recommendations.push('🔴 CRITICAL: Address security vulnerabilities in dependencies'); } if (results.sections.testing?.passRate < 95) { recommendations.push('🟡 Fix failing tests before release'); } if (results.sections.repository?.gitStatus === 'dirty') { recommendations.push('🟡 Commit or revert uncommitted changes'); } if (results.sections.codeQuality?.linting?.errors > 0) { recommendations.push('🟡 Fix linting errors'); } if (!results.sections.documentation?.files?.['CHANGELOG.md']?.exists) { recommendations.push('📝 Add CHANGELOG.md for version history'); } if (results.overall.readyForRelease) { recommendations.push('✅ Repository is ready for release!'); } else { recommendations.push('❌ Repository needs fixes before release'); } results.overall.recommendations = recommendations; } async function generateAuditReport(results, projectPath) { const reportPath = join(projectPath, 'AUDIT_REPORT.md'); const report = `# Comprehensive Audit Report **Generated**: ${results.timestamp} **Project**: ${results.projectPath} **Audit Type**: ${results.auditType} **Target Version**: ${results.targetVersion || 'N/A'} ## 📊 Overall Results - **Score**: ${results.overall.score}/100 - **Status**: ${results.overall.status.toUpperCase()} - **Ready for Release**: ${results.overall.readyForRelease ? '✅ YES' : '❌ NO'} - **Critical Issues**: ${results.overall.criticalIssues} - **Warnings**: ${results.overall.warnings} ## 🎯 Recommendations ${results.overall.recommendations.map((rec) => `- ${rec}`).join('\n')} ## 📋 Detailed Results ${Object.entries(results.sections) .map( ([name, section]) => ` ### ${name.charAt(0).toUpperCase() + name.slice(1)} - **Score**: ${section.score}/100 - **Status**: ${section.status.toUpperCase()} ${section.issues?.length > 0 ? `- **Issues**: ${section.issues.length}\n${section.issues.map((issue) => ` - ${issue}`).join('\n')}` : ''} ${section.fixes?.length > 0 ? `- **Fixes Applied**: ${section.fixes.length}\n${section.fixes.map((fix) => ` - ${fix}`).join('\n')}` : ''} `, ) .join('\n')} --- *Report generated by MOIDVK Audit Completion Tool* `; writeFileSync(reportPath, report); console.log(`📄 Audit report saved to: ${reportPath}`); } function formatAuditResults(results) { const statusEmoji = { pass: '✅', warning: '⚠️', fail: '❌', unknown: '❓', }; return `🔍 **MOIDVK Comprehensive Audit Results** ## 📊 Overall Assessment ${statusEmoji[results.overall.status]} **Status**: ${results.overall.status.toUpperCase()} 📈 **Score**: ${results.overall.score}/100 🚀 **Ready for Release**: ${results.overall.readyForRelease ? '✅ YES' : '❌ NO'} 🚨 **Critical Issues**: ${results.overall.criticalIssues} ⚠️ **Warnings**: ${results.overall.warnings} ## 🎯 Key Recommendations ${results.overall.recommendations.map((rec) => `${rec}`).join('\n')} ## 📋 Section Breakdown ${Object.entries(results.sections) .map(([name, section]) => `**${name}**: ${statusEmoji[section.status]} ${section.score}/100`) .join('\n')} ${ results.overall.readyForRelease ? '🎉 **Congratulations!** Your repository is ready for release.' : '🔧 **Action Required**: Please address the issues above before releasing.' } --- *Audit completed at ${results.timestamp}* *Target version: ${results.targetVersion || 'Not specified'}*`; }

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/moikas-code/moidvk'

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