/** * Gitea 协作工作流 * * Main Agent 通过 Gitea 协调 Sub Agents 完成任务 */ import { generateGiteaRules, getRepoUrl, getBranchName, getCommitTemplate } from '../helpers/gitea-rules-generator.js' import { createGiteaAdapter } from '../helpers/gitea-mcp-adapter.js' import { generateProgressUpdate, generateExecuteMarker, generateReviewMarker, generateTestMarker, generateVerificationMarker } from '../helpers/comment-markers.js' import { findLatestMarker, MarkerType } from '../helpers/comment-parser.js' export const meta = { name: 'gitea-orchestration', description: '通过 Gitea 协调 Sub Agents 完成开发任务', phases: [ { title: '分析', detail: 'Main Agent 分析任务并创建 Issue' }, { title: '执行', detail: 'Execute Sub Agent 实现并创建 PR' }, { title: '审查', detail: 'Review Sub Agent 审查代码' }, { title: '修复', detail: '如需要,Execute Sub Agent 修复问题' }, { title: '验收', detail: 'Main Agent 验收并合并 PR' } ] } export default async function giteaOrchestration(task, options = {}) { // 生成 Gitea 规则(从配置文件读取) const giteaRules = generateGiteaRules() const repoUrl = getRepoUrl() // 初始化 Gitea 适配器 const gitea = createGiteaAdapter() log(`📦 仓库: ${repoUrl}`) log(`🔌 已连接 Gitea: ${gitea.getRepoInfo().baseUrl}`) // ===== Phase 1: 分析 ===== phase('分析') // 创建 Issue 记录任务 const issue = await createIssue(task, giteaRules, gitea) log(`✅ Issue 创建: ${issue.url}`) // 分析任务 const analysis = await analyzeTask(task) log(`📊 任务复杂度: ${analysis.complexity}`) log(`👥 需要的 Sub Agents: ${analysis.requiredAgents.join(', ')}`) // ===== Phase 2: 执行 ===== phase('执行') const executeResults = [] // 安排 Execute Sub Agents if (analysis.requiredAgents.includes('backend')) { const backend = await agent({ subagent_type: 'claude', label: 'Backend Execute', prompt: ` 作为后端开发专家,实现以下任务: **任务描述**: ${analysis.backendTask} **Issue**: ${issue.url} ${giteaRules} 执行步骤: 1. Clone 仓库 2. 创建分支: ${getBranchName('feature', 'backend-' + task.slug)} 3. 实现代码 4. 编写测试 5. Commit 并 Push 6. 创建 PR 完成后返回 PR URL。 ` }) executeResults.push({ type: 'backend', ...backend }) log(`✅ Backend Execute 完成: ${backend.prUrl}`) } if (analysis.requiredAgents.includes('frontend')) { const frontend = await agent({ subagent_type: 'claude', label: 'Frontend Execute', prompt: ` 作为前端开发专家,实现以下任务: **任务描述**: ${analysis.frontendTask} **Issue**: ${issue.url} ${giteaRules} 执行步骤: 1. Clone 仓库 2. 创建分支: ${getBranchName('feature', 'frontend-' + task.slug)} 3. 实现代码 4. 编写测试 5. Commit 并 Push 6. 创建 PR 完成后返回 PR URL。 ` }) executeResults.push({ type: 'frontend', ...frontend }) log(`✅ Frontend Execute 完成: ${frontend.prUrl}`) } // ===== Phase 3: 审查 ===== phase('审查') if (analysis.requiredAgents.includes('review')) { for (const execute of executeResults) { const review = await agent({ subagent_type: 'claude', label: `${execute.type} Review`, prompt: ` 作为代码审查专家,审查以下 PR: **PR URL**: ${execute.prUrl} **Issue**: ${issue.url} ${giteaRules} 审查检查: - 代码质量 - 安全性 - 性能 - 测试覆盖 在 PR 中添加 Review 评论,标注问题代码行。 完成后返回 Review 结果: { "passed": true|false, "findings": ["问题1", "问题2"], "suggestions": ["建议1", "建议2"] } ` }) execute.review = review log(`${review.passed ? '✅' : '❌'} ${execute.type} Review: ${review.passed ? '通过' : '需要修复'}`) } } // ===== Phase 4: 修复 (如需要) ===== phase('修复') for (const execute of executeResults) { let maxRetries = 3 let retryCount = 0 while (!execute.review?.passed && retryCount < maxRetries) { log(`🔄 ${execute.type} 需要修复 (尝试 ${retryCount + 1}/${maxRetries})`) const fix = await agent({ subagent_type: 'claude', label: `${execute.type} Fix`, prompt: ` **你的 PR 有审查意见,请修复** **PR URL**: ${execute.prUrl} **Review 问题**: ${execute.review.findings.join('\n')} ${giteaRules} 修复步骤: 1. 在 PR 分支上修复问题 2. Commit: ${getCommitTemplate('fix').replace('{description}', '修复 Review 问题')} 3. Push 更新 4. 回复 Review 评论说明修复内容 修复完成后返回更新后的状态。 ` }) log(`✅ ${execute.type} 修复完成`) // 重新审查 const reReview = await agent({ subagent_type: 'claude', label: `${execute.type} Re-review`, prompt: ` 重新审查 PR: ${execute.prUrl} ${giteaRules} 检查之前的 Review 问题是否已修复。 返回 Review 结果: { "passed": true|false, "findings": ["剩余问题"], "suggestions": ["建议"] } ` }) execute.review = reReview retryCount++ if (reReview.passed) { log(`✅ ${execute.type} 重新审查通过`) } } if (!execute.review?.passed && retryCount >= maxRetries) { log(`⚠️ ${execute.type} 达到最大重试次数,需要人工介入`) } } // ===== Phase 5: 验收 ===== phase('验收') // 安排 Test Sub Agent 验证 if (analysis.requiredAgents.includes('test')) { for (const execute of executeResults) { const test = await agent({ subagent_type: 'claude', label: `${execute.type} Test`, prompt: ` 作为测试工程师,验证以下 PR 的功能: **PR URL**: ${execute.prUrl} **Issue**: ${issue.url} ${giteaRules} 测试步骤: 1. 拉取 PR 代码 2. 安装依赖 3. 运行测试 4. 检查测试覆盖率 返回测试结果: { "passed": true|false, "coverage": "85%", "failures": [], "summary": "测试摘要" } ` }) execute.test = test log(`${test.passed ? '✅' : '❌'} ${execute.type} Test: ${test.passed ? '通过' : '失败'}`) } } // 最终验收 const allPassed = executeResults.every(e => e.review?.passed && (!e.test || e.test?.passed) ) if (allPassed) { // 合并所有 PR for (const execute of executeResults) { await mergePR(execute.prUrl, gitea) log(`✅ 已合并 PR: ${execute.prUrl}`) } // 关闭 Issue await closeIssue(issue.number, gitea) log(`✅ 已关闭 Issue: ${issue.url}`) return { status: '完成', issue: issue.url, prs: executeResults.map(e => e.prUrl), message: '任务完成并已合并' } } else { return { status: '需要人工介入', issue: issue.url, prs: executeResults.map(e => ({ url: e.prUrl, review: e.review, test: e.test })), message: '部分 PR 未通过审查或测试,需要人工处理' } } } /** * 辅助函数 */ async function createIssue(task, rules, gitea) { const repoInfo = gitea.getRepoInfo() // 生成 Issue 标题(包含项目标识) const title = `[${repoInfo.repo}] ${task.title || task}` // 生成 Issue 正文 const body = generateIssueBody(task, rules) // 生成标签 const labels = generateIssueLabels(task) try { const issue = await gitea.withRetry(async () => { return await gitea.createIssue({ title, body, labels }) }) log(`✅ Issue 已创建: ${issue.url}`) return issue } catch (error) { log(`❌ 创建 Issue 失败: ${error.message}`) throw error } } /** * 生成 Issue 正文 */ function generateIssueBody(task, rules) { return ` ## 项目信息 - 项目: ${task.project || 'sanguo_moziplus_v3'} - 需求编号: ${task.id || 'D-PENDING'} - 复杂度: ${task.complexity || '待评估'} ## 需求描述 ${task.description || task} ## 执行清单 ### 📋 Execute Sub Agent > **负责**: Execute Sub Agent > **状态**: 🔄 进行中 - [ ] 任务分析和规划 - [ ] 代码实现 - [ ] 单元测试编写 - [ ] 本地验证 **完成时标记**: \`@main-agent ✅ EXECUTE_DONE\` ### 📋 Review Sub Agent > **负责**: Review Sub Agent > **状态**: ⏳ 等待中 - [ ] 代码质量检查 - [ ] 安全性审查 - [ ] 性能评估 **完成时标记**: \`@main-agent ✅ REVIEW_DONE verdict=approved\` ### 📋 Test Sub Agent > **负责**: Test Sub Agent > **状态**: ⏳ 等待中 - [ ] 测试用例编写 - [ ] 测试执行 - [ ] 覆盖率检查 **完成时标记**: \`@main-agent ✅ TEST_DONE result=passed\` ### 📋 Main Agent 验收 > **负责**: Main Agent > **状态**: ⏳ 等待中 - [ ] 需求 → 设计 一致性 - [ ] 设计 → 编码 一致性 - [ ] 需求 → 编码 一致性 **完成时标记**: \`@main-agent ✅ VERIFICATION_PASSED\` ## 执行状态 | 阶段 | 负责者 | 状态 | 更新时间 | |------|-------|------|---------| | Execute | Execute Sub Agent | 🔄 进行中 | - | | Review | Review Sub Agent | ⏳ 等待 | - | | Test | Test Sub Agent | ⏳ 等待 | - | | 验收 | Main Agent | ⏳ 等待 | - | ` } /** * 生成 Issue 标签 */ function generateIssueLabels(task) { const labels = [] // 添加复杂度标签 if (task.complexity) { labels.push(`complexity:${task.complexity}`) } // 添加类型标签 if (task.type) { labels.push(task.type) } return labels } async function analyzeTask(task) { return await agent({ subagent_type: 'Plan', prompt: ` 分析任务: "${task}" 返回 JSON: { "complexity": "low|medium|high", "requiredAgents": ["backend", "frontend", "review", "test"], "backendTask": "后端任务描述", "frontendTask": "前端任务描述", "slug": "task-slug" } `, schema: { type: "object", properties: { complexity: { type: "string", enum: ["low", "medium", "high"] }, requiredAgents: { type: "array", items: { type: "string" } }, backendTask: { type: "string" }, frontendTask: { type: "string" }, slug: { type: "string" } } } }) } async function mergePR(prUrl, gitea) { try { // 从 PR URL 中提取 PR 编号 const prMatch = prUrl.match(/\/pulls\/(\d+)/) if (!prMatch) { throw new Error(`无效的 PR URL: ${prUrl}`) } const prNumber = parseInt(prMatch[1], 10) await gitea.withRetry(async () => { return await gitea.mergePullRequest(prNumber, { deleteBranch: true, mergeStyle: 'merge' }) }) log(`✅ PR 已合并: ${prUrl}`) } catch (error) { log(`❌ 合并 PR 失败: ${error.message}`) throw error } } async function closeIssue(issueId, gitea) { try { await gitea.withRetry(async () => { return await gitea.closeIssue(issueId) }) log(`✅ Issue 已关闭: #${issueId}`) } catch (error) { log(`❌ 关闭 Issue 失败: ${error.message}`) throw error } } /** * 等待 Sub Agent 完成并返回标记数据 * * @param {Object} gitea - Gitea 适配器实例 * @param {number} issueNumber - Issue 编号 * @param {string} markerType - 期望的标记类型 * @param {Object} options - 选项 * @returns {Promise} 标记数据 */ async function waitForSubAgentCompletion(gitea, issueNumber, markerType, options = {}) { const { timeout = 30 * 60 * 1000, // 30 分钟超时 interval = 10 * 1000, // 10 秒检查间隔 onProgress = null // 进度回调 } = options const startTime = Date.now() let checkCount = 0 log(`⏳ 等待 ${markerType} 标记...`) while (Date.now() - startTime < timeout) { checkCount++ try { // 读取 Issue Comments const comments = await gitea.getIssueComments(issueNumber) // 查找目标标记 const marker = findLatestMarker(comments, markerType) if (marker) { log(`✅ 检测到 ${markerType} 标记 (检查 ${checkCount} 次)`) return marker.data } // 进度回调 if (onProgress) { onProgress({ elapsed: Date.now() - startTime, checkCount, markerType: null }) } // 等待下次检查 await new Promise(resolve => setTimeout(resolve, interval)) } catch (error) { log(`⚠️ 检查标记时出错: ${error.message}`) } } throw new Error(`等待 ${markerType} 标记超时 (${timeout / 1000} 秒)`) } /** * 发布进度更新 Comment * * @param {Object} gitea - Gitea 适配器实例 * @param {number} issueNumber - Issue 编号 * @param {string} stage - 当前阶段 * @param {Array} completed - 已完成任务 * @param {Array} inProgress - 进行中任务 */ async function publishProgressUpdate(gitea, issueNumber, stage, completed, inProgress) { const content = generateProgressUpdate({ stage, completed, inProgress }) await gitea.createIssueComment(issueNumber, content) log(`📝 进度更新已发布: ${stage} 阶段`) } function log(message) { console.log(`[${new Date().toISOString()}] ${message}`) } function phase(title) { console.log(`\n=== ${title} ===`) }