/** * Comment 解析器 * * 解析 Issue Comments 中的标记,提取关键信息 */ /** * 标记类型枚举 */ export const MarkerType = { EXECUTE_DONE: 'EXECUTE_DONE', REVIEW_DONE: 'REVIEW_DONE', TEST_DONE: 'TEST_DONE', VERIFICATION_PASSED: 'VERIFICATION_PASSED', VERIFICATION_REJECTED: 'VERIFICATION_REJECTED', PROGRESS_UPDATE: 'PROGRESS_UPDATE', CONSISTENCY_ISSUE: 'CONSISTENCY_ISSUE' } /** * 解析完成标记 * * @param {string} commentBody - Comment 内容 * @returns {Object|null} 解析结果 */ export function parseCompletionMarker(commentBody) { // 检测 EXECUTE_DONE if (commentBody.includes('EXECUTE_DONE')) { return { type: MarkerType.EXECUTE_DONE, status: 'completed', data: extractExecuteData(commentBody) } } // 检测 REVIEW_DONE if (commentBody.includes('REVIEW_DONE')) { return { type: MarkerType.REVIEW_DONE, status: 'completed', data: extractReviewData(commentBody) } } // 检测 TEST_DONE if (commentBody.includes('TEST_DONE')) { return { type: MarkerType.TEST_DONE, status: 'completed', data: extractTestData(commentBody) } } // 检测 VERIFICATION_PASSED if (commentBody.includes('VERIFICATION_PASSED')) { return { type: MarkerType.VERIFICATION_PASSED, status: 'completed', data: extractVerificationData(commentBody) } } // 检测 VERIFICATION_REJECTED if (commentBody.includes('VERIFICATION_REJECTED')) { return { type: MarkerType.VERIFICATION_REJECTED, status: 'failed', data: extractVerificationData(commentBody) } } // 检测 PROGRESS_UPDATE if (commentBody.includes('进度更新')) { return { type: MarkerType.PROGRESS_UPDATE, status: 'in_progress', data: extractProgressData(commentBody) } } // 检测 CONSISTENCY_ISSUE if (commentBody.includes('CONSISTENCY_ISSUE')) { return { type: MarkerType.CONSISTENCY_ISSUE, status: 'failed', data: extractDeviationData(commentBody) } } return null } /** * 提取 EXECUTE_DONE 数据 */ function extractExecuteData(commentBody) { const deliverables = [] const deliverableMatch = commentBody.match(/### 交付物\n([\s\S]*?)\n\n/) if (deliverableMatch) { const lines = deliverableMatch[1].split('\n') lines.forEach(line => { const match = line.match(/\d+\.\s*`([^`]+)`/) if (match) { deliverables.push(match[1]) } }) } const testLocalMatch = commentBody.match(/本地运行:\s*([✅❌])\s*(\w+)/) const coverageMatch = commentBody.match(/覆盖率:\s*([\d%]+)/) const executorMatch = commentBody.match(/\*\*执行者\*\*:\s*([^\n]+)/) const completedAtMatch = commentBody.match(/\*\*完成时间\*\*:\s*([^\n]+)/) return { deliverables, testStatus: { local: testLocalMatch ? `${testLocalMatch[1]} ${testLocalMatch[2]}` : 'UNKNOWN', coverage: coverageMatch ? coverageMatch[1] : '0%' }, executor: executorMatch ? executorMatch[1].trim() : 'Execute Sub Agent', completedAt: completedAtMatch ? completedAtMatch[1].trim() : null } } /** * 提取 REVIEW_DONE 数据 */ function extractReviewData(commentBody) { const verdictMatch = commentBody.match(/verdict=(\w+)/) const verdict = verdictMatch ? verdictMatch[1].toLowerCase() : 'unknown' const findings = [] const findingsMatch = commentBody.match(/### 发现的问题\n([\s\S]*?)\n\n/) if (findingsMatch) { const lines = findingsMatch[1].split('\n') lines.forEach(line => { const match = line.match(/\d+\.\s*(.+)/) if (match) { findings.push(match[1].trim()) } }) } const suggestions = [] const suggestionsMatch = commentBody.match(/### 改进建议\n([\s\S]*?)\n\n/) if (suggestionsMatch) { const lines = suggestionsMatch[1].split('\n') lines.forEach(line => { const match = line.match(/\d+\.\s*(.+)/) if (match) { suggestions.push(match[1].trim()) } }) } const reviewerMatch = commentBody.match(/\*\*审查者\*\*:\s*([^\n]+)/) const completedAtMatch = commentBody.match(/\*\*完成时间\*\*:\s*([^\n]+)/) return { verdict: verdict === 'approved' ? 'approved' : 'rejected', findings, suggestions, reviewer: reviewerMatch ? reviewerMatch[1].trim() : 'Review Sub Agent', completedAt: completedAtMatch ? completedAtMatch[1].trim() : null } } /** * 提取 TEST_DONE 数据 */ function extractTestData(commentBody) { const resultMatch = commentBody.match(/result=(\w+)/) const coverageMatch = commentBody.match(/coverage=([\d%]+)/) const summaryMatch = commentBody.match(/### 测试摘要\n([\s\S]*?)\n\n/) const failures = [] const failuresMatch = commentBody.match(/### 失败的测试\n([\s\S]*?)\n\n/) if (failuresMatch) { const lines = failuresMatch[1].split('\n') lines.forEach(line => { const match = line.match(/\d+\.\s*(.+)/) if (match) { failures.push(match[1].trim()) } }) } const testerMatch = commentBody.match(/\*\*测试者\*\*:\s*([^\n]+)/) const completedAtMatch = commentBody.match(/\*\*完成时间\*\*:\s*([^\n]+)/) return { result: resultMatch ? resultMatch[1].toLowerCase() : 'unknown', coverage: coverageMatch ? coverageMatch[1] : '0%', summary: summaryMatch ? summaryMatch[1].trim() : '', failures, tester: testerMatch ? testerMatch[1].trim() : 'Test Sub Agent', completedAt: completedAtMatch ? completedAtMatch[1].trim() : null } } /** * 提取 VERIFICATION 数据 */ function extractVerificationData(commentBody) { // 提取三个检查结果 const requirementDesignPassed = commentBody.includes('需求→设计: 通过') const designCodePassed = commentBody.includes('设计→编码: 通过') const requirementCodePassed = commentBody.includes('需求→编码: 通过') const allPassed = requirementDesignPassed && designCodePassed && requirementCodePassed const completedAtMatch = commentBody.match(/\*\*完成时间\*\*:\s*([^\n]+)/) return { requirementDesign: { passed: requirementDesignPassed }, designCode: { passed: designCodePassed }, requirementCode: { passed: requirementCodePassed }, allPassed, verdict: allPassed ? 'approved' : 'rejected', completedAt: completedAtMatch ? completedAtMatch[1].trim() : null } } /** * 提取 PROGRESS_UPDATE 数据 */ function extractProgressData(commentBody) { const progressMatch = commentBody.match(/\*\*进度\*\*:\s*(\d+)%/) const stageMatch = commentBody.match(/进度更新:\s*(\w+)\s*阶段/) const completed = [] const completedMatch = commentBody.match(/## 已完成\n([\s\S]*?)\n\n/) if (completedMatch) { const lines = completedMatch[1].split('\n') lines.forEach(line => { const match = line.match/- ✅ (.+)/) if (match) { completed.push(match[1].trim()) } }) } const inProgress = [] const inProgressMatch = commentBody.match(/## 进行中\n([\s\S]*?)\n\n/) if (inProgressMatch) { const lines = inProgressMatch[1].split('\n') lines.forEach(line => { const match = line.match/- 🔄 (.+)/) if (match) { inProgress.push(match[1].trim()) } }) } return { stage: stageMatch ? stageMatch[1] : 'Unknown', progress: progressMatch ? parseInt(progressMatch[1], 10) : 0, completed, inProgress } } /** * 提取 CONSISTENCY_ISSUE 数据 */ function extractDeviationData(commentBody) { const typeMatch = commentBody.match(/问题:\s*(.+)\s*偏差/) const issueMatch = commentBody.match(/\*\*问题描述\*\*:\s*([^\n]+)/) const expectedMatch = commentBody.match(/\*\*预期\*\*:\s*([^\n]+)/) const actualMatch = commentBody.match(/\*\*实际\*\*:\s*([^\n]+)/) const actionRequired = [] const actionMatch = commentBody.match(/### 处理要求\n([\s\S]*?)\n\n/) if (actionMatch) { const lines = actionMatch[1].split('\n') lines.forEach(line => { const match = line.match(/\d+\.\s*(.+)/) if (match) { actionRequired.push(match[1].trim()) } }) } return { type: typeMatch ? typeMatch[1].trim() : 'unknown', issue: issueMatch ? issueMatch[1].trim() : '', expected: expectedMatch ? expectedMatch[1].trim() : '', actual: actualMatch ? actualMatch[1].trim() : '', actionRequired } } /** * 查找最新的指定类型标记 * * @param {Array} comments - Comments 数组 * @param {string} markerType - 标记类型 * @returns {Object|null} 最新的标记数据 */ export function findLatestMarker(comments, markerType) { if (!comments || comments.length === 0) { return null } // 从后往前遍历(最新的在前) for (let i = comments.length - 1; i >= 0; i--) { const comment = comments[i] const parsed = parseCompletionMarker(comment.body || comment.content || '') if (parsed && parsed.type === markerType) { return { ...parsed, commentId: comment.id, createdAt: comment.created_at || comment.timestamp, author: comment.user?.login || comment.author || 'Unknown' } } } return null } /** * 检查指定标记是否存在 * * @param {Array} comments - Comments 数组 * @param {string} markerType - 标记类型 * @returns {boolean} 是否存在 */ export function hasMarker(comments, markerType) { return findLatestMarker(comments, markerType) !== null } /** * 获取所有标记的摘要 * * @param {Array} comments - Comments 数组 * @returns {Object} 标记摘要 */ export function getMarkersSummary(comments) { const summary = { executeDone: findLatestMarker(comments, MarkerType.EXECUTE_DONE), reviewDone: findLatestMarker(comments, MarkerType.REVIEW_DONE), testDone: findLatestMarker(comments, MarkerType.TEST_DONE), verificationPassed: findLatestMarker(comments, MarkerType.VERIFICATION_PASSED), verificationRejected: findLatestMarker(comments, MarkerType.VERIFICATION_REJECTED), progressUpdate: findLatestMarker(comments, MarkerType.PROGRESS_UPDATE), consistencyIssue: findLatestMarker(comments, MarkerType.CONSISTENCY_ISSUE) } // 计算当前阶段状态 summary.currentPhase = determineCurrentPhase(summary) return summary } /** * 确定当前阶段 */ function determineCurrentPhase(summary) { if (summary.verificationPassed || summary.verificationRejected) { return 'completed' } if (summary.reviewDone && summary.testDone) { return 'verification' } if (summary.executeDone) { return 'review_and_test' } if (summary.progressUpdate) { return 'executing' } return 'pending' } /** * 导出所有解析函数 */ export default { MarkerType, parseCompletionMarker, extractMarkerData: parseCompletionMarker, findLatestMarker, hasMarker, getMarkersSummary, determineCurrentPhase }