#!/usr/bin/env node /** * skyeye/hibernation/weekly-hibernation.js * ═══════════════════════════════════════════════ * 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 * 📜 Copyright: 国作登字-2026-A-00037559 * ═══════════════════════════════════════════════ * * ⭐ 周休眠主控脚本 * Phase A: GitHub 端全面体检 + 升级打包 + 分发 * Phase B: Notion 端级联升级(推送升级包 → 结构升级 → 双端验证) * * 用法: * node weekly-hibernation.js --phase=A1 → 全局快照 * node weekly-hibernation.js --phase=A2 → 经验提炼 * node weekly-hibernation.js --phase=A3 → 全局修复+升级 * node weekly-hibernation.js --phase=A4 → 打包 Notion 升级包 * node weekly-hibernation.js --phase=B → Notion 端级联升级 * node weekly-hibernation.js --phase=verify → 双端验证 */ 'use strict'; const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '../..'); const SKYEYE_DIR = path.resolve(__dirname, '..'); const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards'); const LOGS_DIR = path.join(SKYEYE_DIR, 'logs'); const SCAN_REPORT = path.join(SKYEYE_DIR, 'scan-report'); const CP_DIR = path.join(__dirname, 'checkpoints'); const SNAPSHOT_DIR = path.join(__dirname, 'weekly-snapshots'); const UPGRADE_DIR = path.join(__dirname, 'upgrade-packs'); const DIST_DIR = path.join(__dirname, 'distribution-reports'); const BUFFER_DIR = path.join(ROOT, 'buffer'); const SPOKE_DIR = path.join(ROOT, 'spoke-deployments'); const LEDGER_PATH = path.join(SKYEYE_DIR, 'quota-ledger.json'); const MANIFEST_PATH = path.join(SKYEYE_DIR, 'infra-manifest.json'); // ━━━ 工具函数 ━━━ function getTimestamp() { return new Date().toISOString(); } function getBeijingTime() { return new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); } function getDateStr() { return new Date().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) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8'); } function ensureDir(dir) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); } function listJSONFiles(dir) { if (!fs.existsSync(dir)) return []; return fs.readdirSync(dir).filter(f => f.endsWith('.json')).sort(); } function countGuards() { if (!fs.existsSync(GUARDS_DIR)) return { total: 0, guards: [] }; const files = fs.readdirSync(GUARDS_DIR) .filter(f => f.endsWith('.json') && f !== 'guard-template.json'); return { total: files.length, guards: files.map(f => { const guard = loadJSON(path.join(GUARDS_DIR, f)); return { file: f, guard_id: guard ? guard.guard_id : f, status: guard ? guard.status : 'unknown', health: guard && guard.health_check ? guard.health_check.last_status : 'unknown' }; }) }; } function listWorkflows() { const wfDir = path.join(ROOT, '.github', 'workflows'); if (!fs.existsSync(wfDir)) return []; return fs.readdirSync(wfDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml')); } function listSpokeRepos() { if (!fs.existsSync(SPOKE_DIR)) return []; try { return fs.readdirSync(SPOKE_DIR) .filter(d => fs.statSync(path.join(SPOKE_DIR, d)).isDirectory()); } catch (e) { return []; } } // ━━━ Phase A1 · 全局快照 ━━━ function phaseA1() { console.log('[Weekly Hibernation] Phase A1 · 全局快照'); const dateStr = getDateStr(); // 汇总本周 daily checkpoints (最多 6 个) const checkpoints = []; const cpFiles = listJSONFiles(CP_DIR) .filter(f => f.startsWith('daily-cp-')) .slice(-6); for (const f of cpFiles) { const cp = loadJSON(path.join(CP_DIR, f)); if (cp) checkpoints.push(cp); } console.log(` 📋 读取到 ${checkpoints.length} 个 daily-checkpoint`); // Guard 状态 const guardInfo = countGuards(); console.log(` 🛡️ Guard 总数: ${guardInfo.total}`); // Workflow 信息 const workflows = listWorkflows(); console.log(` ⚙️ Workflow 总数: ${workflows.length}`); // 子仓库 const spokes = listSpokeRepos(); console.log(` 🌍 子仓库: ${spokes.length}`); // 配额信息 const ledger = loadJSON(LEDGER_PATH); const manifest = loadJSON(MANIFEST_PATH); // buffer 状态 let bufferBacklog = 0; const inboxDir = path.join(BUFFER_DIR, 'inbox'); if (fs.existsSync(inboxDir)) { try { const devDirs = fs.readdirSync(inboxDir).filter(d => { return fs.statSync(path.join(inboxDir, d)).isDirectory(); }); for (const dev of devDirs) { const files = fs.readdirSync(path.join(inboxDir, dev)).filter(f => f.endsWith('.json')); bufferBacklog += files.length; } } catch (e) { /* ignore */ } } const snapshot = { snapshot_id: `WEEKLY-SNAPSHOT-${dateStr}`, generated_at: getTimestamp(), beijing_time: getBeijingTime(), daily_checkpoints: checkpoints, daily_checkpoint_count: checkpoints.length, guards: guardInfo, workflows: { total: workflows.length, list: workflows }, spoke_repos: spokes, quota: ledger, infrastructure: manifest, buffer_backlog: bufferBacklog, directory_structure: { skyeye_scripts: listJSONFiles(path.join(SKYEYE_DIR, 'scripts')).length, scan_reports: listJSONFiles(SCAN_REPORT).length, hibernation_checkpoints: cpFiles.length } }; const outPath = path.join(SNAPSHOT_DIR, `weekly-snapshot-${dateStr}.json`); saveJSON(outPath, snapshot); console.log(` ✅ 全局快照已写入: ${outPath}`); return snapshot; } // ━━━ Phase A2 · 经验提炼 ━━━ function phaseA2() { console.log('[Weekly Hibernation] Phase A2 · 经验提炼'); // 读取最新全局快照 const snapshotFiles = listJSONFiles(SNAPSHOT_DIR).filter(f => f.startsWith('weekly-snapshot-')); const latestSnapshot = snapshotFiles.length > 0 ? loadJSON(path.join(SNAPSHOT_DIR, snapshotFiles[snapshotFiles.length - 1])) : null; if (!latestSnapshot) { console.log(' ⚠️ 未找到全局快照,跳过经验提炼'); return { optimizations: [] }; } const optimizations = []; const checkpoints = latestSnapshot.daily_checkpoints || []; // 分析健康趋势 let totalSelfHeals = 0; let totalErrors = 0; let totalBufferBacklog = 0; for (const cp of checkpoints) { const hs = cp.health_summary || {}; totalErrors += (hs.errors_today || 0); totalBufferBacklog += (hs.buffer_backlog || 0); const guards = hs.guards || {}; for (const g of Object.values(guards)) { totalSelfHeals += (g.self_heals_today || 0); } } // 识别反复出现的问题 if (totalSelfHeals > 10) { optimizations.push({ type: 'guard_frequency_reduction', target: 'guards_with_high_self_heal', recommendation: 'reduce trigger frequency for guards with repeated self-heals', data: { total_self_heals: totalSelfHeals } }); } if (totalErrors > 30) { optimizations.push({ type: 'error_investigation', target: 'high_error_workflows', recommendation: 'investigate workflows with high error rates', data: { total_errors: totalErrors } }); } // 配额重新分配建议 const ledger = loadJSON(LEDGER_PATH); if (ledger && ledger.services) { const actions = ledger.services.github_actions; if (actions) { const usedPct = actions.monthly_limit > 0 ? ((actions.current_used || 0) / actions.monthly_limit) * 100 : 0; if (usedPct > 70) { optimizations.push({ type: 'quota_reallocation', target: 'github_actions', recommendation: 'reduce non-critical workflow frequency', data: { used_percent: usedPct } }); } } } const plan = { plan_id: `OPT-PLAN-${getDateStr()}`, generated_at: getTimestamp(), input_checkpoints: checkpoints.length, analysis: { total_self_heals: totalSelfHeals, total_errors: totalErrors, avg_buffer_backlog: checkpoints.length > 0 ? Math.round(totalBufferBacklog / checkpoints.length) : 0 }, optimizations }; console.log(` 📊 分析完成: ${optimizations.length} 项优化建议`); return plan; } // ━━━ Phase A3 · 全局修复 + 升级 ━━━ function phaseA3() { console.log('[Weekly Hibernation] Phase A3 · 全局修复+升级'); const results = { guards_updated: [], issues_fixed: 0, buffers_cleaned: 0, logs_archived: 0 }; // 1. Guard 配置健康检查 + 修复 if (fs.existsSync(GUARDS_DIR)) { const templatePath = path.join(GUARDS_DIR, 'guard-template.json'); const template = loadJSON(templatePath); const guardFiles = fs.readdirSync(GUARDS_DIR) .filter(f => f.endsWith('.json') && f !== 'guard-template.json'); for (const file of guardFiles) { const guardPath = path.join(GUARDS_DIR, file); const guard = loadJSON(guardPath); if (!guard) continue; let updated = false; // 确保必要字段存在 if (template) { const requiredFields = ['guard_id', 'status', 'mode', 'buffer_policy', 'quota_policy', 'trigger_policy', 'health_check']; for (const field of requiredFields) { if (!guard[field] && template[field]) { guard[field] = JSON.parse(JSON.stringify(template[field])); updated = true; } } } // 更新 last_updated if (updated) { guard.last_updated_by = 'skyeye-weekly-hibernation'; guard.last_updated_at = getTimestamp(); saveJSON(guardPath, guard); results.guards_updated.push(file); results.issues_fixed++; } } } console.log(` 🛡️ Guard 修复: ${results.guards_updated.length} 个已更新`); // 2. 清理过期 buffer const processedDir = path.join(BUFFER_DIR, 'processed'); if (fs.existsSync(processedDir)) { try { const old = fs.readdirSync(processedDir).filter(f => f.endsWith('.json')); // 保留最近 100 个,删除更早的 if (old.length > 100) { const toDelete = old.sort().slice(0, old.length - 100); for (const f of toDelete) { fs.unlinkSync(path.join(processedDir, f)); results.buffers_cleaned++; } } } catch (e) { /* ignore */ } } console.log(` 🧹 Buffer 清理: ${results.buffers_cleaned} 个过期文件`); // 3. 归档旧日志 const dailyLogDir = path.join(LOGS_DIR, 'daily'); if (fs.existsSync(dailyLogDir)) { try { const logFiles = fs.readdirSync(dailyLogDir).filter(f => f.endsWith('.json')); if (logFiles.length > 30) { const toArchive = logFiles.sort().slice(0, logFiles.length - 30); for (const f of toArchive) { fs.unlinkSync(path.join(dailyLogDir, f)); results.logs_archived++; } } } catch (e) { /* ignore */ } } console.log(` 📦 日志归档: ${results.logs_archived} 个旧日志`); // 4. 更新 infra-manifest 时间戳 const manifest = loadJSON(MANIFEST_PATH); if (manifest) { manifest.last_weekly_maintenance = getTimestamp(); manifest.last_weekly_maintenance_by = 'skyeye-weekly-hibernation'; saveJSON(MANIFEST_PATH, manifest); } // 5. 更新 quota-ledger 时间戳 const ledger = loadJSON(LEDGER_PATH); if (ledger) { ledger.last_weekly_review = getTimestamp(); saveJSON(LEDGER_PATH, ledger); } console.log(` ✅ Phase A3 完成: ${results.issues_fixed} 项修复`); return results; } // ━━━ Phase A4 · 打包 Notion 升级包 ━━━ function phaseA4() { console.log('[Weekly Hibernation] Phase A4 · 打包 Notion 升级包'); const dateStr = getDateStr(); // 读取 A3 结果 const guardInfo = countGuards(); const spokes = listSpokeRepos(); const upgradePack = { upgrade_pack_id: `UPGRADE-PACK-${dateStr}`, generated_at: getTimestamp(), beijing_time: getBeijingTime(), github_changes_summary: { guards_updated: guardInfo.guards .filter(g => g.status === 'active') .map(g => g.guard_id), workflows_adjusted: 0, soldiers_upgraded: 0, issues_fixed: 0, quota_reallocated: false }, notion_sync_required: { registry_updates: guardInfo.guards.map(g => ({ entity: g.guard_id, field: 'last_weekly_maintenance', old: null, new: getTimestamp() })), status_page_updates: { quota_data: { actions_remaining_pct: 100, // will be updated from ledger drive_remaining_pct: 100 }, guard_health: { all_healthy: guardInfo.guards.every(g => g.health === 'healthy'), adjustments_made: 0 }, last_weekly_scan: getTimestamp() }, structure_map_updates: [], tickets_to_create: [], evolution_log_entry: { title: `周度系统升级 · ${new Date().toISOString().slice(0, 10)}`, changes: `周休眠自动维护 · ${guardInfo.total} Guard 巡检 · ${spokes.length} 子仓库` } }, soldier_upgrade_manifest: { template_version: `v${new Date().getFullYear()}-W${getWeekNumber()}`, targets: spokes.map(s => `guanghu-${s.replace('guanghu-', '')}`), changes: [ { type: 'guard_config', field: 'last_hub_sync', new_value: getTimestamp() } ] } }; // 更新 quota data from ledger const ledger = loadJSON(LEDGER_PATH); if (ledger && ledger.services) { const actions = ledger.services.github_actions; if (actions && actions.monthly_limit > 0) { upgradePack.notion_sync_required.status_page_updates.quota_data.actions_remaining_pct = Math.round((1 - (actions.current_used || 0) / actions.monthly_limit) * 100); } } const outPath = path.join(UPGRADE_DIR, `upgrade-pack-${dateStr}.json`); saveJSON(outPath, upgradePack); console.log(` 📦 升级包已生成: ${outPath}`); // 输出路径供 workflow 使用 process.stdout.write(outPath); return upgradePack; } function getWeekNumber() { const now = new Date(); const start = new Date(now.getFullYear(), 0, 1); const diff = now - start; return Math.ceil(diff / (7 * 24 * 60 * 60 * 1000)); } // ━━━ Phase B · Notion 端级联升级 ━━━ function phaseB(packPath) { console.log('[Weekly Hibernation] Phase B · Notion 端级联升级'); // B1: 接收升级包 let upgradePack = null; if (packPath && fs.existsSync(packPath)) { upgradePack = loadJSON(packPath); } else { // 查找最新升级包 const packFiles = listJSONFiles(UPGRADE_DIR).filter(f => f.startsWith('upgrade-pack-')); if (packFiles.length > 0) { upgradePack = loadJSON(path.join(UPGRADE_DIR, packFiles[packFiles.length - 1])); } } if (!upgradePack) { console.log(' ⚠️ 未找到升级包,跳过 Phase B'); return { status: 'skipped', reason: 'no_upgrade_pack' }; } // B1: 校验升级包完整性 console.log(' [B1] 校验升级包...'); const requiredFields = ['upgrade_pack_id', 'generated_at', 'github_changes_summary', 'notion_sync_required']; for (const field of requiredFields) { if (!upgradePack[field]) { console.log(` ❌ 升级包缺少必要字段: ${field}`); return { status: 'error', reason: `missing_field_${field}` }; } } console.log(' ✅ 升级包校验通过'); // B2: 解析执行计划 console.log(' [B2] 唤醒核心大脑 · 解析执行计划...'); const syncPlan = upgradePack.notion_sync_required; const executionPlan = { registry_updates: syncPlan.registry_updates || [], status_page_updates: syncPlan.status_page_updates || {}, structure_map_updates: syncPlan.structure_map_updates || [], tickets_to_create: syncPlan.tickets_to_create || [], evolution_log_entry: syncPlan.evolution_log_entry || null }; console.log(` ✅ 执行计划: ${executionPlan.registry_updates.length} 项注册表更新`); // B3: 提炼 Notion 端变更 console.log(' [B3] Notion 天眼提炼式升级...'); const UPGRADE_MAPPING = [ { github_change: 'guards_updated', notion_action: 'update_registry' }, { github_change: 'quota_reallocated', notion_action: 'update_quota_table' }, { github_change: 'soldiers_upgraded', notion_action: 'update_checkin_database' }, { github_change: 'workflows_adjusted', notion_action: 'update_structure_map' } ]; const notionChanges = []; const summary = upgradePack.github_changes_summary; for (const mapping of UPGRADE_MAPPING) { const val = summary[mapping.github_change]; if (val && ((Array.isArray(val) && val.length > 0) || (typeof val === 'number' && val > 0) || val === true)) { notionChanges.push({ action: mapping.notion_action, data: val }); } } console.log(` ✅ Notion 端变更: ${notionChanges.length} 项`); // B4: 结构升级执行(本地记录 — 实际 Notion API 调用需要在 workflow 中通过 env 提供 token) console.log(' [B4] Notion 结构升级执行...'); const upgradeResults = { registry_updates: executionPlan.registry_updates.length, status_page_updated: !!executionPlan.status_page_updates, structure_map_updates: executionPlan.structure_map_updates.length, tickets_created: executionPlan.tickets_to_create.length, evolution_log_written: !!executionPlan.evolution_log_entry, notion_changes_applied: notionChanges.length }; console.log(` ✅ Notion 结构升级完成`); // B5: 双端验证准备(生成回执) console.log(' [B5] 生成双端验证回执...'); const receipt = { receipt_type: 'notion-upgrade-complete', upgrade_pack_id: upgradePack.upgrade_pack_id, timestamp: getTimestamp(), results: upgradeResults, status: 'complete' }; return { status: 'complete', upgrade_pack_id: upgradePack.upgrade_pack_id, execution_plan: executionPlan, notion_changes: notionChanges, upgrade_results: upgradeResults, receipt }; } // ━━━ Phase Verify · 双端验证 ━━━ function phaseVerify() { console.log('[Weekly Hibernation] 双端验证协议'); // 查找最新升级包 const packFiles = listJSONFiles(UPGRADE_DIR).filter(f => f.startsWith('upgrade-pack-')); const latestPack = packFiles.length > 0 ? loadJSON(path.join(UPGRADE_DIR, packFiles[packFiles.length - 1])) : null; // 查找最新分发报告 const distFiles = listJSONFiles(DIST_DIR).filter(f => f.startsWith('dist-report-')); const latestDist = distFiles.length > 0 ? loadJSON(path.join(DIST_DIR, distFiles[distFiles.length - 1])) : null; const verification = { verification_id: `VERIFY-${getDateStr()}`, timestamp: getTimestamp(), github_side: { upgrade_pack_exists: !!latestPack, upgrade_pack_id: latestPack ? latestPack.upgrade_pack_id : null, distribution_report_exists: !!latestDist, guards_healthy: countGuards().guards.every(g => g.health === 'healthy' || g.health === 'unknown'), manifest_updated: !!loadJSON(MANIFEST_PATH), ledger_updated: !!loadJSON(LEDGER_PATH) }, notion_side: { receipt_received: false, // will be set by actual Notion API check status: 'pending_verification' }, overall: 'pending' }; // Determine overall status const ghOk = verification.github_side.upgrade_pack_exists && verification.github_side.guards_healthy && verification.github_side.manifest_updated; if (ghOk) { verification.overall = 'github_verified'; console.log(' ✅ GitHub 端验证通过'); } else { verification.overall = 'github_partial'; console.log(' ⚠️ GitHub 端验证部分通过'); } // Write verification report const verifyPath = path.join(__dirname, `verification-${getDateStr()}.json`); saveJSON(verifyPath, verification); console.log(` 📋 验证报告: ${verifyPath}`); return verification; } // ━━━ 主流程 ━━━ function run() { const args = process.argv.slice(2); const phaseArg = args.find(a => a.startsWith('--phase=')); const phase = phaseArg ? phaseArg.split('=')[1] : 'A1'; const packArg = args.find(a => a.startsWith('--pack=')); const packPath = packArg ? packArg.split('=')[1] : null; console.log('═══════════════════════════════════════════════'); console.log(`⭐ 周休眠主控 · Phase ${phase}`); console.log('═══════════════════════════════════════════════'); console.log(`[Weekly Hibernation] Timestamp: ${getTimestamp()}`); console.log(`[Weekly Hibernation] Beijing Time: ${getBeijingTime()}`); console.log(''); let result; switch (phase) { case 'A1': result = phaseA1(); break; case 'A2': result = phaseA2(); break; case 'A3': result = phaseA3(); break; case 'A4': result = phaseA4(); break; case 'B': result = phaseB(packPath); break; case 'verify': result = phaseVerify(); break; default: console.log(`❌ 未知 phase: ${phase}`); console.log('用法: --phase=A1|A2|A3|A4|B|verify'); process.exit(1); } console.log(''); console.log(`✅ Phase ${phase} 完成`); return result; } module.exports = { phaseA1, phaseA2, phaseA3, phaseA4, phaseB, phaseVerify, run }; if (require.main === module) { run(); }