From a30ba496bf36ec3f67bee1e43ae9d716d13f4f55 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 00:55:31 +0530 Subject: [PATCH 01/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 366 ++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 .github/workflows/ai-pr.yml diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml new file mode 100644 index 0000000..5a2bd4e --- /dev/null +++ b/.github/workflows/ai-pr.yml @@ -0,0 +1,366 @@ +name: AI Automated PR Review with Inline Comments +on: + pull_request: + types: [opened, synchronize] + +jobs: + ai_pr_review: + runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GITHUB_TOKEN: ${{ github.token }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_REPO: ${{ github.repository }} + + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq curl + # Install GitHub CLI + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update + sudo apt-get install -y gh + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} + + - name: Validate environment variables + run: | + if [ -z "$OPENAI_API_KEY" ]; then + echo "❌ OPENAI_API_KEY is not set" + exit 1 + fi + echo "✅ Environment variables validated" + + - name: Extract PR changes and files + id: extract_changes + run: | + # Get PR commit SHAs + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + + echo "BASE_SHA=$BASE_SHA" >> $GITHUB_ENV + echo "HEAD_SHA=$HEAD_SHA" >> $GITHUB_ENV + + # Extract git diff + git diff $BASE_SHA..$HEAD_SHA > pr_diff.txt + + # Fetch GitHub API diff with file information + curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ + > pr_files.json + + - name: Check for meaningful changes + id: change_detection + run: | + if [ -s pr_diff.txt ] && [ "$(cat pr_files.json | jq length)" -gt 0 ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "✅ Detected meaningful changes in PR" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "ℹ️ No meaningful changes detected" + fi + + - name: Test OpenAI API connectivity + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "🧪 Testing OpenAI API connectivity..." + response=$(curl -s https://api.openai.com/v1/models \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -w "%{http_code}") + + http_code="${response: -3}" + if [ "$http_code" = "200" ]; then + echo "✅ OpenAI API connection successful" + else + echo "❌ OpenAI API connection failed with code: $http_code" + exit 1 + fi + + - name: Generate AI inline review comments + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "🤖 Generating AI-powered inline review comments..." + + # Initialize inline comments array + echo "[]" > inline_comments.json + + # Get total files to process + total_files=$(cat pr_files.json | jq '[.[] | select(.patch != null)] | length') + echo "📊 Found $total_files files with patches to analyze" + + if [ "$total_files" -eq 0 ]; then + echo "ℹ️ No files with patches found, skipping AI analysis" + exit 0 + fi + + # Process each changed file with patches + echo "$(cat pr_files.json)" | jq -r '.[] | select(.patch != null) | @base64' | while read -r encoded_file; do + if [ -z "$encoded_file" ]; then + continue + fi + + file_data=$(echo "$encoded_file" | base64 -d) + filename=$(echo "$file_data" | jq -r '.filename') + patch=$(echo "$file_data" | jq -r '.patch // ""') + + if [ -n "$patch" ] && [ "$patch" != "null" ] && [ "$patch" != "" ]; then + echo "📝 Processing file: $filename" + + # Create structured prompt for AI with specific instructions + escaped_patch=$(echo "$patch" | jq -Rs .) + escaped_filename=$(echo "$filename" | jq -Rs .) + + # AI prompt with structured output schema + ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"gpt-4o-mini\", + \"messages\": [ + { + \"role\": \"system\", + \"content\": \"You are an expert code reviewer. Analyze the provided git patch and generate 1-3 specific inline review suggestions. For each suggestion, you MUST identify the exact line number in the RIGHT side of the diff where the comment should be placed. Return ONLY a valid JSON array with objects containing: {\\\"path\\\": \\\"filename\\\", \\\"line\\\": line_number, \\\"side\\\": \\\"RIGHT\\\", \\\"body\\\": \\\"suggestion_text\\\"}. Only suggest improvements for actual code changes (lines starting with +), not context lines. Focus on code quality, security, performance, and best practices. If no suggestions are needed, return an empty array [].\" + }, + { + \"role\": \"user\", + \"content\": \"File: $escaped_filename\\n\\nPatch:\\n$escaped_patch\" + } + ], + \"max_tokens\": 600, + \"temperature\": 0.2, + \"response_format\": { + \"type\": \"json_schema\", + \"json_schema\": { + \"name\": \"review_comments\", + \"schema\": { + \"type\": \"object\", + \"properties\": { + \"comments\": { + \"type\": \"array\", + \"items\": { + \"type\": \"object\", + \"properties\": { + \"path\": {\"type\": \"string\"}, + \"line\": {\"type\": \"integer\", \"minimum\": 1}, + \"side\": {\"type\": \"string\", \"enum\": [\"RIGHT\"]}, + \"body\": {\"type\": \"string\", \"minLength\": 10} + }, + \"required\": [\"path\", \"line\", \"side\", \"body\"], + \"additionalProperties\": false + } + } + }, + \"required\": [\"comments\"], + \"additionalProperties\": false + }, + \"strict\": true + } + } + }") + + # Extract and validate AI response + if echo "$ai_response" | jq -e .choices > /dev/null 2>&1; then + ai_content=$(echo "$ai_response" | jq -r '.choices[0].message.content // "{\"comments\": []}"') + + # Validate JSON structure and extract comments + if echo "$ai_content" | jq empty 2>/dev/null; then + suggestions=$(echo "$ai_content" | jq -r '.comments // []') + + # Validate that suggestions is an array and has valid structure + if echo "$suggestions" | jq -e 'type == "array"' > /dev/null 2>&1; then + suggestion_count=$(echo "$suggestions" | jq 'length') + + if [ "$suggestion_count" -gt 0 ]; then + echo "✅ Generated $suggestion_count suggestions for $filename" + + # Validate each suggestion has required fields before adding + valid_suggestions=$(echo "$suggestions" | jq '[.[] | select(.path and .line and .side and .body and (.line | type == "number") and .line > 0)]') + valid_count=$(echo "$valid_suggestions" | jq 'length') + + if [ "$valid_count" -gt 0 ]; then + # Add suggestions to the main comments array + current_comments=$(cat inline_comments.json) + echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp + mv inline_comments.json.tmp inline_comments.json + echo "✅ Added $valid_count valid suggestions for $filename" + else + echo "⚠️ No valid suggestions after validation for $filename" + fi + else + echo "ℹ️ No suggestions generated for $filename" + fi + else + echo "⚠️ Invalid suggestions array format for $filename" + fi + else + echo "⚠️ Invalid JSON response from AI for $filename" + echo "AI Response: $ai_content" + fi + else + echo "⚠️ AI API call failed for $filename" + echo "Response: $ai_response" + fi + else + echo "⚠️ No patch data for $filename" + fi + done + + - name: Create PR review with inline comments + if: steps.change_detection.outputs.has_changes == 'true' + run: | + COMMIT_SHA="${{ github.event.pull_request.head.sha }}" + comment_count=$(cat inline_comments.json | jq 'length') + + echo "📊 Total inline comments generated: $comment_count" + + if [ "$comment_count" -gt 0 ]; then + echo "🚀 Creating PR review with inline comments..." + + # Prepare review body + review_body="🤖 **AI Code Review**\n\nI've analyzed your changes and provided $comment_count specific inline suggestions. These recommendations focus on code quality, best practices, and potential improvements." + + # Create the review with inline comments + review_response=$(curl -s -w "%{http_code}" -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ + -d "{ + \"commit_id\": \"$COMMIT_SHA\", + \"body\": \"$review_body\", + \"event\": \"COMMENT\", + \"comments\": $(cat inline_comments.json) + }") + + http_code="${review_response: -3}" + review_body_response="${review_response%???}" + + if [ "$http_code" = "200" ]; then + echo "✅ Successfully created PR review with $comment_count inline comments" + + # Extract review ID for logging + review_id=$(echo "$review_body_response" | jq -r '.id // "unknown"') + echo "📋 Review ID: $review_id" + else + echo "❌ Failed to create PR review. HTTP status: $http_code" + echo "Response: $review_body_response" + + # Create a fallback comment if review creation fails + fallback_body="🤖 **AI Code Review Summary**\n\nI attempted to create an inline review but encountered an issue. Here are the $comment_count suggestions I generated:\n\n$(cat inline_comments.json | jq -r '.[] | \"**\" + .path + \"** (line \" + (.line|tostring) + \"): \" + .body' | head -10)" + + gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ + -F body="$fallback_body" || echo "⚠️ Fallback comment creation also failed" + fi + else + echo "ℹ️ No inline comments to create - skipping review creation" + + # Post a summary comment indicating no issues found + summary_body="🤖 **AI Code Review Complete**\n\n✅ I've analyzed your changes and found no specific issues requiring inline comments. The code looks good overall!" + + gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ + -F body="$summary_body" || echo "⚠️ Summary comment creation failed" + fi + + - name: Generate comprehensive PR description + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "📝 Generating comprehensive PR description..." + + # Get full diff content for description generation + full_diff=$(cat pr_diff.txt) + escaped_diff=$(echo "$full_diff" | jq -Rs .) + + description_response=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"gpt-4o-mini\", + \"messages\": [ + { + \"role\": \"system\", + \"content\": \"You create concise, professional PR descriptions. Focus on what changed, why it changed, and the impact. Use markdown formatting.\" + }, + { + \"role\": \"user\", + \"content\": $escaped_diff + } + ], + \"max_tokens\": 400, + \"temperature\": 0.3 + }") + + if echo "$description_response" | jq -e .choices > /dev/null 2>&1; then + pr_desc=$(echo "$description_response" | jq -r '.choices[0].message.content // "Failed to generate PR description."') + else + pr_desc="## Summary\n\nThis PR includes changes to improve the codebase. Please review the inline comments for specific suggestions." + fi + + echo "PR_DESCRIPTION<> $GITHUB_ENV + echo "$pr_desc" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Update PR description + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "📄 Updating PR description..." + + # Update PR body with generated description + gh api repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER \ + -X PATCH \ + -F body="$PR_DESCRIPTION" || echo "⚠️ PR description update failed" + + - name: Post review summary + if: steps.change_detection.outputs.has_changes == 'true' + run: | + comment_count=$(cat inline_comments.json | jq 'length') + + summary="### 📊 AI Review Summary\n\n" + summary+="- **Files analyzed**: $(cat pr_files.json | jq length)\n" + summary+="- **Inline comments**: $comment_count\n" + summary+="- **Review status**: Complete ✅\n\n" + + if [ "$comment_count" -gt 0 ]; then + summary+="💡 **Key suggestions**: Check the inline comments above for specific recommendations on code quality and best practices.\n\n" + else + summary+="🎉 **Great work!** No specific issues identified in this review.\n\n" + fi + + summary+="_This review was automatically generated by AI. Please consider the suggestions as recommendations and apply your judgment._" + + # Find and update existing summary comment or create new one + existing_comment_id=$(gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ + -q ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 📊 AI Review Summary\")) | .id" | head -1) + + if [ -n "$existing_comment_id" ]; then + gh api repos/$GITHUB_REPO/issues/comments/$existing_comment_id \ + -X PATCH -F body="$summary" || echo "⚠️ Failed to update existing summary" + else + gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ + -F body="$summary" || echo "⚠️ Failed to create new summary" + fi + + - name: Cleanup temporary files + if: always() + run: | + echo "🧹 Cleaning up temporary files..." + rm -f pr_diff.txt pr_files.json inline_comments.json inline_comments.json.tmp + echo "✅ Cleanup complete" + + - name: Report workflow status + if: always() + run: | + if [ "${{ steps.change_detection.outputs.has_changes }}" == "true" ]; then + comment_count=$([ -f inline_comments.json ] && cat inline_comments.json | jq 'length' || echo "0") + echo "🎯 **Workflow Summary**:" + echo "- Changes detected: ✅" + echo "- Inline comments generated: $comment_count" + echo "- Review posted: $([ "$comment_count" -gt 0 ] && echo "✅" || echo "ℹ️ (No issues found)")" + else + echo "ℹ️ No changes requiring review detected" + fi From 1ef2a72859a32f998e401ecbc96a78e9845409e9 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 01:27:50 +0530 Subject: [PATCH 02/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index 5a2bd4e..5d7bc83 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -363,4 +363,4 @@ jobs: echo "- Review posted: $([ "$comment_count" -gt 0 ] && echo "✅" || echo "ℹ️ (No issues found)")" else echo "ℹ️ No changes requiring review detected" - fi + fi \ No newline at end of file From 04589f5a75e1609f7f6f785d8141564984c148ce Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 01:35:20 +0530 Subject: [PATCH 03/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 380 ++++++++++-------------------------- 1 file changed, 98 insertions(+), 282 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index 5d7bc83..dd3cb3d 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -15,217 +15,123 @@ jobs: steps: - name: Install dependencies run: | - sudo apt-get update - sudo apt-get install -y jq curl - # Install GitHub CLI + sudo apt-get update && sudo apt-get install -y jq curl curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update - sudo apt-get install -y gh + sudo apt-get update && sudo apt-get install -y gh - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.head_ref }} - - name: Validate environment variables + - name: Validate environment run: | if [ -z "$OPENAI_API_KEY" ]; then echo "❌ OPENAI_API_KEY is not set" exit 1 fi - echo "✅ Environment variables validated" - - name: Extract PR changes and files - id: extract_changes + - name: Extract PR changes run: | - # Get PR commit SHAs BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" - + echo "BASE_SHA=$BASE_SHA" >> $GITHUB_ENV echo "HEAD_SHA=$HEAD_SHA" >> $GITHUB_ENV - # Extract git diff - git diff $BASE_SHA..$HEAD_SHA > pr_diff.txt - - # Fetch GitHub API diff with file information + # Get detailed file changes with line numbers for inline comments curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ > pr_files.json - - name: Check for meaningful changes - id: change_detection + - name: Generate AI inline comments run: | - if [ -s pr_diff.txt ] && [ "$(cat pr_files.json | jq length)" -gt 0 ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "✅ Detected meaningful changes in PR" - else - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "ℹ️ No meaningful changes detected" - fi - - - name: Test OpenAI API connectivity - if: steps.change_detection.outputs.has_changes == 'true' - run: | - echo "🧪 Testing OpenAI API connectivity..." - response=$(curl -s https://api.openai.com/v1/models \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -w "%{http_code}") - - http_code="${response: -3}" - if [ "$http_code" = "200" ]; then - echo "✅ OpenAI API connection successful" - else - echo "❌ OpenAI API connection failed with code: $http_code" - exit 1 - fi - - - name: Generate AI inline review comments - if: steps.change_detection.outputs.has_changes == 'true' - run: | - echo "🤖 Generating AI-powered inline review comments..." - - # Initialize inline comments array + echo "🤖 Analyzing code changes for inline review..." + + # Initialize results echo "[]" > inline_comments.json + total_comments=0 - # Get total files to process - total_files=$(cat pr_files.json | jq '[.[] | select(.patch != null)] | length') - echo "📊 Found $total_files files with patches to analyze" - - if [ "$total_files" -eq 0 ]; then - echo "ℹ️ No files with patches found, skipping AI analysis" - exit 0 - fi - - # Process each changed file with patches - echo "$(cat pr_files.json)" | jq -r '.[] | select(.patch != null) | @base64' | while read -r encoded_file; do - if [ -z "$encoded_file" ]; then + # Process each file with patches + cat pr_files.json | jq -c '.[] | select(.patch != null)' | while read -r file_data; do + filename=$(echo "$file_data" | jq -r '.filename') + patch=$(echo "$file_data" | jq -r '.patch') + + echo "📝 Analyzing: $filename" + + # Extract added lines from patch for precise line targeting + added_lines=$(echo "$patch" | grep -n "^+" | grep -v "^+++" | sed 's/^\([0-9]*\):+/\1:/' || true) + + if [ -z "$added_lines" ]; then + echo "ℹ️ No added lines found in $filename" continue fi + + # Create enhanced AI prompt for comprehensive review including test scenarios + escaped_patch=$(echo "$patch" | jq -Rs .) + escaped_filename=$(echo "$filename" | jq -Rs .) - file_data=$(echo "$encoded_file" | base64 -d) - filename=$(echo "$file_data" | jq -r '.filename') - patch=$(echo "$file_data" | jq -r '.patch // ""') - - if [ -n "$patch" ] && [ "$patch" != "null" ] && [ "$patch" != "" ]; then - echo "📝 Processing file: $filename" + ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"gpt-4o-mini\", + \"messages\": [ + { + \"role\": \"system\", + \"content\": \"You are an expert code reviewer. Analyze the git patch and provide specific inline comments for code quality, security, performance, best practices, and test scenarios. Focus ONLY on lines that start with '+' (new/changed code). For each suggestion, specify the exact line number where it should appear in the diff. Return a JSON array with objects: [{\\\"path\\\": \\\"filename\\\", \\\"line\\\": line_number, \\\"side\\\": \\\"RIGHT\\\", \\\"body\\\": \\\"detailed_comment_with_suggestions_and_test_scenarios\\\"}]. If no issues found, return [].\" + }, + { + \"role\": \"user\", + \"content\": \"Review this code change and suggest improvements including potential test scenarios:\\n\\nFile: $escaped_filename\\n\\nPatch:\\n$escaped_patch\" + } + ], + \"max_tokens\": 800, + \"temperature\": 0.1 + }") + + # Process AI response and extract valid comments + if echo "$ai_response" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + ai_content=$(echo "$ai_response" | jq -r '.choices[0].message.content') - # Create structured prompt for AI with specific instructions - escaped_patch=$(echo "$patch" | jq -Rs .) - escaped_filename=$(echo "$filename" | jq -Rs .) + # Try to parse as JSON array + if echo "$ai_content" | jq -e 'type == "array"' > /dev/null 2>&1; then + suggestions=$(echo "$ai_content") + else + # Handle cases where AI returns JSON wrapped in markdown + suggestions=$(echo "$ai_content" | sed -n '/\[/,/\]/p' | jq '.' 2>/dev/null || echo "[]") + fi - # AI prompt with structured output schema - ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\": \"gpt-4o-mini\", - \"messages\": [ - { - \"role\": \"system\", - \"content\": \"You are an expert code reviewer. Analyze the provided git patch and generate 1-3 specific inline review suggestions. For each suggestion, you MUST identify the exact line number in the RIGHT side of the diff where the comment should be placed. Return ONLY a valid JSON array with objects containing: {\\\"path\\\": \\\"filename\\\", \\\"line\\\": line_number, \\\"side\\\": \\\"RIGHT\\\", \\\"body\\\": \\\"suggestion_text\\\"}. Only suggest improvements for actual code changes (lines starting with +), not context lines. Focus on code quality, security, performance, and best practices. If no suggestions are needed, return an empty array [].\" - }, - { - \"role\": \"user\", - \"content\": \"File: $escaped_filename\\n\\nPatch:\\n$escaped_patch\" - } - ], - \"max_tokens\": 600, - \"temperature\": 0.2, - \"response_format\": { - \"type\": \"json_schema\", - \"json_schema\": { - \"name\": \"review_comments\", - \"schema\": { - \"type\": \"object\", - \"properties\": { - \"comments\": { - \"type\": \"array\", - \"items\": { - \"type\": \"object\", - \"properties\": { - \"path\": {\"type\": \"string\"}, - \"line\": {\"type\": \"integer\", \"minimum\": 1}, - \"side\": {\"type\": \"string\", \"enum\": [\"RIGHT\"]}, - \"body\": {\"type\": \"string\", \"minLength\": 10} - }, - \"required\": [\"path\", \"line\", \"side\", \"body\"], - \"additionalProperties\": false - } - } - }, - \"required\": [\"comments\"], - \"additionalProperties\": false - }, - \"strict\": true - } - } - }") + # Validate and add suggestions + valid_suggestions=$(echo "$suggestions" | jq '[.[] | select(.path and .line and .side and .body and (.line | type == "number"))]' 2>/dev/null || echo "[]") + suggestion_count=$(echo "$valid_suggestions" | jq 'length') - # Extract and validate AI response - if echo "$ai_response" | jq -e .choices > /dev/null 2>&1; then - ai_content=$(echo "$ai_response" | jq -r '.choices[0].message.content // "{\"comments\": []}"') + if [ "$suggestion_count" -gt 0 ]; then + echo "✅ Generated $suggestion_count inline comments for $filename" - # Validate JSON structure and extract comments - if echo "$ai_content" | jq empty 2>/dev/null; then - suggestions=$(echo "$ai_content" | jq -r '.comments // []') - - # Validate that suggestions is an array and has valid structure - if echo "$suggestions" | jq -e 'type == "array"' > /dev/null 2>&1; then - suggestion_count=$(echo "$suggestions" | jq 'length') - - if [ "$suggestion_count" -gt 0 ]; then - echo "✅ Generated $suggestion_count suggestions for $filename" - - # Validate each suggestion has required fields before adding - valid_suggestions=$(echo "$suggestions" | jq '[.[] | select(.path and .line and .side and .body and (.line | type == "number") and .line > 0)]') - valid_count=$(echo "$valid_suggestions" | jq 'length') - - if [ "$valid_count" -gt 0 ]; then - # Add suggestions to the main comments array - current_comments=$(cat inline_comments.json) - echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp - mv inline_comments.json.tmp inline_comments.json - echo "✅ Added $valid_count valid suggestions for $filename" - else - echo "⚠️ No valid suggestions after validation for $filename" - fi - else - echo "ℹ️ No suggestions generated for $filename" - fi - else - echo "⚠️ Invalid suggestions array format for $filename" - fi - else - echo "⚠️ Invalid JSON response from AI for $filename" - echo "AI Response: $ai_content" - fi - else - echo "⚠️ AI API call failed for $filename" - echo "Response: $ai_response" + # Merge with existing comments + current_comments=$(cat inline_comments.json) + echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp + mv inline_comments.json.tmp inline_comments.json + total_comments=$((total_comments + suggestion_count)) fi else - echo "⚠️ No patch data for $filename" + echo "⚠️ AI API call failed for $filename" fi done - - name: Create PR review with inline comments - if: steps.change_detection.outputs.has_changes == 'true' + - name: Post inline review comments run: | COMMIT_SHA="${{ github.event.pull_request.head.sha }}" comment_count=$(cat inline_comments.json | jq 'length') - - echo "📊 Total inline comments generated: $comment_count" + + echo "📊 Generated $comment_count inline comments" if [ "$comment_count" -gt 0 ]; then - echo "🚀 Creating PR review with inline comments..." - - # Prepare review body - review_body="🤖 **AI Code Review**\n\nI've analyzed your changes and provided $comment_count specific inline suggestions. These recommendations focus on code quality, best practices, and potential improvements." + echo "🚀 Posting inline review comments..." - # Create the review with inline comments + # Create review with inline comments using the correct GitHub API review_response=$(curl -s -w "%{http_code}" -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ @@ -233,134 +139,44 @@ jobs: "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ -d "{ \"commit_id\": \"$COMMIT_SHA\", - \"body\": \"$review_body\", + \"body\": \"🤖 **AI Code Review** - Found $comment_count areas for improvement\", \"event\": \"COMMENT\", \"comments\": $(cat inline_comments.json) }") http_code="${review_response: -3}" - review_body_response="${review_response%???}" if [ "$http_code" = "200" ]; then - echo "✅ Successfully created PR review with $comment_count inline comments" - - # Extract review ID for logging - review_id=$(echo "$review_body_response" | jq -r '.id // "unknown"') - echo "📋 Review ID: $review_id" + echo "✅ Successfully posted $comment_count inline comments" else - echo "❌ Failed to create PR review. HTTP status: $http_code" - echo "Response: $review_body_response" - - # Create a fallback comment if review creation fails - fallback_body="🤖 **AI Code Review Summary**\n\nI attempted to create an inline review but encountered an issue. Here are the $comment_count suggestions I generated:\n\n$(cat inline_comments.json | jq -r '.[] | \"**\" + .path + \"** (line \" + (.line|tostring) + \"): \" + .body' | head -10)" + echo "❌ Failed to post review (HTTP $http_code)" + echo "${review_response%???}" - gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ - -F body="$fallback_body" || echo "⚠️ Fallback comment creation also failed" + # Fallback: Post individual line comments + echo "🔄 Attempting to post individual comments..." + cat inline_comments.json | jq -c '.[]' | while read -r comment; do + path=$(echo "$comment" | jq -r '.path') + line=$(echo "$comment" | jq -r '.line') + body=$(echo "$comment" | jq -r '.body') + + curl -s -X POST \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ + -d "{ + \"commit_id\": \"$COMMIT_SHA\", + \"path\": \"$path\", + \"line\": $line, + \"side\": \"RIGHT\", + \"body\": \"$body\" + }" > /dev/null + done fi else - echo "ℹ️ No inline comments to create - skipping review creation" - - # Post a summary comment indicating no issues found - summary_body="🤖 **AI Code Review Complete**\n\n✅ I've analyzed your changes and found no specific issues requiring inline comments. The code looks good overall!" - - gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ - -F body="$summary_body" || echo "⚠️ Summary comment creation failed" + echo "ℹ️ No inline comments generated - code looks good!" fi - - name: Generate comprehensive PR description - if: steps.change_detection.outputs.has_changes == 'true' - run: | - echo "📝 Generating comprehensive PR description..." - - # Get full diff content for description generation - full_diff=$(cat pr_diff.txt) - escaped_diff=$(echo "$full_diff" | jq -Rs .) - - description_response=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\": \"gpt-4o-mini\", - \"messages\": [ - { - \"role\": \"system\", - \"content\": \"You create concise, professional PR descriptions. Focus on what changed, why it changed, and the impact. Use markdown formatting.\" - }, - { - \"role\": \"user\", - \"content\": $escaped_diff - } - ], - \"max_tokens\": 400, - \"temperature\": 0.3 - }") - - if echo "$description_response" | jq -e .choices > /dev/null 2>&1; then - pr_desc=$(echo "$description_response" | jq -r '.choices[0].message.content // "Failed to generate PR description."') - else - pr_desc="## Summary\n\nThis PR includes changes to improve the codebase. Please review the inline comments for specific suggestions." - fi - - echo "PR_DESCRIPTION<> $GITHUB_ENV - echo "$pr_desc" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Update PR description - if: steps.change_detection.outputs.has_changes == 'true' - run: | - echo "📄 Updating PR description..." - - # Update PR body with generated description - gh api repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER \ - -X PATCH \ - -F body="$PR_DESCRIPTION" || echo "⚠️ PR description update failed" - - - name: Post review summary - if: steps.change_detection.outputs.has_changes == 'true' - run: | - comment_count=$(cat inline_comments.json | jq 'length') - - summary="### 📊 AI Review Summary\n\n" - summary+="- **Files analyzed**: $(cat pr_files.json | jq length)\n" - summary+="- **Inline comments**: $comment_count\n" - summary+="- **Review status**: Complete ✅\n\n" - - if [ "$comment_count" -gt 0 ]; then - summary+="💡 **Key suggestions**: Check the inline comments above for specific recommendations on code quality and best practices.\n\n" - else - summary+="🎉 **Great work!** No specific issues identified in this review.\n\n" - fi - - summary+="_This review was automatically generated by AI. Please consider the suggestions as recommendations and apply your judgment._" - - # Find and update existing summary comment or create new one - existing_comment_id=$(gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ - -q ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 📊 AI Review Summary\")) | .id" | head -1) - - if [ -n "$existing_comment_id" ]; then - gh api repos/$GITHUB_REPO/issues/comments/$existing_comment_id \ - -X PATCH -F body="$summary" || echo "⚠️ Failed to update existing summary" - else - gh api repos/$GITHUB_REPO/issues/$GITHUB_PR_NUMBER/comments \ - -F body="$summary" || echo "⚠️ Failed to create new summary" - fi - - - name: Cleanup temporary files + - name: Cleanup if: always() run: | - echo "🧹 Cleaning up temporary files..." - rm -f pr_diff.txt pr_files.json inline_comments.json inline_comments.json.tmp - echo "✅ Cleanup complete" - - - name: Report workflow status - if: always() - run: | - if [ "${{ steps.change_detection.outputs.has_changes }}" == "true" ]; then - comment_count=$([ -f inline_comments.json ] && cat inline_comments.json | jq 'length' || echo "0") - echo "🎯 **Workflow Summary**:" - echo "- Changes detected: ✅" - echo "- Inline comments generated: $comment_count" - echo "- Review posted: $([ "$comment_count" -gt 0 ] && echo "✅" || echo "ℹ️ (No issues found)")" - else - echo "ℹ️ No changes requiring review detected" - fi \ No newline at end of file + rm -f pr_files.json inline_comments.json inline_comments.json.tmp From 93ac0dbfa66648526b3e16173a33b41864123171 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 01:50:20 +0530 Subject: [PATCH 04/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 199 ++++++++++++++---------------------- 1 file changed, 78 insertions(+), 121 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index dd3cb3d..bcbd233 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -1,4 +1,5 @@ name: AI Automated PR Review with Inline Comments + on: pull_request: types: [opened, synchronize] @@ -16,9 +17,6 @@ jobs: - name: Install dependencies run: | sudo apt-get update && sudo apt-get install -y jq curl - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update && sudo apt-get install -y gh - name: Checkout repository uses: actions/checkout@v4 @@ -28,155 +26,114 @@ jobs: - name: Validate environment run: | if [ -z "$OPENAI_API_KEY" ]; then - echo "❌ OPENAI_API_KEY is not set" + echo "❌ OPENAI_API_KEY is required" exit 1 fi - - name: Extract PR changes + - name: Fetch PR files and patches run: | - BASE_SHA="${{ github.event.pull_request.base.sha }}" - HEAD_SHA="${{ github.event.pull_request.head.sha }}" - - echo "BASE_SHA=$BASE_SHA" >> $GITHUB_ENV - echo "HEAD_SHA=$HEAD_SHA" >> $GITHUB_ENV - - # Get detailed file changes with line numbers for inline comments curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ > pr_files.json - - name: Generate AI inline comments + - name: Parse diff hunks in Bash and extract changed lines run: | - echo "🤖 Analyzing code changes for inline review..." - - # Initialize results - echo "[]" > inline_comments.json - total_comments=0 - - # Process each file with patches - cat pr_files.json | jq -c '.[] | select(.patch != null)' | while read -r file_data; do - filename=$(echo "$file_data" | jq -r '.filename') - patch=$(echo "$file_data" | jq -r '.patch') - - echo "📝 Analyzing: $filename" - - # Extract added lines from patch for precise line targeting - added_lines=$(echo "$patch" | grep -n "^+" | grep -v "^+++" | sed 's/^\([0-9]*\):+/\1:/' || true) - - if [ -z "$added_lines" ]; then - echo "ℹ️ No added lines found in $filename" - continue + echo "{}" > line_mappings.json + + jq -c '.[] | select(.patch != null) | {path: .filename, patch: .patch}' pr_files.json | + while read -r file; do + path=$(echo "$file" | jq -r '.path') + patch=$(echo "$file" | jq -r '.patch') + new_line=0 + added_lines=() + + while IFS= read -r line; do + if [[ $line =~ ^@@\ -(.*)\ \+(.*)\ @@ ]]; then + hunk_info=${BASH_REMATCH[2]} + new_line=${hunk_info%%,*} + continue + fi + if [[ $line =~ ^\+ ]] && [[ ! $line =~ ^\+\+\+ ]]; then + added_lines+=("$new_line") + ((new_line++)) + elif [[ $line =~ ^- ]] && [[ ! $line =~ ^\-\-\- ]]; then + continue + else + ((new_line++)) + fi + done < <(printf '%s\n' "$patch") + + if [ ${#added_lines[@]} -gt 0 ]; then + file_obj=$(jq -n \ + --arg path "$path" \ + --arg patch "$patch" \ + --argjson lines "$(printf '%s\n' "${added_lines[@]}" | jq -R . | jq -cs .)" \ + '{path: $path, patch: $patch, changed_lines: $lines}') + jq --argjson obj "$file_obj" '. + {($obj.path): obj}' line_mappings.json > tmp && mv tmp line_mappings.json + echo "📍 $path → lines ${added_lines[*]:0:10}${#added_lines[@] > 10 ? "…" : ""}" fi + done + + - name: Generate AI review comments via OpenAI + run: | + echo "[]" > ai_comments.json + + cat line_mappings.json | jq -c 'to_entries[]' | + while read -r entry; do + path=$(echo "$entry" | jq -r '.value.path') + patch=$(echo "$entry" | jq -r '.value.patch') + changed_lines=$(echo "$entry" | jq -r '.value.changed_lines[]') - # Create enhanced AI prompt for comprehensive review including test scenarios escaped_patch=$(echo "$patch" | jq -Rs .) - escaped_filename=$(echo "$filename" | jq -Rs .) - + ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"model\": \"gpt-4o-mini\", \"messages\": [ - { - \"role\": \"system\", - \"content\": \"You are an expert code reviewer. Analyze the git patch and provide specific inline comments for code quality, security, performance, best practices, and test scenarios. Focus ONLY on lines that start with '+' (new/changed code). For each suggestion, specify the exact line number where it should appear in the diff. Return a JSON array with objects: [{\\\"path\\\": \\\"filename\\\", \\\"line\\\": line_number, \\\"side\\\": \\\"RIGHT\\\", \\\"body\\\": \\\"detailed_comment_with_suggestions_and_test_scenarios\\\"}]. If no issues found, return [].\" - }, - { - \"role\": \"user\", - \"content\": \"Review this code change and suggest improvements including potential test scenarios:\\n\\nFile: $escaped_filename\\n\\nPatch:\\n$escaped_patch\" - } + {\"role\":\"system\",\"content\":\"You are an expert code reviewer. Analyze the provided git patch and generate inline review comments focusing on code quality, best practices, security, performance, and test scenarios. Only comment on lines added (‘+’). Return a JSON array like [{\\\"line\\\":,\\\"body\\\":\\\"comment text\\\"}]. If no suggestions, return [].\"}, + {\"role\":\"user\",\"content\":\"File: $path\nPatch:\n$escaped_patch\"} ], \"max_tokens\": 800, \"temperature\": 0.1 }") - # Process AI response and extract valid comments - if echo "$ai_response" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - ai_content=$(echo "$ai_response" | jq -r '.choices[0].message.content') - - # Try to parse as JSON array - if echo "$ai_content" | jq -e 'type == "array"' > /dev/null 2>&1; then - suggestions=$(echo "$ai_content") - else - # Handle cases where AI returns JSON wrapped in markdown - suggestions=$(echo "$ai_content" | sed -n '/\[/,/\]/p' | jq '.' 2>/dev/null || echo "[]") - fi - - # Validate and add suggestions - valid_suggestions=$(echo "$suggestions" | jq '[.[] | select(.path and .line and .side and .body and (.line | type == "number"))]' 2>/dev/null || echo "[]") - suggestion_count=$(echo "$valid_suggestions" | jq 'length') - - if [ "$suggestion_count" -gt 0 ]; then - echo "✅ Generated $suggestion_count inline comments for $filename" - - # Merge with existing comments - current_comments=$(cat inline_comments.json) - echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp - mv inline_comments.json.tmp inline_comments.json - total_comments=$((total_comments + suggestion_count)) - fi - else - echo "⚠️ AI API call failed for $filename" + content=$(echo "$ai_response" | jq -r '.choices[0].message.content') + suggestions=$(echo "$content" | sed -n '/\[/,/\]/p' | jq '.' 2>/dev/null || echo "[]") + + count=$(echo "$suggestions" | jq 'length') + if [ "$count" -gt 0 ]; then + formatted=$(echo "$suggestions" | jq --arg p "$path" ' + map({path: $p, line: .line, side: "RIGHT", body: .body})') + jq --argjson new "$formatted" '. + $new' ai_comments.json > tmp && mv tmp ai_comments.json + echo "✅ $count suggestions for $path" fi done - - name: Post inline review comments + - name: Post inline comments to PR run: | COMMIT_SHA="${{ github.event.pull_request.head.sha }}" - comment_count=$(cat inline_comments.json | jq 'length') - - echo "📊 Generated $comment_count inline comments" - - if [ "$comment_count" -gt 0 ]; then - echo "🚀 Posting inline review comments..." - - # Create review with inline comments using the correct GitHub API - review_response=$(curl -s -w "%{http_code}" -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ - -d "{ - \"commit_id\": \"$COMMIT_SHA\", - \"body\": \"🤖 **AI Code Review** - Found $comment_count areas for improvement\", - \"event\": \"COMMENT\", - \"comments\": $(cat inline_comments.json) - }") - - http_code="${review_response: -3}" - - if [ "$http_code" = "200" ]; then - echo "✅ Successfully posted $comment_count inline comments" - else - echo "❌ Failed to post review (HTTP $http_code)" - echo "${review_response%???}" - - # Fallback: Post individual line comments - echo "🔄 Attempting to post individual comments..." - cat inline_comments.json | jq -c '.[]' | while read -r comment; do - path=$(echo "$comment" | jq -r '.path') - line=$(echo "$comment" | jq -r '.line') - body=$(echo "$comment" | jq -r '.body') - - curl -s -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ - -d "{ - \"commit_id\": \"$COMMIT_SHA\", - \"path\": \"$path\", - \"line\": $line, - \"side\": \"RIGHT\", - \"body\": \"$body\" - }" > /dev/null - done - fi + total=$(jq 'length' ai_comments.json) + + if [ "$total" -gt 0 ]; then + echo "Posting $total inline comments..." + jq -c '.[]' ai_comments.json | + while read -r c; do + path=$(echo "$c" | jq -r '.path') + line=$(echo "$c" | jq -r '.line') + body=$(echo "$c" | jq -r '.body') + curl -s -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ + -d "$(jq -n --arg b "$body" --arg p "$path" --arg c "$COMMIT_SHA" --argjson l "$line" '{body:$b, commit_id:$c, path:$p, line:$l, side:"RIGHT"}')" + done else - echo "ℹ️ No inline comments generated - code looks good!" + echo "ℹ️ No inline comments generated—code looks good!" fi - name: Cleanup if: always() run: | - rm -f pr_files.json inline_comments.json inline_comments.json.tmp + rm -f pr_files.json line_mappings.json ai_comments.json tmp \ No newline at end of file From cbf8fc8375e758504a962e9142f27a2bc50d9f80 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 01:55:00 +0530 Subject: [PATCH 05/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 261 ++++++++++++++++++++++-------------- 1 file changed, 157 insertions(+), 104 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index bcbd233..4a252cf 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -1,139 +1,192 @@ -name: AI Automated PR Review with Inline Comments - +name: AI Batching PR Description (OpenAI - Modular) on: pull_request: types: [opened, synchronize] jobs: - ai_pr_review: + generate_pr_description: runs-on: ubuntu-latest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ github.token }} - GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_REPO: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BATCH_SIZE: 150 steps: - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -y jq curl - - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Validate environment + - name: Install dependencies run: | - if [ -z "$OPENAI_API_KEY" ]; then - echo "❌ OPENAI_API_KEY is required" - exit 1 - fi + sudo apt-get update + sudo apt-get install -y jq gh - - name: Fetch PR files and patches + - name: Validate environment variables run: | - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ - > pr_files.json + test -n "$OPENAI_API_KEY" || { echo "❌ OPENAI_API_KEY not set"; exit 1; } - - name: Parse diff hunks in Bash and extract changed lines - run: | - echo "{}" > line_mappings.json + - name: Extract git diff + run: git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > diff.txt - jq -c '.[] | select(.patch != null) | {path: .filename, patch: .patch}' pr_files.json | - while read -r file; do - path=$(echo "$file" | jq -r '.path') - patch=$(echo "$file" | jq -r '.patch') - new_line=0 - added_lines=() + - name: Detect changes + id: detect_changes + run: | + if [ -s diff.txt ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + fi - while IFS= read -r line; do - if [[ $line =~ ^@@\ -(.*)\ \+(.*)\ @@ ]]; then - hunk_info=${BASH_REMATCH[2]} - new_line=${hunk_info%%,*} - continue - fi - if [[ $line =~ ^\+ ]] && [[ ! $line =~ ^\+\+\+ ]]; then - added_lines+=("$new_line") - ((new_line++)) - elif [[ $line =~ ^- ]] && [[ ! $line =~ ^\-\-\- ]]; then - continue - else - ((new_line++)) - fi - done < <(printf '%s\n' "$patch") + - name: Split diff into batches + if: steps.detect_changes.outputs.has_changes == 'true' + run: | + split -l $BATCH_SIZE diff.txt diff_batch_ + echo "BATCH_COUNT=$(ls diff_batch_* | wc -l)" >> $GITHUB_ENV - if [ ${#added_lines[@]} -gt 0 ]; then - file_obj=$(jq -n \ - --arg path "$path" \ - --arg patch "$patch" \ - --argjson lines "$(printf '%s\n' "${added_lines[@]}" | jq -R . | jq -cs .)" \ - '{path: $path, patch: $patch, changed_lines: $lines}') - jq --argjson obj "$file_obj" '. + {($obj.path): obj}' line_mappings.json > tmp && mv tmp line_mappings.json - echo "📍 $path → lines ${added_lines[*]:0:10}${#added_lines[@] > 10 ? "…" : ""}" - fi + - name: Summarize batches via OpenAI + if: steps.detect_changes.outputs.has_changes == 'true' + run: | + rm -f batch_summaries.txt + for batch in diff_batch_*; do + summary=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d @- <> batch_summaries.txt + echo "$summary" | jq -r '.choices[0].message.content' >> batch_summaries.txt + echo >> batch_summaries.txt done - - name: Generate AI review comments via OpenAI + - name: Aggregate and rephrase summary + if: steps.detect_changes.outputs.has_changes == 'true' run: | - echo "[]" > ai_comments.json - - cat line_mappings.json | jq -c 'to_entries[]' | - while read -r entry; do - path=$(echo "$entry" | jq -r '.value.path') - patch=$(echo "$entry" | jq -r '.value.patch') - changed_lines=$(echo "$entry" | jq -r '.value.changed_lines[]') - - escaped_patch=$(echo "$patch" | jq -Rs .) - - ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ + full=$(jq -Rs . < batch_summaries.txt) + pr_desc=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d @- <> $GITHUB_ENV + echo "$pr_desc" | jq -r '.choices[0].message.content' >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Generate AI review & test scenarios + if: steps.detect_changes.outputs.has_changes == 'true' + run: | + diff=$(jq -Rs . < diff.txt) + code_review=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d @- <> $GITHUB_ENV + echo "$code_review" | jq -r '.choices[0].message.content' >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + echo "AI_TEST_SCENARIOS<> $GITHUB_ENV + echo "$test_scenarios" | jq -r '.choices[0].message.content' >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Generate inline comments per file + if: steps.detect_changes.outputs.has_changes == 'true' + run: | + echo "[]" > comments.json + gh pr diff --unified=0 --json files -q '.files[] | {path, patch}' | \ + jq -rc '.[] | select(.patch != null) | [.path, .patch] | @base64' | \ + while read entry; do + decoded=$(echo "$entry" | base64 -d | jq -r '.') + path=$(jq -r '.[0]' <<<"$decoded") + patch=$(jq -r '.[1]' <<<"$decoded") + suggestions=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ - -d "{ - \"model\": \"gpt-4o-mini\", - \"messages\": [ - {\"role\":\"system\",\"content\":\"You are an expert code reviewer. Analyze the provided git patch and generate inline review comments focusing on code quality, best practices, security, performance, and test scenarios. Only comment on lines added (‘+’). Return a JSON array like [{\\\"line\\\":,\\\"body\\\":\\\"comment text\\\"}]. If no suggestions, return [].\"}, - {\"role\":\"user\",\"content\":\"File: $path\nPatch:\n$escaped_patch\"} - ], - \"max_tokens\": 800, - \"temperature\": 0.1 - }") - - content=$(echo "$ai_response" | jq -r '.choices[0].message.content') - suggestions=$(echo "$content" | sed -n '/\[/,/\]/p' | jq '.' 2>/dev/null || echo "[]") - - count=$(echo "$suggestions" | jq 'length') - if [ "$count" -gt 0 ]; then - formatted=$(echo "$suggestions" | jq --arg p "$path" ' - map({path: $p, line: .line, side: "RIGHT", body: .body})') - jq --argjson new "$formatted" '. + $new' ai_comments.json > tmp && mv tmp ai_comments.json - echo "✅ $count suggestions for $path" + -d @- </dev/null <<<"$suggestions"; then + jq --argjson new "$suggestions" '. + $new' comments.json > tmp.json + mv tmp.json comments.json fi done - - name: Post inline comments to PR + - name: Post inline comments as PR review + if: steps.detect_changes.outputs.has_changes == 'true' run: | - COMMIT_SHA="${{ github.event.pull_request.head.sha }}" - total=$(jq 'length' ai_comments.json) - - if [ "$total" -gt 0 ]; then - echo "Posting $total inline comments..." - jq -c '.[]' ai_comments.json | - while read -r c; do - path=$(echo "$c" | jq -r '.path') - line=$(echo "$c" | jq -r '.line') - body=$(echo "$c" | jq -r '.body') - curl -s -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ - -d "$(jq -n --arg b "$body" --arg p "$path" --arg c "$COMMIT_SHA" --argjson l "$line" '{body:$b, commit_id:$c, path:$p, line:$l, side:"RIGHT"}')" - done + comments=$(cat comments.json) + if [ "$(jq length <<<"$comments")" -gt 0 ]; then + gh pr review ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --body "🤖 **AI Code Review Inline Comments**" \ + --comments "$comments" else - echo "ℹ️ No inline comments generated—code looks good!" + echo "No inline comments to post." fi - - name: Cleanup - if: always() + - name: Update PR description + if: steps.detect_changes.outputs.has_changes == 'true' + run: | + gh pr edit ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --body "$PR_DESCRIPTION" + + - name: Post AI review summary comment + if: steps.detect_changes.outputs.has_changes == 'true' run: | - rm -f pr_files.json line_mappings.json ai_comments.json tmp \ No newline at end of file + gh pr comment ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --body $'### 🤖 AI Code Review\n'"$AI_CODE_REVIEW"$'\n---\n### 🧪 AI Test Scenarios\n'"$AI_TEST_SCENARIOS" From a577c855d2aa20990ff869733f8110db412486db Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 01:57:56 +0530 Subject: [PATCH 06/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 239 ++++++++++++++++++++++-------------- 1 file changed, 148 insertions(+), 91 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index 4a252cf..ed1bcdf 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -49,24 +49,29 @@ jobs: run: | rm -f batch_summaries.txt for batch in diff_batch_*; do + content=$(jq -Rs . <"$batch") summary=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ - -d @- <> batch_summaries.txt - echo "$summary" | jq -r '.choices[0].message.content' >> batch_summaries.txt - echo >> batch_summaries.txt + -d "{ + \"model\":\"gpt-4o-mini\", + \"messages\":[ + {\"role\":\"system\",\"content\":\"You summarize git diffs for PR descriptions.\"}, + {\"role\":\"user\",\"content\":$content} + ], + \"max_tokens\":300, + \"temperature\":0.2 + }") + + if echo "$summary" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + echo "## $(basename $batch)" >> batch_summaries.txt + echo "$summary" | jq -r '.choices[0].message.content' >> batch_summaries.txt + echo >> batch_summaries.txt + else + echo "## $(basename $batch)" >> batch_summaries.txt + echo "Error summarizing this batch" >> batch_summaries.txt + echo >> batch_summaries.txt + fi done - name: Aggregate and rephrase summary @@ -76,21 +81,29 @@ EOF pr_desc=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ - -d @- <> $GITHUB_ENV - echo "$pr_desc" | jq -r '.choices[0].message.content' >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + -d "{ + \"model\":\"gpt-4o-mini\", + \"messages\":[ + {\"role\":\"system\",\"content\":\"You create concise, professional PR descriptions.\"}, + {\"role\":\"user\",\"content\":$full} + ], + \"max_tokens\":400, + \"temperature\":0.3 + }") + + if echo "$pr_desc" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + { + echo "PR_DESCRIPTION<> $GITHUB_ENV + else + { + echo "PR_DESCRIPTION<> $GITHUB_ENV + fi - name: Generate AI review & test scenarios if: steps.detect_changes.outputs.has_changes == 'true' @@ -99,80 +112,114 @@ EOF code_review=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ - -d @- <> $GITHUB_ENV - echo "$code_review" | jq -r '.choices[0].message.content' >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "AI_TEST_SCENARIOS<> $GITHUB_ENV - echo "$test_scenarios" | jq -r '.choices[0].message.content' >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + -d "{ + \"model\":\"gpt-4o-mini\", + \"messages\":[ + {\"role\":\"system\",\"content\":\"You are a test architect. Provide test case scenarios.\"}, + {\"role\":\"user\",\"content\":$diff} + ], + \"max_tokens\":500, + \"temperature\":0.2 + }") + + if echo "$code_review" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + { + echo "AI_CODE_REVIEW<> $GITHUB_ENV + else + { + echo "AI_CODE_REVIEW<> $GITHUB_ENV + fi + + if echo "$test_scenarios" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + { + echo "AI_TEST_SCENARIOS<> $GITHUB_ENV + else + { + echo "AI_TEST_SCENARIOS<> $GITHUB_ENV + fi - name: Generate inline comments per file if: steps.detect_changes.outputs.has_changes == 'true' run: | echo "[]" > comments.json - gh pr diff --unified=0 --json files -q '.files[] | {path, patch}' | \ - jq -rc '.[] | select(.patch != null) | [.path, .patch] | @base64' | \ - while read entry; do - decoded=$(echo "$entry" | base64 -d | jq -r '.') - path=$(jq -r '.[0]' <<<"$decoded") - patch=$(jq -r '.[1]' <<<"$decoded") - suggestions=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d @- </dev/null <<<"$suggestions"; then - jq --argjson new "$suggestions" '. + $new' comments.json > tmp.json - mv tmp.json comments.json + + # Get changed files + git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | \ + while read -r filepath; do + if [ -f "$filepath" ]; then + # Get patch for this specific file + patch=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$filepath") + + if [ -n "$patch" ]; then + patch_escaped=$(echo "$patch" | jq -Rs .) + suggestions=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\":\"gpt-4o-mini\", + \"messages\":[ + {\"role\":\"system\",\"content\":\"You are a code reviewer. For the provided diff patch, identify specific lines that need improvement and return a JSON array of objects with: path (string), line (number - the line number in the new version), side (always 'RIGHT'), and body (string - your review comment). Only include actionable feedback.\"}, + {\"role\":\"user\",\"content\":$patch_escaped} + ], + \"max_tokens\":300, + \"temperature\":0.2 + }") + + # Extract JSON response and validate + if echo "$suggestions" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + response_content=$(echo "$suggestions" | jq -r '.choices[0].message.content') + # Try to parse as JSON array + if echo "$response_content" | jq -e 'type == "array"' > /dev/null 2>&1; then + # Update comments.json by merging the new comments + echo "$response_content" | jq --argjson existing "$(cat comments.json)" '$existing + .' > tmp_comments.json + mv tmp_comments.json comments.json + fi + fi + fi fi done - name: Post inline comments as PR review if: steps.detect_changes.outputs.has_changes == 'true' run: | - comments=$(cat comments.json) - if [ "$(jq length <<<"$comments")" -gt 0 ]; then - gh pr review ${{ github.event.pull_request.number }} \ - --repo ${{ github.repository }} \ - --body "🤖 **AI Code Review Inline Comments**" \ - --comments "$comments" + comment_count=$(jq length comments.json) + echo "Found $comment_count inline comments" + + if [ "$comment_count" -gt 0 ]; then + # Create a review with inline comments + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \ + -f body="🤖 **AI Code Review with Inline Comments**" \ + -f event="COMMENT" \ + -f comments="$(cat comments.json)" || echo "Failed to post inline comments" else echo "No inline comments to post." fi @@ -187,6 +234,16 @@ EOF - name: Post AI review summary comment if: steps.detect_changes.outputs.has_changes == 'true' run: | + { + echo "### 🤖 AI Code Review" + echo "$AI_CODE_REVIEW" + echo "" + echo "---" + echo "" + echo "### 🧪 AI Test Scenarios" + echo "$AI_TEST_SCENARIOS" + } > comment_body.txt + gh pr comment ${{ github.event.pull_request.number }} \ --repo ${{ github.repository }} \ - --body $'### 🤖 AI Code Review\n'"$AI_CODE_REVIEW"$'\n---\n### 🧪 AI Test Scenarios\n'"$AI_TEST_SCENARIOS" + --body-file comment_body.txt From c7c127830c3289e87ee42e350e2a21cb3e612e7b Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 08:43:57 +0530 Subject: [PATCH 07/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 659 ++++++++++++++++++++++++++---------- 1 file changed, 480 insertions(+), 179 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index ed1bcdf..c752273 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -8,29 +8,59 @@ jobs: runs-on: ubuntu-latest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_REPO: ${{ github.repository }} BATCH_SIZE: 150 steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y jq gh + sudo apt-get install -y jq + # Install GitHub CLI + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update + sudo apt-get install -y gh + + - name: Checkout repository and set PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} - name: Validate environment variables run: | - test -n "$OPENAI_API_KEY" || { echo "❌ OPENAI_API_KEY not set"; exit 1; } + if [ -z "$OPENAI_API_KEY" ]; then + echo "❌ OPENAI_API_KEY is not set"; exit 1 + fi - name: Extract git diff - run: git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > diff.txt + run: | + git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > diff.txt + + - name: Fetch GitHub API diff with full patch context + run: | + # Fetch files with patches + curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3.patch" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files?per_page=100" \ + > files_diff.json + + # Also fetch the full PR diff for better context + curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3.diff" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER" \ + > pr_full_diff.txt - - name: Detect changes - id: detect_changes + - name: Combine both diffs into JSON + run: | + jq -n --arg gitdiff "$(cat diff.txt)" --argjson apidiff "$(cat files_diff.json)" \ + '{gitdiff: $gitdiff, apidiff: $apidiff}' > combined_diff.json + + - name: Check for meaningful changes + id: change_detection run: | if [ -s diff.txt ]; then echo "has_changes=true" >> $GITHUB_OUTPUT @@ -39,211 +69,482 @@ jobs: fi - name: Split diff into batches - if: steps.detect_changes.outputs.has_changes == 'true' + if: steps.change_detection.outputs.has_changes == 'true' run: | split -l $BATCH_SIZE diff.txt diff_batch_ echo "BATCH_COUNT=$(ls diff_batch_* | wc -l)" >> $GITHUB_ENV - - name: Summarize batches via OpenAI - if: steps.detect_changes.outputs.has_changes == 'true' + - name: Test OpenAI API connectivity + if: steps.change_detection.outputs.has_changes == 'true' + run: | + curl -s https://api.openai.com/v1/models \ + -H "Authorization: Bearer $OPENAI_API_KEY" | jq '.data | length' + + - name: Initialize batch processing + if: steps.change_detection.outputs.has_changes == 'true' + run: echo "PROCESSED_BATCHES=0" >> $GITHUB_ENV + + - name: Process each batch with OpenAI + if: steps.change_detection.outputs.has_changes == 'true' run: | rm -f batch_summaries.txt + processed=0 for batch in diff_batch_*; do - content=$(jq -Rs . <"$batch") - summary=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ + diff_content=$(cat "$batch") + escaped_content=$(echo "$diff_content" | jq -Rs .) + response=$(curl -s "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o-mini\", - \"messages\":[ - {\"role\":\"system\",\"content\":\"You summarize git diffs for PR descriptions.\"}, - {\"role\":\"user\",\"content\":$content} - ], - \"max_tokens\":300, - \"temperature\":0.2 - }") + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You summarize git diffs for PR descriptions.\"},{\"role\":\"user\",\"content\":$escaped_content}],\"max_tokens\":300,\"temperature\":0.2}") - if echo "$summary" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - echo "## $(basename $batch)" >> batch_summaries.txt - echo "$summary" | jq -r '.choices[0].message.content' >> batch_summaries.txt - echo >> batch_summaries.txt - else - echo "## $(basename $batch)" >> batch_summaries.txt - echo "Error summarizing this batch" >> batch_summaries.txt - echo >> batch_summaries.txt + if echo "$response" | jq -e .choices > /dev/null 2>&1; then + summary=$(echo "$response" | jq -r '.choices[0].message.content // ""') + if [ -n "$summary" ]; then + echo "## Batch $(basename "$batch")" >> batch_summaries.txt + echo "$summary" >> batch_summaries.txt; echo "" >> batch_summaries.txt + processed=$((processed+1)) + fi fi done + echo "PROCESSED_BATCHES=$processed" >> $GITHUB_ENV - - name: Aggregate and rephrase summary - if: steps.detect_changes.outputs.has_changes == 'true' + - name: Aggregate and rephrase summaries + if: steps.change_detection.outputs.has_changes == 'true' run: | - full=$(jq -Rs . < batch_summaries.txt) - pr_desc=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ + full_summary=$(cat batch_summaries.txt) + escaped_summary=$(echo "$full_summary" | jq -Rs .) + response=$(curl -s "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o-mini\", - \"messages\":[ - {\"role\":\"system\",\"content\":\"You create concise, professional PR descriptions.\"}, - {\"role\":\"user\",\"content\":$full} - ], - \"max_tokens\":400, - \"temperature\":0.3 - }") - - if echo "$pr_desc" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - { - echo "PR_DESCRIPTION<> $GITHUB_ENV + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You create concise, professional PR descriptions.\"},{\"role\":\"user\",\"content\":$escaped_summary}],\"max_tokens\":400,\"temperature\":0.3}") + if echo "$response" | jq -e .choices > /dev/null 2>&1; then + pr_desc=$(echo "$response" | jq -r '.choices[0].message.content // ""') else - { - echo "PR_DESCRIPTION<> $GITHUB_ENV + pr_desc="Failed to generate PR description." fi + echo "PR_DESCRIPTION<> $GITHUB_ENV + echo "$pr_desc" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV - - name: Generate AI review & test scenarios - if: steps.detect_changes.outputs.has_changes == 'true' + - name: Generate AI Code Review and Test Scenarios + if: steps.change_detection.outputs.has_changes == 'true' + id: ai_reviews run: | - diff=$(jq -Rs . < diff.txt) - code_review=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o-mini\", - \"messages\":[ - {\"role\":\"system\",\"content\":\"You are a strict senior code reviewer. Provide detailed review comments.\"}, - {\"role\":\"user\",\"content\":$diff} - ], - \"max_tokens\":500, - \"temperature\":0.2 - }") + diff=$(cat diff.txt) + escaped_diff=$(echo "$diff" | jq -Rs .) + review=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \ + -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a strict senior code reviewer. Provide detailed review comments.\"},{\"role\":\"user\",\"content\":$escaped_diff}],\"max_tokens\":500,\"temperature\":0.2}") + tests=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \ + -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a test architect. Provide test case scenarios.\"},{\"role\":\"user\",\"content\":$escaped_diff}],\"max_tokens\":500,\"temperature\":0.2}") + review_text=$(echo "$review" | jq -r '.choices[0].message.content // ""') + test_text=$(echo "$tests" | jq -r '.choices[0].message.content // ""') + echo "AI_CODE_REVIEW<> $GITHUB_ENV + echo "$review_text" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + echo "AI_TEST_SCENARIOS<> $GITHUB_ENV + echo "$test_text" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV - test_scenarios=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o-mini\", - \"messages\":[ - {\"role\":\"system\",\"content\":\"You are a test architect. Provide test case scenarios.\"}, - {\"role\":\"user\",\"content\":$diff} - ], - \"max_tokens\":500, - \"temperature\":0.2 - }") + # ENHANCED: Generate comprehensive inline comments for ALL changed files + - name: Generate Comprehensive AI Inline Comments + if: steps.change_detection.outputs.has_changes == 'true' + run: | + COMMIT_SHA="${{ github.event.pull_request.head.sha }}" + + # Initialize empty array for all comments + echo "[]" > all_inline_comments.json + + # Process each file in the PR + echo "📝 Processing files for inline comments..." + file_count=0 + total_comments=0 + + # Get all changed files with their patches + echo "$(cat files_diff.json)" | jq -c '.[]' | while read -r file_data; do + filename=$(echo "$file_data" | jq -r '.filename') + patch=$(echo "$file_data" | jq -r '.patch // ""') + additions=$(echo "$file_data" | jq -r '.additions // 0') + deletions=$(echo "$file_data" | jq -r '.deletions // 0') + + echo "Processing file: $filename (additions: $additions, deletions: $deletions)" + file_count=$((file_count + 1)) + + if [ -n "$patch" ] && [ "$patch" != "null" ] && [ "$patch" != "" ]; then + # Parse the patch to extract line numbers for additions + echo "$patch" | awk -v filename="$filename" ' + BEGIN { + right_line = 0; + in_hunk = 0; + print "[" + first_comment = 1 + } + /^@@/ { + # Parse hunk header: @@ -old_start,old_count +new_start,new_count @@ + match($0, /\+([0-9]+)/, arr) + right_line = arr[1] - 1 + in_hunk = 1 + next + } + in_hunk { + if (/^[+]/) { + right_line++ + # This is an added line - generate comment for it + if (!first_comment) print "," + first_comment = 0 + # Extract the actual code content (remove the + prefix) + code_line = substr($0, 2) + printf "{\"path\":\"%s\",\"position\":%d,\"line\":%d,\"side\":\"RIGHT\"}", filename, NR, right_line + } else if (/^[-]/) { + # Deleted line - dont increment right_line + } else if (/^[ ]/) { + # Context line - increment right_line + right_line++ + } + } + END { print "]" } + ' > temp_lines_${file_count}.json + + # Get all added lines for this file + added_lines=$(cat temp_lines_${file_count}.json | jq -c '.') + + if [ "$(echo "$added_lines" | jq 'length')" -gt 0 ]; then + # Prepare the patch with line numbers for AI analysis + escaped_patch=$(echo "$patch" | jq -Rs .) + escaped_filename=$(echo "$filename" | jq -Rs .) + + # Create a more detailed prompt for AI to analyze EVERY change + ai_prompt="Analyze this Git patch for file $filename and provide specific inline code review comments. - if echo "$code_review" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - { - echo "AI_CODE_REVIEW<> $GITHUB_ENV - else - { - echo "AI_CODE_REVIEW<> $GITHUB_ENV - fi + IMPORTANT INSTRUCTIONS: + 1. Review EVERY added line (lines starting with +) in the patch + 2. Provide constructive feedback for code improvements, best practices, potential bugs, or security issues + 3. Generate AT LEAST one comment for every 5-10 lines of added code + 4. Focus on: + - Code quality and maintainability + - Potential bugs or edge cases + - Security vulnerabilities + - Performance improvements + - Best practices and conventions + - Documentation needs + - Error handling + - Testing requirements - if echo "$test_scenarios" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - { - echo "AI_TEST_SCENARIOS<> $GITHUB_ENV - else - { - echo "AI_TEST_SCENARIOS<> $GITHUB_ENV - fi + Return a JSON array where each object has: + - \"line\": the line number in the new file (right side) + - \"body\": your review comment (be specific and constructive) - - name: Generate inline comments per file - if: steps.detect_changes.outputs.has_changes == 'true' - run: | - echo "[]" > comments.json + Analyze this patch thoroughly and provide comprehensive feedback: + $patch - # Get changed files - git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | \ - while read -r filepath; do - if [ -f "$filepath" ]; then - # Get patch for this specific file - patch=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$filepath") - - if [ -n "$patch" ]; then - patch_escaped=$(echo "$patch" | jq -Rs .) - suggestions=$(curl -s https://api.openai.com/v1/chat/completions \ + Return ONLY valid JSON array. Generate multiple relevant comments for different aspects of the code." + + # Call OpenAI with enhanced prompt for comprehensive review + ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d "{ - \"model\":\"gpt-4o-mini\", + \"model\":\"gpt-4o\", \"messages\":[ - {\"role\":\"system\",\"content\":\"You are a code reviewer. For the provided diff patch, identify specific lines that need improvement and return a JSON array of objects with: path (string), line (number - the line number in the new version), side (always 'RIGHT'), and body (string - your review comment). Only include actionable feedback.\"}, - {\"role\":\"user\",\"content\":$patch_escaped} + { + \"role\":\"system\", + \"content\":\"You are an expert code reviewer. Analyze the provided Git patch and generate comprehensive inline review comments for EVERY significant code change. Focus on code quality, bugs, security, performance, and best practices. Generate AT LEAST one comment per 5-10 lines of new code. Return ONLY a JSON array with objects containing 'line' (number) and 'body' (comment text) fields.\" + }, + { + \"role\":\"user\", + \"content\":$(echo "$ai_prompt" | jq -Rs .) + } ], - \"max_tokens\":300, - \"temperature\":0.2 + \"max_tokens\":2000, + \"temperature\":0.3 }") - # Extract JSON response and validate - if echo "$suggestions" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - response_content=$(echo "$suggestions" | jq -r '.choices[0].message.content') - # Try to parse as JSON array - if echo "$response_content" | jq -e 'type == "array"' > /dev/null 2>&1; then - # Update comments.json by merging the new comments - echo "$response_content" | jq --argjson existing "$(cat comments.json)" '$existing + .' > tmp_comments.json - mv tmp_comments.json comments.json - fi + # Extract AI suggestions + ai_suggestions=$(echo "$ai_response" | jq -r '.choices[0].message.content // "[]"' | jq -c '.') + + # Validate and process AI suggestions + if echo "$ai_suggestions" | jq empty 2>/dev/null; then + # Combine AI suggestions with file information + comments_for_file=$(echo "$ai_suggestions" | jq --arg file "$filename" --argjson lines "$added_lines" ' + . as $suggestions | + $suggestions | map({ + path: $file, + line: .line, + side: "RIGHT", + body: ("🤖 **AI Review**: " + .body) + }) + ') + + # Add to the main comments collection + current_comments=$(cat all_inline_comments.json) + echo "$current_comments" | jq --argjson new "$comments_for_file" '. + $new' > all_inline_comments.json.tmp + mv all_inline_comments.json.tmp all_inline_comments.json + + comment_count=$(echo "$comments_for_file" | jq 'length') + total_comments=$((total_comments + comment_count)) + echo "✅ Generated $comment_count comments for $filename" + else + echo "⚠️ Failed to generate valid JSON comments for $filename" fi + else + echo "ℹ️ No added lines found in $filename" fi + + # Clean up temp file + rm -f temp_lines_${file_count}.json + else + echo "ℹ️ No patch available for $filename" fi done + + # Save the final comments + cp all_inline_comments.json inline_comments.json + + echo "📊 Total files processed: $file_count" + echo "💬 Total inline comments generated: $(cat inline_comments.json | jq 'length')" + + # Also generate additional smart comments based on patterns + echo "🔍 Generating additional pattern-based comments..." + + # Analyze the entire diff for patterns that need comments + full_diff=$(cat diff.txt) + escaped_full_diff=$(echo "$full_diff" | jq -Rs .) + + pattern_response=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\":\"gpt-4o\", + \"messages\":[ + { + \"role\":\"system\", + \"content\":\"Analyze this complete PR diff and identify critical issues, security concerns, or important improvements that MUST be commented on. For each issue, specify the exact file path and line number. Return a JSON array with objects containing 'path', 'line', and 'body' fields. Focus on: security vulnerabilities, performance issues, code smells, missing error handling, potential bugs.\" + }, + { + \"role\":\"user\", + \"content\":$escaped_full_diff + } + ], + \"max_tokens\":1500, + \"temperature\":0.2 + }") + + pattern_comments=$(echo "$pattern_response" | jq -r '.choices[0].message.content // "[]"') + + if echo "$pattern_comments" | jq empty 2>/dev/null; then + # Add side field and merge with existing comments + formatted_pattern_comments=$(echo "$pattern_comments" | jq 'map(. + {side: "RIGHT", body: ("⚠️ **Critical**: " + .body)})') + current_all=$(cat inline_comments.json) + echo "$current_all" | jq --argjson patterns "$formatted_pattern_comments" '. + $patterns' > inline_comments.json + fi + + echo "✅ Final total inline comments: $(cat inline_comments.json | jq 'length')" - - name: Post inline comments as PR review - if: steps.detect_changes.outputs.has_changes == 'true' + # ENHANCED: Create PR review with batched inline comments + - name: Create Comprehensive PR Review with All Inline Comments + if: steps.change_detection.outputs.has_changes == 'true' run: | - comment_count=$(jq length comments.json) - echo "Found $comment_count inline comments" - - if [ "$comment_count" -gt 0 ]; then - # Create a review with inline comments - gh api \ - --method POST \ + COMMIT_SHA="${{ github.event.pull_request.head.sha }}" + + # Check total comment count + total_comments=$(cat inline_comments.json | jq 'length') + + if [ "$total_comments" -gt 0 ]; then + echo "📝 Creating PR review with $total_comments inline comments..." + + # GitHub API has a limit on comments per review, so we'll batch them + MAX_COMMENTS_PER_REVIEW=50 + + # Split comments into batches + cat inline_comments.json | jq -c --argjson batch_size $MAX_COMMENTS_PER_REVIEW ' + [., .[]] | [.[0:$batch_size], .[$batch_size:]] | map(select(length > 0)) + ' > comment_batches.json + + batch_count=$(cat comment_batches.json | jq 'length') + echo "📦 Split into $batch_count batches" + + # Process each batch + batch_num=1 + cat comment_batches.json | jq -c '.[]' | while read -r batch; do + batch_size=$(echo "$batch" | jq 'length') + + if [ $batch_num -eq 1 ]; then + review_body="## 🤖 AI Code Review - Comprehensive Analysis\n\nI've thoroughly analyzed all changes in this PR and provided detailed inline comments.\n\n**Review Summary:**\n- Total files reviewed: $(cat files_diff.json | jq 'length')\n- Total inline comments: $total_comments\n- Review batch: $batch_num of $batch_count\n\nPlease review all inline comments below for specific suggestions and improvements." + else + review_body="## 🤖 AI Code Review - Continued (Batch $batch_num of $batch_count)\n\nAdditional inline comments for your PR:" + fi + + # Create review with this batch of comments + response=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ + -d "{ + \"commit_id\":\"$COMMIT_SHA\", + \"body\":$(echo "$review_body" | jq -Rs .), + \"event\":\"COMMENT\", + \"comments\":$batch + }") + + http_code=$(echo "$response" | tail -n 1) + response_body=$(echo "$response" | head -n -1) + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + echo "✅ Successfully created review batch $batch_num with $batch_size comments" + else + echo "❌ Failed to create review batch $batch_num. HTTP code: $http_code" + echo "Response: $response_body" + + # Try to create review without comments if inline comments fail + if [ $batch_num -eq 1 ]; then + echo "Attempting to create review without inline comments..." + curl -s -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ + -d "{ + \"commit_id\":\"$COMMIT_SHA\", + \"body\":\"## 🤖 AI Code Review\\n\\nI encountered an issue adding inline comments directly. Please check the review comment below for suggestions.\\n\\n$AI_CODE_REVIEW\", + \"event\":\"COMMENT\" + }" + fi + fi + + batch_num=$((batch_num + 1)) + + # Add a small delay between batches to avoid rate limiting + if [ $batch_num -le $batch_count ]; then + sleep 2 + fi + done + else + echo "ℹ️ No inline comments generated" + + # Still create a review with general comments + curl -s -X POST \ -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \ - -f body="🤖 **AI Code Review with Inline Comments**" \ - -f event="COMMENT" \ - -f comments="$(cat comments.json)" || echo "Failed to post inline comments" + -H "Authorization: Bearer $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ + -d "{ + \"commit_id\":\"$COMMIT_SHA\", + \"body\":\"## 🤖 AI Code Review\\n\\n$AI_CODE_REVIEW\\n\\n---\\n\\n## 🧪 Test Scenarios\\n\\n$AI_TEST_SCENARIOS\", + \"event\":\"COMMENT\" + }" + fi + + - name: Post or update AI review summary comment + if: steps.change_detection.outputs.has_changes == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMENT_BODY="### 🤖 AI Code Review Summary + + **Inline Comments Added:** $(cat inline_comments.json | jq 'length') comments across all changed files + + $AI_CODE_REVIEW + --- + ### 🧪 AI Test Case Scenarios + $AI_TEST_SCENARIOS + + --- + *💡 Check the inline comments above for specific line-by-line suggestions*" + + # Find existing AI comment + comment_id=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ + -q ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id") + + if [ -n "$comment_id" ]; then + gh api repos/${{ github.repository }}/issues/comments/$comment_id \ + -X PATCH -F body="$COMMENT_BODY" + else + gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ + -F body="$COMMENT_BODY" + fi + + - name: Extract Jira issue key and comment on Jira + if: steps.change_detection.outputs.has_changes == 'true' + env: + JIRA_BASE_URL: https://cisco-sbg.atlassian.net + JIRA_TOKEN: ${{ secrets.JIRA_PERSONAL_TOKEN }} + JIRA_EMAIL: itsingh@cisco.com + run: | + # Find first Jira key in PR title or branch name + PR_TITLE="${{ github.event.pull_request.title }}" + BRANCH="${{ github.head_ref }}" + KEY=$(echo -e "$PR_TITLE\n$BRANCH" | grep -oE '[A-Z]{2,10}-[0-9]+' | head -1) + + if [ -z "$KEY" ]; then + echo "No Jira issue key found; skipping Jira update." + exit 0 + fi + + echo "Found Jira key: $KEY" + + # Test Jira API access + echo "Testing Jira API access with SAML/SSO authentication..." + test_response=$(curl -s -w "%{http_code}" -X GET \ + -u "$JIRA_EMAIL:$JIRA_TOKEN" \ + -H "Content-Type: application/json" \ + "$JIRA_BASE_URL/rest/api/3/issue/$KEY") + test_code="${test_response: -3}" + + if [ "$test_code" != "200" ]; then + echo "❌ Cannot access Jira issue $KEY. HTTP code: $test_code" + echo "Response: ${test_response%???}" + exit 1 + fi + + echo "✅ Jira API access successful" + + # Create comment for Jira + echo "GitHub PR Information:" > /tmp/comment.txt + echo "- Repository: ${{ github.repository }}" >> /tmp/comment.txt + echo "- PR Number: #${{ github.event.pull_request.number }}" >> /tmp/comment.txt + echo "- Title: ${{ github.event.pull_request.title }}" >> /tmp/comment.txt + echo "- Link: ${{ github.event.pull_request.html_url }}" >> /tmp/comment.txt + echo "- AI Comments Generated: $(cat inline_comments.json | jq 'length') inline reviews" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "AI Generated Description:" >> /tmp/comment.txt + echo "$PR_DESCRIPTION" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "Test Scenarios:" >> /tmp/comment.txt + echo "$AI_TEST_SCENARIOS" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "Comment automatically posted by GitHub Actions" >> /tmp/comment.txt + + # Convert to JSON + COMMENT_JSON=$(cat /tmp/comment.txt | jq -Rs '{body: {type: "doc", version: 1, content: [{type: "paragraph", content: [{type: "text", text: .}]}]}}') + + # Send to Jira + response=$(curl -s -w "%{http_code}" -X POST \ + -u "$JIRA_EMAIL:$JIRA_TOKEN" \ + -H "Content-Type: application/json" \ + "$JIRA_BASE_URL/rest/api/3/issue/$KEY/comment" \ + -d "$COMMENT_JSON") + http_code="${response: -3}" + + if [ "$http_code" = "201" ]; then + echo "✅ Successfully posted AI content to Jira issue $KEY" else - echo "No inline comments to post." + echo "❌ Failed to post to Jira. HTTP code: $http_code" + echo "Response body: ${response%???}" fi - - name: Update PR description - if: steps.detect_changes.outputs.has_changes == 'true' - run: | - gh pr edit ${{ github.event.pull_request.number }} \ - --repo ${{ github.repository }} \ - --body "$PR_DESCRIPTION" - - - name: Post AI review summary comment - if: steps.detect_changes.outputs.has_changes == 'true' - run: | - { - echo "### 🤖 AI Code Review" - echo "$AI_CODE_REVIEW" - echo "" - echo "---" - echo "" - echo "### 🧪 AI Test Scenarios" - echo "$AI_TEST_SCENARIOS" - } > comment_body.txt - - gh pr comment ${{ github.event.pull_request.number }} \ - --repo ${{ github.repository }} \ - --body-file comment_body.txt + - name: Update PR body + if: steps.change_detection.outputs.has_changes == 'true' + run: | + # Update with the AI-generated description + gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} \ + -X PATCH \ + -F body="$PR_DESCRIPTION" + + - name: Cleanup temporary files + if: always() && steps.change_detection.outputs.has_changes == 'true' + run: | + rm -f diff.txt files_diff.json combined_diff.json pr_full_diff.txt + rm -f diff_batch_* batch_summaries.txt + rm -f inline_comments.json all_inline_comments.json + rm -f comment_batches.json temp_lines_*.json + rm -f /tmp/comment.txt \ No newline at end of file From 405ab0082122b95715be35db942aacbd2b48369a Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 16:22:34 +0530 Subject: [PATCH 08/14] trying to add inline comments --- .github/workflows/ai-pr.yml | 511 +++++++++++++++--------------------- 1 file changed, 213 insertions(+), 298 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index c752273..d818566 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -18,7 +18,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y jq - # Install GitHub CLI curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null sudo apt-get update @@ -33,26 +32,19 @@ jobs: - name: Validate environment variables run: | if [ -z "$OPENAI_API_KEY" ]; then - echo "❌ OPENAI_API_KEY is not set"; exit 1 + echo "❌ OPENAI_API_KEY is not set" + exit 1 fi - name: Extract git diff run: | git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > diff.txt - - name: Fetch GitHub API diff with full patch context + - name: Fetch GitHub API diff run: | - # Fetch files with patches curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3.patch" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files?per_page=100" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ > files_diff.json - - # Also fetch the full PR diff for better context - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3.diff" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER" \ - > pr_full_diff.txt - name: Combine both diffs into JSON run: | @@ -101,7 +93,8 @@ jobs: summary=$(echo "$response" | jq -r '.choices[0].message.content // ""') if [ -n "$summary" ]; then echo "## Batch $(basename "$batch")" >> batch_summaries.txt - echo "$summary" >> batch_summaries.txt; echo "" >> batch_summaries.txt + echo "$summary" >> batch_summaries.txt + echo "" >> batch_summaries.txt processed=$((processed+1)) fi fi @@ -128,7 +121,6 @@ jobs: - name: Generate AI Code Review and Test Scenarios if: steps.change_detection.outputs.has_changes == 'true' - id: ai_reviews run: | diff=$(cat diff.txt) escaped_diff=$(echo "$diff" | jq -Rs .) @@ -147,318 +139,211 @@ jobs: echo "$test_text" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - # ENHANCED: Generate comprehensive inline comments for ALL changed files - - name: Generate Comprehensive AI Inline Comments + - name: Generate AI Inline Comments if: steps.change_detection.outputs.has_changes == 'true' run: | - COMMIT_SHA="${{ github.event.pull_request.head.sha }}" - - # Initialize empty array for all comments - echo "[]" > all_inline_comments.json - - # Process each file in the PR - echo "📝 Processing files for inline comments..." - file_count=0 - total_comments=0 + echo "🔍 Generating inline comments for changed files..." + echo "[]" > inline_comments.json - # Get all changed files with their patches - echo "$(cat files_diff.json)" | jq -c '.[]' | while read -r file_data; do - filename=$(echo "$file_data" | jq -r '.filename') - patch=$(echo "$file_data" | jq -r '.patch // ""') - additions=$(echo "$file_data" | jq -r '.additions // 0') - deletions=$(echo "$file_data" | jq -r '.deletions // 0') + cat files_diff.json | jq -c '.[] | select(.patch != null and .patch != "")' | while read -r file_obj; do + filename=$(echo "$file_obj" | jq -r '.filename') + patch=$(echo "$file_obj" | jq -r '.patch') + status=$(echo "$file_obj" | jq -r '.status') - echo "Processing file: $filename (additions: $additions, deletions: $deletions)" - file_count=$((file_count + 1)) + echo "📁 Processing file: $filename (status: $status)" + + if echo "$filename" | grep -qE '\.(png|jpg|jpeg|gif|ico|pdf|zip|tar|gz|exe|dll|so|dylib)$'; then + echo "⏭️ Skipping binary file: $filename" + continue + fi + + escaped_patch=$(echo "$patch" | jq -Rs .) + escaped_filename=$(echo "$filename" | jq -Rs .) + + echo "🤖 Requesting AI analysis for $filename..." + system_prompt="You are an expert code reviewer. Analyze the git patch and provide specific inline suggestions. + + CRITICAL INSTRUCTIONS: + 1. Look for lines that START with '+' (additions) in the patch + 2. For each suggestion, calculate the exact line number in the NEW file version + 3. Focus on: code quality, potential bugs, security issues, performance, best practices + 4. Return ONLY valid JSON array format + 5. Each suggestion must specify the exact line number where the comment should appear + 6. Maximum 5 suggestions per file to avoid spam + + REQUIRED JSON FORMAT: + [ + { + \"path\": \"exact_filename\", + \"line\": line_number_in_new_file, + \"side\": \"RIGHT\", + \"body\": \"💡 Suggestion: [your specific advice here]\" + } + ] + + If no meaningful suggestions, return empty array: []" + + escaped_system_prompt=$(echo "$system_prompt" | jq -Rs .) + user_content="File: $escaped_filename\n\nPatch:\n$escaped_patch" + escaped_user_content=$(echo "$user_content" | jq -Rs .) - if [ -n "$patch" ] && [ "$patch" != "null" ] && [ "$patch" != "" ]; then - # Parse the patch to extract line numbers for additions - echo "$patch" | awk -v filename="$filename" ' - BEGIN { - right_line = 0; - in_hunk = 0; - print "[" - first_comment = 1 - } - /^@@/ { - # Parse hunk header: @@ -old_start,old_count +new_start,new_count @@ - match($0, /\+([0-9]+)/, arr) - right_line = arr[1] - 1 - in_hunk = 1 - next - } - in_hunk { - if (/^[+]/) { - right_line++ - # This is an added line - generate comment for it - if (!first_comment) print "," - first_comment = 0 - # Extract the actual code content (remove the + prefix) - code_line = substr($0, 2) - printf "{\"path\":\"%s\",\"position\":%d,\"line\":%d,\"side\":\"RIGHT\"}", filename, NR, right_line - } else if (/^[-]/) { - # Deleted line - dont increment right_line - } else if (/^[ ]/) { - # Context line - increment right_line - right_line++ + ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\":\"gpt-4o-mini\", + \"messages\":[ + { + \"role\":\"system\", + \"content\":$escaped_system_prompt + }, + { + \"role\":\"user\", + \"content\":$escaped_user_content } - } - END { print "]" } - ' > temp_lines_${file_count}.json - - # Get all added lines for this file - added_lines=$(cat temp_lines_${file_count}.json | jq -c '.') + ], + \"max_tokens\":600, + \"temperature\":0.1 + }") + + if echo "$ai_response" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then + suggestions_raw=$(echo "$ai_response" | jq -r '.choices[0].message.content // "[]"') - if [ "$(echo "$added_lines" | jq 'length')" -gt 0 ]; then - # Prepare the patch with line numbers for AI analysis - escaped_patch=$(echo "$patch" | jq -Rs .) - escaped_filename=$(echo "$filename" | jq -Rs .) - - # Create a more detailed prompt for AI to analyze EVERY change - ai_prompt="Analyze this Git patch for file $filename and provide specific inline code review comments. - - IMPORTANT INSTRUCTIONS: - 1. Review EVERY added line (lines starting with +) in the patch - 2. Provide constructive feedback for code improvements, best practices, potential bugs, or security issues - 3. Generate AT LEAST one comment for every 5-10 lines of added code - 4. Focus on: - - Code quality and maintainability - - Potential bugs or edge cases - - Security vulnerabilities - - Performance improvements - - Best practices and conventions - - Documentation needs - - Error handling - - Testing requirements - - Return a JSON array where each object has: - - \"line\": the line number in the new file (right side) - - \"body\": your review comment (be specific and constructive) - - Analyze this patch thoroughly and provide comprehensive feedback: - $patch - - Return ONLY valid JSON array. Generate multiple relevant comments for different aspects of the code." - - # Call OpenAI with enhanced prompt for comprehensive review - ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o\", - \"messages\":[ - { - \"role\":\"system\", - \"content\":\"You are an expert code reviewer. Analyze the provided Git patch and generate comprehensive inline review comments for EVERY significant code change. Focus on code quality, bugs, security, performance, and best practices. Generate AT LEAST one comment per 5-10 lines of new code. Return ONLY a JSON array with objects containing 'line' (number) and 'body' (comment text) fields.\" - }, - { - \"role\":\"user\", - \"content\":$(echo "$ai_prompt" | jq -Rs .) - } - ], - \"max_tokens\":2000, - \"temperature\":0.3 - }") - - # Extract AI suggestions - ai_suggestions=$(echo "$ai_response" | jq -r '.choices[0].message.content // "[]"' | jq -c '.') + if echo "$suggestions_raw" | jq empty 2>/dev/null; then + valid_suggestions=$(echo "$suggestions_raw" | jq '[.[] | select(.path != null and .line != null and .body != null and (.line | type) == "number")]') - # Validate and process AI suggestions - if echo "$ai_suggestions" | jq empty 2>/dev/null; then - # Combine AI suggestions with file information - comments_for_file=$(echo "$ai_suggestions" | jq --arg file "$filename" --argjson lines "$added_lines" ' - . as $suggestions | - $suggestions | map({ - path: $file, - line: .line, - side: "RIGHT", - body: ("🤖 **AI Review**: " + .body) - }) - ') + if [ "$(echo "$valid_suggestions" | jq 'length')" -gt 0 ]; then + echo "✅ Generated $(echo "$valid_suggestions" | jq 'length') valid suggestions for $filename" - # Add to the main comments collection - current_comments=$(cat all_inline_comments.json) - echo "$current_comments" | jq --argjson new "$comments_for_file" '. + $new' > all_inline_comments.json.tmp - mv all_inline_comments.json.tmp all_inline_comments.json - - comment_count=$(echo "$comments_for_file" | jq 'length') - total_comments=$((total_comments + comment_count)) - echo "✅ Generated $comment_count comments for $filename" + current_comments=$(cat inline_comments.json) + echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp + mv inline_comments.json.tmp inline_comments.json else - echo "⚠️ Failed to generate valid JSON comments for $filename" + echo "⚠️ No valid suggestions generated for $filename" fi else - echo "ℹ️ No added lines found in $filename" + echo "⚠️ Invalid JSON response for $filename" fi - - # Clean up temp file - rm -f temp_lines_${file_count}.json else - echo "ℹ️ No patch available for $filename" + echo "❌ Failed to get AI response for $filename" fi done - # Save the final comments - cp all_inline_comments.json inline_comments.json - - echo "📊 Total files processed: $file_count" - echo "💬 Total inline comments generated: $(cat inline_comments.json | jq 'length')" - - # Also generate additional smart comments based on patterns - echo "🔍 Generating additional pattern-based comments..." - - # Analyze the entire diff for patterns that need comments - full_diff=$(cat diff.txt) - escaped_full_diff=$(echo "$full_diff" | jq -Rs .) - - pattern_response=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o\", - \"messages\":[ - { - \"role\":\"system\", - \"content\":\"Analyze this complete PR diff and identify critical issues, security concerns, or important improvements that MUST be commented on. For each issue, specify the exact file path and line number. Return a JSON array with objects containing 'path', 'line', and 'body' fields. Focus on: security vulnerabilities, performance issues, code smells, missing error handling, potential bugs.\" - }, - { - \"role\":\"user\", - \"content\":$escaped_full_diff - } - ], - \"max_tokens\":1500, - \"temperature\":0.2 - }") - - pattern_comments=$(echo "$pattern_response" | jq -r '.choices[0].message.content // "[]"') - - if echo "$pattern_comments" | jq empty 2>/dev/null; then - # Add side field and merge with existing comments - formatted_pattern_comments=$(echo "$pattern_comments" | jq 'map(. + {side: "RIGHT", body: ("⚠️ **Critical**: " + .body)})') - current_all=$(cat inline_comments.json) - echo "$current_all" | jq --argjson patterns "$formatted_pattern_comments" '. + $patterns' > inline_comments.json - fi - - echo "✅ Final total inline comments: $(cat inline_comments.json | jq 'length')" + total_comments=$(cat inline_comments.json | jq 'length') + echo "📊 Total inline comments generated: $total_comments" - # ENHANCED: Create PR review with batched inline comments - - name: Create Comprehensive PR Review with All Inline Comments + - name: Create PR Review with Inline Comments if: steps.change_detection.outputs.has_changes == 'true' run: | COMMIT_SHA="${{ github.event.pull_request.head.sha }}" + comment_count=$(cat inline_comments.json | jq 'length') - # Check total comment count - total_comments=$(cat inline_comments.json | jq 'length') + echo "📝 Creating PR review with $comment_count inline comments..." - if [ "$total_comments" -gt 0 ]; then - echo "📝 Creating PR review with $total_comments inline comments..." + if [ "$comment_count" -gt 0 ]; then + review_body="🤖 **AI Code Review Complete** + + I've analyzed your changes and provided $comment_count specific inline suggestions. Please review the comments below for potential improvements in code quality, security, and best practices. + + *This review was generated automatically. Please use your judgment when considering these suggestions.*" - # GitHub API has a limit on comments per review, so we'll batch them - MAX_COMMENTS_PER_REVIEW=50 + escaped_review_body=$(echo "$review_body" | jq -Rs .) - # Split comments into batches - cat inline_comments.json | jq -c --argjson batch_size $MAX_COMMENTS_PER_REVIEW ' - [., .[]] | [.[0:$batch_size], .[$batch_size:]] | map(select(length > 0)) - ' > comment_batches.json + review_payload=$(jq -n \ + --arg commit_id "$COMMIT_SHA" \ + --arg body "$review_body" \ + --arg event "COMMENT" \ + --argjson comments "$(cat inline_comments.json)" \ + '{ + commit_id: $commit_id, + body: $body, + event: $event, + comments: $comments + }') - batch_count=$(cat comment_batches.json | jq 'length') - echo "📦 Split into $batch_count batches" + echo "🚀 Submitting review to GitHub API..." - # Process each batch - batch_num=1 - cat comment_batches.json | jq -c '.[]' | while read -r batch; do - batch_size=$(echo "$batch" | jq 'length') - - if [ $batch_num -eq 1 ]; then - review_body="## 🤖 AI Code Review - Comprehensive Analysis\n\nI've thoroughly analyzed all changes in this PR and provided detailed inline comments.\n\n**Review Summary:**\n- Total files reviewed: $(cat files_diff.json | jq 'length')\n- Total inline comments: $total_comments\n- Review batch: $batch_num of $batch_count\n\nPlease review all inline comments below for specific suggestions and improvements." - else - review_body="## 🤖 AI Code Review - Continued (Batch $batch_num of $batch_count)\n\nAdditional inline comments for your PR:" - fi - - # Create review with this batch of comments - response=$(curl -s -w "\n%{http_code}" -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ - -d "{ - \"commit_id\":\"$COMMIT_SHA\", - \"body\":$(echo "$review_body" | jq -Rs .), - \"event\":\"COMMENT\", - \"comments\":$batch - }") - - http_code=$(echo "$response" | tail -n 1) - response_body=$(echo "$response" | head -n -1) + response=$(curl -s -w "HTTPSTATUS:%{http_code}" \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ + -d "$review_payload") + + http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + echo "✅ Successfully created PR review with $comment_count inline comments" + review_id=$(echo "$response_body" | jq -r '.id // "unknown"') + echo "Review ID: $review_id" + else + echo "❌ Failed to create PR review. HTTP code: $http_code" + echo "Response: $response_body" - if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then - echo "✅ Successfully created review batch $batch_num with $batch_size comments" - else - echo "❌ Failed to create review batch $batch_num. HTTP code: $http_code" - echo "Response: $response_body" + echo "🔄 Attempting fallback: posting individual inline comments..." + cat inline_comments.json | jq -c '.[]' | while read -r comment; do + path=$(echo "$comment" | jq -r '.path') + line=$(echo "$comment" | jq -r '.line') + body=$(echo "$comment" | jq -r '.body') + + individual_response=$(curl -s -w "HTTPSTATUS:%{http_code}" \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ + -d "{ + \"commit_id\":\"$COMMIT_SHA\", + \"path\":\"$path\", + \"line\":$line, + \"side\":\"RIGHT\", + \"body\":\"$body\" + }") - # Try to create review without comments if inline comments fail - if [ $batch_num -eq 1 ]; then - echo "Attempting to create review without inline comments..." - curl -s -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ - -d "{ - \"commit_id\":\"$COMMIT_SHA\", - \"body\":\"## 🤖 AI Code Review\\n\\nI encountered an issue adding inline comments directly. Please check the review comment below for suggestions.\\n\\n$AI_CODE_REVIEW\", - \"event\":\"COMMENT\" - }" + individual_code=$(echo "$individual_response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + if [ "$individual_code" = "201" ]; then + echo "✅ Posted comment for $path:$line" + else + echo "❌ Failed to post comment for $path:$line (HTTP: $individual_code)" fi - fi - - batch_num=$((batch_num + 1)) - - # Add a small delay between batches to avoid rate limiting - if [ $batch_num -le $batch_count ]; then - sleep 2 - fi - done + done + fi else - echo "ℹ️ No inline comments generated" - - # Still create a review with general comments - curl -s -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ - -d "{ - \"commit_id\":\"$COMMIT_SHA\", - \"body\":\"## 🤖 AI Code Review\\n\\n$AI_CODE_REVIEW\\n\\n---\\n\\n## 🧪 Test Scenarios\\n\\n$AI_TEST_SCENARIOS\", - \"event\":\"COMMENT\" - }" + echo "ℹ️ No inline comments to post" fi - - name: Post or update AI review summary comment + - name: Post or update AI review comment if: steps.change_detection.outputs.has_changes == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - COMMENT_BODY="### 🤖 AI Code Review Summary + inline_count=$(cat inline_comments.json | jq 'length') - **Inline Comments Added:** $(cat inline_comments.json | jq 'length') comments across all changed files + COMMENT_BODY="### 🤖 AI Code Review Summary + **Inline Comments Generated:** $inline_count specific suggestions have been posted as inline comments on the relevant lines. + + **Overall Assessment:** $AI_CODE_REVIEW + --- ### 🧪 AI Test Case Scenarios $AI_TEST_SCENARIOS - + --- - *💡 Check the inline comments above for specific line-by-line suggestions*" - - # Find existing AI comment + *💡 Tip: Check the inline comments above for specific, line-by-line suggestions!*" + comment_id=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ - -q ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id") + --jq ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id") if [ -n "$comment_id" ]; then + echo "🔄 Updating existing AI review comment (ID: $comment_id)" gh api repos/${{ github.repository }}/issues/comments/$comment_id \ -X PATCH -F body="$COMMENT_BODY" else + echo "✨ Creating new AI review comment" gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ -F body="$COMMENT_BODY" fi @@ -470,41 +355,41 @@ jobs: JIRA_TOKEN: ${{ secrets.JIRA_PERSONAL_TOKEN }} JIRA_EMAIL: itsingh@cisco.com run: | - # Find first Jira key in PR title or branch name PR_TITLE="${{ github.event.pull_request.title }}" BRANCH="${{ github.head_ref }}" KEY=$(echo -e "$PR_TITLE\n$BRANCH" | grep -oE '[A-Z]{2,10}-[0-9]+' | head -1) - if [ -z "$KEY" ]; then echo "No Jira issue key found; skipping Jira update." exit 0 fi - echo "Found Jira key: $KEY" - - # Test Jira API access + echo "Testing Jira API access with SAML/SSO authentication..." test_response=$(curl -s -w "%{http_code}" -X GET \ -u "$JIRA_EMAIL:$JIRA_TOKEN" \ -H "Content-Type: application/json" \ "$JIRA_BASE_URL/rest/api/3/issue/$KEY") test_code="${test_response: -3}" - if [ "$test_code" != "200" ]; then echo "❌ Cannot access Jira issue $KEY. HTTP code: $test_code" echo "Response: ${test_response%???}" + echo "This could be due to:" + echo "- Invalid JIRA_PERSONAL_TOKEN" + echo "- Insufficient permissions for issue $KEY" + echo "- Issue $KEY doesn't exist" + echo "- SAML/SSO authentication issues" exit 1 fi + echo "✅ Jira API access successful with SAML authentication" + + inline_count=$(cat inline_comments.json | jq 'length') - echo "✅ Jira API access successful" - - # Create comment for Jira echo "GitHub PR Information:" > /tmp/comment.txt echo "- Repository: ${{ github.repository }}" >> /tmp/comment.txt echo "- PR Number: #${{ github.event.pull_request.number }}" >> /tmp/comment.txt echo "- Title: ${{ github.event.pull_request.title }}" >> /tmp/comment.txt echo "- Link: ${{ github.event.pull_request.html_url }}" >> /tmp/comment.txt - echo "- AI Comments Generated: $(cat inline_comments.json | jq 'length') inline reviews" >> /tmp/comment.txt + echo "- AI Inline Comments: $inline_count specific suggestions posted" >> /tmp/comment.txt echo "" >> /tmp/comment.txt echo "AI Generated Description:" >> /tmp/comment.txt echo "$PR_DESCRIPTION" >> /tmp/comment.txt @@ -513,20 +398,23 @@ jobs: echo "$AI_TEST_SCENARIOS" >> /tmp/comment.txt echo "" >> /tmp/comment.txt echo "Comment automatically posted by GitHub Actions" >> /tmp/comment.txt - - # Convert to JSON + COMMENT_JSON=$(cat /tmp/comment.txt | jq -Rs '{body: {type: "doc", version: 1, content: [{type: "paragraph", content: [{type: "text", text: .}]}]}}') - - # Send to Jira + response=$(curl -s -w "%{http_code}" -X POST \ -u "$JIRA_EMAIL:$JIRA_TOKEN" \ -H "Content-Type: application/json" \ "$JIRA_BASE_URL/rest/api/3/issue/$KEY/comment" \ -d "$COMMENT_JSON") http_code="${response: -3}" - if [ "$http_code" = "201" ]; then echo "✅ Successfully posted AI content to Jira issue $KEY" + elif [ "$http_code" = "403" ]; then + echo "❌ Access denied (403). Possible issues:" + echo " - Invalid or expired API token" + echo " - Insufficient permissions for issue $KEY" + echo " - Project CSSCC access restricted" + echo " - SAML/SSO authentication problems" else echo "❌ Failed to post to Jira. HTTP code: $http_code" echo "Response body: ${response%???}" @@ -535,16 +423,43 @@ jobs: - name: Update PR body if: steps.change_detection.outputs.has_changes == 'true' run: | - # Update with the AI-generated description + inline_count=$(cat inline_comments.json | jq 'length') + + enhanced_description="$PR_DESCRIPTION + + --- + ## 🤖 AI Review Summary + - **Inline Comments**: $inline_count specific suggestions posted directly on relevant lines + - **Overall Review**: See comments below for detailed analysis + - **Test Scenarios**: Comprehensive test cases provided in comments + + *💡 Check the 'Files changed' tab to see inline comments on specific lines!*" + gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} \ -X PATCH \ - -F body="$PR_DESCRIPTION" + -F body="$enhanced_description" + + echo "✅ Updated PR description with inline comment summary" - name: Cleanup temporary files if: always() && steps.change_detection.outputs.has_changes == 'true' run: | - rm -f diff.txt files_diff.json combined_diff.json pr_full_diff.txt - rm -f diff_batch_* batch_summaries.txt - rm -f inline_comments.json all_inline_comments.json - rm -f comment_batches.json temp_lines_*.json - rm -f /tmp/comment.txt \ No newline at end of file + rm -f diff.txt files_diff.json combined_diff.json diff_batch_* batch_summaries.txt + rm -f inline_comments.json inline_comments.json.tmp /tmp/comment.txt + echo "🧹 Cleanup completed" + + - name: Review Summary + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "🎉 AI PR Review Workflow Complete!" + echo "===========================================" + echo "✅ PR Description: Updated" + echo "✅ Inline Comments: $(cat inline_comments.json 2>/dev/null | jq 'length' 2>/dev/null || echo '0') posted" + echo "✅ Summary Comment: Posted/Updated" + echo "✅ Jira Integration: Attempted" + echo "===========================================" + echo "📋 Next Steps:" + echo "1. Review the AI-generated PR description" + echo "2. Check inline comments in the 'Files changed' tab" + echo "3. Address any valid suggestions from the AI review" + echo "4. Verify Jira ticket has been updated (if applicable)" \ No newline at end of file From ac996f7bd9d1a48d2c1086162b8a012fa69e95b2 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Mon, 1 Sep 2025 23:19:09 +0530 Subject: [PATCH 09/14] added code for pr inline comments --- README.md | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f761632..c5706d6 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,44 @@ tizen-buildroot =============== -####Scripts preparing buildroot directory from scratch, used for Tizen system +#### Scripts preparing buildroot directory from scratch, used for Tizen system -1. Create "user_path_config.sh" and edit pathes there (see "user_path_config.example"). +## Prerequisites + +Before running the build scripts, ensure you have all required host tools installed. + +## Setup Instructions + +1. Create "user_path_config.sh" and edit paths there (see "user_path_config.example"). + +## Build Process 2. To create rootfs and build packages for required architecture: -`./build_pkgs.sh mipsel` -`sudo ./prepare_rootfs.sh mipsel` -`sudo ./build_rpms.sh mipsel` +```bash +./build_pkgs.sh mipsel +sudo ./prepare_rootfs.sh mipsel +sudo ./build_rpms.sh mipsel +``` You can specify which rpms need to build: -`sudo ./build_rpms.sh mipsel "argp-standalone acl" ` +```bash +sudo ./build_rpms.sh mipsel "argp-standalone acl" +``` -Some required host tools: +## Required Host Tools help2man, flex, flex-devel, ncurses-devel, texinfo, texinfo-tex, gettext-devel, rcs, transfig, libtool, autoconf, automake, bison, gperf, libgpg-error-devel, libxml2-devel -By default temporary build directory will be deleted, to save it you should set environment variable: - `export DONT_CLEAN=1 ` +## Environment Variables + +By default temporary build directory will be deleted. To preserve it, set: +```bash +export DONT_CLEAN=1 +``` + +## Important Notes *You should update macros.tizen-platform for new snapshot of Tizen (copy it from libtzplatform-config-devel-1.0-0.mipsel.rpm).* -**Important:** the bash package v.4.3.30 is working incorrect in chroot, so currently using v.4.2. +**Important:** The bash package v.4.3.30 works incorrectly in chroot, so currently using v.4.2. From 95ce8bcfce7d3cbf0a7221fa2d21d20b4c4f9212 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Tue, 2 Sep 2025 08:58:36 +0530 Subject: [PATCH 10/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 550 ++++++++++++------------------------ 1 file changed, 173 insertions(+), 377 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index d818566..80a27a8 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -1,95 +1,121 @@ name: AI Batching PR Description (OpenAI - Modular) + on: pull_request: types: [opened, synchronize] +concurrency: + group: ai-batching-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + issues: write + jobs: generate_pr_description: runs-on: ubuntu-latest + # optional timeout: uncomment to limit long-running jobs + # timeout-minutes: 30 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_REPO: ${{ github.repository }} BATCH_SIZE: 150 + MAX_COMMENTS_PER_FILE: 5 + MAX_TOTAL_COMMENTS: 120 steps: - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y jq - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update - sudo apt-get install -y gh - - - name: Checkout repository and set PR branch + - name: Checkout repository (full history) uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref }} + - name: Install runtime deps + run: | + sudo apt-get update -y + sudo apt-get install -y jq curl git + # GitHub CLI (gh) + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update -y + sudo apt-get install -y gh + - name: Validate environment variables run: | - if [ -z "$OPENAI_API_KEY" ]; then + if [ -z "${OPENAI_API_KEY:-}" ]; then echo "❌ OPENAI_API_KEY is not set" exit 1 fi + if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "❌ GITHUB_TOKEN is not set" + exit 1 + fi - name: Extract git diff run: | - git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > diff.txt + git diff "${{ github.event.pull_request.base.sha }}"..."${{ github.event.pull_request.head.sha }}" > diff.txt || true - - name: Fetch GitHub API diff + - name: Fetch GitHub API diff (files) run: | - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/files" \ - > files_diff.json + # Save raw files list (fallback to empty array) + curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/files" -o files_diff.json || echo '[]' > files_diff.json + # ensure it's valid JSON array + if ! jq -e . files_diff.json >/dev/null 2>&1; then + echo "[] " > files_diff.json + fi - - name: Combine both diffs into JSON + - name: Combine both diffs into JSON (safe) run: | - jq -n --arg gitdiff "$(cat diff.txt)" --argjson apidiff "$(cat files_diff.json)" \ - '{gitdiff: $gitdiff, apidiff: $apidiff}' > combined_diff.json + gitdiff=$(cat diff.txt 2>/dev/null || true) + # use slurpfile to safely import JSON even if it contains newlines + if [ -s files_diff.json ]; then + jq -n --arg gitdiff "$gitdiff" --slurpfile apidiff files_diff.json '{gitdiff: $gitdiff, apidiff: $apidiff[0]}' > combined_diff.json + else + jq -n --arg gitdiff "$gitdiff" '{gitdiff: $gitdiff, apidiff: []}' > combined_diff.json + fi - name: Check for meaningful changes id: change_detection run: | if [ -s diff.txt ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT + echo "has_changes=true" >> "$GITHUB_OUTPUT" else - echo "has_changes=false" >> $GITHUB_OUTPUT + echo "has_changes=false" >> "$GITHUB_OUTPUT" fi - name: Split diff into batches if: steps.change_detection.outputs.has_changes == 'true' run: | - split -l $BATCH_SIZE diff.txt diff_batch_ - echo "BATCH_COUNT=$(ls diff_batch_* | wc -l)" >> $GITHUB_ENV + rm -f diff_batch_* || true + split -l "${BATCH_SIZE}" diff.txt diff_batch_ || true + echo "BATCH_COUNT=$(ls diff_batch_* 2>/dev/null | wc -l)" >> "$GITHUB_ENV" - name: Test OpenAI API connectivity if: steps.change_detection.outputs.has_changes == 'true' run: | - curl -s https://api.openai.com/v1/models \ - -H "Authorization: Bearer $OPENAI_API_KEY" | jq '.data | length' + curl -s https://api.openai.com/v1/models -H "Authorization: Bearer ${OPENAI_API_KEY}" | jq '.data | length' || true - - name: Initialize batch processing - if: steps.change_detection.outputs.has_changes == 'true' - run: echo "PROCESSED_BATCHES=0" >> $GITHUB_ENV - - - name: Process each batch with OpenAI + - name: Process each batch with OpenAI (PR summary) if: steps.change_detection.outputs.has_changes == 'true' run: | - rm -f batch_summaries.txt + rm -f batch_summaries.txt || true processed=0 for batch in diff_batch_*; do + [ -f "$batch" ] || continue diff_content=$(cat "$batch") - escaped_content=$(echo "$diff_content" | jq -Rs .) - response=$(curl -s "https://api.openai.com/v1/chat/completions" \ + request=$(jq -n --arg model "gpt-4o-mini" \ + --arg system "You summarize git diffs for PR descriptions." \ + --arg user "$diff_content" \ + '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:300,temperature:0.2}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You summarize git diffs for PR descriptions.\"},{\"role\":\"user\",\"content\":$escaped_content}],\"max_tokens\":300,\"temperature\":0.2}") - - if echo "$response" | jq -e .choices > /dev/null 2>&1; then + -H "Authorization: Bearer ${OPENAI_API_KEY}" \ + -d "$request") + if echo "$response" | jq -e .choices >/dev/null 2>&1; then summary=$(echo "$response" | jq -r '.choices[0].message.content // ""') if [ -n "$summary" ]; then echo "## Batch $(basename "$batch")" >> batch_summaries.txt @@ -97,369 +123,139 @@ jobs: echo "" >> batch_summaries.txt processed=$((processed+1)) fi + else + echo "Warning: no summary for $batch" fi done - echo "PROCESSED_BATCHES=$processed" >> $GITHUB_ENV + echo "PROCESSED_BATCHES=$processed" >> "$GITHUB_ENV" - - name: Aggregate and rephrase summaries + - name: Aggregate and rephrase summaries (strong prompt) if: steps.change_detection.outputs.has_changes == 'true' run: | - full_summary=$(cat batch_summaries.txt) - escaped_summary=$(echo "$full_summary" | jq -Rs .) - response=$(curl -s "https://api.openai.com/v1/chat/completions" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You create concise, professional PR descriptions.\"},{\"role\":\"user\",\"content\":$escaped_summary}],\"max_tokens\":400,\"temperature\":0.3}") - if echo "$response" | jq -e .choices > /dev/null 2>&1; then + full_summary=$(cat batch_summaries.txt 2>/dev/null || "") + system_prompt="You are a professional release engineer and technical writer. Given a collection of git-diff summaries, produce a concise, structured PR description. Output must include: 1) Short summary (1-2 lines), 2) Bullet list of changed areas/files, 3) Impact/risk (Low/Medium/High) with justification, 4) Testing notes and validation commands, 5) Suggested reviewers/teams. Keep it production-ready and concise." + request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$full_summary" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:400,temperature:0.2}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${OPENAI_API_KEY}" -d "$request") + if echo "$response" | jq -e .choices >/dev/null 2>&1; then pr_desc=$(echo "$response" | jq -r '.choices[0].message.content // ""') else pr_desc="Failed to generate PR description." fi - echo "PR_DESCRIPTION<> $GITHUB_ENV - echo "$pr_desc" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + # Write PR_DESCRIPTION into GITHUB_ENV safely + echo 'PR_DESCRIPTION<> "$GITHUB_ENV" + echo "$pr_desc" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" - name: Generate AI Code Review and Test Scenarios if: steps.change_detection.outputs.has_changes == 'true' run: | - diff=$(cat diff.txt) - escaped_diff=$(echo "$diff" | jq -Rs .) - review=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \ - -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a strict senior code reviewer. Provide detailed review comments.\"},{\"role\":\"user\",\"content\":$escaped_diff}],\"max_tokens\":500,\"temperature\":0.2}") - tests=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \ - -d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a test architect. Provide test case scenarios.\"},{\"role\":\"user\",\"content\":$escaped_diff}],\"max_tokens\":500,\"temperature\":0.2}") - review_text=$(echo "$review" | jq -r '.choices[0].message.content // ""') - test_text=$(echo "$tests" | jq -r '.choices[0].message.content // ""') - echo "AI_CODE_REVIEW<> $GITHUB_ENV - echo "$review_text" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "AI_TEST_SCENARIOS<> $GITHUB_ENV - echo "$test_text" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Generate AI Inline Comments + diff_all=$(cat diff.txt 2>/dev/null || "") + review_prompt="You are a strict senior code reviewer. For the provided unified diff, list file-level issues (security, correctness, style, performance) with a short rationale and suggested change. Output as plain text grouped by file." + tests_prompt="You are a test architect. From the diff, produce prioritized test cases and steps to validate the changes, including boundary and negative cases." + req_review=$(jq -n --arg model "gpt-4o-mini" --arg system "$review_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') + req_tests=$(jq -n --arg model "gpt-4o-mini" --arg system "$tests_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') + review_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_review") + tests_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_tests") + review_text=$(echo "$review_resp" | jq -r '.choices[0].message.content // ""') + test_text=$(echo "$tests_resp" | jq -r '.choices[0].message.content // ""') + echo 'AI_CODE_REVIEW<> "$GITHUB_ENV" + echo "$review_text" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" + echo 'AI_TEST_SCENARIOS<> "$GITHUB_ENV" + echo "$test_text" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" + + - name: Generate AI Inline Comments (bash + awk, validated) if: steps.change_detection.outputs.has_changes == 'true' run: | - echo "🔍 Generating inline comments for changed files..." + set -euo pipefail echo "[]" > inline_comments.json - - cat files_diff.json | jq -c '.[] | select(.patch != null and .patch != "")' | while read -r file_obj; do - filename=$(echo "$file_obj" | jq -r '.filename') - patch=$(echo "$file_obj" | jq -r '.patch') - status=$(echo "$file_obj" | jq -r '.status') - - echo "📁 Processing file: $filename (status: $status)" - - if echo "$filename" | grep -qE '\.(png|jpg|jpeg|gif|ico|pdf|zip|tar|gz|exe|dll|so|dylib)$'; then - echo "⏭️ Skipping binary file: $filename" - continue - fi - - escaped_patch=$(echo "$patch" | jq -Rs .) - escaped_filename=$(echo "$filename" | jq -Rs .) - - echo "🤖 Requesting AI analysis for $filename..." - system_prompt="You are an expert code reviewer. Analyze the git patch and provide specific inline suggestions. - - CRITICAL INSTRUCTIONS: - 1. Look for lines that START with '+' (additions) in the patch - 2. For each suggestion, calculate the exact line number in the NEW file version - 3. Focus on: code quality, potential bugs, security issues, performance, best practices - 4. Return ONLY valid JSON array format - 5. Each suggestion must specify the exact line number where the comment should appear - 6. Maximum 5 suggestions per file to avoid spam - - REQUIRED JSON FORMAT: - [ - { - \"path\": \"exact_filename\", - \"line\": line_number_in_new_file, - \"side\": \"RIGHT\", - \"body\": \"💡 Suggestion: [your specific advice here]\" - } - ] + rm -f added_lines.ndjson inline_comments.tmp || true + + # Build NDJSON of added lines (limit per file) + jq -c '.[] | select(.patch != null and .patch != "") | {filename: .filename, patch: .patch}' files_diff.json \ + | while read -r fileobj; do + filename=$(echo "$fileobj" | jq -r '.filename') + patch=$(echo "$fileobj" | jq -r '.patch') + # skip binary types + if echo "$filename" | grep -qE '\.(png|jpg|jpeg|gif|ico|pdf|zip|tar|gz|exe|dll|so|dylib)$'; then + continue + fi + echo "$patch" | awk -v filename="$filename" -v max_per="${MAX_COMMENTS_PER_FILE}" ' + BEGIN { pos=0; new_line=0; added_count=0; } + /^(\-\-\- |\+\+\+ )/ { next } + /^@@/ { + if (match($0, /\+([0-9]+)/, arr)) { + new_line = arr[1] - 1 + } + next + } + { + if (length($0) == 0) next + first = substr($0,1,1) + if (first != " " && first != "+" && first != "-") next + pos++ + if (first == "+") { + new_line++ + if (added_count < max_per) { + line = substr($0,2) + gsub(/"/, "\\\"", line) + gsub(/\r/,"",line) + printf("{\"path\":\"%s\",\"position\":%d,\"new_line\":%d,\"content\":\"%s\"}\n", filename, pos, new_line, line) + added_count++ + } + } else if (first == " ") { + new_line++ + } + }' >> added_lines.ndjson + done - If no meaningful suggestions, return empty array: []" + if [ ! -f added_lines.ndjson ] || [ ! -s added_lines.ndjson ]; then + echo "No added lines found; skipping inline comment creation." + echo "[]" > inline_comments.json + else + # Limit overall comments to MAX_TOTAL_COMMENTS to protect rate limits + total=0 + touch inline_comments.tmp + while read -r entry && [ "$total" -lt "${MAX_TOTAL_COMMENTS}" ]; do + path=$(echo "$entry" | jq -r '.path') + position=$(echo "$entry" | jq -r '.position') + new_line=$(echo "$entry" | jq -r '.new_line') + content=$(echo "$entry" | jq -r '.content') + + system_prompt='You are an expert senior code reviewer. Return a concise JSON: {"comment":"..","severity":"Low|Medium|High","suggested_fix":"..."} or {"comment":"","severity":"Low","suggested_fix":""} if none.' + user_content="File: ${path}\nLine: ${new_line}\nAdded line:\n${content}\n\nProvide a concise production-ready suggestion for this line." + + request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$user_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:180,temperature:0.05}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$request") + suggestion=$(echo "$response" | jq -r '.choices[0].message.content // ""' | tr -d '\r') + + if [ -z "$suggestion" ]; then + continue + fi - escaped_system_prompt=$(echo "$system_prompt" | jq -Rs .) - user_content="File: $escaped_filename\n\nPatch:\n$escaped_patch" - escaped_user_content=$(echo "$user_content" | jq -Rs .) - - ai_response=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\":\"gpt-4o-mini\", - \"messages\":[ - { - \"role\":\"system\", - \"content\":$escaped_system_prompt - }, - { - \"role\":\"user\", - \"content\":$escaped_user_content - } - ], - \"max_tokens\":600, - \"temperature\":0.1 - }") - - if echo "$ai_response" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then - suggestions_raw=$(echo "$ai_response" | jq -r '.choices[0].message.content // "[]"') - - if echo "$suggestions_raw" | jq empty 2>/dev/null; then - valid_suggestions=$(echo "$suggestions_raw" | jq '[.[] | select(.path != null and .line != null and .body != null and (.line | type) == "number")]') - - if [ "$(echo "$valid_suggestions" | jq 'length')" -gt 0 ]; then - echo "✅ Generated $(echo "$valid_suggestions" | jq 'length') valid suggestions for $filename" - - current_comments=$(cat inline_comments.json) - echo "$current_comments" | jq --argjson new "$valid_suggestions" '. + $new' > inline_comments.json.tmp - mv inline_comments.json.tmp inline_comments.json - else - echo "⚠️ No valid suggestions generated for $filename" - fi + # Validate: try to parse model JSON; if parse fails, wrap suggestion as plain text comment + if echo "$suggestion" | jq -e . >/dev/null 2>&1; then + comment_body=$(echo "$suggestion" | jq -r 'if type=="object" and (.comment // "") != "" then ("💡 " + .comment + "\n\nSeverity: " + (.severity//"Low") + "\n\nSuggested fix:\n" + (.suggested_fix//"")) else (. | tostring) end') else - echo "⚠️ Invalid JSON response for $filename" + comment_body=$(printf "💡 %s" "$suggestion") fi - else - echo "❌ Failed to get AI response for $filename" - fi - done - - total_comments=$(cat inline_comments.json | jq 'length') - echo "📊 Total inline comments generated: $total_comments" - - name: Create PR Review with Inline Comments - if: steps.change_detection.outputs.has_changes == 'true' - run: | - COMMIT_SHA="${{ github.event.pull_request.head.sha }}" - comment_count=$(cat inline_comments.json | jq 'length') - - echo "📝 Creating PR review with $comment_count inline comments..." - - if [ "$comment_count" -gt 0 ]; then - review_body="🤖 **AI Code Review Complete** + # escape newlines/quotes for JSON payload + body_safe=$(printf "%s" "$comment_body" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') + printf '{"path":"%s","position":%s,"side":"RIGHT","body":"%s"}\n' "$path" "$position" "$body_safe" >> inline_comments.tmp - I've analyzed your changes and provided $comment_count specific inline suggestions. Please review the comments below for potential improvements in code quality, security, and best practices. + total=$((total+1)) + done < added_lines.ndjson - *This review was generated automatically. Please use your judgment when considering these suggestions.*" - - escaped_review_body=$(echo "$review_body" | jq -Rs .) - - review_payload=$(jq -n \ - --arg commit_id "$COMMIT_SHA" \ - --arg body "$review_body" \ - --arg event "COMMENT" \ - --argjson comments "$(cat inline_comments.json)" \ - '{ - commit_id: $commit_id, - body: $body, - event: $event, - comments: $comments - }') - - echo "🚀 Submitting review to GitHub API..." - - response=$(curl -s -w "HTTPSTATUS:%{http_code}" \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/reviews" \ - -d "$review_payload") - - http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') - response_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') - - if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then - echo "✅ Successfully created PR review with $comment_count inline comments" - review_id=$(echo "$response_body" | jq -r '.id // "unknown"') - echo "Review ID: $review_id" + if [ -s inline_comments.tmp ]; then + jq -s . inline_comments.tmp > inline_comments.json else - echo "❌ Failed to create PR review. HTTP code: $http_code" - echo "Response: $response_body" - - echo "🔄 Attempting fallback: posting individual inline comments..." - cat inline_comments.json | jq -c '.[]' | while read -r comment; do - path=$(echo "$comment" | jq -r '.path') - line=$(echo "$comment" | jq -r '.line') - body=$(echo "$comment" | jq -r '.body') - - individual_response=$(curl -s -w "HTTPSTATUS:%{http_code}" \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/$GITHUB_REPO/pulls/$GITHUB_PR_NUMBER/comments" \ - -d "{ - \"commit_id\":\"$COMMIT_SHA\", - \"path\":\"$path\", - \"line\":$line, - \"side\":\"RIGHT\", - \"body\":\"$body\" - }") - - individual_code=$(echo "$individual_response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') - if [ "$individual_code" = "201" ]; then - echo "✅ Posted comment for $path:$line" - else - echo "❌ Failed to post comment for $path:$line (HTTP: $individual_code)" - fi - done + echo "[]" > inline_comments.json fi - else - echo "ℹ️ No inline comments to post" fi - - name: Post or update AI review comment - if: steps.change_detection.outputs.has_changes == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - inline_count=$(cat inline_comments.json | jq 'length') - - COMMENT_BODY="### 🤖 AI Code Review Summary - - **Inline Comments Generated:** $inline_count specific suggestions have been posted as inline comments on the relevant lines. - - **Overall Assessment:** - $AI_CODE_REVIEW - - --- - ### 🧪 AI Test Case Scenarios - $AI_TEST_SCENARIOS - - --- - *💡 Tip: Check the inline comments above for specific, line-by-line suggestions!*" - - comment_id=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ - --jq ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id") - - if [ -n "$comment_id" ]; then - echo "🔄 Updating existing AI review comment (ID: $comment_id)" - gh api repos/${{ github.repository }}/issues/comments/$comment_id \ - -X PATCH -F body="$COMMENT_BODY" - else - echo "✨ Creating new AI review comment" - gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ - -F body="$COMMENT_BODY" - fi - - - name: Extract Jira issue key and comment on Jira - if: steps.change_detection.outputs.has_changes == 'true' - env: - JIRA_BASE_URL: https://cisco-sbg.atlassian.net - JIRA_TOKEN: ${{ secrets.JIRA_PERSONAL_TOKEN }} - JIRA_EMAIL: itsingh@cisco.com - run: | - PR_TITLE="${{ github.event.pull_request.title }}" - BRANCH="${{ github.head_ref }}" - KEY=$(echo -e "$PR_TITLE\n$BRANCH" | grep -oE '[A-Z]{2,10}-[0-9]+' | head -1) - if [ -z "$KEY" ]; then - echo "No Jira issue key found; skipping Jira update." - exit 0 - fi - echo "Found Jira key: $KEY" - - echo "Testing Jira API access with SAML/SSO authentication..." - test_response=$(curl -s -w "%{http_code}" -X GET \ - -u "$JIRA_EMAIL:$JIRA_TOKEN" \ - -H "Content-Type: application/json" \ - "$JIRA_BASE_URL/rest/api/3/issue/$KEY") - test_code="${test_response: -3}" - if [ "$test_code" != "200" ]; then - echo "❌ Cannot access Jira issue $KEY. HTTP code: $test_code" - echo "Response: ${test_response%???}" - echo "This could be due to:" - echo "- Invalid JIRA_PERSONAL_TOKEN" - echo "- Insufficient permissions for issue $KEY" - echo "- Issue $KEY doesn't exist" - echo "- SAML/SSO authentication issues" - exit 1 - fi - echo "✅ Jira API access successful with SAML authentication" - - inline_count=$(cat inline_comments.json | jq 'length') - - echo "GitHub PR Information:" > /tmp/comment.txt - echo "- Repository: ${{ github.repository }}" >> /tmp/comment.txt - echo "- PR Number: #${{ github.event.pull_request.number }}" >> /tmp/comment.txt - echo "- Title: ${{ github.event.pull_request.title }}" >> /tmp/comment.txt - echo "- Link: ${{ github.event.pull_request.html_url }}" >> /tmp/comment.txt - echo "- AI Inline Comments: $inline_count specific suggestions posted" >> /tmp/comment.txt - echo "" >> /tmp/comment.txt - echo "AI Generated Description:" >> /tmp/comment.txt - echo "$PR_DESCRIPTION" >> /tmp/comment.txt - echo "" >> /tmp/comment.txt - echo "Test Scenarios:" >> /tmp/comment.txt - echo "$AI_TEST_SCENARIOS" >> /tmp/comment.txt - echo "" >> /tmp/comment.txt - echo "Comment automatically posted by GitHub Actions" >> /tmp/comment.txt - - COMMENT_JSON=$(cat /tmp/comment.txt | jq -Rs '{body: {type: "doc", version: 1, content: [{type: "paragraph", content: [{type: "text", text: .}]}]}}') - - response=$(curl -s -w "%{http_code}" -X POST \ - -u "$JIRA_EMAIL:$JIRA_TOKEN" \ - -H "Content-Type: application/json" \ - "$JIRA_BASE_URL/rest/api/3/issue/$KEY/comment" \ - -d "$COMMENT_JSON") - http_code="${response: -3}" - if [ "$http_code" = "201" ]; then - echo "✅ Successfully posted AI content to Jira issue $KEY" - elif [ "$http_code" = "403" ]; then - echo "❌ Access denied (403). Possible issues:" - echo " - Invalid or expired API token" - echo " - Insufficient permissions for issue $KEY" - echo " - Project CSSCC access restricted" - echo " - SAML/SSO authentication problems" - else - echo "❌ Failed to post to Jira. HTTP code: $http_code" - echo "Response body: ${response%???}" - fi - - - name: Update PR body - if: steps.change_detection.outputs.has_changes == 'true' - run: | - inline_count=$(cat inline_comments.json | jq 'length') - - enhanced_description="$PR_DESCRIPTION - - --- - ## 🤖 AI Review Summary - - **Inline Comments**: $inline_count specific suggestions posted directly on relevant lines - - **Overall Review**: See comments below for detailed analysis - - **Test Scenarios**: Comprehensive test cases provided in comments - - *💡 Check the 'Files changed' tab to see inline comments on specific lines!*" - - gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} \ - -X PATCH \ - -F body="$enhanced_description" - - echo "✅ Updated PR description with inline comment summary" - - - name: Cleanup temporary files - if: always() && steps.change_detection.outputs.has_changes == 'true' - run: | - rm -f diff.txt files_diff.json combined_diff.json diff_batch_* batch_summaries.txt - rm -f inline_comments.json inline_comments.json.tmp /tmp/comment.txt - echo "🧹 Cleanup completed" - - - name: Review Summary - if: steps.change_detection.outputs.has_changes == 'true' - run: | - echo "🎉 AI PR Review Workflow Complete!" - echo "===========================================" - echo "✅ PR Description: Updated" - echo "✅ Inline Comments: $(cat inline_comments.json 2>/dev/null | jq 'length' 2>/dev/null || echo '0') posted" - echo "✅ Summary Comment: Posted/Updated" - echo "✅ Jira Integration: Attempted" - echo "===========================================" - echo "📋 Next Steps:" - echo "1. Review the AI-generated PR description" - echo "2. Check inline comments in the 'Files changed' tab" - echo "3. Address any valid suggestions from the AI review" - echo "4. Verify Jira ticket has been updated (if applicable)" \ No newline at end of file + # Final validate inline_comments.json is an array of objects with required fields + if ! jq -e 'type == "array" and (.[] + | (.path? and .position? and .body?))' inline_comments.json >/dev/null 2>&1; then + echo "inline_comments_ From 46e612c1ab878be5280e48bbc0455b6db74f7d54 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Tue, 2 Sep 2025 09:02:06 +0530 Subject: [PATCH 11/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index 80a27a8..d02a100 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -258,4 +258,40 @@ jobs: # Final validate inline_comments.json is an array of objects with required fields if ! jq -e 'type == "array" and (.[] | (.path? and .position? and .body?))' inline_comments.json >/dev/null 2>&1; then - echo "inline_comments_ + echo "Invalid inline_comments.json format; resetting to empty array" + echo "[]" > inline_comments.json + fi + + - name: Update PR description + if: steps.change_detection.outputs.has_changes == 'true' + run: | + gh pr edit "$GITHUB_PR_NUMBER" --body "$PR_DESCRIPTION" --repo "$GITHUB_REPO" + + - name: Post inline comments (batch) + if: steps.change_detection.outputs.has_changes == 'true' + run: | + if [ -s inline_comments.json ] && [ "$(jq 'length' inline_comments.json)" -gt 0 ]; then + jq -c '.[]' inline_comments.json | while read -r comment; do + path=$(echo "$comment" | jq -r '.path') + position=$(echo "$comment" | jq -r '.position') + body=$(echo "$comment" | jq -r '.body') + + curl -s -X POST \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/comments" \ + -d "$(jq -n --arg path "$path" --arg position "$position" --arg body "$body" '{path: $path, position: ($position | tonumber), side: "RIGHT", body: $body}')" || true + done + else + echo "No inline comments to post" + fi + + - name: Summary output + if: always() + run: | + echo "✅ AI PR description workflow completed" + echo "📊 Processed batches: ${PROCESSED_BATCHES:-0}/${BATCH_COUNT:-0}" + if [ -f inline_comments.json ]; then + comment_count=$(jq 'length' inline_comments.json 2>/dev/null || echo "0") + echo "💬 Generated inline comments: $comment_count" + fi From e45f311e496ad686ec86c38dd096f6a9ac8d94da Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Tue, 2 Sep 2025 09:09:10 +0530 Subject: [PATCH 12/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 405 +++++++++++++----------------------- 1 file changed, 141 insertions(+), 264 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index d02a100..02baddb 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -1,12 +1,16 @@ -name: AI Batching PR Description (OpenAI - Modular) +name: AI Inline Comment Diagnostic (one-shot) on: + workflow_dispatch: + inputs: + pr_number: + description: 'PR number (optional when running from a pull_request event)' + required: false + head_sha: + description: 'Head commit SHA (optional)' + required: false pull_request: - types: [opened, synchronize] - -concurrency: - group: ai-batching-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true + types: [opened, synchronize, reopened] permissions: contents: read @@ -14,284 +18,157 @@ permissions: issues: write jobs: - generate_pr_description: + post_single_inline_comment: runs-on: ubuntu-latest - # optional timeout: uncomment to limit long-running jobs - # timeout-minutes: 30 - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_REPO: ${{ github.repository }} - BATCH_SIZE: 150 - MAX_COMMENTS_PER_FILE: 5 - MAX_TOTAL_COMMENTS: 120 - steps: - - name: Checkout repository (full history) - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.head_ref }} - - - name: Install runtime deps + - name: Setup run: | sudo apt-get update -y - sudo apt-get install -y jq curl git - # GitHub CLI (gh) - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install -y gh - - - name: Validate environment variables + sudo apt-get install -y jq curl + - name: Compute and post a single inline comment (diagnostic) + env: + GITHUB_REPO: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # these values are available when workflow triggered by pull_request + PR_FROM_EVENT: ${{ github.event.pull_request.number }} + HEAD_SHA_FROM_EVENT: ${{ github.event.pull_request.head.sha }} + # inputs when run via workflow_dispatch + PR_FROM_INPUT: ${{ github.event.inputs.pr_number }} + HEAD_SHA_FROM_INPUT: ${{ github.event.inputs.head_sha }} + GITHUB_EVENT_NAME: ${{ github.event_name }} run: | - if [ -z "${OPENAI_API_KEY:-}" ]; then - echo "❌ OPENAI_API_KEY is not set" - exit 1 - fi - if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "❌ GITHUB_TOKEN is not set" - exit 1 + set -euo pipefail + echo "Event: $GITHUB_EVENT_NAME" + # Determine PR number and head SHA + if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + PR_NUMBER="${PR_FROM_EVENT}" + HEAD_SHA="${HEAD_SHA_FROM_EVENT}" + else + PR_NUMBER="${PR_FROM_INPUT:-}" + HEAD_SHA="${HEAD_SHA_FROM_INPUT:-}" fi - - name: Extract git diff - run: | - git diff "${{ github.event.pull_request.base.sha }}"..."${{ github.event.pull_request.head.sha }}" > diff.txt || true - - - name: Fetch GitHub API diff (files) - run: | - # Save raw files list (fallback to empty array) - curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/files" -o files_diff.json || echo '[]' > files_diff.json - # ensure it's valid JSON array - if ! jq -e . files_diff.json >/dev/null 2>&1; then - echo "[] " > files_diff.json + if [ -z "${PR_NUMBER:-}" ] || [ "${PR_NUMBER}" = "null" ]; then + echo "ERROR: PR number not provided. If you ran this manually, provide the 'pr_number' input." + exit 1 fi - - name: Combine both diffs into JSON (safe) - run: | - gitdiff=$(cat diff.txt 2>/dev/null || true) - # use slurpfile to safely import JSON even if it contains newlines - if [ -s files_diff.json ]; then - jq -n --arg gitdiff "$gitdiff" --slurpfile apidiff files_diff.json '{gitdiff: $gitdiff, apidiff: $apidiff[0]}' > combined_diff.json - else - jq -n --arg gitdiff "$gitdiff" '{gitdiff: $gitdiff, apidiff: []}' > combined_diff.json + if [ -z "${HEAD_SHA:-}" ] || [ "${HEAD_SHA}" = "null" ]; then + echo "WARN: head SHA not provided. Will attempt to read PR head sha from API." + HEAD_SHA="" fi - - name: Check for meaningful changes - id: change_detection - run: | - if [ -s diff.txt ]; then - echo "has_changes=true" >> "$GITHUB_OUTPUT" - else - echo "has_changes=false" >> "$GITHUB_OUTPUT" + echo "Target repo: $GITHUB_REPO" + echo "Target PR: $PR_NUMBER" + if [ -n "$HEAD_SHA" ]; then + echo "Using provided HEAD SHA: $HEAD_SHA" fi - - name: Split diff into batches - if: steps.change_detection.outputs.has_changes == 'true' - run: | - rm -f diff_batch_* || true - split -l "${BATCH_SIZE}" diff.txt diff_batch_ || true - echo "BATCH_COUNT=$(ls diff_batch_* 2>/dev/null | wc -l)" >> "$GITHUB_ENV" - - - name: Test OpenAI API connectivity - if: steps.change_detection.outputs.has_changes == 'true' - run: | - curl -s https://api.openai.com/v1/models -H "Authorization: Bearer ${OPENAI_API_KEY}" | jq '.data | length' || true - - - name: Process each batch with OpenAI (PR summary) - if: steps.change_detection.outputs.has_changes == 'true' - run: | - rm -f batch_summaries.txt || true - processed=0 - for batch in diff_batch_*; do - [ -f "$batch" ] || continue - diff_content=$(cat "$batch") - request=$(jq -n --arg model "gpt-4o-mini" \ - --arg system "You summarize git diffs for PR descriptions." \ - --arg user "$diff_content" \ - '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:300,temperature:0.2}') - response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer ${OPENAI_API_KEY}" \ - -d "$request") - if echo "$response" | jq -e .choices >/dev/null 2>&1; then - summary=$(echo "$response" | jq -r '.choices[0].message.content // ""') - if [ -n "$summary" ]; then - echo "## Batch $(basename "$batch")" >> batch_summaries.txt - echo "$summary" >> batch_summaries.txt - echo "" >> batch_summaries.txt - processed=$((processed+1)) - fi - else - echo "Warning: no summary for $batch" - fi - done - echo "PROCESSED_BATCHES=$processed" >> "$GITHUB_ENV" + # Fetch PR files + echo "Fetching files for PR #$PR_NUMBER..." + curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}/files" -o files.json - - name: Aggregate and rephrase summaries (strong prompt) - if: steps.change_detection.outputs.has_changes == 'true' - run: | - full_summary=$(cat batch_summaries.txt 2>/dev/null || "") - system_prompt="You are a professional release engineer and technical writer. Given a collection of git-diff summaries, produce a concise, structured PR description. Output must include: 1) Short summary (1-2 lines), 2) Bullet list of changed areas/files, 3) Impact/risk (Low/Medium/High) with justification, 4) Testing notes and validation commands, 5) Suggested reviewers/teams. Keep it production-ready and concise." - request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$full_summary" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:400,temperature:0.2}') - response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${OPENAI_API_KEY}" -d "$request") - if echo "$response" | jq -e .choices >/dev/null 2>&1; then - pr_desc=$(echo "$response" | jq -r '.choices[0].message.content // ""') - else - pr_desc="Failed to generate PR description." + if ! jq -e . files.json >/dev/null 2>&1; then + echo "ERROR: files.json is not valid JSON; printing raw content:" + cat files.json || true + exit 1 fi - # Write PR_DESCRIPTION into GITHUB_ENV safely - echo 'PR_DESCRIPTION<> "$GITHUB_ENV" - echo "$pr_desc" >> "$GITHUB_ENV" - echo 'EOF' >> "$GITHUB_ENV" - - - name: Generate AI Code Review and Test Scenarios - if: steps.change_detection.outputs.has_changes == 'true' - run: | - diff_all=$(cat diff.txt 2>/dev/null || "") - review_prompt="You are a strict senior code reviewer. For the provided unified diff, list file-level issues (security, correctness, style, performance) with a short rationale and suggested change. Output as plain text grouped by file." - tests_prompt="You are a test architect. From the diff, produce prioritized test cases and steps to validate the changes, including boundary and negative cases." - req_review=$(jq -n --arg model "gpt-4o-mini" --arg system "$review_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') - req_tests=$(jq -n --arg model "gpt-4o-mini" --arg system "$tests_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') - review_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_review") - tests_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_tests") - review_text=$(echo "$review_resp" | jq -r '.choices[0].message.content // ""') - test_text=$(echo "$tests_resp" | jq -r '.choices[0].message.content // ""') - echo 'AI_CODE_REVIEW<> "$GITHUB_ENV" - echo "$review_text" >> "$GITHUB_ENV" - echo 'EOF' >> "$GITHUB_ENV" - echo 'AI_TEST_SCENARIOS<> "$GITHUB_ENV" - echo "$test_text" >> "$GITHUB_ENV" - echo 'EOF' >> "$GITHUB_ENV" - - - name: Generate AI Inline Comments (bash + awk, validated) - if: steps.change_detection.outputs.has_changes == 'true' - run: | - set -euo pipefail - echo "[]" > inline_comments.json - rm -f added_lines.ndjson inline_comments.tmp || true - - # Build NDJSON of added lines (limit per file) - jq -c '.[] | select(.patch != null and .patch != "") | {filename: .filename, patch: .patch}' files_diff.json \ - | while read -r fileobj; do - filename=$(echo "$fileobj" | jq -r '.filename') - patch=$(echo "$fileobj" | jq -r '.patch') - # skip binary types - if echo "$filename" | grep -qE '\.(png|jpg|jpeg|gif|ico|pdf|zip|tar|gz|exe|dll|so|dylib)$'; then - continue - fi - echo "$patch" | awk -v filename="$filename" -v max_per="${MAX_COMMENTS_PER_FILE}" ' - BEGIN { pos=0; new_line=0; added_count=0; } - /^(\-\-\- |\+\+\+ )/ { next } - /^@@/ { - if (match($0, /\+([0-9]+)/, arr)) { - new_line = arr[1] - 1 - } - next - } - { - if (length($0) == 0) next - first = substr($0,1,1) - if (first != " " && first != "+" && first != "-") next - pos++ - if (first == "+") { - new_line++ - if (added_count < max_per) { - line = substr($0,2) - gsub(/"/, "\\\"", line) - gsub(/\r/,"",line) - printf("{\"path\":\"%s\",\"position\":%d,\"new_line\":%d,\"content\":\"%s\"}\n", filename, pos, new_line, line) - added_count++ - } - } else if (first == " ") { - new_line++ - } - }' >> added_lines.ndjson - done - - if [ ! -f added_lines.ndjson ] || [ ! -s added_lines.ndjson ]; then - echo "No added lines found; skipping inline comment creation." - echo "[]" > inline_comments.json - else - # Limit overall comments to MAX_TOTAL_COMMENTS to protect rate limits - total=0 - touch inline_comments.tmp - while read -r entry && [ "$total" -lt "${MAX_TOTAL_COMMENTS}" ]; do - path=$(echo "$entry" | jq -r '.path') - position=$(echo "$entry" | jq -r '.position') - new_line=$(echo "$entry" | jq -r '.new_line') - content=$(echo "$entry" | jq -r '.content') - - system_prompt='You are an expert senior code reviewer. Return a concise JSON: {"comment":"..","severity":"Low|Medium|High","suggested_fix":"..."} or {"comment":"","severity":"Low","suggested_fix":""} if none.' - user_content="File: ${path}\nLine: ${new_line}\nAdded line:\n${content}\n\nProvide a concise production-ready suggestion for this line." - request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$user_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:180,temperature:0.05}') - response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$request") - suggestion=$(echo "$response" | jq -r '.choices[0].message.content // ""' | tr -d '\r') + echo "Listing first 3 files (name + status):" + jq -r '.[] | "\(.filename) (\.status)"' files.json | sed -n '1,3p' || true - if [ -z "$suggestion" ]; then - continue - fi - - # Validate: try to parse model JSON; if parse fails, wrap suggestion as plain text comment - if echo "$suggestion" | jq -e . >/dev/null 2>&1; then - comment_body=$(echo "$suggestion" | jq -r 'if type=="object" and (.comment // "") != "" then ("💡 " + .comment + "\n\nSeverity: " + (.severity//"Low") + "\n\nSuggested fix:\n" + (.suggested_fix//"")) else (. | tostring) end') - else - comment_body=$(printf "💡 %s" "$suggestion") - fi - - # escape newlines/quotes for JSON payload - body_safe=$(printf "%s" "$comment_body" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') - printf '{"path":"%s","position":%s,"side":"RIGHT","body":"%s"}\n' "$path" "$position" "$body_safe" >> inline_comments.tmp - - total=$((total+1)) - done < added_lines.ndjson - - if [ -s inline_comments.tmp ]; then - jq -s . inline_comments.tmp > inline_comments.json - else - echo "[]" > inline_comments.json - fi + # pick the first file that has a patch + file_obj=$(jq -c '.[] | select(.patch != null and .patch != "")' files.json | head -n1 || true) + if [ -z "$file_obj" ]; then + echo "No file with a patch found in this PR. Nothing to test." + exit 0 fi - # Final validate inline_comments.json is an array of objects with required fields - if ! jq -e 'type == "array" and (.[] - | (.path? and .position? and .body?))' inline_comments.json >/dev/null 2>&1; then - echo "Invalid inline_comments.json format; resetting to empty array" - echo "[]" > inline_comments.json + path=$(echo "$file_obj" | jq -r '.filename') + patch=$(echo "$file_obj" | jq -r '.patch') + + echo "Selected file: $path" + echo "Patch (first 40 lines):" + printf "%s\n" "$patch" | sed -n '1,40p' + echo "----" + + # Compute first added line's diff position and new file line number + pos_and_new=$(printf "%s\n" "$patch" | awk ' + BEGIN {pos=0; new_line=0;} + /^(\-\-\- |\+\+\+ )/ { next } + /^@@/ { + if (match($0, /\+([0-9]+)/, arr)) new_line = arr[1] - 1 + next + } + { + if (length($0) == 0) next + first = substr($0,1,1) + if (first != " " && first != "+" && first != "-") next + pos++ + if (first == "+") { + new_line++ + print pos ":" new_line ":" substr($0,2) + exit + } else if (first == " ") { + new_line++ + } + }' ) + + if [ -z "$pos_and_new" ]; then + echo "No added '+' line found in the patch for $path. Exiting." + exit 0 fi - - name: Update PR description - if: steps.change_detection.outputs.has_changes == 'true' - run: | - gh pr edit "$GITHUB_PR_NUMBER" --body "$PR_DESCRIPTION" --repo "$GITHUB_REPO" - - - name: Post inline comments (batch) - if: steps.change_detection.outputs.has_changes == 'true' - run: | - if [ -s inline_comments.json ] && [ "$(jq 'length' inline_comments.json)" -gt 0 ]; then - jq -c '.[]' inline_comments.json | while read -r comment; do - path=$(echo "$comment" | jq -r '.path') - position=$(echo "$comment" | jq -r '.position') - body=$(echo "$comment" | jq -r '.body') - - curl -s -X POST \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/comments" \ - -d "$(jq -n --arg path "$path" --arg position "$position" --arg body "$body" '{path: $path, position: ($position | tonumber), side: "RIGHT", body: $body}')" || true - done - else - echo "No inline comments to post" + pos=$(echo "$pos_and_new" | cut -d: -f1) + new_line=$(echo "$pos_and_new" | cut -d: -f2) + added_content=$(echo "$pos_and_new" | cut -d: -f3-) + + echo "Computed position: $pos" + echo "Computed new_file line: $new_line" + echo "Added content preview: $added_content" + + # If HEAD_SHA wasn't supplied, try to get it from the PR object + if [ -z "${HEAD_SHA}" ] || [ "${HEAD_SHA}" = "null" ]; then + echo "Retrieving head SHA from PR metadata..." + HEAD_SHA=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}" | jq -r '.head.sha // empty') + if [ -z "$HEAD_SHA" ]; then + echo "ERROR: could not determine head SHA for PR $PR_NUMBER" + exit 1 + fi + echo "Found head SHA: $HEAD_SHA" fi - - name: Summary output - if: always() - run: | - echo "✅ AI PR description workflow completed" - echo "📊 Processed batches: ${PROCESSED_BATCHES:-0}/${BATCH_COUNT:-0}" - if [ -f inline_comments.json ]; then - comment_count=$(jq 'length' inline_comments.json 2>/dev/null || echo "0") - echo "💬 Generated inline comments: $comment_count" - fi + # Build payload to POST a single inline comment (position-based) + comment_body="Test inline comment from workflow — position=${pos}, new_line=${new_line} + + This is a diagnostic test (delete me)." + + payload=$(jq -n --arg commit_id "$HEAD_SHA" --arg path "$path" --arg position "$pos" --arg body "$comment_body" \ + '{commit_id:$commit_id, path:$path, position:($position|tonumber), body:$body}') + + echo "Posting a single inline comment to the PR (comments endpoint)..." + echo "Payload:" + echo "$payload" + + response=$(curl -s -w "\nHTTPSTATUS:%{http_code}\n" -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}/comments" \ + -d "$payload") + + echo "API response:" + echo "$response" + echo "----" + + # Print helpful hints + status=$(echo "$response" | tr -s '\n' ' ' | sed -n 's/.*HTTPSTATUS:\([0-9][0-9][0-9]\).*/\1/p') + if [ "$status" = "201" ]; then + echo "SUCCESS: comment created (HTTP 201). Check the Files changed tab in the PR." + else + echo "Comment create HTTP status: ${status} — if 422 or 200 but not visible, commit_id/position/path likely mismatched." + echo "If you see 422: position is out of range -> patch parsing or head SHA mismatch." + echo "If you see 403: permission problem with token (needs pull-requests: write & issues: write)." + echo "If you see 201 but nothing appears: check the Files changed UI and that the PR hasn't moved to a new commit after the position was computed." + fi From 94e5ceb5f7cc79cb99b935b80ab80acaf2147112 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Tue, 2 Sep 2025 11:27:45 +0530 Subject: [PATCH 13/14] added code for pr inline comments --- .github/workflows/ai-pr.yml | 502 ++++++++++++++++++++++++++---------- README.md | 2 +- 2 files changed, 364 insertions(+), 140 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index 02baddb..cf39202 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -1,16 +1,12 @@ -name: AI Inline Comment Diagnostic (one-shot) +name: AI Batching PR Description (OpenAI - Modular) on: - workflow_dispatch: - inputs: - pr_number: - description: 'PR number (optional when running from a pull_request event)' - required: false - head_sha: - description: 'Head commit SHA (optional)' - required: false pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize] + +concurrency: + group: ai-batching-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true permissions: contents: read @@ -18,157 +14,385 @@ permissions: issues: write jobs: - post_single_inline_comment: + generate_pr_description: runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_REPO: ${{ github.repository }} + BATCH_SIZE: 150 + MAX_COMMENTS_PER_FILE: 5 + MAX_TOTAL_COMMENTS: 120 + steps: - - name: Setup + - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y jq curl - - name: Compute and post a single inline comment (diagnostic) - env: - GITHUB_REPO: ${{ github.repository }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # these values are available when workflow triggered by pull_request - PR_FROM_EVENT: ${{ github.event.pull_request.number }} - HEAD_SHA_FROM_EVENT: ${{ github.event.pull_request.head.sha }} - # inputs when run via workflow_dispatch - PR_FROM_INPUT: ${{ github.event.inputs.pr_number }} - HEAD_SHA_FROM_INPUT: ${{ github.event.inputs.head_sha }} - GITHUB_EVENT_NAME: ${{ github.event_name }} + sudo apt-get install -y jq curl git + # GitHub CLI + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update -y + sudo apt-get install -y gh + + - name: Checkout repository and set PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} + + - name: Validate environment variables run: | - set -euo pipefail - echo "Event: $GITHUB_EVENT_NAME" - # Determine PR number and head SHA - if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - PR_NUMBER="${PR_FROM_EVENT}" - HEAD_SHA="${HEAD_SHA_FROM_EVENT}" - else - PR_NUMBER="${PR_FROM_INPUT:-}" - HEAD_SHA="${HEAD_SHA_FROM_INPUT:-}" + if [ -z "${OPENAI_API_KEY:-}" ]; then + echo "❌ OPENAI_API_KEY is not set"; exit 1 + fi + if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "❌ GITHUB_TOKEN is not set"; exit 1 fi - if [ -z "${PR_NUMBER:-}" ] || [ "${PR_NUMBER}" = "null" ]; then - echo "ERROR: PR number not provided. If you ran this manually, provide the 'pr_number' input." - exit 1 + - name: Extract git diff + run: | + git diff "${{ github.event.pull_request.base.sha }}"..."${{ github.event.pull_request.head.sha }}" > diff.txt || true + + - name: Fetch GitHub API diff (files) + run: | + curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/files" -o files_diff.json || echo '[]' > files_diff.json + # ensure valid JSON + if ! jq -e . files_diff.json >/dev/null 2>&1; then + echo "[] " > files_diff.json fi - if [ -z "${HEAD_SHA:-}" ] || [ "${HEAD_SHA}" = "null" ]; then - echo "WARN: head SHA not provided. Will attempt to read PR head sha from API." - HEAD_SHA="" + - name: Combine both diffs into JSON (safe) + run: | + gitdiff=$(cat diff.txt 2>/dev/null || true) + if [ -s files_diff.json ]; then + jq -n --arg gitdiff "$gitdiff" --slurpfile apidiff files_diff.json '{gitdiff: $gitdiff, apidiff: $apidiff[0]}' > combined_diff.json + else + jq -n --arg gitdiff "$gitdiff" '{gitdiff: $gitdiff, apidiff: []}' > combined_diff.json fi - echo "Target repo: $GITHUB_REPO" - echo "Target PR: $PR_NUMBER" - if [ -n "$HEAD_SHA" ]; then - echo "Using provided HEAD SHA: $HEAD_SHA" + - name: Check for meaningful changes + id: change_detection + run: | + if [ -s diff.txt ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" fi - # Fetch PR files - echo "Fetching files for PR #$PR_NUMBER..." - curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}/files" -o files.json + - name: Split diff into batches + if: steps.change_detection.outputs.has_changes == 'true' + run: | + rm -f diff_batch_* || true + split -l "${BATCH_SIZE}" diff.txt diff_batch_ || true + echo "BATCH_COUNT=$(ls diff_batch_* 2>/dev/null | wc -l)" >> "$GITHUB_ENV" - if ! jq -e . files.json >/dev/null 2>&1; then - echo "ERROR: files.json is not valid JSON; printing raw content:" - cat files.json || true - exit 1 + - name: Test OpenAI API connectivity + if: steps.change_detection.outputs.has_changes == 'true' + run: | + curl -s https://api.openai.com/v1/models -H "Authorization: Bearer ${OPENAI_API_KEY}" | jq '.data | length' || true + + - name: Initialize batch processing + if: steps.change_detection.outputs.has_changes == 'true' + run: echo "PROCESSED_BATCHES=0" >> "$GITHUB_ENV" + + - name: Process each batch with OpenAI (PR summary) + if: steps.change_detection.outputs.has_changes == 'true' + run: | + rm -f batch_summaries.txt || true + processed=0 + for batch in diff_batch_*; do + [ -f "$batch" ] || continue + diff_content=$(cat "$batch") + request=$(jq -n --arg model "gpt-4o-mini" --arg system "You summarize git diffs for PR descriptions." --arg user "$diff_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:300,temperature:0.2}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${OPENAI_API_KEY}" -d "$request") + if echo "$response" | jq -e .choices >/dev/null 2>&1; then + summary=$(echo "$response" | jq -r '.choices[0].message.content // ""') + if [ -n "$summary" ]; then + echo "## Batch $(basename "$batch")" >> batch_summaries.txt + echo "$summary" >> batch_summaries.txt + echo "" >> batch_summaries.txt + processed=$((processed+1)) + fi + fi + done + echo "PROCESSED_BATCHES=$processed" >> "$GITHUB_ENV" + + - name: Aggregate and rephrase summaries + if: steps.change_detection.outputs.has_changes == 'true' + run: | + full_summary=$(cat batch_summaries.txt 2>/dev/null || "") + system_prompt="You are a professional release engineer and technical writer. Given a collection of git-diff summaries, produce a concise, structured PR description. Output must include: 1) Short summary (1-2 lines), 2) Bullet list of changed areas/files, 3) Impact/risk (Low/Medium/High) with justification, 4) Testing notes and validation commands, 5) Suggested reviewers/teams. Keep it production-ready and concise." + request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$full_summary" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:400,temperature:0.2}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${OPENAI_API_KEY}" -d "$request") + if echo "$response" | jq -e .choices >/dev/null 2>&1; then + pr_desc=$(echo "$response" | jq -r '.choices[0].message.content // ""') + else + pr_desc="Failed to generate PR description." fi + echo 'PR_DESCRIPTION<> "$GITHUB_ENV" + echo "$pr_desc" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" - echo "Listing first 3 files (name + status):" - jq -r '.[] | "\(.filename) (\.status)"' files.json | sed -n '1,3p' || true + - name: Generate AI Code Review and Test Scenarios + if: steps.change_detection.outputs.has_changes == 'true' + id: ai_reviews + run: | + diff_all=$(cat diff.txt 2>/dev/null || "") + review_prompt="You are a strict senior code reviewer. For the provided unified diff, list file-level issues (security, correctness, style, performance) with a short rationale and suggested change. Output as plain text grouped by file." + tests_prompt="You are a test architect. From the diff, produce prioritized test cases and steps to validate the changes, including boundary and negative cases." + req_review=$(jq -n --arg model "gpt-4o-mini" --arg system "$review_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') + req_tests=$(jq -n --arg model "gpt-4o-mini" --arg system "$tests_prompt" --arg user "$diff_all" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:500,temperature:0.2}') + review_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_review") + tests_resp=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$req_tests") + review_text=$(echo "$review_resp" | jq -r '.choices[0].message.content // ""') + test_text=$(echo "$tests_resp" | jq -r '.choices[0].message.content // ""') + echo 'AI_CODE_REVIEW<> "$GITHUB_ENV" + echo "$review_text" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" + echo 'AI_TEST_SCENARIOS<> "$GITHUB_ENV" + echo "$test_text" >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" - # pick the first file that has a patch - file_obj=$(jq -c '.[] | select(.patch != null and .patch != "")' files.json | head -n1 || true) - if [ -z "$file_obj" ]; then - echo "No file with a patch found in this PR. Nothing to test." - exit 0 + - name: Generate AI Inline Comments (compute positions, call OpenAI per-line) + if: steps.change_detection.outputs.has_changes == 'true' + run: | + set -euo pipefail + echo "[]" > inline_comments.json + rm -f added_lines.ndjson inline_comments.tmp || true + + # Build NDJSON for added lines (max per-file) + jq -c '.[] | select(.patch != null and .patch != "") | {filename: .filename, patch: .patch}' files_diff.json \ + | while read -r fileobj; do + filename=$(echo "$fileobj" | jq -r '.filename') + patch=$(echo "$fileobj" | jq -r '.patch') + + # skip binary-like files + if echo "$filename" | grep -qE '\.(png|jpg|jpeg|gif|ico|pdf|zip|tar|gz|exe|dll|so|dylib)$'; then + echo "Skipping binary: $filename" + continue + fi + + # parse patch to extract added lines and diff positions (awk) + echo "$patch" | awk -v filename="$filename" -v max_per="${MAX_COMMENTS_PER_FILE}" ' + BEGIN { pos=0; new_line=0; added_count=0; } + /^(\-\-\- |\+\+\+ )/ { next } + /^@@/ { + if (match($0, /\+([0-9]+)/, arr)) new_line = arr[1] - 1 + next + } + { + if (length($0) == 0) next + first = substr($0,1,1) + if (first != " " && first != "+" && first != "-") next + pos++ + if (first == "+") { + new_line++ + if (added_count < max_per) { + line = substr($0,2) + gsub(/"/, "\\\"", line) + gsub(/\r/,"",line) + printf("{\"path\":\"%s\",\"position\":%d,\"new_line\":%d,\"content\":\"%s\"}\n", filename, pos, new_line, line) + added_count++ + } + } else if (first == " ") { + new_line++ + } + }' >> added_lines.ndjson + done + + if [ ! -f added_lines.ndjson ] || [ ! -s added_lines.ndjson ]; then + echo "No added lines found; skipping inline comment creation." + echo "[]" > inline_comments.json + else + total=0 + touch inline_comments.tmp + while read -r entry && [ "$total" -lt "${MAX_TOTAL_COMMENTS}" ]; do + path=$(echo "$entry" | jq -r '.path') + position=$(echo "$entry" | jq -r '.position') + new_line=$(echo "$entry" | jq -r '.new_line') + content=$(echo "$entry" | jq -r '.content') + + system_prompt='You are an expert senior code reviewer. Return a concise JSON: {"comment":"..","severity":"Low|Medium|High","suggested_fix":"..."} or {"comment":"","severity":"Low","suggested_fix":""} if none.' + user_content="File: ${path}\nLine: ${new_line}\nAdded line:\n${content}\n\nProvide a concise production-ready suggestion for this line." + + request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$user_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:180,temperature:0.05}') + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$request") + suggestion=$(echo "$response" | jq -r '.choices[0].message.content // ""' | tr -d '\r') + + if [ -z "$suggestion" ]; then + continue + fi + + # If model returns valid JSON, build a readable comment; otherwise use raw text + if echo "$suggestion" | jq -e . >/dev/null 2>&1; then + comment_body=$(echo "$suggestion" | jq -r 'if (type=="object") then (("💡 " + (.comment // "" ) + "\n\nSeverity: " + (.severity // "Low") + "\n\nSuggested fix:\n" + (.suggested_fix // ""))) else . end') + else + comment_body=$(printf "💡 %s" "$suggestion") + fi + + # escape newlines/quotes for JSON payload + body_safe=$(printf "%s" "$comment_body" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') + printf '{"path":"%s","position":%s,"side":"RIGHT","body":"%s"}\n' "$path" "$position" "$body_safe" >> inline_comments.tmp + + total=$((total+1)) + done < added_lines.ndjson + + if [ -s inline_comments.tmp ]; then + jq -s . inline_comments.tmp > inline_comments.json + else + echo "[]" > inline_comments.json + fi fi - path=$(echo "$file_obj" | jq -r '.filename') - patch=$(echo "$file_obj" | jq -r '.patch') - - echo "Selected file: $path" - echo "Patch (first 40 lines):" - printf "%s\n" "$patch" | sed -n '1,40p' - echo "----" - - # Compute first added line's diff position and new file line number - pos_and_new=$(printf "%s\n" "$patch" | awk ' - BEGIN {pos=0; new_line=0;} - /^(\-\-\- |\+\+\+ )/ { next } - /^@@/ { - if (match($0, /\+([0-9]+)/, arr)) new_line = arr[1] - 1 - next - } - { - if (length($0) == 0) next - first = substr($0,1,1) - if (first != " " && first != "+" && first != "-") next - pos++ - if (first == "+") { - new_line++ - print pos ":" new_line ":" substr($0,2) - exit - } else if (first == " ") { - new_line++ - } - }' ) - - if [ -z "$pos_and_new" ]; then - echo "No added '+' line found in the patch for $path. Exiting." + # Validation: ensure array of objects with required keys + if ! jq -e 'type == "array" and (.[] + | (.path? and .position? and .body?))' inline_comments.json >/dev/null 2>&1; then + echo "inline_comments.json failed validation; clearing to []" + echo "[]" > inline_comments.json + fi + + echo "Prepared $(jq 'length' inline_comments.json) inline comments." + + - name: Create PR Review with Inline Comments + if: steps.change_detection.outputs.has_changes == 'true' + run: | + set -euo pipefail + COMMIT_SHA="${{ github.event.pull_request.head.sha }}" + comment_count=$(jq 'length' inline_comments.json 2>/dev/null || echo 0) + if [ "$comment_count" -le 0 ]; then + echo "No inline comments to post." exit 0 fi - pos=$(echo "$pos_and_new" | cut -d: -f1) - new_line=$(echo "$pos_and_new" | cut -d: -f2) - added_content=$(echo "$pos_and_new" | cut -d: -f3-) - - echo "Computed position: $pos" - echo "Computed new_file line: $new_line" - echo "Added content preview: $added_content" - - # If HEAD_SHA wasn't supplied, try to get it from the PR object - if [ -z "${HEAD_SHA}" ] || [ "${HEAD_SHA}" = "null" ]; then - echo "Retrieving head SHA from PR metadata..." - HEAD_SHA=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}" | jq -r '.head.sha // empty') - if [ -z "$HEAD_SHA" ]; then - echo "ERROR: could not determine head SHA for PR $PR_NUMBER" - exit 1 + review_body="🤖 **AI Code Review** - I've analyzed your changes and provided ${comment_count} specific inline suggestions. Please review the comments below for improvements in code quality, security, and best practices. *This review was generated automatically. Use engineering judgement when applying suggestions.*" + + payload=$(jq -n --arg commit_id "$COMMIT_SHA" --arg body "$review_body" --arg event "COMMENT" --argjson comments "$(jq '.' inline_comments.json)" '{commit_id:$commit_id, body:$body, event:$event, comments:$comments}') + + # Try bulk review creation with retries, fallback to individual comments + attempt=0 + max_attempts=3 + success=0 + while [ $attempt -lt $max_attempts ]; do + attempt=$((attempt+1)) + echo "Attempt $attempt to create review..." + response=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/reviews" -d "$payload") + http_code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + resp_body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + echo "✅ Created PR review with ${comment_count} inline comments." + success=1 + break + else + echo "Review create failed (HTTP $http_code)." + echo "$resp_body" + if [ $attempt -lt $max_attempts ]; then + sleep $((attempt * 3)) + fi fi - echo "Found head SHA: $HEAD_SHA" + done + + if [ $success -eq 0 ]; then + echo "Falling back to posting individual inline comments (position-based)..." + jq -c '.[]' inline_comments.json | while read -r c; do + path=$(echo "$c" | jq -r '.path') + position=$(echo "$c" | jq -r '.position') + body_text=$(echo "$c" | jq -r '.body') + + comment_payload=$(jq -n --arg commit_id "$COMMIT_SHA" --arg path "$path" --arg body "$body_text" --argjson pos "$position" '{commit_id:$commit_id, path:$path, position:($pos|tonumber), body:$body}') + + ind_resp=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/comments" -d "$comment_payload") + ind_code=$(echo "$ind_resp" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') + if [ "$ind_code" = "201" ]; then + echo "✅ Posted comment for $path (position $position)" + else + echo "❌ Failed to post comment for $path (HTTP $ind_code). Response: $ind_resp" + fi + done fi - # Build payload to POST a single inline comment (position-based) - comment_body="Test inline comment from workflow — position=${pos}, new_line=${new_line} - - This is a diagnostic test (delete me)." - - payload=$(jq -n --arg commit_id "$HEAD_SHA" --arg path "$path" --arg position "$pos" --arg body "$comment_body" \ - '{commit_id:$commit_id, path:$path, position:($position|tonumber), body:$body}') - - echo "Posting a single inline comment to the PR (comments endpoint)..." - echo "Payload:" - echo "$payload" - - response=$(curl -s -w "\nHTTPSTATUS:%{http_code}\n" -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${GITHUB_REPO}/pulls/${PR_NUMBER}/comments" \ - -d "$payload") - - echo "API response:" - echo "$response" - echo "----" - - # Print helpful hints - status=$(echo "$response" | tr -s '\n' ' ' | sed -n 's/.*HTTPSTATUS:\([0-9][0-9][0-9]\).*/\1/p') - if [ "$status" = "201" ]; then - echo "SUCCESS: comment created (HTTP 201). Check the Files changed tab in the PR." - else - echo "Comment create HTTP status: ${status} — if 422 or 200 but not visible, commit_id/position/path likely mismatched." - echo "If you see 422: position is out of range -> patch parsing or head SHA mismatch." - echo "If you see 403: permission problem with token (needs pull-requests: write & issues: write)." - echo "If you see 201 but nothing appears: check the Files changed UI and that the PR hasn't moved to a new commit after the position was computed." - fi + - name: Post or update AI review summary comment + if: steps.change_detection.outputs.has_changes == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + inline_count=$(jq 'length' inline_comments.json 2>/dev/null || echo 0) + + # Build comment body using printf to avoid YAML parsing issues + COMMENT_BODY=$(printf "### 🤖 AI Code Review Summary\n\n**Inline Comments Generated:** %s specific suggestions (see 'Files changed').\n\n**Overall Assessment:**\n%s\n\n%s\n### 🧪 AI Test Case Scenarios\n%s\n\n*💡 Tip: Check the 'Files changed' tab to see inline comments on the exact diff positions.*" \ + "$inline_count" \ + "$AI_CODE_REVIEW" \ + "---" \ + "$AI_TEST_SCENARIOS") + + # Update existing bot comment (if present) else create + existing=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id" || true) + if [ -n "$existing" ]; then + gh api repos/${{ github.repository }}/issues/comments/$existing -X PATCH -F body="$COMMENT_BODY" + else + gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments -F body="$COMMENT_BODY" + fi + + - name: Extract Jira issue key and comment on Jira + if: steps.change_detection.outputs.has_changes == 'true' + env: + JIRA_BASE_URL: https://cisco-sbg.atlassian.net + JIRA_TOKEN: ${{ secrets.JIRA_PERSONAL_TOKEN }} + JIRA_EMAIL: itsingh@cisco.com + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + BRANCH="${{ github.head_ref }}" + KEY=$(echo -e "$PR_TITLE\n$BRANCH" | grep -oE '[A-Z]{2,10}-[0-9]+' | head -1) + if [ -z "$KEY" ]; then + echo "No Jira issue key found; skipping Jira update." + exit 0 + fi + echo "Found Jira key: $KEY" + test_response=$(curl -s -w "%{http_code}" -X GET -u "$JIRA_EMAIL:$JIRA_TOKEN" -H "Content-Type: application/json" "$JIRA_BASE_URL/rest/api/3/issue/$KEY") + test_code="${test_response: -3}" + if [ "$test_code" != "200" ]; then + echo "❌ Cannot access Jira issue $KEY. HTTP code: $test_code" + exit 1 + fi + echo "✅ Jira API access successful" + echo "GitHub PR Information:" > /tmp/comment.txt + echo "- Repository: ${{ github.repository }}" >> /tmp/comment.txt + echo "- PR Number: #${{ github.event.pull_request.number }}" >> /tmp/comment.txt + echo "- Title: ${{ github.event.pull_request.title }}" >> /tmp/comment.txt + echo "- Link: ${{ github.event.pull_request.html_url }}" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "AI Generated Description:" >> /tmp/comment.txt + echo "$PR_DESCRIPTION" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "Test Scenarios:" >> /tmp/comment.txt + echo "$AI_TEST_SCENARIOS" >> /tmp/comment.txt + echo "" >> /tmp/comment.txt + echo "Comment automatically posted by GitHub Actions" >> /tmp/comment.txt + COMMENT_JSON=$(cat /tmp/comment.txt | jq -Rs '{body: .}') + response=$(curl -s -w "%{http_code}" -X POST -u "$JIRA_EMAIL:$JIRA_TOKEN" -H "Content-Type: application/json" "$JIRA_BASE_URL/rest/api/3/issue/$KEY/comment" -d "$COMMENT_JSON") + http_code="${response: -3}" + if [ "$http_code" = "201" ]; then + echo "✅ Successfully posted AI content to Jira issue $KEY" + else + echo "❌ Failed to post to Jira. HTTP code: $http_code" + fi + + - name: Update PR body + if: steps.change_detection.outputs.has_changes == 'true' + run: | + # Update PR body with generated PR_DESCRIPTION + gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} -X PATCH -F body="$PR_DESCRIPTION" + + - name: Cleanup temporary files + if: always() + run: | + rm -f diff.txt files_diff.json combined_diff.json diff_batch_* batch_summaries.txt added_lines.ndjson inline_comments.tmp inline_comments.json || true + + - name: Review Summary (logs) + if: steps.change_detection.outputs.has_changes == 'true' + run: | + echo "🎉 AI PR Review Workflow Complete!" + echo "✅ PR Description: Updated" + echo "✅ Inline Comments: $(jq 'length' inline_comments.json 2>/dev/null || echo 0) posted" diff --git a/README.md b/README.md index c5706d6..4695aa1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Before running the build scripts, ensure you have all required host tools instal ## Build Process -2. To create rootfs and build packages for required architecture: +2. To create rootfs and build packages for required architecture hellooo ```bash ./build_pkgs.sh mipsel From fb2395f33abed13fcf6daae09282647f376be630 Mon Sep 17 00:00:00 2001 From: itsingh1902 Date: Sun, 7 Sep 2025 22:47:52 +0530 Subject: [PATCH 14/14] trying to add inline comments --- .github/workflows/ai-pr.yml | 122 +++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ai-pr.yml b/.github/workflows/ai-pr.yml index cf39202..58a13ab 100644 --- a/.github/workflows/ai-pr.yml +++ b/.github/workflows/ai-pr.yml @@ -59,7 +59,6 @@ jobs: run: | curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \ "https://api.github.com/repos/${GITHUB_REPO}/pulls/${GITHUB_PR_NUMBER}/files" -o files_diff.json || echo '[]' > files_diff.json - # ensure valid JSON if ! jq -e . files_diff.json >/dev/null 2>&1; then echo "[] " > files_diff.json fi @@ -156,14 +155,17 @@ jobs: echo "$test_text" >> "$GITHUB_ENV" echo 'EOF' >> "$GITHUB_ENV" - - name: Generate AI Inline Comments (compute positions, call OpenAI per-line) + # ------------------------------ + # NEW: smarter per-file batching + strict JSON + filtering + # ------------------------------ + - name: Generate AI Inline Comments (per-file batch, strict JSON, filter Low) if: steps.change_detection.outputs.has_changes == 'true' run: | set -euo pipefail echo "[]" > inline_comments.json rm -f added_lines.ndjson inline_comments.tmp || true - # Build NDJSON for added lines (max per-file) + # Build NDJSON for added lines (max per-file entries produced) jq -c '.[] | select(.patch != null and .patch != "") | {filename: .filename, patch: .patch}' files_diff.json \ | while read -r fileobj; do filename=$(echo "$fileobj" | jq -r '.filename') @@ -207,54 +209,104 @@ jobs: echo "No added lines found; skipping inline comment creation." echo "[]" > inline_comments.json else - total=0 + rm -f inline_comments.tmp || true touch inline_comments.tmp - while read -r entry && [ "$total" -lt "${MAX_TOTAL_COMMENTS}" ]; do - path=$(echo "$entry" | jq -r '.path') - position=$(echo "$entry" | jq -r '.position') - new_line=$(echo "$entry" | jq -r '.new_line') - content=$(echo "$entry" | jq -r '.content') - system_prompt='You are an expert senior code reviewer. Return a concise JSON: {"comment":"..","severity":"Low|Medium|High","suggested_fix":"..."} or {"comment":"","severity":"Low","suggested_fix":""} if none.' - user_content="File: ${path}\nLine: ${new_line}\nAdded line:\n${content}\n\nProvide a concise production-ready suggestion for this line." - - request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$user_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:180,temperature:0.05}') - response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "Content-Type: application/json" -d "$request") - suggestion=$(echo "$response" | jq -r '.choices[0].message.content // ""' | tr -d '\r') + # iterate per-file, batch up to MAX_COMMENTS_PER_FILE lines in the prompt + for path in $(jq -r '.[].path' added_lines.ndjson | sort -u); do + # collect the candidate lines for this file + mapfile -t entries < <(jq -c --arg p "$path" '.[] | select(.path==$p)' added_lines.ndjson | head -n "${MAX_COMMENTS_PER_FILE}") + + # build a short user_content with numbered lines (skip extremely short/unhelpful lines) + user_content="" + idx=0 + for e in "${entries[@]}"; do + idx=$((idx+1)) + new_line=$(echo "$e" | jq -r '.new_line') + content=$(echo "$e" | jq -r '.content') + # skip lines that are too short or only punctuation (likely YAML keys or noise) + if printf "%s" "$content" | sed 's/[[:space:]]//g' | awk '{print length($0)}' | awk '{ if ($0 < 4) exit 0; else exit 1 }'; then + # length < 4 -> skip + continue + fi + # append numbered entry + user_content="${user_content}${idx}) new_line=${new_line}\n${content}\n\n" + done - if [ -z "$suggestion" ]; then + # if nothing meaningful remained, skip this file + if [ -z "$user_content" ]; then + echo "No meaningful added lines to review for $path; skipping." continue fi - # If model returns valid JSON, build a readable comment; otherwise use raw text - if echo "$suggestion" | jq -e . >/dev/null 2>&1; then - comment_body=$(echo "$suggestion" | jq -r 'if (type=="object") then (("💡 " + (.comment // "" ) + "\n\nSeverity: " + (.severity // "Low") + "\n\nSuggested fix:\n" + (.suggested_fix // ""))) else . end') - else - comment_body=$(printf "💡 %s" "$suggestion") + # system prompt: strict JSON output, only Medium/High severity, max 3 suggestions + system_prompt='You are an expert senior code reviewer. You will be given a small list of added lines (numbered and with their new file line numbers). RETURN ONLY valid JSON: an array of suggestion objects. Each suggestion MUST include keys: new_line (the new file line number exactly as provided), comment (a short actionable one-sentence suggestion), severity (one of "Low","Medium","High"), suggested_fix (a short code snippet or exact change can be empty). CRITICAL: Return at most 3 suggestions. Do NOT return style nits or trivial comments. Only return suggestions with severity MEDIUM or HIGH. If there are no actionable suggestions, return an empty array [].' + + # prepare request + request=$(jq -n --arg model "gpt-4o-mini" --arg system "$system_prompt" --arg user "$user_content" '{model:$model,messages:[{role:"system",content:$system},{role:"user",content:$user}],max_tokens:260,temperature:0.0}') + + # call OpenAI once per file + response=$(curl -s --retry 2 --retry-delay 2 "https://api.openai.com/v1/chat/completions" \ + -H "Content-Type: application/json" -H "Authorization: Bearer ${OPENAI_API_KEY}" -d "$request") + + suggestions=$(echo "$response" | jq -r '.choices[0].message.content // "[]"') + + # ensure suggestions are valid JSON + if ! echo "$suggestions" | jq empty 2>/dev/null; then + echo "Invalid JSON from model for $path. Skipping." + continue fi - # escape newlines/quotes for JSON payload - body_safe=$(printf "%s" "$comment_body" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') - printf '{"path":"%s","position":%s,"side":"RIGHT","body":"%s"}\n' "$path" "$position" "$body_safe" >> inline_comments.tmp + # iterate suggestions, map new_line back to position and build inline comment entries + echo "$suggestions" | jq -c '.[]' | while read -r s; do + severity=$(echo "$s" | jq -r '.severity // "Low"') + # ensure only Medium/High (extra safety) + if [ "$severity" != "Medium" ] && [ "$severity" != "High" ]; then + continue + fi - total=$((total+1)) - done < added_lines.ndjson + new_line=$(echo "$s" | jq -r '.new_line') + comment_text=$(echo "$s" | jq -r '.comment // ""') + suggested_fix=$(echo "$s" | jq -r '.suggested_fix // ""') + + if [ -z "$comment_text" ] || [ "$comment_text" = "null" ]; then + continue + fi + + # find the diff position corresponding to this new_line in added_lines.ndjson + position=$(jq -r --arg p "$path" --arg nl "$new_line" '.[] | select(.path==$p and (.new_line|tostring)==$nl) | .position' added_lines.ndjson | head -n1) + + if [ -z "$position" ]; then + echo "Could not find position for $path new_line=$new_line; skipping." + continue + fi + + # build comment body + body="💡 ${comment_text}\n\nSeverity: ${severity}\n\nSuggested fix:\n${suggested_fix}" + body_safe=$(printf "%s" "$body" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') + + printf '{"path":"%s","position":%s,"side":"RIGHT","body":"%s"}\n' "$path" "$position" "$body_safe" >> inline_comments.tmp + done + done + # limit overall comments and produce final inline_comments.json if [ -s inline_comments.tmp ]; then - jq -s . inline_comments.tmp > inline_comments.json + # take only first MAX_TOTAL_COMMENTS + head -n "${MAX_TOTAL_COMMENTS}" inline_comments.tmp > inline_comments.limited.ndjson + jq -s . inline_comments.limited.ndjson > inline_comments.json || echo "[]" > inline_comments.json else echo "[]" > inline_comments.json fi fi - # Validation: ensure array of objects with required keys + # final validation if ! jq -e 'type == "array" and (.[] | (.path? and .position? and .body?))' inline_comments.json >/dev/null 2>&1; then echo "inline_comments.json failed validation; clearing to []" echo "[]" > inline_comments.json fi - echo "Prepared $(jq 'length' inline_comments.json) inline comments." + echo "Prepared $(jq 'length' inline_comments.json) inline comments (post-filtered)." - name: Create PR Review with Inline Comments if: steps.change_detection.outputs.has_changes == 'true' @@ -320,14 +372,9 @@ jobs: run: | inline_count=$(jq 'length' inline_comments.json 2>/dev/null || echo 0) - # Build comment body using printf to avoid YAML parsing issues - COMMENT_BODY=$(printf "### 🤖 AI Code Review Summary\n\n**Inline Comments Generated:** %s specific suggestions (see 'Files changed').\n\n**Overall Assessment:**\n%s\n\n%s\n### 🧪 AI Test Case Scenarios\n%s\n\n*💡 Tip: Check the 'Files changed' tab to see inline comments on the exact diff positions.*" \ - "$inline_count" \ - "$AI_CODE_REVIEW" \ - "---" \ - "$AI_TEST_SCENARIOS") + COMMENT_BODY=$(printf "### 🤖 AI Code Review Summary\n\n**Inline Comments Generated:** %s specific suggestions (see 'Files changed').\n\n**Overall Assessment:**\n%s\n\n---\n\n### 🧪 AI Test Case Scenarios\n\n%s\n\n*💡 Tip: Check the 'Files changed' tab to see inline comments on the exact diff positions.*" \ + "$inline_count" "$AI_CODE_REVIEW" "$AI_TEST_SCENARIOS") - # Update existing bot comment (if present) else create existing=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq ".[] | select(.user.login==\"github-actions[bot]\") | select(.body|startswith(\"### 🤖 AI Code Review\")) | .id" || true) if [ -n "$existing" ]; then gh api repos/${{ github.repository }}/issues/comments/$existing -X PATCH -F body="$COMMENT_BODY" @@ -382,13 +429,12 @@ jobs: - name: Update PR body if: steps.change_detection.outputs.has_changes == 'true' run: | - # Update PR body with generated PR_DESCRIPTION gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} -X PATCH -F body="$PR_DESCRIPTION" - name: Cleanup temporary files if: always() run: | - rm -f diff.txt files_diff.json combined_diff.json diff_batch_* batch_summaries.txt added_lines.ndjson inline_comments.tmp inline_comments.json || true + rm -f diff.txt files_diff.json combined_diff.json diff_batch_* batch_summaries.txt added_lines.ndjson inline_comments.tmp inline_comments.json inline_comments.limited.ndjson || true - name: Review Summary (logs) if: steps.change_detection.outputs.has_changes == 'true'