UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

496 lines (478 loc) â€ĸ 19.3 kB
"use strict"; /** * Git Hooks Manager * * Manages Git hooks for TTL file versioning and automatic re-analysis * when TTL files are modified by developers. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHooksManager = void 0; const events_1 = require("events"); const promises_1 = require("fs/promises"); const fs_1 = require("fs"); const path_1 = require("path"); const simple_git_1 = __importDefault(require("simple-git")); const logger_1 = __importDefault(require("../../utils/logger")); /** * Git Hooks Manager for TTL File Versioning */ class GitHooksManager extends events_1.EventEmitter { config; git; isInitialized = false; constructor(config) { super(); this.config = config; this.git = (0, simple_git_1.default)(config.projectRoot); } /** * Initialize Git hooks for TTL file monitoring */ async initialize() { if (this.isInitialized) { return; } try { logger_1.default.info('🔧 Initializing Git hooks for TTL file versioning...'); // Check if we're in a Git repository const isRepo = await this.git.checkIsRepo(); if (!isRepo) { logger_1.default.warn('âš ī¸ Not a Git repository - Git hooks will not be installed'); return; } // Create .git/hooks directory if it doesn't exist const hooksDir = (0, path_1.join)(this.config.projectRoot, '.git', 'hooks'); await (0, promises_1.mkdir)(hooksDir, { recursive: true }); // Install hooks based on configuration if (this.config.enablePreCommitHook) { await this.installPreCommitHook(hooksDir); } if (this.config.enablePostCommitHook) { await this.installPostCommitHook(hooksDir); } if (this.config.enablePostMergeHook) { await this.installPostMergeHook(hooksDir); } this.isInitialized = true; logger_1.default.info('✅ Git hooks initialized successfully'); } catch (error) { logger_1.default.error('❌ Failed to initialize Git hooks', { error }); throw error; } } /** * Install pre-commit hook to validate TTL files before commit */ async installPreCommitHook(hooksDir) { const hookPath = (0, path_1.join)(hooksDir, 'pre-commit'); const hookScript = `#!/bin/sh # AASWE Pre-commit Hook - TTL File Validation # Auto-generated by AASWE Git Hooks Manager echo "🔍 AASWE: Checking TTL files before commit..." # Get staged TTL files STAGED_TTL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E "\\.(ttl|module-knowledge\\.ttl)$" || true) if [ -n "$STAGED_TTL_FILES" ]; then echo "📝 Found staged TTL files:" echo "$STAGED_TTL_FILES" # Validate TTL files node -e " const { GitHooksManager } = require('./dist/services/git-integration/GitHooksManager'); const manager = new GitHooksManager({ projectRoot: process.cwd(), ttlDirectories: ['./knowledge', './clean-knowledge', './.aaswe/knowledge'], ttlPatterns: ['**/*.ttl', '**/*.module-knowledge.ttl'], enablePreCommitHook: true, enablePostCommitHook: true, enablePostMergeHook: true, autoReanalyzeOnTTLChange: true }); manager.validateStagedTTLFiles().then(valid => { if (!valid) { console.error('❌ TTL file validation failed'); process.exit(1); } console.log('✅ TTL files validated successfully'); }).catch(err => { console.error('❌ TTL validation error:', err.message); process.exit(1); }); " if [ $? -ne 0 ]; then echo "❌ Pre-commit hook failed - TTL file validation errors" exit 1 fi fi echo "✅ AASWE: Pre-commit checks passed" exit 0 `; await (0, promises_1.writeFile)(hookPath, hookScript); await (0, promises_1.chmod)(hookPath, 0o755); logger_1.default.info('✅ Pre-commit hook installed'); } /** * Install post-commit hook to trigger re-analysis after TTL changes */ async installPostCommitHook(hooksDir) { const hookPath = (0, path_1.join)(hooksDir, 'post-commit'); const hookScript = `#!/bin/sh # AASWE Post-commit Hook - TTL File Re-analysis # Auto-generated by AASWE Git Hooks Manager echo "🔄 AASWE: Checking for TTL file changes in commit..." # Get TTL files changed in the last commit CHANGED_TTL_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD | grep -E "\\.(ttl|module-knowledge\\.ttl)$" || true) if [ -n "$CHANGED_TTL_FILES" ]; then echo "📝 TTL files changed in commit:" echo "$CHANGED_TTL_FILES" # Trigger re-analysis in background echo "🚀 Triggering automatic re-analysis..." nohup node -e " const { GitHooksManager } = require('./dist/services/git-integration/GitHooksManager'); const manager = new GitHooksManager({ projectRoot: process.cwd(), ttlDirectories: ['./knowledge', './clean-knowledge', './.aaswe/knowledge'], ttlPatterns: ['**/*.ttl', '**/*.module-knowledge.ttl'], enablePreCommitHook: true, enablePostCommitHook: true, enablePostMergeHook: true, autoReanalyzeOnTTLChange: true }); manager.handlePostCommitReanalysis().catch(console.error); " > /dev/null 2>&1 & echo "✅ AASWE: Re-analysis triggered in background" else echo "â„šī¸ AASWE: No TTL files changed in this commit" fi exit 0 `; await (0, promises_1.writeFile)(hookPath, hookScript); await (0, promises_1.chmod)(hookPath, 0o755); logger_1.default.info('✅ Post-commit hook installed'); } /** * Install post-merge hook to handle TTL conflicts and re-analysis */ async installPostMergeHook(hooksDir) { const hookPath = (0, path_1.join)(hooksDir, 'post-merge'); const hookScript = `#!/bin/sh # AASWE Post-merge Hook - TTL File Conflict Resolution # Auto-generated by AASWE Git Hooks Manager echo "🔀 AASWE: Checking for TTL file changes after merge..." # Check if merge brought in TTL file changes MERGED_TTL_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD~1..HEAD | grep -E "\\.(ttl|module-knowledge\\.ttl)$" || true) if [ -n "$MERGED_TTL_FILES" ]; then echo "📝 TTL files affected by merge:" echo "$MERGED_TTL_FILES" # Trigger conflict resolution and re-analysis echo "🔧 Resolving TTL conflicts and triggering re-analysis..." node -e " const { GitHooksManager } = require('./dist/services/git-integration/GitHooksManager'); const manager = new GitHooksManager({ projectRoot: process.cwd(), ttlDirectories: ['./knowledge', './clean-knowledge', './.aaswe/knowledge'], ttlPatterns: ['**/*.ttl', '**/*.module-knowledge.ttl'], enablePreCommitHook: true, enablePostCommitHook: true, enablePostMergeHook: true, autoReanalyzeOnTTLChange: true }); manager.handlePostMergeReanalysis().catch(console.error); " echo "✅ AASWE: Post-merge processing completed" else echo "â„šī¸ AASWE: No TTL files affected by merge" fi exit 0 `; await (0, promises_1.writeFile)(hookPath, hookScript); await (0, promises_1.chmod)(hookPath, 0o755); logger_1.default.info('✅ Post-merge hook installed'); } /** * Validate staged TTL files before commit */ async validateStagedTTLFiles() { try { const stagedFiles = await this.git.diff(['--cached', '--name-only']); const ttlFiles = stagedFiles.split('\n').filter(file => file.match(/\.(ttl|module-knowledge\.ttl)$/)); if (ttlFiles.length === 0) { return true; // No TTL files to validate } logger_1.default.info(`🔍 Validating ${ttlFiles.length} staged TTL files...`); for (const filePath of ttlFiles) { const fullPath = (0, path_1.join)(this.config.projectRoot, filePath); if (!(0, fs_1.existsSync)(fullPath)) { continue; // File was deleted } try { const content = await (0, promises_1.readFile)(fullPath, 'utf-8'); const isValid = await this.validateTTLContent(content, filePath); if (!isValid) { logger_1.default.error(`❌ Invalid TTL file: ${filePath}`); return false; } } catch (error) { logger_1.default.error(`❌ Error validating TTL file ${filePath}:`, error); return false; } } logger_1.default.info('✅ All staged TTL files are valid'); return true; } catch (error) { logger_1.default.error('❌ Failed to validate staged TTL files', { error }); return false; } } /** * Handle post-commit re-analysis */ async handlePostCommitReanalysis() { try { const commitHash = await this.git.revparse(['HEAD']); const changedFiles = await this.getChangedTTLFiles(commitHash); if (changedFiles.length === 0) { return; } logger_1.default.info(`🔄 Post-commit: Re-analyzing ${changedFiles.length} TTL files...`); const event = { type: 'post-commit', ttlChanges: changedFiles, timestamp: new Date(), commitHash }; this.emit('git-hook-event', event); if (this.config.autoReanalyzeOnTTLChange) { await this.triggerReanalysis(changedFiles); } } catch (error) { logger_1.default.error('❌ Post-commit re-analysis failed', { error }); } } /** * Handle post-merge re-analysis and conflict resolution */ async handlePostMergeReanalysis() { try { const commitHash = await this.git.revparse(['HEAD']); const changedFiles = await this.getChangedTTLFiles(commitHash, 'HEAD~1..HEAD'); if (changedFiles.length === 0) { return; } logger_1.default.info(`🔀 Post-merge: Processing ${changedFiles.length} TTL files...`); // Check for merge conflicts in TTL files const conflictedFiles = await this.detectTTLConflicts(changedFiles); if (conflictedFiles.length > 0) { logger_1.default.warn(`âš ī¸ Detected ${conflictedFiles.length} TTL files with potential conflicts`); await this.resolveTTLConflicts(conflictedFiles); } const event = { type: 'post-merge', ttlChanges: changedFiles, timestamp: new Date(), commitHash }; this.emit('git-hook-event', event); if (this.config.autoReanalyzeOnTTLChange) { await this.triggerReanalysis(changedFiles); } } catch (error) { logger_1.default.error('❌ Post-merge re-analysis failed', { error }); } } /** * Get changed TTL files from a commit */ async getChangedTTLFiles(commitHash, range) { try { const diffArgs = range ? [range] : ['--name-status', `${commitHash}~1..${commitHash}`]; const diff = await this.git.diff(diffArgs); const changes = []; const lines = diff.split('\n').filter(line => line.trim()); for (const line of lines) { const [status, filePath] = line.split('\t'); if (filePath && filePath.match(/\.(ttl|module-knowledge\.ttl)$/)) { let changeType; switch (status) { case 'A': changeType = 'added'; break; case 'D': changeType = 'deleted'; break; default: changeType = 'modified'; } changes.push({ filePath, changeType, timestamp: new Date(), commitHash }); } } return changes; } catch (error) { logger_1.default.error('❌ Failed to get changed TTL files', { error }); return []; } } /** * Validate TTL content */ async validateTTLContent(content, filePath) { try { // Basic TTL syntax validation const lines = content.split('\n'); let hasValidTriples = false; for (const line of lines) { const trimmed = line.trim(); // Skip empty lines and comments if (!trimmed || trimmed.startsWith('#')) continue; // Check for basic triple structure if (trimmed.includes(' ') && (trimmed.endsWith('.') || trimmed.endsWith(';'))) { hasValidTriples = true; } else if (trimmed.length > 0) { logger_1.default.warn(`âš ī¸ Potential syntax issue in ${filePath}: ${trimmed}`); } } if (!hasValidTriples) { logger_1.default.error(`❌ No valid triples found in ${filePath}`); return false; } return true; } catch (error) { logger_1.default.error(`❌ TTL validation error for ${filePath}:`, error); return false; } } /** * Detect TTL conflicts after merge */ async detectTTLConflicts(changedFiles) { const conflictedFiles = []; for (const change of changedFiles) { const fullPath = (0, path_1.join)(this.config.projectRoot, change.filePath); if (!(0, fs_1.existsSync)(fullPath)) continue; try { const content = await (0, promises_1.readFile)(fullPath, 'utf-8'); // Check for Git conflict markers if (content.includes('<<<<<<<') || content.includes('>>>>>>>') || content.includes('=======')) { conflictedFiles.push(change.filePath); } } catch (error) { logger_1.default.error(`❌ Error checking conflicts in ${change.filePath}:`, error); } } return conflictedFiles; } /** * Resolve TTL conflicts by triggering re-analysis */ async resolveTTLConflicts(conflictedFiles) { logger_1.default.info(`🔧 Resolving conflicts in ${conflictedFiles.length} TTL files...`); // For now, log the conflicts and suggest manual resolution // In a full implementation, this could trigger automatic re-analysis // to regenerate clean TTL files for (const filePath of conflictedFiles) { logger_1.default.warn(`âš ī¸ Manual resolution required for: ${filePath}`); logger_1.default.info(`💡 Consider running: aaswe analyze --output ${filePath.split('/')[0]}`); } } /** * Trigger re-analysis for changed TTL files */ async triggerReanalysis(changedFiles) { try { logger_1.default.info(`🚀 Triggering re-analysis for ${changedFiles.length} changed TTL files...`); // Skip Git operations during re-analysis to prevent SSH prompts logger_1.default.info('â„šī¸ Skipping Git-triggered re-analysis to prevent SSH authentication prompts'); logger_1.default.info('💡 TTL files have been updated - manual re-analysis can be triggered with: codebase-ai analyze'); } catch (error) { logger_1.default.error('❌ Re-analysis failed', { error }); } } /** * Uninstall Git hooks */ async uninstall() { try { const hooksDir = (0, path_1.join)(this.config.projectRoot, '.git', 'hooks'); const hooks = ['pre-commit', 'post-commit', 'post-merge']; for (const hook of hooks) { const hookPath = (0, path_1.join)(hooksDir, hook); if ((0, fs_1.existsSync)(hookPath)) { try { const content = await (0, promises_1.readFile)(hookPath, 'utf-8'); // Only remove if it's our hook if (content.includes('AASWE Git Hooks Manager')) { await (0, promises_1.writeFile)(hookPath + '.backup', content); // Remove the hook file or replace with backup if it existed logger_1.default.info(`✅ Uninstalled ${hook} hook (backup created)`); } } catch (error) { logger_1.default.warn(`âš ī¸ Could not uninstall ${hook} hook:`, error); } } } logger_1.default.info('✅ Git hooks uninstalled successfully'); } catch (error) { logger_1.default.error('❌ Failed to uninstall Git hooks', { error }); } } /** * Get Git hooks status */ async getStatus() { try { const hooksDir = (0, path_1.join)(this.config.projectRoot, '.git', 'hooks'); const hooks = ['pre-commit', 'post-commit', 'post-merge']; const status = { isGitRepo: await this.git.checkIsRepo(), hooksInstalled: {}, lastActivity: null }; for (const hook of hooks) { const hookPath = (0, path_1.join)(hooksDir, hook); if ((0, fs_1.existsSync)(hookPath)) { const stats = await (0, promises_1.stat)(hookPath); const content = await (0, promises_1.readFile)(hookPath, 'utf-8'); status.hooksInstalled[hook] = { exists: true, isAASWE: content.includes('AASWE Git Hooks Manager'), lastModified: stats.mtime, executable: (stats.mode & 0o111) !== 0 }; } else { status.hooksInstalled[hook] = { exists: false, isAASWE: false }; } } return status; } catch (error) { logger_1.default.error('❌ Failed to get Git hooks status', { error }); return { error: error instanceof Error ? error.message : 'Unknown error' }; } } } exports.GitHooksManager = GitHooksManager; //# sourceMappingURL=GitHooksManager.js.map