mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
922 lines (903 loc) • 41.7 kB
JavaScript
/**
* 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