Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,11 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
<span class="truncate">Issues</span> ${getSortIcon('issues_count')}
</div>
</th>
<th class="cursor-pointer px-2 py-3 hover:bg-slate-100 dark:hover:bg-slate-900" data-sort-column="risk_summary" style="min-width: 200px;" title="AI-generated risk summary for the PR">
<div class="flex items-center gap-1 min-w-0">
<span class="truncate">Risk Summary</span>
</div>
</th>
<th class="cursor-pointer px-2 py-3 hover:bg-slate-100 dark:hover:bg-slate-900" data-sort-column="last_updated_at" style="min-width: 90px;" title="Click to sort by PR last updated time. Shift+Click to add to sort columns.&#10;API: GET /repos/{owner}/{repo}/pulls/{pr_number} (updated_at field)">
<div class="flex items-center gap-1 min-w-0">
<i class="fas fa-clock text-slate-400 dark:text-slate-500 flex-shrink-0"></i>
Expand Down Expand Up @@ -2704,6 +2709,9 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${emptyTitl
<td class="px-2 py-3" id="readiness-issues-${pr.id}">
<span class="text-xs text-slate-400 dark:text-slate-500">-</span>
</td>
<td class="px-2 py-3 text-center" id="readiness-risk-summary-${pr.id}">
<span class="text-xs text-slate-400 dark:text-slate-500">-</span>
</td>
<td class="px-2 py-3 text-xs text-slate-600 dark:text-slate-400 truncate">
<span class="inline-flex items-center gap-1"><i class="fas fa-clock text-slate-400 dark:text-slate-500"></i>${escapeHtml(timeAgo(pr.last_updated_at))}</span>
</td>
Expand Down Expand Up @@ -3149,6 +3157,25 @@ <h3 class="text-xl font-semibold text-slate-900 dark:text-slate-100">${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 = `
<span class="text-xs ${colorClass}" title="${escapeHtml(summary)}">
${escapeHtml(summary)}
</span>`;
} else {
riskSummaryCell.innerHTML = '<span class="text-xs text-slate-400">-</span>';
}
}

Comment on lines +3160 to +3178
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Risk summary update block is outside updateInlineCells and uses undefined pr.

Line 3161 executes at script load and references pr.id, which triggers a runtime error. Move this block inside updateInlineCells and use prId.

🐛 Suggested fix
 function updateInlineCells(prId, readiness, reviewHealth) {
@@
             if (issuesCell) {
@@
                 issuesCell.innerHTML = issuesHTML;
             }
-        }
- 
-         // Update Risk Summary
-            const riskSummaryCell = document.getElementById(`readiness-risk-summary-${pr.id}`);
+
+            // Update Risk Summary
+            const riskSummaryCell = document.getElementById(`readiness-risk-summary-${prId}`);
             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 = `
                         <span class="text-xs ${colorClass}" title="${escapeHtml(summary)}">
                             ${escapeHtml(summary)}
                         </span>`;
                 } else {
                     riskSummaryCell.innerHTML = '<span class="text-xs text-slate-400">-</span>';
                 }
             }
-        
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/index.html` around lines 3160 - 3178, The risk summary DOM update
block currently runs at module load and references an undefined pr (pr.id),
causing a runtime error; move that entire block into the updateInlineCells
function and change references from pr.id to prId (the function's parameter),
locating the logic around readiness, riskSummaryCell and escapeHtml so it uses
the passed-in prId and the existing readiness object inside updateInlineCells;
ensure the fallback branch remains the same but uses prId to build the element
id (`readiness-risk-summary-${prId}`) and keep colorClass computation and
innerHTML assignments unchanged.

function invalidateApiCache(path) {
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
Expand Down
81 changes: 75 additions & 6 deletions src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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."
Loading