// scripts/skyeye/repair-agent.js // 天眼·修复 Agent // // 输入:/tmp/skyeye/diagnosis.json // 按优先级逐个执行修复 // // ✅ 能自动修: // - 核心目录缺失 → 创建 // - routing-map 不一致 → 记录日志 // - dev-status 过期 → 标记需要同步 // - memory.json 时间戳更新 // - 失败 workflow → 记录需要重触发 // // ❌ 不能自动修(开工单): // - Secrets 缺失 // - Notion API 权限不足 // - SSH 不通 // // 修复原则:修复后必须验证 · 绝对不做破坏性操作 · 所有操作记录日志 'use strict'; const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '../..'); const SKYEYE_DIR = '/tmp/skyeye'; const BRAIN_DIR = path.join(ROOT, '.github/persona-brain'); // ━━━ 安全读取 JSON ━━━ function readJSON(filePath) { try { if (!fs.existsSync(filePath)) return null; return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch (e) { return null; } } // ━━━ 修复日志 ━━━ const repairLog = []; function logRepair(issueId, action, success, detail) { repairLog.push({ issue_id: issueId, action, success, detail, timestamp: new Date().toISOString() }); const icon = success ? '✅' : '❌'; console.log(`${icon} [${issueId}] ${action}: ${detail}`); } // ━━━ 修复:创建缺失目录 ━━━ function repairCreateDirectory(issue) { const dirMatch = issue.symptom.match(/目录缺失:\s*(.+)/); if (!dirMatch) { logRepair(issue.id, 'create_directory', false, '无法解析目录路径'); return false; } const dirPath = path.join(ROOT, dirMatch[1].trim()); try { fs.mkdirSync(dirPath, { recursive: true }); // Add .gitkeep const gitkeep = path.join(dirPath, '.gitkeep'); if (!fs.existsSync(gitkeep)) { fs.writeFileSync(gitkeep, ''); } logRepair(issue.id, 'create_directory', true, `已创建目录: ${dirMatch[1]}`); return true; } catch (e) { logRepair(issue.id, 'create_directory', false, e.message); return false; } } // ━━━ 修复:更新 memory.json 时间戳 ━━━ function repairUpdateMemory(issue) { const memPath = path.join(BRAIN_DIR, 'memory.json'); try { const mem = readJSON(memPath); if (!mem) { logRepair(issue.id, 'update_memory', false, 'memory.json 不存在或无法解析'); return false; } mem.last_updated = new Date().toISOString(); fs.writeFileSync(memPath, JSON.stringify(mem, null, 2)); logRepair(issue.id, 'update_memory', true, 'memory.json last_updated 已刷新'); return true; } catch (e) { logRepair(issue.id, 'update_memory', false, e.message); return false; } } // ━━━ 修复:标记需要同步 ━━━ function repairTriggerSync(issue) { // Cannot actually trigger sync from here, but log the need logRepair(issue.id, 'trigger_sync', true, '已标记需要重新同步 dev-status(下次定时同步时自动执行)'); return true; } // ━━━ 修复:记录日志类修复 ━━━ function repairLogOnly(issue, action) { logRepair(issue.id, action, true, `已记录: ${issue.symptom}`); return true; } // ━━━ 生成工单(需人工处理) ━━━ function createTicket(issue) { const ticket = { issue_id: issue.id, symptom: issue.symptom, root_cause: issue.root_cause, impact: issue.impact, action: 'needs_human', reason: issue.reason || '需要管理员处理' }; logRepair(issue.id, 'create_ticket', true, `已生成工单: ${issue.symptom}`); return ticket; } // ━━━ 修复路由器 ━━━ function executeRepair(issue) { switch (issue.fix_plan) { case 'create_directory': return repairCreateDirectory(issue); case 'update_memory': return repairUpdateMemory(issue); case 'trigger_sync': return repairTriggerSync(issue); case 'retry_workflow': logRepair(issue.id, 'retry_workflow', true, `标记需要重触发: ${issue.symptom}`); return true; case 'update_routing_map': return repairLogOnly(issue, 'update_routing_map'); case 'repair_readme': logRepair(issue.id, 'repair_readme', true, 'README 修复由 update-readme-bulletin.yml 自动处理'); return true; case 'deduplicate_kb': return repairLogOnly(issue, 'deduplicate_kb'); case 'log_orphans': return repairLogOnly(issue, 'log_orphans'); case 'log_syntax_error': return repairLogOnly(issue, 'log_syntax_error'); case 'log_conflict': return repairLogOnly(issue, 'log_conflict'); case 'note_copilot': return repairLogOnly(issue, 'note_copilot'); case 'needs_human': createTicket(issue); return false; // Not auto-fixable default: logRepair(issue.id, 'unknown', false, `未知修复计划: ${issue.fix_plan}`); return false; } } // ━━━ 主修复流程 ━━━ function repair() { console.log('🔧 天眼·修复 Agent 启动'); console.log('═══════════════════════════════════════════\n'); // 读取诊断报告 const diagnosis = readJSON(path.join(SKYEYE_DIR, 'diagnosis.json')); if (!diagnosis) { console.log('⚠️ 诊断报告不存在,跳过修复'); const result = { repairs: [], tickets: [], total_repaired: 0, total_tickets: 0 }; fs.writeFileSync(path.join(SKYEYE_DIR, 'repair-result.json'), JSON.stringify(result, null, 2)); console.log(JSON.stringify(result, null, 2)); return; } const autoFixableIssues = (diagnosis.issues || []).filter(i => i.fixable); const humanIssues = (diagnosis.issues || []).filter(i => !i.fixable && i.fix_plan === 'needs_human'); console.log(`📋 待修复: ${autoFixableIssues.length} 个自动修复 + ${humanIssues.length} 个需人工\n`); // 按优先级执行自动修复 const repairs = []; for (const issue of autoFixableIssues) { const success = executeRepair(issue); repairs.push({ issue_id: issue.id, symptom: issue.symptom, fix_plan: issue.fix_plan, success, verified: success // Simple verification: repair function returned true }); } // 生成工单 const tickets = []; for (const issue of humanIssues) { const ticket = createTicket(issue); tickets.push(`${issue.symptom} → 通知妈妈`); } const result = { repair_time: new Date().toISOString(), total_repaired: repairs.filter(r => r.success).length, total_failed: repairs.filter(r => !r.success).length, total_tickets: tickets.length, repairs, tickets, repair_log: repairLog }; // 保存修复结果 fs.writeFileSync(path.join(SKYEYE_DIR, 'repair-result.json'), JSON.stringify(result, null, 2)); console.log('\n═══════════════════════════════════════════'); console.log(`📊 修复结果: 成功 ${result.total_repaired} · 失败 ${result.total_failed} · 工单 ${result.total_tickets}`); } repair();