Review: PR #4329 #4364
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Impl: Review" | |
| run-name: "Review: PR #${{ inputs.pr_number || github.event.client_payload.pr_number }}" | |
| # AI quality review for implementation PRs | |
| # Triggered by impl-generate.yml after PR creation | |
| # Last updated: 2025-12-23 | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to review' | |
| required: true | |
| type: string | |
| repository_dispatch: | |
| types: [review-pr] | |
| jobs: | |
| review: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # Needed for pushing quality score to PR branch | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| actions: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract PR info | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ inputs.pr_number || github.event.client_payload.pr_number }} | |
| run: | | |
| PR_DATA=$(gh pr view "$PR_NUMBER" --json headRefName,headRefOid,body) | |
| HEAD_REF=$(echo "$PR_DATA" | jq -r '.headRefName') | |
| HEAD_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid') | |
| BODY=$(echo "$PR_DATA" | jq -r '.body') | |
| # Extract spec-id and library from branch: implementation/{spec-id}/{library} | |
| SPEC_ID=$(echo "$HEAD_REF" | cut -d'/' -f2) | |
| LIBRARY=$(echo "$HEAD_REF" | cut -d'/' -f3) | |
| # Extract issue number from PR body | |
| ISSUE_NUMBER=$(echo "$BODY" | grep -oP '\*\*Parent Issue:\*\* #\K\d+' | head -1 || echo "") | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "specification_id=$SPEC_ID" >> $GITHUB_OUTPUT | |
| echo "library=$LIBRARY" >> $GITHUB_OUTPUT | |
| echo "branch=$HEAD_REF" >> $GITHUB_OUTPUT | |
| echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT | |
| echo "issue_number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT | |
| echo "::notice::Reviewing PR #$PR_NUMBER for $LIBRARY implementation of $SPEC_ID (branch: $HEAD_REF)" | |
| - name: Checkout PR code | |
| run: | | |
| git fetch origin ${{ steps.pr.outputs.head_sha }} | |
| git checkout ${{ steps.pr.outputs.head_sha }} | |
| - name: Check attempt count | |
| id: attempts | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.pr.outputs.pr_number }} | |
| run: | | |
| LABELS=$(gh pr view "$PR_NUMBER" --json labels -q '.labels[].name' 2>/dev/null || echo "") | |
| if echo "$LABELS" | grep -q "ai-attempt-3"; then | |
| echo "count=3" >> $GITHUB_OUTPUT | |
| echo "display=3" >> $GITHUB_OUTPUT | |
| elif echo "$LABELS" | grep -q "ai-attempt-2"; then | |
| echo "count=2" >> $GITHUB_OUTPUT | |
| echo "display=3" >> $GITHUB_OUTPUT | |
| elif echo "$LABELS" | grep -q "ai-attempt-1"; then | |
| echo "count=1" >> $GITHUB_OUTPUT | |
| echo "display=2" >> $GITHUB_OUTPUT | |
| else | |
| echo "count=0" >> $GITHUB_OUTPUT | |
| echo "display=1" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup GCS authentication | |
| id: gcs | |
| continue-on-error: true | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| credentials_json: ${{ secrets.GCS_CREDENTIALS }} | |
| - name: Setup gcloud CLI | |
| if: steps.gcs.outcome == 'success' | |
| uses: google-github-actions/setup-gcloud@v3 | |
| - name: Download plot images from staging | |
| if: steps.gcs.outcome == 'success' | |
| env: | |
| SPEC_ID: ${{ steps.pr.outputs.specification_id }} | |
| LIBRARY: ${{ steps.pr.outputs.library }} | |
| run: | | |
| mkdir -p plot_images | |
| gsutil -m cp "gs://pyplots-images/staging/${SPEC_ID}/${LIBRARY}/*" plot_images/ 2>/dev/null || true | |
| ls -la plot_images/ | |
| - name: Verify plot image exists | |
| run: | | |
| if [ ! -f "plot_images/plot.png" ]; then | |
| echo "::error::No plot.png found in staging - cannot review without image" | |
| echo "::error::The impl-generate workflow may have failed to produce an image" | |
| exit 1 | |
| fi | |
| echo "::notice::Found plot.png for review" | |
| - name: React with eyes emoji | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.pr.outputs.pr_number }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/reactions" -f content=eyes | |
| - name: Run AI Quality Review | |
| id: review | |
| continue-on-error: true | |
| timeout-minutes: 30 | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| claude_args: "--model opus" | |
| allowed_bots: '*' | |
| prompt: | | |
| Read `prompts/workflow-prompts/ai-quality-review.md` and follow those instructions. | |
| Variables for this run: | |
| - LIBRARY: ${{ steps.pr.outputs.library }} | |
| - SPEC_ID: ${{ steps.pr.outputs.specification_id }} | |
| - PR_NUMBER: ${{ steps.pr.outputs.pr_number }} | |
| - ATTEMPT: ${{ steps.attempts.outputs.display }} | |
| - name: Extract quality score | |
| id: score | |
| if: steps.review.conclusion == 'success' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| run: | | |
| if [ -f "quality_score.txt" ]; then | |
| SCORE=$(cat quality_score.txt | tr -d '[:space:]') | |
| else | |
| SCORE=$(gh pr view "$PR_NUM" --json comments -q '.comments[-1].body' | grep -oP 'Score: \K\d+' | head -1 || echo "0") | |
| fi | |
| # Validate score is a number between 1-100, default to 0 if invalid | |
| if ! [[ "$SCORE" =~ ^[0-9]+$ ]] || [ "$SCORE" -lt 1 ] || [ "$SCORE" -gt 100 ]; then | |
| echo "::warning::Invalid quality score '$SCORE', defaulting to 0" | |
| SCORE="0" | |
| fi | |
| echo "score=$SCORE" >> $GITHUB_OUTPUT | |
| - name: Validate review output | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score == '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| run: | | |
| echo "::error::AI Review did not produce valid output files" | |
| echo "::error::Expected quality_score.txt but got score=0" | |
| echo "::error::This indicates Claude Code Action ran but didn't complete the review task" | |
| # Add ai-review-failed label so it's visible | |
| gh pr edit "$PR_NUM" --add-label "ai-review-failed" 2>/dev/null || true | |
| # Post error comment on PR | |
| gh pr comment "$PR_NUM" --body "## :x: AI Review Failed | |
| The AI review action completed but did not produce valid output files. | |
| **What happened:** | |
| - The Claude Code Action ran | |
| - No \`quality_score.txt\` file was created | |
| - No review data was extracted | |
| **Action required:** | |
| Re-run the impl-review workflow manually: | |
| \`\`\` | |
| gh workflow run impl-review.yml -f pr_number=$PR_NUM | |
| \`\`\` | |
| --- | |
| :robot: *[impl-review](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| exit 1 | |
| - name: Add quality score label | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score != '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| SCORE: ${{ steps.score.outputs.score }} | |
| run: | | |
| LABEL="quality:${SCORE}" | |
| gh label create "$LABEL" --color "0e8a16" --description "Quality score ${SCORE}/100" 2>/dev/null || true | |
| gh pr edit "$PR_NUM" --add-label "$LABEL" | |
| - name: Add preliminary verdict label (early) | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score != '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| SCORE: ${{ steps.score.outputs.score }} | |
| ATTEMPT_COUNT: ${{ steps.attempts.outputs.count }} | |
| run: | | |
| # Add verdict label early to ensure it's set even if later steps fail | |
| # This is idempotent - re-running won't cause issues | |
| if [ "$SCORE" -ge 90 ]; then | |
| echo "::notice::Adding ai-approved label (score >= 90)" | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" 2>/dev/null || { | |
| echo "::warning::Failed to add ai-approved label, retrying..." | |
| sleep 2 | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" | |
| } | |
| elif [ "$ATTEMPT_COUNT" -eq 3 ] && [ "$SCORE" -ge 50 ]; then | |
| echo "::notice::Adding ai-approved label (attempt 4, score >= 50)" | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" 2>/dev/null || { | |
| echo "::warning::Failed to add ai-approved label, retrying..." | |
| sleep 2 | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" | |
| } | |
| else | |
| echo "::notice::Adding ai-rejected label (score < 90)" | |
| gh pr edit "$PR_NUM" --add-label "ai-rejected" 2>/dev/null || { | |
| echo "::warning::Failed to add ai-rejected label, retrying..." | |
| sleep 2 | |
| gh pr edit "$PR_NUM" --add-label "ai-rejected" | |
| } | |
| if [ "$SCORE" -lt 50 ]; then | |
| gh pr edit "$PR_NUM" --add-label "quality-poor" 2>/dev/null || true | |
| fi | |
| fi | |
| - name: Install Python dependencies for metadata update | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score != '0' | |
| run: pip install pyyaml | |
| - name: Update metadata and implementation header | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score != '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SPEC_ID: ${{ steps.pr.outputs.specification_id }} | |
| LIBRARY: ${{ steps.pr.outputs.library }} | |
| SCORE: ${{ steps.score.outputs.score }} | |
| BRANCH: ${{ steps.pr.outputs.branch }} | |
| run: | | |
| METADATA_FILE="plots/${SPEC_ID}/metadata/${LIBRARY}.yaml" | |
| IMPL_FILE="plots/${SPEC_ID}/implementations/${LIBRARY}.py" | |
| # Configure git auth and checkout the PR branch | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" | |
| git fetch origin "$BRANCH" | |
| git checkout -B "$BRANCH" "origin/$BRANCH" | |
| # Update metadata file with quality score, timestamp, and review feedback | |
| if [ -f "$METADATA_FILE" ]; then | |
| TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| # Write Python script to temp file to avoid YAML/shell escaping issues | |
| cat > /tmp/update_metadata.py << 'EOF' | |
| import yaml | |
| import json | |
| import sys | |
| from pathlib import Path | |
| metadata_file = sys.argv[1] | |
| score = int(sys.argv[2]) | |
| timestamp = sys.argv[3] | |
| # Read existing review data files | |
| strengths = [] | |
| weaknesses = [] | |
| image_description = None | |
| criteria_checklist = None | |
| verdict = None | |
| impl_tags = None | |
| if Path('review_strengths.json').exists(): | |
| try: | |
| with open('review_strengths.json') as f: | |
| strengths = json.load(f) | |
| except: | |
| pass | |
| if Path('review_weaknesses.json').exists(): | |
| try: | |
| with open('review_weaknesses.json') as f: | |
| weaknesses = json.load(f) | |
| except: | |
| pass | |
| if Path('review_image_description.txt').exists(): | |
| try: | |
| with open('review_image_description.txt') as f: | |
| image_description = f.read().strip() | |
| except: | |
| pass | |
| if Path('review_checklist.json').exists(): | |
| try: | |
| with open('review_checklist.json') as f: | |
| criteria_checklist = json.load(f) | |
| except: | |
| pass | |
| if Path('review_verdict.txt').exists(): | |
| try: | |
| with open('review_verdict.txt') as f: | |
| verdict = f.read().strip() | |
| except: | |
| pass | |
| if Path('review_impl_tags.json').exists(): | |
| try: | |
| with open('review_impl_tags.json') as f: | |
| impl_tags = json.load(f) | |
| except: | |
| pass | |
| # Load existing metadata | |
| with open(metadata_file, 'r') as f: | |
| data = yaml.safe_load(f) | |
| data['quality_score'] = score | |
| data['updated'] = timestamp | |
| if 'review' not in data: | |
| data['review'] = {} | |
| # Update review section with all fields | |
| data['review']['strengths'] = strengths | |
| data['review']['weaknesses'] = weaknesses | |
| # Add extended review data (issue #2845) | |
| if image_description: | |
| data['review']['image_description'] = image_description | |
| if criteria_checklist: | |
| data['review']['criteria_checklist'] = criteria_checklist | |
| if verdict: | |
| data['review']['verdict'] = verdict | |
| # Add impl_tags (issue #2434) | |
| if impl_tags: | |
| data['impl_tags'] = impl_tags | |
| def str_representer(dumper, data): | |
| if isinstance(data, str) and data.endswith('Z') and 'T' in data: | |
| return dumper.represent_scalar('tag:yaml.org,2002:str', data, style="'") | |
| # Use literal block style for multi-line strings (image_description) | |
| if isinstance(data, str) and '\n' in data: | |
| return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') | |
| return dumper.represent_scalar('tag:yaml.org,2002:str', data) | |
| yaml.add_representer(str, str_representer) | |
| with open(metadata_file, 'w') as f: | |
| yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) | |
| EOF | |
| python3 /tmp/update_metadata.py "$METADATA_FILE" "$SCORE" "$TIMESTAMP" | |
| echo "::notice::Updated metadata with quality score ${SCORE} and extended review data" | |
| fi | |
| # Update implementation header with quality score | |
| if [ -f "$IMPL_FILE" ]; then | |
| # Get library and python versions from metadata | |
| LIBRARY_VERSION=$(python3 -c "import yaml; print(yaml.safe_load(open('$METADATA_FILE'))['library_version'])" 2>/dev/null || echo "unknown") | |
| PYTHON_VERSION=$(python3 -c "import yaml; print(yaml.safe_load(open('$METADATA_FILE'))['python_version'])" 2>/dev/null || echo "3.13") | |
| DATE_INFO=$(python3 -c " | |
| import yaml | |
| data = yaml.safe_load(open('$METADATA_FILE')) | |
| created = data['created'][:10] | |
| updated = data.get('updated', '')[:10] if data.get('updated') else '' | |
| if updated and updated != created: | |
| print(f'Updated: {updated}') | |
| else: | |
| print(f'Created: {created}') | |
| " 2>/dev/null || echo "Created: $(date +%Y-%m-%d)") | |
| # Get title from specification.yaml | |
| TITLE=$(python3 -c "import yaml; print(yaml.safe_load(open('plots/${SPEC_ID}/specification.yaml'))['title'])" 2>/dev/null || echo "${SPEC_ID}") | |
| # Replace old header using Python | |
| python3 -c " | |
| import re | |
| impl_file = '$IMPL_FILE' | |
| spec_id = '${SPEC_ID}' | |
| title = '''${TITLE}''' | |
| library = '${LIBRARY}' | |
| lib_version = '${LIBRARY_VERSION}' | |
| py_version = '${PYTHON_VERSION}' | |
| score = '${SCORE}' | |
| date_info = '${DATE_INFO}' | |
| new_header = f'''\"\"\" pyplots.ai | |
| {spec_id}: {title} | |
| Library: {library} {lib_version} | Python {py_version} | |
| Quality: {score}/100 | {date_info} | |
| \"\"\"''' | |
| with open(impl_file, 'r') as f: | |
| content = f.read() | |
| pattern = r'^\"\"\".*?\"\"\"' | |
| new_content = re.sub(pattern, new_header, content, count=1, flags=re.DOTALL) | |
| with open(impl_file, 'w') as f: | |
| f.write(new_content) | |
| " | |
| echo "::notice::Updated implementation header with quality score ${SCORE}" | |
| fi | |
| # Commit and push | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add "$METADATA_FILE" "$IMPL_FILE" | |
| if ! git diff --cached --quiet; then | |
| git commit -m "chore(${LIBRARY}): update quality score ${SCORE} and review feedback for ${SPEC_ID}" | |
| git push origin "$BRANCH" | |
| echo "::notice::Changes committed to ${BRANCH}" | |
| fi | |
| - name: Handle review failure | |
| if: steps.attempts.outputs.count != '3' && steps.review.conclusion == 'failure' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| run: | | |
| gh pr edit "$PR_NUM" --add-label "ai-review-failed" | |
| gh pr comment "$PR_NUM" --body "## :warning: AI Review Failed | |
| The AI review action failed or timed out. | |
| **Options:** | |
| 1. Re-run the workflow manually | |
| 2. Request manual human review | |
| --- | |
| :robot: *[impl-review](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| - name: Add verdict label and take action | |
| if: steps.review.conclusion == 'success' && steps.score.outputs.score != '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUM: ${{ steps.pr.outputs.pr_number }} | |
| SPEC_ID: ${{ steps.pr.outputs.specification_id }} | |
| LIBRARY: ${{ steps.pr.outputs.library }} | |
| SCORE: ${{ steps.score.outputs.score }} | |
| ATTEMPT: ${{ steps.attempts.outputs.display }} | |
| ATTEMPT_COUNT: ${{ steps.attempts.outputs.count }} | |
| ISSUE_NUMBER: ${{ steps.pr.outputs.issue_number }} | |
| run: | | |
| # Score thresholds: | |
| # >= 90: Excellent (approved, merge immediately) | |
| # < 90: Rejected (repair loop, up to 3 attempts) | |
| # After 3 attempts: >= 50 = merge, < 50 = close PR | |
| # Check if verdict label was already added by earlier step | |
| CURRENT_LABELS=$(gh pr view "$PR_NUM" --json labels -q '.labels[].name' 2>/dev/null || echo "") | |
| if echo "$CURRENT_LABELS" | grep -q "ai-approved"; then | |
| echo "::notice::ai-approved label already set, triggering merge" | |
| gh workflow run impl-merge.yml -f pr_number="$PR_NUM" | |
| exit 0 | |
| fi | |
| if echo "$CURRENT_LABELS" | grep -q "ai-rejected"; then | |
| echo "::notice::ai-rejected label already set, triggering repair" | |
| gh pr edit "$PR_NUM" --add-label "ai-attempt-${ATTEMPT}" 2>/dev/null || true | |
| gh workflow run impl-repair.yml \ | |
| -f pr_number="$PR_NUM" \ | |
| -f specification_id="$SPEC_ID" \ | |
| -f library="$LIBRARY" \ | |
| -f attempt="$ATTEMPT" | |
| exit 0 | |
| fi | |
| # Fallback: Add verdict labels if not already set (shouldn't happen but ensures robustness) | |
| if [ "$SCORE" -ge 90 ]; then | |
| # Approved - merge immediately | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" 2>/dev/null || true | |
| echo "::notice::Added ai-approved label (score $SCORE >= 90)" | |
| echo "Triggering impl-merge.yml for approved PR" | |
| gh workflow run impl-merge.yml -f pr_number="$PR_NUM" | |
| elif [ "$ATTEMPT_COUNT" -eq 3 ]; then | |
| # This is the 4th review (after 3 repair attempts) | |
| if [ "$SCORE" -ge 50 ]; then | |
| # Score 50-89 after 3 attempts: acceptable, merge to repo | |
| gh pr edit "$PR_NUM" --add-label "ai-approved" 2>/dev/null || true | |
| gh pr comment "$PR_NUM" --body "## AI Review - Final Status | |
| ### Score: ${SCORE}/100 (Acceptable) | |
| After **3 repair attempts**, ${LIBRARY} reached ${SCORE}/100. | |
| Score ≥ 50 is acceptable for the repository. Merging. | |
| --- | |
| :robot: *[impl-review](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| echo "Triggering impl-merge.yml for acceptable PR" | |
| gh workflow run impl-merge.yml -f pr_number="$PR_NUM" | |
| else | |
| # Score < 50 after 3 attempts: not acceptable, close PR | |
| gh pr edit "$PR_NUM" --add-label "quality-poor" | |
| gh pr comment "$PR_NUM" --body "## AI Review - Final Status | |
| ### Score: ${SCORE}/100 (Poor Quality) | |
| After **3 repair attempts**, the score is still below 50. This is not acceptable for the repository. | |
| **This is treated like an auto-reject.** The PR will be closed and any old implementation will be removed from main. | |
| **Options:** | |
| 1. Regenerate from scratch with \`generate:${LIBRARY}\` label | |
| 2. Manual complete rewrite | |
| --- | |
| :robot: *[impl-review](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" | |
| gh pr close "$PR_NUM" | |
| # Remove old implementation from main if it exists | |
| IMPL_FILE="plots/${SPEC_ID}/implementations/${LIBRARY}.py" | |
| META_FILE="plots/${SPEC_ID}/metadata/${LIBRARY}.yaml" | |
| git fetch origin main | |
| if git show origin/main:"$IMPL_FILE" &>/dev/null; then | |
| echo "::notice::Removing old implementation from main: $IMPL_FILE" | |
| git checkout main | |
| git pull origin main | |
| # Delete implementation and metadata | |
| rm -f "$IMPL_FILE" "$META_FILE" | |
| # Commit and push | |
| git add -A | |
| git commit -m "chore(${LIBRARY}): remove ${SPEC_ID} impl (score ${SCORE} < 50 after 3 attempts)" | |
| git push origin main | |
| echo "::notice::Old implementation removed from main" | |
| else | |
| echo "::notice::No old implementation found on main to remove" | |
| fi | |
| if [ -n "$ISSUE_NUMBER" ]; then | |
| gh issue edit "$ISSUE_NUMBER" --add-label "impl:${LIBRARY}:failed" 2>/dev/null || true | |
| gh issue comment "$ISSUE_NUMBER" --body "**${LIBRARY}** implementation: score ${SCORE}/100 after 3 attempts (< 50 = rejected). PR #${PR_NUM} closed. Old implementation removed from repository." | |
| fi | |
| fi | |
| else | |
| # Score < 90, still have repair attempts left | |
| gh pr edit "$PR_NUM" --add-label "ai-rejected" | |
| if [ "$SCORE" -lt 50 ]; then | |
| gh pr edit "$PR_NUM" --add-label "quality-poor" | |
| echo "::notice::Added ai-rejected + quality-poor labels (score $SCORE < 50)" | |
| else | |
| echo "::notice::Added ai-rejected label (score $SCORE: 50-89)" | |
| fi | |
| echo "Triggering impl-repair.yml for rejected PR (attempt $ATTEMPT)" | |
| gh pr edit "$PR_NUM" --add-label "ai-attempt-${ATTEMPT}" 2>/dev/null || true | |
| gh workflow run impl-repair.yml \ | |
| -f pr_number="$PR_NUM" \ | |
| -f specification_id="$SPEC_ID" \ | |
| -f library="$LIBRARY" \ | |
| -f attempt="$ATTEMPT" | |
| fi |