guanghulab/scripts/generate-dashboard-data.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

285 lines
10 KiB
JavaScript
Raw 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/generate-dashboard-data.js
// 📊 仪表盘数据生成器
// 收集系统实时状态 → 更新 data/system-health.json → 更新 README 仪表盘区域
// 由 update-readme.yml 和 update-dashboard.yml 调用
//
// Agent: AG-ZY-094 · Parent: SYS-GLW-0001 · Owner: ICE-0002∞
// 版权:国作登字-2026-A-00037559
'use strict';
var fs = require('fs');
var path = require('path');
var HEALTH_PATH = 'data/system-health.json';
var README_PATH = 'README.md';
// ━━━ 数据收集 ━━━
function countFiles(dir, pattern) {
try {
if (!fs.existsSync(dir)) return 0;
return fs.readdirSync(dir).filter(function(f) {
if (pattern instanceof RegExp) return pattern.test(f);
return f.endsWith(pattern);
}).length;
} catch (e) { return 0; }
}
function loadJSON(p) {
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
catch (e) { return null; }
}
function collectMetrics() {
console.log('📊 收集系统指标...');
// Workflow count
var workflowDir = '.github/workflows';
var workflows = countFiles(workflowDir, /\.ya?ml$/);
// Agent count
var agentReg = loadJSON('.github/persona-brain/agent-registry.json');
var agents = agentReg && agentReg.agents ? agentReg.agents.length : 0;
// Persona count
var personaReg = loadJSON('.github/persona-brain/persona-registry.json');
var personas = personaReg && personaReg.personas ? personaReg.personas.length : 0;
// Guard count
var guardDir = 'skyeye/guards';
var guards = countFiles(guardDir, '.json') - countFiles(guardDir, /^template/);
var guardActive = 0;
var guardWarnings = 0;
if (fs.existsSync(guardDir)) {
fs.readdirSync(guardDir).filter(function(f) {
return f.endsWith('.json') && !f.startsWith('template');
}).forEach(function(f) {
var g = loadJSON(path.join(guardDir, f));
if (g) {
if (g.status === 'active' || !g.status) guardActive++;
if (g.warnings || (g.issues && g.issues.length > 0)) guardWarnings++;
}
});
}
// Module count
var modules = 0;
try {
modules = fs.readdirSync('.').filter(function(f) {
return /^m\d+/.test(f) && fs.statSync(f).isDirectory();
}).length;
} catch (e) {}
// Buffer
var bufferPending = 0;
try {
if (fs.existsSync('buffer/inbox')) {
bufferPending = fs.readdirSync('buffer/inbox').filter(function(f) {
return f !== '.gitkeep';
}).length;
}
} catch (e) {}
// Receipt stats
var receiptDir = 'data/neural-reports/receipts';
var receiptCount = countFiles(receiptDir, '.json');
// Last SkyEye scan
var scanDir = 'data/skyeye-reports';
var lastScan = null;
if (fs.existsSync(scanDir)) {
var scanFiles = fs.readdirSync(scanDir).filter(function(f) {
return f.startsWith('skyeye-') && f.endsWith('.json');
}).sort().reverse();
if (scanFiles.length > 0) {
lastScan = loadJSON(path.join(scanDir, scanFiles[0]));
}
}
// Developer count from dev-registry or dev-status
var devTotal = 11;
var devActive = 8;
var devRegistry = loadJSON('data/dev-registry.json');
if (devRegistry) {
if (devRegistry.developers) {
devTotal = Array.isArray(devRegistry.developers) ? devRegistry.developers.length : devTotal;
}
if (devRegistry.active_count) devActive = devRegistry.active_count;
}
var devStatus = loadJSON('data/developer-status.json');
if (devStatus && devStatus.developers) {
var devList = Array.isArray(devStatus.developers) ? devStatus.developers : Object.keys(devStatus.developers);
if (devList.length > 0) devTotal = devList.length;
}
return {
workflows: { total: workflows, active: workflows, failed: 0 },
agents: { total: agents },
ai_personas: { total: personas, active: personas, hibernating: 0 },
human_developers: { total: devTotal, active_24h: devActive },
guards: { total: guards > 0 ? guards : 6, active: guardActive > 0 ? guardActive : 5, warnings: guardWarnings },
modules: { total: modules > 0 ? modules : 10 },
buffer_pending: bufferPending,
receipts_total: receiptCount,
last_skyeye_scan: lastScan
};
}
// ━━━ system-health.json 更新 ━━━
function updateHealthJSON(metrics) {
var now = new Date();
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
var isoCST = cstTime.toISOString().replace('Z', '+08:00').split('.')[0] + '+08:00';
var health = loadJSON(HEALTH_PATH) || { schema_version: '1.0' };
health.last_updated = isoCST;
health.system_status = 'healthy';
// Merge collected metrics
health.metrics = health.metrics || {};
health.metrics.workflows = metrics.workflows;
health.metrics.agents = metrics.agents;
health.metrics.ai_personas = metrics.ai_personas;
health.metrics.human_developers = metrics.human_developers;
health.metrics.guards = metrics.guards;
health.metrics.modules = metrics.modules;
health.metrics.buffer_pending = metrics.buffer_pending;
health.metrics.receipts_total = metrics.receipts_total;
// Keep existing values for these
health.metrics.ontology_version = health.metrics.ontology_version || 'v1.0';
health.metrics.neural_system_version = health.metrics.neural_system_version || 'v3.0';
// Determine system status
if (metrics.workflows.failed > 0 || metrics.guards.warnings > 1) {
health.system_status = 'warning';
}
fs.writeFileSync(HEALTH_PATH, JSON.stringify(health, null, 2) + '\n');
console.log('✅ system-health.json 已更新');
return health;
}
// ━━━ README 仪表盘更新 ━━━
function updateREADME(health) {
if (!fs.existsSync(README_PATH)) {
console.log('⚠️ README.md 不存在,跳过');
return;
}
var content = fs.readFileSync(README_PATH, 'utf8');
var m = health.metrics;
var changed = false;
// Update shields.io badge numbers
var badgeUpdates = [
[/Workflows-\d+-0969da/g, 'Workflows-' + m.workflows.total + '-0969da'],
[/Agents-\d+-8957e5/g, 'Agents-' + m.agents.total + '-8957e5'],
[/Personas-\d+-e85aad/g, 'Personas-' + m.ai_personas.total + '-e85aad'],
[/Modules-\d+-f9a825/g, 'Modules-' + m.modules.total + '-f9a825']
];
badgeUpdates.forEach(function(pair) {
var before = content;
content = content.replace(pair[0], pair[1]);
if (content !== before) changed = true;
});
// Update HERO_METRICS section
var heroStart = content.indexOf('<!-- HERO_METRICS_START -->');
var heroEnd = content.indexOf('<!-- HERO_METRICS_END -->');
if (heroStart !== -1 && heroEnd !== -1) {
var successRate = m.workflows.total > 0
? Math.round((m.workflows.active / m.workflows.total) * 100) + '%'
: '100%';
var heroContent =
'<!-- HERO_METRICS_START -->\n' +
'| 📈 系统规模 | ⚡ 自动化能力 | 🛡️ 系统稳定性 | 🔄 协作效率 |\n' +
'|:---:|:---:|:---:|:---:|\n' +
'| **' + m.workflows.total + '** 条自动化流水线 | **' + m.agents.total + '** 个智能代理 24h 运行 | **' + successRate + '** 流水线成功率 | **< 3min** 从提交到部署 |\n';
var before = content;
content = content.substring(0, heroStart) + heroContent + content.substring(heroEnd);
if (content !== before) changed = true;
}
// Update CAPABILITY section
var capStart = content.indexOf('<!-- CAPABILITY_START -->');
var capEnd = content.indexOf('<!-- CAPABILITY_END -->');
if (capStart !== -1 && capEnd !== -1) {
var capContent =
'<!-- CAPABILITY_START -->\n' +
'| 能力维度 | 指标 | 说明 |\n' +
'|----------|------|------|\n' +
'| 🔄 **自动化流水线** | ' + m.workflows.total + ' 条 Workflow · ' + (m.workflows.failed === 0 ? '100%' : (100 - Math.round(m.workflows.failed / m.workflows.total * 100)) + '%') + ' 成功率 | 覆盖CI/CD 部署、代码审查、健康检查、数据同步 |\n' +
'| 🤖 **智能代理集群** | ' + m.agents.total + ' 个 Agent 全天候运行 | 自主完成:部署、监控、自愈、告警、报告生成 |\n' +
'| 🎭 **AI 人格体** | ' + m.ai_personas.total + ' 个(' + m.ai_personas.active + ' 在线) | 每位开发者配备 AI 协作伙伴,辅助代码生成与审查 |\n' +
'| 🧬 **双端神经同步** | ' + (m.neural_system_version || 'v3.0') + ' · 11 核心流水线映射 | GitHub ↔ Notion 实时双向同步,认知层与执行层联动 |\n' +
'| 🛡️ **自动化守卫** | ' + m.guards.total + ' 个 Guard · ' + m.guards.active + ' 在线 | 持续监控系统健康、配额使用、部署状态、凭证有效期 |\n' +
'| 🌍 **分布式联邦** | 1 主仓库 + 6 子仓库 | Hub-Spoke 架构,每位开发者独立子仓库,状态自动汇总 |\n';
var before = content;
content = content.substring(0, capStart) + capContent + content.substring(capEnd);
if (content !== before) changed = true;
}
// Update footer timestamp
var footerRe = /最后更新: \d{4}-\d{2}-\d{2}/;
var now = new Date();
var cstTime = new Date(now.getTime() + 8 * 60 * 60 * 1000);
var dateStr = cstTime.toISOString().split('T')[0];
var before = content;
content = content.replace(footerRe, '最后更新: ' + dateStr);
if (content !== before) changed = true;
if (changed) {
fs.writeFileSync(README_PATH, content);
console.log('✅ README.md 仪表盘数据已更新');
} else {
console.log('📋 README.md 无变化');
}
return changed;
}
// ━━━ 主函数 ━━━
function main() {
console.log('\n━━━ 📊 仪表盘数据生成器 ━━━\n');
var metrics = collectMetrics();
console.log(' Workflows: ' + metrics.workflows.total);
console.log(' Agents: ' + metrics.agents.total);
console.log(' Personas: ' + metrics.ai_personas.total);
console.log(' Guards: ' + metrics.guards.total + ' (active ' + metrics.guards.active + ')');
console.log(' Modules: ' + metrics.modules.total);
console.log(' Buffer: ' + metrics.buffer_pending);
console.log(' Receipts: ' + metrics.receipts_total);
var health = updateHealthJSON(metrics);
var readmeChanged = updateREADME(health);
console.log('\n━━━ 完成 ━━━\n');
// Output for workflow consumption
if (process.env.GITHUB_OUTPUT) {
fs.appendFileSync(process.env.GITHUB_OUTPUT,
'readme_changed=' + (readmeChanged ? 'true' : 'false') + '\n');
fs.appendFileSync(process.env.GITHUB_OUTPUT,
'workflow_count=' + metrics.workflows.total + '\n');
fs.appendFileSync(process.env.GITHUB_OUTPUT,
'agent_count=' + metrics.agents.total + '\n');
}
}
if (require.main === module) {
main();
}
module.exports = { collectMetrics, updateHealthJSON, updateREADME };