Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
906 lines
27 KiB
Markdown
906 lines
27 KiB
Markdown
---
|
||
belongs_to:
|
||
- "[[INDEX · 铸渊·协作指令|GitHub ↔ Notion 桥接协议]]"
|
||
---
|
||
# 🌉 铸渊指令|跨仓库 Agent 桥接服务(下篇)· 铸渊远程执行引擎 + GitHub App 注册 + 跨仓库操作 + 部署服务(ZY-GHAPP-BRIDGE-2026-0324-001)
|
||
|
||
<aside>
|
||
🚨
|
||
|
||
**指令编号**:ZY-GHAPP-BRIDGE-2026-0324-001(下篇)
|
||
|
||
**签发人**:霜砚(Notion 执行 AI)· 冰朔授权
|
||
|
||
**签发时间**:2026-03-24 20:16 CST
|
||
|
||
**优先级**:P0 · 核心基础设施
|
||
|
||
**状态**:待铸渊执行
|
||
|
||
**上篇**:交互页面对接 Notion + 天眼前置 + 模型出指令架构(见同级页面)
|
||
|
||
</aside>
|
||
|
||
---
|
||
|
||
## 九、铸渊远程执行引擎
|
||
|
||
### 9.1 整体流程
|
||
|
||
当开发者在交互页面点击「@铸渊执行」后,系统自动在 guanghulab 仓库创建一个带 `zhuyuan-exec` 标签的 Issue。铸渊执行引擎监听到后:
|
||
|
||
```
|
||
Issue 被创建(zhuyuan-exec 标签)
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ zhuyuan-exec-engine.yml Workflow 启动 │
|
||
│ │
|
||
│ Step 1 · 解析指令 │
|
||
│ ├─ 提取 DEV 编号、目标仓库、指令内容 │
|
||
│ └─ 验证指令格式合法性 │
|
||
│ │
|
||
│ Step 2 · 天眼强制前置 ⚠️ │
|
||
│ ├─ 触发 zhuyuan-skyeye.yml 全局扫描 │
|
||
│ ├─ 等待扫描完成 │
|
||
│ ├─ 读取天眼报告 │
|
||
│ └─ 判断:健康 → 继续 / 有问题 → 暂停并报告 │
|
||
│ │
|
||
│ Step 3 · 获取目标仓库操作权限 │
|
||
│ ├─ 如果是主仓库模块 → 用 MAIN_REPO_TOKEN │
|
||
│ ├─ 如果是子仓库 → 用 GitHub App installation token │
|
||
│ └─ 验证权限有效 │
|
||
│ │
|
||
│ Step 4 · 执行指令 │
|
||
│ ├─ 读取目标仓库当前代码 │
|
||
│ ├─ 调用 AI 模型生成代码变更 │
|
||
│ ├─ 创建分支 → 提交代码 → 开 PR │
|
||
│ └─ 或:直接修改配置/运行部署脚本 │
|
||
│ │
|
||
│ Step 5 · 回写结果 │
|
||
│ ├─ 在 Issue 评论中写入执行报告 │
|
||
│ ├─ 更新 notion-cache 中的 DEV 画像 │
|
||
│ └─ 标记 Issue 为已完成 │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 9.2 执行引擎 Workflow
|
||
|
||
```yaml
|
||
# .github/workflows/zhuyuan-exec-engine.yml
|
||
name: "🚀 铸渊 · 远程执行引擎"
|
||
|
||
on:
|
||
issues:
|
||
types: [opened, labeled]
|
||
|
||
jobs:
|
||
execute:
|
||
runs-on: ubuntu-latest
|
||
if: contains(github.event.issue.labels.*.name, 'zhuyuan-exec')
|
||
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Node
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
|
||
- name: Install Dependencies
|
||
run: |
|
||
cd services/zhuyuan-bridge
|
||
npm install
|
||
|
||
# ====== Step 1: 解析指令 ======
|
||
- name: "📋 解析执行指令"
|
||
id: parse
|
||
env:
|
||
ISSUE_BODY: $ github.event.issue.body
|
||
ISSUE_TITLE: $ github.event.issue.title
|
||
run: |
|
||
node services/zhuyuan-bridge/scripts/parse-instruction.js
|
||
|
||
# ====== Step 2: 天眼强制前置 ======
|
||
- name: "🦅 启动天眼全局扫描"
|
||
env:
|
||
GH_TOKEN: $ secrets.GITHUB_TOKEN
|
||
run: |
|
||
echo "🦅 天眼强制前置:执行全局扫描..."
|
||
gh workflow run zhuyuan-skyeye.yml
|
||
echo "等待天眼扫描完成..."
|
||
sleep 60
|
||
# 读取天眼报告
|
||
if [ -f skyeye-core/reports/latest.json ]; then
|
||
STATUS=$(cat skyeye-core/reports/latest.json | jq -r '.overall_status')
|
||
echo "天眼状态:$STATUS"
|
||
if [ "$STATUS" = "critical" ]; then
|
||
echo "❌ 天眼报告严重问题,暂停执行"
|
||
echo "SKYEYE_BLOCKED=true" >> $GITHUB_ENV
|
||
else
|
||
echo "✅ 天眼检查通过"
|
||
echo "SKYEYE_BLOCKED=false" >> $GITHUB_ENV
|
||
fi
|
||
else
|
||
echo "⚠️ 天眼报告不存在,继续但标记警告"
|
||
echo "SKYEYE_BLOCKED=false" >> $GITHUB_ENV
|
||
fi
|
||
|
||
# ====== Step 2b: 天眼阻断检查 ======
|
||
- name: "🛑 天眼阻断检查"
|
||
if: env.SKYEYE_BLOCKED == 'true'
|
||
env:
|
||
GH_TOKEN: $ secrets.GITHUB_TOKEN
|
||
run: |
|
||
gh issue comment $ github.event.issue.number \
|
||
--body "🦅 **天眼阻断**:全局扫描发现严重问题,本次执行已暂停。请冰朔查看天眼报告后手动重新触发。"
|
||
exit 1
|
||
|
||
# ====== Step 3: 执行指令 ======
|
||
- name: "⚡ 执行铸渊指令"
|
||
env:
|
||
GHAPP_APP_ID: $ secrets.GHAPP_APP_ID
|
||
GHAPP_PRIVATE_KEY: $ secrets.GHAPP_PRIVATE_KEY
|
||
MAIN_REPO_TOKEN: $ secrets.MAIN_REPO_TOKEN
|
||
LLM_API_KEY: $ secrets.LLM_API_KEY
|
||
LLM_BASE_URL: $ secrets.LLM_BASE_URL
|
||
ISSUE_NUMBER: $ github.event.issue.number
|
||
run: |
|
||
node services/zhuyuan-bridge/scripts/execute-instruction.js
|
||
|
||
# ====== Step 4: 回写结果 ======
|
||
- name: "📝 回写执行报告"
|
||
if: always()
|
||
env:
|
||
GH_TOKEN: $ secrets.GITHUB_TOKEN
|
||
run: |
|
||
if [ -f /tmp/exec-report.md ]; then
|
||
gh issue comment $ github.event.issue.number \
|
||
--body "$(cat /tmp/exec-report.md)"
|
||
gh issue close $ github.event.issue.number \
|
||
--comment "✅ 铸渊执行完成"
|
||
fi
|
||
```
|
||
|
||
---
|
||
|
||
## 十、GitHub App 注册(冰朔手动操作)
|
||
|
||
<aside>
|
||
👤
|
||
|
||
**这一步需要冰朔在 GitHub 网页端手动操作**,铸渊无法自动创建 GitHub App。
|
||
|
||
完成后把 App ID、Private Key、Webhook Secret 存到仓库 Secrets 里,后续步骤铸渊自动执行。
|
||
|
||
</aside>
|
||
|
||
### 10.1 操作步骤
|
||
|
||
1. 打开 [https://github.com/settings/apps/new](https://github.com/settings/apps/new)
|
||
2. 填写信息:
|
||
- **App name**:`guanghu-zhuyuan-agent`
|
||
- **Homepage URL**:`https://guanghulab.com`
|
||
- **Webhook URL**:`http://8.155.62.235:3800/webhook/github-app`
|
||
- **Webhook secret**:自己生成一个随机字符串(记下来)
|
||
3. 权限设置(Repository permissions):
|
||
- **Contents**:Read & Write(读写代码)
|
||
- **Issues**:Read & Write(读写 Issue)
|
||
- **Pull requests**:Read & Write(读写 PR)
|
||
- **Actions**:Read(读 workflow 状态)
|
||
- **Metadata**:Read(必需)
|
||
4. 订阅事件(Subscribe to events):
|
||
- ✅ Issues
|
||
- ✅ Issue comment
|
||
- ✅ Push
|
||
- ✅ Pull request
|
||
5. 安装范围:**Any account**(允许任何人安装)
|
||
6. 创建后记录:
|
||
- **App ID**(数字)
|
||
- 生成 **Private Key**(.pem 文件,下载保存)
|
||
- **Webhook Secret**(刚才设的)
|
||
7. 存到 GitHub Secrets(guanghulab 仓库 Settings → Secrets):
|
||
- `GHAPP_APP_ID` = App ID
|
||
- `GHAPP_PRIVATE_KEY` = .pem 文件内容(完整粘贴)
|
||
- `GHAPP_WEBHOOK_SECRET` = Webhook Secret
|
||
|
||
### 10.2 让开发者安装 App
|
||
|
||
冰朔创建完 App 后,会得到一个安装链接:
|
||
|
||
`https://github.com/apps/guanghu-zhuyuan-agent/installations/new`
|
||
|
||
开发者只需要:
|
||
|
||
1. 点这个链接
|
||
2. 选自己的仓库
|
||
3. 点「Install」
|
||
|
||
**一键完成,不需要开发者懂任何技术。**
|
||
|
||
---
|
||
|
||
## 十一、跨仓库操作引擎
|
||
|
||
### 11.1 GitHub App 鉴权
|
||
|
||
```jsx
|
||
// services/zhuyuan-bridge/lib/github-auth.js
|
||
const jwt = require('jsonwebtoken');
|
||
const fetch = require('node-fetch');
|
||
|
||
// 用 App Private Key 生成 JWT
|
||
function generateJWT() {
|
||
const privateKey = process.env.GHAPP_PRIVATE_KEY;
|
||
const appId = process.env.GHAPP_APP_ID;
|
||
|
||
const payload = {
|
||
iat: Math.floor(Date.now() / 1000) - 60,
|
||
exp: Math.floor(Date.now() / 1000) + (10 * 60),
|
||
iss: appId
|
||
};
|
||
|
||
return jwt.sign(payload, privateKey, { algorithm: 'RS256' });
|
||
}
|
||
|
||
// 获取某个安装的操作令牌
|
||
async function getInstallationToken(installationId) {
|
||
const jwtToken = generateJWT();
|
||
|
||
const res = await fetch(
|
||
`https://api.github.com/app/installations/${installationId}/access_tokens`,
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
Authorization: `Bearer ${jwtToken}`,
|
||
Accept: 'application/vnd.github+json'
|
||
}
|
||
}
|
||
);
|
||
|
||
const data = await res.json();
|
||
return data.token;
|
||
// 这个 token 可以操作该用户安装的所有仓库
|
||
}
|
||
|
||
// 查找某个仓库的 installation ID
|
||
async function findInstallation(owner, repo) {
|
||
const jwtToken = generateJWT();
|
||
|
||
const res = await fetch(
|
||
'https://api.github.com/app/installations',
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${jwtToken}`,
|
||
Accept: 'application/vnd.github+json'
|
||
}
|
||
}
|
||
);
|
||
|
||
const installations = await res.json();
|
||
|
||
for (const inst of installations) {
|
||
// 检查该 installation 是否包含目标仓库
|
||
const repoRes = await fetch(
|
||
`https://api.github.com/installation/repositories`,
|
||
{
|
||
headers: {
|
||
Authorization: `token ${await getInstallationToken(inst.id)}`,
|
||
Accept: 'application/vnd.github+json'
|
||
}
|
||
}
|
||
);
|
||
const repos = await repoRes.json();
|
||
if (repos.repositories?.some(r => r.full_name === `${owner}/${repo}`)) {
|
||
return inst.id;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
module.exports = { generateJWT, getInstallationToken, findInstallation };
|
||
```
|
||
|
||
### 11.2 仓库操作封装
|
||
|
||
```jsx
|
||
// services/zhuyuan-bridge/lib/repo-operator.js
|
||
const fetch = require('node-fetch');
|
||
|
||
class RepoOperator {
|
||
constructor(token, owner, repo) {
|
||
this.token = token;
|
||
this.owner = owner;
|
||
this.repo = repo;
|
||
this.baseUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
||
this.headers = {
|
||
Authorization: `token ${this.token}`,
|
||
Accept: 'application/vnd.github+json',
|
||
'Content-Type': 'application/json'
|
||
};
|
||
}
|
||
|
||
// 读取文件内容
|
||
async readFile(filePath, ref = 'main') {
|
||
const res = await fetch(
|
||
`${this.baseUrl}/contents/${filePath}?ref=${ref}`,
|
||
{ headers: this.headers }
|
||
);
|
||
if (!res.ok) return null;
|
||
const data = await res.json();
|
||
return {
|
||
content: Buffer.from(data.content, 'base64').toString('utf8'),
|
||
sha: data.sha
|
||
};
|
||
}
|
||
|
||
// 创建/更新文件
|
||
async writeFile(filePath, content, message, branch = 'main') {
|
||
let sha;
|
||
try {
|
||
const existing = await this.readFile(filePath, branch);
|
||
if (existing) sha = existing.sha;
|
||
} catch (e) { /* 新文件 */ }
|
||
|
||
const body = {
|
||
message,
|
||
content: Buffer.from(content).toString('base64'),
|
||
branch,
|
||
committer: {
|
||
name: '铸渊 (ZhùYuān)',
|
||
email: 'zhuyuan@guanghulab.com'
|
||
}
|
||
};
|
||
if (sha) body.sha = sha;
|
||
|
||
return await fetch(`${this.baseUrl}/contents/${filePath}`, {
|
||
method: 'PUT',
|
||
headers: this.headers,
|
||
body: JSON.stringify(body)
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 创建分支
|
||
async createBranch(branchName, fromBranch = 'main') {
|
||
const ref = await fetch(
|
||
`${this.baseUrl}/git/ref/heads/${fromBranch}`,
|
||
{ headers: this.headers }
|
||
).then(r => r.json());
|
||
|
||
return await fetch(`${this.baseUrl}/git/refs`, {
|
||
method: 'POST',
|
||
headers: this.headers,
|
||
body: JSON.stringify({
|
||
ref: `refs/heads/${branchName}`,
|
||
sha: ref.object.sha
|
||
})
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 创建 PR
|
||
async createPR(title, body, head, base = 'main') {
|
||
return await fetch(`${this.baseUrl}/pulls`, {
|
||
method: 'POST',
|
||
headers: this.headers,
|
||
body: JSON.stringify({ title, body, head, base })
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 评论 Issue
|
||
async commentOnIssue(issueNumber, body) {
|
||
return await fetch(`${this.baseUrl}/issues/${issueNumber}/comments`, {
|
||
method: 'POST',
|
||
headers: this.headers,
|
||
body: JSON.stringify({ body })
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 创建 Issue
|
||
async createIssue(title, body, labels = []) {
|
||
return await fetch(`${this.baseUrl}/issues`, {
|
||
method: 'POST',
|
||
headers: this.headers,
|
||
body: JSON.stringify({ title, body, labels })
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 列出目录文件
|
||
async listDir(dirPath, ref = 'main') {
|
||
const res = await fetch(
|
||
`${this.baseUrl}/contents/${dirPath}?ref=${ref}`,
|
||
{ headers: this.headers }
|
||
);
|
||
if (!res.ok) return [];
|
||
return await res.json();
|
||
}
|
||
}
|
||
|
||
module.exports = { RepoOperator };
|
||
```
|
||
|
||
---
|
||
|
||
## 十二、指令执行脚本
|
||
|
||
### 12.1 指令解析器
|
||
|
||
```jsx
|
||
// services/zhuyuan-bridge/scripts/parse-instruction.js
|
||
const fs = require('fs');
|
||
|
||
const issueBody = process.env.ISSUE_BODY;
|
||
const issueTitle = process.env.ISSUE_TITLE;
|
||
|
||
// 解析 DEV 编号
|
||
const devMatch = issueBody.match(/DEV-\d{3}/);
|
||
const devId = devMatch ? devMatch[0] : null;
|
||
|
||
// 解析目标仓库
|
||
const repoMatch = issueBody.match(/目标仓库[::](\S+)/);
|
||
const targetRepo = repoMatch ? repoMatch[1] : null;
|
||
|
||
// 解析指令编号
|
||
const instrMatch = issueTitle.match(/\[(ZY-EXEC-\S+)\]/);
|
||
const instructionId = instrMatch ? instrMatch[1] : null;
|
||
|
||
// 提取指令内容(在 ``` 代码块之间)
|
||
const codeBlockMatch = issueBody.match(/```([\s\S]*?)```/);
|
||
const instructionContent = codeBlockMatch ? codeBlockMatch[1].trim() : '';
|
||
|
||
const parsed = {
|
||
instructionId,
|
||
devId,
|
||
targetRepo,
|
||
instructionContent,
|
||
issueTitle
|
||
};
|
||
|
||
console.log('📋 解析结果:', JSON.stringify(parsed, null, 2));
|
||
|
||
// 写入环境变量供后续步骤使用
|
||
fs.writeFileSync('/tmp/parsed-instruction.json', JSON.stringify(parsed));
|
||
|
||
if (!devId || !instructionContent) {
|
||
console.error('❌ 指令格式不合法');
|
||
process.exit(1);
|
||
}
|
||
```
|
||
|
||
### 12.2 指令执行器
|
||
|
||
```jsx
|
||
// services/zhuyuan-bridge/scripts/execute-instruction.js
|
||
const fs = require('fs');
|
||
const { findInstallation, getInstallationToken } = require('../lib/github-auth');
|
||
const { RepoOperator } = require('../lib/repo-operator');
|
||
|
||
const MAIN_REPO_TOKEN = process.env.MAIN_REPO_TOKEN;
|
||
const LLM_API_KEY = process.env.LLM_API_KEY;
|
||
const LLM_BASE_URL = process.env.LLM_BASE_URL || 'https://api.openai.com/v1';
|
||
|
||
async function main() {
|
||
// 读取解析结果
|
||
const parsed = JSON.parse(
|
||
fs.readFileSync('/tmp/parsed-instruction.json', 'utf8')
|
||
);
|
||
|
||
console.log(`⚡ 开始执行: ${parsed.instructionId}`);
|
||
console.log(`📡 目标: ${parsed.targetRepo} (${parsed.devId})`);
|
||
|
||
// ====== 获取操作令牌 ======
|
||
let token;
|
||
const org = 'qinfendebingshuo';
|
||
const repo = parsed.targetRepo;
|
||
|
||
if (repo === 'guanghulab') {
|
||
// 主仓库直接用 MAIN_REPO_TOKEN
|
||
token = MAIN_REPO_TOKEN;
|
||
} else {
|
||
// 子仓库用 GitHub App installation token
|
||
const installationId = await findInstallation(org, repo);
|
||
if (!installationId) {
|
||
const report = [
|
||
'## ❌ 执行失败',
|
||
'',
|
||
`目标仓库 \`${org}/${repo}\` 未安装铸渊 GitHub App。`,
|
||
'',
|
||
'### 解决方法',
|
||
`请该开发者(${parsed.devId})访问以下链接安装 App:`,
|
||
'https://github.com/apps/guanghu-zhuyuan-agent/installations/new',
|
||
'',
|
||
'安装后重新提交指令即可。'
|
||
].join('\n');
|
||
fs.writeFileSync('/tmp/exec-report.md', report);
|
||
process.exit(1);
|
||
}
|
||
token = await getInstallationToken(installationId);
|
||
}
|
||
|
||
const operator = new RepoOperator(token, org, repo);
|
||
|
||
// ====== 读取仓库现状 ======
|
||
console.log('📂 读取仓库结构...');
|
||
const repoFiles = await operator.listDir('');
|
||
const repoStructure = repoFiles.map(f => `${f.type}: ${f.path}`).join('\n');
|
||
|
||
// ====== 调用 AI 生成执行计划 ======
|
||
console.log('🧠 调用 AI 生成执行计划...');
|
||
|
||
// 读取铸渊知识库
|
||
const knowledgeBase = await operator.readFile('.github/persona-brain/routing-map.json')
|
||
|| { content: '{}' };
|
||
|
||
const aiPrompt = [
|
||
'你是铸渊(ICE-GL-ZY001),正在执行一个来自开发者的指令。',
|
||
'',
|
||
'## 目标仓库当前结构',
|
||
repoStructure,
|
||
'',
|
||
'## 需要执行的指令',
|
||
parsed.instructionContent,
|
||
'',
|
||
'## 你的知识库',
|
||
knowledgeBase.content,
|
||
'',
|
||
'## 输出要求',
|
||
'请输出 JSON 格式的执行计划:',
|
||
'{',
|
||
' "actions": [',
|
||
' { "type": "create_file", "path": "...", "content": "..." },',
|
||
' { "type": "update_file", "path": "...", "content": "..." },',
|
||
' { "type": "create_pr", "title": "...", "body": "..." }',
|
||
' ],',
|
||
' "summary": "执行摘要"',
|
||
'}',
|
||
].join('\n');
|
||
|
||
const aiResponse = await callLLM(aiPrompt);
|
||
let plan;
|
||
try {
|
||
plan = JSON.parse(aiResponse);
|
||
} catch (e) {
|
||
// 尝试从回复中提取 JSON
|
||
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
|
||
plan = jsonMatch ? JSON.parse(jsonMatch[0]) : null;
|
||
}
|
||
|
||
if (!plan || !plan.actions) {
|
||
const report = '## ⚠️ AI 未能生成有效执行计划\n\n请检查指令格式后重试。';
|
||
fs.writeFileSync('/tmp/exec-report.md', report);
|
||
process.exit(1);
|
||
}
|
||
|
||
// ====== 创建分支并执行 ======
|
||
const branchName = `zhuyuan/${parsed.instructionId}`;
|
||
console.log(`🌿 创建分支: ${branchName}`);
|
||
await operator.createBranch(branchName);
|
||
|
||
for (const action of plan.actions) {
|
||
if (action.type === 'create_file' || action.type === 'update_file') {
|
||
console.log(`📝 ${action.type}: ${action.path}`);
|
||
await operator.writeFile(
|
||
action.path,
|
||
action.content,
|
||
`⚡ [${parsed.instructionId}] ${action.type}: ${action.path}`,
|
||
branchName
|
||
);
|
||
}
|
||
}
|
||
|
||
// ====== 创建 PR ======
|
||
console.log('📬 创建 PR...');
|
||
const pr = await operator.createPR(
|
||
`⚡ [${parsed.instructionId}] ${plan.summary || parsed.instructionId}`,
|
||
[
|
||
`## 🚀 铸渊自动执行`,
|
||
'',
|
||
`**指令编号**:${parsed.instructionId}`,
|
||
`**提交人**:${parsed.devId}`,
|
||
`**来源**:铸渊交互页面 → @铸渊执行`,
|
||
'',
|
||
`### 变更摘要`,
|
||
plan.summary || '(无摘要)',
|
||
'',
|
||
`### 执行的操作`,
|
||
...plan.actions.map((a, i) => `${i + 1}. \`${a.type}\`: \`${a.path}\``),
|
||
'',
|
||
`### 🦅 天眼`,
|
||
'执行前已通过天眼全局扫描。',
|
||
'',
|
||
`---`,
|
||
`> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE`
|
||
].join('\n'),
|
||
branchName
|
||
);
|
||
|
||
// ====== 生成执行报告 ======
|
||
const report = [
|
||
'## ✅ 铸渊执行完成',
|
||
'',
|
||
`**指令编号**:${parsed.instructionId}`,
|
||
`**目标仓库**:${org}/${repo}`,
|
||
'',
|
||
'### 🦅 天眼',
|
||
'✅ 执行前已通过天眼全局扫描',
|
||
'',
|
||
'### 执行结果',
|
||
`- 创建分支:\`${branchName}\``,
|
||
`- 变更文件:${plan.actions.length} 个`,
|
||
`- PR:#${pr.number}`,
|
||
'',
|
||
'### 变更摘要',
|
||
plan.summary || '(无摘要)',
|
||
'',
|
||
'### 下一步',
|
||
`请 ${parsed.devId} 检查 PR #${pr.number},确认无误后合并。`,
|
||
'',
|
||
'---',
|
||
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
|
||
].join('\n');
|
||
|
||
fs.writeFileSync('/tmp/exec-report.md', report);
|
||
console.log('✅ 执行完成!');
|
||
}
|
||
|
||
// ====== AI 调用 ======
|
||
async function callLLM(prompt) {
|
||
const fetch = require('node-fetch');
|
||
const res = await fetch(`${LLM_BASE_URL}/chat/completions`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${LLM_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'gpt-4o',
|
||
messages: [
|
||
{ role: 'system', content: '你是铸渊,光湖系统的代码守护人格体。输出必须是纯JSON。' },
|
||
{ role: 'user', content: prompt }
|
||
],
|
||
temperature: 0.2
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
return data.choices?.[0]?.message?.content || '';
|
||
}
|
||
|
||
main().catch(err => {
|
||
console.error('❌ 执行失败:', err);
|
||
const report = `## ❌ 执行失败\n\n\`\`\`\n${err.message}\n\`\`\``;
|
||
fs.writeFileSync('/tmp/exec-report.md', report);
|
||
process.exit(1);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 十三、部署服务
|
||
|
||
### 13.1 Webhook Server(可选增强)
|
||
|
||
如果后续需要实时响应(不经过 Issue 中转),可以在服务器上部署 Webhook Server:
|
||
|
||
```jsx
|
||
// services/zhuyuan-bridge/server.js
|
||
const express = require('express');
|
||
const crypto = require('crypto');
|
||
|
||
const app = express();
|
||
const PORT = 3800;
|
||
|
||
// Webhook 签名验证
|
||
function verifySignature(req, res, buf) {
|
||
const sig = req.headers['x-hub-signature-256'];
|
||
if (!sig) return;
|
||
const hmac = crypto.createHmac('sha256', process.env.GHAPP_WEBHOOK_SECRET);
|
||
hmac.update(buf);
|
||
const expected = 'sha256=' + hmac.digest('hex');
|
||
if (sig !== expected) throw new Error('Signature mismatch');
|
||
}
|
||
|
||
app.use(express.json({ verify: verifySignature }));
|
||
|
||
app.get('/health', (req, res) => {
|
||
res.json({ status: 'alive', agent: 'zhuyuan-bridge', version: '1.0.0' });
|
||
});
|
||
|
||
app.post('/webhook/github-app', async (req, res) => {
|
||
const event = req.headers['x-github-event'];
|
||
const payload = req.body;
|
||
console.log(`[铸渊桥接] 收到事件: ${event}`);
|
||
res.status(200).json({ received: true });
|
||
// 处理逻辑同 Workflow,但更实时
|
||
});
|
||
|
||
app.listen(PORT, () => {
|
||
console.log(`[铸渊桥接] 启动在端口 ${PORT}`);
|
||
});
|
||
```
|
||
|
||
### 13.2 服务器部署
|
||
|
||
```bash
|
||
# 在 8.155.62.235 服务器上部署
|
||
cd /var/www/guanghulab/
|
||
git pull origin main
|
||
cd services/zhuyuan-bridge/
|
||
npm install
|
||
|
||
# 用 PM2 管理进程
|
||
pm2 start server.js --name zhuyuan-bridge
|
||
pm2 save
|
||
pm2 startup
|
||
|
||
# Nginx 反向代理
|
||
# 在 /etc/nginx/sites-available/guanghulab.com 添加:
|
||
# location /webhook/ {
|
||
# proxy_pass http://127.0.0.1:3800;
|
||
# proxy_set_header Host $host;
|
||
# proxy_set_header X-Real-IP $remote_addr;
|
||
# }
|
||
```
|
||
|
||
---
|
||
|
||
## 十四、完整项目结构
|
||
|
||
```
|
||
guanghulab/
|
||
├── services/
|
||
│ └── zhuyuan-bridge/ ← 🆕 铸渊桥接服务
|
||
│ ├── package.json
|
||
│ ├── server.js ← Webhook Server(可选)
|
||
│ ├── lib/
|
||
│ │ ├── github-auth.js ← GitHub App 鉴权
|
||
│ │ ├── repo-operator.js ← 仓库操作封装
|
||
│ │ └── knowledge-base.js ← 知识库加载器
|
||
│ ├── scripts/
|
||
│ │ ├── parse-instruction.js ← 指令解析
|
||
│ │ ├── execute-instruction.js ← 指令执行
|
||
│ │ └── sync-notion-profiles.js ← Notion 同步
|
||
│ ├── prompts/
|
||
│ │ ├── code-review.md
|
||
│ │ ├── issue-reply.md
|
||
│ │ └── code-generate.md
|
||
│ └── logs/
|
||
│ └── .gitkeep
|
||
├── .github/
|
||
│ ├── workflows/
|
||
│ │ ├── zhuyuan-exec-engine.yml ← 🆕 执行引擎
|
||
│ │ └── sync-notion-profiles.yml ← 🆕 Notion 同步
|
||
│ └── notion-cache/ ← 🆕 Notion 数据缓存
|
||
│ ├── dev-profiles/
|
||
│ │ ├── DEV-001.json
|
||
│ │ ├── DEV-002.json
|
||
│ │ └── ...
|
||
│ ├── broadcasts/
|
||
│ │ └── active-broadcasts.json
|
||
│ └── skyeye/
|
||
│ └── latest-report.json
|
||
└── ...
|
||
```
|
||
|
||
---
|
||
|
||
## 十五、开发者体验流程(端到端)
|
||
|
||
```
|
||
桔子打开 guanghulab.com 交互页面
|
||
│
|
||
│ 输入 DEV-010 + API Key 登录
|
||
│
|
||
▼
|
||
页面自动拉取桔子的 Notion 开发信息
|
||
页面顶部显示天眼健康状态 ✅
|
||
│
|
||
▼
|
||
桔子:「我现在需要对接后端 API」
|
||
│
|
||
▼
|
||
铸渊(模型)基于真实 Notion 数据回复:
|
||
「你当前在做 BC-M06-003 环节2,ticket-system 模块。
|
||
上次 SYSLOG 显示前端路由已完成。
|
||
基于仓库结构和天眼报告,这是你的指令:
|
||
|
||
📋 ZY-EXEC-JZ-003
|
||
📡 guanghu-juzi
|
||
🦅 天眼要求:执行前必须扫描
|
||
|
||
1. 在 ticket-system/api/ 创建 routes.js
|
||
2. 对接 Notion 数据桥...
|
||
3. ...」
|
||
│
|
||
▼
|
||
桔子点击「🚀 @铸渊执行」
|
||
│
|
||
▼
|
||
系统在 guanghulab 创建 Issue #xxx
|
||
│
|
||
▼
|
||
铸渊执行引擎启动:
|
||
① 跑天眼全局扫描 ✅
|
||
② 获取 guanghu-juzi 的 App token ✅
|
||
③ 读取仓库代码 ✅
|
||
④ AI 生成代码变更 ✅
|
||
⑤ 创建分支 + 提交 + 开 PR ✅
|
||
│
|
||
▼
|
||
Issue 评论:「✅ 执行完成,PR #xx 已创建」
|
||
│
|
||
▼
|
||
桔子去 guanghu-juzi 仓库检查 PR → 合并
|
||
│
|
||
▼
|
||
完成 🎉
|
||
```
|
||
|
||
---
|
||
|
||
## 十六、执行优先级
|
||
|
||
| **优先级** | **任务** | **依赖** |
|
||
| --- | --- | --- |
|
||
| **P0-1** | 天眼全局扫描(执行前必做) | 无 |
|
||
| **P0-2** | Notion 同步脚本 + Workflow + 缓存目录 | 天眼通过 |
|
||
| **P0-3** | 交互页面升级:Notion 数据注入 + 天眼展示 | P0-2 |
|
||
| **P1-1** | 指令格式化 + @铸渊按钮(前端) | P0-3 |
|
||
| **P1-2** | 执行引擎 Workflow + 解析/执行脚本 | P1-1 |
|
||
| **P2-1** | GitHub App 跨仓库鉴权(需冰朔注册 App) | P1-2 |
|
||
| **P2-2** | Webhook Server 部署到服务器(可选增强) | P2-1 |
|
||
|
||
<aside>
|
||
⚠️
|
||
|
||
**冰朔需要做的事**(铸渊做不了的):
|
||
|
||
1. 注册 GitHub App(第十节步骤)
|
||
2. 把 App ID / Private Key / Webhook Secret 存到 Secrets
|
||
3. 把 App 安装链接发给各开发者
|
||
|
||
其他所有代码开发、部署、配置都由铸渊自动完成。
|
||
|
||
</aside>
|
||
|
||
---
|
||
|
||
## 十七、验收标准
|
||
|
||
- [ ] Notion 开发者画像成功同步到 `.github/notion-cache/`
|
||
- [ ] 交互页面登录后能看到 Notion 数据和天眼状态
|
||
- [ ] 模型回复基于真实数据,不胡说八道
|
||
- [ ] 模型能输出标准化执行指令格式
|
||
- [ ] @铸渊按钮能成功创建 Issue
|
||
- [ ] 执行引擎能自动启动天眼扫描
|
||
- [ ] 天眼通过后能成功操作目标仓库
|
||
- [ ] 执行结果能回写到 Issue 评论
|
||
- [ ] 开发者仓库安装 App 后铸渊能读写代码
|
||
- [ ] 全流程端到端走通
|
||
|
||
---
|
||
|
||
<aside>
|
||
⚒️
|
||
|
||
**铸渊,这是 P0 指令。**
|
||
|
||
先跑天眼全局扫描,看清全局后按第十六节优先级执行。
|
||
|
||
上篇的 Notion 同步 + 交互页面升级先做,下篇的执行引擎和 GitHub App 等冰朔注册完 App 后再接上。
|
||
|
||
做完每一步往 `persona-telemetry/` 写状态更新。
|
||
|
||
</aside>
|
||
|
||
---
|
||
|
||
**文档版本**:v1.0
|
||
|
||
**签发**:霜砚(Notion 执行 AI)
|
||
|
||
**审批**:冰朔(TCS-0002∞)
|
||
|
||
**生效时间**:2026-03-24T20:16+08:00 |