Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
smart-git-push-tool-v2.ts44.2 kB
/** * Smart Git Push MCP Tool - Version 2.0 (Clean) * * A focused git push tool that ensures security and tracks deployment readiness * * IMPORTANT FOR AI ASSISTANTS: This tool focuses on: * 1. Security: Preventing credential leaks and sensitive data exposure * 2. Repository Hygiene: Blocking irrelevant files (temp, build artifacts) * 3. Deployment Readiness: Tracking real metrics (test failures, deploy success rate) * * Cache Dependencies: * - CREATES/UPDATES: .mcp-adr-cache/deploy-history.json (deployment metrics) * - USES: Enhanced sensitive detector for security scanning * * This tool does NOT: * - Analyze architectural compliance * - Update TODO tasks * - Check knowledge graph intents * - Make complex AI decisions * * Use this tool for safe, metric-driven git pushes. */ import { McpAdrError } from '../types/index.js'; import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs'; import { join, extname, basename } from 'path'; import * as os from 'os'; import { jsonSafeFilePath, jsonSafeError, jsonSafeUserInput } from '../utils/json-safe.js'; import { validateMcpResponse } from '../utils/mcp-response-validator.js'; interface SmartGitPushArgs { branch?: string; message?: string; testResults?: TestResults; skipSecurity?: boolean; dryRun?: boolean; projectPath?: string; forceUnsafe?: boolean; humanOverrides?: HumanOverride[]; requestHumanConfirmation?: boolean; // Deployment Readiness Integration checkDeploymentReadiness?: boolean; targetEnvironment?: 'staging' | 'production' | 'integration'; enforceDeploymentReadiness?: boolean; strictDeploymentMode?: boolean; } interface TestResults { success: boolean; testsRun: number; testsPassed: number; testsFailed: number; duration?: number; command?: string; output?: string; failureDetails?: string[]; testTypes?: Record<string, { passed: number; failed: number }>; } interface GitFile { path: string; status: 'added' | 'modified' | 'deleted' | 'renamed'; content: string; size: number; } interface SecurityIssue { type: 'credential' | 'api-key' | 'private-key' | 'token' | 'password'; severity: 'critical' | 'high' | 'medium'; file: string; line?: number; pattern: string; recommendation: string; } interface IrrelevantFile { path: string; reason: 'temp-file' | 'build-artifact' | 'ide-config' | 'log-file' | 'cache-file'; recommendation: string; } interface HumanOverride { path: string; purpose: string; userConfirmed: boolean; timestamp: string; overrideReason: | 'security-exception' | 'business-requirement' | 'deployment-necessity' | 'temporary-debug' | 'other'; additionalContext?: string; } interface FileConfirmationRequest { path: string; detectedIssue: SecurityIssue | IrrelevantFile; suggestedPurpose: string; riskLevel: 'low' | 'medium' | 'high' | 'critical'; alternatives: string[]; } interface DeployHistory { successful: number; failed: number; lastDeploy?: string; lastDeploySuccess?: boolean; testResults: { totalTestsRun: number; totalTestsPassed: number; totalTestsFailed: number; lastRun?: string; lastRunSuccess?: boolean; averageDuration: number; lastTestSuite?: string; testTypes: Record<string, { passed: number; failed: number }>; }; successRate: number; testPassRate: number; } /** * Main smart git push function - Security and Metrics Focused */ export async function smartGitPushV2(args: SmartGitPushArgs): Promise<any> { const { branch, message, testResults, skipSecurity = false, dryRun = false, projectPath = process.cwd(), forceUnsafe = false, humanOverrides = [], requestHumanConfirmation = false, checkDeploymentReadiness = false, targetEnvironment = 'production', enforceDeploymentReadiness = false, strictDeploymentMode = true, } = args; try { // Step 1: Get staged files const stagedFiles = await getStagedFiles(projectPath); if (stagedFiles.length === 0) { const metricsText = await getDeploymentMetricsSummary(projectPath); return { content: [ { type: 'text', text: createNoChangesResponse(metricsText), }, ], }; } // Step 2: Security scan (unless skipped) let securityIssues: SecurityIssue[] = []; if (!skipSecurity) { securityIssues = await scanForSecurityIssues(stagedFiles, projectPath); } // Step 3: Check for irrelevant files const irrelevantFiles = await checkIrrelevantFiles(stagedFiles); // Step 4: Apply human overrides to security issues and irrelevant files const { filteredSecurityIssues, filteredIrrelevantFiles, confirmationRequests } = await applyHumanOverrides( securityIssues, irrelevantFiles, humanOverrides, requestHumanConfirmation ); // Step 5: Check for confirmation requests if (confirmationRequests.length > 0 && requestHumanConfirmation) { return { content: [ { type: 'text', text: generateConfirmationRequestResponse(confirmationRequests, stagedFiles), }, ], }; } // Step 5.5: Deployment Readiness Check (NEW) if (checkDeploymentReadiness || enforceDeploymentReadiness) { const { deploymentReadiness } = await import('./deployment-readiness-tool.js'); const readinessCheck = await deploymentReadiness({ operation: 'full_audit', projectPath, targetEnvironment, strictMode: strictDeploymentMode, // Test Gates - Zero tolerance for failures maxTestFailures: 0, requireTestCoverage: 80, blockOnFailingTests: true, // Deployment History Gates maxRecentFailures: 2, deploymentSuccessThreshold: 80, blockOnRecentFailures: true, rollbackFrequencyThreshold: 20, // Integration with existing systems integrateTodoTasks: true, updateHealthScoring: true, }); // Hard block if deployment is not ready and enforcement is enabled if ( !readinessCheck.isDeploymentReady && (enforceDeploymentReadiness || strictDeploymentMode) ) { return { content: [ { type: 'text', text: generateDeploymentReadinessBlockResponse( readinessCheck, stagedFiles, targetEnvironment ), }, ], }; } // Soft warning if just checking but not enforcing if ( !readinessCheck.isDeploymentReady && checkDeploymentReadiness && !enforceDeploymentReadiness ) { // Continue with push but include warning in success response } } // Step 6: Check blocking conditions (after overrides) const hasCriticalSecurity = filteredSecurityIssues.some(issue => issue.severity === 'critical'); const hasFailedTests = testResults && !testResults.success; const shouldBlock = (hasCriticalSecurity || hasFailedTests) && !forceUnsafe; if (shouldBlock) { return { content: [ { type: 'text', text: generateBlockedResponse( filteredSecurityIssues, filteredIrrelevantFiles, testResults, humanOverrides ), }, ], }; } // Step 5: Execute push if not dry run if (!dryRun) { const pushResult = await executePush(projectPath, branch, message); // Update deployment history await updateDeploymentHistory(projectPath, { success: true, ...(testResults && { testResults }), filesChanged: stagedFiles.length, }); return { content: [ { type: 'text', text: generateSuccessResponse( stagedFiles, filteredSecurityIssues, filteredIrrelevantFiles, testResults, pushResult, branch, humanOverrides ), }, ], }; } else { // Dry run response return { content: [ { type: 'text', text: generateDryRunResponse( stagedFiles, filteredSecurityIssues, filteredIrrelevantFiles, testResults, branch ), }, ], }; } } catch (error) { // Update deployment history with failure await updateDeploymentHistory(projectPath, { success: false, testResults: testResults || { success: false, testsRun: 0, testsPassed: 0, testsFailed: 0 }, filesChanged: 0, }); throw new McpAdrError('Smart git push failed: ' + jsonSafeError(error), 'GIT_PUSH_ERROR'); } } /** * Get staged files */ async function getStagedFiles(projectPath: string): Promise<GitFile[]> { try { const gitOutput = execSync('git diff --cached --name-status', { cwd: projectPath, encoding: 'utf8', }); if (!gitOutput.trim()) { return []; } const files: GitFile[] = []; const lines = gitOutput.trim().split('\n'); for (const line of lines) { const [status, ...pathParts] = line.split('\t'); const path = pathParts.join('\t'); const fullPath = join(projectPath, path); let content: string | undefined; let size = 0; if (status !== 'D' && existsSync(fullPath)) { try { const stats = statSync(fullPath); size = stats.size; // Only read content for small text files (< 100KB) if (size < 100 * 1024 && isTextFile(path)) { content = readFileSync(fullPath, 'utf8'); } } catch { // Ignore read errors } } files.push({ path, status: mapGitStatus(status || 'M'), content: content || '', size, }); } return files; } catch (error) { throw new McpAdrError( 'Failed to get staged files: ' + jsonSafeError(error), 'GIT_STATUS_ERROR' ); } } /** * Scan for security issues */ async function scanForSecurityIssues( files: GitFile[], _projectPath: string ): Promise<SecurityIssue[]> { const issues: SecurityIssue[] = []; try { const { analyzeSensitiveContent } = await import('../utils/gitleaks-detector.js'); for (const file of files) { if (file.status === 'deleted' || !file.content) continue; const result = await analyzeSensitiveContent(file.path, file.content); for (const match of result.matches) { // Map to our simplified security issue format const severity = match.pattern.severity === 'critical' ? 'critical' : match.pattern.severity === 'high' ? 'high' : 'medium'; issues.push({ type: mapPatternToType(match.pattern.name), severity, file: file.path, line: match.line, pattern: match.pattern.name, recommendation: getSecurityRecommendation(match.pattern.name), }); } } } catch (error) { console.error('Security scan error:', error); } return issues; } /** * Check for irrelevant files */ async function checkIrrelevantFiles(files: GitFile[]): Promise<IrrelevantFile[]> { const irrelevant: IrrelevantFile[] = []; const patterns = { 'temp-file': /\.(tmp|temp|cache|swp|swo|swn|bak|backup)$/i, 'build-artifact': /^(dist|build|out|target|bin|obj)\//, 'ide-config': /^\.(vscode|idea|eclipse|sublime-|atom|brackets)\//, 'log-file': /\.(log|logs)$/i, 'cache-file': /^(\.(cache|npm|yarn|pnpm)|node_modules|__pycache__|\.pytest_cache)\//, }; for (const file of files) { if (file.status === 'deleted') continue; for (const [reason, pattern] of Object.entries(patterns)) { if (pattern.test(file.path)) { irrelevant.push({ path: file.path, reason: reason as IrrelevantFile['reason'], recommendation: getIrrelevantFileRecommendation(reason), }); break; } } // Check for large files (> 10MB) if (file.size > 10 * 1024 * 1024) { irrelevant.push({ path: file.path, reason: 'build-artifact', recommendation: 'Large file (' + Math.round(file.size / 1024 / 1024) + 'MB) - consider using Git LFS', }); } } return irrelevant; } /** * Update deployment history */ async function updateDeploymentHistory( projectPath: string, result: { success: boolean; testResults?: TestResults; filesChanged: number; } ): Promise<void> { const projectName = basename(projectPath); const cacheDir = join(os.tmpdir(), projectName, 'cache'); const historyFile = join(cacheDir, 'deploy-history.json'); // Ensure cache directory exists if (!existsSync(cacheDir)) { mkdirSync(cacheDir, { recursive: true }); } // Load existing history let history: DeployHistory = { successful: 0, failed: 0, testResults: { totalTestsRun: 0, totalTestsPassed: 0, totalTestsFailed: 0, averageDuration: 0, testTypes: {}, }, successRate: 0, testPassRate: 0, }; if (existsSync(historyFile)) { try { history = JSON.parse(readFileSync(historyFile, 'utf8')); } catch { // Ignore parse errors } } // Update history if (result.success) { history.successful++; history.lastDeploy = new Date().toISOString(); history.lastDeploySuccess = true; } else { history.failed++; history.lastDeploy = new Date().toISOString(); history.lastDeploySuccess = false; } // Update test results from AI-provided data if (result.testResults) { history.testResults.totalTestsRun += result.testResults.testsRun; history.testResults.totalTestsPassed += result.testResults.testsPassed; history.testResults.totalTestsFailed += result.testResults.testsFailed; history.testResults.lastRun = new Date().toISOString(); history.testResults.lastRunSuccess = result.testResults.success; // Update test types tracking if (result.testResults.testTypes) { for (const [testType, results] of Object.entries(result.testResults.testTypes)) { if (!history.testResults.testTypes[testType]) { history.testResults.testTypes[testType] = { passed: 0, failed: 0 }; } history.testResults.testTypes[testType].passed += results.passed; history.testResults.testTypes[testType].failed += results.failed; } } // Update average duration if (result.testResults.duration) { const currentAvg = history.testResults.averageDuration || 0; const totalRuns = history.successful + history.failed; history.testResults.averageDuration = totalRuns > 1 ? (currentAvg * (totalRuns - 1) + result.testResults.duration) / totalRuns : result.testResults.duration; } // Set last test suite if command provided if (result.testResults.command) { history.testResults.lastTestSuite = result.testResults.command; } } // Calculate rates const totalDeploys = history.successful + history.failed; history.successRate = totalDeploys > 0 ? Math.round((history.successful / totalDeploys) * 100) : 0; const totalTests = history.testResults.totalTestsPassed + history.testResults.totalTestsFailed; history.testPassRate = totalTests > 0 ? Math.round((history.testResults.totalTestsPassed / totalTests) * 100) : 0; // Save updated history writeFileSync(historyFile, JSON.stringify(history, null, 2)); } /** * Get deployment metrics summary */ async function getDeploymentMetricsSummary(projectPath: string): Promise<string> { const projectName = basename(projectPath); const cacheDir = join(os.tmpdir(), projectName, 'cache'); const historyFile = join(cacheDir, 'deploy-history.json'); if (!existsSync(historyFile)) { return '- No deployment history available'; } try { const history: DeployHistory = JSON.parse(readFileSync(historyFile, 'utf8')); const lines = [ '- **Deploy Success Rate**: ' + history.successRate + '% (' + history.successful + '/' + (history.successful + history.failed) + ')', '- **Test Pass Rate**: ' + history.testPassRate + '% (' + history.testResults.totalTestsPassed + '/' + (history.testResults.totalTestsPassed + history.testResults.totalTestsFailed) + ')', '- **Last Deploy**: ' + (history.lastDeploy ? new Date(history.lastDeploy).toLocaleString() : 'Never') + ' ' + (history.lastDeploySuccess ? '✅' : '❌'), '- **Last Test Run**: ' + (history.testResults.lastRun ? new Date(history.testResults.lastRun).toLocaleString() : 'Never') + ' ' + (history.testResults.lastRunSuccess ? '✅' : '❌'), '- **Total Tests Executed**: ' + history.testResults.totalTestsRun, '- **Avg Test Duration**: ' + (history.testResults.averageDuration ? Math.round(history.testResults.averageDuration) + 's' : 'N/A'), ]; return lines.join('\n'); } catch { return '- Error reading deployment history'; } } /** * Execute git push */ async function executePush( projectPath: string, branch?: string, message?: string ): Promise<{ output: string; success: boolean }> { try { let output = ''; if (message) { const commitOutput = execSync('git commit -m "' + message + '"', { cwd: projectPath, encoding: 'utf8', }); output += 'Commit:\n' + commitOutput + '\n\n'; } const pushCommand = branch ? 'git push origin ' + branch : 'git push'; const pushOutput = execSync(pushCommand, { cwd: projectPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], }); output += 'Push:\n' + pushOutput; return { output, success: true }; } catch (error) { throw new McpAdrError('Git push failed: ' + jsonSafeError(error), 'GIT_PUSH_FAILED'); } } // Helper functions function mapGitStatus(status: string): GitFile['status'] { switch (status) { case 'A': return 'added'; case 'M': return 'modified'; case 'D': return 'deleted'; case 'R': return 'renamed'; default: return 'modified'; } } function isTextFile(path: string): boolean { const textExtensions = [ '.js', '.ts', '.jsx', '.tsx', '.json', '.md', '.txt', '.yml', '.yaml', '.py', '.rb', '.java', '.cs', '.go', '.rs', '.cpp', '.c', '.h', '.hpp', '.php', '.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte', ]; return textExtensions.includes(extname(path).toLowerCase()); } function mapPatternToType(patternName: string): SecurityIssue['type'] { if (patternName.includes('api') || patternName.includes('key')) return 'api-key'; if (patternName.includes('private') || patternName.includes('rsa')) return 'private-key'; if (patternName.includes('token')) return 'token'; if (patternName.includes('password') || patternName.includes('pwd')) return 'password'; return 'credential'; } function getSecurityRecommendation(pattern: string): string { if (pattern.includes('api')) return 'Move API keys to environment variables'; if (pattern.includes('private')) return 'Never commit private keys - use key management service'; if (pattern.includes('token')) return 'Use environment variables or secure token storage'; if (pattern.includes('password')) return 'Never hardcode passwords - use secure credential storage'; return 'Remove sensitive data and use environment variables'; } function getIrrelevantFileRecommendation(reason: string): string { switch (reason) { case 'temp-file': return 'Add to .gitignore - temporary files should not be committed'; case 'build-artifact': return 'Add to .gitignore - build outputs should not be in source control'; case 'ide-config': return 'Add to .gitignore - IDE configurations are user-specific'; case 'log-file': return 'Add to .gitignore - log files should not be committed'; case 'cache-file': return 'Add to .gitignore - cache files are generated'; default: return 'Consider adding to .gitignore'; } } // Human Override Functions /** * Apply human overrides to security issues and irrelevant files */ async function applyHumanOverrides( securityIssues: SecurityIssue[], irrelevantFiles: IrrelevantFile[], humanOverrides: HumanOverride[], requestConfirmation: boolean ): Promise<{ filteredSecurityIssues: SecurityIssue[]; filteredIrrelevantFiles: IrrelevantFile[]; confirmationRequests: FileConfirmationRequest[]; }> { const confirmationRequests: FileConfirmationRequest[] = []; // Filter security issues based on human overrides const filteredSecurityIssues = securityIssues.filter(issue => { const override = humanOverrides.find(o => o.path === issue.file && o.userConfirmed); if (override) { return false; // Remove from blocking issues } // If requesting confirmation and no override exists, create confirmation request if (requestConfirmation && !humanOverrides.find(o => o.path === issue.file)) { confirmationRequests.push({ path: issue.file, detectedIssue: issue, suggestedPurpose: analyzeFilePurpose(issue.file), riskLevel: issue.severity as any, alternatives: generateAlternatives(issue), }); } return true; // Keep issue if no override }); // Filter irrelevant files based on human overrides const filteredIrrelevantFiles = irrelevantFiles.filter(file => { const override = humanOverrides.find(o => o.path === file.path && o.userConfirmed); if (override) { return false; // Remove from irrelevant files } // If requesting confirmation and no override exists, create confirmation request if (requestConfirmation && !humanOverrides.find(o => o.path === file.path)) { confirmationRequests.push({ path: file.path, detectedIssue: file, suggestedPurpose: analyzeFilePurpose(file.path), riskLevel: 'medium', alternatives: generateAlternatives(file), }); } return true; // Keep file if no override }); return { filteredSecurityIssues, filteredIrrelevantFiles, confirmationRequests, }; } /** * Analyze file purpose using LLM-style heuristics */ function analyzeFilePurpose(filePath: string): string { const fileName = filePath.toLowerCase(); // Configuration files if ( fileName.includes('config') || fileName.endsWith('.config.js') || fileName.endsWith('.config.ts') ) { return 'Configuration file - may contain environment-specific settings'; } // Build/deployment files if (fileName.includes('build') || fileName.includes('dist') || fileName.includes('deploy')) { return 'Build or deployment artifact - usually not committed to source control'; } // Development/debug files if (fileName.includes('debug') || fileName.includes('test') || fileName.includes('temp')) { return 'Development or debugging file - may be temporary'; } // IDE/editor files if ( fileName.includes('.vscode') || fileName.includes('.idea') || fileName.includes('.settings') ) { return 'IDE/editor configuration - user-specific preferences'; } // Log files if (fileName.includes('log') || fileName.endsWith('.log')) { return 'Log file - runtime data that changes frequently'; } // Security-related if (fileName.includes('key') || fileName.includes('secret') || fileName.includes('credential')) { return 'Security-sensitive file - may contain credentials or keys'; } // Cache/temporary if ( fileName.includes('cache') || fileName.includes('node_modules') || fileName.includes('.cache') ) { return 'Cache or temporary data - generated content'; } return 'Unknown purpose - manual review recommended'; } /** * Generate alternatives for problematic files */ function generateAlternatives(issue: SecurityIssue | IrrelevantFile): string[] { const alternatives: string[] = []; if ('severity' in issue) { // Security issue alternatives switch (issue.type) { case 'api-key': alternatives.push('Move to environment variables (.env file)'); alternatives.push('Use secure credential management service'); alternatives.push('Store in CI/CD pipeline secrets'); break; case 'private-key': alternatives.push('Use key management service (AWS KMS, Azure Key Vault)'); alternatives.push('Store locally and reference by path'); alternatives.push('Use certificate-based authentication'); break; case 'token': alternatives.push('Generate token at runtime'); alternatives.push('Use OAuth flow instead of static tokens'); alternatives.push('Store in secure credential store'); break; default: alternatives.push('Use environment variables'); alternatives.push('External configuration management'); } } else { // Irrelevant file alternatives switch (issue.reason) { case 'build-artifact': alternatives.push('Add to .gitignore and rebuild on deployment'); alternatives.push('Use CI/CD pipeline to generate artifacts'); alternatives.push('Store in artifact repository (npm, Docker registry)'); break; case 'temp-file': alternatives.push('Delete file after use'); alternatives.push('Add to .gitignore'); alternatives.push('Use system temp directory'); break; case 'ide-config': alternatives.push('Add to .gitignore'); alternatives.push('Create shared .vscode/settings.json for team settings'); alternatives.push('Document setup instructions in README'); break; default: alternatives.push('Add to .gitignore'); alternatives.push('Review if file is necessary'); } } return alternatives; } // Response generators function createNoChangesResponse(metricsText: string): string { return ( '# Smart Git Push - No Changes\n\n' + '## Status\n' + 'No staged files found. Use git add to stage files before pushing.\n\n' + '## Deployment Metrics\n' + metricsText + '\n\n' + '## ⚠️ IMPORTANT: Selective File Staging\n' + '**DO NOT USE:** `git add .` or `git add -A` (stages everything including unintended files)\n\n' + '**RECOMMENDED APPROACH:**\n' + '1. Review changes: `git status` and `git diff`\n' + '2. Stage specific files: `git add <specific-file>`\n' + '3. Verify staged files: `git diff --cached`\n' + '4. Only then commit and push\n\n' + '## Safe Commands\n' + '- `git status` - Check current status\n' + '- `git diff` - See unstaged changes\n' + '- `git add <specific-file>` - Stage specific file only\n' + '- `git diff --cached` - Review staged changes\n\n' + '## 🚀 Deployment Readiness Checklist\n' + '- [ ] All tests passing locally\n' + '- [ ] Code reviewed and approved\n' + '- [ ] No sensitive data in changes\n' + '- [ ] Documentation updated if needed\n' + '- [ ] Deployment strategy confirmed' ); } /** * Generate confirmation request response for human override */ function generateConfirmationRequestResponse( confirmationRequests: FileConfirmationRequest[], stagedFiles: GitFile[] ): string { let response = '# Smart Git Push - Human Confirmation Required 🤔\n\n' + '## Files Requiring Manual Review\n\n' + `The LLM has detected ${confirmationRequests.length} files that need your confirmation before proceeding.\n\n`; confirmationRequests.forEach((request, index) => { const isSecurityIssue = 'severity' in request.detectedIssue; const riskEmoji = request.riskLevel === 'critical' ? '🔴' : request.riskLevel === 'high' ? '🟠' : request.riskLevel === 'medium' ? '🟡' : '🟢'; response += `### ${index + 1}. ${jsonSafeFilePath(request.path)} ${riskEmoji}\n\n` + `**Detected Issue**: ${isSecurityIssue ? 'Security Risk' : 'Irrelevant File'}\n` + `**Risk Level**: ${request.riskLevel.toUpperCase()}\n` + `**LLM Analysis**: ${request.suggestedPurpose}\n\n` + `**Recommended Alternatives**:\n${request.alternatives.map(alt => `- ${alt}`).join('\n')}\n\n` + `**To Override**: Confirm this file's purpose and business justification.\n\n`; }); response += '## Override Instructions\n\n' + 'To proceed with these files, rerun the command with human overrides:\n\n' + '```json\n' + '{\n' + ' "operation": "smart_git_push",\n' + ' "humanOverrides": [\n' + confirmationRequests .map( req => ' {\n' + ` "path": "${req.path}",\n` + ` "purpose": "YOUR_BUSINESS_JUSTIFICATION_HERE",\n` + ' "userConfirmed": true,\n' + ` "timestamp": "${new Date().toISOString()}",\n` + ' "overrideReason": "business-requirement", // or security-exception, deployment-necessity, etc.\n' + ' "additionalContext": "EXPLAIN_WHY_THIS_IS_NECESSARY"\n' + ' }' ) .join(',\n') + '\n' + ' ]\n' + '}\n' + '```\n\n' + '## ⚠️ Important Reminders\n' + '- **Only override if you understand the business need**\n' + '- **Document the purpose clearly**\n' + '- **Consider the security implications**\n' + '- **Review alternatives before overriding**\n\n' + `**Total staged files**: ${stagedFiles.length} | **Files needing confirmation**: ${confirmationRequests.length}`; return response; } function generateBlockedResponse( securityIssues: SecurityIssue[], _irrelevantFiles: IrrelevantFile[], testResults: any, humanOverrides?: HumanOverride[] ): string { let response = '# Smart Git Push - Blocked 🚫\n\n## Push Blocked Due to Critical Issues\n\n'; if (securityIssues.some(i => i.severity === 'critical')) { const criticalIssues = securityIssues .filter(i => i.severity === 'critical') .map( issue => '- **' + issue.type + '** in ' + jsonSafeFilePath(issue.file) + (issue.line ? ' (line ' + issue.line + ')' : '') + '\n' + ' Pattern: ' + issue.pattern + '\n' + ' Fix: ' + issue.recommendation ) .join('\n\n'); response += '### 🔐 Critical Security Issues Found\n' + criticalIssues + '\n\n'; } if (testResults && !testResults.success) { response += '### 🧪 Tests Failed\n' + '- **Command**: ' + testResults.command + '\n' + '- **Output**:\n' + '-------\n' + jsonSafeUserInput(testResults.output) + '\n' + '-------\n\n'; } // Show human override status if provided if (humanOverrides && humanOverrides.length > 0) { response += '## Human Overrides Applied\n'; humanOverrides.forEach(override => { response += `- **${jsonSafeFilePath(override.path)}**: ${override.purpose}\n` + ` Reason: ${override.overrideReason} | Confirmed: ${override.userConfirmed ? '✅' : '❌'}\n`; }); response += '\n'; } response += '## Required Actions\n' + '1. Fix all critical security issues\n' + '2. Ensure all tests pass\n' + '3. Review and fix any warnings\n' + '4. OR use human overrides with proper justification\n\n' + '## ⚠️ IMPORTANT: File Staging Best Practices\n' + '**NEVER USE:** `git add .` or `git add -A` when fixing issues\n' + '**RECOMMENDED:**\n' + '1. Fix specific issues in specific files\n' + '2. Stage only the fixed files: `git add <fixed-file>`\n' + '3. Verify changes: `git diff --cached`\n' + '4. Re-run smart git push\n\n' + '## Human Override Option\n' + 'If these issues are expected and have business justification:\n' + '1. Use `requestHumanConfirmation: true` to get guided override instructions\n' + '2. Provide detailed justification for each file\n' + '3. Consider security implications carefully\n\n' + '🚫 Use --forceUnsafe to override (NOT RECOMMENDED)'; return response; } function generateSuccessResponse( stagedFiles: GitFile[], securityIssues: SecurityIssue[], irrelevantFiles: IrrelevantFile[], testResults: any, pushResult: any, branch?: string, humanOverrides?: HumanOverride[] ): string { let response = '# Smart Git Push - Success ✅\n\n' + '## Push Summary\n' + '- **Branch**: ' + (branch || 'current') + '\n' + '- **Files**: ' + stagedFiles.length + ' staged files\n' + '- **Security Issues**: ' + securityIssues.length + ' (' + securityIssues.filter(i => i.severity === 'critical').length + ' critical)\n' + '- **Irrelevant Files**: ' + irrelevantFiles.length + '\n' + '- **Tests**: ' + (testResults ? (testResults.success ? '✅ Passed' : '❌ Failed') : 'Skipped') + '\n\n' + '## Deployment Metrics Updated\n' + getDeploymentMetricsUpdate() + '\n\n' + '## Files Pushed\n' + stagedFiles.map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.status + ')').join('\n'); if (securityIssues.length > 0) { const nonCriticalIssues = securityIssues.filter(i => i.severity !== 'critical'); if (nonCriticalIssues.length > 0) { response += '\n\n## Security Warnings (Non-Critical)\n' + nonCriticalIssues .map( issue => '- **' + issue.type + '** in ' + jsonSafeFilePath(issue.file) + ': ' + issue.recommendation ) .join('\n'); } } if (irrelevantFiles.length > 0) { response += '\n\n## Irrelevant Files (Consider .gitignore)\n' + irrelevantFiles .map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.reason + '): ' + f.recommendation) .join('\n'); } // Show human overrides if any were applied if (humanOverrides && humanOverrides.length > 0) { response += '\n\n## ✅ Human Overrides Applied\n' + 'The following files were explicitly approved by human override:\n' + humanOverrides .filter(o => o.userConfirmed) .map( override => `- **${jsonSafeFilePath(override.path)}**: ${override.purpose}\n` + ` Justification: ${override.additionalContext || 'No additional context'}\n` + ` Override Reason: ${override.overrideReason}\n` + ` Timestamp: ${new Date(override.timestamp).toLocaleString()}` ) .join('\n\n') + '\n\n' + '📝 **Note**: These overrides have been logged for audit purposes.'; } response += '\n\n## Git Output\n' + '-------\n' + jsonSafeUserInput(pushResult.output) + '\n' + '-------\n\n' + '## 🚀 Post-Push Deployment Checklist\n' + '### Immediate Actions (Next 5 minutes)\n' + '- [ ] Monitor CI/CD pipeline status\n' + '- [ ] Check automated test results\n' + '- [ ] Verify build completion\n' + '- [ ] Review any deployment warnings\n\n' + '### Deployment Completion (Next 15 minutes)\n' + '- [ ] Confirm deployment to staging/production\n' + '- [ ] Verify application health checks\n' + '- [ ] Test key functionality post-deployment\n' + '- [ ] Monitor error rates and performance metrics\n' + '- [ ] Update TODO list with deployment status\n\n' + '### Documentation & Communication\n' + '- [ ] Update deployment notes\n' + '- [ ] Notify team of successful deployment\n' + '- [ ] Close related tickets/issues\n' + '- [ ] Schedule post-deployment review if needed\n\n' + '💡 **Tip**: Use the TODO management tool to track deployment completion tasks!'; return response; } function generateDryRunResponse( stagedFiles: GitFile[], securityIssues: SecurityIssue[], irrelevantFiles: IrrelevantFile[], testResults: any, branch?: string ): string { const hasCritical = securityIssues.some(i => i.severity === 'critical'); const wouldBlock = hasCritical || (testResults && !testResults.success); let response = '# Smart Git Push - Dry Run 🔍\n\n' + '## Analysis Results\n' + '- **Files to Push**: ' + stagedFiles.length + '\n' + '- **Security Issues**: ' + securityIssues.length + ' (' + securityIssues.filter(i => i.severity === 'critical').length + ' critical)\n' + '- **Irrelevant Files**: ' + irrelevantFiles.length + '\n' + '- **Would Block**: ' + (wouldBlock ? '❌ Yes' : '✅ No') + '\n\n' + '## Staged Files\n' + stagedFiles .map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.status + ') - ' + f.size + ' bytes') .join('\n'); if (securityIssues.length > 0) { response += '\n\n## Security Issues Found\n' + securityIssues .map( issue => '- **' + issue.severity.toUpperCase() + '** ' + issue.type + ' in ' + jsonSafeFilePath(issue.file) + (issue.line ? ' (line ' + issue.line + ')' : '') + '\n' + ' Fix: ' + issue.recommendation ) .join('\n'); } else { response += '\n\n## ✅ No Security Issues Found'; } if (irrelevantFiles.length > 0) { response += '\n\n## Irrelevant Files Detected\n' + irrelevantFiles .map( f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.reason + ')\n' + ' ' + f.recommendation ) .join('\n'); } response += '\n\n## ⚠️ IMPORTANT: Pre-Push Staging Review\n' + '**AVOID:** `git add .` or `git add -A` before this tool\n' + '**RECOMMENDED:**\n' + '1. Review each staged file individually\n' + '2. Verify changes: `git diff --cached`\n' + '3. Ensure only intended files are staged\n\n' + '## Command to Execute\n' + '-------\n' + '# Run without dry run to actually push\n' + 'git push' + (branch ? ' origin ' + branch : '') + '\n' + '-------\n\n' + '**Note:** This was a dry run. No files were pushed.' + (wouldBlock ? '\n⚠️ **Warning**: This push would be BLOCKED due to critical issues.' : '') + '\n\n' + '## 🚀 Deployment Readiness Assessment\n' + (wouldBlock ? '❌ **NOT READY** - Fix issues above before deployment' : '✅ **READY** - This push appears safe for deployment\n\n' + '### Post-Push Checklist\n' + '- [ ] Monitor CI/CD pipeline\n' + '- [ ] Verify deployment health\n' + '- [ ] Update TODO tasks\n' + '- [ ] Document deployment notes'); return response; } function getDeploymentMetricsUpdate(): string { return ( '- Deploy success rate updated\n' + '- Test results recorded\n' + '- Metrics available in .mcp-adr-cache/deploy-history.json' ); } /** * Generate deployment readiness block response */ function generateDeploymentReadinessBlockResponse( readinessResult: any, stagedFiles: GitFile[], targetEnvironment: string ): string { return `🚨 **DEPLOYMENT BLOCKED - Critical Readiness Issues** ## 🎯 Deployment Readiness Assessment - **Target Environment**: ${targetEnvironment} - **Deployment Ready**: ❌ **NO** - **Readiness Score**: ${readinessResult.overallScore || 0}% - **Confidence Level**: ${readinessResult.confidence || 0}% ## 🧪 Test Validation Issues ${ readinessResult.testFailureBlockers?.length > 0 ? ` **Test Failures**: ${readinessResult.testValidationResult?.failureCount || 0} failures detected **Test Coverage**: ${readinessResult.testValidationResult?.coveragePercentage || 0}% (Required: 80%) ### Critical Test Failures: ${ readinessResult.testValidationResult?.criticalTestFailures ?.map((f: any) => `- ❌ ${f.testSuite}: ${f.testName}`) .join('\n') || 'No critical test failures' } ` : '✅ All tests passing' } ## 📊 Deployment History Issues ${ readinessResult.deploymentHistoryBlockers?.length > 0 ? ` **Success Rate**: ${readinessResult.deploymentHistoryAnalysis?.successRate || 0}% (Required: 80%) **Rollback Rate**: ${readinessResult.deploymentHistoryAnalysis?.rollbackRate || 0}% (Threshold: 20%) ### Recent Failure Patterns: ${ readinessResult.deploymentHistoryAnalysis?.failurePatterns ?.map((p: any) => `- **${p.pattern}**: ${p.frequency} occurrences`) .join('\n') || 'No failure patterns detected' } ` : '✅ Deployment history stable' } ## 🚨 Critical Blockers (Must Fix Before Push) ${ readinessResult.criticalBlockers ?.map( (blocker: any) => ` ### ${blocker.category.toUpperCase()}: ${blocker.title} - **Impact**: ${blocker.impact} - **Resolution Steps**: ${blocker.resolutionSteps?.join(' → ') || 'See deployment readiness tool for details'} - **Estimated Time**: ${blocker.estimatedResolutionTime || 'Unknown'} ` ) .join('\n') || 'No critical blockers found' } ## 📁 Staged Files (${stagedFiles.length} files ready to push) ${stagedFiles .slice(0, 10) .map(file => `- ${file.status}: ${file.path}`) .join('\n')} ${stagedFiles.length > 10 ? `\n... and ${stagedFiles.length - 10} more files` : ''} ## 🛠️ Required Actions Before Git Push ### 1. Fix Test Issues \`\`\`bash # Run tests and identify failures npm test # Check detailed test coverage npm run test:coverage # Fix failing tests one by one \`\`\` ### 2. Address Deployment History \`\`\`bash # Review recent deployment failures # Fix underlying infrastructure issues # Improve deployment reliability \`\`\` ### 3. Re-validate Deployment Readiness \`\`\`bash # Run comprehensive deployment readiness check deployment_readiness --operation full_audit --target-environment ${targetEnvironment} \`\`\` ### 4. Retry Git Push When Ready \`\`\`bash # Only retry when all blockers are resolved smart_git_push --enforce-deployment-readiness --target-environment ${targetEnvironment} \`\`\` ## ⚠️ Emergency Override (Critical Fixes Only) If this is a critical security fix or emergency: \`\`\`bash # Emergency bypass (requires business justification) deployment_readiness --operation emergency_override --business-justification "Critical security patch" # Then retry push with force override smart_git_push --force-unsafe \`\`\` ## 📋 Deployment Checklist Integration ${ readinessResult.todoTasksCreated?.length > 0 ? ` **Auto-Generated Tasks**: ${readinessResult.todoTasksCreated.length} tasks created Use \`manage_todo_v2 --operation get_tasks\` to view blocking tasks. ` : 'No automatic tasks created' } **❌ GIT PUSH BLOCKED UNTIL ALL CRITICAL ISSUES ARE RESOLVED** This blocking is enforced to prevent failed deployments and protect ${targetEnvironment} environment stability.`; } /** * Exported smart git push function */ export async function smartGitPush(args: SmartGitPushArgs): Promise<any> { const result = await smartGitPushV2(args); return validateMcpResponse(result); } /** * MCP-safe wrapper */ export async function smartGitPushMcpSafe(args: SmartGitPushArgs): Promise<any> { try { return await smartGitPush(args); } catch (error) { const errorResponse = { content: [ { type: 'text', text: '# Smart Git Push - Error\n\n**Error**: ' + jsonSafeError(error) + '\n\nPlease check your git configuration and try again.', }, ], isError: true, }; return validateMcpResponse(errorResponse); } }

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/tosin2013/mcp-adr-analysis-server'

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