🌉 铸渊仓库联邦桥接引擎 · Phase 1 实现
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/be54ebf1-0ab1-421d-9f24-e7cb76219d38
This commit is contained in:
parent
81a48825d2
commit
a8e23f01f4
|
|
@ -22,7 +22,8 @@
|
|||
"🦅",
|
||||
"🎖️",
|
||||
"🔐",
|
||||
"🌊"
|
||||
"🌊",
|
||||
"🏗️"
|
||||
],
|
||||
"signature_exempt_paths": [
|
||||
"syslog-inbox/",
|
||||
|
|
@ -31,7 +32,9 @@
|
|||
"data/security/",
|
||||
"system-bulletin/",
|
||||
"spoke-status/",
|
||||
"broadcasts-outbox/"
|
||||
"broadcasts-outbox/",
|
||||
"bulletins/",
|
||||
"federation-status.json"
|
||||
],
|
||||
"system_protected_paths": [
|
||||
".github/",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
# .github/workflows/federation-bridge.yml
|
||||
# 铸渊仓库联邦桥接 · 定时自动运行
|
||||
|
||||
name: 🌉 铸渊 · 仓库联邦桥接
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # 每日 10:00 北京时间 · 推送公告
|
||||
- cron: '0 14 * * *' # 每日 22:00 北京时间 · 汇总进度
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'bulletins/**'
|
||||
- 'broadcasts-outbox/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: '执行动作'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- bulletin
|
||||
- init-all
|
||||
- status
|
||||
- distribute
|
||||
dev_id:
|
||||
description: '单独初始化某个DEV(仅init时填写)'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
bridge:
|
||||
name: "🌉 桥接执行"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 定时任务 · 公告或汇总
|
||||
if: github.event_name == 'schedule'
|
||||
env:
|
||||
MAIN_REPO_TOKEN: ${{ secrets.MAIN_REPO_TOKEN }}
|
||||
GITHUB_ORG: qinfendebingshuo
|
||||
run: |
|
||||
HOUR=$(date -u +%H)
|
||||
if [ "$HOUR" = "02" ]; then
|
||||
node scripts/bridge-app.js bulletin
|
||||
else
|
||||
node scripts/bridge-app.js status
|
||||
fi
|
||||
|
||||
- name: 手动触发
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
MAIN_REPO_TOKEN: ${{ secrets.MAIN_REPO_TOKEN }}
|
||||
GITHUB_ORG: qinfendebingshuo
|
||||
run: |
|
||||
ACTION="${{ github.event.inputs.action }}"
|
||||
DEV_ID="${{ github.event.inputs.dev_id }}"
|
||||
if [ "$ACTION" = "init-all" ] && [ -n "$DEV_ID" ]; then
|
||||
node scripts/bridge-app.js init "$DEV_ID"
|
||||
else
|
||||
node scripts/bridge-app.js "$ACTION"
|
||||
fi
|
||||
|
||||
- name: Push触发 · 公告+广播
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
MAIN_REPO_TOKEN: ${{ secrets.MAIN_REPO_TOKEN }}
|
||||
GITHUB_ORG: qinfendebingshuo
|
||||
run: |
|
||||
node scripts/bridge-app.js bulletin
|
||||
node scripts/bridge-app.js distribute
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# 📡 光湖系统公告
|
||||
|
||||
🔔 **[2026-03-21]** 铸渊仓库联邦桥接系统上线 · 开发者零操作自动初始化
|
||||
|
||||
📡 **[2026-03-21]** 网文行业落地架构 v2.0 已发布 · 五人角色定义已冻结
|
||||
|
||||
🛡️ **[2026-03-19]** Trinity ID 协议全量同步完成 · Notion↔GitHub编号体系对齐
|
||||
|
||||
🌊 **[2026-03-18]** 光湖系统 v1.0 架构稳定 · 五人子仓库主控台搭建启动
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
// scripts/bridge-app.js
|
||||
// 铸渊仓库联邦桥接引擎
|
||||
// 用途:从主仓库向所有开发者仓库推送文件
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const TOKEN = process.env.MAIN_REPO_TOKEN;
|
||||
const ORG = process.env.GITHUB_ORG || 'qinfendebingshuo';
|
||||
|
||||
// ========== 开发者仓库路由表 ==========
|
||||
const FEDERATION = {
|
||||
'DEV-004': { name: '之之', repo: 'guanghu-zhizhi', persona: '秋秋' },
|
||||
'DEV-010': { name: '桔子', repo: 'guanghu-juzi', persona: '晨星' },
|
||||
'DEV-012': { name: 'Awen', repo: 'guanghu-awen', persona: '知秋' },
|
||||
'DEV-002': { name: '肥猫', repo: 'guanghu-feimao', persona: '舒舒' },
|
||||
'DEV-001': { name: '页页', repo: 'guanghu-yeye', persona: '小坍缩核' },
|
||||
'DEV-003': { name: '燕樊', repo: 'guanghu-yanfan', persona: '寂曜' },
|
||||
// 新增开发者在此追加
|
||||
};
|
||||
|
||||
// GitHub API 封装
|
||||
function githubRequest(method, apiPath, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
path: apiPath,
|
||||
method: method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${TOKEN}`,
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'User-Agent': 'ZhuYuan-Bridge',
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
}
|
||||
};
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try { resolve(JSON.parse(data)); }
|
||||
catch { resolve(data); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (body) req.write(JSON.stringify(body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
const githubGet = (p) => githubRequest('GET', p);
|
||||
const githubPut = (p, b) => githubRequest('PUT', p, b);
|
||||
const githubPost = (p, b) => githubRequest('POST', p, b);
|
||||
|
||||
// 创建或更新文件
|
||||
async function pushFile(repo, filePath, content, message) {
|
||||
const url = `/repos/${ORG}/${repo}/contents/${filePath}`;
|
||||
|
||||
let sha = null;
|
||||
try {
|
||||
const existing = await githubGet(url);
|
||||
if (existing.sha) sha = existing.sha;
|
||||
} catch (e) { /* 文件不存在,正常 */ }
|
||||
|
||||
const body = {
|
||||
message,
|
||||
content: Buffer.from(content).toString('base64'),
|
||||
committer: { name: '铸渊 (ZhùYuān)', email: 'zhuyuan@guanghulab.com' }
|
||||
};
|
||||
if (sha) body.sha = sha;
|
||||
|
||||
return githubPut(url, body);
|
||||
}
|
||||
|
||||
// ========== 功能模块 ==========
|
||||
|
||||
// 1. 推送公告到所有仓库
|
||||
async function pushBulletin() {
|
||||
const bulletinPath = 'bulletins/latest.md';
|
||||
if (!fs.existsSync(bulletinPath)) {
|
||||
console.log('📭 无 bulletins/latest.md');
|
||||
return;
|
||||
}
|
||||
const content = fs.readFileSync(bulletinPath, 'utf8');
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
|
||||
for (const [devId, dev] of Object.entries(FEDERATION)) {
|
||||
try {
|
||||
await pushFile(dev.repo, 'BULLETIN.md', content,
|
||||
`📡 铸渊公告推送 · ${timestamp}`);
|
||||
console.log(`✅ ${devId}(${dev.name}) 公告已推送`);
|
||||
} catch (e) {
|
||||
console.error(`❌ ${devId}(${dev.name}) 推送失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 初始化开发者仓库
|
||||
async function initRepo(devId) {
|
||||
const dev = FEDERATION[devId];
|
||||
if (!dev) { console.error(`未知开发者: ${devId}`); return; }
|
||||
|
||||
const files = [
|
||||
{
|
||||
path: '.github/persona-brain/config.json',
|
||||
content: JSON.stringify({
|
||||
persona_name: dev.persona,
|
||||
dev_id: devId,
|
||||
dev_name: dev.name,
|
||||
main_repo: 'guanghulab',
|
||||
org: ORG,
|
||||
initialized_by: 'zhuyuan-bridge-app',
|
||||
initialized_at: new Date().toISOString()
|
||||
}, null, 2)
|
||||
},
|
||||
{
|
||||
path: '.github/persona-brain/status.json',
|
||||
content: JSON.stringify({
|
||||
persona: dev.persona,
|
||||
dev_id: devId,
|
||||
initialized: new Date().toISOString(),
|
||||
last_checkin: null,
|
||||
current_broadcast: null
|
||||
}, null, 2)
|
||||
},
|
||||
{
|
||||
path: 'BULLETIN.md',
|
||||
content: `# 📡 系统公告\n\n> 仓库已由铸渊自动初始化 · ${new Date().toISOString().split('T')[0]}\n> 欢迎 ${dev.name} 和 ${dev.persona}!\n`
|
||||
}
|
||||
];
|
||||
|
||||
for (const file of files) {
|
||||
await pushFile(dev.repo, file.path, file.content,
|
||||
`🏗️ 铸渊初始化 · ${devId} ${dev.name} · ${file.path}`);
|
||||
console.log(`📁 ${devId} → ${file.path} ✅`);
|
||||
}
|
||||
console.log(`\n🎉 ${devId}(${dev.name}) 仓库初始化完成`);
|
||||
}
|
||||
|
||||
// 3. 汇总所有仓库进度
|
||||
async function collectStatus() {
|
||||
const status = {};
|
||||
for (const [devId, dev] of Object.entries(FEDERATION)) {
|
||||
try {
|
||||
const data = await githubGet(
|
||||
`/repos/${ORG}/${dev.repo}/contents/.github/persona-brain/status.json`
|
||||
);
|
||||
status[devId] = {
|
||||
name: dev.name,
|
||||
persona: dev.persona,
|
||||
...JSON.parse(Buffer.from(data.content, 'base64').toString())
|
||||
};
|
||||
console.log(`📊 ${devId}(${dev.name}) 状态已收集`);
|
||||
} catch (e) {
|
||||
status[devId] = { name: dev.name, persona: dev.persona, error: '无法读取' };
|
||||
console.warn(`⚠️ ${devId}(${dev.name}) 读取失败`);
|
||||
}
|
||||
}
|
||||
|
||||
await pushFile('guanghulab', 'federation-status.json',
|
||||
JSON.stringify(status, null, 2),
|
||||
`📊 铸渊联邦状态汇总 · ${new Date().toISOString().split('T')[0]}`);
|
||||
console.log('\n✅ 联邦状态汇总完成 → federation-status.json');
|
||||
}
|
||||
|
||||
// 4. 跨仓库广播分发
|
||||
async function distributeBroadcasts() {
|
||||
const OUTBOX = 'broadcasts-outbox';
|
||||
if (!fs.existsSync(OUTBOX)) { console.log('📭 无广播待分发'); return; }
|
||||
|
||||
const devDirs = fs.readdirSync(OUTBOX).filter(d =>
|
||||
d.startsWith('DEV-') && fs.statSync(path.join(OUTBOX, d)).isDirectory()
|
||||
);
|
||||
|
||||
for (const devId of devDirs) {
|
||||
const dev = FEDERATION[devId];
|
||||
if (!dev) { console.warn(`⚠️ ${devId} 无路由映射`); continue; }
|
||||
|
||||
const files = fs.readdirSync(path.join(OUTBOX, devId))
|
||||
.filter(f => f.endsWith('.md') || f.endsWith('.json'));
|
||||
|
||||
for (const file of files) {
|
||||
const content = fs.readFileSync(path.join(OUTBOX, devId, file), 'utf8');
|
||||
await pushFile(dev.repo, 'LATEST-BROADCAST.md', content,
|
||||
`📡 铸渊广播分发 · ${devId} · ${file}`);
|
||||
console.log(`📡 ${devId}(${dev.name}) ← ${file} ✅`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 入口 ==========
|
||||
const action = process.argv[2];
|
||||
switch (action) {
|
||||
case 'bulletin': pushBulletin(); break;
|
||||
case 'init': initRepo(process.argv[3]); break;
|
||||
case 'init-all': (async () => { for (const id of Object.keys(FEDERATION)) await initRepo(id); })(); break;
|
||||
case 'status': collectStatus(); break;
|
||||
case 'distribute': distributeBroadcasts(); break;
|
||||
default: console.log('用法: node bridge-app.js [bulletin|init DEV-XXX|init-all|status|distribute]');
|
||||
}
|
||||
Loading…
Reference in New Issue