zhizhi/scripts/skyeye/scan-brain-health.js

220 lines
7.0 KiB
JavaScript
Raw Normal View History

// scripts/skyeye/scan-brain-health.js
// 天眼·扫描模块C · 核心大脑健康度扫描
//
// 扫描内容:
// ① memory.json — last_updated 是否 24h 内,数据结构完整
// ② routing-map.json — 映射目录是否真实存在,是否有新目录未映射
// ③ dev-status.json — last_sync 是否 24h 内开发者列表一致性72h+ 无活动告警
// ④ knowledge-base.json — 重复条目检测
// ⑤ copilot-instructions.md — 存在且非空
//
// 输出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 BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const now = new Date();
const HOUR_24_MS = 24 * 3600 * 1000;
const HOUR_72_MS = 72 * 3600 * 1000;
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return { exists: false, data: null };
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
return { exists: true, data, valid: true };
} catch (e) {
return { exists: true, data: null, valid: false, error: e.message };
}
}
// ━━━ 检查 memory.json ━━━
function checkMemory() {
const result = readJSON(path.join(BRAIN_DIR, 'memory.json'));
if (!result.exists) return { status: '❌', detail: 'memory.json 不存在', fresh: false };
if (!result.valid) return { status: '❌', detail: 'memory.json 解析失败: ' + result.error, fresh: false };
const mem = result.data;
const issues = [];
// 必要字段检查
if (!mem.persona_id) issues.push('缺少 persona_id');
if (!mem.persona_name) issues.push('缺少 persona_name');
if (!mem.recent_events) issues.push('缺少 recent_events');
if (!mem.last_updated) issues.push('缺少 last_updated');
// 新鲜度检查
let fresh = false;
if (mem.last_updated) {
const lastUpdate = new Date(mem.last_updated);
fresh = (now.getTime() - lastUpdate.getTime()) < HOUR_24_MS;
if (!fresh) issues.push('last_updated 超过 24h');
}
return {
status: issues.length === 0 ? '✅' : '⚠️',
fresh,
last_updated: mem.last_updated || null,
event_count: Array.isArray(mem.recent_events) ? mem.recent_events.length : 0,
issues
};
}
// ━━━ 检查 routing-map.json ━━━
function checkRoutingMap() {
const result = readJSON(path.join(BRAIN_DIR, 'routing-map.json'));
if (!result.exists) return { status: '❌', detail: 'routing-map.json 不存在', aligned: false };
if (!result.valid) return { status: '❌', detail: 'routing-map.json 解析失败', aligned: false };
const rmap = result.data;
const issues = [];
// 检查 domains 存在
if (!rmap.domains) {
issues.push('缺少 domains 字段');
return { status: '⚠️', aligned: false, issues };
}
// 验证每个 domain 的 interfaces
let totalInterfaces = 0;
let implementedInterfaces = 0;
for (const [domain, cfg] of Object.entries(rmap.domains)) {
if (cfg.interfaces) {
totalInterfaces += cfg.interfaces.length;
implementedInterfaces += cfg.interfaces.filter(i => i.status !== 'pending').length;
}
}
return {
status: issues.length === 0 ? '✅' : '⚠️',
aligned: issues.length === 0,
domain_count: Object.keys(rmap.domains).length,
total_interfaces: totalInterfaces,
implemented_interfaces: implementedInterfaces,
issues
};
}
// ━━━ 检查 dev-status.json ━━━
function checkDevStatus() {
const result = readJSON(path.join(BRAIN_DIR, 'dev-status.json'));
if (!result.exists) return { status: '❌', detail: 'dev-status.json 不存在', fresh: false };
if (!result.valid) return { status: '❌', detail: 'dev-status.json 解析失败', fresh: false };
const ds = result.data;
const issues = [];
const alerts = [];
// 新鲜度
let fresh = false;
if (ds.last_sync) {
const lastSync = new Date(ds.last_sync);
fresh = (now.getTime() - lastSync.getTime()) < HOUR_24_MS;
if (!fresh) issues.push('last_sync 超过 24h');
} else {
issues.push('缺少 last_sync');
}
// 开发者列表检查
const team = ds.team || [];
for (const dev of team) {
// 72h 无活动告警
if (dev.streak === 0 || (dev.waiting && dev.waiting.includes('72h'))) {
alerts.push(`${dev.dev_id} ${dev.name}: 超 72h 无活动`);
}
}
return {
status: issues.length === 0 ? '✅' : '⚠️',
fresh,
last_sync: ds.last_sync || null,
dev_count: team.length,
alerts,
issues
};
}
// ━━━ 检查 knowledge-base.json ━━━
function checkKnowledgeBase() {
const result = readJSON(path.join(BRAIN_DIR, 'knowledge-base.json'));
if (!result.exists) return { status: '⚠️', detail: 'knowledge-base.json 不存在', entries: 0 };
if (!result.valid) return { status: '❌', detail: 'knowledge-base.json 解析失败', entries: 0 };
const kb = result.data;
let entries = 0;
let duplicates = 0;
// Count entries and check for duplicates
if (Array.isArray(kb)) {
entries = kb.length;
const titles = kb.map(e => e.title || e.name || JSON.stringify(e));
const unique = new Set(titles);
duplicates = titles.length - unique.size;
} else if (typeof kb === 'object') {
entries = Object.keys(kb).length;
}
const issues = [];
if (duplicates > 0) issues.push(`发现 ${duplicates} 个重复条目`);
return {
status: issues.length === 0 ? '✅' : '⚠️',
entries,
duplicates,
issues
};
}
// ━━━ 检查 copilot-instructions.md ━━━
function checkCopilotInstructions() {
const filePath = path.join(ROOT, '.github/copilot-instructions.md');
try {
if (!fs.existsSync(filePath)) {
return { status: '⚠️', exists: false, detail: 'copilot-instructions.md 不存在' };
}
const content = fs.readFileSync(filePath, 'utf8');
if (content.trim().length === 0) {
return { status: '⚠️', exists: true, empty: true, detail: 'copilot-instructions.md 内容为空' };
}
return { status: '✅', exists: true, empty: false, size: content.length };
} catch (e) {
return { status: '❌', exists: false, error: e.message };
}
}
// ━━━ 主扫描 ━━━
function scanBrainHealth() {
const result = {
scan_time: new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 19) + '+08:00',
memory: checkMemory(),
routing_map: checkRoutingMap(),
dev_status: checkDevStatus(),
knowledge_base: checkKnowledgeBase(),
copilot_instructions: checkCopilotInstructions(),
// Summary
integrity: 'ok',
memory_fresh: false,
routing_map_aligned: false,
dev_status_fresh: false
};
// Compute summary
result.memory_fresh = result.memory.fresh;
result.routing_map_aligned = result.routing_map.aligned;
result.dev_status_fresh = result.dev_status.fresh;
const hasError = [result.memory, result.routing_map, result.dev_status]
.some(r => r.status === '❌');
result.integrity = hasError ? 'damaged' : 'ok';
console.log(JSON.stringify(result, null, 2));
}
scanBrainHealth();