b9327f3dea
## 第一阶段:核心 Gitea MCP 集成 - 新增 gitea-mcp-adapter.js 封装 Gitea MCP 工具调用 - 更新 gitea-orchestration.js 使用真实 MCP 调用 - 实现 Issue/PR/分支操作的完整适配层 - 添加错误处理和重试机制 ## 第二阶段:Comment 标记约定 - 新增 comment-markers.js 生成标准完成标记 - 新增 comment-parser.js 解析 Sub Agent 完成标记 - 实现轮询机制 waitForSubAgentCompletion - 支持 EXECUTE_DONE、REVIEW_DONE、TEST_DONE、VERIFICATION_PASSED 标记 ## 第三阶段:三向一致性检查 - 新增 consistency-checker.js 实现三向比对逻辑 - 新增 requirement-tracker.js 追踪需求映射 - 实现需求→设计、设计→编码、需求→编码检查 - 支持偏差检测和报告生成 ## 第四阶段:Linus 三问决策框架 - 新增 linus-triad.js 实现工程审慎决策 - 三问:现实问题、需要解决、方案可行 - 支持拒绝响应生成 - 集成 Agent 分析和关键词回退 ## 第五阶段:Superpowers 工作流集成 - 新增 superpowers-integration.js 封装五技能调用 - 新增 CLAUDE.md Main Agent 配置 - 实现 writing-plans、executing-plans、requesting-code-review、systematic-debugging、finishing-a-development-branch 总计:8 个新文件,1 个修改文件,约 2,982 行代码
551 lines
13 KiB
JavaScript
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} ===`)
|
|
}
|