ExportReportAsync(
HistoryHealthReport report,
ReportFormat format,
CancellationToken ct = default)
{
return format switch
{
ReportFormat.Json => ExportToJson(report),
ReportFormat.Markdown => ExportToMarkdown(report),
ReportFormat.Html => ExportToHtml(report),
ReportFormat.Console => ExportToConsole(report),
_ => throw new ArgumentOutOfRangeException(nameof(format))
};
}
public async Task ExportReportToFileAsync(
HistoryHealthReport report,
ReportFormat format,
string outputPath,
CancellationToken ct = default)
{
var content = await ExportReportAsync(report, format, ct);
await File.WriteAllTextAsync(outputPath, content, ct);
}
private string ExportToJson(HistoryHealthReport report)
{
return JsonSerializer.Serialize(report, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
}
private string ExportToMarkdown(HistoryHealthReport report)
{
var sb = new StringBuilder();
sb.AppendLine("# Git History Health Report");
sb.AppendLine();
sb.AppendLine($"**Repository:** {report.RepoName}");
sb.AppendLine($"**Branch:** {report.CurrentBranch}");
sb.AppendLine($"**Generated:** {report.GeneratedAt:yyyy-MM-dd HH:mm:ss} UTC");
sb.AppendLine($"**Commits Analyzed:** {report.CommitsAnalyzed}");
sb.AppendLine();
sb.AppendLine("---");
sb.AppendLine();
// Overall score
var gradeIcon = report.Score.Grade.GetIcon();
sb.AppendLine($"## Overall Health Score: {report.Score.OverallScore}/100 ({report.Score.Grade}) {gradeIcon}");
sb.AppendLine();
sb.AppendLine(report.Score.Grade.GetDescription());
sb.AppendLine();
// Component scores
sb.AppendLine("### Component Scores");
sb.AppendLine();
sb.AppendLine($"| Component | Score | Status |");
sb.AppendLine($"|-----------|-------|--------|");
sb.AppendLine($"| Messages | {report.Score.ComponentScores.MessageScore}/100 | {GetStatusIcon(report.Score.ComponentScores.MessageScore)} |");
sb.AppendLine($"| Merges | {report.Score.ComponentScores.MergeScore}/100 | {GetStatusIcon(report.Score.ComponentScores.MergeScore)} |");
sb.AppendLine($"| Duplicates | {report.Score.ComponentScores.DuplicateScore}/100 | {GetStatusIcon(report.Score.ComponentScores.DuplicateScore)} |");
sb.AppendLine($"| Branches | {report.Score.ComponentScores.BranchScore}/100 | {GetStatusIcon(report.Score.ComponentScores.BranchScore)} |");
sb.AppendLine($"| Authorship | {report.Score.ComponentScores.AuthorshipScore}/100 | {GetStatusIcon(report.Score.ComponentScores.AuthorshipScore)} |");
sb.AppendLine();
// Issues
if (report.Issues.Count > 0)
{
sb.AppendLine("---");
sb.AppendLine();
sb.AppendLine($"## Issues Found ({report.Issues.Count})");
sb.AppendLine();
foreach (var issue in report.Issues)
{
var severityIcon = issue.Severity switch
{
HealthIssueSeverity.Critical => "đ¨",
HealthIssueSeverity.Error => "â",
HealthIssueSeverity.Warning => "â ī¸",
_ => "âšī¸"
};
sb.AppendLine($"### {severityIcon} {issue.Title}");
sb.AppendLine();
sb.AppendLine($"**Code:** `{issue.Code}` | **Category:** {issue.Category} | **Impact:** -{issue.ImpactScore} points");
sb.AppendLine();
sb.AppendLine(issue.Description);
sb.AppendLine();
if (issue.AffectedCommits.Count > 0)
{
sb.AppendLine($"**Affected commits:** `{string.Join("`, `", issue.AffectedCommits.Take(5))}`" +
(issue.AffectedCommits.Count > 5 ? $" and {issue.AffectedCommits.Count - 5} more..." : ""));
sb.AppendLine();
}
}
}
// Recommendations
if (report.Recommendations.Count > 0)
{
sb.AppendLine("---");
sb.AppendLine();
sb.AppendLine("## Recommendations");
sb.AppendLine();
foreach (var rec in report.Recommendations)
{
sb.AppendLine($"### {rec.Title}");
sb.AppendLine();
sb.AppendLine($"**Priority:** {rec.PriorityScore}/100 | **Effort:** {rec.Effort} | **Expected Improvement:** +{rec.ExpectedScoreImprovement} points");
sb.AppendLine();
sb.AppendLine(rec.Description);
sb.AppendLine();
sb.AppendLine($"**Action:** {rec.Action}");
sb.AppendLine();
}
}
// Cleanup suggestions
if (report.CleanupSuggestions != null && report.CleanupSuggestions.TotalOperations > 0)
{
sb.AppendLine("---");
sb.AppendLine();
sb.AppendLine("## Cleanup Operations");
sb.AppendLine();
sb.AppendLine($"**Total expected improvement:** +{report.CleanupSuggestions.TotalExpectedImprovement} points");
sb.AppendLine();
if (report.CleanupSuggestions.AutomatedOperations.Count > 0)
{
sb.AppendLine("### Automated (Safe)");
foreach (var op in report.CleanupSuggestions.AutomatedOperations)
{
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
}
sb.AppendLine();
}
if (report.CleanupSuggestions.SemiAutomatedOperations.Count > 0)
{
sb.AppendLine("### Semi-Automated (Review Required)");
foreach (var op in report.CleanupSuggestions.SemiAutomatedOperations)
{
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
if (!string.IsNullOrEmpty(op.GitCommand))
{
sb.AppendLine($" ```bash");
sb.AppendLine($" {op.GitCommand}");
sb.AppendLine($" ```");
}
}
sb.AppendLine();
}
if (report.CleanupSuggestions.ManualOperations.Count > 0)
{
sb.AppendLine("### Manual (High Risk)");
foreach (var op in report.CleanupSuggestions.ManualOperations)
{
sb.AppendLine($"- **{op.Title}**: {op.Description} (+{op.ExpectedScoreImprovement} points)");
if (!string.IsNullOrEmpty(op.GitCommand))
{
sb.AppendLine($" ```bash");
sb.AppendLine($" {op.GitCommand}");
sb.AppendLine($" ```");
}
}
sb.AppendLine();
}
}
return sb.ToString();
}
private string ExportToHtml(HistoryHealthReport report)
{
// Simple HTML wrapper around markdown content
var markdown = ExportToMarkdown(report);
return $@"
Git Health Report - {report.RepoName}
{System.Web.HttpUtility.HtmlEncode(markdown)}
";
}
private string ExportToConsole(HistoryHealthReport report)
{
var sb = new StringBuilder();
sb.AppendLine($"ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
sb.AppendLine($"â GIT HISTORY HEALTH REPORT â");
sb.AppendLine($"â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââŖ");
sb.AppendLine($"â Repository: {report.RepoName,-48} â");
sb.AppendLine($"â Branch: {report.CurrentBranch,-52} â");
sb.AppendLine($"â Commits: {report.CommitsAnalyzed,-51} â");
sb.AppendLine($"â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââŖ");
sb.AppendLine($"â OVERALL SCORE: {report.Score.OverallScore,3}/100 Grade: {report.Score.Grade,-22} â");
sb.AppendLine($"â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââŖ");
sb.AppendLine($"â Components: â");
sb.AppendLine($"â Messages: {report.Score.ComponentScores.MessageScore,3}/100 {GetBar(report.Score.ComponentScores.MessageScore),-30} â");
sb.AppendLine($"â Merges: {report.Score.ComponentScores.MergeScore,3}/100 {GetBar(report.Score.ComponentScores.MergeScore),-30} â");
sb.AppendLine($"â Duplicates: {report.Score.ComponentScores.DuplicateScore,3}/100 {GetBar(report.Score.ComponentScores.DuplicateScore),-30} â");
sb.AppendLine($"â Branches: {report.Score.ComponentScores.BranchScore,3}/100 {GetBar(report.Score.ComponentScores.BranchScore),-30} â");
sb.AppendLine($"â Authorship: {report.Score.ComponentScores.AuthorshipScore,3}/100 {GetBar(report.Score.ComponentScores.AuthorshipScore),-30} â");
sb.AppendLine($"â âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââŖ");
sb.AppendLine($"â Issues: {report.CriticalIssueCount} critical, {report.ErrorCount} errors, {report.WarningCount} warnings â");
sb.AppendLine($"ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
return sb.ToString();
}
private static string GetStatusIcon(int score) => score switch
{
>= 90 => "â
Excellent",
>= 70 => "đ Good",
>= 50 => "â ī¸ Fair",
>= 30 => "â Poor",
_ => "đ¨ Critical"
};
private static string GetBar(int score)
{
var filled = score / 5;
var empty = 20 - filled;
return $"[{new string('â', filled)}{new string('â', empty)}]";
}
}