guanghulab/scripts/skyeye/scan-structure.js

214 lines
6.0 KiB
JavaScript
Raw Permalink Normal View History

// scripts/skyeye/scan-structure.js
// 天眼·扫描模块B · 仓库结构完整性扫描
//
// 扫描内容:
// ① 核心目录是否存在
// ② 开发者沙盒目录(对照 routing-map.json
// ③ 孤儿文件检测
// ④ 根目录是否干净
// ⑤ README.md 完整性
// ⑥ package.json 依赖一致性
//
// 输出JSON → stdout
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const now = new Date();
// ━━━ 核心目录列表 ━━━
const CORE_DIRS = [
'.github/persona-brain',
'.github/workflows',
'scripts',
'data',
'core',
'core/brain-wake',
'connectors',
'brain',
'docs',
'src',
'skyeye/hibernation',
'skyeye/hibernation/checkpoints',
'skyeye/hibernation/weekly-snapshots',
'skyeye/hibernation/upgrade-packs',
'skyeye/hibernation/distribution-reports'
];
// ━━━ 预期的根目录文件/目录 ━━━
const EXPECTED_ROOT_ITEMS = [
'.github', '.git', '.gitignore', '.env',
'README.md', 'package.json', 'package-lock.json',
'node_modules',
'brain', 'core', 'connectors', 'scripts', 'data', 'docs', 'src',
'backend', 'frontend', 'modules', 'persona-studio', 'dingtalk-bot',
'persona-brain-db', 'openclaw',
'broadcasts', 'broadcasts-outbox', 'bulletin-board',
'syslog', 'syslog-processed', 'signal-log', 'collaboration-logs',
'reports', 'notification', 'dashboard', 'cloud-drive',
'config.js', 'config.json', 'routing-map.json', 'dev-status.json',
'broadcast-generator.js', 'server.js',
'dev', 'm10-cloud',
'guanghulab-main',
'skyeye', 'buffer', 'grid-db',
'spoke-deployments', 'federation',
'channel-map.json'
];
// ━━━ README Markers ━━━
const README_MARKERS = [
'BINGSHUO_BULLETIN_START', 'BINGSHUO_BULLETIN_END',
'BINGSHUO_ALERT_START', 'BINGSHUO_ALERT_END',
'COLLABORATOR_BULLETIN_START', 'COLLABORATOR_BULLETIN_END',
'COLLABORATOR_ALERT_START', 'COLLABORATOR_ALERT_END',
'SKYEYE-STATUS-BEGIN', 'SKYEYE-STATUS-END'
];
// ━━━ 检查核心目录 ━━━
function checkCoreDirs() {
const results = [];
let allOk = true;
for (const dir of CORE_DIRS) {
const dirPath = path.join(ROOT, dir);
const exists = fs.existsSync(dirPath);
if (!exists) allOk = false;
results.push({ path: dir, exists });
}
return { all_ok: allOk, dirs: results };
}
// ━━━ 检查根目录是否干净 ━━━
function checkRootCleanliness() {
const orphans = [];
try {
const items = fs.readdirSync(ROOT);
for (const item of items) {
if (item.startsWith('.') && !EXPECTED_ROOT_ITEMS.includes(item) && item !== '.github' && item !== '.git' && item !== '.gitignore' && item !== '.env') {
orphans.push(item);
} else if (!item.startsWith('.') && !EXPECTED_ROOT_ITEMS.includes(item)) {
orphans.push(item);
}
}
} catch (e) {
return { clean: false, orphans: [], error: e.message };
}
return { clean: orphans.length === 0, orphans };
}
// ━━━ 检查 README.md 完整性 ━━━
function checkReadme() {
const readmePath = path.join(ROOT, 'README.md');
try {
if (!fs.existsSync(readmePath)) {
return { exists: false, markers_ok: false, missing_markers: README_MARKERS };
}
const content = fs.readFileSync(readmePath, 'utf8');
const missingMarkers = [];
for (const marker of README_MARKERS) {
if (!content.includes(marker)) {
missingMarkers.push(marker);
}
}
return {
exists: true,
size: content.length,
markers_ok: missingMarkers.length === 0,
missing_markers: missingMarkers
};
} catch (e) {
return { exists: false, error: e.message };
}
}
// ━━━ 检查 package.json ━━━
function checkPackageJson() {
const pkgPath = path.join(ROOT, 'package.json');
try {
if (!fs.existsSync(pkgPath)) {
return { exists: false };
}
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const hasNodeModules = fs.existsSync(path.join(ROOT, 'node_modules'));
return {
exists: true,
name: pkg.name,
version: pkg.version,
dep_count: Object.keys(pkg.dependencies || {}).length,
dev_dep_count: Object.keys(pkg.devDependencies || {}).length,
node_modules_exists: hasNodeModules,
has_scripts: Object.keys(pkg.scripts || {}).length > 0
};
} catch (e) {
return { exists: true, parse_error: e.message };
}
}
// ━━━ 检查开发者沙盒目录 ━━━
function checkDevSandboxes() {
const devDir = path.join(ROOT, 'dev');
const results = [];
if (!fs.existsSync(devDir)) {
return { dev_dir_exists: false, sandboxes: [] };
}
try {
const items = fs.readdirSync(devDir);
for (const item of items) {
const itemPath = path.join(devDir, item);
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
const files = fs.readdirSync(itemPath);
results.push({
name: item,
file_count: files.length,
has_readme: files.includes('README.md')
});
}
}
} catch (e) {
return { dev_dir_exists: true, error: e.message };
}
return { dev_dir_exists: true, sandboxes: results };
}
// ━━━ 主扫描 ━━━
function scanStructure() {
const result = {
scan_time: new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 19) + '+08:00',
core_dirs: checkCoreDirs(),
root_cleanliness: checkRootCleanliness(),
readme: checkReadme(),
package_json: checkPackageJson(),
dev_sandboxes: checkDevSandboxes(),
// Summary
core_dirs_ok: true,
orphan_files: 0,
missing_dirs: [],
readme_ok: true
};
// Compute summary
result.core_dirs_ok = result.core_dirs.all_ok;
result.missing_dirs = result.core_dirs.dirs.filter(d => !d.exists).map(d => d.path);
result.orphan_files = result.root_cleanliness.orphans ? result.root_cleanliness.orphans.length : 0;
result.readme_ok = result.readme.exists && result.readme.markers_ok;
console.log(JSON.stringify(result, null, 2));
}
scanStructure();