guanghulab/skyeye/scripts/scan-engine.js
2026-05-10 13:12:44 +08:00

477 lines
15 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.

#!/usr/bin/env node
/**
* skyeye/scripts/scan-engine.js
* 天眼扫描引擎 — 全量 + 增量基础设施扫描
* Phase 1 of weekly scan: Infrastructure Scan
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const SKYEYE_DIR = path.resolve(__dirname, '..');
const MANIFEST_PATH = path.join(SKYEYE_DIR, 'infra-manifest.json');
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
function getTimestamp() {
return new Date().toISOString();
}
function getDateStr() {
const d = new Date();
return d.toISOString().slice(0, 10).replace(/-/g, '');
}
function loadJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
function saveJSON(filePath, data) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
function scanGuardConfigs() {
const results = { total: 0, valid: 0, invalid: [], missing_fields: [] };
const requiredFields = ['guard_id', 'status', 'mode', 'quota_policy', 'health_check'];
const files = fs.readdirSync(GUARDS_DIR).filter(f => f.endsWith('.json') && f !== 'guard-template.json');
results.total = files.length;
for (const file of files) {
const filePath = path.join(GUARDS_DIR, file);
const guard = loadJSON(filePath);
if (!guard) {
results.invalid.push({ file, reason: 'Invalid JSON' });
continue;
}
const missing = requiredFields.filter(f => !(f in guard));
if (missing.length > 0) {
results.missing_fields.push({ file, missing });
} else {
results.valid++;
}
}
return results;
}
function scanWorkflows() {
const workflowDir = path.join(ROOT, '.github/workflows');
const results = { total: 0, files: [], errors: [] };
if (!fs.existsSync(workflowDir)) {
results.errors.push('Workflow directory not found');
return results;
}
const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
results.total = files.length;
results.files = files;
return results;
}
function scanDirectoryStructure() {
const requiredDirs = [
'buffer',
'buffer/inbox',
'buffer/staging',
'buffer/scripts',
'buffer/config',
'grid-db',
'skyeye',
'skyeye/guards',
'skyeye/scripts',
'skyeye/logs',
'skyeye/scan-report',
'skyeye/hibernation',
'skyeye/hibernation/checkpoints',
'skyeye/hibernation/weekly-snapshots',
'skyeye/hibernation/upgrade-packs',
'skyeye/hibernation/distribution-reports',
'.github/persona-brain',
'.github/workflows',
'scripts/skyeye'
];
const results = { checked: requiredDirs.length, present: 0, missing: [] };
for (const dir of requiredDirs) {
const fullPath = path.join(ROOT, dir);
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
results.present++;
} else {
results.missing.push(dir);
}
}
return results;
}
function scanSubRepos(manifest) {
const results = { total: 0, verified: [] };
if (!manifest || !manifest.infrastructure || !manifest.infrastructure.github) return results;
const repos = manifest.infrastructure.github.repos || [];
results.total = repos.length;
for (const repo of repos) {
results.verified.push({
name: repo.name,
role: repo.role,
guard_path_exists: repo.guard ? fs.existsSync(path.join(ROOT, repo.guard)) : false
});
}
return results;
}
// D16 · 本体论完整性检查 (Ontology Integrity)
function checkOntologyIntegrity() {
const result = {
dimension: 'D16',
name: '本体论完整性',
checks: []
};
// 检查 ontology.json 存在且版本正确
const ontologyPath = path.join(ROOT, '.github/persona-brain/ontology.json');
if (fs.existsSync(ontologyPath)) {
const ontology = loadJSON(ontologyPath);
if (ontology) {
result.checks.push({
item: 'ontology.json 存在',
status: '✅',
version: ontology.version
});
// 验证六条公理完整
const axiomCount = Object.keys(ontology.core_axioms || {}).length;
result.checks.push({
item: '核心公理完整性',
status: axiomCount >= 6 ? '✅' : '🔴',
detail: `${axiomCount}/6 条公理`
});
// 验证三层安全定义完整
const layers = ontology.security_layers || {};
const layerCount = Object.keys(layers).length;
result.checks.push({
item: '三层安全定义',
status: layerCount >= 3 ? '✅' : '🔴',
detail: `${layerCount}/3 层`
});
} else {
result.checks.push({
item: 'ontology.json 存在',
status: '🔴',
detail: 'JSON 解析失败'
});
}
} else {
result.checks.push({
item: 'ontology.json 存在',
status: '🔴',
detail: '文件缺失'
});
}
// 检查 copilot-instructions.md 包含本体论宣言
const copilotPath = path.join(ROOT, '.github/copilot-instructions.md');
if (fs.existsSync(copilotPath)) {
const content = fs.readFileSync(copilotPath, 'utf8');
const hasOntology = content.includes('数字地球本体论');
result.checks.push({
item: 'copilot-instructions 本体论宣言',
status: hasOntology ? '✅' : '🟡',
detail: hasOntology ? '已注入' : '未找到本体论段落'
});
}
// 检查 README 包含本体论语言
const readmePath = path.join(ROOT, 'README.md');
if (fs.existsSync(readmePath)) {
const readme = fs.readFileSync(readmePath, 'utf8');
const hasOntology = readme.includes('数字地球') || readme.includes('Digital Earth Ontology');
result.checks.push({
item: 'README 本体论标识',
status: hasOntology ? '✅' : '🟡',
detail: hasOntology ? '已注入' : '未找到'
});
}
return result;
}
// D20 · 双端映射完整性检查 (Neural Map Integrity)
function checkNeuralMapIntegrity() {
var result = {
dimension: 'D20',
name: '双端映射完整性',
checks: [],
unmapped_workflows: [],
invalid_brains: []
};
var neuralMapPath = path.join(SKYEYE_DIR, 'neural-map.json');
if (!fs.existsSync(neuralMapPath)) {
result.checks.push({
item: 'neural-map.json 存在',
status: '🔴',
detail: '文件缺失'
});
return result;
}
var neuralMap = loadJSON(neuralMapPath);
if (!neuralMap) {
result.checks.push({
item: 'neural-map.json 可解析',
status: '🔴',
detail: 'JSON 解析失败'
});
return result;
}
result.checks.push({
item: 'neural-map.json 存在且可解析',
status: '✅',
detail: 'version: ' + (neuralMap.version || 'unknown')
});
// 检查所有映射的 Workflow 文件是否实际存在
var mappedFiles = {};
var workflowEntries = Object.entries(neuralMap.github_workflows || {});
var existCount = 0;
var missingCount = 0;
for (var i = 0; i < workflowEntries.length; i++) {
var wfId = workflowEntries[i][0];
var wf = workflowEntries[i][1];
var filePath = path.join(ROOT, wf.file);
mappedFiles[wf.file] = true;
if (fs.existsSync(filePath)) {
existCount++;
} else {
missingCount++;
result.checks.push({
item: '映射文件存在: ' + wf.file,
status: '🔴',
detail: wfId + ' 映射的文件不存在'
});
}
}
result.checks.push({
item: '映射 Workflow 文件检查',
status: missingCount === 0 ? '✅' : '🔴',
detail: existCount + '/' + workflowEntries.length + ' 文件存在'
});
// 检查实际 .yml 中是否有未映射的(仅检查核心 Workflow非全量
var workflowDir = path.join(ROOT, '.github/workflows');
if (fs.existsSync(workflowDir)) {
var actualFiles = fs.readdirSync(workflowDir)
.filter(function(f) { return f.endsWith('.yml') || f.endsWith('.yaml'); });
for (var j = 0; j < actualFiles.length; j++) {
var ymlPath = '.github/workflows/' + actualFiles[j];
if (!mappedFiles[ymlPath]) {
result.unmapped_workflows.push(actualFiles[j]);
}
}
}
// Note: unmapped is not necessarily an error since neural-map only maps core workflows
// The total workflow count minus mapped workflows gives the expected unmapped count
var mappedCount = workflowEntries.length;
result.checks.push({
item: '未映射 Workflow 检查',
status: '✅',
detail: result.unmapped_workflows.length + ' 个未映射(非核心 Workflow 可不映射,已映射 ' + mappedCount + ' 个)'
});
// 检查每个 brain 是否在 notion_brains 中注册
for (var k = 0; k < workflowEntries.length; k++) {
var wfId2 = workflowEntries[k][0];
var wf2 = workflowEntries[k][1];
if (wf2.brain && !(neuralMap.notion_brains || {})[wf2.brain]) {
result.invalid_brains.push({ workflow: wfId2, brain: wf2.brain });
}
}
result.checks.push({
item: 'Brain 注册完整性',
status: result.invalid_brains.length === 0 ? '✅' : '🔴',
detail: result.invalid_brains.length === 0
? '所有 Brain 均已注册'
: result.invalid_brains.length + ' 个 Brain 未注册'
});
// 检查 report_path 目录是否存在
var reportPathMissing = 0;
for (var m = 0; m < workflowEntries.length; m++) {
var wf3 = workflowEntries[m][1];
if (wf3.report_path && !fs.existsSync(path.join(ROOT, wf3.report_path))) {
reportPathMissing++;
}
}
result.checks.push({
item: '报告目录存在性',
status: reportPathMissing === 0 ? '✅' : '🟡',
detail: reportPathMissing === 0
? '所有报告目录已创建'
: reportPathMissing + ' 个报告目录缺失'
});
return result;
}
function run() {
const args = process.argv.slice(2);
const mode = args.find(a => a.startsWith('--mode='))?.split('=')[1] || 'full';
console.log(`[SkyEye Scan Engine] Mode: ${mode}`);
console.log(`[SkyEye Scan Engine] Timestamp: ${getTimestamp()}`);
const manifest = loadJSON(MANIFEST_PATH);
if (!manifest) {
console.error('[SkyEye Scan Engine] ERROR: Cannot load infra-manifest.json');
process.exit(1);
}
const scanResult = {
scan_id: `SCAN-${getDateStr()}`,
mode,
timestamp: getTimestamp(),
guard_scan: scanGuardConfigs(),
workflow_scan: scanWorkflows(),
directory_scan: scanDirectoryStructure(),
sub_repo_scan: scanSubRepos(manifest),
ontology_scan: checkOntologyIntegrity(),
neural_map_scan: checkNeuralMapIntegrity(),
issues: [],
summary: {}
};
// Collect issues
if (scanResult.guard_scan.invalid.length > 0) {
scanResult.issues.push(...scanResult.guard_scan.invalid.map(i => ({
severity: 'error',
category: 'guard_config',
detail: `Invalid guard config: ${i.file} - ${i.reason}`
})));
}
if (scanResult.guard_scan.missing_fields.length > 0) {
scanResult.issues.push(...scanResult.guard_scan.missing_fields.map(i => ({
severity: 'warning',
category: 'guard_config',
detail: `Guard ${i.file} missing fields: ${i.missing.join(', ')}`
})));
}
if (scanResult.directory_scan.missing.length > 0) {
scanResult.issues.push(...scanResult.directory_scan.missing.map(d => ({
severity: 'warning',
category: 'directory_structure',
detail: `Missing directory: ${d}`
})));
}
// D16 · 本体论完整性 issues
if (scanResult.ontology_scan && scanResult.ontology_scan.checks) {
for (const check of scanResult.ontology_scan.checks) {
if (check.status === '🔴') {
scanResult.issues.push({
severity: 'error',
category: 'ontology_integrity',
detail: `D16 · ${check.item}: ${check.detail || '失败'}`
});
} else if (check.status === '🟡') {
scanResult.issues.push({
severity: 'warning',
category: 'ontology_integrity',
detail: `D16 · ${check.item}: ${check.detail || '需关注'}`
});
}
}
}
// D20 · 双端映射完整性 issues
if (scanResult.neural_map_scan && scanResult.neural_map_scan.checks) {
for (const check of scanResult.neural_map_scan.checks) {
if (check.status === '🔴') {
scanResult.issues.push({
severity: 'error',
category: 'neural_map_integrity',
detail: `D20 · ${check.item}: ${check.detail || '失败'}`
});
} else if (check.status === '🟡') {
scanResult.issues.push({
severity: 'warning',
category: 'neural_map_integrity',
detail: `D20 · ${check.item}: ${check.detail || '需关注'}`
});
}
}
if (scanResult.neural_map_scan.invalid_brains.length > 0) {
scanResult.issues.push({
severity: 'error',
category: 'neural_map_integrity',
detail: `D20 · Brain 映射指向不存在的 Agent: ${scanResult.neural_map_scan.invalid_brains.map(b => b.brain).join(', ')}`
});
}
}
// Summary
scanResult.summary = {
total_issues: scanResult.issues.length,
errors: scanResult.issues.filter(i => i.severity === 'error').length,
warnings: scanResult.issues.filter(i => i.severity === 'warning').length,
guards_healthy: `${scanResult.guard_scan.valid}/${scanResult.guard_scan.total}`,
workflows_found: scanResult.workflow_scan.total,
directories_ok: `${scanResult.directory_scan.present}/${scanResult.directory_scan.checked}`
};
// Update manifest
manifest.last_scan = getTimestamp();
manifest.last_scan_by = 'skyeye-scan-engine';
saveJSON(MANIFEST_PATH, manifest);
// Write log
const logDir = path.join(LOGS_DIR, mode === 'full' ? 'weekly' : 'daily');
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
const logPath = path.join(logDir, `scan-${getDateStr()}.json`);
saveJSON(logPath, scanResult);
console.log(`[SkyEye Scan Engine] Scan complete.`);
console.log(`[SkyEye Scan Engine] Guards: ${scanResult.summary.guards_healthy}`);
console.log(`[SkyEye Scan Engine] Workflows: ${scanResult.summary.workflows_found}`);
console.log(`[SkyEye Scan Engine] Directories: ${scanResult.summary.directories_ok}`);
console.log(`[SkyEye Scan Engine] Issues: ${scanResult.summary.total_issues} (${scanResult.summary.errors} errors, ${scanResult.summary.warnings} warnings)`);
// D16 · 本体论完整性状态
if (scanResult.ontology_scan) {
console.log(`[SkyEye Scan Engine] D16 Ontology:`);
for (const check of scanResult.ontology_scan.checks) {
console.log(` ${check.status} ${check.item}${check.detail ? ' · ' + check.detail : ''}${check.version ? ' · v' + check.version : ''}`);
}
}
// D20 · 双端映射完整性状态
if (scanResult.neural_map_scan) {
console.log(`[SkyEye Scan Engine] D20 Neural Map:`);
for (const check of scanResult.neural_map_scan.checks) {
console.log(` ${check.status} ${check.item}${check.detail ? ' · ' + check.detail : ''}`);
}
}
console.log(`[SkyEye Scan Engine] Log saved: ${logPath}`);
// Output full result as JSON for pipeline
console.log('---SCAN_RESULT_JSON---');
console.log(JSON.stringify(scanResult, null, 2));
return scanResult;
}
run();