diff --git a/src/formatters/html_formatter.py b/src/formatters/html_formatter.py
new file mode 100644
index 0000000..54bee65
--- /dev/null
+++ b/src/formatters/html_formatter.py
@@ -0,0 +1,132 @@
+from datetime import datetime
+from typing import Any
+
+from jinja2 import Template
+from src.formatters.base import BaseFormatter
+
+
+HTML_TEMPLATE = """
+
+
+
+
+
+ Git Insights Report
+
+
+
+
+
Git Insights Report
+
Generated: {{ timestamp }}
+
+ {% if commit_analysis %}
+
+
Commit Analysis
+
+
{{ commit_analysis.total_commits }}
+
Total Commits
+
+
+
{{ commit_analysis.unique_authors }}
+
Authors
+
+
+
{{ "%.1f"|format(commit_analysis.average_commits_per_day) }}
+
Avg/Day
+
+
+ | Author | Commits |
+ {% for author in commit_analysis.top_authors[:5] %}
+ | {{ author.name }} | {{ author.commit_count }} |
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% if velocity_analysis %}
+
+
Velocity Analysis
+
+
{{ "%.1f"|format(velocity_analysis.commits_per_day) }}
+
Commits/Day
+
+
+
{{ "%.1f"|format(velocity_analysis.commits_per_week) }}
+
Commits/Week
+
+
+
{{ velocity_analysis.velocity_trend|capitalize }}
+
Trend
+
+
+ {% endif %}
+
+ {% if code_churn_analysis %}
+
+
Code Churn
+
+
{{ code_churn_analysis.total_lines_added }}
+
Lines Added
+
+
+
{{ code_churn_analysis.total_lines_deleted }}
+
Lines Deleted
+
+
+
{{ code_churn_analysis.net_change }}
+
Net Change
+
+
+ {% endif %}
+
+ {% if risky_commit_analysis %}
+
+
Risky Commits
+
+
{{ risky_commit_analysis.total_risky_commits }}
+
Total Risky
+
+
+
{{ "%.1f"|format(risky_commit_analysis.risk_score) }}%
+
Risk Score
+
+
+ {% endif %}
+
+
+
+"""
+
+
+class HTMLFormatter(BaseFormatter):
+ """HTML output formatter."""
+
+ @staticmethod
+ def format(data: Any) -> str:
+ """Format data as HTML."""
+ template = Template(HTML_TEMPLATE)
+
+ return template.render(
+ timestamp=datetime.now().isoformat(),
+ commit_analysis=data.commit_analysis if hasattr(data, "commit_analysis") else None,
+ velocity_analysis=data.velocity_analysis if hasattr(data, "velocity_analysis") else None,
+ code_churn_analysis=data.code_churn_analysis if hasattr(data, "code_churn_analysis") else None,
+ risky_commit_analysis=data.risky_commit_analysis if hasattr(data, "risky_commit_analysis") else None,
+ )