guanghulab/scripts/skyeye/repair-agent.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

233 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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();