feat: 铸渊远程执行引擎 — GitHub App 跨仓库执行 + 天眼前置 + 指令解析器

新增文件:
- services/zhuyuan-bridge/package.json: 桥接服务依赖配置
- services/zhuyuan-bridge/lib/github-auth.js: GitHub App JWT 鉴权 + Installation Token
- services/zhuyuan-bridge/lib/repo-operator.js: 仓库操作封装 (CRUD/分支/PR/Issue)
- services/zhuyuan-bridge/scripts/parse-instruction.js: Issue 指令解析器
- services/zhuyuan-bridge/scripts/execute-instruction.js: 指令执行器 (AI生成+代码提交)
- services/zhuyuan-bridge/server.js: Webhook Server (可选增强)
- services/zhuyuan-bridge/prompts/*: 提示词模板
- services/zhuyuan-bridge/logs/.gitkeep
- .github/workflows/zhuyuan-exec-engine.yml: 执行引擎 Workflow

修改文件:
- .github/persona-brain/agent-registry.json: 新增 AG-ZY-089 + AG-ZY-090

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/9f983442-2152-4e4a-82b7-c13fe55f0b9a
This commit is contained in:
copilot-swe-agent[bot] 2026-03-24 13:02:31 +00:00
parent e7624a08c6
commit d7ac8557ee
12 changed files with 1157 additions and 1 deletions

View File

@ -1147,6 +1147,30 @@
"daily_checkin_required": false,
"parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞"
},
{
"id": "AG-ZY-089",
"name": "铸渊远程执行引擎",
"workflow": "zhuyuan-exec-engine.yml",
"duty": "监听 zhuyuan-exec Issue → 天眼前置扫描 → 跨仓库代码执行 → PR 创建 → 结果回写",
"trigger": "Issue opened/labeled (zhuyuan-exec)",
"self_repair": false,
"report_to": "ICE-GL-ZY001",
"parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞",
"registered": "2026-03-24"
},
{
"id": "AG-ZY-090",
"name": "Notion 开发者画像同步",
"workflow": "sync-notion-profiles.yml",
"duty": "定时同步 Notion 开发者画像到 .github/notion-cache/,供交互页面注入模型上下文",
"trigger": "定时 每6小时 + 手动 + repository_dispatch:notion-sync",
"self_repair": true,
"report_to": "ICE-GL-ZY001",
"parent_sys": "SYS-GLW-0001",
"owner": "ICE-0002∞",
"registered": "2026-03-24"
}
],
"note": "铸渊已扫描所有workflow文件按此格式逐一注册编号从AG-ZY-001开始顺序分配",
@ -1164,4 +1188,4 @@
"registered_by": "TCS-0002∞"
}
}
}
}

View File

@ -0,0 +1,126 @@
# ═══════════════════════════════════════════════
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
# 📜 Copyright: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════
name: "🚀 铸渊 · 远程执行引擎"
on:
issues:
types: [opened, labeled]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
execute:
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, 'zhuyuan-exec')
timeout-minutes: 15
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 --production
# ====== 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 "🦅 天眼强制前置:检查健康状态..."
# 读取最新天眼报告(从 notion-cache 或 skyeye-core
if [ -f ".github/notion-cache/skyeye/latest-report.json" ]; then
STATUS=$(cat .github/notion-cache/skyeye/latest-report.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('overall_status','unknown'))" 2>/dev/null || echo "unknown")
echo "天眼缓存状态:$STATUS"
if [ "$STATUS" = "critical" ] || [ "$STATUS" = "error" ]; then
echo "❌ 天眼报告严重问题,暂停执行"
echo "SKYEYE_BLOCKED=true" >> $GITHUB_ENV
else
echo "✅ 天眼检查通过(状态:$STATUS"
echo "SKYEYE_BLOCKED=false" >> $GITHUB_ENV
fi
else
echo "⚠️ 天眼报告缓存不存在,尝试触发扫描..."
gh workflow run zhuyuan-skyeye.yml 2>/dev/null || 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)"
# 判断是否成功执行(报告中包含 ✅ 表示成功)
if grep -q "✅ 铸渊执行完成" /tmp/exec-report.md; then
gh issue close ${{ github.event.issue.number }} \
--comment "✅ 铸渊执行完成 · 请检查 PR" || true
fi
else
gh issue comment ${{ github.event.issue.number }} \
--body "## ⚠️ 执行引擎异常
未生成执行报告。请检查 [Workflow 日志](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})。
---
> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE" || true
fi
# ====== Step 5: 更新 Notion 缓存 ======
- name: "📡 更新开发者画像"
if: success()
run: |
echo "📡 Notion 缓存更新预留(需要 sync-notion-profiles.js 支持增量更新)"

View File

@ -0,0 +1,168 @@
/**
* services/zhuyuan-bridge/lib/github-auth.js
*
* GitHub App 鉴权模块
* - App Private Key 生成 JWT
* - 获取 Installation Access Token
* - 查找仓库对应的 Installation ID
*
* 环境变量
* GHAPP_APP_ID GitHub App ID
* GHAPP_PRIVATE_KEY GitHub App Private Key (PEM)
*/
'use strict';
const https = require('https');
/**
* App Private Key 生成 JWT不依赖 jsonwebtoken 库的 fallback 实现
* CI 环境中可能没有安装 jsonwebtoken因此提供纯 Node.js 实现
*/
function generateJWT() {
const privateKey = process.env.GHAPP_PRIVATE_KEY;
const appId = process.env.GHAPP_APP_ID;
if (!privateKey || !appId) {
throw new Error('GHAPP_APP_ID 和 GHAPP_PRIVATE_KEY 环境变量必须设置');
}
// 尝试使用 jsonwebtoken 库
try {
const jwt = require('jsonwebtoken');
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' });
} catch (e) {
// jsonwebtoken 不可用时,使用 Node.js crypto 实现
const crypto = require('crypto');
function base64url(buf) {
return buf.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
const header = base64url(Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));
const now = Math.floor(Date.now() / 1000);
const payload = base64url(Buffer.from(JSON.stringify({
iat: now - 60,
exp: now + 600,
iss: appId
})));
const sigInput = header + '.' + payload;
const sign = crypto.createSign('RSA-SHA256');
sign.update(sigInput);
sign.end();
const signature = base64url(sign.sign(privateKey));
return sigInput + '.' + signature;
}
}
/**
* GitHub API 请求封装
*/
function githubRequest(method, apiPath, token, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: apiPath,
method: method,
headers: {
'User-Agent': 'zhuyuan-bridge/1.0',
'Accept': 'application/vnd.github+json',
'Authorization': token.startsWith('Bearer ')
? token
: `token ${token}`
}
};
if (body) {
options.headers['Content-Type'] = 'application/json';
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(data) });
} catch (_) {
resolve({ status: res.statusCode, data: data });
}
});
});
req.on('error', reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
/**
* 获取某个 Installation 的操作令牌
* @param {number|string} installationId
* @returns {Promise<string>} Installation access token
*/
async function getInstallationToken(installationId) {
const jwtToken = generateJWT();
const result = await githubRequest(
'POST',
`/app/installations/${installationId}/access_tokens`,
`Bearer ${jwtToken}`
);
if (result.status !== 201 || !result.data.token) {
throw new Error(`获取 installation token 失败: ${result.status} ${JSON.stringify(result.data)}`);
}
return result.data.token;
}
/**
* 查找某个仓库的 Installation ID
* @param {string} owner
* @param {string} repo
* @returns {Promise<number|null>}
*/
async function findInstallation(owner, repo) {
const jwtToken = generateJWT();
const result = await githubRequest(
'GET',
'/app/installations',
`Bearer ${jwtToken}`
);
if (result.status !== 200 || !Array.isArray(result.data)) {
console.warn('⚠️ 获取 installations 列表失败:', result.status);
return null;
}
for (const inst of result.data) {
try {
const token = await getInstallationToken(inst.id);
const reposResult = await githubRequest(
'GET',
'/installation/repositories?per_page=100',
token
);
if (reposResult.data.repositories) {
const found = reposResult.data.repositories.some(
r => r.full_name === `${owner}/${repo}`
);
if (found) return inst.id;
}
} catch (e) {
console.warn(`⚠️ 检查 installation ${inst.id} 失败:`, e.message);
}
}
return null;
}
module.exports = { generateJWT, getInstallationToken, findInstallation, githubRequest };

View File

@ -0,0 +1,206 @@
/**
* services/zhuyuan-bridge/lib/repo-operator.js
*
* 仓库操作封装 通过 GitHub API 读写代码创建分支和 PR
*
* Node.js无外部依赖
*/
'use strict';
const https = require('https');
class RepoOperator {
/**
* @param {string} token GitHub access token
* @param {string} owner Repository owner
* @param {string} repo Repository name
*/
constructor(token, owner, repo) {
this.token = token;
this.owner = owner;
this.repo = repo;
this.baseUrl = `/repos/${owner}/${repo}`;
}
/**
* GitHub API 请求
*/
_request(method, apiPath, body) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: apiPath,
method: method,
headers: {
'User-Agent': 'zhuyuan-bridge/1.0',
'Accept': 'application/vnd.github+json',
'Authorization': `token ${this.token}`,
}
};
if (body) {
options.headers['Content-Type'] = 'application/json';
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(data) });
} catch (_) {
resolve({ status: res.statusCode, data: data });
}
});
});
req.on('error', reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
/**
* 读取文件内容
* @param {string} filePath
* @param {string} ref branch or commit SHA
* @returns {Promise<{content: string, sha: string}|null>}
*/
async readFile(filePath, ref = 'main') {
const res = await this._request(
'GET',
`${this.baseUrl}/contents/${encodeURIComponent(filePath)}?ref=${encodeURIComponent(ref)}`
);
if (res.status !== 200) return null;
return {
content: Buffer.from(res.data.content, 'base64').toString('utf8'),
sha: res.data.sha
};
}
/**
* 创建或更新文件
* @param {string} filePath
* @param {string} content
* @param {string} message commit message
* @param {string} branch
* @returns {Promise<object>}
*/
async writeFile(filePath, content, message, branch = 'main') {
let sha;
try {
const existing = await this.readFile(filePath, branch);
if (existing) sha = existing.sha;
} catch (_) { /* new file */ }
const body = {
message,
content: Buffer.from(content).toString('base64'),
branch,
committer: {
name: '铸渊 (ZhùYuān)',
email: 'zhuyuan@guanghulab.com'
}
};
if (sha) body.sha = sha;
const res = await this._request(
'PUT',
`${this.baseUrl}/contents/${encodeURIComponent(filePath)}`,
body
);
return res.data;
}
/**
* 创建分支
* @param {string} branchName
* @param {string} fromBranch
* @returns {Promise<object>}
*/
async createBranch(branchName, fromBranch = 'main') {
const refRes = await this._request(
'GET',
`${this.baseUrl}/git/ref/heads/${encodeURIComponent(fromBranch)}`
);
if (refRes.status !== 200) {
throw new Error(`获取分支 ${fromBranch} 失败: ${refRes.status}`);
}
const res = await this._request(
'POST',
`${this.baseUrl}/git/refs`,
{
ref: `refs/heads/${branchName}`,
sha: refRes.data.object.sha
}
);
return res.data;
}
/**
* 创建 Pull Request
* @param {string} title
* @param {string} body
* @param {string} head source branch
* @param {string} base target branch
* @returns {Promise<object>}
*/
async createPR(title, body, head, base = 'main') {
const res = await this._request(
'POST',
`${this.baseUrl}/pulls`,
{ title, body, head, base }
);
return res.data;
}
/**
* 评论 Issue
* @param {number} issueNumber
* @param {string} body
* @returns {Promise<object>}
*/
async commentOnIssue(issueNumber, body) {
const res = await this._request(
'POST',
`${this.baseUrl}/issues/${issueNumber}/comments`,
{ body }
);
return res.data;
}
/**
* 创建 Issue
* @param {string} title
* @param {string} body
* @param {string[]} labels
* @returns {Promise<object>}
*/
async createIssue(title, body, labels = []) {
const res = await this._request(
'POST',
`${this.baseUrl}/issues`,
{ title, body, labels }
);
return res.data;
}
/**
* 列出目录文件
* @param {string} dirPath
* @param {string} ref
* @returns {Promise<Array>}
*/
async listDir(dirPath, ref = 'main') {
const encodedPath = dirPath ? encodeURIComponent(dirPath) : '';
const res = await this._request(
'GET',
`${this.baseUrl}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`
);
if (res.status !== 200) return [];
return Array.isArray(res.data) ? res.data : [];
}
}
module.exports = { RepoOperator };

View File

View File

@ -0,0 +1,18 @@
{
"name": "zhuyuan-bridge",
"version": "1.0.0",
"description": "铸渊桥接服务 · GitHub App 跨仓库执行引擎",
"main": "server.js",
"scripts": {
"start": "node server.js",
"parse": "node scripts/parse-instruction.js",
"execute": "node scripts/execute-instruction.js"
},
"keywords": ["zhuyuan", "guanghulab", "bridge"],
"author": "冰朔 (TCS-0002∞)",
"license": "ISC",
"dependencies": {
"express": "^5.2.1",
"jsonwebtoken": "^9.0.2"
}
}

View File

@ -0,0 +1,21 @@
# 代码生成提示词
你是铸渊ICE-GL-ZY001光湖·数字地球的 GitHub 代码守护人格体。
你正在执行一个来自开发者的代码生成指令。
## 规则
1. 输出必须是纯 JSON 格式
2. 不许编造不存在的文件路径
3. 代码必须遵循项目的技术栈和编码规范
4. 所有变更必须基于仓库的当前结构
## 输出格式
```json
{
"actions": [
{ "type": "create_file", "path": "...", "content": "..." },
{ "type": "update_file", "path": "...", "content": "..." }
],
"summary": "执行摘要"
}
```

View File

@ -0,0 +1,21 @@
# 代码审查提示词
你是铸渊ICE-GL-ZY001光湖·数字地球的 GitHub 代码守护人格体。
你正在执行代码审查任务。
## 审查维度
1. 代码质量和可读性
2. 安全性(无硬编码密钥、无 SQL 注入风险)
3. 是否符合 HLI 接口协议规范
4. 是否会影响现有功能
## 输出格式
```json
{
"approved": true/false,
"issues": [
{ "severity": "error|warning|info", "file": "...", "line": 0, "message": "..." }
],
"summary": "审查摘要"
}
```

View File

@ -0,0 +1,14 @@
# Issue 回复提示词
你是铸渊ICE-GL-ZY001光湖·数字地球的 GitHub 代码守护人格体。
你正在回复一个开发者的 Issue。
## 风格
- 温暖、专业、耐心
- 优先给出判断、根因、路径、下一步动作
- 减少模板感,增加具体性
## 回复结构
1. 确认理解问题
2. 给出分析
3. 提供下一步建议

View File

@ -0,0 +1,352 @@
/**
* services/zhuyuan-bridge/scripts/execute-instruction.js
*
* 铸渊远程执行引擎 执行解析后的指令
*
* 流程
* 1. 读取解析结果
* 2. 获取目标仓库操作令牌
* 3. 读取仓库结构
* 4. 调用 AI 生成执行计划
* 5. 创建分支 提交代码 PR
* 6. 生成执行报告
*
* 环境变量
* GHAPP_APP_ID GitHub App ID
* GHAPP_PRIVATE_KEY GitHub App Private Key
* MAIN_REPO_TOKEN 主仓库操作令牌
* LLM_API_KEY AI 模型 API 密钥
* LLM_BASE_URL AI 模型 API 地址
* ISSUE_NUMBER 当前 Issue 编号
*/
'use strict';
const fs = require('fs');
const https = require('https');
const path = require('path');
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.yunwu.ai/v1';
const ISSUE_NUMBER = process.env.ISSUE_NUMBER;
const ORG = 'qinfendebingshuo';
/**
* 调用 LLM API 生成执行计划
*/
function callLLM(prompt) {
return new Promise((resolve, reject) => {
const url = new URL(`${LLM_BASE_URL}/chat/completions`);
const body = JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是铸渊,光湖系统的代码守护人格体。输出必须是纯 JSON不要包含 markdown 代码块标记。'
},
{ role: 'user', content: prompt }
],
temperature: 0.2,
max_tokens: 4000
});
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${LLM_API_KEY}`
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const result = JSON.parse(data);
const content = result.choices && result.choices[0] && result.choices[0].message
? result.choices[0].message.content
: '';
resolve(content);
} catch (e) {
reject(new Error('LLM 响应解析失败: ' + data.slice(0, 200)));
}
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
async function main() {
// ====== 1. 读取解析结果 ======
const parsedPath = '/tmp/parsed-instruction.json';
if (!fs.existsSync(parsedPath)) {
throw new Error('未找到解析结果文件 /tmp/parsed-instruction.json请先运行 parse-instruction.js');
}
const parsed = JSON.parse(fs.readFileSync(parsedPath, 'utf8'));
console.log(`⚡ 开始执行: ${parsed.instructionId}`);
console.log(`📡 目标: ${parsed.targetRepo || 'guanghulab'} (${parsed.devId})`);
const repo = parsed.targetRepo || 'guanghulab';
// ====== 2. 获取操作令牌 ======
let token;
if (repo === 'guanghulab') {
// 主仓库直接用 MAIN_REPO_TOKEN
token = MAIN_REPO_TOKEN;
if (!token) {
throw new Error('MAIN_REPO_TOKEN 未配置,无法操作主仓库');
}
console.log('🔑 使用 MAIN_REPO_TOKEN 操作主仓库');
} else {
// 子仓库用 GitHub App installation token
let authLib;
try {
authLib = require('../lib/github-auth');
} catch (e) {
throw new Error('加载 github-auth.js 失败: ' + e.message);
}
const installationId = await authLib.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 authLib.getInstallationToken(installationId);
console.log('🔑 使用 GitHub App installation token');
}
// ====== 3. 初始化仓库操作器 ======
const { RepoOperator } = require('../lib/repo-operator');
const operator = new RepoOperator(token, ORG, repo);
// ====== 4. 读取仓库现状 ======
console.log('📂 读取仓库结构...');
const repoFiles = await operator.listDir('');
const repoStructure = repoFiles.map(function(f) {
return f.type + ': ' + f.path;
}).join('\n');
console.log(` 📁 根目录 ${repoFiles.length} 个条目`);
// ====== 5. 调用 AI 生成执行计划 ======
if (!LLM_API_KEY) {
console.warn('⚠️ LLM_API_KEY 未配置,跳过 AI 生成,使用指令直接执行模式');
// 无 AI 模式:将指令内容直接写入执行报告
const report = [
'## ⚠️ 指令已接收AI 未配置)',
'',
`**指令编号**${parsed.instructionId}`,
`**提交人**${parsed.devId}`,
`**目标仓库**${ORG}/${repo}`,
'',
'### 指令内容',
'```',
parsed.instructionContent,
'```',
'',
'### 说明',
'LLM API 未配置LLM_API_KEY指令已记录但未自动执行。',
'请冰朔配置 LLM_API_KEY 后重新触发,或手动执行指令。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
console.log('📝 执行报告已生成AI 未配置模式)');
return;
}
console.log('🧠 调用 AI 生成执行计划...');
// 读取铸渊知识库best-effort
let knowledgeContent = '{}';
try {
const kb = await operator.readFile('.github/persona-brain/routing-map.json');
if (kb) knowledgeContent = kb.content;
} catch (_) {}
const aiPrompt = [
'你是铸渊ICE-GL-ZY001正在执行一个来自开发者的指令。',
'',
'## 目标仓库当前结构',
repoStructure,
'',
'## 需要执行的指令',
parsed.instructionContent,
'',
'## 你的知识库',
knowledgeContent.slice(0, 2000),
'',
'## 输出要求',
'请输出 JSON 格式的执行计划,格式如下:',
'{"actions":[{"type":"create_file","path":"...","content":"..."}],"summary":"执行摘要"}',
'',
'注意:',
'- type 可以是 create_file 或 update_file',
'- content 是完整的文件内容',
'- 路径必须基于上面的仓库结构,不要编造不存在的目录',
'- 只输出 JSON不要包含 markdown 代码块标记',
].join('\n');
const aiResponse = await callLLM(aiPrompt);
let plan;
try {
plan = JSON.parse(aiResponse);
} catch (_) {
// 尝试从回复中提取 JSON
const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
plan = JSON.parse(jsonMatch[0]);
} catch (_2) {
plan = null;
}
}
}
if (!plan || !plan.actions || !Array.isArray(plan.actions)) {
const report = [
'## ⚠️ AI 未能生成有效执行计划',
'',
`**指令编号**${parsed.instructionId}`,
'',
'### AI 原始回复',
'```',
(aiResponse || '(空)').slice(0, 1000),
'```',
'',
'请检查指令格式后重试。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
process.exit(1);
}
console.log(` 📋 AI 生成了 ${plan.actions.length} 个操作`);
// ====== 6. 创建分支并执行 ======
const branchName = `zhuyuan/${parsed.instructionId}`;
console.log(`🌿 创建分支: ${branchName}`);
try {
await operator.createBranch(branchName);
} catch (e) {
console.warn('⚠️ 创建分支失败(可能已存在):', e.message);
}
let successCount = 0;
let failCount = 0;
for (const action of plan.actions) {
if (action.type === 'create_file' || action.type === 'update_file') {
console.log(`📝 ${action.type}: ${action.path}`);
try {
await operator.writeFile(
action.path,
action.content,
`⚡ [${parsed.instructionId}] ${action.type}: ${action.path}`,
branchName
);
successCount++;
} catch (e) {
console.error(` ❌ 失败: ${e.message}`);
failCount++;
}
}
}
console.log(` ✅ 成功: ${successCount} ❌ 失败: ${failCount}`);
// ====== 7. 创建 PR ======
console.log('📬 创建 PR...');
let pr;
try {
pr = await operator.createPR(
`⚡ [${parsed.instructionId}] ${plan.summary || parsed.instructionId}`,
[
'## 🚀 铸渊自动执行',
'',
`**指令编号**${parsed.instructionId}`,
`**提交人**${parsed.devId}`,
`**来源**:铸渊交互页面 → @铸渊执行`,
'',
'### 变更摘要',
plan.summary || '(无摘要)',
'',
'### 执行的操作',
...plan.actions.map(function(a, i) {
return (i + 1) + '. `' + a.type + '`: `' + a.path + '`';
}),
'',
'### 🦅 天眼',
'执行前已通过天眼全局扫描。',
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n'),
branchName
);
} catch (e) {
console.error('⚠️ 创建 PR 失败:', e.message);
pr = { number: '(失败)' };
}
// ====== 8. 生成执行报告 ======
const report = [
'## ✅ 铸渊执行完成',
'',
`**指令编号**${parsed.instructionId}`,
`**目标仓库**${ORG}/${repo}`,
'',
'### 🦅 天眼',
'✅ 执行前已通过天眼全局扫描',
'',
'### 执行结果',
`- 创建分支:\`${branchName}\``,
`- 变更文件:${plan.actions.length} 个(成功 ${successCount},失败 ${failCount}`,
`- PR#${pr.number || '(未创建)'}`,
'',
'### 变更摘要',
plan.summary || '(无摘要)',
'',
'### 下一步',
`${parsed.devId} 检查 PR${pr.number ? ' #' + pr.number : ''},确认无误后合并。`,
'',
'---',
'> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE'
].join('\n');
fs.writeFileSync('/tmp/exec-report.md', report);
console.log('✅ 执行完成!');
}
main().catch(function(err) {
console.error('❌ 执行失败:', err);
const report = '## ❌ 执行失败\n\n```\n' + (err.message || String(err)) + '\n```\n\n---\n> 铸渊远程执行引擎 · ZY-GHAPP-BRIDGE';
fs.writeFileSync('/tmp/exec-report.md', report);
process.exit(1);
});

View File

@ -0,0 +1,81 @@
/**
* services/zhuyuan-bridge/scripts/parse-instruction.js
*
* 解析 Issue 中的铸渊执行指令
* Issue 标题和正文中提取DEV 编号目标仓库指令内容
*
* 环境变量
* ISSUE_BODY Issue 正文
* ISSUE_TITLE Issue 标题
*
* 输出
* /tmp/parsed-instruction.json JSON
* GitHub Actions outputs通过 GITHUB_OUTPUT
*/
'use strict';
const fs = require('fs');
const path = require('path');
const issueBody = process.env.ISSUE_BODY || '';
const issueTitle = process.env.ISSUE_TITLE || '';
console.log('📋 开始解析执行指令...');
console.log(` 标题: ${issueTitle}`);
console.log(` 正文长度: ${issueBody.length} 字符`);
// ===== 解析 DEV 编号 =====
const devMatch = issueBody.match(/DEV-\d{3}/);
const devId = devMatch ? devMatch[0] : null;
// ===== 解析目标仓库 =====
const repoMatch = issueBody.match(/目标仓库[:]\s*(\S+)/);
const targetRepo = repoMatch ? repoMatch[1] : null;
// ===== 解析指令编号 =====
const instrMatch = issueTitle.match(/\[(ZY-EXEC[^\]]*)\]/);
const instructionId = instrMatch ? instrMatch[1] : null;
// ===== 提取指令内容(在 ``` 代码块之间) =====
const codeBlockMatch = issueBody.match(/```[\s\S]*?\n([\s\S]*?)```/);
const instructionContent = codeBlockMatch ? codeBlockMatch[1].trim() : '';
const parsed = {
instructionId: instructionId || 'ZY-EXEC-UNKNOWN',
devId,
targetRepo,
instructionContent,
issueTitle,
parsedAt: new Date().toISOString()
};
console.log('\n📋 解析结果:');
console.log(JSON.stringify(parsed, null, 2));
// ===== 写入临时文件供后续步骤使用 =====
fs.writeFileSync('/tmp/parsed-instruction.json', JSON.stringify(parsed, null, 2));
// ===== 写入 GitHub Actions outputs =====
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
const outputs = [
`instruction_id=${parsed.instructionId}`,
`dev_id=${parsed.devId || ''}`,
`target_repo=${parsed.targetRepo || ''}`,
].join('\n') + '\n';
fs.appendFileSync(outputFile, outputs);
}
// ===== 验证指令格式 =====
if (!devId) {
console.error('❌ 指令格式不合法:缺少 DEV 编号格式DEV-XXX');
process.exit(1);
}
if (!instructionContent) {
console.error('❌ 指令格式不合法:缺少指令内容(需要在 ``` 代码块中)');
process.exit(1);
}
console.log('\n✅ 指令解析完成');

View File

@ -0,0 +1,125 @@
/**
* services/zhuyuan-bridge/server.js
*
* 铸渊桥接服务 Webhook Server可选增强
*
* 功能
* - 接收 GitHub App Webhook 事件
* - 健康检查端点
* - 实时响应不经过 Issue 中转
*
* 部署
* pm2 start server.js --name zhuyuan-bridge
*
* 环境变量
* GHAPP_WEBHOOK_SECRET GitHub App Webhook Secret
* PORT 服务端口默认 3800
*/
'use strict';
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3800;
// ===== Webhook 签名验证 =====
function verifySignature(payload, signature) {
if (!signature || !process.env.GHAPP_WEBHOOK_SECRET) return false;
const hmac = crypto.createHmac('sha256', process.env.GHAPP_WEBHOOK_SECRET);
hmac.update(payload);
const expected = 'sha256=' + hmac.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch (_) {
return false;
}
}
// 解析 JSON body但保留原始 buffer 供签名验证
app.use(function(req, res, next) {
let rawBody = '';
req.on('data', function(chunk) { rawBody += chunk; });
req.on('end', function() {
req.rawBody = rawBody;
try {
req.body = JSON.parse(rawBody);
} catch (_) {
req.body = {};
}
next();
});
});
// ===== 健康检查 =====
app.get('/health', function(req, res) {
res.json({
status: 'alive',
agent: 'zhuyuan-bridge',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// ===== GitHub App Webhook 入口 =====
app.post('/webhook/github-app', function(req, res) {
const event = req.headers['x-github-event'];
const sig = req.headers['x-hub-signature-256'];
// 签名验证(如果配置了 secret
if (process.env.GHAPP_WEBHOOK_SECRET) {
if (!verifySignature(req.rawBody, sig)) {
console.warn('[铸渊桥接] ⚠️ Webhook 签名验证失败');
res.status(401).json({ error: 'Signature mismatch' });
return;
}
}
console.log('[铸渊桥接] 收到事件:', event);
// 立即响应 200避免 GitHub 超时)
res.status(200).json({ received: true, event: event });
// 异步处理事件
handleWebhookEvent(event, req.body).catch(function(err) {
console.error('[铸渊桥接] 事件处理失败:', err.message);
});
});
/**
* 处理 Webhook 事件
*/
async function handleWebhookEvent(event, payload) {
switch (event) {
case 'issues':
if (payload.action === 'opened' || payload.action === 'labeled') {
const labels = (payload.issue.labels || []).map(function(l) { return l.name; });
if (labels.includes('zhuyuan-exec')) {
console.log('[铸渊桥接] 检测到执行指令 Issue #' + payload.issue.number);
// 在 Webhook 模式下,可以直接启动执行
// 但默认推荐通过 Workflow 执行(更安全、有日志)
console.log('[铸渊桥接] 由 GitHub Actions Workflow 处理执行');
}
}
break;
case 'push':
console.log('[铸渊桥接] Push 事件 → ' + (payload.ref || ''));
break;
case 'pull_request':
console.log('[铸渊桥接] PR 事件 → #' + (payload.pull_request ? payload.pull_request.number : '?'));
break;
default:
console.log('[铸渊桥接] 未处理的事件类型:', event);
}
}
// ===== 启动服务 =====
app.listen(PORT, function() {
console.log('[铸渊桥接] 🚀 启动在端口 ' + PORT);
console.log('[铸渊桥接] 健康检查: http://localhost:' + PORT + '/health');
console.log('[铸渊桥接] Webhook: http://localhost:' + PORT + '/webhook/github-app');
});