Auto-Tag: auto/area-basic/seaborn #39
Workflow file for this run
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: "Bot: Auto-Tag" | |
| run-name: "Auto-Tag: ${{ github.event.pull_request.head.ref }}" | |
| on: | |
| pull_request: | |
| types: [closed] | |
| jobs: | |
| auto-tag: | |
| name: Generate Tags for Merged Plot | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Check conditions | |
| id: check | |
| run: | | |
| MERGED="${{ github.event.pull_request.merged }}" | |
| BRANCH="${{ github.event.pull_request.head.ref }}" | |
| LABELS="${{ join(github.event.pull_request.labels.*.name, ',') }}" | |
| if [[ "$MERGED" != "true" ]]; then | |
| echo "::notice::Skipping: PR was closed but not merged" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [[ ! "$LABELS" =~ ai-approved ]]; then | |
| echo "::notice::Skipping: PR does not have 'ai-approved' label" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [[ ! "$BRANCH" =~ ^auto/ ]]; then | |
| echo "::notice::Skipping: Branch '$BRANCH' is not auto/*" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "should_run=true" >> $GITHUB_OUTPUT | |
| - name: Checkout code | |
| if: steps.check.outputs.should_run == 'true' | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| if: steps.check.outputs.should_run == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Install uv | |
| if: steps.check.outputs.should_run == 'true' | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Install dependencies | |
| if: steps.check.outputs.should_run == 'true' | |
| run: | | |
| uv sync --all-extras | |
| - name: Extract spec ID from branch | |
| if: steps.check.outputs.should_run == 'true' | |
| id: extract_spec | |
| run: | | |
| BRANCH="${{ github.event.pull_request.head.ref }}" | |
| SPEC_ID=$(echo "$BRANCH" | sed 's/auto\///') | |
| echo "spec_id=$SPEC_ID" >> $GITHUB_OUTPUT | |
| echo "Extracted spec ID: $SPEC_ID" | |
| - name: Find related issue | |
| if: steps.check.outputs.should_run == 'true' | |
| id: find_issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Try to find issue from PR body | |
| PR_BODY="${{ github.event.pull_request.body }}" | |
| ISSUE_NUM=$(echo "$PR_BODY" | grep -oP '#\K\d+' | head -1 || echo "") | |
| if [ -z "$ISSUE_NUM" ]; then | |
| # Search by spec ID | |
| SPEC_ID="${{ steps.extract_spec.outputs.spec_id }}" | |
| ISSUE_NUM=$(gh issue list --label plot-request --search "$SPEC_ID in:title" --json number -q '.[0].number' || echo "") | |
| fi | |
| echo "issue_num=$ISSUE_NUM" >> $GITHUB_OUTPUT | |
| echo "Found issue: #$ISSUE_NUM" | |
| - name: Read spec and code files | |
| if: steps.check.outputs.should_run == 'true' | |
| id: read_files | |
| run: | | |
| SPEC_ID="${{ steps.extract_spec.outputs.spec_id }}" | |
| # Read spec file | |
| SPEC_FILE="specs/${SPEC_ID}.md" | |
| if [ -f "$SPEC_FILE" ]; then | |
| SPEC_CONTENT=$(cat "$SPEC_FILE") | |
| echo "spec_exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "spec_exists=false" >> $GITHUB_OUTPUT | |
| SPEC_CONTENT="" | |
| fi | |
| # Find plot files | |
| PLOT_FILES=$(find plots -name "*.py" -path "*/${SPEC_ID}/*" 2>/dev/null | head -5) | |
| echo "plot_files<<EOF" >> $GITHUB_OUTPUT | |
| echo "$PLOT_FILES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Generate tags with Claude | |
| id: generate_tags | |
| if: steps.check.outputs.should_run == 'true' && steps.read_files.outputs.spec_exists == 'true' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| SPEC_ID="${{ steps.extract_spec.outputs.spec_id }}" | |
| # Read spec content | |
| SPEC_CONTENT=$(cat "specs/${SPEC_ID}.md") | |
| # Read first plot file as example | |
| PLOT_FILE=$(echo "${{ steps.read_files.outputs.plot_files }}" | head -1) | |
| if [ -n "$PLOT_FILE" ] && [ -f "$PLOT_FILE" ]; then | |
| PLOT_CONTENT=$(cat "$PLOT_FILE") | |
| else | |
| PLOT_CONTENT="" | |
| fi | |
| # Create prompt for Claude | |
| cat > /tmp/tag_prompt.txt << 'PROMPTEOF' | |
| Analyze this plot specification and implementation to generate a hierarchical tag structure. | |
| ## Specification: | |
| ${SPEC_CONTENT} | |
| ## Implementation (example): | |
| ${PLOT_CONTENT} | |
| ## Task: | |
| Generate a 5-level tag hierarchy for this plot. Format as JSON: | |
| { | |
| "level1_category": "visualization_type", | |
| "level2_type": "specific_chart_type", | |
| "level3_style": "style_variant", | |
| "level4_features": ["feature1", "feature2"], | |
| "level5_use_cases": ["use_case1", "use_case2"], | |
| "search_tags": ["tag1", "tag2", "tag3"] | |
| } | |
| Categories: | |
| - Level 1: Main category (scatter, bar, line, heatmap, distribution, comparison, etc.) | |
| - Level 2: Specific type (scatter-basic, bar-grouped, line-multi, etc.) | |
| - Level 3: Style variant (default, minimal, presentation, publication, etc.) | |
| - Level 4: Features used (gridlines, annotations, legends, colors, etc.) | |
| - Level 5: Use cases (exploratory, presentation, publication, dashboard, etc.) | |
| Return ONLY valid JSON, no markdown formatting. | |
| PROMPTEOF | |
| # Replace placeholders | |
| sed -i "s|\${SPEC_CONTENT}|${SPEC_CONTENT}|g" /tmp/tag_prompt.txt | |
| sed -i "s|\${PLOT_CONTENT}|${PLOT_CONTENT}|g" /tmp/tag_prompt.txt | |
| # Call Claude API (with timeout) | |
| RESPONSE=$(curl -s --max-time 60 --connect-timeout 10 https://api.anthropic.com/v1/messages \ | |
| -H "Content-Type: application/json" \ | |
| -H "x-api-key: $ANTHROPIC_API_KEY" \ | |
| -H "anthropic-version: 2023-06-01" \ | |
| -d "{ | |
| \"model\": \"claude-sonnet-4-20250514\", | |
| \"max_tokens\": 1024, | |
| \"messages\": [{ | |
| \"role\": \"user\", | |
| \"content\": $(cat /tmp/tag_prompt.txt | jq -Rs .) | |
| }] | |
| }") | |
| # Validate API response | |
| if ! echo "$RESPONSE" | jq -e '.content[0].text' > /dev/null 2>&1; then | |
| echo "::warning::API response invalid or empty" | |
| echo "Response: $(echo "$RESPONSE" | jq -r '.error // .' 2>/dev/null || echo "$RESPONSE")" | |
| echo "tags_generated=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Extract tags from response | |
| TAGS=$(echo "$RESPONSE" | jq -r '.content[0].text // empty') | |
| if [ -n "$TAGS" ]; then | |
| echo "$TAGS" > /tmp/generated_tags.json | |
| echo "tags_generated=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "tags_generated=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Post tags to issue | |
| if: steps.check.outputs.should_run == 'true' && steps.generate_tags.outputs.tags_generated == 'true' && steps.find_issue.outputs.issue_num != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ISSUE_NUM="${{ steps.find_issue.outputs.issue_num }}" | |
| SPEC_ID="${{ steps.extract_spec.outputs.spec_id }}" | |
| TAGS=$(cat /tmp/generated_tags.json) | |
| # Format tags as markdown | |
| cat > /tmp/tag_comment.md << COMMENTEOF | |
| ## 🏷️ Auto-Generated Tags | |
| Plot \`${SPEC_ID}\` has been merged and tagged. | |
| ### Tag Hierarchy | |
| \`\`\`json | |
| ${TAGS} | |
| \`\`\` | |
| ### Quick Search Tags | |
| $(echo "$TAGS" | jq -r '.search_tags // [] | map("- `" + . + "`") | join("\n")' 2>/dev/null || echo "- No search tags generated") | |
| --- | |
| 🤖 *Generated by [bot-auto-tag workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})* | |
| COMMENTEOF | |
| gh issue comment "$ISSUE_NUM" --body-file /tmp/tag_comment.md | |
| - name: Save tags to database (placeholder) | |
| if: steps.check.outputs.should_run == 'true' && steps.generate_tags.outputs.tags_generated == 'true' | |
| run: | | |
| # TODO: Implement database storage | |
| # This would call an API endpoint to store the tags | |
| echo "Tags generated for ${{ steps.extract_spec.outputs.spec_id }}" | |
| echo "Database storage not yet implemented" | |
| cat /tmp/generated_tags.json |