UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

922 lines (903 loc) 41.7 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 } from 'path'; import { jsonSafeFilePath, jsonSafeError, jsonSafeUserInput } from '../utils/json-safe.js'; import { validateMcpResponse } from '../utils/mcp-response-validator.js'; /** * Main smart git push function - Security and Metrics Focused */ async function smartGitPushV2(args) { 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 = []; 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) { try { const gitOutput = execSync('git diff --cached --name-status', { cwd: projectPath, encoding: 'utf8' }); if (!gitOutput.trim()) { return []; } const files = []; 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; 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 (err) { // 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, _projectPath) { const issues = []; try { const { analyzeSensitiveContent } = await import('../utils/enhanced-sensitive-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) { const irrelevant = []; 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, 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, result) { const cacheDir = join(projectPath, '.mcp-adr-cache'); const historyFile = join(cacheDir, 'deploy-history.json'); // Ensure cache directory exists if (!existsSync(cacheDir)) { mkdirSync(cacheDir, { recursive: true }); } // Load existing history let history = { 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 (error) { // 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) { const historyFile = join(projectPath, '.mcp-adr-cache', 'deploy-history.json'); if (!existsSync(historyFile)) { return '- No deployment history available'; } try { const history = 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 (error) { return '- Error reading deployment history'; } } /** * Execute git push */ async function executePush(projectPath, branch, message) { 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) { switch (status) { case 'A': return 'added'; case 'M': return 'modified'; case 'D': return 'deleted'; case 'R': return 'renamed'; default: return 'modified'; } } function isTextFile(path) { 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) { 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) { 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) { 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, irrelevantFiles, humanOverrides, requestConfirmation) { const confirmationRequests = []; // 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, 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) { 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) { const alternatives = []; 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) { 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, stagedFiles) { 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, _irrelevantFiles, testResults, humanOverrides) { 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, securityIssues, irrelevantFiles, testResults, pushResult, branch, humanOverrides) { 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, securityIssues, irrelevantFiles, testResults, branch) { 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() { 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, stagedFiles, targetEnvironment) { 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) => `- ❌ ${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) => `- **${p.pattern}**: ${p.frequency} occurrences`).join('\n') || 'No failure patterns detected'} ` : '✅ Deployment history stable'} ## 🚨 Critical Blockers (Must Fix Before Push) ${readinessResult.criticalBlockers?.map((blocker) => ` ### ${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) { const result = await smartGitPushV2(args); return validateMcpResponse(result); } /** * MCP-safe wrapper */ export async function smartGitPushMcpSafe(args) { 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); } } //# sourceMappingURL=smart-git-push-tool-v2.js.map