251 lines
9.5 KiB
JavaScript
251 lines
9.5 KiB
JavaScript
// scripts/skyeye/diagnose.js
|
|
// 天眼·诊断引擎
|
|
//
|
|
// 输入:/tmp/skyeye/ 下所有扫描结果
|
|
// 输出:诊断报告 JSON → stdout
|
|
//
|
|
// 关键逻辑:
|
|
// 汇总异常 → 根因分析 → 检测因果关系 → 生成修复计划
|
|
// 诊断要找根因,不要只看症状
|
|
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const SKYEYE_DIR = '/tmp/skyeye';
|
|
|
|
// ━━━ 安全读取 JSON ━━━
|
|
function readScanResult(filename) {
|
|
const filePath = path.join(SKYEYE_DIR, filename);
|
|
try {
|
|
if (!fs.existsSync(filePath)) return null;
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
} catch (e) {
|
|
console.error(`⚠️ 无法读取 ${filename}: ${e.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ━━━ 问题分类器 ━━━
|
|
function classifyIssue(symptom, source) {
|
|
// 根据症状判断可修复性和优先级
|
|
const autoFixablePatterns = [
|
|
{ pattern: /routing.?map.*不一致|未映射/i, fix: 'update_routing_map', priority: 'P1' },
|
|
{ pattern: /dev.?status.*过期|超过.*24h/i, fix: 'trigger_sync', priority: 'P1' },
|
|
{ pattern: /memory.*过期|超过.*24h/i, fix: 'update_memory', priority: 'P1' },
|
|
{ pattern: /workflow.*失败|run_failure/i, fix: 'retry_workflow', priority: 'P1' },
|
|
{ pattern: /目录.*缺失|不存在/i, fix: 'create_directory', priority: 'P1' },
|
|
{ pattern: /孤儿文件/i, fix: 'log_orphans', priority: 'P2' },
|
|
{ pattern: /README.*marker|标记.*缺失/i, fix: 'repair_readme', priority: 'P1' },
|
|
{ pattern: /knowledge.?base.*重复/i, fix: 'deduplicate_kb', priority: 'P2' },
|
|
{ pattern: /copilot.*instructions.*空|不存在/i, fix: 'note_copilot', priority: 'P2' },
|
|
{ pattern: /syntax.*error|语法.*错误/i, fix: 'log_syntax_error', priority: 'P0' },
|
|
{ pattern: /cron.*conflict|冲突/i, fix: 'log_conflict', priority: 'P2' },
|
|
{ pattern: /hibernation.*checkpoint.*过期|checkpoint.*缺失/i, fix: 'trigger_daily_hibernation', priority: 'P1' },
|
|
{ pattern: /hibernation.*目录.*缺失/i, fix: 'create_directory', priority: 'P1' },
|
|
{ pattern: /upgrade.?pack.*缺失|升级包.*缺失/i, fix: 'trigger_weekly_hibernation', priority: 'P1' }
|
|
];
|
|
|
|
const needsHumanPatterns = [
|
|
{ pattern: /Secret.*缺失|未设置|NOTION_TOKEN|SMTP|DEPLOY_KEY/i, reason: 'Secret 需管理员配置' },
|
|
{ pattern: /Notion.*API.*失败|权限/i, reason: 'Notion 集成需人工检查' },
|
|
{ pattern: /SSH.*不通|服务器.*连接/i, reason: '服务器访问需人工排查' },
|
|
{ pattern: /数据库.*结构/i, reason: 'Notion 数据库结构需人工修改' }
|
|
];
|
|
|
|
// Check if auto-fixable
|
|
for (const p of autoFixablePatterns) {
|
|
if (p.pattern.test(symptom)) {
|
|
return { fixable: true, fix_plan: p.fix, priority: p.priority };
|
|
}
|
|
}
|
|
|
|
// Check if needs human
|
|
for (const p of needsHumanPatterns) {
|
|
if (p.pattern.test(symptom)) {
|
|
return { fixable: false, fix_plan: 'needs_human', priority: 'P0', reason: p.reason };
|
|
}
|
|
}
|
|
|
|
// Default: watch list
|
|
return { fixable: false, fix_plan: 'watch', priority: 'P2' };
|
|
}
|
|
|
|
// ━━━ 根因分析 ━━━
|
|
function analyzeRootCause(issues) {
|
|
// Group related issues and find root causes
|
|
const rootCauses = new Map();
|
|
|
|
for (const issue of issues) {
|
|
// Check if this issue could be caused by another
|
|
let rootCause = issue.symptom;
|
|
|
|
// Missing secret → downstream API failures
|
|
if (/API.*失败|连通.*失败/.test(issue.symptom)) {
|
|
const secretIssue = issues.find(i => /Secret.*缺失/.test(i.symptom) && i.symptom.includes('NOTION'));
|
|
if (secretIssue) {
|
|
rootCause = 'Secret 缺失导致 API 连接失败';
|
|
issue.root_cause = rootCause;
|
|
issue.related_to = secretIssue.id;
|
|
}
|
|
}
|
|
|
|
// dev-status stale could be caused by Notion API failure
|
|
if (/dev.?status.*过期/.test(issue.symptom)) {
|
|
const notionIssue = issues.find(i => /Notion.*API/.test(i.symptom));
|
|
if (notionIssue) {
|
|
rootCause = 'Notion API 不可用导致 dev-status 无法同步';
|
|
issue.root_cause = rootCause;
|
|
issue.related_to = notionIssue.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return issues;
|
|
}
|
|
|
|
// ━━━ 从扫描结果提取问题 ━━━
|
|
function extractIssues() {
|
|
const issues = [];
|
|
let issueCounter = 0;
|
|
|
|
function addIssue(source, symptom, impact) {
|
|
issueCounter++;
|
|
const id = `SKYEYE-${String(issueCounter).padStart(3, '0')}`;
|
|
const classification = classifyIssue(symptom, source);
|
|
issues.push({
|
|
id,
|
|
source,
|
|
symptom,
|
|
root_cause: symptom, // Will be refined by analyzeRootCause
|
|
impact: impact || '系统稳定性',
|
|
...classification
|
|
});
|
|
}
|
|
|
|
// ── Workflow Health Issues ──
|
|
const wfHealth = readScanResult('workflow-health.json');
|
|
if (wfHealth) {
|
|
for (const issue of (wfHealth.issues || [])) {
|
|
addIssue('workflow', `${issue.file}: ${issue.detail}`, 'Workflow 功能异常');
|
|
}
|
|
for (const conflict of (wfHealth.cron_conflicts || [])) {
|
|
addIssue('workflow', `Cron 冲突: ${conflict.detail}`, '定时任务时序冲突');
|
|
}
|
|
}
|
|
|
|
// ── Structure Health Issues ──
|
|
const structHealth = readScanResult('structure-health.json');
|
|
if (structHealth) {
|
|
for (const dir of (structHealth.missing_dirs || [])) {
|
|
addIssue('structure', `目录缺失: ${dir}`, '仓库结构不完整');
|
|
}
|
|
if (structHealth.orphan_files > 0) {
|
|
addIssue('structure', `发现 ${structHealth.orphan_files} 个孤儿文件`, '仓库整洁度');
|
|
}
|
|
if (structHealth.readme && !structHealth.readme_ok) {
|
|
const missing = structHealth.readme.missing_markers || [];
|
|
addIssue('structure', `README marker 缺失: ${missing.join(', ')}`, 'README 自动更新失败');
|
|
}
|
|
}
|
|
|
|
// ── Brain Health Issues ──
|
|
const brainHealth = readScanResult('brain-health.json');
|
|
if (brainHealth) {
|
|
if (brainHealth.memory && brainHealth.memory.status !== '✅') {
|
|
for (const issue of (brainHealth.memory.issues || [])) {
|
|
addIssue('brain', `memory.json: ${issue}`, '核心大脑数据过期');
|
|
}
|
|
}
|
|
if (brainHealth.routing_map && brainHealth.routing_map.status !== '✅') {
|
|
for (const issue of (brainHealth.routing_map.issues || [])) {
|
|
addIssue('brain', `routing-map.json: ${issue}`, '路由映射不一致');
|
|
}
|
|
}
|
|
if (brainHealth.dev_status && brainHealth.dev_status.status !== '✅') {
|
|
for (const issue of (brainHealth.dev_status.issues || [])) {
|
|
addIssue('brain', `dev-status.json: ${issue}`, '开发者状态数据过期');
|
|
}
|
|
}
|
|
if (brainHealth.knowledge_base && brainHealth.knowledge_base.status !== '✅') {
|
|
for (const issue of (brainHealth.knowledge_base.issues || [])) {
|
|
addIssue('brain', `knowledge-base.json: ${issue}`, '知识库数据质量');
|
|
}
|
|
}
|
|
if (brainHealth.copilot_instructions && brainHealth.copilot_instructions.status !== '✅') {
|
|
addIssue('brain', 'copilot-instructions.md 不存在或为空', '代码助手指令缺失');
|
|
}
|
|
}
|
|
|
|
// ── Bridge Health Issues ──
|
|
const bridgeHealth = readScanResult('bridge-health.json');
|
|
if (bridgeHealth) {
|
|
if (bridgeHealth.notion_api && bridgeHealth.notion_api.status !== '🟢') {
|
|
addIssue('bridge', `Notion API: ${bridgeHealth.notion_api.detail}`, '外部数据同步中断');
|
|
}
|
|
if (bridgeHealth.github_api && bridgeHealth.github_api.status !== '🟢') {
|
|
addIssue('bridge', `GitHub API: ${bridgeHealth.github_api.detail}`, 'CI/CD 功能受限');
|
|
}
|
|
if (bridgeHealth.secrets && !bridgeHealth.secrets.complete) {
|
|
const missing = bridgeHealth.secrets.missing || [];
|
|
for (const secret of missing) {
|
|
addIssue('bridge', `Secret 缺失: ${secret}`, '功能不可用');
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Hibernation Health Issues ──
|
|
const hibDir = path.join(path.resolve(__dirname, '../..'), 'skyeye/hibernation');
|
|
if (!fs.existsSync(hibDir)) {
|
|
addIssue('hibernation', 'hibernation 目录缺失', '休眠系统不可用');
|
|
} else {
|
|
const cpDir = path.join(hibDir, 'checkpoints');
|
|
if (fs.existsSync(cpDir)) {
|
|
const cpFiles = fs.readdirSync(cpDir).filter(f => f.startsWith('daily-cp-') && f.endsWith('.json'));
|
|
if (cpFiles.length === 0) {
|
|
addIssue('hibernation', 'hibernation checkpoint 缺失: 无日检查点', '日休眠可能未运行');
|
|
}
|
|
}
|
|
const requiredSubDirs = ['checkpoints', 'weekly-snapshots', 'upgrade-packs', 'distribution-reports'];
|
|
for (const sub of requiredSubDirs) {
|
|
if (!fs.existsSync(path.join(hibDir, sub))) {
|
|
addIssue('hibernation', `hibernation 目录缺失: ${sub}`, '休眠子系统不完整');
|
|
}
|
|
}
|
|
}
|
|
|
|
return issues;
|
|
}
|
|
|
|
// ━━━ 主诊断 ━━━
|
|
function diagnose() {
|
|
let issues = extractIssues();
|
|
|
|
// 根因分析
|
|
issues = analyzeRootCause(issues);
|
|
|
|
// 统计
|
|
const autoFixable = issues.filter(i => i.fixable).length;
|
|
const needsHuman = issues.filter(i => !i.fixable && i.fix_plan === 'needs_human').length;
|
|
const watchList = issues.filter(i => !i.fixable && i.fix_plan === 'watch').length;
|
|
|
|
// 按优先级排序
|
|
const priorityOrder = { 'P0': 0, 'P1': 1, 'P2': 2 };
|
|
issues.sort((a, b) => (priorityOrder[a.priority] || 9) - (priorityOrder[b.priority] || 9));
|
|
|
|
const result = {
|
|
diagnosis_time: new Date(new Date().getTime() + 8 * 3600 * 1000)
|
|
.toISOString().replace('T', ' ').slice(0, 19) + '+08:00',
|
|
total_issues: issues.length,
|
|
auto_fixable: autoFixable,
|
|
needs_human: needsHuman,
|
|
watch_list: watchList,
|
|
issues
|
|
};
|
|
|
|
console.log(JSON.stringify(result, null, 2));
|
|
}
|
|
|
|
diagnose();
|