zhizhi/scripts/skyeye/report-generator.js

197 lines
6.7 KiB
JavaScript
Raw Normal View History

// scripts/skyeye/report-generator.js
// 天眼·全局健康报告生成器
//
// 汇总所有扫描结果 + 诊断 + 修复结果 → 生成统一报告
// 报告写入:/tmp/skyeye/full-report.json + memory.json skyeye 段
'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');
const MEMORY_PATH = path.join(BRAIN_DIR, 'memory.json');
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const startTime = Date.now();
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
// ━━━ 生成报告 ━━━
function generateReport() {
const now = new Date();
const dateStr = now.toISOString().slice(0, 10);
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
.replace('T', ' ').slice(0, 19) + '+08:00';
// 读取所有扫描结果
const wfHealth = readJSON(path.join(SKYEYE_DIR, 'workflow-health.json'));
const structHealth = readJSON(path.join(SKYEYE_DIR, 'structure-health.json'));
const brainHealth = readJSON(path.join(SKYEYE_DIR, 'brain-health.json'));
const bridgeHealth = readJSON(path.join(SKYEYE_DIR, 'bridge-health.json'));
const diagnosis = readJSON(path.join(SKYEYE_DIR, 'diagnosis.json'));
const repairResult = readJSON(path.join(SKYEYE_DIR, 'repair-result.json'));
// 计算时长
const durationSeconds = Math.round((Date.now() - startTime) / 1000);
// 确定整体健康度
let overallHealth = '🟢';
if (diagnosis && diagnosis.needs_human > 0) {
overallHealth = '🔴';
} else if (diagnosis && diagnosis.total_issues > 0) {
overallHealth = '🟡';
}
// 构建报告
const report = {
report_id: `SKYEYE-${dateStr.replace(/-/g, '')}`,
timestamp: bjTime,
duration_seconds: durationSeconds,
overall_health: overallHealth,
brain_status: {
integrity: brainHealth ? brainHealth.integrity : 'unknown',
memory_fresh: brainHealth ? brainHealth.memory_fresh : false,
routing_map_aligned: brainHealth ? brainHealth.routing_map_aligned : false,
dev_status_last_sync: brainHealth && brainHealth.dev_status
? brainHealth.dev_status.last_sync : null,
knowledge_base_entries: brainHealth && brainHealth.knowledge_base
? brainHealth.knowledge_base.entries : 0
},
workflow_health: {
total: wfHealth ? wfHealth.total_workflows : 0,
healthy: wfHealth ? wfHealth.healthy : 0,
failed: wfHealth ? (wfHealth.total_workflows - wfHealth.healthy) : 0,
never_triggered: 0, // Determined from recent runs
conflicts: wfHealth ? (wfHealth.cron_conflicts || []).length : 0,
details: wfHealth ? (wfHealth.workflow_map || []).map(w => ({
name: w.name,
file: w.file,
status: w.status,
last_run: w.last_run || 'unknown'
})) : []
},
structure_health: {
core_dirs_ok: structHealth ? structHealth.core_dirs_ok : false,
orphan_files: structHealth ? structHealth.orphan_files : 0,
missing_dirs: structHealth ? structHealth.missing_dirs : [],
readme_ok: structHealth ? structHealth.readme_ok : false
},
bridge_health: {
notion_api: bridgeHealth ? bridgeHealth.notion_api.status : '❓',
server_ssh: bridgeHealth ? bridgeHealth.server_ssh.status : '❓',
github_api: bridgeHealth ? bridgeHealth.github_api.status : '❓',
secrets_complete: bridgeHealth && bridgeHealth.secrets
? bridgeHealth.secrets.complete : false
},
diagnosis: {
total_issues: diagnosis ? diagnosis.total_issues : 0,
auto_fixed: repairResult ? repairResult.total_repaired : 0,
needs_human: diagnosis ? diagnosis.needs_human : 0,
watching: diagnosis ? diagnosis.watch_list : 0,
issues: diagnosis ? (diagnosis.issues || []).map(i => ({
id: i.id,
symptom: i.symptom,
root_cause: i.root_cause,
impact: i.impact,
fix_applied: i.fixable ? i.fix_plan : null,
verified: repairResult
? (repairResult.repairs || []).find(r => r.issue_id === i.id)?.verified || false
: false
})) : []
},
repairs_applied: repairResult
? (repairResult.repairs || []).filter(r => r.success).map(r => r.symptom)
: [],
tickets_created: repairResult
? (repairResult.tickets || [])
: [],
next_actions: []
};
// 生成下一步建议
if (diagnosis && diagnosis.watch_list > 0) {
report.next_actions.push('持续观察 watch_list 中的问题');
}
if (report.brain_status.integrity !== 'ok') {
report.next_actions.push('核心大脑需要修复');
}
if (!report.brain_status.memory_fresh) {
report.next_actions.push('memory.json 数据需要刷新');
}
// 保存完整报告
fs.mkdirSync(SKYEYE_DIR, { recursive: true });
fs.writeFileSync(path.join(SKYEYE_DIR, 'full-report.json'), JSON.stringify(report, null, 2));
// 更新 memory.json 的 skyeye 段
updateMemory(report);
console.log(JSON.stringify(report, null, 2));
console.log(`\n🦅 天眼报告生成完毕 · ${report.report_id} · 整体健康: ${overallHealth}`);
}
// ━━━ 更新 memory.json ━━━
function updateMemory(report) {
try {
const mem = readJSON(MEMORY_PATH);
if (!mem) return;
// 添加 skyeye 段
mem.skyeye = {
last_run: report.timestamp,
report_id: report.report_id,
overall_health: report.overall_health,
issues_found: report.diagnosis.total_issues,
auto_fixed: report.diagnosis.auto_fixed,
needs_human: report.diagnosis.needs_human
};
mem.last_updated = new Date().toISOString();
// 添加天眼事件到 recent_events检查重复
const eventDate = new Date().toISOString().slice(0, 10);
const existingEvent = (mem.recent_events || []).find(
e => e.date === eventDate && e.type === 'skyeye_scan'
);
if (!existingEvent) {
if (!mem.recent_events) mem.recent_events = [];
mem.recent_events.unshift({
date: eventDate,
type: 'skyeye_scan',
description: `天眼扫描 · ${report.overall_health} · 问题:${report.diagnosis.total_issues} 自动修复:${report.diagnosis.auto_fixed} 需人工:${report.diagnosis.needs_human}`,
by: '天眼系统'
});
// 保持最近 20 条事件
if (mem.recent_events.length > 20) {
mem.recent_events = mem.recent_events.slice(0, 20);
}
}
fs.writeFileSync(MEMORY_PATH, JSON.stringify(mem, null, 2));
console.log('✅ memory.json 已更新天眼段');
} catch (e) {
console.error('⚠️ 更新 memory.json 失败:', e.message);
}
}
generateReport();