|
@@ -2704,6 +2709,9 @@ ${emptyTitl
|
-
|
+
+ -
+ |
${escapeHtml(timeAgo(pr.last_updated_at))}
|
@@ -3149,6 +3157,25 @@ ${emptyTitl
}
}
+ // Update Risk Summary
+ const riskSummaryCell = document.getElementById(`readiness-risk-summary-${pr.id}`);
+ if (riskSummaryCell) {
+ const summary = readiness.risk_summary;
+ if (summary && summary.trim()) {
+ const colorClass = readiness.merge_ready
+ ? 'text-emerald-700 dark:text-emerald-400'
+ : readiness.blockers && readiness.blockers.length > 0
+ ? 'text-red-700 dark:text-red-400'
+ : 'text-amber-700 dark:text-amber-400';
+ riskSummaryCell.innerHTML = `
+
+ ${escapeHtml(summary)}
+ `;
+ } else {
+ riskSummaryCell.innerHTML = '-';
+ }
+ }
+
function invalidateApiCache(path) {
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
diff --git a/src/utils.py b/src/utils.py
index 9295747..c287717 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -16,12 +16,12 @@ def parse_pr_url(pr_url):
"""
Parse GitHub PR URL to extract owner, repo, and PR number.
- Security Hardening (Issue #45):
+ Security Hardening (Issue `#45`):
- Type validation to prevent type confusion attacks
- Anchored regex pattern to block malformed URLs with trailing junk
- Raises ValueError instead of returning None for better error handling
"""
- # FIX Issue #45: Type validation
+ # FIX Issue `#45`: Type validation
if not isinstance(pr_url, str):
raise ValueError("PR URL must be a string")
@@ -30,12 +30,12 @@ def parse_pr_url(pr_url):
pr_url = pr_url.strip().rstrip('/')
- # FIX Issue #45: Anchored regex - must match EXACTLY, no trailing junk allowed
+ # FIX Issue `#45`: Anchored regex - must match EXACTLY, no trailing junk allowed
pattern = r'^https?://github\.com/([^/]+)/([^/]+)/pull/(\d+)$'
match = re.match(pattern, pr_url)
if not match:
- # FIX Issue #45: Raise error instead of returning None
+ # FIX Issue `#45`: Raise error instead of returning None
raise ValueError("Invalid GitHub PR URL. Format: https://github.com/OWNER/REPO/pull/NUMBER")
return {
@@ -576,7 +576,7 @@ def calculate_pr_readiness(pr_data, review_classification, review_score):
classification = 'NEEDS_WORK'
else:
classification = 'NOT_READY'
-
+
return {
'overall_score': overall_score,
'ci_score': ci_score,
@@ -585,5 +585,74 @@ def calculate_pr_readiness(pr_data, review_classification, review_score):
'merge_ready': merge_ready,
'blockers': blockers,
'warnings': warnings,
- 'recommendations': recommendations
+ 'recommendations': recommendations,
+ 'risk_summary': generate_ai_risk_summary({
+ 'overall_score': overall_score,
+ 'ci_score': ci_score,
+ 'review_score': review_score,
+ 'classification': classification,
+ 'merge_ready': merge_ready,
+ 'blockers': blockers,
+ 'warnings': warnings,
+ 'recommendations': recommendations
+ })
}
+
+
+#Add ai summary feat
+def generate_ai_risk_summary(pr_readiness_data):
+ """
+ Generate a concise AI-powered risk summary for a PR.
+
+ Args:
+ pr_readiness_data: Dict from calculate_pr_readiness()
+
+ Returns:
+ str: AI-generated summary or fallback summary
+ """
+ # Extract key data for the prompt
+ blockers = pr_readiness_data.get('blockers', [])
+ warnings = pr_readiness_data.get('warnings', [])
+ recommendations = pr_readiness_data.get('recommendations', [])
+ overall_score = pr_readiness_data.get('overall_score', 0)
+ classification = pr_readiness_data.get('classification', 'NOT_READY')
+
+ # Build the prompt for Gemini
+ prompt = (
+ f"Generate a concise, professional risk summary for a PR with the following details:\n"
+ f"- Classification: {classification}\n"
+ f"- Overall score: {overall_score}\n"
+ f"- Blockers: {blockers}\n"
+ f"- Warnings: {warnings}\n"
+ f"- Recommendations: {recommendations}\n\n"
+ f"Focus on why the PR may be risky and what should be addressed first. "
+ f"Keep the summary to 1-2 sentences. If the PR is ready, say so clearly."
+ )
+
+ # Call Gemini (pseudo-code; replace with actual API call)
+ # TODO: Implement actual Gemini API call
+ return generate_fallback_summary(pr_readiness_data)
+
+
+def generate_fallback_summary(pr_readiness_data):
+ """
+ Generate a deterministic fallback summary if AI fails.
+ """
+ blockers = pr_readiness_data.get('blockers', [])
+ warnings = pr_readiness_data.get('warnings', [])
+ classification = pr_readiness_data.get('classification', 'NOT_READY')
+
+ if blockers:
+ return (
+ f"This PR is not merge-ready due to {len(blockers)} blocker(s), "
+ f"including: {', '.join(blockers[:2])}. "
+ f"Address these issues before proceeding."
+ )
+ elif warnings:
+ return (
+ f"This PR is nearly ready but has {len(warnings)} warning(s), "
+ f"such as: {', '.join(warnings[:2])}. "
+ f"Review these before merging."
+ )
+ else:
+ return f"This PR is {classification.lower().replace('_', ' ')} and ready for review/merge."
|