918bbed0fc
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service) - 移除登录页面上的明文密码提示 - 改进前端错误处理,避免数据加载失败导致登录显示错误
392 lines
11 KiB
JavaScript
392 lines
11 KiB
JavaScript
/**
|
|
* 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<Object>} 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<Object>} comments - Comments 数组
|
|
* @param {string} markerType - 标记类型
|
|
* @returns {boolean} 是否存在
|
|
*/
|
|
export function hasMarker(comments, markerType) {
|
|
return findLatestMarker(comments, markerType) !== null
|
|
}
|
|
|
|
/**
|
|
* 获取所有标记的摘要
|
|
*
|
|
* @param {Array<Object>} 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
|
|
}
|