diff --git a/public/index.html b/public/index.html index ff0b215..f9e7c85 100644 --- a/public/index.html +++ b/public/index.html @@ -2378,6 +2378,11 @@

${emptyTitl Issues ${getSortIcon('issues_count')} + +
+ Risk Summary +
+
@@ -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."