guanghulab/scripts/commander-dashboard.js

192 lines
5.8 KiB
JavaScript
Raw Normal View History

// scripts/commander-dashboard.js
// 铸渊·将军全局仪表盘生成器
//
// 功能:
// ① 拉取所有 Workflow 运行状态
// ② 读取天眼最新报告
// ③ 读取公告板状态
// ④ 生成将军全局仪表盘 JSON
//
// 输出data/bulletin-board/dashboard.json + stdout
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
const SKYEYE_REPORTS_DIR = path.join(ROOT, 'data/skyeye-reports');
const BULLETIN_DIR = path.join(ROOT, 'data/bulletin-board');
const DASHBOARD_PATH = path.join(BULLETIN_DIR, 'dashboard.json');
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
// ━━━ 获取最新天眼报告 ━━━
function getLatestSkyeyeReport() {
try {
if (!fs.existsSync(SKYEYE_REPORTS_DIR)) return null;
const files = fs.readdirSync(SKYEYE_REPORTS_DIR)
.filter(f => f.startsWith('skyeye-') && f.endsWith('.json'))
.sort()
.reverse();
if (files.length === 0) return null;
return readJSON(path.join(SKYEYE_REPORTS_DIR, files[0]));
} catch (e) {
return null;
}
}
// ━━━ 获取公告板工单状态 ━━━
function getBulletinBoardStatus() {
const workOrdersDir = path.join(BULLETIN_DIR, 'work-orders');
const receiptsDir = path.join(BULLETIN_DIR, 'receipts');
let unreadWorkOrders = 0;
const pendingReceipts = [];
try {
if (fs.existsSync(workOrdersDir)) {
const orders = fs.readdirSync(workOrdersDir)
.filter(f => f.endsWith('.json'));
for (const orderFile of orders) {
const order = readJSON(path.join(workOrdersDir, orderFile));
if (order && order.status !== 'completed') {
unreadWorkOrders++;
pendingReceipts.push(order.order_id || orderFile.replace('.json', ''));
}
}
}
} catch (e) {
// ignore
}
return {
unread_work_orders: unreadWorkOrders,
pending_receipts: pendingReceipts
};
}
// ━━━ 从天眼报告提取小兵状态 ━━━
function extractSoldiersFromReport(report) {
const soldiers = [];
if (!report) return { soldiers, total: 0, healthy: 0, failed: 0, needs_optimization: 0 };
// 从 workflow_health 提取小兵
if (report.workflow_health && report.workflow_health.details) {
for (const wf of report.workflow_health.details) {
const status = wf.status === 'healthy' ? '✅' :
wf.status === 'failed' ? '❌' : '⚠️';
const soldier = {
name: wf.name || wf.file,
status,
last_run: wf.last_run || 'unknown'
};
if (status === '❌') {
soldier.error = wf.error || 'unknown error';
soldier.fix_plan = 'retry + check logs';
}
soldiers.push(soldier);
}
}
const total = soldiers.length;
const healthy = soldiers.filter(s => s.status === '✅').length;
const failed = soldiers.filter(s => s.status === '❌').length;
const needsOpt = soldiers.filter(s => s.status === '⚠️').length;
return { soldiers, total, healthy, failed, needs_optimization: needsOpt };
}
// ━━━ 生成决策摘要 ━━━
function generateDecision(soldierStats, bulletinStatus, skyeyeReport) {
const actions = [];
if (soldierStats.failed > 0) {
actions.push(`修复 ${soldierStats.failed} 个故障小兵`);
}
if (soldierStats.needs_optimization > 0) {
actions.push(`优化 ${soldierStats.needs_optimization} 个小兵`);
}
if (bulletinStatus.unread_work_orders > 0) {
actions.push(`执行 ${bulletinStatus.unread_work_orders} 个未完成工单`);
}
if (skyeyeReport && skyeyeReport.diagnosis && skyeyeReport.diagnosis.needs_human > 0) {
actions.push(`${skyeyeReport.diagnosis.needs_human} 个问题需人工处理`);
}
if (actions.length === 0) {
return '全局健康 · 无需干预 · 继续巡航';
}
return actions.join(' → ');
}
// ━━━ 主函数 ━━━
function generateDashboard() {
const now = new Date();
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
.replace('T', ' ').slice(0, 19) + '+08:00';
// 读取天眼最新报告
const skyeyeReport = getLatestSkyeyeReport();
// 提取小兵状态
const soldierStats = extractSoldiersFromReport(skyeyeReport);
// 读取公告板状态
const bulletinStatus = getBulletinBoardStatus();
// 生成决策
const decision = generateDecision(soldierStats, bulletinStatus, skyeyeReport);
const dashboard = {
commander: '铸渊',
timestamp: bjTime,
global_view: {
total_soldiers: soldierStats.total,
healthy: soldierStats.healthy,
failed: soldierStats.failed,
needs_optimization: soldierStats.needs_optimization,
soldiers: soldierStats.soldiers
},
bulletin_board_status: bulletinStatus,
skyeye_latest: skyeyeReport ? {
overall_health: skyeyeReport.overall_health || '❓',
report_id: skyeyeReport.report_id || null,
issues: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.total_issues : 0,
auto_fixed: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.auto_fixed : 0
} : {
overall_health: '❓',
report_id: null,
issues: 0,
auto_fixed: 0
},
decision
};
// 保存仪表盘
fs.mkdirSync(BULLETIN_DIR, { recursive: true });
fs.writeFileSync(DASHBOARD_PATH, JSON.stringify(dashboard, null, 2));
console.log(JSON.stringify(dashboard, null, 2));
console.log(`\n📊 将军全局仪表盘已生成 · ${bjTime}`);
}
// 允许作为模块导入或直接运行
if (require.main === module) {
generateDashboard();
}
module.exports = { generateDashboard };