diff --git a/.github/workflows/ai-code-reviewer.yml b/.github/workflows/ai-code-reviewer.yml index 2a9d68f..db53033 100644 --- a/.github/workflows/ai-code-reviewer.yml +++ b/.github/workflows/ai-code-reviewer.yml @@ -6,7 +6,7 @@ on: jobs: comprehensive-review: - name: šŸ¤– AI Code Review + name: AI Code Review runs-on: ubuntu-latest if: github.event.action == 'labeled' && github.event.label.name == 'ai_code_review' permissions: @@ -34,9 +34,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - AI_MODEL: ${{ vars.AI_MODEL || 'z-ai/glm-4.6' }} + AI_MODEL: ${{ vars.AI_MODEL || 'moonshotai/kimi-k2-thinking' }} AI_TEMPERATURE: ${{ vars.AI_TEMPERATURE || '0.1' }} - AI_MAX_TOKENS: ${{ vars.AI_MAX_TOKENS || '2000' }} + AI_MAX_TOKENS: ${{ vars.AI_MAX_TOKENS || '64000' }} MAX_DIFF_SIZE: ${{ vars.MAX_DIFF_SIZE || '800000' }} EXCLUDE_FILE_PATTERNS: ${{ vars.EXCLUDE_FILE_PATTERNS || '*.lock,*.min.js,*.min.css,package-lock.json,yarn.lock' }} PR_NUMBER: ${{ github.event.pull_request.number }} @@ -48,10 +48,11 @@ jobs: echo "Running AI code review..." AI_RESPONSE=$(cat diff.txt | bash ai-reviewer.sh) - # Save response for debugging (only in debug mode) + # Output raw response for debugging (only in debug mode) if [ "$DEBUG_MODE" = "true" ]; then - echo "Debug: AI response saved to ai_response.txt" - echo "$AI_RESPONSE" > ai_response.txt + echo "=== RAW AI RESPONSE FOR DEBUG ===" + echo "$AI_RESPONSE" + echo "=== END RAW AI RESPONSE ===" fi # Check if AI_RESPONSE is empty @@ -63,8 +64,26 @@ jobs: # Parse JSON response if ! echo "$AI_RESPONSE" | jq . >/dev/null 2>&1; then echo "āš ļø AI response is not valid JSON. Cannot process review." + + # Log raw response for debugging (redact sensitive info) + echo "=== DEBUG: Raw AI Response (first 2000 chars) ===" + echo "$AI_RESPONSE" | head -c 2000 + echo "" + echo "=== END DEBUG ===" + + # Try to extract JSON from thinking model response + if echo "$AI_RESPONSE" | grep -q '"content"'; then + echo "=== DEBUG: Attempting to extract content from response ===" + EXTRACTED_CONTENT=$(echo "$AI_RESPONSE" | jq -r '.choices[0].message.content // empty' 2>/dev/null || echo "") + if [ -n "$EXTRACTED_CONTENT" ]; then + echo "Extracted content (first 1000 chars):" + echo "$EXTRACTED_CONTENT" | head -c 1000 + echo "" + fi + fi + # Don't post raw response as it may contain sensitive data - echo "AI response could not be processed. Please check the workflow logs." | gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} -F - + echo "AI response could not be processed. Please check the workflow logs for debugging information." | gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} -F - exit 1 fi diff --git a/README.md b/README.md index f498017..84b4826 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ -# Friendly AI Reviewer (FAIR) Setup Guide +# Friendly AI Reviewer Setup Guide - Creates highly-customizable AI Reviews as PR comments. -- Installation: Just 2 files copied to your repo and a open router API Key in your secrets. -- Costs: $0.01 - $0.05 per review (depends highly on model) -- ** Example output ** https://github.com/LearningCircuit/local-deep-research/pull/959#issuecomment-3445396169 +- Installation: Just 2 files copied to your repo and an OpenRouter API Key in your secrets. +- Costs: $0.01 - $0.05 per review (even for large PRs with full context) +- **Example output**: https://github.com/LearningCircuit/local-deep-research/pull/959#issuecomment-3445396169 This guide explains how to set up the automated AI PR review system using OpenRouter to analyze pull requests with your choice of AI model. +## What's New + +**Latest Updates:** +- **Thinking Model Support**: Now supports advanced reasoning models like Kimi K2 that use `` tags +- **Rich Context**: Includes PR descriptions, commit messages, and human comments for comprehensive reviews +- **Higher Token Limits**: Default 64k tokens for complete reviews without truncation +- **Smart Context Management**: Only fetches most recent AI review to save tokens +- **Enhanced Error Handling**: Robust parsing of various AI response formats + ## Overview The AI Code Reviewer provides automated, comprehensive code reviews covering: @@ -45,12 +54,20 @@ The review is posted as a single comprehensive comment on your pull request with ### 3. Configure Workflow (Optional) -The workflow is pre-configured with sensible defaults, but you can customize it by editing `.github/workflows/ai-code-reviewer.yml`: +The workflow is pre-configured with sensible defaults, but you can customize it by setting repository variables in **Settings** → **Secrets and variables** → **Actions** → **Variables**: -- **AI_MODEL**: Change the AI model (see [OpenRouter models](https://openrouter.ai/models)) +- **AI_MODEL**: Change the AI model (default: `moonshotai/kimi-k2-thinking`) + - See [OpenRouter models](https://openrouter.ai/models) for options + - Recommended: Models with reasoning capabilities (Kimi K2, o1, etc.) - **AI_TEMPERATURE**: Adjust randomness (default: `0.1` for consistent reviews) -- **AI_MAX_TOKENS**: Maximum response length (default: `2000`) +- **AI_MAX_TOKENS**: Maximum response length (default: `64000`) + - High limit ensures comprehensive reviews without truncation + - For large PRs with thinking models, this prevents cut-off responses + - Adjust lower for cost savings on smaller PRs - **MAX_DIFF_SIZE**: Maximum diff size in bytes (default: `800000` / 800KB) +- **DEBUG_MODE**: Enable debug logging (default: `false`) + - āš ļø Warning: Exposes code diff in workflow logs when enabled + - Only enable temporarily for troubleshooting ## Usage @@ -79,11 +96,31 @@ The AI posts a comprehensive comment analyzing your code across all focus areas. ## Cost Estimation -Costs vary by model, but most code-focused models on OpenRouter are very affordable: -- Typical small PR (< 1000 lines): $0.001 - $0.01 -- Large PR (1000-5000 lines): $0.01 - $0.05 +Costs with the default Kimi K2 thinking model are very affordable. Based on real usage data: + +**Typical Costs:** +- Small PR (< 1000 lines): $0.01 - $0.02 +- Medium PR (1000-3000 lines): $0.02 - $0.04 +- Large PR (3000+ lines): $0.04 - $0.06 + +**Example from a 20-commit PR with full context:** +- Input: ~5,000-9,000 tokens (diff + PR description + commits + human comments) +- Output: ~2,000-6,000 tokens (comprehensive review) +- **Total cost: $0.01 - $0.05 per review** + +**Why So Affordable:** +- Kimi K2 has competitive pricing (~$0.001-$0.003 per 1k tokens) +- Smart context management (only most recent AI review, limited commit history) +- Most PRs are smaller than you think in token count +- The 64k token limit is a ceiling, not typical usage + +**Cost varies based on:** +- PR size (larger diffs = more input tokens) +- Review complexity (detailed reviews = more output tokens) +- Number of human comments and commit messages included +- OpenRouter provider routing (prices vary slightly by provider) -Check [OpenRouter pricing](https://openrouter.ai/models) for specific model costs. +Check [OpenRouter pricing](https://openrouter.ai/models) for current Kimi K2 rates. ## Customization @@ -138,11 +175,14 @@ If you get a "Diff is too large" error: The workflow fetches and sends these repository elements to the AI: 1. **Code Changes**: Full diff of modified files -2. **Labels**: All repository labels with descriptions and colors -3. **Previous Comments**: All previous AI review comments from the PR -4. **CI/CD Status**: GitHub Actions check runs and build statuses -5. **PR Metadata**: Pull request details, head SHA, repository information -6. **Files**: May include sensitive configuration files, keys, or credentials +2. **PR Description**: Title and description text from the pull request +3. **Commit Messages**: Up to 15 most recent commit messages (excluding merges) +4. **Human Comments**: All comments from human reviewers on the PR +5. **Labels**: All repository labels with descriptions and colors +6. **Previous AI Review**: Most recent AI review comment only (limited to 10k chars) +7. **CI/CD Status**: GitHub Actions check runs and build statuses +8. **PR Metadata**: Pull request details, head SHA, repository information +9. **Files**: May include sensitive configuration files, keys, or credentials ### Recommended Mitigations diff --git a/ai-reviewer.sh b/ai-reviewer.sh index 765d664..cb811b9 100644 --- a/ai-reviewer.sh +++ b/ai-reviewer.sh @@ -5,21 +5,30 @@ set -e +# Constants +REVIEW_HEADER="## AI Code Review" +REVIEW_FOOTER="---\n*Review by [Friendly AI Reviewer](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - made with ā¤ļø*" + +# Helper function to generate error response JSON +generate_error_response() { + local error_msg="$1" + echo "{\"review\":\"$REVIEW_HEADER\n\nāŒ **Error**: $error_msg\n\n$REVIEW_FOOTER\",\"fail_pass_workflow\":\"uncertain\",\"labels_added\":[]}" +} # Get API key from environment variable API_KEY="${OPENROUTER_API_KEY}" if [ -z "$API_KEY" ]; then - echo "## šŸ¤– AI Code Review + echo "$REVIEW_HEADER āŒ **Error**: Missing OPENROUTER_API_KEY environment variable" exit 1 fi # Configuration with defaults -AI_MODEL="${AI_MODEL:-z-ai/glm-4.6}" +AI_MODEL="${AI_MODEL:-moonshotai/kimi-k2-thinking}" AI_TEMPERATURE="${AI_TEMPERATURE:-0.1}" -AI_MAX_TOKENS="${AI_MAX_TOKENS:-2000}" +AI_MAX_TOKENS="${AI_MAX_TOKENS:-64000}" MAX_DIFF_SIZE="${MAX_DIFF_SIZE:-800000}" # 800KB default limit (~200K tokens, matching model context size) EXCLUDE_FILE_PATTERNS="${EXCLUDE_FILE_PATTERNS:-*.lock,*.min.js,*.min.css,package-lock.json,yarn.lock}" @@ -27,7 +36,7 @@ EXCLUDE_FILE_PATTERNS="${EXCLUDE_FILE_PATTERNS:-*.lock,*.min.js,*.min.css,packag DIFF_CONTENT=$(cat) if [ -z "$DIFF_CONTENT" ]; then - echo "## šŸ¤– AI Code Review + echo "$REVIEW_HEADER āŒ **Error**: No diff content to analyze" exit 1 @@ -46,19 +55,27 @@ fi # Validate diff size to prevent excessive API usage DIFF_SIZE=${#DIFF_CONTENT} if [ "$DIFF_SIZE" -gt "$MAX_DIFF_SIZE" ]; then - echo "## šŸ¤– AI Code Review + echo "$REVIEW_HEADER āŒ **Error**: Diff is too large ($DIFF_SIZE bytes, max: $MAX_DIFF_SIZE bytes) Please split this PR into smaller changes for review." exit 1 fi -# Fetch previous AI review comments for context (if PR_NUMBER and REPO_FULL_NAME are set) +# Fetch previous AI review (only the most recent one) for context PREVIOUS_REVIEWS="" if [ -n "$PR_NUMBER" ] && [ -n "$REPO_FULL_NAME" ] && [ -n "$GITHUB_TOKEN" ]; then - # Fetch comments that start with "## šŸ¤– AI Code Review" + # Fetch only the most recent AI review comment PREVIOUS_REVIEWS=$(gh api "repos/$REPO_FULL_NAME/issues/$PR_NUMBER/comments" \ - --jq '.[] | select(.body | startswith("## šŸ¤– AI Code Review")) | "### Previous Review (" + .created_at + "):\n" + .body + "\n---\n"' 2>/dev/null | head -c 50000 || echo "") + --jq '[.[] | select(.body | startswith("## AI Code Review"))] | last | if . then "### Previous AI Review (" + .created_at + "):\n" + .body + "\n---\n" else "" end' 2>/dev/null | head -c 10000 || echo "") +fi + +# Fetch human comments for context +HUMAN_COMMENTS="" +if [ -n "$PR_NUMBER" ] && [ -n "$REPO_FULL_NAME" ] && [ -n "$GITHUB_TOKEN" ]; then + # Fetch comments from humans (not the bot) + HUMAN_COMMENTS=$(gh api "repos/$REPO_FULL_NAME/issues/$PR_NUMBER/comments" \ + --jq '[.[] | select(.body | startswith("## AI Code Review") | not)] | map("**" + .user.login + "** (" + .created_at + "):\n" + .body) | join("\n\n---\n\n")' 2>/dev/null | head -c 20000 || echo "") fi # Fetch GitHub Actions check runs status (if PR_NUMBER and REPO_FULL_NAME are set) @@ -90,19 +107,42 @@ if [ -n "$PR_NUMBER" ] && [ -n "$REPO_FULL_NAME" ] && [ -n "$GITHUB_TOKEN" ]; th fi fi +# Fetch PR title and description +PR_DESCRIPTION="" +if [ -n "$PR_NUMBER" ] && [ -n "$REPO_FULL_NAME" ] && [ -n "$GITHUB_TOKEN" ]; then + echo "šŸ” Fetching PR title and description..." >&2 + PR_DESCRIPTION=$(gh api "repos/$REPO_FULL_NAME/pulls/$PR_NUMBER" \ + --jq '"**PR Title**: " + .title + "\n\n**Description**:\n" + (.body // "No description provided")' 2>/dev/null | head -c 2000 || echo "") + + if [ -n "$PR_DESCRIPTION" ]; then + echo "āœ… Successfully fetched PR description" >&2 + fi +fi + +# Fetch commit messages (limit to 15 most recent, exclude merges) +COMMIT_MESSAGES="" +if [ -n "$PR_NUMBER" ] && [ -n "$REPO_FULL_NAME" ] && [ -n "$GITHUB_TOKEN" ]; then + echo "šŸ” Fetching commit messages..." >&2 + COMMIT_MESSAGES=$(gh api "repos/$REPO_FULL_NAME/pulls/$PR_NUMBER/commits" --paginate \ + --jq '[.[] | select(.commit.message | startswith("Merge") | not)] | .[-15:] | .[] | "- " + (.commit.message | split("\n")[0]) + (if (.commit.message | split("\n\n")[1]) then "\n " + (.commit.message | split("\n\n")[1]) else "" end)' 2>/dev/null | head -c 2500 || echo "") + + if [ -n "$COMMIT_MESSAGES" ]; then + COMMIT_COUNT=$(echo "$COMMIT_MESSAGES" | grep -c "^- " || echo "0") + echo "āœ… Successfully fetched $COMMIT_COUNT commit messages" >&2 + fi +fi + # Create the JSON request with proper escaping using jq # Write diff to temporary file to avoid "Argument list too long" error DIFF_FILE=$(mktemp) echo "$DIFF_CONTENT" > "$DIFF_FILE" # Build the user prompt using the diff file -PROMPT_PREFIX="Please analyze this code diff and provide a comprehensive review in markdown format: +PROMPT_PREFIX="Please analyze this code diff and provide a comprehensive review in markdown format. + +Focus on security, performance, code quality, and best practices. -Focus Areas: -- Security: Look for hardcoded secrets, SQL injection, XSS, authentication issues, input validation problems -- Performance: Check for inefficient algorithms, N+1 queries, missing indexes, memory issues, blocking operations -- Code Quality: Evaluate readability, maintainability, proper error handling, naming conventions, documentation -- Best Practices: Ensure adherence to coding standards, proper patterns, type safety, dead code removal +Keep the review scannable and grouped by importance. Lead with critical issues if any exist. " # Add GitHub Actions check status if available @@ -126,10 +166,40 @@ If none of these labels are appropriate for the changes, you may suggest new one " fi -# Add previous reviews context if available +# Add PR description if available +if [ -n "$PR_DESCRIPTION" ]; then + PROMPT_PREFIX="${PROMPT_PREFIX} +Pull Request Context: +$PR_DESCRIPTION + +" +fi + +# Add commit messages if available +if [ -n "$COMMIT_MESSAGES" ]; then + PROMPT_PREFIX="${PROMPT_PREFIX} +Commit History (showing development journey): +$COMMIT_MESSAGES + +Please consider the commit history to understand what was tried, what issues were discovered, and how the solution evolved. + +" +fi + +# Add human comments context if available +if [ -n "$HUMAN_COMMENTS" ]; then + PROMPT_PREFIX="${PROMPT_PREFIX} +Human Comments on this PR: +$HUMAN_COMMENTS + +Please consider these human comments when reviewing the code. +" +fi + +# Add previous AI review context if available (only most recent) if [ -n "$PREVIOUS_REVIEWS" ]; then PROMPT_PREFIX="${PROMPT_PREFIX} -Previous AI Reviews (for context on what was already reviewed): +Previous AI Review (for context on what was already reviewed): $PREVIOUS_REVIEWS " fi @@ -143,29 +213,24 @@ Code diff to analyze: # Read diff content DIFF_CONTENT=$(cat "$DIFF_FILE") -# Simple text prompt -PROMPT="Please analyze this code diff and provide a comprehensive review. +# Simple text prompt requesting JSON response +PROMPT="You are an expert code reviewer. Please analyze this code diff and provide a comprehensive review. Focus on security, performance, code quality, and best practices. -IMPORTANT: Respond with valid JSON only using this exact format: +Focus on high-value issues. Style suggestions are welcome if impactful, but not minor optimizations. Be concise and dense - use bullet points for clear structure. Avoid repetition - in summary sections, only repeat critical issues (security, bugs, breaking changes). If flagging issues about code not visible in the diff, clearly state what you're assuming and why. For non-critical improvements, consider approving with recommendations rather than requesting changes. + +Required JSON format: { - \"review\": \"Detailed review in markdown format\", + \"review\": \"## AI Code Review\\n\\n[Your detailed review in markdown format]\\n\\n---\\n*Review by [Friendly AI Reviewer](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - made with ā¤ļø*\", \"fail_pass_workflow\": \"pass\", \"labels_added\": [\"bug\", \"feature\", \"enhancement\"] } -For the labels_added field: -- First check if any existing repository labels (listed above) are appropriate -- Prefer existing labels over creating new ones when possible -- Only suggest new labels when no existing ones fit the changes -- Keep labels concise and descriptive - -Focus action items on critical fixes only, not trivial nitpicks. - -IMPORTANT: End your review with a clear final assessment section like: ---- -## Final Assessment: APPROVED / CHANGES REQUESTED / NEEDS REVISION +Instructions: +1. Respond with a single valid JSON object +2. Include the Friendly AI Reviewer footer with heart emoji at the end of the review field +3. For labels_added, prefer existing repository labels when possible Code to review: $PROMPT_PREFIX @@ -197,30 +262,52 @@ RESPONSE=$(curl -s -X POST "https://openrouter.ai/api/v1/chat/completions" \ # Check if API call was successful if [ -z "$RESPONSE" ]; then - echo '{"review":"## šŸ¤– AI Code Review\n\nāŒ **Error**: API call failed - no response received","fail_pass_workflow":"uncertain","labels_added":[]}' + generate_error_response "API call failed - no response received" exit 1 fi # Check if response is valid JSON if ! echo "$RESPONSE" | jq . >/dev/null 2>&1; then - echo '{"review":"## šŸ¤– AI Code Review\n\nāŒ **Error**: Invalid JSON response from API","fail_pass_workflow":"uncertain","labels_added":[]}' + echo "=== API DEBUG: Raw response from $AI_MODEL ===" >&2 + echo "$RESPONSE" >&2 + echo "=== END API DEBUG ===" >&2 + generate_error_response "Invalid JSON response from API" exit 1 fi +# Log the API response structure for debugging thinking models (if debug mode enabled) +if [ "$DEBUG_MODE" = "true" ]; then + echo "=== API STRUCTURE DEBUG from $AI_MODEL ===" >&2 + echo "Response keys: $(echo "$RESPONSE" | jq -r 'keys | join(", ")')" >&2 + echo "Choices count: $(echo "$RESPONSE" | jq '.choices | length')" >&2 + echo "First choice keys: $(echo "$RESPONSE" | jq -r '.choices[0] | keys | join(", ")')" >&2 + echo "Content type: $(echo "$RESPONSE" | jq -r '.choices[0].message | type')" >&2 + echo "=== END API STRUCTURE DEBUG ===" >&2 +fi + # Extract the content CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "error"') +# Log the extracted content from thinking model (if debug mode enabled) +if [ "$DEBUG_MODE" = "true" ]; then + echo "=== CONTENT DEBUG: Extracted from $AI_MODEL ===" >&2 + echo "Content length: $(echo "$CONTENT" | wc -c)" >&2 + echo "Full content:" >&2 + echo "$CONTENT" >&2 + echo "=== END CONTENT DEBUG ===" >&2 +fi + if [ "$CONTENT" = "error" ]; then # Try to extract error details from the API response ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error.message // "Invalid API response format"') ERROR_CODE=$(echo "$RESPONSE" | jq -r '.error.code // ""') # Return error as JSON - ERROR_CONTENT="## šŸ¤– AI Code Review\n\nāŒ **Error**: $ERROR_MSG" + ERROR_CONTENT="$REVIEW_HEADER\n\nāŒ **Error**: $ERROR_MSG" if [ -n "$ERROR_CODE" ]; then ERROR_CONTENT="$ERROR_CONTENT\n\nError code: \`$ERROR_CODE\`" fi - ERROR_CONTENT="$ERROR_CONTENT\n\n---\n*Review by [FAIR](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - needs human verification*" + ERROR_CONTENT="$ERROR_CONTENT\n\n$REVIEW_FOOTER" echo "{\"review\":\"$ERROR_CONTENT\",\"fail_pass_workflow\":\"uncertain\",\"labels_added\":[]}" @@ -234,22 +321,58 @@ fi # Ensure CONTENT is not empty if [ -z "$CONTENT" ]; then - echo '{"review":"## šŸ¤– AI Code Review\n\nāŒ **Error**: AI returned empty response","fail_pass_workflow":"uncertain","labels_added":[]}' + generate_error_response "AI returned empty response" + exit 0 +fi + +# Remove thinking tags and content - everything between and +# Use perl for proper multiline and inline handling +CONTENT=$(echo "$CONTENT" | perl -0pe 's/.*?<\/thinking>\s*//gs') + +# Remove markdown code blocks if present (check for actual backticks at line start) +if echo "$CONTENT" | grep -qE '^\s*```json'; then + if [ "$DEBUG_MODE" = "true" ]; then + echo "=== REMOVING MARKDOWN CODE BLOCKS ===" >&2 + fi + # Remove the opening ```json and closing ``` lines, keep the content + CONTENT=$(echo "$CONTENT" | perl -0pe 's/^\s*```json\s*\n//g; s/\n```\s*$//g') +fi + +# Trim leading and trailing whitespace +CONTENT=$(echo "$CONTENT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + +# Enhanced empty check (catches whitespace-only content) +if [ -z "$CONTENT" ] || [ -z "$(echo "$CONTENT" | tr -d '[:space:]')" ]; then + generate_error_response "AI returned empty response after processing" exit 0 fi # Validate that CONTENT is valid JSON if ! echo "$CONTENT" | jq . >/dev/null 2>&1; then - # If not JSON, wrap it in JSON structure - JSON_CONTENT="{\"review\":\"## šŸ¤– AI Code Review\n\n$CONTENT\n\n---\n*Review by [FAIR](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - needs human verification*\",\"fail_pass_workflow\":\"uncertain\",\"labels_added\":[]}" - echo "$JSON_CONTENT" + if [ "$DEBUG_MODE" = "true" ]; then + echo "=== JSON VALIDATION FAILED ===" >&2 + echo "Content is not valid JSON" >&2 + echo "=== RAW CONTENT FOR DEBUG ===" >&2 + echo "$CONTENT" >&2 + echo "=== END DEBUG ===" >&2 + fi + + # Fallback to error response + generate_error_response "Invalid JSON response from AI model" else - # If already JSON, validate it has the required structure + if [ "$DEBUG_MODE" = "true" ]; then + echo "=== CONTENT IS VALID JSON ===" >&2 + fi + # Validate it has the required structure if ! echo "$CONTENT" | jq -e '.review' >/dev/null 2>&1; then - JSON_CONTENT="{\"review\":\"## šŸ¤– AI Code Review\n\n$CONTENT\n\n---\n*Review by [FAIR](https://github.com/LearningCircuit/Friendly-AI-Reviewer) - needs human verification*\",\"fail_pass_workflow\":\"uncertain\",\"labels_added\":[]}" - echo "$JSON_CONTENT" + if [ "$DEBUG_MODE" = "true" ]; then + echo "JSON missing required 'review' field" >&2 + fi + generate_error_response "AI response missing required review field" else - # If already valid JSON with required structure, return as-is + if [ "$DEBUG_MODE" = "true" ]; then + echo "JSON has required structure, using as-is" >&2 + fi echo "$CONTENT" fi fi