214 lines
6.0 KiB
JavaScript
214 lines
6.0 KiB
JavaScript
|
|
// 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();
|