mcp-adr-analysis-server 2.0.11 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,922 @@
1
+ /**
2
+ * Smart Git Push MCP Tool - Version 2.0 (Clean)
3
+ *
4
+ * A focused git push tool that ensures security and tracks deployment readiness
5
+ *
6
+ * IMPORTANT FOR AI ASSISTANTS: This tool focuses on:
7
+ * 1. Security: Preventing credential leaks and sensitive data exposure
8
+ * 2. Repository Hygiene: Blocking irrelevant files (temp, build artifacts)
9
+ * 3. Deployment Readiness: Tracking real metrics (test failures, deploy success rate)
10
+ *
11
+ * Cache Dependencies:
12
+ * - CREATES/UPDATES: .mcp-adr-cache/deploy-history.json (deployment metrics)
13
+ * - USES: Enhanced sensitive detector for security scanning
14
+ *
15
+ * This tool does NOT:
16
+ * - Analyze architectural compliance
17
+ * - Update TODO tasks
18
+ * - Check knowledge graph intents
19
+ * - Make complex AI decisions
20
+ *
21
+ * Use this tool for safe, metric-driven git pushes.
22
+ */
23
+ import { McpAdrError } from '../types/index.js';
24
+ import { execSync } from 'child_process';
25
+ import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs';
26
+ import { join, extname } from 'path';
27
+ import { jsonSafeFilePath, jsonSafeError, jsonSafeUserInput } from '../utils/json-safe.js';
28
+ import { validateMcpResponse } from '../utils/mcp-response-validator.js';
29
+ /**
30
+ * Main smart git push function - Security and Metrics Focused
31
+ */
32
+ async function smartGitPushV2(args) {
33
+ 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;
34
+ try {
35
+ // Step 1: Get staged files
36
+ const stagedFiles = await getStagedFiles(projectPath);
37
+ if (stagedFiles.length === 0) {
38
+ const metricsText = await getDeploymentMetricsSummary(projectPath);
39
+ return {
40
+ content: [{
41
+ type: 'text',
42
+ text: createNoChangesResponse(metricsText)
43
+ }]
44
+ };
45
+ }
46
+ // Step 2: Security scan (unless skipped)
47
+ let securityIssues = [];
48
+ if (!skipSecurity) {
49
+ securityIssues = await scanForSecurityIssues(stagedFiles, projectPath);
50
+ }
51
+ // Step 3: Check for irrelevant files
52
+ const irrelevantFiles = await checkIrrelevantFiles(stagedFiles);
53
+ // Step 4: Apply human overrides to security issues and irrelevant files
54
+ const { filteredSecurityIssues, filteredIrrelevantFiles, confirmationRequests } = await applyHumanOverrides(securityIssues, irrelevantFiles, humanOverrides, requestHumanConfirmation);
55
+ // Step 5: Check for confirmation requests
56
+ if (confirmationRequests.length > 0 && requestHumanConfirmation) {
57
+ return {
58
+ content: [{
59
+ type: 'text',
60
+ text: generateConfirmationRequestResponse(confirmationRequests, stagedFiles)
61
+ }]
62
+ };
63
+ }
64
+ // Step 5.5: Deployment Readiness Check (NEW)
65
+ if (checkDeploymentReadiness || enforceDeploymentReadiness) {
66
+ const { deploymentReadiness } = await import('./deployment-readiness-tool.js');
67
+ const readinessCheck = await deploymentReadiness({
68
+ operation: 'full_audit',
69
+ projectPath,
70
+ targetEnvironment,
71
+ strictMode: strictDeploymentMode,
72
+ // Test Gates - Zero tolerance for failures
73
+ maxTestFailures: 0,
74
+ requireTestCoverage: 80,
75
+ blockOnFailingTests: true,
76
+ // Deployment History Gates
77
+ maxRecentFailures: 2,
78
+ deploymentSuccessThreshold: 80,
79
+ blockOnRecentFailures: true,
80
+ rollbackFrequencyThreshold: 20,
81
+ // Integration with existing systems
82
+ integrateTodoTasks: true,
83
+ updateHealthScoring: true
84
+ });
85
+ // Hard block if deployment is not ready and enforcement is enabled
86
+ if (!readinessCheck.isDeploymentReady && (enforceDeploymentReadiness || strictDeploymentMode)) {
87
+ return {
88
+ content: [{
89
+ type: 'text',
90
+ text: generateDeploymentReadinessBlockResponse(readinessCheck, stagedFiles, targetEnvironment)
91
+ }]
92
+ };
93
+ }
94
+ // Soft warning if just checking but not enforcing
95
+ if (!readinessCheck.isDeploymentReady && checkDeploymentReadiness && !enforceDeploymentReadiness) {
96
+ // Continue with push but include warning in success response
97
+ }
98
+ }
99
+ // Step 6: Check blocking conditions (after overrides)
100
+ const hasCriticalSecurity = filteredSecurityIssues.some(issue => issue.severity === 'critical');
101
+ const hasFailedTests = testResults && !testResults.success;
102
+ const shouldBlock = (hasCriticalSecurity || hasFailedTests) && !forceUnsafe;
103
+ if (shouldBlock) {
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: generateBlockedResponse(filteredSecurityIssues, filteredIrrelevantFiles, testResults, humanOverrides)
108
+ }]
109
+ };
110
+ }
111
+ // Step 5: Execute push if not dry run
112
+ if (!dryRun) {
113
+ const pushResult = await executePush(projectPath, branch, message);
114
+ // Update deployment history
115
+ await updateDeploymentHistory(projectPath, {
116
+ success: true,
117
+ ...(testResults && { testResults }),
118
+ filesChanged: stagedFiles.length
119
+ });
120
+ return {
121
+ content: [{
122
+ type: 'text',
123
+ text: generateSuccessResponse(stagedFiles, filteredSecurityIssues, filteredIrrelevantFiles, testResults, pushResult, branch, humanOverrides)
124
+ }]
125
+ };
126
+ }
127
+ else {
128
+ // Dry run response
129
+ return {
130
+ content: [{
131
+ type: 'text',
132
+ text: generateDryRunResponse(stagedFiles, filteredSecurityIssues, filteredIrrelevantFiles, testResults, branch)
133
+ }]
134
+ };
135
+ }
136
+ }
137
+ catch (error) {
138
+ // Update deployment history with failure
139
+ await updateDeploymentHistory(projectPath, {
140
+ success: false,
141
+ testResults: testResults || { success: false, testsRun: 0, testsPassed: 0, testsFailed: 0 },
142
+ filesChanged: 0
143
+ });
144
+ throw new McpAdrError('Smart git push failed: ' + jsonSafeError(error), 'GIT_PUSH_ERROR');
145
+ }
146
+ }
147
+ /**
148
+ * Get staged files
149
+ */
150
+ async function getStagedFiles(projectPath) {
151
+ try {
152
+ const gitOutput = execSync('git diff --cached --name-status', {
153
+ cwd: projectPath,
154
+ encoding: 'utf8'
155
+ });
156
+ if (!gitOutput.trim()) {
157
+ return [];
158
+ }
159
+ const files = [];
160
+ const lines = gitOutput.trim().split('\n');
161
+ for (const line of lines) {
162
+ const [status, ...pathParts] = line.split('\t');
163
+ const path = pathParts.join('\t');
164
+ const fullPath = join(projectPath, path);
165
+ let content;
166
+ let size = 0;
167
+ if (status !== 'D' && existsSync(fullPath)) {
168
+ try {
169
+ const stats = statSync(fullPath);
170
+ size = stats.size;
171
+ // Only read content for small text files (< 100KB)
172
+ if (size < 100 * 1024 && isTextFile(path)) {
173
+ content = readFileSync(fullPath, 'utf8');
174
+ }
175
+ }
176
+ catch (err) {
177
+ // Ignore read errors
178
+ }
179
+ }
180
+ files.push({
181
+ path,
182
+ status: mapGitStatus(status || 'M'),
183
+ content: content || '',
184
+ size
185
+ });
186
+ }
187
+ return files;
188
+ }
189
+ catch (error) {
190
+ throw new McpAdrError('Failed to get staged files: ' + jsonSafeError(error), 'GIT_STATUS_ERROR');
191
+ }
192
+ }
193
+ /**
194
+ * Scan for security issues
195
+ */
196
+ async function scanForSecurityIssues(files, _projectPath) {
197
+ const issues = [];
198
+ try {
199
+ const { analyzeSensitiveContent } = await import('../utils/enhanced-sensitive-detector.js');
200
+ for (const file of files) {
201
+ if (file.status === 'deleted' || !file.content)
202
+ continue;
203
+ const result = await analyzeSensitiveContent(file.path, file.content);
204
+ for (const match of result.matches) {
205
+ // Map to our simplified security issue format
206
+ const severity = match.pattern.severity === 'critical' ? 'critical' :
207
+ match.pattern.severity === 'high' ? 'high' : 'medium';
208
+ issues.push({
209
+ type: mapPatternToType(match.pattern.name),
210
+ severity,
211
+ file: file.path,
212
+ line: match.line,
213
+ pattern: match.pattern.name,
214
+ recommendation: getSecurityRecommendation(match.pattern.name)
215
+ });
216
+ }
217
+ }
218
+ }
219
+ catch (error) {
220
+ console.error('Security scan error:', error);
221
+ }
222
+ return issues;
223
+ }
224
+ /**
225
+ * Check for irrelevant files
226
+ */
227
+ async function checkIrrelevantFiles(files) {
228
+ const irrelevant = [];
229
+ const patterns = {
230
+ 'temp-file': /\.(tmp|temp|cache|swp|swo|swn|bak|backup)$/i,
231
+ 'build-artifact': /^(dist|build|out|target|bin|obj)\//,
232
+ 'ide-config': /^\.(vscode|idea|eclipse|sublime-|atom|brackets)\//,
233
+ 'log-file': /\.(log|logs)$/i,
234
+ 'cache-file': /^(\.(cache|npm|yarn|pnpm)|node_modules|__pycache__|\.pytest_cache)\//
235
+ };
236
+ for (const file of files) {
237
+ if (file.status === 'deleted')
238
+ continue;
239
+ for (const [reason, pattern] of Object.entries(patterns)) {
240
+ if (pattern.test(file.path)) {
241
+ irrelevant.push({
242
+ path: file.path,
243
+ reason: reason,
244
+ recommendation: getIrrelevantFileRecommendation(reason)
245
+ });
246
+ break;
247
+ }
248
+ }
249
+ // Check for large files (> 10MB)
250
+ if (file.size > 10 * 1024 * 1024) {
251
+ irrelevant.push({
252
+ path: file.path,
253
+ reason: 'build-artifact',
254
+ recommendation: 'Large file (' + Math.round(file.size / 1024 / 1024) + 'MB) - consider using Git LFS'
255
+ });
256
+ }
257
+ }
258
+ return irrelevant;
259
+ }
260
+ /**
261
+ * Update deployment history
262
+ */
263
+ async function updateDeploymentHistory(projectPath, result) {
264
+ const cacheDir = join(projectPath, '.mcp-adr-cache');
265
+ const historyFile = join(cacheDir, 'deploy-history.json');
266
+ // Ensure cache directory exists
267
+ if (!existsSync(cacheDir)) {
268
+ mkdirSync(cacheDir, { recursive: true });
269
+ }
270
+ // Load existing history
271
+ let history = {
272
+ successful: 0,
273
+ failed: 0,
274
+ testResults: {
275
+ totalTestsRun: 0,
276
+ totalTestsPassed: 0,
277
+ totalTestsFailed: 0,
278
+ averageDuration: 0,
279
+ testTypes: {}
280
+ },
281
+ successRate: 0,
282
+ testPassRate: 0
283
+ };
284
+ if (existsSync(historyFile)) {
285
+ try {
286
+ history = JSON.parse(readFileSync(historyFile, 'utf8'));
287
+ }
288
+ catch (error) {
289
+ // Ignore parse errors
290
+ }
291
+ }
292
+ // Update history
293
+ if (result.success) {
294
+ history.successful++;
295
+ history.lastDeploy = new Date().toISOString();
296
+ history.lastDeploySuccess = true;
297
+ }
298
+ else {
299
+ history.failed++;
300
+ history.lastDeploy = new Date().toISOString();
301
+ history.lastDeploySuccess = false;
302
+ }
303
+ // Update test results from AI-provided data
304
+ if (result.testResults) {
305
+ history.testResults.totalTestsRun += result.testResults.testsRun;
306
+ history.testResults.totalTestsPassed += result.testResults.testsPassed;
307
+ history.testResults.totalTestsFailed += result.testResults.testsFailed;
308
+ history.testResults.lastRun = new Date().toISOString();
309
+ history.testResults.lastRunSuccess = result.testResults.success;
310
+ // Update test types tracking
311
+ if (result.testResults.testTypes) {
312
+ for (const [testType, results] of Object.entries(result.testResults.testTypes)) {
313
+ if (!history.testResults.testTypes[testType]) {
314
+ history.testResults.testTypes[testType] = { passed: 0, failed: 0 };
315
+ }
316
+ history.testResults.testTypes[testType].passed += results.passed;
317
+ history.testResults.testTypes[testType].failed += results.failed;
318
+ }
319
+ }
320
+ // Update average duration
321
+ if (result.testResults.duration) {
322
+ const currentAvg = history.testResults.averageDuration || 0;
323
+ const totalRuns = history.successful + history.failed;
324
+ history.testResults.averageDuration =
325
+ totalRuns > 1 ? (currentAvg * (totalRuns - 1) + result.testResults.duration) / totalRuns : result.testResults.duration;
326
+ }
327
+ // Set last test suite if command provided
328
+ if (result.testResults.command) {
329
+ history.testResults.lastTestSuite = result.testResults.command;
330
+ }
331
+ }
332
+ // Calculate rates
333
+ const totalDeploys = history.successful + history.failed;
334
+ history.successRate = totalDeploys > 0 ?
335
+ Math.round((history.successful / totalDeploys) * 100) : 0;
336
+ const totalTests = history.testResults.totalTestsPassed + history.testResults.totalTestsFailed;
337
+ history.testPassRate = totalTests > 0 ?
338
+ Math.round((history.testResults.totalTestsPassed / totalTests) * 100) : 0;
339
+ // Save updated history
340
+ writeFileSync(historyFile, JSON.stringify(history, null, 2));
341
+ }
342
+ /**
343
+ * Get deployment metrics summary
344
+ */
345
+ async function getDeploymentMetricsSummary(projectPath) {
346
+ const historyFile = join(projectPath, '.mcp-adr-cache', 'deploy-history.json');
347
+ if (!existsSync(historyFile)) {
348
+ return '- No deployment history available';
349
+ }
350
+ try {
351
+ const history = JSON.parse(readFileSync(historyFile, 'utf8'));
352
+ const lines = [
353
+ '- **Deploy Success Rate**: ' + history.successRate + '% (' + history.successful + '/' + (history.successful + history.failed) + ')',
354
+ '- **Test Pass Rate**: ' + history.testPassRate + '% (' + history.testResults.totalTestsPassed + '/' + (history.testResults.totalTestsPassed + history.testResults.totalTestsFailed) + ')',
355
+ '- **Last Deploy**: ' + (history.lastDeploy ? new Date(history.lastDeploy).toLocaleString() : 'Never') + ' ' + (history.lastDeploySuccess ? '✅' : '❌'),
356
+ '- **Last Test Run**: ' + (history.testResults.lastRun ? new Date(history.testResults.lastRun).toLocaleString() : 'Never') + ' ' + (history.testResults.lastRunSuccess ? '✅' : '❌'),
357
+ '- **Total Tests Executed**: ' + history.testResults.totalTestsRun,
358
+ '- **Avg Test Duration**: ' + (history.testResults.averageDuration ? Math.round(history.testResults.averageDuration) + 's' : 'N/A')
359
+ ];
360
+ return lines.join('\n');
361
+ }
362
+ catch (error) {
363
+ return '- Error reading deployment history';
364
+ }
365
+ }
366
+ /**
367
+ * Execute git push
368
+ */
369
+ async function executePush(projectPath, branch, message) {
370
+ try {
371
+ let output = '';
372
+ if (message) {
373
+ const commitOutput = execSync('git commit -m "' + message + '"', {
374
+ cwd: projectPath,
375
+ encoding: 'utf8'
376
+ });
377
+ output += 'Commit:\n' + commitOutput + '\n\n';
378
+ }
379
+ const pushCommand = branch ? 'git push origin ' + branch : 'git push';
380
+ const pushOutput = execSync(pushCommand, {
381
+ cwd: projectPath,
382
+ encoding: 'utf8',
383
+ stdio: ['pipe', 'pipe', 'pipe']
384
+ });
385
+ output += 'Push:\n' + pushOutput;
386
+ return { output, success: true };
387
+ }
388
+ catch (error) {
389
+ throw new McpAdrError('Git push failed: ' + jsonSafeError(error), 'GIT_PUSH_FAILED');
390
+ }
391
+ }
392
+ // Helper functions
393
+ function mapGitStatus(status) {
394
+ switch (status) {
395
+ case 'A': return 'added';
396
+ case 'M': return 'modified';
397
+ case 'D': return 'deleted';
398
+ case 'R': return 'renamed';
399
+ default: return 'modified';
400
+ }
401
+ }
402
+ function isTextFile(path) {
403
+ const textExtensions = [
404
+ '.js', '.ts', '.jsx', '.tsx', '.json', '.md', '.txt', '.yml', '.yaml',
405
+ '.py', '.rb', '.java', '.cs', '.go', '.rs', '.cpp', '.c', '.h', '.hpp',
406
+ '.php', '.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte'
407
+ ];
408
+ return textExtensions.includes(extname(path).toLowerCase());
409
+ }
410
+ function mapPatternToType(patternName) {
411
+ if (patternName.includes('api') || patternName.includes('key'))
412
+ return 'api-key';
413
+ if (patternName.includes('private') || patternName.includes('rsa'))
414
+ return 'private-key';
415
+ if (patternName.includes('token'))
416
+ return 'token';
417
+ if (patternName.includes('password') || patternName.includes('pwd'))
418
+ return 'password';
419
+ return 'credential';
420
+ }
421
+ function getSecurityRecommendation(pattern) {
422
+ if (pattern.includes('api'))
423
+ return 'Move API keys to environment variables';
424
+ if (pattern.includes('private'))
425
+ return 'Never commit private keys - use key management service';
426
+ if (pattern.includes('token'))
427
+ return 'Use environment variables or secure token storage';
428
+ if (pattern.includes('password'))
429
+ return 'Never hardcode passwords - use secure credential storage';
430
+ return 'Remove sensitive data and use environment variables';
431
+ }
432
+ function getIrrelevantFileRecommendation(reason) {
433
+ switch (reason) {
434
+ case 'temp-file': return 'Add to .gitignore - temporary files should not be committed';
435
+ case 'build-artifact': return 'Add to .gitignore - build outputs should not be in source control';
436
+ case 'ide-config': return 'Add to .gitignore - IDE configurations are user-specific';
437
+ case 'log-file': return 'Add to .gitignore - log files should not be committed';
438
+ case 'cache-file': return 'Add to .gitignore - cache files are generated';
439
+ default: return 'Consider adding to .gitignore';
440
+ }
441
+ }
442
+ // Human Override Functions
443
+ /**
444
+ * Apply human overrides to security issues and irrelevant files
445
+ */
446
+ async function applyHumanOverrides(securityIssues, irrelevantFiles, humanOverrides, requestConfirmation) {
447
+ const confirmationRequests = [];
448
+ // Filter security issues based on human overrides
449
+ const filteredSecurityIssues = securityIssues.filter(issue => {
450
+ const override = humanOverrides.find(o => o.path === issue.file && o.userConfirmed);
451
+ if (override) {
452
+ return false; // Remove from blocking issues
453
+ }
454
+ // If requesting confirmation and no override exists, create confirmation request
455
+ if (requestConfirmation && !humanOverrides.find(o => o.path === issue.file)) {
456
+ confirmationRequests.push({
457
+ path: issue.file,
458
+ detectedIssue: issue,
459
+ suggestedPurpose: analyzeFilePurpose(issue.file),
460
+ riskLevel: issue.severity,
461
+ alternatives: generateAlternatives(issue)
462
+ });
463
+ }
464
+ return true; // Keep issue if no override
465
+ });
466
+ // Filter irrelevant files based on human overrides
467
+ const filteredIrrelevantFiles = irrelevantFiles.filter(file => {
468
+ const override = humanOverrides.find(o => o.path === file.path && o.userConfirmed);
469
+ if (override) {
470
+ return false; // Remove from irrelevant files
471
+ }
472
+ // If requesting confirmation and no override exists, create confirmation request
473
+ if (requestConfirmation && !humanOverrides.find(o => o.path === file.path)) {
474
+ confirmationRequests.push({
475
+ path: file.path,
476
+ detectedIssue: file,
477
+ suggestedPurpose: analyzeFilePurpose(file.path),
478
+ riskLevel: 'medium',
479
+ alternatives: generateAlternatives(file)
480
+ });
481
+ }
482
+ return true; // Keep file if no override
483
+ });
484
+ return {
485
+ filteredSecurityIssues,
486
+ filteredIrrelevantFiles,
487
+ confirmationRequests
488
+ };
489
+ }
490
+ /**
491
+ * Analyze file purpose using LLM-style heuristics
492
+ */
493
+ function analyzeFilePurpose(filePath) {
494
+ const fileName = filePath.toLowerCase();
495
+ // Configuration files
496
+ if (fileName.includes('config') || fileName.endsWith('.config.js') || fileName.endsWith('.config.ts')) {
497
+ return 'Configuration file - may contain environment-specific settings';
498
+ }
499
+ // Build/deployment files
500
+ if (fileName.includes('build') || fileName.includes('dist') || fileName.includes('deploy')) {
501
+ return 'Build or deployment artifact - usually not committed to source control';
502
+ }
503
+ // Development/debug files
504
+ if (fileName.includes('debug') || fileName.includes('test') || fileName.includes('temp')) {
505
+ return 'Development or debugging file - may be temporary';
506
+ }
507
+ // IDE/editor files
508
+ if (fileName.includes('.vscode') || fileName.includes('.idea') || fileName.includes('.settings')) {
509
+ return 'IDE/editor configuration - user-specific preferences';
510
+ }
511
+ // Log files
512
+ if (fileName.includes('log') || fileName.endsWith('.log')) {
513
+ return 'Log file - runtime data that changes frequently';
514
+ }
515
+ // Security-related
516
+ if (fileName.includes('key') || fileName.includes('secret') || fileName.includes('credential')) {
517
+ return 'Security-sensitive file - may contain credentials or keys';
518
+ }
519
+ // Cache/temporary
520
+ if (fileName.includes('cache') || fileName.includes('node_modules') || fileName.includes('.cache')) {
521
+ return 'Cache or temporary data - generated content';
522
+ }
523
+ return 'Unknown purpose - manual review recommended';
524
+ }
525
+ /**
526
+ * Generate alternatives for problematic files
527
+ */
528
+ function generateAlternatives(issue) {
529
+ const alternatives = [];
530
+ if ('severity' in issue) {
531
+ // Security issue alternatives
532
+ switch (issue.type) {
533
+ case 'api-key':
534
+ alternatives.push('Move to environment variables (.env file)');
535
+ alternatives.push('Use secure credential management service');
536
+ alternatives.push('Store in CI/CD pipeline secrets');
537
+ break;
538
+ case 'private-key':
539
+ alternatives.push('Use key management service (AWS KMS, Azure Key Vault)');
540
+ alternatives.push('Store locally and reference by path');
541
+ alternatives.push('Use certificate-based authentication');
542
+ break;
543
+ case 'token':
544
+ alternatives.push('Generate token at runtime');
545
+ alternatives.push('Use OAuth flow instead of static tokens');
546
+ alternatives.push('Store in secure credential store');
547
+ break;
548
+ default:
549
+ alternatives.push('Use environment variables');
550
+ alternatives.push('External configuration management');
551
+ }
552
+ }
553
+ else {
554
+ // Irrelevant file alternatives
555
+ switch (issue.reason) {
556
+ case 'build-artifact':
557
+ alternatives.push('Add to .gitignore and rebuild on deployment');
558
+ alternatives.push('Use CI/CD pipeline to generate artifacts');
559
+ alternatives.push('Store in artifact repository (npm, Docker registry)');
560
+ break;
561
+ case 'temp-file':
562
+ alternatives.push('Delete file after use');
563
+ alternatives.push('Add to .gitignore');
564
+ alternatives.push('Use system temp directory');
565
+ break;
566
+ case 'ide-config':
567
+ alternatives.push('Add to .gitignore');
568
+ alternatives.push('Create shared .vscode/settings.json for team settings');
569
+ alternatives.push('Document setup instructions in README');
570
+ break;
571
+ default:
572
+ alternatives.push('Add to .gitignore');
573
+ alternatives.push('Review if file is necessary');
574
+ }
575
+ }
576
+ return alternatives;
577
+ }
578
+ // Response generators
579
+ function createNoChangesResponse(metricsText) {
580
+ return '# Smart Git Push - No Changes\n\n' +
581
+ '## Status\n' +
582
+ 'No staged files found. Use git add to stage files before pushing.\n\n' +
583
+ '## Deployment Metrics\n' +
584
+ metricsText + '\n\n' +
585
+ '## ⚠️ IMPORTANT: Selective File Staging\n' +
586
+ '**DO NOT USE:** `git add .` or `git add -A` (stages everything including unintended files)\n\n' +
587
+ '**RECOMMENDED APPROACH:**\n' +
588
+ '1. Review changes: `git status` and `git diff`\n' +
589
+ '2. Stage specific files: `git add <specific-file>`\n' +
590
+ '3. Verify staged files: `git diff --cached`\n' +
591
+ '4. Only then commit and push\n\n' +
592
+ '## Safe Commands\n' +
593
+ '- `git status` - Check current status\n' +
594
+ '- `git diff` - See unstaged changes\n' +
595
+ '- `git add <specific-file>` - Stage specific file only\n' +
596
+ '- `git diff --cached` - Review staged changes\n\n' +
597
+ '## 🚀 Deployment Readiness Checklist\n' +
598
+ '- [ ] All tests passing locally\n' +
599
+ '- [ ] Code reviewed and approved\n' +
600
+ '- [ ] No sensitive data in changes\n' +
601
+ '- [ ] Documentation updated if needed\n' +
602
+ '- [ ] Deployment strategy confirmed';
603
+ }
604
+ /**
605
+ * Generate confirmation request response for human override
606
+ */
607
+ function generateConfirmationRequestResponse(confirmationRequests, stagedFiles) {
608
+ let response = '# Smart Git Push - Human Confirmation Required 🤔\n\n' +
609
+ '## Files Requiring Manual Review\n\n' +
610
+ `The LLM has detected ${confirmationRequests.length} files that need your confirmation before proceeding.\n\n`;
611
+ confirmationRequests.forEach((request, index) => {
612
+ const isSecurityIssue = 'severity' in request.detectedIssue;
613
+ const riskEmoji = request.riskLevel === 'critical' ? '🔴' :
614
+ request.riskLevel === 'high' ? '🟠' :
615
+ request.riskLevel === 'medium' ? '🟡' : '🟢';
616
+ response += `### ${index + 1}. ${jsonSafeFilePath(request.path)} ${riskEmoji}\n\n` +
617
+ `**Detected Issue**: ${isSecurityIssue ? 'Security Risk' : 'Irrelevant File'}\n` +
618
+ `**Risk Level**: ${request.riskLevel.toUpperCase()}\n` +
619
+ `**LLM Analysis**: ${request.suggestedPurpose}\n\n` +
620
+ `**Recommended Alternatives**:\n${request.alternatives.map(alt => `- ${alt}`).join('\n')}\n\n` +
621
+ `**To Override**: Confirm this file's purpose and business justification.\n\n`;
622
+ });
623
+ response += '## Override Instructions\n\n' +
624
+ 'To proceed with these files, rerun the command with human overrides:\n\n' +
625
+ '```json\n' +
626
+ '{\n' +
627
+ ' "operation": "smart_git_push",\n' +
628
+ ' "humanOverrides": [\n' +
629
+ confirmationRequests.map(req => ' {\n' +
630
+ ` "path": "${req.path}",\n` +
631
+ ` "purpose": "YOUR_BUSINESS_JUSTIFICATION_HERE",\n` +
632
+ ' "userConfirmed": true,\n' +
633
+ ` "timestamp": "${new Date().toISOString()}",\n` +
634
+ ' "overrideReason": "business-requirement", // or security-exception, deployment-necessity, etc.\n' +
635
+ ' "additionalContext": "EXPLAIN_WHY_THIS_IS_NECESSARY"\n' +
636
+ ' }').join(',\n') + '\n' +
637
+ ' ]\n' +
638
+ '}\n' +
639
+ '```\n\n' +
640
+ '## ⚠️ Important Reminders\n' +
641
+ '- **Only override if you understand the business need**\n' +
642
+ '- **Document the purpose clearly**\n' +
643
+ '- **Consider the security implications**\n' +
644
+ '- **Review alternatives before overriding**\n\n' +
645
+ `**Total staged files**: ${stagedFiles.length} | **Files needing confirmation**: ${confirmationRequests.length}`;
646
+ return response;
647
+ }
648
+ function generateBlockedResponse(securityIssues, _irrelevantFiles, testResults, humanOverrides) {
649
+ let response = '# Smart Git Push - Blocked 🚫\n\n## Push Blocked Due to Critical Issues\n\n';
650
+ if (securityIssues.some(i => i.severity === 'critical')) {
651
+ const criticalIssues = securityIssues.filter(i => i.severity === 'critical').map(issue => '- **' + issue.type + '** in ' + jsonSafeFilePath(issue.file) + (issue.line ? ' (line ' + issue.line + ')' : '') + '\n' +
652
+ ' Pattern: ' + issue.pattern + '\n' +
653
+ ' Fix: ' + issue.recommendation).join('\n\n');
654
+ response += '### 🔐 Critical Security Issues Found\n' + criticalIssues + '\n\n';
655
+ }
656
+ if (testResults && !testResults.success) {
657
+ response += '### 🧪 Tests Failed\n' +
658
+ '- **Command**: ' + testResults.command + '\n' +
659
+ '- **Output**:\n' +
660
+ '-------\n' +
661
+ jsonSafeUserInput(testResults.output) + '\n' +
662
+ '-------\n\n';
663
+ }
664
+ // Show human override status if provided
665
+ if (humanOverrides && humanOverrides.length > 0) {
666
+ response += '## Human Overrides Applied\n';
667
+ humanOverrides.forEach(override => {
668
+ response += `- **${jsonSafeFilePath(override.path)}**: ${override.purpose}\n` +
669
+ ` Reason: ${override.overrideReason} | Confirmed: ${override.userConfirmed ? '✅' : '❌'}\n`;
670
+ });
671
+ response += '\n';
672
+ }
673
+ response += '## Required Actions\n' +
674
+ '1. Fix all critical security issues\n' +
675
+ '2. Ensure all tests pass\n' +
676
+ '3. Review and fix any warnings\n' +
677
+ '4. OR use human overrides with proper justification\n\n' +
678
+ '## ⚠️ IMPORTANT: File Staging Best Practices\n' +
679
+ '**NEVER USE:** `git add .` or `git add -A` when fixing issues\n' +
680
+ '**RECOMMENDED:**\n' +
681
+ '1. Fix specific issues in specific files\n' +
682
+ '2. Stage only the fixed files: `git add <fixed-file>`\n' +
683
+ '3. Verify changes: `git diff --cached`\n' +
684
+ '4. Re-run smart git push\n\n' +
685
+ '## Human Override Option\n' +
686
+ 'If these issues are expected and have business justification:\n' +
687
+ '1. Use `requestHumanConfirmation: true` to get guided override instructions\n' +
688
+ '2. Provide detailed justification for each file\n' +
689
+ '3. Consider security implications carefully\n\n' +
690
+ '🚫 Use --forceUnsafe to override (NOT RECOMMENDED)';
691
+ return response;
692
+ }
693
+ function generateSuccessResponse(stagedFiles, securityIssues, irrelevantFiles, testResults, pushResult, branch, humanOverrides) {
694
+ let response = '# Smart Git Push - Success ✅\n\n' +
695
+ '## Push Summary\n' +
696
+ '- **Branch**: ' + (branch || 'current') + '\n' +
697
+ '- **Files**: ' + stagedFiles.length + ' staged files\n' +
698
+ '- **Security Issues**: ' + securityIssues.length + ' (' + securityIssues.filter(i => i.severity === 'critical').length + ' critical)\n' +
699
+ '- **Irrelevant Files**: ' + irrelevantFiles.length + '\n' +
700
+ '- **Tests**: ' + (testResults ? (testResults.success ? '✅ Passed' : '❌ Failed') : 'Skipped') + '\n\n' +
701
+ '## Deployment Metrics Updated\n' +
702
+ getDeploymentMetricsUpdate() + '\n\n' +
703
+ '## Files Pushed\n' +
704
+ stagedFiles.map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.status + ')').join('\n');
705
+ if (securityIssues.length > 0) {
706
+ const nonCriticalIssues = securityIssues.filter(i => i.severity !== 'critical');
707
+ if (nonCriticalIssues.length > 0) {
708
+ response += '\n\n## Security Warnings (Non-Critical)\n' +
709
+ nonCriticalIssues.map(issue => '- **' + issue.type + '** in ' + jsonSafeFilePath(issue.file) + ': ' + issue.recommendation).join('\n');
710
+ }
711
+ }
712
+ if (irrelevantFiles.length > 0) {
713
+ response += '\n\n## Irrelevant Files (Consider .gitignore)\n' +
714
+ irrelevantFiles.map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.reason + '): ' + f.recommendation).join('\n');
715
+ }
716
+ // Show human overrides if any were applied
717
+ if (humanOverrides && humanOverrides.length > 0) {
718
+ response += '\n\n## ✅ Human Overrides Applied\n' +
719
+ 'The following files were explicitly approved by human override:\n' +
720
+ humanOverrides.filter(o => o.userConfirmed).map(override => `- **${jsonSafeFilePath(override.path)}**: ${override.purpose}\n` +
721
+ ` Justification: ${override.additionalContext || 'No additional context'}\n` +
722
+ ` Override Reason: ${override.overrideReason}\n` +
723
+ ` Timestamp: ${new Date(override.timestamp).toLocaleString()}`).join('\n\n') + '\n\n' +
724
+ '📝 **Note**: These overrides have been logged for audit purposes.';
725
+ }
726
+ response += '\n\n## Git Output\n' +
727
+ '-------\n' +
728
+ jsonSafeUserInput(pushResult.output) + '\n' +
729
+ '-------\n\n' +
730
+ '## 🚀 Post-Push Deployment Checklist\n' +
731
+ '### Immediate Actions (Next 5 minutes)\n' +
732
+ '- [ ] Monitor CI/CD pipeline status\n' +
733
+ '- [ ] Check automated test results\n' +
734
+ '- [ ] Verify build completion\n' +
735
+ '- [ ] Review any deployment warnings\n\n' +
736
+ '### Deployment Completion (Next 15 minutes)\n' +
737
+ '- [ ] Confirm deployment to staging/production\n' +
738
+ '- [ ] Verify application health checks\n' +
739
+ '- [ ] Test key functionality post-deployment\n' +
740
+ '- [ ] Monitor error rates and performance metrics\n' +
741
+ '- [ ] Update TODO list with deployment status\n\n' +
742
+ '### Documentation & Communication\n' +
743
+ '- [ ] Update deployment notes\n' +
744
+ '- [ ] Notify team of successful deployment\n' +
745
+ '- [ ] Close related tickets/issues\n' +
746
+ '- [ ] Schedule post-deployment review if needed\n\n' +
747
+ '💡 **Tip**: Use the TODO management tool to track deployment completion tasks!';
748
+ return response;
749
+ }
750
+ function generateDryRunResponse(stagedFiles, securityIssues, irrelevantFiles, testResults, branch) {
751
+ const hasCritical = securityIssues.some(i => i.severity === 'critical');
752
+ const wouldBlock = hasCritical || (testResults && !testResults.success);
753
+ let response = '# Smart Git Push - Dry Run 🔍\n\n' +
754
+ '## Analysis Results\n' +
755
+ '- **Files to Push**: ' + stagedFiles.length + '\n' +
756
+ '- **Security Issues**: ' + securityIssues.length + ' (' + securityIssues.filter(i => i.severity === 'critical').length + ' critical)\n' +
757
+ '- **Irrelevant Files**: ' + irrelevantFiles.length + '\n' +
758
+ '- **Would Block**: ' + (wouldBlock ? '❌ Yes' : '✅ No') + '\n\n' +
759
+ '## Staged Files\n' +
760
+ stagedFiles.map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.status + ') - ' + f.size + ' bytes').join('\n');
761
+ if (securityIssues.length > 0) {
762
+ response += '\n\n## Security Issues Found\n' +
763
+ securityIssues.map(issue => '- **' + issue.severity.toUpperCase() + '** ' + issue.type + ' in ' + jsonSafeFilePath(issue.file) + (issue.line ? ' (line ' + issue.line + ')' : '') + '\n' +
764
+ ' Fix: ' + issue.recommendation).join('\n');
765
+ }
766
+ else {
767
+ response += '\n\n## ✅ No Security Issues Found';
768
+ }
769
+ if (irrelevantFiles.length > 0) {
770
+ response += '\n\n## Irrelevant Files Detected\n' +
771
+ irrelevantFiles.map(f => '- ' + jsonSafeFilePath(f.path) + ' (' + f.reason + ')\n' +
772
+ ' ' + f.recommendation).join('\n');
773
+ }
774
+ response += '\n\n## ⚠️ IMPORTANT: Pre-Push Staging Review\n' +
775
+ '**AVOID:** `git add .` or `git add -A` before this tool\n' +
776
+ '**RECOMMENDED:**\n' +
777
+ '1. Review each staged file individually\n' +
778
+ '2. Verify changes: `git diff --cached`\n' +
779
+ '3. Ensure only intended files are staged\n\n' +
780
+ '## Command to Execute\n' +
781
+ '-------\n' +
782
+ '# Run without dry run to actually push\n' +
783
+ 'git push' + (branch ? ' origin ' + branch : '') + '\n' +
784
+ '-------\n\n' +
785
+ '**Note:** This was a dry run. No files were pushed.' +
786
+ (wouldBlock ? '\n⚠️ **Warning**: This push would be BLOCKED due to critical issues.' : '') + '\n\n' +
787
+ '## 🚀 Deployment Readiness Assessment\n' +
788
+ (wouldBlock ?
789
+ '❌ **NOT READY** - Fix issues above before deployment' :
790
+ '✅ **READY** - This push appears safe for deployment\n\n' +
791
+ '### Post-Push Checklist\n' +
792
+ '- [ ] Monitor CI/CD pipeline\n' +
793
+ '- [ ] Verify deployment health\n' +
794
+ '- [ ] Update TODO tasks\n' +
795
+ '- [ ] Document deployment notes');
796
+ return response;
797
+ }
798
+ function getDeploymentMetricsUpdate() {
799
+ return '- Deploy success rate updated\n' +
800
+ '- Test results recorded\n' +
801
+ '- Metrics available in .mcp-adr-cache/deploy-history.json';
802
+ }
803
+ /**
804
+ * Generate deployment readiness block response
805
+ */
806
+ function generateDeploymentReadinessBlockResponse(readinessResult, stagedFiles, targetEnvironment) {
807
+ return `🚨 **DEPLOYMENT BLOCKED - Critical Readiness Issues**
808
+
809
+ ## 🎯 Deployment Readiness Assessment
810
+ - **Target Environment**: ${targetEnvironment}
811
+ - **Deployment Ready**: ❌ **NO**
812
+ - **Readiness Score**: ${readinessResult.overallScore || 0}%
813
+ - **Confidence Level**: ${readinessResult.confidence || 0}%
814
+
815
+ ## 🧪 Test Validation Issues
816
+ ${readinessResult.testFailureBlockers?.length > 0 ? `
817
+ **Test Failures**: ${readinessResult.testValidationResult?.failureCount || 0} failures detected
818
+ **Test Coverage**: ${readinessResult.testValidationResult?.coveragePercentage || 0}% (Required: 80%)
819
+
820
+ ### Critical Test Failures:
821
+ ${readinessResult.testValidationResult?.criticalTestFailures?.map((f) => `- ❌ ${f.testSuite}: ${f.testName}`).join('\n') || 'No critical test failures'}
822
+ ` : '✅ All tests passing'}
823
+
824
+ ## 📊 Deployment History Issues
825
+ ${readinessResult.deploymentHistoryBlockers?.length > 0 ? `
826
+ **Success Rate**: ${readinessResult.deploymentHistoryAnalysis?.successRate || 0}% (Required: 80%)
827
+ **Rollback Rate**: ${readinessResult.deploymentHistoryAnalysis?.rollbackRate || 0}% (Threshold: 20%)
828
+
829
+ ### Recent Failure Patterns:
830
+ ${readinessResult.deploymentHistoryAnalysis?.failurePatterns?.map((p) => `- **${p.pattern}**: ${p.frequency} occurrences`).join('\n') || 'No failure patterns detected'}
831
+ ` : '✅ Deployment history stable'}
832
+
833
+ ## 🚨 Critical Blockers (Must Fix Before Push)
834
+ ${readinessResult.criticalBlockers?.map((blocker) => `
835
+ ### ${blocker.category.toUpperCase()}: ${blocker.title}
836
+ - **Impact**: ${blocker.impact}
837
+ - **Resolution Steps**: ${blocker.resolutionSteps?.join(' → ') || 'See deployment readiness tool for details'}
838
+ - **Estimated Time**: ${blocker.estimatedResolutionTime || 'Unknown'}
839
+ `).join('\n') || 'No critical blockers found'}
840
+
841
+ ## 📁 Staged Files (${stagedFiles.length} files ready to push)
842
+ ${stagedFiles.slice(0, 10).map(file => `- ${file.status}: ${file.path}`).join('\n')}
843
+ ${stagedFiles.length > 10 ? `\n... and ${stagedFiles.length - 10} more files` : ''}
844
+
845
+ ## 🛠️ Required Actions Before Git Push
846
+
847
+ ### 1. Fix Test Issues
848
+ \`\`\`bash
849
+ # Run tests and identify failures
850
+ npm test
851
+
852
+ # Check detailed test coverage
853
+ npm run test:coverage
854
+
855
+ # Fix failing tests one by one
856
+ \`\`\`
857
+
858
+ ### 2. Address Deployment History
859
+ \`\`\`bash
860
+ # Review recent deployment failures
861
+ # Fix underlying infrastructure issues
862
+ # Improve deployment reliability
863
+ \`\`\`
864
+
865
+ ### 3. Re-validate Deployment Readiness
866
+ \`\`\`bash
867
+ # Run comprehensive deployment readiness check
868
+ deployment_readiness --operation full_audit --target-environment ${targetEnvironment}
869
+ \`\`\`
870
+
871
+ ### 4. Retry Git Push When Ready
872
+ \`\`\`bash
873
+ # Only retry when all blockers are resolved
874
+ smart_git_push --enforce-deployment-readiness --target-environment ${targetEnvironment}
875
+ \`\`\`
876
+
877
+ ## ⚠️ Emergency Override (Critical Fixes Only)
878
+ If this is a critical security fix or emergency:
879
+ \`\`\`bash
880
+ # Emergency bypass (requires business justification)
881
+ deployment_readiness --operation emergency_override --business-justification "Critical security patch"
882
+
883
+ # Then retry push with force override
884
+ smart_git_push --force-unsafe
885
+ \`\`\`
886
+
887
+ ## 📋 Deployment Checklist Integration
888
+ ${readinessResult.todoTasksCreated?.length > 0 ? `
889
+ **Auto-Generated Tasks**: ${readinessResult.todoTasksCreated.length} tasks created
890
+ Use \`manage_todo_v2 --operation get_tasks\` to view blocking tasks.
891
+ ` : 'No automatic tasks created'}
892
+
893
+ **❌ GIT PUSH BLOCKED UNTIL ALL CRITICAL ISSUES ARE RESOLVED**
894
+
895
+ This blocking is enforced to prevent failed deployments and protect ${targetEnvironment} environment stability.`;
896
+ }
897
+ /**
898
+ * Exported smart git push function
899
+ */
900
+ export async function smartGitPush(args) {
901
+ const result = await smartGitPushV2(args);
902
+ return validateMcpResponse(result);
903
+ }
904
+ /**
905
+ * MCP-safe wrapper
906
+ */
907
+ export async function smartGitPushMcpSafe(args) {
908
+ try {
909
+ return await smartGitPush(args);
910
+ }
911
+ catch (error) {
912
+ const errorResponse = {
913
+ content: [{
914
+ type: 'text',
915
+ text: '# Smart Git Push - Error\n\n**Error**: ' + jsonSafeError(error) + '\n\nPlease check your git configuration and try again.'
916
+ }],
917
+ isError: true
918
+ };
919
+ return validateMcpResponse(errorResponse);
920
+ }
921
+ }
922
+ //# sourceMappingURL=smart-git-push-tool-v2.js.map