UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

327 lines 12.4 kB
/** * Location-based filtering system for smart git push * * Determines if files are in appropriate locations based on their type and content */ import { basename, dirname, extname } from 'path'; /** * Default location rules for common development artifacts */ export const DEFAULT_LOCATION_RULES = [ // Debug files { name: 'debug-scripts', description: 'Debug scripts should be in development directories', filePattern: /^debug_.*\.(py|js|ts|sh|rb|go)$/, contentPattern: /print\s*\(.*debug.*\)|console\.log\s*\(.*debug.*\)|logging\.debug|debugger;/i, allowedPaths: ['tests/', 'test/', 'scripts/', 'tools/', 'dev/', 'debug/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'warning', category: 'development' }, // Test files { name: 'test-files', description: 'Test files should be in test directories', filePattern: /^test_.*\.(py|js|ts|rb|go)$|.*\.test\.(js|ts|py)$|.*\.spec\.(js|ts|py)$/, allowedPaths: ['tests/', 'test/', '__tests__/', 'spec/', 'specs/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'error', category: 'testing' }, // Mock files { name: 'mock-data', description: 'Mock data files should be in test or fixture directories', filePattern: /^mock_.*\.(json|js|ts|py|yaml|yml)$/, contentPattern: /mock|fixture|fake.*data|dummy.*data/i, allowedPaths: ['tests/', 'test/', '__tests__/', 'fixtures/', 'mocks/', 'dev/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'warning', category: 'testing' }, // Temporary files { name: 'temporary-files', description: 'Temporary files should not be committed', filePattern: /^temp_.*|.*\.tmp$|.*\.temp$|.*\.bak$|.*\.orig$/, allowedPaths: ['tmp/', 'temp/', 'dev/'], blockedPaths: ['src/', 'lib/', 'app/', 'tests/', './'], severity: 'error', category: 'temporary' }, // Log files { name: 'log-files', description: 'Log files should not be committed to main codebase', filePattern: /.*\.log$|.*\.out$|.*\.err$/, allowedPaths: ['logs/', 'dev/', 'examples/'], blockedPaths: ['src/', 'lib/', 'app/', 'tests/', './'], severity: 'error', category: 'temporary' }, // Configuration files with secrets { name: 'config-secrets', description: 'Configuration files with secrets need careful review', filePattern: /\.env$|config\..*\.(json|yaml|yml|ini)$|secrets\..*$/, contentPattern: /password|secret|key|token|api.*key|private.*key/i, allowedPaths: ['config/', 'configs/', 'env/', 'examples/'], blockedPaths: ['./'], severity: 'error', category: 'security' }, // Scripts { name: 'utility-scripts', description: 'Utility scripts should be in scripts directory', filePattern: /^util_.*\.(sh|py|js|ts|rb)$|^helper_.*\.(sh|py|js|ts|rb)$/, allowedPaths: ['scripts/', 'tools/', 'bin/', 'utils/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'info', category: 'development' }, // Experimental code { name: 'experimental-code', description: 'Experimental code should be in development directories', filePattern: /^experiment_.*|^poc_.*|^prototype_.*|^scratch_.*|^playground_.*/, allowedPaths: ['experiments/', 'poc/', 'dev/', 'playground/', 'prototypes/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'warning', category: 'development' }, // Documentation drafts { name: 'documentation-drafts', description: 'Documentation drafts should be organized properly', filePattern: /^draft_.*\.md$|^notes_.*\.md$|^todo_.*\.md$/, allowedPaths: ['docs/', 'documentation/', 'dev/', 'drafts/'], blockedPaths: ['src/', 'lib/', 'app/', './'], severity: 'info', category: 'development' } ]; /** * Validate file location against rules */ export function validateFileLocation(filePath, content, customRules = []) { const fileName = basename(filePath); const dirPath = dirname(filePath); const allRules = [...DEFAULT_LOCATION_RULES, ...customRules]; // Check each rule for (const rule of allRules) { if (rule.filePattern.test(fileName)) { // Check content pattern if specified if (rule.contentPattern && content && !rule.contentPattern.test(content)) { continue; // Content pattern doesn't match, skip this rule } // Check if current path is blocked const isBlocked = rule.blockedPaths.some(blocked => dirPath.startsWith(blocked) || dirPath === blocked.replace('/', '')); if (isBlocked) { return { isValid: false, rule, currentPath: dirPath, suggestedPaths: rule.allowedPaths, severity: rule.severity, message: `${rule.description}. Currently in '${dirPath}/', should be in: ${rule.allowedPaths.join(', ')}` }; } // Check if current path is allowed const isAllowed = rule.allowedPaths.some(allowed => dirPath.startsWith(allowed) || dirPath === allowed.replace('/', '')); if (!isAllowed) { return { isValid: false, rule, currentPath: dirPath, suggestedPaths: rule.allowedPaths, severity: rule.severity, message: `${rule.description}. Currently in '${dirPath}/', should be in: ${rule.allowedPaths.join(', ')}` }; } // File is in correct location return { isValid: true, rule, currentPath: dirPath, suggestedPaths: [], severity: 'info', message: `File is correctly located in '${dirPath}/'` }; } } // No specific rule found, file is generally acceptable return { isValid: true, currentPath: dirPath, suggestedPaths: [], severity: 'info', message: 'No specific location restrictions apply' }; } /** * Get location suggestions for a file */ export function getLocationSuggestions(filePath, content, customRules = []) { const fileName = basename(filePath); const fileExt = extname(filePath); const allRules = [...DEFAULT_LOCATION_RULES, ...customRules]; // Find matching rules const matchingRules = allRules.filter(rule => { const fileMatches = rule.filePattern.test(fileName); const contentMatches = !rule.contentPattern || !content || rule.contentPattern.test(content); return fileMatches && contentMatches; }); if (matchingRules.length > 0) { const rule = matchingRules[0]; // Use first matching rule return { suggestions: rule.allowedPaths, reasoning: rule.description, category: rule.category }; } // Generic suggestions based on file type const genericSuggestions = getGenericLocationSuggestions(fileName, fileExt); return { suggestions: genericSuggestions, reasoning: 'Based on file type and naming conventions', category: 'general' }; } /** * Get generic location suggestions based on file characteristics */ function getGenericLocationSuggestions(fileName, fileExt) { const suggestions = []; // Test files if (fileName.includes('test') || fileName.includes('spec')) { suggestions.push('tests/', 'test/', '__tests__/'); } // Config files if (fileName.includes('config') || fileName.includes('settings')) { suggestions.push('config/', 'configs/', 'settings/'); } // Documentation if (['.md', '.txt', '.rst'].includes(fileExt)) { suggestions.push('docs/', 'documentation/'); } // Scripts if (['.sh', '.bat', '.ps1'].includes(fileExt)) { suggestions.push('scripts/', 'bin/', 'tools/'); } // Data files if (['.json', '.yaml', '.yml', '.xml', '.csv'].includes(fileExt)) { suggestions.push('data/', 'fixtures/', 'config/'); } // Default suggestions if (suggestions.length === 0) { suggestions.push('src/', 'lib/', 'utils/'); } return suggestions; } /** * Check if a file should be ignored based on common patterns */ export function shouldIgnoreFile(filePath) { const fileName = basename(filePath); // Temporary files that should definitely be ignored const temporaryPatterns = [ { pattern: /.*\.tmp$/, reason: 'Temporary file' }, { pattern: /.*\.temp$/, reason: 'Temporary file' }, { pattern: /.*\.bak$/, reason: 'Backup file' }, { pattern: /.*\.orig$/, reason: 'Original file backup' }, { pattern: /.*~$/, reason: 'Editor backup file' }, { pattern: /^\.DS_Store$/, reason: 'macOS system file' }, { pattern: /^Thumbs\.db$/, reason: 'Windows system file' }, { pattern: /^\.env\.local$/, reason: 'Local environment file' }, { pattern: /^\.env\.dev$/, reason: 'Development environment file' } ]; for (const { pattern, reason } of temporaryPatterns) { if (pattern.test(fileName)) { return { shouldIgnore: true, reason, severity: 'error' }; } } // Development files that should probably be ignored const developmentPatterns = [ { pattern: /^debug_/, reason: 'Debug script' }, { pattern: /^scratch_/, reason: 'Scratch file' }, { pattern: /^playground_/, reason: 'Playground file' }, { pattern: /^experiment_/, reason: 'Experimental file' }, { pattern: /.*\.debug$/, reason: 'Debug file' }, { pattern: /.*\.log$/, reason: 'Log file' } ]; for (const { pattern, reason } of developmentPatterns) { if (pattern.test(fileName)) { return { shouldIgnore: true, reason, severity: 'warning' }; } } return { shouldIgnore: false, reason: 'File appears to be legitimate', severity: 'info' }; } /** * Create a custom location rule */ export function createLocationRule(name, description, filePattern, allowedPaths, blockedPaths = [], severity = 'warning', category = 'development', contentPattern) { return { name, description, filePattern: new RegExp(filePattern), ...(contentPattern && { contentPattern: new RegExp(contentPattern, 'i') }), allowedPaths, blockedPaths, severity, category }; } /** * Load location rules from project configuration */ export function loadLocationRules(_projectPath) { // This would load from .smartgit.json or similar configuration file // For now, return default rules return DEFAULT_LOCATION_RULES; } /** * Validate multiple files at once */ export function validateMultipleFiles(files, customRules = []) { return files.map(file => ({ filePath: file.path, ...validateFileLocation(file.path, file.content, customRules) })); } /** * Get summary statistics for location validation */ export function getLocationValidationSummary(results) { const summary = { totalFiles: results.length, validFiles: results.filter(r => r.isValid).length, invalidFiles: results.filter(r => !r.isValid).length, errorCount: results.filter(r => r.severity === 'error').length, warningCount: results.filter(r => r.severity === 'warning').length, infoCount: results.filter(r => r.severity === 'info').length, categorySummary: {} }; // Count by category for (const result of results) { if (result.rule) { const category = result.rule.category; summary.categorySummary[category] = (summary.categorySummary[category] || 0) + 1; } } return summary; } //# sourceMappingURL=location-filter.js.map