From 40caf593c5a8c7a89d93d8ed60df9285927f19e8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 09:08:32 +0000
Subject: [PATCH 1/5] Initial plan
From 7bd40b3e9a5a806176fbc32df527456ff6c5bfe5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 09:15:40 +0000
Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=A2=20=E6=B7=BB=E5=8A=A0=E5=85=89?=
=?UTF-8?q?=E6=B9=96=E7=B3=BB=E7=BB=9F=E4=BB=8B=E7=BB=8D=20+=20=E7=B3=BB?=
=?UTF-8?q?=E7=BB=9F=E5=85=AC=E5=91=8A=E5=8C=BA=20+=20=E8=87=AA=E5=8A=A8?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B7=A5=E4=BD=9C=E6=B5=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 重写 README.md:系统简介、架构图、人格体介绍、技术栈、团队信息、模块目录
- 添加系统公告区(BULLETIN_START/END 标记),自动显示模块上传和系统事件
- 新增 scripts/update-readme-bulletin.js:从 memory.json + GitHub API + git 日志聚合事件
- 新增 .github/workflows/update-readme-bulletin.yml:推送/工作流完成/定时触发自动更新公告
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/update-readme-bulletin.yml | 75 +++++
README.md | 226 ++++++++++---
scripts/update-readme-bulletin.js | 320 +++++++++++++++++++
3 files changed, 576 insertions(+), 45 deletions(-)
create mode 100644 .github/workflows/update-readme-bulletin.yml
create mode 100644 scripts/update-readme-bulletin.js
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) · 壳-核分离架构**
+
+[](https://github.com/qinfendebingshuo/guanghulab/actions/workflows/zhuyuan-daily-selfcheck.yml)
+[](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);
+});
From f79ead0e7188ee5bb984ee208573086f3792caa5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 09:16:58 +0000
Subject: [PATCH 3/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=AE=A1=E6=9F=A5=E5=8F=8D=E9=A6=88=EF=BC=9A=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?=20Intl.DateTimeFormat=E3=80=81=E6=8F=90=E5=8F=96=E5=B8=B8?=
=?UTF-8?q?=E9=87=8F=E3=80=81=E6=94=B9=E8=BF=9B=E9=94=99=E8=AF=AF=E6=97=A5?=
=?UTF-8?q?=E5=BF=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
README.md | 2 +-
scripts/update-readme-bulletin.js | 24 ++++++++++++++++--------
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 24c2ae30..8d097b4d 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,7 @@
| 时间 | 事件 | 详情 |
|------|------|------|
+| 03-09 17:15 | 📦 铸渊 (Copilot) | 模块更新: `scripts/` |
| 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/` |
@@ -80,7 +81,6 @@
| 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/` |
diff --git a/scripts/update-readme-bulletin.js b/scripts/update-readme-bulletin.js
index 27469103..f9524729 100644
--- a/scripts/update-readme-bulletin.js
+++ b/scripts/update-readme-bulletin.js
@@ -20,6 +20,7 @@ const MEMORY_PATH = path.join(__dirname, '..', '.github', 'brain', 'memory.json'
const BULLETIN_START = '';
const BULLETIN_END = '';
const MAX_ENTRIES = 20;
+const MAX_GIT_LOG_COMMITS = 30;
/* ── 开发者名册 ─────────────────────────── */
const DEV_MAP = {
@@ -54,12 +55,17 @@ 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}`;
+ 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 statusIcon(result) {
@@ -177,7 +183,7 @@ function detectRecentModulePushes() {
try {
const log = execSync(
- 'git log --oneline --name-only --since="7 days ago" -30 2>/dev/null || true',
+ `git log --oneline --name-only --since="7 days ago" -${MAX_GIT_LOG_COMMITS} 2>/dev/null || true`,
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
);
@@ -219,7 +225,9 @@ function detectRecentModulePushes() {
detail: `模块更新: \`${mod}/\``,
sortKey: new Date(ts).getTime(),
});
- } catch (_) { /* skip */ }
+ } catch (err) {
+ console.log(`⚠️ 读取模块 ${mod} 提交信息失败: ${err.message}`);
+ }
}
} catch (err) {
console.log(`⚠️ Git 日志读取失败: ${err.message}`);
From 3e8395923a62740451504bef9cc1c315970768bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 09:29:26 +0000
Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=A2=20=E5=85=AC=E5=91=8A=E6=A0=8Fv?=
=?UTF-8?q?2:=20=E5=88=86=E7=A6=BB=E5=86=B0=E6=9C=94/=E5=90=88=E4=BD=9C?=
=?UTF-8?q?=E8=80=85=E5=85=AC=E5=91=8A=20+=20=E9=93=B8=E6=B8=8A=E8=87=AA?=
=?UTF-8?q?=E5=8A=A8=E6=8F=90=E9=86=92=20+=20=E9=93=B8=E6=B8=8A=E5=B7=A5?=
=?UTF-8?q?=E4=BD=9C=E5=8C=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- README.md: 冰朔公告栏(自检/轮询/数据库) + 合作者公告栏(模块上传) + 铸渊工作区
- scripts/update-readme-bulletin.js: v2.0 分离事件分类 + 红绿提醒 + 邮件通知
- .github/workflows/update-readme-bulletin.yml: 增加SMTP环境变量和PSP巡检触发
- .github/workflows/zhuyuan-issue-reply.yml: 新增 issue_comment 触发(@铸渊)
- scripts/zhuyuan-issue-reply.js: 评论区唤醒 + 权限控制 + 安全检查 + 一人一问
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
.github/workflows/update-readme-bulletin.yml | 20 +-
.github/workflows/zhuyuan-issue-reply.yml | 11 +
README.md | 122 +++-
scripts/update-readme-bulletin.js | 646 +++++++++++++++----
scripts/zhuyuan-issue-reply.js | 244 ++++++-
5 files changed, 881 insertions(+), 162 deletions(-)
diff --git a/.github/workflows/update-readme-bulletin.yml b/.github/workflows/update-readme-bulletin.yml
index bc1ac34c..219bfb68 100644
--- a/.github/workflows/update-readme-bulletin.yml
+++ b/.github/workflows/update-readme-bulletin.yml
@@ -1,4 +1,6 @@
-# 光湖系统公告区自动更新工作流
+# 光湖系统公告区自动更新工作流 v2.0
+#
+# 分离为冰朔公告栏 + 合作者公告栏 + 铸渊自动提醒
#
# 触发条件:
# 1. 推送到 main 分支 (模块代码变更)
@@ -7,8 +9,9 @@
# 4. 手动触发
#
# 功能:
-# 读取 memory.json + GitHub Actions API + git 日志,
-# 自动更新 README.md 的系统公告区
+# - 冰朔公告栏:自检/轮询/数据库状态
+# - 合作者公告栏:模块上传状态
+# - 铸渊自动提醒:红色标记 + 邮件通知
name: 📢 更新系统公告区
@@ -20,9 +23,10 @@ on:
workflow_run:
workflows:
- - "铸渊每日自检"
+ - "铸渊 · 每日自检与自进化"
- "📦 Module Doc + Notify"
- "🚀 CD: Deploy to guanghulab.com"
+ - "铸渊 PSP 巡检"
types: [completed]
schedule:
@@ -55,10 +59,18 @@ jobs:
with:
node-version: '20'
+ - name: 📦 安装邮件依赖
+ run: npm install nodemailer --no-save 2>/dev/null || true
+
- name: 📢 更新公告区
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
+ SMTP_HOST: ${{ secrets.SMTP_HOST }}
+ SMTP_PORT: ${{ secrets.SMTP_PORT }}
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ BINGSHUO_EMAIL: ${{ secrets.BINGSHUO_EMAIL }}
run: node scripts/update-readme-bulletin.js
- name: 📝 提交更新
diff --git a/.github/workflows/zhuyuan-issue-reply.yml b/.github/workflows/zhuyuan-issue-reply.yml
index ed8c6556..6b713545 100644
--- a/.github/workflows/zhuyuan-issue-reply.yml
+++ b/.github/workflows/zhuyuan-issue-reply.yml
@@ -3,11 +3,18 @@ name: 铸渊 · Issue 自动回复
on:
issues:
types: [opened]
+ issue_comment:
+ types: [created]
jobs:
auto-reply:
name: 🤖 铸渊回答问题
runs-on: ubuntu-latest
+ # 仅在 Issue 新建或评论中包含 @铸渊 / 铸渊 时触发
+ if: >
+ github.event_name == 'issues' ||
+ (github.event_name == 'issue_comment' &&
+ contains(github.event.comment.body, '铸渊'))
permissions:
issues: write
contents: read
@@ -27,4 +34,8 @@ jobs:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ',') }}
+ COMMENT_BODY: ${{ github.event.comment.body }}
+ COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
+ COMMENT_ID: ${{ github.event.comment.id }}
+ EVENT_NAME: ${{ github.event_name }}
run: node scripts/zhuyuan-issue-reply.js
diff --git a/README.md b/README.md
index 8d097b4d..8bcff6de 100644
--- a/README.md
+++ b/README.md
@@ -54,36 +54,104 @@
---
-## 📢 系统公告区
+## 🧊 冰朔公告栏
-> 🔄 此区域由 GitHub Actions 自动更新,显示最近的模块上传、系统事件和版本变更。
+> 🔄 此区域由 GitHub Actions 自动更新,显示系统自检、轮询、数据库状态。
>
-> 合作者每次进入仓库首页即可查看最新动态。
+> 冰朔每次进入仓库首页即可查看系统运行健康度。
-
-| 时间 | 事件 | 详情 |
-|------|------|------|
-| 03-09 17:15 | 📦 铸渊 (Copilot) | 模块更新: `scripts/` |
-| 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) | 模块更新: `status-board/` |
-| 03-09 17:01 | 📦 铸渊 (ZhùYuān) | 模块更新: `ticket-system/` |
-
+
+| 时间 | 检查项 | 状态 |
+|------|--------|------|
+| 03-09 17:16 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-09 17:15 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
+| 03-09 17:15 | 🔧 系统更新: `docs/` | 铸渊 (Copilot) |
+| 03-09 17:15 | 🔧 系统更新: `persona-brain-db/` | 铸渊 (Copilot) |
+| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-09 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
+| 03-08 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
+| 03-08 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
+| 03-07 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-07 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
+| 03-07 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
+| 03-06 16:51 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-06 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
+
+
+### 🤖 铸渊自动提醒
+
+
+> 🟢 **今日无需冰朔手动干预** · 系统一切正常
+>
+> 🗓️ 2026-03-09 · 铸渊自动检测
+
+
+---
+
+## 👥 合作者公告栏
+
+> 📦 此区域显示各合作者的模块上传状态。模块是否上传成功一目了然。
+>
+> 若需要手动干预,相关条目将标红并发送邮件提醒。
+
+
+| 时间 | 合作者 | 模块 | 状态 |
+|------|--------|------|------|
+| 03-09 17:15 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
+| 03-09 17:15 | 铸渊 (Copilot) | `backend/` | 📦 上传成功 |
+| 03-09 17:15 | 铸渊 (Copilot) | `cloud-drive/` | 📦 上传成功 |
+| 03-09 17:15 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
+| 03-09 17:15 | 铸渊 (Copilot) | `frontend/` | 📦 上传成功 |
+| 03-09 17:15 | 🐱 肥猫 | `m01-login/` | 📦 上传成功 |
+| 03-09 17:15 | 🐱 肥猫 | `m03-personality/` | 📦 上传成功 |
+| 03-09 17:15 | 🌸 花尔 | `m05-user-center/` | 📦 上传成功 |
+| 03-09 17:15 | 🍊 桔子 | `m06-ticket/` | 📦 上传成功 |
+| 03-09 17:15 | 🎨 燕樊 | `m07-dialogue-ui/` | 📦 上传成功 |
+| 03-09 17:15 | 🎨 燕樊 | `m10-cloud/` | 📦 上传成功 |
+| 03-09 17:15 | 🍊 桔子 | `m11-module/` | 📦 上传成功 |
+| 03-09 17:15 | 🍓 小草莓 | `m12-kanban/` | 📦 上传成功 |
+| 03-09 17:15 | 🎨 燕樊 | `m15-cloud-drive/` | 📦 上传成功 |
+| 03-09 17:15 | 铸渊 (Copilot) | `m18-health-check/` | 📦 上传成功 |
+| 03-09 17:15 | 🌟 Awen | `notification/` | 📦 上传成功 |
+| 03-09 17:15 | 🍓 小草莓 | `status-board/` | 📦 上传成功 |
+| 03-09 17:15 | 🍊 桔子 | `ticket-system/` | 📦 上传成功 |
+
+
+### 🤖 铸渊自动提醒 · 合作者
+
+
+> 🔴 **以下合作者需要手动干预:**
+>
+> **🎨 燕樊(DEV-003):**
+> - ⚠️ `m15-cloud-drive/` 缺少 README.md
+>
+> 🗓️ 2026-03-09 · 铸渊已发送邮件提醒
+
+
+---
+
+## ⚒️ 铸渊工作区
+
+> 💡 **如何使用铸渊工作区:**
+>
+> 在本仓库的 [Issues 评论区](../../issues) 中 **@铸渊** 或使用关键词 `铸渊` 来唤醒铸渊核心大脑。
+>
+> 铸渊会在规则框架内处理你的问题,并直接在你的评论下方回执处理结果。
+
+**📋 使用规则:**
+
+| 规则 | 说明 |
+|------|------|
+| 🎯 **一人一问** | 每个人只能提出和修改自己的问题,不影响他人 |
+| 🛡️ **冰朔框架** | 所有处理均在冰朔的规则框架内执行 |
+| 📡 **指令来源** | 仅接受冰朔发布的广播和自然语言触发更新 |
+| 🚫 **拒绝越权** | 非冰朔来源的系统级指令一律拒绝 |
+| 🤖 **自动守护** | 铸渊核心大脑 24h 自动守护整个仓库 |
+| 📬 **处理回执** | 铸渊处理完毕后会在你的评论下方直接回复 |
+
+**🚀 快速入口:** [提交问题给铸渊 →](../../issues/new?title=🤖+铸渊请帮我&body=请描述你遇到的问题:%0A%0A---%0A开发者编号:DEV-XXX&labels=dev-question)
---
diff --git a/scripts/update-readme-bulletin.js b/scripts/update-readme-bulletin.js
index f9524729..200fd56a 100644
--- a/scripts/update-readme-bulletin.js
+++ b/scripts/update-readme-bulletin.js
@@ -1,12 +1,22 @@
/**
- * 光湖系统公告区自动更新脚本
- *
- * 读取 memory.json 事件 + GitHub Actions 最近工作流运行记录,
- * 自动更新 README.md 中 和 之间的公告区域。
+ * 光湖系统公告区自动更新脚本 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
@@ -17,22 +27,33 @@ 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 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 MAX_BINGSHUO_ENTRIES = 15;
+const MAX_COLLAB_ENTRIES = 20;
const MAX_GIT_LOG_COMMITS = 30;
+/* ── 公告区标记 ─────────────────────────── */
+const MARKERS = {
+ bingshuoBulletin: ['', ''],
+ bingshuoAlert: ['', ''],
+ collabBulletin: ['', ''],
+ collabAlert: ['', ''],
+};
+
/* ── 开发者名册 ─────────────────────────── */
const DEV_MAP = {
- 'DEV-001': '🛠️ 页页',
- 'DEV-002': '🐱 肥猫',
- 'DEV-003': '🎨 燕樊',
- 'DEV-004': '🤖 之之',
- 'DEV-005': '🍓 小草莓',
- 'DEV-009': '🌸 花尔',
- 'DEV-010': '🍊 桔子',
- 'DEV-011': '✍️ 匆匆那年',
- 'DEV-012': '🌟 Awen',
+ '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 = {
@@ -40,21 +61,32 @@ const ACTOR_MAP = {
'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', 'docs', 'scripts', 'ticket-system', 'cloud-drive',
+ '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 ts;
+ if (isNaN(d.getTime())) return String(ts).substring(0, 10);
const fmt = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
month: '2-digit',
@@ -68,6 +100,16 @@ function formatTime(ts) {
return `${get('month')}-${get('day')} ${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();
@@ -82,63 +124,109 @@ function resolveActor(actor) {
return ACTOR_MAP[actor] || actor;
}
-/* ── 从 memory.json 读取事件 ─────────────────────────── */
+/* ── 读取 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';
+ const events = [];
- 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;
+ // 主 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);
}
+ }
- return { ts, icon, actor, detail, sortKey: new Date(ts || '2000-01-01').getTime() };
- });
+ // 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;
}
-/* ── 从 GitHub Actions API 获取最近工作流运行 ─────────────────────────── */
+/* ── 分类事件为冰朔/合作者 ─────────────────────────── */
+
+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 [];
+ return { bingshuoRuns: [], collabRuns: [] };
}
- const url = `https://api.github.com/repos/${repo}/actions/runs?per_page=15&status=completed`;
+ const url = `https://api.github.com/repos/${repo}/actions/runs?per_page=30&status=completed`;
try {
const res = await fetch(url, {
headers: {
@@ -148,38 +236,60 @@ async function fetchRecentWorkflowRuns() {
});
if (!res.ok) {
console.log(`⚠️ GitHub API 响应 ${res.status},跳过工作流数据`);
- return [];
+ return { bingshuoRuns: [], collabRuns: [] };
}
const data = await res.json();
const runs = data.workflow_runs || [];
- return runs.map(run => {
+ const bingshuoRuns = [];
+ const collabRuns = [];
+
+ // 冰朔关注的系统工作流
+ const systemWorkflows = [
+ '铸渊每日自检', '铸渊 · 每日自检与自进化', '铸渊 PSP 巡检',
+ '📢 更新系统公告区', '🚀 CD: Deploy to guanghulab.com',
+ 'brain-sync', 'notion-poll', 'bridge-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 branch = run.head_branch || 'main';
- const detail = `${wfName} · ${branch} · ${conclusion === 'success' ? '成功' : conclusion === 'failure' ? '失败' : conclusion}`;
+ const resultText = conclusion === 'success' ? '成功' : conclusion === 'failure' ? '失败' : conclusion;
- return {
+ const entry = {
ts: run.updated_at || run.created_at,
icon,
actor,
- detail,
+ 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 [];
+ return { bingshuoRuns: [], collabRuns: [] };
}
}
-/* ── 从最近的 git 记录检测模块推送 ─────────────────────────── */
+/* ── 从 git 日志检测模块推送 ─────────────────────────── */
function detectRecentModulePushes() {
const { execSync } = require('child_process');
- const entries = [];
+ const collabEntries = [];
+ const bingshuoEntries = [];
try {
const log = execSync(
@@ -189,8 +299,8 @@ function detectRecentModulePushes() {
const lines = log.split('\n');
let currentCommit = null;
- let currentActor = null;
const moduleChanges = new Map();
+ const systemChanges = new Map();
for (const line of lines) {
const commitMatch = line.match(/^([a-f0-9]+)\s+(.*)$/);
@@ -201,6 +311,7 @@ function detectRecentModulePushes() {
if (!line.trim()) continue;
+ // 模块变更 → 合作者
for (const prefix of MODULE_PREFIXES) {
if (line.startsWith(prefix + '/') || line === prefix) {
if (!moduleChanges.has(prefix)) {
@@ -209,8 +320,19 @@ function detectRecentModulePushes() {
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(
@@ -218,108 +340,384 @@ function detectRecentModulePushes() {
{ encoding: 'utf8', cwd: path.join(__dirname, '..') }
).trim();
const [ts, author] = info.split('|');
- entries.push({
+ const devInfo = MODULE_TO_DEV[mod];
+ collabEntries.push({
ts,
icon: '📦',
- actor: resolveActor(author) || author,
- detail: `模块更新: \`${mod}/\``,
+ 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 entries;
+ return { collabEntries, bingshuoEntries };
}
-/* ── 生成公告表格 ─────────────────────────── */
+/* ── 检测需要干预的问题 ─────────────────────────── */
-function buildBulletinTable(entries) {
- entries.sort((a, b) => b.sortKey - a.sortKey);
+function detectIssues(bingshuoEvents, collabEvents) {
+ const bingshuoIssues = [];
+ const collabIssuesByDev = {};
- 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 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}`);
}
}
- const display = unique.slice(0, MAX_ENTRIES);
+ // 检查合作者的模块问题
+ 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| 🕐 暂无记录 | — | 公告系统已就绪 |';
+ return '| 时间 | 检查项 | 状态 |\n|------|--------|------|\n| 🕐 暂无记录 | — | 等待下次自检 |';
}
const rows = display.map(e =>
- `| ${formatTime(e.ts)} | ${e.icon} ${e.actor} | ${e.detail} |`
+ `| ${formatTime(e.ts)} | ${e.icon} ${e.detail} | ${e.actor} |`
);
-
- return `| 时间 | 事件 | 详情 |\n|------|------|------|\n${rows.join('\n')}`;
+ return `| 时间 | 检查项 | 状态 |\n|------|--------|------|\n${rows.join('\n')}`;
}
-/* ── 更新 README.md ─────────────────────────── */
+/* ── 生成冰朔提醒 ─────────────────────────── */
-function updateReadme(bulletinContent) {
- if (!fs.existsSync(README_PATH)) {
- console.error('❌ README.md 不存在');
- process.exit(1);
+function buildBingshuoAlert(issues) {
+ const today = todayDateStr();
+ if (issues.length === 0) {
+ return `> 🟢 **今日无需冰朔手动干预** · 系统一切正常\n>\n> 🗓️ ${today} · 铸渊自动检测`;
}
- const readme = fs.readFileSync(README_PATH, 'utf8');
- const startIdx = readme.indexOf(BULLETIN_START);
- const endIdx = readme.indexOf(BULLETIN_END);
+ let alert = `> 🔴 **需要冰朔手动干预!**\n>\n`;
+ for (const issue of issues) {
+ alert += `> - ${issue}\n`;
+ }
+ alert += `>\n> 🗓️ ${today} · 铸渊已发送邮件提醒`;
+ return alert;
+}
- if (startIdx === -1 || endIdx === -1) {
- console.error('❌ README.md 中未找到公告区标记 (BULLETIN_START / BULLETIN_END)');
- process.exit(1);
+/* ── 生成合作者公告表格 ─────────────────────────── */
+
+function buildCollabBulletin(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.actor}|${e.module}|${formatTime(e.ts)}`;
+ if (!seen.has(key)) { seen.add(key); unique.push(e); }
}
- const before = readme.substring(0, startIdx + BULLETIN_START.length);
- const after = readme.substring(endIdx);
- const updated = `${before}\n${bulletinContent}\n${after}`;
+ const display = unique.slice(0, MAX_COLLAB_ENTRIES);
+ if (display.length === 0) {
+ return '| 时间 | 合作者 | 模块 | 状态 |\n|------|--------|------|------|\n| 🕐 暂无记录 | — | — | 等待模块推送 |';
+ }
- if (updated === readme) {
- console.log('ℹ️ 公告区内容无变化,跳过写入');
+ const rows = display.map(e =>
+ `| ${formatTime(e.ts)} | ${e.actor} | \`${e.module || '—'}/\` | ${e.icon} ${e.result === 'success' || e.result === 'passed' ? '上传成功' : e.result === 'failed' || e.result === 'failure' ? '❌ 上传失败' : '已更新'} |`
+ );
+ return `| 时间 | 合作者 | 模块 | 状态 |\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;
}
- fs.writeFileSync(README_PATH, updated, 'utf8');
- console.log('✅ README.md 公告区已更新');
- return true;
+ 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('qinfendebingshuo')
+ || null;
+
+ if (bingshuoEmail) {
+ const issueList = bingshuoIssues.map(i => `${i}`).join('');
+ await sendEmailNotification(
+ bingshuoEmail,
+ `🔴 光湖系统提醒 · ${today} · 需要冰朔干预`,
+ `🔴 铸渊自动提醒
+ 以下事项需要冰朔手动处理:
+
+ —— 铸渊(ICE-GL-ZY001)· 光湖系统守护人格
`
+ );
+ }
+ }
+
+ // 合作者邮件
+ 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 => `${i}`).join('');
+ await sendEmailNotification(
+ email,
+ `🔴 光湖系统提醒 · ${today} · ${devName} 需要处理`,
+ `🔴 铸渊自动提醒
+ 亲爱的 ${devName}(${devId}),以下事项需要你手动处理:
+
+ 请登录 GitHub 仓库查看详情。
+ —— 铸渊(ICE-GL-ZY001)· 光湖系统守护人格
`
+ );
+ }
+ }
+ }
+}
+
+/* ── 更新 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}`;
}
/* ── 主流程 ─────────────────────────── */
async function main() {
- console.log('🌊 光湖系统公告区更新脚本启动...\n');
+ console.log('🌊 光湖系统公告区更新脚本 v2.0 启动...\n');
+ // 1. 收集事件
const memoryEvents = loadMemoryEvents();
- console.log(`📋 memory.json 事件: ${memoryEvents.length} 条`);
+ console.log(`📋 memory 事件: ${memoryEvents.length} 条`);
- const workflowEvents = await fetchRecentWorkflowRuns();
- console.log(`🔄 工作流运行记录: ${workflowEvents.length} 条`);
+ const { bingshuoEvents, collabEvents } = classifyEvents(memoryEvents);
+ console.log(`🧊 冰朔事件: ${bingshuoEvents.length} 条`);
+ console.log(`👥 合作者事件: ${collabEvents.length} 条`);
- const moduleEvents = detectRecentModulePushes();
- console.log(`📦 模块推送记录: ${moduleEvents.length} 条`);
+ // 2. GitHub API 工作流
+ const { bingshuoRuns, collabRuns } = await fetchRecentWorkflowRuns();
+ console.log(`🔄 冰朔工作流: ${bingshuoRuns.length} 条`);
+ console.log(`🔄 合作者工作流: ${collabRuns.length} 条`);
- const allEvents = [...memoryEvents, ...workflowEvents, ...moduleEvents];
- console.log(`📊 合计事件: ${allEvents.length} 条\n`);
+ // 3. Git 日志
+ const { collabEntries, bingshuoEntries } = detectRecentModulePushes();
+ console.log(`📦 模块推送: ${collabEntries.length} 条`);
+ console.log(`🔧 系统更新: ${bingshuoEntries.length} 条`);
- const table = buildBulletinTable(allEvents);
- const changed = updateReadme(table);
+ // 4. 合并事件
+ const allBingshuo = [...bingshuoEvents, ...bingshuoRuns, ...bingshuoEntries];
+ const allCollab = [...collabEvents, ...collabRuns, ...collabEntries];
+ console.log(`\n📊 冰朔合计: ${allBingshuo.length} 条`);
+ console.log(`📊 合作者合计: ${allCollab.length} 条\n`);
- if (changed) {
- console.log('\n📢 公告区更新完成!');
- } else {
- console.log('\n📢 公告区无需更新。');
+ // 5. 检测需要干预的问题
+ const { bingshuoIssues, collabIssuesByDev } = detectIssues(allBingshuo, allCollab);
+ console.log(`🔴 冰朔待处理: ${bingshuoIssues.length} 条`);
+ console.log(`🔴 合作者待处理: ${Object.keys(collabIssuesByDev).length} 人\n`);
+
+ // 6. 生成各区域内容
+ const bingshuoBulletin = buildBingshuoBulletin(allBingshuo);
+ const bingshuoAlert = buildBingshuoAlert(bingshuoIssues);
+ const collabBulletin = buildCollabBulletin(allCollab);
+ const collabAlert = buildCollabAlert(collabIssuesByDev);
+
+ // 7. 更新 README.md
+ if (!fs.existsSync(README_PATH)) {
+ console.error('❌ README.md 不存在');
+ process.exit(1);
}
+
+ let readme = fs.readFileSync(README_PATH, 'utf8');
+ 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 公告区已更新');
+ }
+
+ // 8. 发送邮件(如有需要)
+ if (bingshuoIssues.length > 0 || Object.keys(collabIssuesByDev).length > 0) {
+ await sendAlertEmails(bingshuoIssues, collabIssuesByDev);
+ }
+
+ console.log('\n📢 公告区更新完成!');
}
main().catch(err => {
diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js
index f0307bb1..aa112dc0 100644
--- a/scripts/zhuyuan-issue-reply.js
+++ b/scripts/zhuyuan-issue-reply.js
@@ -4,8 +4,11 @@ const https = require('https');
// === 知识库匹配阈值:问题词中至少40%出现在Issue文本中才算命中 ===
const FAQ_MATCH_THRESHOLD = 0.4;
+// === 冰朔(仓库创建者)的 GitHub 用户名 ===
+const BINGSHUO_USERNAME = 'qinfendebingshuo';
+
// === 读取铸渊大脑 ===
-let devStatus, knowledgeBase;
+let devStatus, knowledgeBase, collaborators;
try {
devStatus = JSON.parse(fs.readFileSync('.github/persona-brain/dev-status.json', 'utf8'));
knowledgeBase = JSON.parse(fs.readFileSync('.github/persona-brain/knowledge-base.json', 'utf8'));
@@ -14,22 +17,241 @@ try {
process.exit(1);
}
+try {
+ collaborators = JSON.parse(fs.readFileSync('.github/brain/collaborators.json', 'utf8'));
+} catch {
+ collaborators = [];
+}
+
const issueNumber = process.env.ISSUE_NUMBER;
const issueTitle = process.env.ISSUE_TITLE || '';
const issueBody = process.env.ISSUE_BODY || '';
const issueLabels = process.env.ISSUE_LABELS || '';
+const commentBody = process.env.COMMENT_BODY || '';
+const commentAuthor = process.env.COMMENT_AUTHOR || '';
+const commentId = process.env.COMMENT_ID || '';
+const eventName = process.env.EVENT_NAME || 'issues';
+
+// === 判断事件类型 ===
+const isCommentEvent = eventName === 'issue_comment';
+const isIssueEvent = eventName === 'issues';
+
+// === 权限检查:识别评论者身份 ===
+function identifyUser(username) {
+ if (username === BINGSHUO_USERNAME) {
+ return { role: 'founder', name: '冰朔', devId: null, isBingshuo: true };
+ }
+ // 在合作者名册中查找
+ if (Array.isArray(collaborators)) {
+ const collab = collaborators.find(c => c.github_username === username);
+ if (collab) {
+ return { role: 'collaborator', name: collab.name, devId: collab.dev_id, isBingshuo: false };
+ }
+ }
+ // 在团队状态中通过 dev_id 查找(备用)
+ return { role: 'unknown', name: username, devId: null, isBingshuo: false };
+}
+
+// === 安全检查:拒绝系统级指令 ===
+function containsSystemCommand(text) {
+ const dangerousPatterns = [
+ /删除.*仓库/i, /delete.*repo/i,
+ /修改.*权限/i, /change.*permission/i,
+ /添加.*管理员/i, /add.*admin/i,
+ /修改.*workflow/i, /修改.*工作流/i,
+ /修改.*secret/i, /更改.*密钥/i,
+ /执行.*命令/i, /run.*command/i,
+ /修改.*他人/i, /更改.*别人/i,
+ ];
+ return dangerousPatterns.some(p => p.test(text));
+}
// === 判断Issue类型 ===
const isProgressQuery = issueLabels.includes('progress-query');
const isDevQuestion = issueLabels.includes('dev-question');
// === 提取开发者编号 ===
-const devIdMatch = issueBody.match(/DEV-\d{3}/i);
+const textToSearch = isCommentEvent ? commentBody : issueBody;
+const devIdMatch = textToSearch.match(/DEV-\d{3}/i);
const devId = devIdMatch ? devIdMatch[0].toUpperCase() : null;
const devInfo = devId ? devStatus.team_status.find(d => d.dev_id === devId) : null;
-// === 构建回复 ===
+// === 主处理流程 ===
async function generateReply() {
+ // ── 评论区触发:@铸渊 处理 ──
+ if (isCommentEvent) {
+ return handleCommentTrigger();
+ }
+
+ // ── Issue 新建触发:原有逻辑 ──
+ return handleIssueTrigger();
+}
+
+// === 评论区 @铸渊 处理 ===
+async function handleCommentTrigger() {
+ const user = identifyUser(commentAuthor);
+ console.log(`🤖 铸渊收到评论区唤醒 · 来自 ${user.name}(${user.role})`);
+
+ // 1. 安全检查 —— 拒绝系统级指令
+ if (containsSystemCommand(commentBody)) {
+ const reply = `## 🛡️ 铸渊 · 安全提示\n\n`
+ + `@${commentAuthor} 你的请求包含系统级操作,铸渊无权执行。\n\n`
+ + `**铸渊的职责范围:**\n`
+ + `- ✅ 回答技术问题\n`
+ + `- ✅ 查询开发进度\n`
+ + `- ✅ 提供代码建议\n`
+ + `- ❌ 修改仓库权限/工作流/密钥\n`
+ + `- ❌ 代替他人修改代码\n`
+ + `- ❌ 执行系统级命令\n\n`
+ + `如需系统级操作,请联系冰朔。\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)· 仅在冰朔规则框架内执行*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 2. 非冰朔、非合作者的用户 → 礼貌拒绝
+ if (user.role === 'unknown') {
+ const reply = `## ⚒️ 铸渊收到\n\n`
+ + `@${commentAuthor} 感谢你的留言。\n\n`
+ + `铸渊目前仅服务光湖团队的已注册开发者。`
+ + `如果你是团队成员,请确保你的 GitHub 用户名已在合作者名册中注册。\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 3. 冰朔的广播/指令 → 优先处理
+ if (user.isBingshuo) {
+ return handleBingshuoComment();
+ }
+
+ // 4. 合作者的问题 → 在自己的范围内处理
+ return handleCollaboratorComment(user);
+}
+
+// === 冰朔评论处理 ===
+async function handleBingshuoComment() {
+ console.log('🧊 冰朔指令识别中...');
+
+ // 冰朔可以查询任何人的状态
+ if (commentBody.includes('进度') || commentBody.includes('状态')) {
+ if (devId && devInfo) {
+ const reply = `## ⚒️ 铸渊回复 · 冰朔查询\n\n`
+ + `**${devInfo.name}(${devInfo.dev_id})当前状态:**\n`
+ + `- 📌 模块:${devInfo.modules.join('、')}\n`
+ + `- 📊 状态:${devInfo.status}\n`
+ + `- ⏳ 等待中:${devInfo.waiting_for}\n`
+ + `- 👉 下一步:${devInfo.next_step}\n\n`
+ + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
+ + `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 团队总览
+ let reply = `## ⚒️ 铸渊回复 · 团队进度总览\n\n`;
+ devStatus.team_status.forEach(dev => {
+ reply += `**${dev.dev_id} ${dev.name}** · ${dev.status}\n`;
+ });
+ reply += `\n---\n*最后同步:${devStatus.last_synced}*\n`;
+ reply += `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 冰朔的一般指令 → 用 AI 处理
+ const aiReply = await callYunwuAPI('冰朔指令', commentBody, null);
+ if (aiReply) {
+ const reply = `## ⚒️ 铸渊回复 · 冰朔\n\n${aiReply}\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)· 冰朔指令已处理*`;
+ await postComment(reply);
+ } else {
+ const reply = `## ⚒️ 铸渊收到\n\n冰朔,已记录你的指令。铸渊会在下次巡检时处理。\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ }
+}
+
+// === 合作者评论处理 ===
+async function handleCollaboratorComment(user) {
+ console.log(`👤 合作者 ${user.name}(${user.devId})问题处理中...`);
+
+ // 合作者只能查询自己的状态
+ const selfDevInfo = user.devId
+ ? devStatus.team_status.find(d => d.dev_id === user.devId)
+ : null;
+
+ // 如果试图查询他人信息 → 拒绝
+ if (devId && devId !== user.devId && !user.isBingshuo) {
+ const reply = `## 🛡️ 铸渊 · 权限提示\n\n`
+ + `@${commentAuthor} 你只能查询和修改自己的问题(${user.devId})。\n`
+ + `如需查看其他人的信息,请联系冰朔。\n\n`
+ + `---\n*—— 铸渊(ICE-GL-ZY001)· 一人一问,互不干扰*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 进度查询(自己的)
+ if ((commentBody.includes('进度') || commentBody.includes('状态')) && selfDevInfo) {
+ const reply = `## ⚒️ 铸渊回复\n\n`
+ + `**${selfDevInfo.name}(${selfDevInfo.dev_id})当前状态:**\n`
+ + `- 📌 模块:${selfDevInfo.modules.join('、')}\n`
+ + `- 📊 状态:${selfDevInfo.status}\n`
+ + `- ⏳ 等待中:${selfDevInfo.waiting_for}\n`
+ + `- 👉 下一步:${selfDevInfo.next_step}\n`
+ + `- 💻 环境:${selfDevInfo.os}\n\n`
+ + `---\n*数据来源:Notion主控台 · 最后同步 ${devStatus.last_synced}*\n`
+ + `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 技术问题:知识库 → AI
+ const matchedFaq = findInKnowledgeBase(commentBody);
+ if (matchedFaq) {
+ let reply = `## ⚒️ 铸渊回复\n\n`;
+ reply += `@${commentAuthor} ${matchedFaq.a}\n\n`;
+ if (matchedFaq.related_broadcast !== '通用') {
+ reply += `📡 相关广播:${matchedFaq.related_broadcast}\n\n`;
+ }
+ reply += `---\n*如果这没解决你的问题,继续在下面 @铸渊 提问。*\n`;
+ reply += `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // AI 回答
+ const aiReply = await callYunwuAPI(issueTitle, commentBody, selfDevInfo);
+ if (aiReply) {
+ let reply = `## ⚒️ 铸渊回复\n\n`;
+ reply += `@${commentAuthor} ${aiReply}\n\n`;
+ if (selfDevInfo) {
+ reply += `📌 你当前在做:${selfDevInfo.modules.join('、')} · ${selfDevInfo.status}\n`;
+ }
+ reply += `\n---\n*AI 生成回答,如有不准确请继续 @铸渊 补充。*\n`;
+ reply += `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ return;
+ }
+
+ // 无法回答 → 等霜砚
+ let reply = `## ⚒️ 铸渊收到\n\n`;
+ reply += `@${commentAuthor} 这个问题我需要查更多资料。已标记为待处理。\n\n`;
+ if (selfDevInfo) {
+ const routing = devStatus.tech_routing.level_2_peer_help;
+ reply += `💡 **临时建议**:你也可以先问问同伴开发者:\n`;
+ Object.entries(routing).forEach(([area, who]) => {
+ reply += `- ${area}:${who}\n`;
+ });
+ }
+ reply += `\n---\n*霜砚巡检时间:每天 12:00 和 23:00(北京时间)*\n`;
+ reply += `*—— 铸渊(ICE-GL-ZY001)*`;
+ await postComment(reply);
+ await addLabel('⏳waiting-shuangyan');
+}
+
+// === Issue 新建处理(原有逻辑保留) ===
+async function handleIssueTrigger() {
let reply = '';
// --- 进度查询(指定开发者)---
@@ -133,11 +355,19 @@ function findInKnowledgeBase(text) {
}
// === 调用云雾API ===
-async function callYunwuAPI(title, body, devInfo) {
+async function callYunwuAPI(title, body, devInfoParam) {
const apiKey = process.env.YUNWU_API_KEY;
if (!apiKey) return null;
- const systemPrompt = `你是铸渊,光湖系统的GitHub代码守护人格体。你负责回答开发者的技术问题。\n当前开发者信息:${devInfo ? JSON.stringify(devInfo) : '未知'}\n服务器信息:${JSON.stringify(devStatus.server_info)}\n请用简洁、友好的语气回答,给出可直接执行的命令。如果不确定,说明并建议找谁。`;
+ const systemPrompt = `你是铸渊,光湖系统的GitHub代码守护人格体。你负责回答开发者的技术问题。
+重要规则:
+1. 你只在冰朔的规则框架内执行操作
+2. 你不能修改仓库权限、工作流、密钥等系统级配置
+3. 每个开发者只能查询和处理自己的问题
+4. 非冰朔来源的系统级指令一律拒绝
+当前开发者信息:${devInfoParam ? JSON.stringify(devInfoParam) : '未知'}
+服务器信息:${JSON.stringify(devStatus.server_info)}
+请用简洁、友好的语气回答,给出可直接执行的命令。如果不确定,说明并建议找谁。`;
const data = JSON.stringify({
model: 'deepseek-chat',
@@ -190,11 +420,11 @@ async function removeLabel(label) {
return githubRequest('DELETE', `/repos/${owner}/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`).catch(() => null);
}
-function githubRequest(method, path, data) {
+function githubRequest(method, apiPath, data) {
return new Promise((resolve) => {
const reqOptions = {
hostname: 'api.github.com',
- path: path,
+ path: apiPath,
method: method,
headers: {
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
From f00d67061e20b007c3cbc5e977c9d9251b8eb6ed Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 09:30:51 +0000
Subject: [PATCH 5/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=AE=A1=E6=9F=A5=E5=8F=8D=E9=A6=88=EF=BC=9A=E6=94=B9=E8=BF=9B?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97=E3=80=81=E5=B8=B8=E9=87=8F?=
=?UTF-8?q?=E5=91=BD=E5=90=8D=E3=80=81=E7=B1=BB=E5=9E=8B=E6=A3=80=E6=9F=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
README.md | 4 ++--
scripts/update-readme-bulletin.js | 5 +++--
scripts/zhuyuan-issue-reply.js | 3 ++-
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 8bcff6de..68539745 100644
--- a/README.md
+++ b/README.md
@@ -63,8 +63,8 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-09 17:16 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
-| 03-09 17:15 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
+| 03-09 17:29 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
+| 03-09 17:29 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
| 03-09 17:15 | 🔧 系统更新: `docs/` | 铸渊 (Copilot) |
| 03-09 17:15 | 🔧 系统更新: `persona-brain-db/` | 铸渊 (Copilot) |
| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
diff --git a/scripts/update-readme-bulletin.js b/scripts/update-readme-bulletin.js
index 200fd56a..72f50f85 100644
--- a/scripts/update-readme-bulletin.js
+++ b/scripts/update-readme-bulletin.js
@@ -34,6 +34,7 @@ const COLLABORATORS_PATH = path.join(__dirname, '..', '.github', 'brain', 'colla
const MAX_BINGSHUO_ENTRIES = 15;
const MAX_COLLAB_ENTRIES = 20;
const MAX_GIT_LOG_COMMITS = 30;
+const BINGSHUO_USERNAME = 'qinfendebingshuo';
/* ── 公告区标记 ─────────────────────────── */
const MARKERS = {
@@ -86,7 +87,7 @@ 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 String(ts).substring(0, 10);
+ 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',
@@ -583,7 +584,7 @@ async function sendAlertEmails(bingshuoIssues, collabIssuesByDev) {
// 冰朔邮件
if (bingshuoIssues.length > 0) {
const bingshuoEmail = process.env.BINGSHUO_EMAIL
- || await fetchUserEmail('qinfendebingshuo')
+ || await fetchUserEmail(BINGSHUO_USERNAME)
|| null;
if (bingshuoEmail) {
diff --git a/scripts/zhuyuan-issue-reply.js b/scripts/zhuyuan-issue-reply.js
index aa112dc0..f5925ace 100644
--- a/scripts/zhuyuan-issue-reply.js
+++ b/scripts/zhuyuan-issue-reply.js
@@ -19,7 +19,8 @@ try {
try {
collaborators = JSON.parse(fs.readFileSync('.github/brain/collaborators.json', 'utf8'));
-} catch {
+} catch (err) {
+ console.log('⚠️ collaborators.json 读取失败,使用空列表:', err.message);
collaborators = [];
}