github-mcp-auto-git
Version:
GitHub MCP Auto Git v3.0 - メモリ効率化・統合MCP・モジュール化完了の完全自動Git操作システム
419 lines • 17.6 kB
JavaScript
/**
* Error Recovery System - 堅牢なエラーハンドリング・リカバリー機能
* Constitutional AI原則の「Fail Fast」に基づく高度なエラー処理
*/
import { promises as fs } from 'fs';
import { join } from 'path';
// エラー分類とレベル定義
export var ErrorLevel;
(function (ErrorLevel) {
ErrorLevel["LOW"] = "low";
ErrorLevel["MEDIUM"] = "medium";
ErrorLevel["HIGH"] = "high";
ErrorLevel["CRITICAL"] = "critical";
})(ErrorLevel || (ErrorLevel = {}));
export var ErrorCategory;
(function (ErrorCategory) {
ErrorCategory["NETWORK"] = "network";
ErrorCategory["FILE_SYSTEM"] = "file_system";
ErrorCategory["GIT_OPERATION"] = "git_operation";
ErrorCategory["GITHUB_API"] = "github_api";
ErrorCategory["SUBAGENT"] = "subagent";
ErrorCategory["CONFIGURATION"] = "configuration";
ErrorCategory["PERMISSION"] = "permission";
ErrorCategory["VALIDATION"] = "validation";
})(ErrorCategory || (ErrorCategory = {}));
export class ErrorRecoverySystem {
constructor() {
this.errorLog = [];
this.retryCount = new Map();
this.maxRetries = 3;
this.baseDelayMs = 1000;
}
/**
* メインエラーハンドリング関数 - Fail Fast原則
*/
async handleError(error, context, fallbackFn) {
const errorReport = this.createErrorReport(error, context);
this.errorLog.push(errorReport);
console.error(`🚨 [${errorReport.level.toUpperCase()}] ${errorReport.category}: ${errorReport.message}`);
console.error(`📍 Context: ${errorReport.context.operation} (attempt ${errorReport.context.attempt})`);
// Critical エラーは即座に停止(Fail Fast)
if (errorReport.level === ErrorLevel.CRITICAL) {
await this.logErrorToFile(errorReport);
throw new Error(`CRITICAL ERROR: ${errorReport.message}. Operation aborted.`);
}
// リカバリーアクションの実行
return await this.executeRecoveryAction(errorReport, fallbackFn);
}
/**
* エラー分類とリカバリー戦略の決定
*/
createErrorReport(error, context) {
const category = this.categorizeError(error);
const level = this.determineErrorLevel(error, category, context);
const recoveryAction = this.determineRecoveryAction(category, level, context);
return {
id: this.generateErrorId(),
level,
category,
message: error.message,
originalError: error,
context,
recoveryAction,
resolved: false
};
}
/**
* エラー分類
*/
categorizeError(error) {
const message = error.message.toLowerCase();
const stack = error.stack?.toLowerCase() || '';
// ネットワークエラー
if (message.includes('network') || message.includes('timeout') ||
message.includes('enotfound') || message.includes('econnrefused')) {
return ErrorCategory.NETWORK;
}
// GitHub API エラー
if (message.includes('github') || message.includes('octokit') ||
message.includes('api rate limit') || message.includes('unauthorized')) {
return ErrorCategory.GITHUB_API;
}
// Git操作エラー
if (message.includes('git') || message.includes('repository') ||
message.includes('commit') || message.includes('push')) {
return ErrorCategory.GIT_OPERATION;
}
// ファイルシステムエラー
if (message.includes('enoent') || message.includes('eacces') ||
message.includes('file') || message.includes('directory')) {
return ErrorCategory.FILE_SYSTEM;
}
// 権限エラー
if (message.includes('permission') || message.includes('unauthorized') ||
message.includes('forbidden') || message.includes('eacces')) {
return ErrorCategory.PERMISSION;
}
// サブエージェントエラー
if (message.includes('subagent') || message.includes('agent') ||
stack.includes('subagent-manager')) {
return ErrorCategory.SUBAGENT;
}
// 設定エラー
if (message.includes('config') || message.includes('invalid') ||
message.includes('missing')) {
return ErrorCategory.CONFIGURATION;
}
return ErrorCategory.VALIDATION;
}
/**
* エラーレベルの決定
*/
determineErrorLevel(error, category, context) {
const message = error.message.toLowerCase();
// Critical レベル - 即座に停止すべきエラー
if (message.includes('secret') || message.includes('credential') ||
message.includes('destructive') || message.includes('security')) {
return ErrorLevel.CRITICAL;
}
if (category === ErrorCategory.PERMISSION && context.operation.includes('commit')) {
return ErrorLevel.CRITICAL;
}
// High レベル - 重要だが回復可能
if (category === ErrorCategory.GITHUB_API && message.includes('rate limit')) {
return ErrorLevel.HIGH;
}
if (category === ErrorCategory.GIT_OPERATION && context.attempt > 2) {
return ErrorLevel.HIGH;
}
// Medium レベル - 一般的なエラー
if (category === ErrorCategory.NETWORK || category === ErrorCategory.FILE_SYSTEM) {
return ErrorLevel.MEDIUM;
}
// Low レベル - 軽微なエラー
return ErrorLevel.LOW;
}
/**
* リカバリーアクション決定
*/
determineRecoveryAction(category, level, context) {
switch (category) {
case ErrorCategory.NETWORK:
return {
type: 'retry',
description: 'ネットワーク接続を再試行',
maxAttempts: 3,
delayMs: this.calculateBackoffDelay(context.attempt)
};
case ErrorCategory.GITHUB_API:
if (level === ErrorLevel.HIGH) {
return {
type: 'retry',
description: 'GitHub API レート制限による待機後再試行',
maxAttempts: 2,
delayMs: 60000 // 1分待機
};
}
return {
type: 'fallback',
description: 'GitHub操作をスキップしてローカル処理を継続',
fallbackFunction: () => this.createLocalOnlyFallback(context)
};
case ErrorCategory.GIT_OPERATION:
if (level === ErrorLevel.HIGH) {
return {
type: 'manual',
description: 'Git操作が複数回失敗。手動確認が必要です。'
};
}
return {
type: 'retry',
description: 'Git操作を再試行',
maxAttempts: 2,
delayMs: 2000
};
case ErrorCategory.FILE_SYSTEM:
return {
type: 'retry',
description: 'ファイルアクセスを再試行',
maxAttempts: 2,
delayMs: 1000
};
case ErrorCategory.SUBAGENT:
return {
type: 'fallback',
description: 'サブエージェント処理をフォールバック実装で継続',
fallbackFunction: () => this.createSubagentFallback(context)
};
case ErrorCategory.PERMISSION:
return {
type: 'abort',
description: '権限エラー: 手動で権限を確認してください'
};
case ErrorCategory.CONFIGURATION:
return {
type: 'abort',
description: '設定エラー: 設定ファイルを確認してください'
};
default:
return {
type: 'skip',
description: '不明なエラー: 処理をスキップして継続'
};
}
}
/**
* リカバリーアクション実行
*/
async executeRecoveryAction(errorReport, fallbackFn) {
const startTime = Date.now();
const { recoveryAction, context } = errorReport;
try {
switch (recoveryAction.type) {
case 'retry':
return await this.executeRetry(errorReport, fallbackFn);
case 'fallback':
console.log(`🔄 フォールバック処理を実行: ${recoveryAction.description}`);
const result = recoveryAction.fallbackFunction
? await recoveryAction.fallbackFunction()
: (fallbackFn ? await fallbackFn() : null);
this.markResolved(errorReport, 'partial', Date.now() - startTime);
return result;
case 'skip':
console.log(`⏭️ 処理をスキップ: ${recoveryAction.description}`);
this.markResolved(errorReport, 'partial', Date.now() - startTime);
return null;
case 'abort':
console.error(`🛑 処理を中止: ${recoveryAction.description}`);
this.markResolved(errorReport, 'failure', Date.now() - startTime);
throw new Error(`Operation aborted: ${recoveryAction.description}`);
case 'manual':
console.warn(`👤 手動確認が必要: ${recoveryAction.description}`);
this.markResolved(errorReport, 'failure', Date.now() - startTime);
throw new Error(`Manual intervention required: ${recoveryAction.description}`);
default:
throw new Error(`Unknown recovery action: ${recoveryAction.type}`);
}
}
catch (error) {
this.markResolved(errorReport, 'failure', Date.now() - startTime);
throw error;
}
}
/**
* リトライ実行
*/
async executeRetry(errorReport, fallbackFn) {
const { recoveryAction, context } = errorReport;
const maxAttempts = recoveryAction.maxAttempts || this.maxRetries;
const retryKey = `${context.operation}-${context.workingDir}`;
const currentAttempts = this.retryCount.get(retryKey) || 0;
if (currentAttempts >= maxAttempts) {
console.error(`❌ 最大再試行回数に到達: ${maxAttempts} 回`);
throw new Error(`Max retry attempts reached: ${maxAttempts}`);
}
const delayMs = recoveryAction.delayMs || this.calculateBackoffDelay(currentAttempts);
console.log(`🔄 再試行 ${currentAttempts + 1}/${maxAttempts} (${delayMs}ms後): ${recoveryAction.description}`);
await this.delay(delayMs);
this.retryCount.set(retryKey, currentAttempts + 1);
// 再試行はfallbackFnを実行
if (!fallbackFn) {
throw new Error('No retry function provided');
}
const startTime = Date.now();
try {
const result = await fallbackFn();
this.markResolved(errorReport, 'success', Date.now() - startTime);
this.retryCount.delete(retryKey); // 成功時はカウントリセット
return result;
}
catch (retryError) {
const error = retryError;
console.warn(`⚠️ 再試行失敗: ${error.message}`);
throw error;
}
}
/**
* フォールバック実装
*/
async createLocalOnlyFallback(context) {
console.log('📋 ローカルのみでの処理を実行');
return {
success: true,
message: 'GitHub操作はスキップされましたが、ローカル処理は完了しました',
details: {
operation: context.operation,
timestamp: new Date().toISOString(),
warning: 'GitHub連携が無効です'
}
};
}
async createSubagentFallback(context) {
console.log('🤖 サブエージェントフォールバック処理を実行');
return {
safetyScore: 70,
level: 'WARNING',
risks: [],
recommendations: ['エラー発生のため簡易分析を実行しました'],
autoApprove: false,
confidence: 0.3
};
}
/**
* ユーティリティ関数
*/
calculateBackoffDelay(attempt) {
return Math.min(this.baseDelayMs * Math.pow(2, attempt), 30000); // 最大30秒
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
generateErrorId() {
return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
markResolved(errorReport, outcome, resolutionTime) {
errorReport.resolved = true;
errorReport.finalOutcome = outcome;
errorReport.resolutionTime = resolutionTime;
console.log(`✅ エラー解決: ${errorReport.id} (${outcome}, ${resolutionTime}ms)`);
}
async logErrorToFile(errorReport) {
try {
const logDir = join(process.cwd(), 'logs');
await fs.mkdir(logDir, { recursive: true });
const logFile = join(logDir, `error-${new Date().toISOString().split('T')[0]}.json`);
const logEntry = {
...errorReport,
originalError: {
message: errorReport.originalError.message,
stack: errorReport.originalError.stack
}
};
await fs.appendFile(logFile, JSON.stringify(logEntry, null, 2) + '\n');
}
catch (logError) {
console.error('Failed to log error to file:', logError);
}
}
/**
* エラー統計とヘルスチェック
*/
getErrorStatistics() {
const stats = {
total: this.errorLog.length,
byLevel: {},
byCategory: {},
resolvedCount: 0,
avgResolutionTime: 0
};
// 初期化
Object.values(ErrorLevel).forEach(level => stats.byLevel[level] = 0);
Object.values(ErrorCategory).forEach(category => stats.byCategory[category] = 0);
let totalResolutionTime = 0;
this.errorLog.forEach(error => {
stats.byLevel[error.level]++;
stats.byCategory[error.category]++;
if (error.resolved) {
stats.resolvedCount++;
if (error.resolutionTime) {
totalResolutionTime += error.resolutionTime;
}
}
});
stats.avgResolutionTime = stats.resolvedCount > 0
? totalResolutionTime / stats.resolvedCount
: 0;
return stats;
}
/**
* システムヘルスチェック
*/
checkSystemHealth() {
const stats = this.getErrorStatistics();
const recentErrors = this.errorLog.filter(error => Date.now() - error.context.timestamp.getTime() < 3600000 // 1時間以内
);
const criticalCount = stats.byLevel[ErrorLevel.CRITICAL];
const recentCriticalCount = recentErrors.filter(e => e.level === ErrorLevel.CRITICAL).length;
const unresolved = stats.total - stats.resolvedCount;
if (criticalCount > 0 || recentCriticalCount > 0) {
return {
status: 'critical',
message: `${criticalCount}個のクリティカルエラーが検出されています`,
recommendations: [
'システム管理者に即座に連絡してください',
'すべてのGit操作を一時停止してください',
'セキュリティログを確認してください'
]
};
}
if (unresolved > 5 || recentErrors.length > 10) {
return {
status: 'warning',
message: `未解決エラー${unresolved}個、直近1時間で${recentErrors.length}個のエラーが発生`,
recommendations: [
'ネットワーク接続を確認してください',
'GitHub API制限を確認してください',
'システムリソースを確認してください'
]
};
}
return {
status: 'healthy',
message: 'システムは正常に動作しています',
recommendations: []
};
}
/**
* エラーログのクリーンアップ
*/
clearOldErrors(olderThanHours = 24) {
const cutoff = Date.now() - (olderThanHours * 3600000);
const initialCount = this.errorLog.length;
this.errorLog = this.errorLog.filter(error => error.context.timestamp.getTime() > cutoff);
const removed = initialCount - this.errorLog.length;
console.log(`🧹 ${removed}個の古いエラーログをクリーンアップしました`);
return removed;
}
}
//# sourceMappingURL=error-recovery.js.map