UNPKG

github-mcp-auto-git

Version:

GitHub MCP Auto Git v3.0 - メモリ効率化・統合MCP・モジュール化完了の完全自動Git操作システム

285 lines 10.8 kB
/** * File Watch Manager Module * Handles file monitoring and change processing following Constitutional AI principles */ import { watch } from 'chokidar'; export class FileWatchManager { constructor(config, gitOps) { this.isProcessing = false; this.config = config; this.gitOps = gitOps; } /** * Start file watching with robust error handling * Fail Fast: Immediate validation of watch patterns and permissions * Be Lazy: Efficient file filtering and debounced processing * TypeScript First: Complete type safety for file operations */ async startWatching() { if (!this.config.enabled) { throw new Error('File watching is disabled in configuration'); } // Validate watch patterns if (!this.config.paths || this.config.paths.length === 0) { throw new Error('No watch patterns configured'); } console.log('👀 ファイル監視を開始します...'); console.log('📁 監視対象:', this.config.paths.join(', ')); try { this.watcher = watch(this.config.paths, { ignored: this.getIgnoredPatterns(), ignoreInitial: true, persistent: true, awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 } }); this.setupEventHandlers(); console.log('✅ ファイル監視が開始されました'); console.log(`📋 PID: ${process.pid} (プロセス監視用)`); console.log('💡 Ctrl+C で停止できます'); } catch (error) { throw new Error(`Failed to start file watching: ${error}`); } } /** * Stop file watching and cleanup resources * Fail Fast: Immediate cleanup on errors */ async stopWatching() { if (this.watcher) { await this.watcher.close(); this.watcher = undefined; console.log('⏹️ ファイル監視を停止しました'); } if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = undefined; } } /** * Process file changes with debouncing and error recovery * Be Lazy: Debounced processing to avoid excessive operations * Fail Fast: Early termination on processing conflicts */ async processChanges(files) { if (this.isProcessing) { console.log('⏳ 既に処理中です...'); return { success: false, message: 'Already processing changes' }; } this.isProcessing = true; const startTime = Date.now(); try { console.log('\n🔄 Git操作を開始します...'); const result = await this.gitOps.executeGitWorkflow(files, { autoCommit: true, autoPush: this.config.github.token ? true : false, createPR: this.config.subAgents.prManagementAgent.enabled && this.config.github.token ? true : false }); if (result.success) { console.log('\n' + result.message); if (this.config.notifications.detailed && result.details) { this.displayDetailedResult(result); } if (result.warnings && result.warnings.length > 0 && this.config.notifications.warnings) { console.log('\n⚠️ 警告:'); result.warnings.forEach(warning => console.log(` • ${warning}`)); } return { success: true, message: result.message, details: result.details, warnings: result.warnings }; } else { console.error('\n❌ Git操作が失敗しました:', result.message); if (result.warnings && result.warnings.length > 0) { console.log('\n詳細:'); result.warnings.forEach(warning => console.log(` • ${warning}`)); } return { success: false, message: result.message, warnings: result.warnings }; } } catch (error) { console.error('❌ 予期しないエラーが発生しました:', error); return { success: false, message: `Unexpected error: ${error}` }; } finally { this.isProcessing = false; const processingTime = Date.now() - startTime; console.log(`\n⏱️ 処理時間: ${processingTime}ms`); console.log('👀 ファイル監視を継続中...\n'); } } /** * Check if currently processing changes * TypeScript First: Type-safe status checking */ isCurrentlyProcessing() { return this.isProcessing; } /** * Get current watch status * Be Lazy: Efficient status reporting */ getWatchStatus() { return { watching: !!this.watcher, processing: this.isProcessing, patterns: this.config.paths, ignored: this.getIgnoredPatterns().map(pattern => pattern instanceof RegExp ? pattern.toString() : pattern) }; } /** * Setup event handlers for file changes * Fail Fast: Immediate error handling for file events */ setupEventHandlers() { if (!this.watcher) { throw new Error('Watcher not initialized'); } this.watcher .on('change', (path) => this.handleFileChange(path, 'change')) .on('add', (path) => this.handleFileChange(path, 'add')) .on('unlink', (path) => this.handleFileChange(path, 'delete')) .on('error', (error) => { console.error('❌ ファイル監視エラー:', error); // Attempt to restart watching after error this.handleWatchError(error); }) .on('ready', () => { console.log('🔄 ファイル監視システム準備完了'); }); } /** * Handle individual file change events * Be Lazy: Debounced processing to batch changes */ async handleFileChange(filePath, type) { if (this.isProcessing) { return; } // Filter out ignored patterns at runtime if (this.shouldIgnoreFile(filePath)) { return; } console.log(`📝 ファイル${type === 'change' ? '変更' : type === 'add' ? '追加' : '削除'}: ${filePath}`); // Clear existing debounce timer if (this.debounceTimer) { clearTimeout(this.debounceTimer); } // Debounce file changes to avoid excessive processing this.debounceTimer = setTimeout(async () => { await this.processChanges([filePath]); }, 2000); } /** * Handle watch errors with recovery attempts * Fail Fast: Immediate error detection and recovery */ async handleWatchError(error) { console.warn('⚠️ ファイル監視でエラーが発生しました。復旧を試行します...', error.message); try { // Attempt to restart watching await this.stopWatching(); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second await this.startWatching(); console.log('✅ ファイル監視を復旧しました'); } catch (restartError) { console.error('❌ ファイル監視の復旧に失敗しました:', restartError); throw new Error(`File watching recovery failed: ${restartError}`); } } /** * Get patterns to ignore during file watching * Be Lazy: Comprehensive ignore patterns for efficiency */ getIgnoredPatterns() { return [ /node_modules/, /\.git/, /dist/, /build/, /coverage/, '**/*.pid', '.github-auto-git.pid', '**/*.log', '**/*.tmp', '**/.*', '!.env', '!.gitignore' ]; } /** * Check if a specific file should be ignored * Fail Fast: Early filtering of irrelevant files */ shouldIgnoreFile(filePath) { const ignoredPatterns = this.getIgnoredPatterns(); return ignoredPatterns.some(pattern => { if (pattern instanceof RegExp) { return pattern.test(filePath); } // Handle glob patterns if (typeof pattern === 'string') { if (pattern.startsWith('!')) { return false; // Negation patterns are handled by chokidar } // Simple pattern matching if (pattern.includes('*')) { const regexPattern = pattern .replace(/\*\*/g, '.*') .replace(/\*/g, '[^/]*'); return new RegExp(regexPattern).test(filePath); } return filePath.includes(pattern); } return false; }); } /** * Display detailed processing results * TypeScript First: Type-safe result display */ displayDetailedResult(result) { console.log('\n📊 詳細結果:'); if (result.details.safety) { const safety = result.details.safety; console.log(` 🔒 安全性: ${safety.level} (${safety.safetyScore}/100)`); if (safety.risks.length > 0) { console.log(' ⚠️ リスク:'); safety.risks.forEach((risk) => { console.log(` • ${risk.description}`); }); } } if (result.details.commitMessage) { const msg = result.details.commitMessage; console.log(` 📝 コミットタイトル: ${msg.title}`); console.log(` 📋 Conventional: ${msg.conventional}`); } if (result.details.prManagement) { const pr = result.details.prManagement; console.log(` 🔀 マージ戦略: ${pr.mergeStrategy}`); console.log(` 🏷️ ラベル: ${pr.labels.join(', ')}`); if (pr.reviewers.length > 0) { console.log(` 👥 レビュアー: ${pr.reviewers.join(', ')}`); } } } } //# sourceMappingURL=file-watch-manager.js.map