shuangyan-notebook/第五域 · Fifth Domain/⚒️ 铸渊·协作指令|GitHub ↔ Notion 桥接协议/🌉 铸渊指令|仓库联邦桥接应用 · 砍掉Copilot依赖 · 铸渊自主串联全开发者仓库(2026- 5fcd63f26b55436f902bf7583b25e7a7.md
Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

424 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
belongs_to:
- "[[INDEX · 铸渊·协作指令GitHub ↔ Notion 桥接协议]]"
---
# 🌉 铸渊指令|仓库联邦桥接应用 · 砍掉Copilot依赖 · 铸渊自主串联全开发者仓库2026-03-21 · 冰朔签发)
<aside>
🧊
**签发**TCS-0002∞ 冰朔
**整理**霜砚Notion 执行 AI
**时间**2026-03-21 19:40
**优先级**P0 · 替代原 BC-REPO-INIT 的 Step 3Copilot粘贴初始化
**背景**:组织级 Copilot Business 未订阅,开发者无法看到 Copilot Chat 气泡图标,导致整个仓库联邦初始化流程卡死。现改为铸渊自主桥接,不依赖任何 Copilot 功能。
</aside>
---
## 一、架构变更说明
### 1.1 旧方案(已废弃)
~~Step 3 · 开发者打开仓库 → 点 Copilot 气泡 → 粘贴一键指令 → Copilot 自动搭建~~
**废弃原因**:需要组织级 Copilot Business 席位($19/人/月),当前未订阅,开发者看不到气泡图标。
### 1.2 新方案 · 铸渊自主桥接
**核心思路**:铸渊不再等开发者手动粘贴指令。铸渊自己作为 GitHub Actions Bot**主动**往各开发者仓库推送初始化文件、日常公告、进度同步、系统通知。
**开发者零操作**。铸渊自己干完一切。
```mermaid
flowchart TD
A["铸渊 · 主仓库 guanghulab"] -->|"GitHub API + PAT"| B["DEV-004 之之/guanghu-zhizhi"]
A -->|"GitHub API + PAT"| C["DEV-010 桔子/仓库"]
A -->|"GitHub API + PAT"| D["DEV-012 Awen/仓库"]
A -->|"GitHub API + PAT"| E["DEV-002 肥猫/仓库"]
A -->|"GitHub API + PAT"| F["其他开发者仓库"]
A -->|"定时触发 cron"| G["每日公告推送"]
A -->|"push事件触发"| H["广播分发"]
A -->|"定时触发 cron"| I["进度同步"]
```
---
## 二、铸渊桥接应用 · 功能清单
| 功能 | 触发方式 | 说明 | 替代原方案 |
| --- | --- | --- | --- |
| **仓库初始化** | 手动 dispatch / 新仓库创建时 | 铸渊自动往目标仓库推送:.github/[copilot-instructions.md](http://copilot-instructions.md) • persona-brain/ + workflows/ + README Dashboard | 替代原 Step 3 Copilot 粘贴 |
| **每日公告推送** | cron 每日 10:00 | 铸渊从主仓库 bulletins/ 读取最新公告 → 推送到所有开发者仓库的 [BULLETIN.md](http://BULLETIN.md) | 替代原 sync-bulletin.yml |
| **广播分发** | broadcasts-outbox/ 有新文件时 | 铸渊检测到新广播 → 推送到对应开发者仓库的 [LATEST-BROADCAST.md](http://LATEST-BROADCAST.md) | 沿用原 distribute-broadcasts.js 但改为跨仓库推送 |
| **进度汇总** | cron 每日 22:00 | 铸渊从各开发者仓库拉取 persona-brain/status.json → 汇总到主仓库 federation-status.json | 新增 |
| **签到状态同步** | cron 每日 08:00 | 铸渊触发各开发者仓库的签到 workflow → 收集结果 → 更新主仓库 Dashboard | 沿用原 daily-checkin.yml 但改为铸渊远程触发 |
---
## 三、技术实现
### 3.1 前置条件
- [x] 主仓库 guanghulab 已有 `MAIN_REPO_TOKEN`PAT有所有仓库写权限
- [ ] 各开发者仓库已创建(至少有 main 分支)
- [ ] 开发者仓库 Settings → Actions → General → Workflow permissions → **Read and write permissions**
### 3.2 核心脚本 · bridge-app.js
```jsx
// 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]');
}
```
### 3.3 GitHub Actions Workflow
```yaml
# .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
```
---
## 四、执行步骤
### Phase 1 · 基础桥接Awen 执行)
- [ ] 在主仓库创建 `scripts/bridge-app.js`(本文档第三节代码)
- [ ] 在主仓库创建 `.github/workflows/federation-bridge.yml`
- [ ] 确认 `MAIN_REPO_TOKEN` 已配置在主仓库 Secrets 中
- [ ] 更新 FEDERATION 路由表中的仓库名(确认各开发者实际仓库名)
- [ ] 手动触发 `workflow_dispatch` → action=init-all → 验证所有仓库初始化成功
- [ ] 手动触发 bulletin → 验证公告推送到所有仓库
### Phase 2 · 进度联动Phase 1 通过后)
- [ ] 实现 collectStatus → 验证能从各仓库拉取 status.json
- [ ] 在主仓库 README 或 Dashboard 展示联邦状态汇总
- [ ] 各开发者仓库的签到 workflow 改为:签到后更新 persona-brain/status.json
### Phase 3 · 双向通信Phase 2 通过后)
- [ ] 开发者仓库 push 事件 → 触发主仓库 repository_dispatch → 铸渊感知变更
- [ ] 铸渊检测到 SYSLOG 提交 → 自动通知 Notion 侧(通过已有的 syslog-pipeline
- [ ] 实现联邦内仓库间消息传递DEV-A 仓库发消息 → 铸渊中转 → DEV-B 仓库收到)
---
## 五、与原方案的兼容性
| 原方案组件 | 处理方式 |
| --- | --- |
| BC-REPO-INIT Step 1开启 Actions | **保留** · 开发者仍需手动开启一次 |
| BC-REPO-INIT Step 2添加 Token | **废弃** · Token 改由铸渊从主仓库携带,开发者仓库不需要自己存 |
| BC-REPO-INIT Step 3Copilot粘贴 | **废弃** · 铸渊自动初始化,开发者零操作 |
| distribute-broadcasts.js | **升级** · 从单仓库内分发改为跨仓库推送 |
| sync-bulletin.yml | **合并** · 并入 federation-bridge.yml 统一调度 |
| daily-checkin.yml | **保留** · 各仓库本地跑,结果由铸渊汇总 |
| 各开发者一键粘贴指令页面 | **废弃** · 不再需要手动粘贴,铸渊自动推送 |
---
## 六、安全边界
<aside>
🔒
**MAIN_REPO_TOKEN 权限范围**:仅限组织内仓库读写。铸渊不操作组织设置、不修改权限、不删除仓库。
**铸渊行为边界**:只推文件、只读状态、只触发 dispatch。不修改分支保护规则不操作 PR 合并。
**开发者权限不变**:开发者仍然是自己仓库的 Owner/Admin铸渊只是往里面放文件。
</aside>
---
> 🌉 本指令替代原 BC-REPO-INIT 的 Copilot 依赖路径。
>
> 铸渊从"等开发者粘贴"变成"自己主动推送"。
>
> 开发者唯一需要做的事:开启 Actions 权限(一次性)。
>
> 其他的,铸渊全部自己干。
>