guanghulab/services/zhuyuan-bridge/scripts/execute-instruction.js

357 lines
11 KiB
JavaScript
Raw Permalink Normal View History

/**
* 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非贪婪匹配找第一个完整 JSON 对象)
const jsonMatch = aiResponse.match(/\{[\s\S]*?"actions"[\s\S]*?\}(?=\s*$|\s*\n)/);
if (jsonMatch) {
try {
plan = JSON.parse(jsonMatch[0]);
} catch (_2) {
// 降级:找所有 { } 对中最长的合法 JSON
const allMatches = aiResponse.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g) || [];
for (const m of allMatches) {
try { plan = JSON.parse(m); if (plan.actions) break; } catch (_3) { 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);
});