diff --git a/.github/workflows/update-readme-bulletin.yml b/.github/workflows/update-readme-bulletin.yml new file mode 100644 index 00000000..bc1ac34c --- /dev/null +++ b/.github/workflows/update-readme-bulletin.yml @@ -0,0 +1,75 @@ +# 光湖系统公告区自动更新工作流 +# +# 触发条件: +# 1. 推送到 main 分支 (模块代码变更) +# 2. 其他工作流完成后 (CI/部署/巡检结果) +# 3. 每日定时更新 (北京时间 09:00 / 21:00) +# 4. 手动触发 +# +# 功能: +# 读取 memory.json + GitHub Actions API + git 日志, +# 自动更新 README.md 的系统公告区 + +name: 📢 更新系统公告区 + +on: + push: + branches: [main] + paths-ignore: + - 'README.md' # 避免自身更新触发循环 + + workflow_run: + workflows: + - "铸渊每日自检" + - "📦 Module Doc + Notify" + - "🚀 CD: Deploy to guanghulab.com" + types: [completed] + + schedule: + # 北京时间 09:00 (UTC 01:00) + - cron: '0 1 * * *' + # 北京时间 21:00 (UTC 13:00) + - cron: '0 13 * * *' + + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: update-bulletin + cancel-in-progress: true + +jobs: + update-bulletin: + runs-on: ubuntu-latest + steps: + - name: 📥 检出代码 + uses: actions/checkout@v4 + with: + fetch-depth: 50 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: 🟢 设置 Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: 📢 更新公告区 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: node scripts/update-readme-bulletin.js + + - name: 📝 提交更新 + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add README.md + if git diff --cached --quiet; then + echo "📢 公告区无变化,跳过提交" + else + git commit -m "📢 自动更新系统公告区 [skip ci]" + git push + echo "✅ 公告区已更新并推送" + fi diff --git a/README.md b/README.md index bda8f34e..24c2ae30 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,183 @@ -# HoloLake 光湖系统 +
-## 模块目录 +# 🌊 HoloLake 光湖系统 -- app/ -- backend-integration/ -- backend/ -- broadcasts-outbox/ -- chat-bubble/ -- cloud-drive/ -- coldstart/ -- cost-control/ -- dashboard/ -- dev-nodes/ -- dingtalk-bot/ -- docs/ -- frontend/ -- guanghulab-main/ -- help-center/ -- m01-login/ -- m03-personality/ -- m05-user-center/ -- m06-ticket/ -- m07-dialogue-ui/ -- m10-cloud/ -- m11-module/ -- m12-kanban/ -- m15-cloud-drive/ -- m18-health-check/ -- multi-persona/ -- notification/ -- notion-push/ -- persona-selector/ -- portal/ -- public/ -- reports/ -- scripts/ -- settings/ -- signal-log/ -- src/ -- status-board/ -- style-system/ -- syslog-inbox/ -- syslog-processed/ -- tests/ -- ticket-system/ -- user-center/ +**人格语言操作系统(AGE OS) · 壳-核分离架构** + +[![Daily Check](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml) +[![Deploy](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml/badge.svg)](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/deploy-to-server.yml) + +`guanghulab.com` · Node.js 20 + Express + PM2 + Nginx + +
+ +--- + +## 📖 系统简介 + +**光湖(HoloLake)** 是一个基于 **人格语言操作系统(AGE OS)** 的智能协作平台,采用 **壳-核分离** 设计理念: + +- **壳(Shell)**:前端交互层,包括对话界面、用户中心、工单系统、云盘等模块 +- **核(Core)**:后端智能层,包括人格引擎、广播分发、信号处理、Notion 桥接等 + +### 🏛️ 核心架构 + +``` +┌─────────────────────────────────────────────────┐ +│ 光湖 HoloLake │ +├──────────────┬──────────────────────────────────┤ +│ 壳 Shell │ 核 Core │ +│ │ │ +│ 🖥️ 对话 UI │ 🧠 铸渊 (Zhùyuān) 代码守护人格 │ +│ 👤 用户中心 │ 📡 广播分发系统 │ +│ 🎫 工单系统 │ 🔔 信号处理 + Notion 桥接 │ +│ ☁️ 云盘 │ 🔄 CI/CD 自动化流水线 │ +│ 📊 状态看板 │ 📋 模块自检 + 文档生成 │ +└──────────────┴──────────────────────────────────┘ +``` + +### 🤖 智能人格体 + +| 人格体 | 角色 | 职责 | +|--------|------|------| +| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复 | +| **冰朔 Bīng Shuò** | 系统创建者 | 系统架构设计、核心决策、广播发布 | +| **霜砚 Shuāng Yàn** | 人格导师 | 人格调校、风格管理、联觉语言系统 | + +### 🔧 技术栈 + +- **运行时**:Node.js 20 + Next.js 15 + React 19 +- **后端**:Express + PM2 进程管理 +- **数据库**:SQLite (better-sqlite3) + Notion 数据桥接 +- **部署**:Nginx 反向代理 + GitHub Actions CI/CD +- **自动化**:23+ GitHub Actions 工作流 + +--- + +## 📢 系统公告区 + +> 🔄 此区域由 GitHub Actions 自动更新,显示最近的模块上传、系统事件和版本变更。 +> +> 合作者每次进入仓库首页即可查看最新动态。 + + +| 时间 | 事件 | 详情 | +|------|------|------| +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `backend-integration/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `backend/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `cloud-drive/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `dingtalk-bot/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `docs/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `frontend/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m01-login/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m03-personality/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m05-user-center/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m06-ticket/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m07-dialogue-ui/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m10-cloud/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m11-module/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m12-kanban/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m15-cloud-drive/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `m18-health-check/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `notification/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `scripts/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `status-board/` | +| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `ticket-system/` | + + +--- + +## 👥 开发团队 + +| 编号 | 成员 | 角色 | 负责模块 | +|------|------|------|----------| +| DEV-001 | 🛠️ 页页 | 后端中间件 | backend-integration | +| DEV-002 | 🐱 肥猫 | 前端 + 副操 | m01-login, m03-personality | +| DEV-003 | 🎨 燕樊 | 对话 UI | m07-dialogue-ui, m15-cloud-drive | +| DEV-004 | 🤖 之之 | 钉钉机器人 | dingtalk-bot | +| DEV-005 | 🍓 小草莓 | 状态看板 | status-board, m12-kanban | +| DEV-009 | 🌸 花尔 | 用户中心 | m05-user-center | +| DEV-010 | 🍊 桔子 | 前端主力 | m06-ticket, ticket-system | +| DEV-011 | ✍️ 匆匆那年 | 写作工坊 | — | +| DEV-012 | 🌟 Awen | 通知中心 | notification | + +--- + +## 📦 模块目录 + +
+点击展开完整模块列表(47+ 模块) + +### 核心功能模块 +| 模块 | 说明 | +|------|------| +| `m01-login/` | 登录系统 | +| `m03-personality/` | 人格系统 | +| `m05-user-center/` | 用户中心 | +| `m06-ticket/` | 工单系统 | +| `m07-dialogue-ui/` | 对话界面 | +| `m10-cloud/` | 云服务 | +| `m11-module/` | 模块管理 | +| `m12-kanban/` | 看板系统 | +| `m15-cloud-drive/` | 云盘 | +| `m18-health-check/` | 健康检查 | + +### 基础设施 +| 模块 | 说明 | +|------|------| +| `backend/` | 后端服务 | +| `backend-integration/` | 后端集成中间件 | +| `frontend/` | 前端主体 | +| `status-board/` | 状态看板 | +| `dingtalk-bot/` | 钉钉机器人 | +| `notification/` | 通知系统 | + +### 系统支撑 +| 模块 | 说明 | +|------|------| +| `scripts/` | 自动化脚本 | +| `tests/` | 契约测试 + 冒烟测试 | +| `docs/` | 文档中心 | +| `broadcasts-outbox/` | 广播发件箱 | +| `syslog-inbox/` | 系统日志收件箱 | +| `persona-selector/` | 人格选择器 | +| `signal-log/` | 信号日志 | + +
+ +--- + +## 🚀 快速开始 + +```bash +# 安装依赖 +npm install + +# 本地开发 +npm run dev + +# 运行契约测试 +npm run test:contract + +# 运行冒烟测试 +npm run test:smoke +``` + +--- + +## 📊 系统状态 + +- **每日巡检**:铸渊 PSP 自动巡检(每日 08:30 UTC) +- **模块自检**:推送时自动检测模块完整性(README.md + package.json + src/) +- **部署流水线**:推送到 main → 验证 → 同步 → 重启 → Notion 通知 +- **广播系统**:冰朔广播 → 分发至各开发者 outbox → 邮件/钉钉通知 + +--- + +
+ +**光湖 HoloLake** · 由冰朔创建 · 铸渊守护 + +*壳-核分离 · 人格共生 · 协作共建* + +
diff --git a/scripts/update-readme-bulletin.js b/scripts/update-readme-bulletin.js new file mode 100644 index 00000000..27469103 --- /dev/null +++ b/scripts/update-readme-bulletin.js @@ -0,0 +1,320 @@ +/** + * 光湖系统公告区自动更新脚本 + * + * 读取 memory.json 事件 + GitHub Actions 最近工作流运行记录, + * 自动更新 README.md 中 之间的公告区域。 + * + * 环境变量: + * GITHUB_TOKEN - GitHub API token (Actions 自动提供) + * GITHUB_REPOSITORY - owner/repo (Actions 自动提供) + * + * 用法: + * 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 BULLETIN_START = ''; +const BULLETIN_END = ''; +const MAX_ENTRIES = 20; + +/* ── 开发者名册 ─────────────────────────── */ +const DEV_MAP = { + 'DEV-001': '🛠️ 页页', + 'DEV-002': '🐱 肥猫', + 'DEV-003': '🎨 燕樊', + 'DEV-004': '🤖 之之', + 'DEV-005': '🍓 小草莓', + 'DEV-009': '🌸 花尔', + 'DEV-010': '🍊 桔子', + 'DEV-011': '✍️ 匆匆那年', + 'DEV-012': '🌟 Awen', +}; + +const ACTOR_MAP = { + 'qinfendebingshuo': '冰朔', + 'copilot-swe-agent[bot]': '铸渊 (Copilot)', +}; + +/* ── 模块路径映射 ─────────────────────────── */ +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', 'docs', 'scripts', 'ticket-system', 'cloud-drive', +]; + +/* ── 工具函数 ─────────────────────────── */ + +function formatTime(ts) { + if (!ts) return '—'; + const d = new Date(ts); + if (isNaN(d.getTime())) return ts; + const bj = new Date(d.getTime() + 8 * 3600000); + const mm = String(bj.getUTCMonth() + 1).padStart(2, '0'); + const dd = String(bj.getUTCDate()).padStart(2, '0'); + const hh = String(bj.getUTCHours()).padStart(2, '0'); + const mi = String(bj.getUTCMinutes()).padStart(2, '0'); + return `${mm}-${dd} ${hh}:${mi}`; +} + +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() { + if (!fs.existsSync(MEMORY_PATH)) return []; + const memory = JSON.parse(fs.readFileSync(MEMORY_PATH, 'utf8')); + const events = memory.events || []; + return events.map(ev => { + const ts = ev.timestamp || ev.date || ''; + const actor = resolveActor(ev.actor || ev.by); + const type = ev.type || 'event'; + + let icon, detail; + switch (type) { + case 'daily_check': + icon = statusIcon(ev.result); + detail = `铸渊每日巡检 ${ev.result === 'passed' ? '通过' : '异常'}`; + 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; + case 'module_upload': + icon = statusIcon(ev.result); + detail = `模块上传: ${ev.module || '未知模块'}`; + break; + default: + icon = '📋'; + detail = ev.title || ev.description || type; + } + + return { ts, icon, actor, detail, sortKey: new Date(ts || '2000-01-01').getTime() }; + }); +} + +/* ── 从 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 []; + } + + const url = `https://api.github.com/repos/${repo}/actions/runs?per_page=15&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 []; + } + const data = await res.json(); + const runs = data.workflow_runs || []; + + return runs.map(run => { + const actor = resolveActor(run.actor?.login); + const conclusion = run.conclusion || 'unknown'; + const icon = statusIcon(conclusion); + const wfName = run.name || '工作流'; + const branch = run.head_branch || 'main'; + const detail = `${wfName} · ${branch} · ${conclusion === 'success' ? '成功' : conclusion === 'failure' ? '失败' : conclusion}`; + + return { + ts: run.updated_at || run.created_at, + icon, + actor, + detail, + sortKey: new Date(run.updated_at || run.created_at).getTime(), + }; + }); + } catch (err) { + console.log(`⚠️ 获取工作流数据失败: ${err.message}`); + return []; + } +} + +/* ── 从最近的 git 记录检测模块推送 ─────────────────────────── */ + +function detectRecentModulePushes() { + const { execSync } = require('child_process'); + const entries = []; + + try { + const log = execSync( + 'git log --oneline --name-only --since="7 days ago" -30 2>/dev/null || true', + { encoding: 'utf8', cwd: path.join(__dirname, '..') } + ); + + const lines = log.split('\n'); + let currentCommit = null; + let currentActor = null; + const moduleChanges = 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 [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('|'); + entries.push({ + ts, + icon: '📦', + actor: resolveActor(author) || author, + detail: `模块更新: \`${mod}/\``, + sortKey: new Date(ts).getTime(), + }); + } catch (_) { /* skip */ } + } + } catch (err) { + console.log(`⚠️ Git 日志读取失败: ${err.message}`); + } + + return entries; +} + +/* ── 生成公告表格 ─────────────────────────── */ + +function buildBulletinTable(entries) { + entries.sort((a, b) => b.sortKey - a.sortKey); + + const seen = new Set(); + const unique = []; + for (const e of entries) { + const key = `${e.detail}|${formatTime(e.ts)}`; + if (!seen.has(key)) { + seen.add(key); + unique.push(e); + } + } + + const display = unique.slice(0, MAX_ENTRIES); + + if (display.length === 0) { + return '| 时间 | 事件 | 详情 |\n|------|------|------|\n| 🕐 暂无记录 | — | 公告系统已就绪 |'; + } + + const rows = display.map(e => + `| ${formatTime(e.ts)} | ${e.icon} ${e.actor} | ${e.detail} |` + ); + + return `| 时间 | 事件 | 详情 |\n|------|------|------|\n${rows.join('\n')}`; +} + +/* ── 更新 README.md ─────────────────────────── */ + +function updateReadme(bulletinContent) { + if (!fs.existsSync(README_PATH)) { + console.error('❌ README.md 不存在'); + process.exit(1); + } + + const readme = fs.readFileSync(README_PATH, 'utf8'); + const startIdx = readme.indexOf(BULLETIN_START); + const endIdx = readme.indexOf(BULLETIN_END); + + if (startIdx === -1 || endIdx === -1) { + console.error('❌ README.md 中未找到公告区标记 (BULLETIN_START / BULLETIN_END)'); + process.exit(1); + } + + const before = readme.substring(0, startIdx + BULLETIN_START.length); + const after = readme.substring(endIdx); + const updated = `${before}\n${bulletinContent}\n${after}`; + + if (updated === readme) { + console.log('ℹ️ 公告区内容无变化,跳过写入'); + return false; + } + + fs.writeFileSync(README_PATH, updated, 'utf8'); + console.log('✅ README.md 公告区已更新'); + return true; +} + +/* ── 主流程 ─────────────────────────── */ + +async function main() { + console.log('🌊 光湖系统公告区更新脚本启动...\n'); + + const memoryEvents = loadMemoryEvents(); + console.log(`📋 memory.json 事件: ${memoryEvents.length} 条`); + + const workflowEvents = await fetchRecentWorkflowRuns(); + console.log(`🔄 工作流运行记录: ${workflowEvents.length} 条`); + + const moduleEvents = detectRecentModulePushes(); + console.log(`📦 模块推送记录: ${moduleEvents.length} 条`); + + const allEvents = [...memoryEvents, ...workflowEvents, ...moduleEvents]; + console.log(`📊 合计事件: ${allEvents.length} 条\n`); + + const table = buildBulletinTable(allEvents); + const changed = updateReadme(table); + + if (changed) { + console.log('\n📢 公告区更新完成!'); + } else { + console.log('\n📢 公告区无需更新。'); + } +} + +main().catch(err => { + console.error('❌ 脚本执行失败:', err); + process.exit(1); +});