guanghulab/scripts/update-readme-bulletin.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

942 lines
31 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

/**
* 光湖系统公告区自动更新脚本 v2.0
*
* 分离公告为两个区域:
* 1. 冰朔公告栏 — 自检/轮询/数据库/铸渊自动提醒
* 2. 合作者公告栏 — 模块上传状态/各人提醒
*
* 铸渊自动提醒逻辑:
* - 绿色 = 今日无需手动干预
* - 红色 = 需要干预,列出待处理事项,并发邮件通知
*
* 环境变量:
* GITHUB_TOKEN - GitHub API token (Actions 自动提供)
* GITHUB_REPOSITORY - owner/repo (Actions 自动提供)
* SMTP_HOST - 邮件服务器 (可选)
* SMTP_PORT - 邮件端口 (可选, 默认 465)
* SMTP_USER - 邮件账号 (可选)
* SMTP_PASS - 邮件密码 (可选)
* BINGSHUO_EMAIL - 冰朔邮箱 (可选, 默认从 GitHub API 获取)
*
* 用法:
* node scripts/update-readme-bulletin.js
*/
const fs = require('fs');
const path = require('path');
const README_PATH = path.join(__dirname, '..', 'README.md');
const MEMORY_PATH = path.join(__dirname, '..', '.github', 'brain', 'memory.json');
const PERSONA_MEMORY_PATH = path.join(__dirname, '..', '.github', 'persona-brain', 'memory.json');
const DEV_STATUS_PATH = path.join(__dirname, '..', '.github', 'persona-brain', 'dev-status.json');
const COLLABORATORS_PATH = path.join(__dirname, '..', '.github', 'brain', 'collaborators.json');
const BULLETIN_CACHE_PATH = path.join(__dirname, '..', '.github', 'brain', 'bulletin-board-today.json');
const MAX_BINGSHUO_ENTRIES = 15;
const MAX_COLLAB_ENTRIES = 20;
const MAX_GIT_LOG_COMMITS = 30;
const BINGSHUO_USERNAME = 'qinfendebingshuo';
/* ── 公告区标记 ─────────────────────────── */
const MARKERS = {
bingshuoBulletin: ['<!-- BINGSHUO_BULLETIN_START -->', '<!-- BINGSHUO_BULLETIN_END -->'],
bingshuoAlert: ['<!-- BINGSHUO_ALERT_START -->', '<!-- BINGSHUO_ALERT_END -->'],
collabBulletin: ['<!-- COLLABORATOR_BULLETIN_START -->', '<!-- COLLABORATOR_BULLETIN_END -->'],
collabAlert: ['<!-- COLLABORATOR_ALERT_START -->', '<!-- COLLABORATOR_ALERT_END -->'],
};
/* ── 开发者名册 ─────────────────────────── */
const DEV_MAP = {
'DEV-001': { name: '🛠️ 页页', modules: ['backend-integration'] },
'DEV-002': { name: '🐱 肥猫', modules: ['m01-login', 'm03-personality'] },
'DEV-003': { name: '🎨 燕樊', modules: ['m07-dialogue-ui', 'm15-cloud-drive', 'm10-cloud'] },
'DEV-004': { name: '🤖 之之', modules: ['dingtalk-bot'] },
'DEV-005': { name: '🍓 小草莓', modules: ['status-board', 'm12-kanban'] },
'DEV-009': { name: '🌸 花尔', modules: ['m05-user-center'] },
'DEV-010': { name: '🍊 桔子', modules: ['m06-ticket', 'm11-module', 'ticket-system'] },
'DEV-011': { name: '✍️ 匆匆那年', modules: [] },
'DEV-012': { name: '🌟 Awen', modules: ['notification'] },
};
const ACTOR_MAP = {
'qinfendebingshuo': '冰朔',
'copilot-swe-agent[bot]': '铸渊 (Copilot)',
};
/* ── 模块 → 开发者 映射 ─────────────────────────── */
const MODULE_TO_DEV = {};
for (const [devId, info] of Object.entries(DEV_MAP)) {
for (const mod of info.modules) {
MODULE_TO_DEV[mod] = { devId, name: info.name };
}
}
/* ── 模块路径 ─────────────────────────── */
const MODULE_PREFIXES = [
'm01-login', 'm03-personality', 'm05-user-center', 'm06-ticket',
'm07-dialogue-ui', 'm10-cloud', 'm11-module', 'm12-kanban',
'm15-cloud-drive', 'm18-health-check', 'dingtalk-bot',
'backend-integration', 'status-board', 'backend', 'frontend',
'notification', 'ticket-system', 'cloud-drive',
];
/* ── 冰朔系统相关路径(非模块) ─────────────────────────── */
const SYSTEM_PREFIXES = ['scripts', 'docs', '.github', 'persona-brain-db'];
/* ── 工具函数 ─────────────────────────── */
function formatTime(ts) {
if (!ts) return '—';
const d = new Date(ts);
if (isNaN(d.getTime())) return typeof ts === 'string' ? ts.substring(0, Math.min(10, ts.length)) : '—';
const fmt = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
const parts = fmt.formatToParts(d);
const get = (type) => (parts.find(p => p.type === type) || {}).value || '';
return `${get('month')}-${get('day')} ${get('hour')}:${get('minute')}`;
}
function formatTimeShort(ts) {
if (!ts) return '—';
const d = new Date(ts);
if (isNaN(d.getTime())) return '—';
const fmt = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
const parts = fmt.formatToParts(d);
const get = (type) => (parts.find(p => p.type === type) || {}).value || '';
return `${get('hour')}:${get('minute')}`;
}
function todayDateStr() {
const fmt = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric', month: '2-digit', day: '2-digit',
});
const parts = fmt.formatToParts(new Date());
const get = (type) => (parts.find(p => p.type === type) || {}).value || '';
return `${get('year')}-${get('month')}-${get('day')}`;
}
function statusIcon(result) {
if (!result) return '🔵';
const r = result.toLowerCase();
if (r === 'passed' || r === 'success' || r === 'completed') return '✅';
if (r === 'failed' || r === 'failure') return '❌';
if (r === 'cancelled') return '⏹️';
return '🔵';
}
function resolveActor(actor) {
if (!actor) return '系统';
return ACTOR_MAP[actor] || actor;
}
/* ── 读取 memory.json 事件 ─────────────────────────── */
function loadMemoryEvents() {
const events = [];
// 主 memory.json (.github/brain/)
if (fs.existsSync(MEMORY_PATH)) {
const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8'));
for (const ev of (memory.events || [])) {
events.push(ev);
}
}
// persona memory.json (.github/persona-brain/)
if (fs.existsSync(PERSONA_MEMORY_PATH)) {
const pm = JSON.parse(fs.readFileSync(PERSONA_MEMORY_PATH, 'utf8'));
for (const ev of (pm.recent_events || [])) {
events.push(ev);
}
}
return events;
}
/* ── 公告栏缓存(当日追加式) ─────────────────────────── */
function loadBulletinCache() {
const today = todayDateStr();
try {
if (fs.existsSync(BULLETIN_CACHE_PATH)) {
const cache = JSON.parse(fs.readFileSync(BULLETIN_CACHE_PATH, 'utf8'));
if (cache.date === today) {
return cache;
}
}
} catch (err) {
console.log(`⚠️ 公告栏缓存读取失败: ${err.message}`);
}
return { date: today, records: [] };
}
function saveBulletinCache(cache) {
try {
fs.writeFileSync(BULLETIN_CACHE_PATH, JSON.stringify(cache, null, 2), 'utf8');
} catch (err) {
console.log(`⚠️ 公告栏缓存写入失败: ${err.message}`);
}
}
function appendToCache(cache, newEntries) {
const existingKeys = new Set(cache.records.map(r => r.key));
let added = 0;
for (const e of newEntries) {
const key = `${e.actor}|${e.module || '—'}|${e.ts || ''}`;
if (!existingKeys.has(key)) {
existingKeys.add(key);
cache.records.push({
key,
ts: e.ts,
actor: e.actor,
module: e.module || '—',
result: e.result,
icon: e.icon,
sortKey: e.sortKey,
});
added++;
}
}
cache.records.sort((a, b) => a.sortKey - b.sortKey);
return added;
}
/* ── 分类事件为冰朔/合作者 ─────────────────────────── */
function classifyEvents(events) {
const bingshuoEvents = [];
const collabEvents = [];
for (const ev of events) {
const ts = ev.timestamp || ev.date || '';
const actor = resolveActor(ev.actor || ev.by || '');
const type = ev.type || 'event';
// 冰朔系统级事件
if (['daily_check', 'daily_selfcheck', 'psp_inspection', 'system_build',
'brain_upgrade', 'ci_run'].includes(type)) {
let icon, detail;
switch (type) {
case 'daily_check':
icon = statusIcon(ev.result);
detail = `每日巡检 ${ev.result === 'passed' ? '✅ 通过' : '❌ 异常'}`;
break;
case 'daily_selfcheck':
icon = '🔍';
detail = ev.description || '铸渊每日自检完成';
break;
case 'ci_run':
icon = statusIcon(ev.result);
detail = `CI 构建 ${ev.result === 'passed' ? '通过' : ev.result === 'unknown' ? '状态未知' : '失败'}`;
break;
case 'psp_inspection':
icon = ev.description?.includes('通过') ? '✅' : '⚠️';
detail = ev.description || 'PSP 巡检';
break;
case 'system_build':
icon = '🚀';
detail = ev.title || '系统构建';
break;
case 'brain_upgrade':
icon = '🧠';
detail = ev.title || ev.description || '大脑升级';
break;
default:
icon = '📋';
detail = ev.title || ev.description || type;
}
bingshuoEvents.push({
ts, icon, actor, detail,
result: ev.result,
sortKey: new Date(ts || '2000-01-01').getTime(),
});
}
// 模块上传事件 → 合作者
if (type === 'module_upload') {
collabEvents.push({
ts,
icon: statusIcon(ev.result),
actor,
module: ev.module || '未知',
detail: `${ev.module || '未知'} · ${ev.result === 'passed' || ev.result === 'success' ? '上传成功' : '上传失败'}`,
result: ev.result,
sortKey: new Date(ts || '2000-01-01').getTime(),
});
}
}
return { bingshuoEvents, collabEvents };
}
/* ── 从 GitHub Actions API 获取工作流运行 ─────────────────────────── */
async function fetchRecentWorkflowRuns() {
const token = process.env.GITHUB_TOKEN;
const repo = process.env.GITHUB_REPOSITORY;
if (!token || !repo) {
console.log('⚠️ GITHUB_TOKEN 或 GITHUB_REPOSITORY 未设置,跳过 API 查询');
return { bingshuoRuns: [], collabRuns: [] };
}
const url = `https://api.github.com/repos/${repo}/actions/runs?per_page=30&status=completed`;
try {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github.v3+json',
},
});
if (!res.ok) {
console.log(`⚠️ GitHub API 响应 ${res.status},跳过工作流数据`);
return { bingshuoRuns: [], collabRuns: [] };
}
const data = await res.json();
const runs = data.workflow_runs || [];
const bingshuoRuns = [];
const collabRuns = [];
// 冰朔关注的系统工作流
const systemWorkflows = [
'铸渊 · 每日自检',
'铸渊 · PSP 分身巡检',
'铸渊巡检 Agent',
'更新系统公告区',
'铸渊 CD · 自动部署',
'Brain Sync',
'Notion',
'SYSLOG',
];
for (const run of runs) {
const actor = resolveActor(run.actor?.login);
const conclusion = run.conclusion || 'unknown';
const icon = statusIcon(conclusion);
const wfName = run.name || '工作流';
const resultText = conclusion === 'success' ? '成功' : conclusion === 'failure' ? '失败' : conclusion;
const entry = {
ts: run.updated_at || run.created_at,
icon,
actor,
detail: `${wfName} · ${resultText}`,
result: conclusion,
sortKey: new Date(run.updated_at || run.created_at).getTime(),
};
if (systemWorkflows.some(sw => wfName.includes(sw))) {
bingshuoRuns.push(entry);
}
// 模块相关工作流 → 合作者
if (wfName.includes('Module Doc') || wfName.includes('模块')) {
collabRuns.push(entry);
}
}
return { bingshuoRuns, collabRuns };
} catch (err) {
console.log(`⚠️ 获取工作流数据失败: ${err.message}`);
return { bingshuoRuns: [], collabRuns: [] };
}
}
/* ── 从 git 日志检测模块推送 ─────────────────────────── */
function detectRecentModulePushes() {
const { execSync } = require('child_process');
const collabEntries = [];
const bingshuoEntries = [];
try {
const log = execSync(
`git log --oneline --name-only --since="7 days ago" -${MAX_GIT_LOG_COMMITS} 2>/dev/null || true`,
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
);
const lines = log.split('\n');
let currentCommit = null;
const moduleChanges = new Map();
const systemChanges = new Map();
for (const line of lines) {
const commitMatch = line.match(/^([a-f0-9]+)\s+(.*)$/);
if (commitMatch) {
currentCommit = commitMatch[1];
continue;
}
if (!line.trim()) continue;
// 模块变更 → 合作者
for (const prefix of MODULE_PREFIXES) {
if (line.startsWith(prefix + '/') || line === prefix) {
if (!moduleChanges.has(prefix)) {
moduleChanges.set(prefix, currentCommit);
}
break;
}
}
// 系统变更 → 冰朔
for (const prefix of SYSTEM_PREFIXES) {
if (line.startsWith(prefix + '/') || line === prefix) {
if (!systemChanges.has(prefix)) {
systemChanges.set(prefix, currentCommit);
}
break;
}
}
}
// 解析模块变更
for (const [mod, commit] of moduleChanges) {
try {
const info = execSync(
`git log -1 --format="%aI|%an" ${commit} 2>/dev/null || true`,
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
).trim();
const [ts, author] = info.split('|');
const devInfo = MODULE_TO_DEV[mod];
collabEntries.push({
ts,
icon: '📦',
actor: devInfo ? devInfo.name : (resolveActor(author) || author),
module: mod,
detail: `\`${mod}/\` · 更新推送`,
result: 'success',
devId: devInfo?.devId || null,
sortKey: new Date(ts).getTime(),
});
} catch (err) {
console.log(`⚠️ 读取模块 ${mod} 提交信息失败: ${err.message}`);
}
}
// 解析系统变更
for (const [prefix, commit] of systemChanges) {
try {
const info = execSync(
`git log -1 --format="%aI|%an" ${commit} 2>/dev/null || true`,
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
).trim();
const [ts, author] = info.split('|');
bingshuoEntries.push({
ts,
icon: '🔧',
actor: resolveActor(author) || author,
detail: `系统更新: \`${prefix}/\``,
sortKey: new Date(ts).getTime(),
});
} catch (err) {
console.log(`⚠️ 读取系统 ${prefix} 提交信息失败: ${err.message}`);
}
}
} catch (err) {
console.log(`⚠️ Git 日志读取失败: ${err.message}`);
}
return { collabEntries, bingshuoEntries };
}
/* ── 检测需要干预的问题 ─────────────────────────── */
function detectIssues(bingshuoEvents, collabEvents) {
const bingshuoIssues = [];
const collabIssuesByDev = {};
const today = todayDateStr();
// 检查冰朔的系统问题
const todayChecks = bingshuoEvents.filter(e =>
(e.ts || '').startsWith(today) &&
['daily_check', 'ci_run', 'psp_inspection', 'daily_selfcheck'].includes(e.type || '')
);
// 检查是否有失败的事件
for (const ev of bingshuoEvents) {
if (!(ev.ts || '').startsWith(today)) continue;
if (ev.result === 'failed' || ev.result === 'failure') {
bingshuoIssues.push(`${ev.icon} ${ev.detail}`);
}
}
// 检查合作者的模块问题
for (const ev of collabEvents) {
if (ev.result === 'failed' || ev.result === 'failure') {
const devId = ev.devId || '未知';
if (!collabIssuesByDev[devId]) collabIssuesByDev[devId] = [];
collabIssuesByDev[devId].push(`${ev.icon} ${ev.module || '未知模块'}: ${ev.detail}`);
}
}
// 检查模块结构完整性README.md 缺失等)
for (const [devId, info] of Object.entries(DEV_MAP)) {
for (const mod of info.modules) {
const modDir = path.join(__dirname, '..', mod);
if (fs.existsSync(modDir)) {
const readmePath = path.join(modDir, 'README.md');
if (!fs.existsSync(readmePath)) {
if (!collabIssuesByDev[devId]) collabIssuesByDev[devId] = [];
collabIssuesByDev[devId].push(`⚠️ \`${mod}/\` 缺少 README.md`);
}
}
}
}
return { bingshuoIssues, collabIssuesByDev };
}
/* ── 生成冰朔公告表格 ─────────────────────────── */
function buildBingshuoBulletin(entries) {
const sorted = [...entries].sort((a, b) => b.sortKey - a.sortKey);
const seen = new Set();
const unique = [];
for (const e of sorted) {
const key = `${e.detail}|${formatTime(e.ts)}`;
if (!seen.has(key)) { seen.add(key); unique.push(e); }
}
const display = unique.slice(0, MAX_BINGSHUO_ENTRIES);
if (display.length === 0) {
return '| 时间 | 检查项 | 状态 |\n|------|--------|------|\n| 🕐 暂无记录 | — | 等待下次自检 |';
}
const rows = display.map(e =>
`| ${formatTime(e.ts)} | ${e.icon} ${e.detail} | ${e.actor} |`
);
return `| 时间 | 检查项 | 状态 |\n|------|--------|------|\n${rows.join('\n')}`;
}
/* ── 生成冰朔提醒 ─────────────────────────── */
function buildBingshuoAlert(issues) {
const today = todayDateStr();
if (issues.length === 0) {
return `> 🟢 **今日无需冰朔手动干预** · 系统一切正常\n>\n> 🗓️ ${today} · 铸渊自动检测`;
}
let alert = `> 🔴 **需要冰朔手动干预!**\n>\n`;
for (const issue of issues) {
alert += `> - ${issue}\n`;
}
alert += `>\n> 🗓️ ${today} · 铸渊已发送邮件提醒`;
return alert;
}
/* ── 生成合作者公告表格(追加式,从缓存构建) ─────────────────────────── */
function buildCollabBulletin(cacheRecords) {
const today = todayDateStr();
const display = cacheRecords.slice(0, MAX_COLLAB_ENTRIES);
if (display.length === 0) {
return `👥 合作者公告栏(${today}\n\n| 时间 | 合作者 | 模块 | 状态 |\n|------|--------|------|------|\n| 🕐 暂无记录 | — | — | 等待模块推送 |`;
}
const rows = display.map(e =>
`| ${formatTimeShort(e.ts)} | ${e.actor} | \`${e.module || '—'}/\` | ${e.icon} ${e.result === 'success' || e.result === 'passed' ? '上传成功' : e.result === 'failed' || e.result === 'failure' ? '❌ 上传失败' : '已更新'} |`
);
return `👥 合作者公告栏(${today}\n\n| 时间 | 合作者 | 模块 | 状态 |\n|------|--------|------|------|\n${rows.join('\n')}`;
}
/* ── 生成合作者提醒 ─────────────────────────── */
function buildCollabAlert(issuesByDev) {
const today = todayDateStr();
const devIds = Object.keys(issuesByDev);
if (devIds.length === 0) {
return `> 🟢 **今日无需合作者手动干预** · 所有模块状态正常\n>\n> 🗓️ ${today} · 铸渊自动检测`;
}
let alert = `> 🔴 **以下合作者需要手动干预:**\n>\n`;
for (const devId of devIds) {
const devName = DEV_MAP[devId]?.name || devId;
alert += `> **${devName}${devId}**\n`;
for (const issue of issuesByDev[devId]) {
alert += `> - ${issue}\n`;
}
alert += `>\n`;
}
alert += `> 🗓️ ${today} · 铸渊已发送邮件提醒`;
return alert;
}
/* ── 发送邮件通知(需要 SMTP 配置) ─────────────────────────── */
async function sendEmailNotification(to, subject, body) {
// 检查是否有 nodemailer 可用
let nodemailer;
try {
nodemailer = require('nodemailer');
} catch {
console.log('⚠️ nodemailer 未安装,跳过邮件通知');
return false;
}
const host = process.env.SMTP_HOST;
const port = parseInt(process.env.SMTP_PORT || '465', 10);
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_PASS;
if (!host || !user || !pass) {
console.log('⚠️ SMTP 配置不完整,跳过邮件通知');
return false;
}
try {
const transporter = nodemailer.createTransport({
host, port, secure: port === 465,
auth: { user, pass },
});
await transporter.sendMail({
from: `"铸渊 · 光湖系统" <${user}>`,
to,
subject,
html: body,
});
console.log(`📧 邮件已发送至 ${to}`);
return true;
} catch (err) {
console.log(`⚠️ 邮件发送失败: ${err.message}`);
return false;
}
}
/* ── 获取用户邮箱(通过 GitHub API ─────────────────────────── */
async function fetchUserEmail(username) {
if (!username || !process.env.GITHUB_TOKEN) return null;
try {
const res = await fetch(`https://api.github.com/users/${username}`, {
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
},
});
if (!res.ok) return null;
const data = await res.json();
return data.email || null;
} catch {
return null;
}
}
/* ── 发送提醒邮件 ─────────────────────────── */
async function sendAlertEmails(bingshuoIssues, collabIssuesByDev) {
const today = todayDateStr();
// 冰朔邮件
if (bingshuoIssues.length > 0) {
const bingshuoEmail = process.env.BINGSHUO_EMAIL
|| await fetchUserEmail(BINGSHUO_USERNAME)
|| null;
if (bingshuoEmail) {
const issueList = bingshuoIssues.map(i => `<li>${i}</li>`).join('');
await sendEmailNotification(
bingshuoEmail,
`🔴 光湖系统提醒 · ${today} · 需要冰朔干预`,
`<h2>🔴 铸渊自动提醒</h2>
<p>以下事项需要冰朔手动处理:</p>
<ul>${issueList}</ul>
<p>—— 铸渊ICE-GL-ZY001· 光湖系统守护人格</p>`
);
}
}
// 合作者邮件
let collaborators = {};
if (fs.existsSync(COLLABORATORS_PATH)) {
try {
collaborators = JSON.parse(fs.readFileSync(COLLABORATORS_PATH, 'utf8'));
} catch {
console.log('⚠️ collaborators.json 读取失败');
}
}
for (const [devId, issues] of Object.entries(collabIssuesByDev)) {
const devName = DEV_MAP[devId]?.name || devId;
// 查找 GitHub 用户名
let githubUsername = null;
if (Array.isArray(collaborators)) {
const collab = collaborators.find(c => c.dev_id === devId);
githubUsername = collab?.github_username || null;
}
if (githubUsername) {
const email = await fetchUserEmail(githubUsername);
if (email) {
const issueList = issues.map(i => `<li>${i}</li>`).join('');
await sendEmailNotification(
email,
`🔴 光湖系统提醒 · ${today} · ${devName} 需要处理`,
`<h2>🔴 铸渊自动提醒</h2>
<p>亲爱的 ${devName}${devId}),以下事项需要你手动处理:</p>
<ul>${issueList}</ul>
<p>请登录 GitHub 仓库查看详情。</p>
<p>—— 铸渊ICE-GL-ZY001· 光湖系统守护人格</p>`
);
}
}
}
}
/* ── 更新 README.md 指定区域 ─────────────────────────── */
function updateSection(readme, startMarker, endMarker, content) {
const startIdx = readme.indexOf(startMarker);
const endIdx = readme.indexOf(endMarker);
if (startIdx === -1 || endIdx === -1) {
console.log(`⚠️ 未找到标记 ${startMarker},跳过`);
return readme;
}
const before = readme.substring(0, startIdx + startMarker.length);
const after = readme.substring(endIdx);
return `${before}\n${content}\n${after}`;
}
/* ── README 骨架模板(首页被覆盖时的兜底重建) ─────────────────────────── */
function buildReadmeSkeleton() {
return `<div align="center">
# 🌊 光湖纪元 · HoloLake Era
### 第五代人工智能语言人格高级智能平台
**AGE-5 · Artificial General Evolution · 数字地球操作系统**
\`guanghulab.com\`
</div>
---
## 📊 Zone 2 · System Dashboard (区域2 · 系统仪表盘)
<!-- DASHBOARD_START -->
<!-- ARCH_SUMMARY_START -->
> 🦅 **天眼自动汇总** · 每周六 20:00 CST 自动更新 · 等待首次扫描
<!-- ARCH_SUMMARY_END -->
<!-- SKYEYE-STATUS-BEGIN -->
<!-- SKYEYE-STATUS-END -->
### 🚨 系统告警
<!-- BINGSHUO_ALERT_START -->
> 🟢 **当前无需人类干预** · 系统一切正常
<!-- BINGSHUO_ALERT_END -->
<!-- DASHBOARD_END -->
---
## 🌍 Zone 3 · Federation Status (区域3 · 子仓库联邦状态)
<!-- FEDERATION_START -->
<!-- FEDERATION_END -->
---
## 📢 Zone 4 · System Announcements (区域4 · 系统公告)
<!-- BULLETIN_START -->
<!-- BULLETIN_END -->
---
## 🤝 Zone 5 · Symbiosis Dynamics (区域5 · 共生动态)
<!-- SYMBIOSIS_START -->
<table>
<tr>
<td width="50%" valign="top">
#### 👥 人类开发者活动
<!-- COLLABORATOR_BULLETIN_START -->
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
| 🕐 暂无记录 | — | — | 等待模块推送 |
<!-- COLLABORATOR_BULLETIN_END -->
<!-- COLLABORATOR_ALERT_START -->
> 🟢 **今日无需合作者手动干预** · 所有模块状态正常
<!-- COLLABORATOR_ALERT_END -->
</td>
<td width="50%" valign="top">
#### 🤖 AI 人格体活动
<!-- BINGSHUO_BULLETIN_START -->
| 时间 | 检查项 | 状态 |
|------|--------|------|
| 🕐 暂无记录 | — | 等待下次自检 |
<!-- BINGSHUO_BULLETIN_END -->
</td>
</tr>
</table>
<!-- SYMBIOSIS_END -->
---
## 🚀 Zone 8 · Quick Entry (区域8 · 快速入口)
<div align="center">
<!-- WEEKLY_GREETING_START -->
<!-- WEEKLY_GREETING_END -->
**光湖 HoloLake** · 由冰朔创建 · 铸渊守护 · 🏛️ 国作登字-2026-A-00037559
</div>
`;
}
/* ── 主流程 ─────────────────────────── */
async function main() {
console.log('🌊 光湖系统公告区更新脚本 v2.0 启动...\n');
// 1. 收集事件
const memoryEvents = loadMemoryEvents();
console.log(`📋 memory 事件: ${memoryEvents.length}`);
const { bingshuoEvents, collabEvents } = classifyEvents(memoryEvents);
console.log(`🧊 冰朔事件: ${bingshuoEvents.length}`);
console.log(`👥 合作者事件: ${collabEvents.length}`);
// 2. GitHub API 工作流
const { bingshuoRuns, collabRuns } = await fetchRecentWorkflowRuns();
console.log(`🔄 冰朔工作流: ${bingshuoRuns.length}`);
console.log(`🔄 合作者工作流: ${collabRuns.length}`);
// 3. Git 日志
const { collabEntries, bingshuoEntries } = detectRecentModulePushes();
console.log(`📦 模块推送: ${collabEntries.length}`);
console.log(`🔧 系统更新: ${bingshuoEntries.length}`);
// 4. 合并事件
const allBingshuo = [...bingshuoEvents, ...bingshuoRuns, ...bingshuoEntries];
const allCollab = [...collabEvents, ...collabRuns, ...collabEntries];
console.log(`\n📊 冰朔合计: ${allBingshuo.length}`);
console.log(`📊 合作者合计: ${allCollab.length}\n`);
// 5. 合作者公告栏:追加式缓存
const cache = loadBulletinCache();
const addedCount = appendToCache(cache, allCollab);
saveBulletinCache(cache);
console.log(`📋 公告栏缓存: ${cache.records.length} 条记录(本次新增 ${addedCount} 条)`);
// 6. 检测需要干预的问题
const { bingshuoIssues, collabIssuesByDev } = detectIssues(allBingshuo, allCollab);
console.log(`🔴 冰朔待处理: ${bingshuoIssues.length}`);
console.log(`🔴 合作者待处理: ${Object.keys(collabIssuesByDev).length}\n`);
// 7. 生成各区域内容
const bingshuoBulletin = buildBingshuoBulletin(allBingshuo);
const bingshuoAlert = buildBingshuoAlert(bingshuoIssues);
const collabBulletin = buildCollabBulletin(cache.records);
const collabAlert = buildCollabAlert(collabIssuesByDev);
// 8. 更新 README.md含自愈检测
if (!fs.existsSync(README_PATH)) {
console.error('❌ README.md 不存在');
process.exit(1);
}
let readme = fs.readFileSync(README_PATH, 'utf8');
// 🛡️ 首页自愈检测:如果 README 被覆盖(缺少公告栏标记),从 git 历史恢复
const hasMarkers = Object.values(MARKERS).every(
([start, end]) => readme.includes(start) && readme.includes(end)
);
if (!hasMarkers) {
console.log('⚠️ README.md 首页被覆盖!公告栏标记丢失,尝试从 git 历史恢复...');
const { execSync } = require('child_process');
try {
const commits = execSync(
'git log --all --oneline -- README.md',
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
).trim().split('\n');
let restored = false;
for (const line of commits) {
const sha = line.split(' ')[0];
try {
const candidate = execSync(
`git show ${sha}:README.md`,
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
);
const candidateHasMarkers = Object.values(MARKERS).every(
([start, end]) => candidate.includes(start) && candidate.includes(end)
);
if (candidateHasMarkers) {
readme = candidate;
restored = true;
console.log(`✅ 从 git 历史 (${sha}) 恢复了 README.md 首页结构`);
break;
}
} catch { /* commit inaccessible or corrupt, skip */ }
}
if (!restored) {
console.log('⚠️ 无法从 git 历史恢复,使用内置骨架模板重建首页');
readme = buildReadmeSkeleton();
}
} catch (err) {
console.log(`⚠️ git 历史查询失败 (${err.message}),使用内置骨架模板重建首页`);
readme = buildReadmeSkeleton();
}
}
readme = updateSection(readme, MARKERS.bingshuoBulletin[0], MARKERS.bingshuoBulletin[1], bingshuoBulletin);
readme = updateSection(readme, MARKERS.bingshuoAlert[0], MARKERS.bingshuoAlert[1], bingshuoAlert);
readme = updateSection(readme, MARKERS.collabBulletin[0], MARKERS.collabBulletin[1], collabBulletin);
readme = updateSection(readme, MARKERS.collabAlert[0], MARKERS.collabAlert[1], collabAlert);
const original = fs.readFileSync(README_PATH, 'utf8');
if (readme === original) {
console.log(' 公告区内容无变化,跳过写入');
} else {
fs.writeFileSync(README_PATH, readme, 'utf8');
console.log('✅ README.md 公告区已更新');
}
// 9. 发送邮件(如有需要)
if (bingshuoIssues.length > 0 || Object.keys(collabIssuesByDev).length > 0) {
await sendAlertEmails(bingshuoIssues, collabIssuesByDev);
}
console.log('\n📢 公告区更新完成!');
}
main().catch(err => {
console.error('❌ 脚本执行失败:', err);
process.exit(1);
});