gitea_workflow_generate_report
Generate workflow reports for Gitea repositories with issue statistics, health scores, and actionable recommendations in JSON and Markdown formats.
Instructions
Generate a comprehensive workflow report including issue statistics, health score, and recommendations. Output in JSON and Markdown formats.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| owner | No | Repository owner. Uses context if not provided | |
| repo | No | Repository name. Uses context if not provided | |
| time_range | No | Time range for statistics (default: all time) |
Implementation Reference
- src/tools/workflow.ts:937-1103 (handler)The core handler function `workflowGenerateReport` that implements the tool logic. It loads config, fetches open/closed issues, computes statistics by status/priority/type, calculates average age, blocked count, health score, generates recommendations, and produces a Markdown report.export async function workflowGenerateReport( ctx: WorkflowToolsContext, args: { owner?: string; repo?: string; config?: WorkflowConfig; time_range?: 'day' | 'week' | 'month'; } ): Promise<{ success: boolean; report: { summary: { total_open: number; total_closed: number; by_status: Record<string, number>; by_priority: Record<string, number>; by_type: Record<string, number>; }; blocked_count: number; avg_age_days: number; health_score: number; recommendations: string[]; }; markdown_report: string; error?: string; }> { logger.debug({ args: { ...args, config: args.config ? '[provided]' : undefined } }, 'Generating report'); const { owner, repo } = ctx.contextManager.resolveOwnerRepo(args.owner, args.repo); // 获取配置 let config = args.config; if (!config) { const loadResult = await workflowLoadConfig(ctx, { owner, repo }); if (!loadResult.success || !loadResult.config) { return { success: false, report: { summary: { total_open: 0, total_closed: 0, by_status: {}, by_priority: {}, by_type: {} }, blocked_count: 0, avg_age_days: 0, health_score: 0, recommendations: [], }, markdown_report: '', error: loadResult.error || '无法加载配置', }; } config = loadResult.config; } try { // 获取开放和关闭的 Issue const openIssues = await ctx.client.get<Issue[]>(`/repos/${owner}/${repo}/issues?state=open&limit=100`); const closedIssues = await ctx.client.get<Issue[]>(`/repos/${owner}/${repo}/issues?state=closed&limit=50`); // 统计 const byStatus: Record<string, number> = {}; const byPriority: Record<string, number> = {}; const byType: Record<string, number> = {}; let totalAgeDays = 0; let blockedCount = 0; for (const issue of openIssues) { // 状态统计 const status = getIssueStatus(issue) || 'unknown'; byStatus[status] = (byStatus[status] || 0) + 1; // 优先级统计 const priority = getIssuePriority(issue) || 'unknown'; byPriority[priority] = (byPriority[priority] || 0) + 1; // 类型统计 const prefixes = getLabelPrefixes(config); const typeLabel = issue.labels.find((l) => matchLabel(prefixes.type, l.name) !== null); const type = typeLabel ? (matchLabel(prefixes.type, typeLabel.name) || 'unknown') : 'unknown'; byType[type] = (byType[type] || 0) + 1; // 年龄统计 totalAgeDays += calculateIssueAgeDays(issue); // 阻塞统计 const blockedLabel = buildLabel(prefixes.workflow, 'blocked'); if (hasLabel(issue, blockedLabel)) { blockedCount++; } } const avgAgeDays = openIssues.length > 0 ? Math.round(totalAgeDays / openIssues.length) : 0; // 计算健康度评分 (0-100) let healthScore = 100; if (blockedCount > 0) healthScore -= blockedCount * 5; if (avgAgeDays > 30) healthScore -= 20; else if (avgAgeDays > 14) healthScore -= 10; if (byPriority['P0'] && byPriority['P0'] > 0) healthScore -= byPriority['P0'] * 10; healthScore = Math.max(0, Math.min(100, healthScore)); // 生成建议 const recommendations: string[] = []; if (blockedCount > 0) { recommendations.push(`有 ${blockedCount} 个 Issue 被阻塞,请及时处理`); } if (byPriority['P0'] && byPriority['P0'] > 0) { recommendations.push(`有 ${byPriority['P0']} 个紧急 Issue (P0),需要立即关注`); } if (avgAgeDays > 30) { recommendations.push(`Issue 平均年龄为 ${avgAgeDays} 天,建议加快处理速度`); } if (byStatus['unknown'] && byStatus['unknown'] > 0) { recommendations.push(`有 ${byStatus['unknown']} 个 Issue 缺少状态标签`); } // 生成 Markdown 报告 const markdownReport = generateMarkdownReport( owner, repo, { total_open: openIssues.length, total_closed: closedIssues.length, by_status: byStatus, by_priority: byPriority, by_type: byType, }, blockedCount, avgAgeDays, healthScore, recommendations ); logger.info({ owner, repo, open: openIssues.length, health_score: healthScore }, 'Report generated'); return { success: true, report: { summary: { total_open: openIssues.length, total_closed: closedIssues.length, by_status: byStatus, by_priority: byPriority, by_type: byType, }, blocked_count: blockedCount, avg_age_days: avgAgeDays, health_score: healthScore, recommendations, }, markdown_report: markdownReport, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ owner, repo, error: errorMessage }, 'Failed to generate report'); return { success: false, report: { summary: { total_open: 0, total_closed: 0, by_status: {}, by_priority: {}, by_type: {} }, blocked_count: 0, avg_age_days: 0, health_score: 0, recommendations: [], }, markdown_report: '', error: errorMessage, }; } }
- src/tools-registry/workflow-registry.ts:322-354 (registration)Registers the 'gitea_workflow_generate_report' tool with MCP server, including title, description, Zod input schema (owner, repo, time_range), and wrapper handler that calls the core workflowGenerateReport function.mcpServer.registerTool( 'gitea_workflow_generate_report', { title: '生成工作流报告', description: 'Generate a comprehensive workflow report including issue statistics, health score, and recommendations. Output in JSON and Markdown formats.', inputSchema: z.object({ owner: z.string().optional().describe('Repository owner. Uses context if not provided'), repo: z.string().optional().describe('Repository name. Uses context if not provided'), time_range: z .enum(['day', 'week', 'month']) .optional() .describe('Time range for statistics (default: all time)'), }), }, async (args) => { try { const result = await WorkflowTools.workflowGenerateReport( { client: ctx.client, contextManager: ctx.contextManager }, args ); return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: !result.success, }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text' as const, text: `Error: ${errorMessage}` }], isError: true, }; } }
- Zod input schema definition for the tool, specifying optional owner/repo (resolved from context) and time_range enum.{ title: '生成工作流报告', description: 'Generate a comprehensive workflow report including issue statistics, health score, and recommendations. Output in JSON and Markdown formats.', inputSchema: z.object({ owner: z.string().optional().describe('Repository owner. Uses context if not provided'), repo: z.string().optional().describe('Repository name. Uses context if not provided'), time_range: z .enum(['day', 'week', 'month']) .optional() .describe('Time range for statistics (default: all time)'), }), },
- src/tools/workflow.ts:1107-1177 (helper)Helper function to generate the Markdown-formatted workflow report from computed statistics and recommendations.function generateMarkdownReport( owner: string, repo: string, summary: { total_open: number; total_closed: number; by_status: Record<string, number>; by_priority: Record<string, number>; by_type: Record<string, number>; }, blockedCount: number, avgAgeDays: number, healthScore: number, recommendations: string[] ): string { const lines: string[] = [ `# ${owner}/${repo} 工作流报告`, '', `生成时间: ${new Date().toISOString()}`, '', '## 概览', '', `| 指标 | 值 |`, `|------|-----|`, `| 开放 Issue | ${summary.total_open} |`, `| 已关闭 Issue | ${summary.total_closed} |`, `| 阻塞 Issue | ${blockedCount} |`, `| 平均年龄 | ${avgAgeDays} 天 |`, `| 健康度评分 | ${healthScore}/100 |`, '', '## 状态分布', '', '| 状态 | 数量 |', '|------|------|', ]; for (const [status, count] of Object.entries(summary.by_status)) { lines.push(`| ${status} | ${count} |`); } lines.push(''); lines.push('## 优先级分布'); lines.push(''); lines.push('| 优先级 | 数量 |'); lines.push('|--------|------|'); for (const [priority, count] of Object.entries(summary.by_priority)) { lines.push(`| ${priority} | ${count} |`); } lines.push(''); lines.push('## 类型分布'); lines.push(''); lines.push('| 类型 | 数量 |'); lines.push('|------|------|'); for (const [type, count] of Object.entries(summary.by_type)) { lines.push(`| ${type} | ${count} |`); } if (recommendations.length > 0) { lines.push(''); lines.push('## 建议'); lines.push(''); for (const rec of recommendations) { lines.push(`- ${rec}`); } } return lines.join('\n'); }