Merge pull request #5 from qinfendebingshuo/copilot/create-auto-trigger-mechanism

feat: HoloLake Era 模块自动文档 + 合作者收讫通知系统
This commit is contained in:
qinfendebingshuo 2026-03-06 22:55:35 +08:00 committed by GitHub
commit 160b4ddf71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1047 additions and 0 deletions

62
.github/brain/collaborators.json vendored Normal file
View File

@ -0,0 +1,62 @@
{
"description": "HoloLake 合作者 GitHub 用户名映射表 · 铸渊通知系统使用",
"note": "请将每位合作者的 github_username 填写为他们的真实 GitHub 用户名(即 @xxx 中的 xxx",
"collaborators": [
{
"dev_id": "DEV-001",
"name": "页页",
"github_username": "",
"emoji": "🖥️",
"modules": ["backend-integration"]
},
{
"dev_id": "DEV-002",
"name": "肥猫",
"github_username": "",
"emoji": "🦁",
"modules": ["m01-login", "m03-personality"]
},
{
"dev_id": "DEV-003",
"name": "燕樊",
"github_username": "",
"emoji": "🌸",
"modules": ["m07-dialogue-ui", "m10-cloud", "m15-cloud-drive"]
},
{
"dev_id": "DEV-004",
"name": "之之",
"github_username": "",
"emoji": "🤖",
"modules": ["dingtalk-bot"]
},
{
"dev_id": "DEV-005",
"name": "小草莓",
"github_username": "",
"emoji": "🍓",
"modules": ["m12-kanban", "status-board"]
},
{
"dev_id": "DEV-009",
"name": "花尔",
"github_username": "",
"emoji": "🌺",
"modules": ["m05-user-center"]
},
{
"dev_id": "DEV-010",
"name": "桔子",
"github_username": "",
"emoji": "🍊",
"modules": ["m06-ticket", "m11-module"]
},
{
"dev_id": "DEV-011",
"name": "匆匆那年",
"github_username": "",
"emoji": "🌙",
"modules": []
}
]
}

View File

@ -0,0 +1,95 @@
name: 铸渊 · HoloLake Era 模块文档自动生成
on:
push:
branches: [main]
paths:
- 'm01-login/**'
- 'm03-personality/**'
- 'm05-user-center/**'
- 'm06-ticket/**'
- 'm07-dialogue-ui/**'
- 'm10-cloud/**'
- 'm11-module/**'
- 'm12-kanban/**'
- 'm15-cloud-drive/**'
- 'dingtalk-bot/**'
- 'backend-integration/**'
- 'status-board/**'
- 'scripts/generate-module-doc.js'
workflow_dispatch:
jobs:
generate-doc:
name: 📋 生成部署模块总文档
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 生成 HoloLake Era 模块文档
run: node scripts/generate-module-doc.js
- name: 提交更新文档
run: |
git config user.name "铸渊 (ZhùYuān)"
git config user.email "zhuyuan@guanghulab.com"
git add docs/HoloLake-Era-OS-Modules.md
if git diff --cached --quiet; then
echo "📋 文档无变化,跳过提交"
else
git commit -m "📋 自动更新 HoloLake Era 模块文档 · $(date -u +%Y-%m-%d\ %H:%M\ UTC)"
git push
fi
notify-collaborator:
name: 📧 通知合作者模块已收到
runs-on: ubuntu-latest
# 仅 push 事件触发workflow_dispatch 为手动,不发通知)
if: github.event_name == 'push'
permissions:
contents: read
issues: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 获取本次 push 改动的文件列表
id: changed
run: |
# 获取本次 push 相对于上一个 commit 改动的文件
FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git show --name-only --format="" HEAD)
echo "files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "📄 改动文件:$FILES"
- name: 发送模块收讫通知
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
PUSHER_LOGIN: ${{ github.actor }}
CHANGED_FILES: ${{ steps.changed.outputs.files }}
COMMIT_SHA: ${{ github.sha }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: node scripts/notify-module-received.js

View File

@ -0,0 +1,333 @@
# HoloLake Era 操作系统部署模块
> 📋 **自动生成文档** · 铸渊ZhùYuān维护 · 最后更新2026-03-06 14:43 UTC
>
> 本文档由 GitHub Actions 自动触发生成,每当合作者上传/更新模块时自动刷新。
> 按合作者编号DEV-XXX整理所有已上传模块。
---
## 📑 目录
- [DEV-001 · 🖥️ 页页](#dev-001-页页) 1 个模块)
- [DEV-002 · 🦁 肥猫](#dev-002-肥猫) 2 个模块)
- [DEV-003 · 🌸 燕樊](#dev-003-燕樊) 3 个模块)
- [DEV-004 · 🤖 之之](#dev-004-之之) 1 个模块)
- [DEV-005 · 🍓 小草莓](#dev-005-小草莓) 2 个模块)
- [DEV-009 · 🌺 花尔](#dev-009-花尔) 1 个模块)
- [DEV-010 · 🍊 桔子](#dev-010-桔子) 2 个模块)
- [DEV-011 · 🌙 匆匆那年](#dev-011-匆匆那年) (待上传)
---
## DEV-001 · 🖥️ 页页
**角色:** 后端工程师
### 📦 后端集成中间层
| 字段 | 内容 |
|------|------|
| **目录** | `backend-integration/` |
| **负责人** | 页页 |
| **状态** | 进行中 |
| **技术栈** | Node.js |
| **依赖模块** | 所有前端模块 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
backend-integration/
README.md
```
---
## DEV-002 · 🦁 肥猫
**角色:** 光湖团队总控
### 📦 M01 用户登录界面
| 字段 | 内容 |
|------|------|
| **目录** | `m01-login/` |
| **负责人** | 肥猫 |
| **状态** | 进行中 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m01-login/
README.md
```
### 📦 M03 人格系统
| 字段 | 内容 |
|------|------|
| **目录** | `m03-personality/` |
| **负责人** | 肥猫 |
| **状态** | 进行中 |
| **技术栈** | 待定 |
| **依赖模块** | M01 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m03-personality/
README.md
```
---
## DEV-003 · 🌸 燕樊
**角色:** 前端工程师
### 📦 M07 对话UI
| 字段 | 内容 |
|------|------|
| **目录** | `m07-dialogue-ui/` |
| **负责人** | 燕樊 |
| **状态** | 进行中 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m07-dialogue-ui/
README.md
index.html
script.js
style.css
```
### 📦 M10 云盘系统
| 字段 | 内容 |
|------|------|
| **目录** | `m10-cloud/` |
| **负责人** | 燕樊 |
| **状态** | 等待SYSLOG |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m10-cloud/
README.md
help.css
help.html
help.js
```
### 📦 m15-cloud-drive
| 字段 | 内容 |
|------|------|
| **目录** | `m15-cloud-drive/` |
| **负责人** | 燕樊 |
| **状态** | 未知 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m15-cloud-drive/
cloud-drive-style.css
cloud-drive.html
cloud-drive.js
```
---
## DEV-004 · 🤖 之之
**角色:** 机器人工程师
### 📦 钉钉机器人
| 字段 | 内容 |
|------|------|
| **目录** | `dingtalk-bot/` |
| **负责人** | 之之 |
| **状态** | 环节0已完成 |
| **技术栈** | Node.js + Express |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
dingtalk-bot/
README.md
```
---
## DEV-005 · 🍓 小草莓
**角色:** 看板工程师
### 📦 M12 状态看板
| 字段 | 内容 |
|------|------|
| **目录** | `m12-kanban/` |
| **负责人** | 小草莓 |
| **状态** | 进行中 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m12-kanban/
README.md
```
### 📦 status-board
| 字段 | 内容 |
|------|------|
| **目录** | `status-board/` |
| **负责人** | 小草莓 |
| **状态** | 未知 |
| **技术栈** | 待定 |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ✅ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
status-board/
api-config.js
api.js
index.html
mock-ws-server.js
package-lock.json
package.json
render.js
style.css
ws-client.js
```
---
## DEV-009 · 🌺 花尔
**角色:** 前端工程师
### 📦 M05 用户中心界面
| 字段 | 内容 |
|------|------|
| **目录** | `m05-user-center/` |
| **负责人** | 花尔 |
| **状态** | 环节0 |
| **技术栈** | HTML/CSS/JS |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m05-user-center/
README.md
index.html
script.js
style.css
```
---
## DEV-010 · 🍊 桔子
**角色:** 光湖主控
### 📦 M06 工单管理界面
| 字段 | 内容 |
|------|------|
| **目录** | `m06-ticket/` |
| **负责人** | 桔子 |
| **状态** | 环节0 |
| **技术栈** | HTML/CSS/JS |
| **依赖模块** | 无 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m06-ticket/
README.md
```
### 📦 M11 工单管理模块
| 字段 | 内容 |
|------|------|
| **目录** | `m11-module/` |
| **负责人** | 桔子 |
| **状态** | 进行中 |
| **技术栈** | 待定 |
| **依赖模块** | M06 |
**结构检查:** ⚠️ src/ · ⚠️ package.json · ⚠️ SYSLOG.md
**已上传文件:**
```
m11-module/
README.md
```
---
## DEV-011 · 🌙 匆匆那年
**角色:** 开发者
> 🕐 暂无分配模块,待安排。
---
## 📊 部署统计
| 项目 | 数量 |
|------|------|
| 合作者总数 | 8 |
| 计划模块数 | 12 |
| 已上传模块数 | 12 |
| 待上传模块数 | 0 |
| 上传完成率 | 100% |
| 文档更新时间 | 2026-03-06 14:43 UTC |
---
*由 铸渊ZhùYuān· GitHub Copilot Agent 自动生成 · 仓库guanghulab*

View File

@ -0,0 +1,297 @@
// scripts/generate-module-doc.js
// 铸渊 · HoloLake Era 操作系统部署模块文档生成器
// 检测所有合作者上传的模块目录 → 按 DEV 编号整理 → 生成总文档
const fs = require('fs');
const path = require('path');
const OUTPUT_FILE = 'docs/HoloLake-Era-OS-Modules.md';
// ========== 合作者编号 → 模块目录 路由映射表 ==========
// 新增合作者或模块时,在此表维护即可
const COLLABORATOR_MODULES = [
{
devId: 'DEV-001',
name: '页页',
emoji: '🖥️',
role: '后端工程师',
dirs: ['backend-integration'],
},
{
devId: 'DEV-002',
name: '肥猫',
emoji: '🦁',
role: '光湖团队总控',
dirs: ['m01-login', 'm03-personality'],
},
{
devId: 'DEV-003',
name: '燕樊',
emoji: '🌸',
role: '前端工程师',
dirs: ['m07-dialogue-ui', 'm10-cloud', 'm15-cloud-drive'],
},
{
devId: 'DEV-004',
name: '之之',
emoji: '🤖',
role: '机器人工程师',
dirs: ['dingtalk-bot'],
},
{
devId: 'DEV-005',
name: '小草莓',
emoji: '🍓',
role: '看板工程师',
dirs: ['m12-kanban', 'status-board'],
},
{
devId: 'DEV-009',
name: '花尔',
emoji: '🌺',
role: '前端工程师',
dirs: ['m05-user-center'],
},
{
devId: 'DEV-010',
name: '桔子',
emoji: '🍊',
role: '光湖主控',
dirs: ['m06-ticket', 'm11-module'],
},
{
devId: 'DEV-011',
name: '匆匆那年',
emoji: '🌙',
role: '开发者',
dirs: [],
},
];
// ========== 读取模块元信息 ==========
function readModuleInfo(dir) {
const info = {
dir,
title: dir,
owner: '',
status: '未知',
techStack: '待定',
dependencies: '无',
hasSrc: false,
hasPackageJson: false,
hasSyslog: false,
files: [],
description: '',
};
// 读取 README.md
const readmePath = path.join(dir, 'README.md');
if (fs.existsSync(readmePath)) {
const content = fs.readFileSync(readmePath, 'utf8');
const titleMatch = content.match(/^#\s+(.+)$/m);
if (titleMatch) info.title = titleMatch[1].trim();
const ownerMatch = content.match(/负责人[:]\s*(.+)/);
if (ownerMatch) info.owner = ownerMatch[1].trim();
const statusMatch = content.match(/状态[:]\s*(.+)/);
if (statusMatch) info.status = statusMatch[1].trim();
const techMatch = content.match(/技术栈[:]\s*(.+)/);
if (techMatch) info.techStack = techMatch[1].trim();
const depsMatch = content.match(/依赖模块[:]\s*(.+)/);
if (depsMatch) info.dependencies = depsMatch[1].trim();
// 收集额外描述行(非元数据)
const lines = content.split('\n').filter(l =>
l.trim() &&
!l.startsWith('#') &&
!l.match(/^\s*-\s*(负责人|状态|技术栈|依赖模块)[:]/)
);
if (lines.length > 0) info.description = lines.slice(0, 3).join(' ').trim();
}
// 检查结构完整性
info.hasSrc = fs.existsSync(path.join(dir, 'src'));
info.hasPackageJson = fs.existsSync(path.join(dir, 'package.json'));
info.hasSyslog = fs.existsSync(path.join(dir, 'SYSLOG.md'));
// 列出文件最多15个排除 node_modules
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
info.files = entries
.filter(e => e.name !== 'node_modules' && e.name !== '.git')
.map(e => (e.isDirectory() ? e.name + '/' : e.name))
.sort();
} catch (_) {
// 目录不可读时忽略
}
return info;
}
// ========== 生成与 GitHub 一致的 Markdown anchor ==========
// GitHub 规则:小写 → 保留字母/数字/空格/连字符/中文 → 空格转连字符
function toGitHubAnchor(headingText) {
return headingText
.toLowerCase()
.replace(/[^\p{L}\p{N}\s-]/gu, '') // 去除非字母/数字/空格/连字符(支持 Unicode
.replace(/\s+/g, '-'); // 空格转连字符
}
function structureBadge(info) {
const checks = [
info.hasSrc ? '✅ src/' : '⚠️ src/',
info.hasPackageJson ? '✅ package.json' : '⚠️ package.json',
info.hasSyslog ? '✅ SYSLOG.md' : '⚠️ SYSLOG.md',
];
return checks.join(' · ');
}
// ========== 主函数:生成文档 ==========
function generateDoc() {
const now = new Date();
const timestamp = now.toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
const dateStr = now.toISOString().slice(0, 10);
const lines = [];
lines.push('# HoloLake Era 操作系统部署模块');
lines.push('');
lines.push('> 📋 **自动生成文档** · 铸渊ZhùYuān维护 · 最后更新:' + timestamp);
lines.push('> ');
lines.push('> 本文档由 GitHub Actions 自动触发生成,每当合作者上传/更新模块时自动刷新。');
lines.push('> 按合作者编号DEV-XXX整理所有已上传模块。');
lines.push('');
lines.push('---');
lines.push('');
// 生成目录
lines.push('## 📑 目录');
lines.push('');
for (const collab of COLLABORATOR_MODULES) {
const moduleCount = collab.dirs.filter(d => fs.existsSync(d)).length;
const badge = moduleCount > 0 ? `${moduleCount} 个模块)` : '(待上传)';
const headingText = `${collab.devId} · ${collab.emoji} ${collab.name}`;
const anchor = toGitHubAnchor(headingText);
lines.push(`- [${headingText}](#${anchor}) ${badge}`);
}
lines.push('');
lines.push('---');
lines.push('');
// 统计
let totalModules = 0;
let uploadedModules = 0;
// 各合作者章节
for (const collab of COLLABORATOR_MODULES) {
lines.push(`## ${collab.devId} · ${collab.emoji} ${collab.name}`);
lines.push('');
lines.push(`**角色:** ${collab.role}`);
lines.push('');
const existingDirs = collab.dirs.filter(d => fs.existsSync(d));
const missingDirs = collab.dirs.filter(d => !fs.existsSync(d));
totalModules += collab.dirs.length;
uploadedModules += existingDirs.length;
if (collab.dirs.length === 0) {
lines.push('> 🕐 暂无分配模块,待安排。');
lines.push('');
lines.push('---');
lines.push('');
continue;
}
if (existingDirs.length === 0) {
lines.push('> ⏳ 模块目录尚未创建,等待上传。');
lines.push('');
if (missingDirs.length > 0) {
lines.push('**待上传模块:** ' + missingDirs.join('、'));
lines.push('');
}
lines.push('---');
lines.push('');
continue;
}
for (const dir of existingDirs) {
const info = readModuleInfo(dir);
lines.push(`### 📦 ${info.title}`);
lines.push('');
lines.push(`| 字段 | 内容 |`);
lines.push(`|------|------|`);
lines.push(`| **目录** | \`${dir}/\` |`);
lines.push(`| **负责人** | ${info.owner || collab.name} |`);
lines.push(`| **状态** | ${info.status} |`);
lines.push(`| **技术栈** | ${info.techStack} |`);
lines.push(`| **依赖模块** | ${info.dependencies} |`);
lines.push('');
lines.push('**结构检查:** ' + structureBadge(info));
lines.push('');
if (info.files.length > 0) {
lines.push('**已上传文件:**');
lines.push('');
lines.push('```');
lines.push(dir + '/');
info.files.forEach(f => lines.push(' ' + f));
lines.push('```');
lines.push('');
}
if (info.hasSyslog) {
const syslogContent = fs.readFileSync(path.join(dir, 'SYSLOG.md'), 'utf8');
const syslogLines = syslogContent.split('\n').filter(l => l.trim()).slice(0, 5);
if (syslogLines.length > 0) {
lines.push('<details>');
lines.push('<summary>📝 SYSLOG 摘要(点击展开)</summary>');
lines.push('');
lines.push('```');
syslogLines.forEach(l => lines.push(l));
lines.push('```');
lines.push('');
lines.push('</details>');
lines.push('');
}
}
}
if (missingDirs.length > 0) {
lines.push('> ⏳ **待上传:** ' + missingDirs.map(d => '`' + d + '/`').join('、'));
lines.push('');
}
lines.push('---');
lines.push('');
}
// 汇总统计
lines.push('## 📊 部署统计');
lines.push('');
lines.push(`| 项目 | 数量 |`);
lines.push(`|------|------|`);
lines.push(`| 合作者总数 | ${COLLABORATOR_MODULES.length} |`);
lines.push(`| 计划模块数 | ${totalModules} |`);
lines.push(`| 已上传模块数 | ${uploadedModules} |`);
lines.push(`| 待上传模块数 | ${totalModules - uploadedModules} |`);
lines.push(`| 上传完成率 | ${totalModules > 0 ? Math.round(uploadedModules / totalModules * 100) : 0}% |`);
lines.push(`| 文档更新时间 | ${timestamp} |`);
lines.push('');
lines.push('---');
lines.push('');
lines.push('*由 铸渊ZhùYuān· GitHub Copilot Agent 自动生成 · 仓库guanghulab*');
lines.push('');
// 确保 docs/ 目录存在
if (!fs.existsSync('docs')) {
fs.mkdirSync('docs', { recursive: true });
}
fs.writeFileSync(OUTPUT_FILE, lines.join('\n'), 'utf8');
console.log('✅ 文档已生成:' + OUTPUT_FILE);
console.log('📊 合作者: ' + COLLABORATOR_MODULES.length + ' | 计划模块: ' + totalModules + ' | 已上传: ' + uploadedModules);
}
generateDoc();

View File

@ -0,0 +1,260 @@
// scripts/notify-module-received.js
// 铸渊 · 模块收讫通知系统
// 当合作者上传模块后,自动创建 GitHub Issue @提及 → GitHub 自动发邮件给对方
//
// 环境变量(由 GitHub Actions 注入):
// GITHUB_TOKEN — 仓库 token自动注入
// GITHUB_REPO — 格式: owner/repo如 qinfendebingshuo/guanghulab
// PUSHER_LOGIN — github.actor即推送者的 GitHub 用户名
// CHANGED_FILES — 本次 push 中改动的文件列表(换行分隔)
// COMMIT_SHA — 本次 commit SHA前8位
// COMMIT_MESSAGE — 本次 commit 消息
const fs = require('fs');
const https = require('https');
// ========== 读取配置 ==========
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const GITHUB_REPO = process.env.GITHUB_REPO || '';
const PUSHER_LOGIN = (process.env.PUSHER_LOGIN || '').trim();
const CHANGED_FILES = (process.env.CHANGED_FILES || '').trim();
const COMMIT_SHA = (process.env.COMMIT_SHA || '').slice(0, 8);
const COMMIT_MSG = (process.env.COMMIT_MESSAGE || '').split('\n')[0].slice(0, 80);
if (!GITHUB_TOKEN || !GITHUB_REPO) {
console.error('❌ 缺少 GITHUB_TOKEN 或 GITHUB_REPO');
process.exit(1);
}
if (!PUSHER_LOGIN) {
console.log('⚠️ 未获取到推送者用户名,跳过通知');
process.exit(0);
}
const [OWNER, REPO] = GITHUB_REPO.split('/');
// ========== 加载合作者配置 ==========
let collaborators = [];
try {
const config = JSON.parse(fs.readFileSync('.github/brain/collaborators.json', 'utf8'));
collaborators = config.collaborators || [];
} catch (e) {
console.error('❌ 无法读取 collaborators.json:', e.message);
process.exit(1);
}
// ========== 按推送者 GitHub 用户名查找合作者 ==========
function findCollaborator(githubLogin) {
// 先按 github_username 精确匹配
const byUsername = collaborators.find(c =>
c.github_username && c.github_username.toLowerCase() === githubLogin.toLowerCase()
);
if (byUsername) return byUsername;
return null;
}
// ========== 分析改动了哪些模块 ==========
function detectChangedModules(changedFilesStr) {
if (!changedFilesStr) return [];
const files = changedFilesStr.split('\n').map(f => f.trim()).filter(Boolean);
const moduleDirs = new Set();
// 已知模块目录前缀列表
const KNOWN_MODULE_PREFIXES = [
'm01-login', 'm03-personality', 'm05-user-center', 'm06-ticket',
'm07-dialogue-ui', 'm10-cloud', 'm11-module', 'm12-kanban',
'm15-cloud-drive', 'dingtalk-bot', 'backend-integration', 'status-board',
];
for (const file of files) {
for (const prefix of KNOWN_MODULE_PREFIXES) {
if (file.startsWith(prefix + '/') || file === prefix) {
moduleDirs.add(prefix);
}
}
}
return Array.from(moduleDirs);
}
// ========== 构建通知 Issue 内容 ==========
function buildIssueContent(collab, changedModules, allFiles) {
const now = new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
const mention = collab && collab.github_username ? `@${collab.github_username}` : `@${PUSHER_LOGIN}`;
const devLabel = collab ? `${collab.dev_id} · ${collab.emoji} ${collab.name}` : PUSHER_LOGIN;
const moduleListStr = changedModules.length > 0
? changedModules.map(m => `- \`${m}/\``).join('\n')
: '- (系统自动检测,请核对)';
const fileLines = allFiles.split('\n').filter(Boolean);
const fileListStr = fileLines.slice(0, 20).map(f => ` - \`${f}\``).join('\n');
const extraNote = fileLines.length > 20 ? `\n - ...(共 ${fileLines.length} 个文件)` : '';
const title = `📦 铸渊收讫 · [${collab ? collab.dev_id : 'DEV-???'}] ${collab ? collab.name : PUSHER_LOGIN} 模块已收到`;
const body = `## 铸渊收讫通知
${mention} 你好🎉
铸渊自动化系统已确认收到你上传的模块
---
### 📋 收讫详情
| 字段 | 内容 |
|------|------|
| **合作者** | ${devLabel} |
| **GitHub 账号** | @${PUSHER_LOGIN} |
| **收到时间** | ${now} |
| **Commit** | \`${COMMIT_SHA}\` |
| **提交说明** | ${COMMIT_MSG || '(无)'} |
### 📦 涉及模块
${moduleListStr}
### 📄 上传文件清单
${fileListStr}${extraNote}
---
### 📌 下一步
1. **铸渊将自动更新** [HoloLake Era 操作系统部署模块](https://github.com/${GITHUB_REPO}/blob/main/docs/HoloLake-Era-OS-Modules.md) 总文档
2. **模块结构检查** 已自动运行请确认 CI 状态绿灯
3. 如需补充 \`SYSLOG.md\`\`package.json\`\`src/\` 目录,请继续推送
> 💡 如有问题请直接在本 Issue 回复铸渊会收到通知
---
* 铸渊ZhùYuān· 光湖自动化系统 · 自动通知 #${now}*`;
return { title, body };
}
// ========== 调用 GitHub API 创建 Issue ==========
function createIssue(title, body) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({ title, body, labels: ['模块收讫'] });
const options = {
hostname: 'api.github.com',
path: `/repos/${OWNER}/${REPO}/issues`,
method: 'POST',
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'User-Agent': 'ZhuyuanBot/1.0',
'X-GitHub-Api-Version': '2022-11-28',
},
};
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
if (res.statusCode === 201) {
const issue = JSON.parse(data);
resolve(issue);
} else {
reject(new Error(`GitHub API 返回 ${res.statusCode}: ${data}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
// ========== 确保标签存在(不存在则自动创建)==========
function ensureLabel() {
return new Promise((resolve) => {
const labelPayload = JSON.stringify({
name: '模块收讫',
color: '0075ca',
description: '铸渊自动通知:合作者模块收讫确认',
});
const options = {
hostname: 'api.github.com',
path: `/repos/${OWNER}/${REPO}/labels`,
method: 'POST',
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(labelPayload),
'User-Agent': 'ZhuyuanBot/1.0',
'X-GitHub-Api-Version': '2022-11-28',
},
};
const req = https.request(options, res => {
let data = '';
res.on('data', c => { data += c; });
res.on('end', () => {
// 201 = created, 422 = already exists都算OK
resolve();
});
});
req.on('error', () => resolve()); // 标签创建失败不阻断主流程
req.write(labelPayload);
req.end();
});
}
// ========== 主流程 ==========
async function main() {
console.log(`👤 推送者:${PUSHER_LOGIN}`);
console.log(`📁 改动文件:${CHANGED_FILES ? CHANGED_FILES.split('\n').length : 0}`);
const collab = findCollaborator(PUSHER_LOGIN);
if (collab) {
console.log(`✅ 识别合作者:${collab.dev_id} · ${collab.name}`);
if (!collab.github_username) {
console.log(`⚠️ ${collab.dev_id}${collab.name})的 github_username 尚未填写`);
console.log(` 请在 .github/brain/collaborators.json 中补充,以便 @提及 生效`);
console.log(` 通知将仍然创建,但不会有 @提及 邮件通知`);
}
} else {
console.log(` 未在 collaborators.json 中找到 "${PUSHER_LOGIN}" 的映射,将使用 GitHub 用户名通知`);
}
const changedModules = detectChangedModules(CHANGED_FILES);
console.log(`📦 涉及模块:${changedModules.join(', ') || '(未检测到标准模块目录)'}`);
// 如果改动文件全部在 docs/ 或 scripts/ 或 .github/,说明是系统自动提交,跳过通知
const allFiles = CHANGED_FILES.split('\n').filter(Boolean);
const isSystemCommit = allFiles.length > 0 && allFiles.every(f =>
f.startsWith('docs/') || f.startsWith('scripts/') || f.startsWith('.github/')
);
if (isSystemCommit) {
console.log('🤖 检测到系统自动提交(非模块上传),跳过通知');
process.exit(0);
}
if (changedModules.length === 0) {
console.log('⚠️ 未检测到标准模块目录的改动,跳过通知');
process.exit(0);
}
// 确保标签存在
await ensureLabel();
// 构建并发送通知
const { title, body } = buildIssueContent(collab, changedModules, CHANGED_FILES);
console.log(`📝 创建通知 Issue${title}`);
const issue = await createIssue(title, body);
console.log(`✅ Issue 已创建:#${issue.number}${issue.html_url}`);
console.log(`📧 GitHub 将自动发送邮件通知给 @${collab?.github_username || PUSHER_LOGIN}`);
}
main().catch(err => {
console.error('❌ 通知失败:', err.message);
process.exit(1);
});