guanghulab/scripts/skyeye/scan-brain-health.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

220 lines
7.0 KiB
JavaScript
Raw Permalink 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/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();