guanghulab/skyeye/scripts/scan-engine.js

477 lines
15 KiB
JavaScript
Raw Normal View History

#!/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();