Files
sanguo_vnpy_v2/.claude/workflows/gitea-orchestration.js
T
claude_dev 918bbed0fc fix: 修复登录500错误和移除明文密码提示
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service)
- 移除登录页面上的明文密码提示
- 改进前端错误处理,避免数据加载失败导致登录显示错误
2026-07-02 12:23:55 +08:00

551 lines
13 KiB
JavaScript

/**
* 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<Object>} 标记数据
*/
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<string>} completed - 已完成任务
* @param {Array<string>} 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} ===`)
}