Skip to main content
Glama

gitea_workflow_generate_report

Generate workflow reports with issue statistics, health scores, and actionable recommendations for Gitea repositories. Outputs 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
NameRequiredDescriptionDefault
ownerNoRepository owner. Uses context if not provided
repoNoRepository name. Uses context if not provided
time_rangeNoTime range for statistics (default: all time)

Implementation Reference

  • The core handler function that generates comprehensive workflow reports. Fetches open and closed issues, computes statistics by status/priority/type, calculates average age and 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, }; } }
  • Zod input schema defining parameters: owner, repo (optional), time_range (enum: day/week/month, optional).
    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)'), }),
  • Tool registration in MCP server, including title, description, input schema, and handler wrapper that calls the workflowGenerateReport function and handles errors.
    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, }; } } );
  • Helper function to generate the Markdown-formatted report string used in the handler.
    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'); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SupenBysz/gitea-mcp-tool'

If you have feedback or need assistance with the MCP directory API, please join our Discord server