Skip to content

Generate: generate:highcharts for issue #5228

Generate: generate:highcharts for issue

Generate: generate:highcharts for issue #5228

Workflow file for this run

name: "Impl: Generate"
run-name: "Generate: ${{ inputs.library || github.event.label.name }} for ${{ inputs.specification_id || 'issue' }}"
# Generates single library implementation
# Triggers:
# - generate:{library} label on spec-ready issue
# - workflow_dispatch with specification_id + library
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
specification_id:
description: "Specification ID (e.g., scatter-basic)"
required: true
type: string
library:
description: "Library to generate"
required: true
type: choice
options:
- matplotlib
- seaborn
- plotly
- bokeh
- altair
- plotnine
- pygal
- highcharts
- letsplot
issue_number:
description: "Issue number (optional, for tracking)"
required: false
type: string
# Global concurrency: max 3 concurrent implementation workflows
concurrency:
group: impl-generate-${{ inputs.specification_id || github.event.issue.number }}-${{ inputs.library || github.event.label.name }}
cancel-in-progress: false
env:
# Library dependencies mapping
DEPS_matplotlib: "matplotlib>=3.9.0 numpy>=1.26.0"
DEPS_seaborn: "seaborn>=0.13.0 matplotlib>=3.9.0 numpy>=1.26.0"
DEPS_plotly: "plotly>=5.18.0 kaleido>=0.2.1 numpy>=1.26.0"
DEPS_bokeh: "bokeh>=3.4.0 numpy>=1.26.0 selenium>=4.15.0 webdriver-manager>=4.0.0"
DEPS_altair: "altair>=5.2.0 vl-convert-python>=1.3.0 numpy>=1.26.0"
DEPS_plotnine: "plotnine>=0.13.0 numpy>=1.26.0"
DEPS_pygal: "pygal>=3.0.0 cairosvg>=2.7.0"
DEPS_highcharts: "highcharts-core>=1.10.0 numpy>=1.26.0 selenium>=4.15.0 webdriver-manager>=4.0.0"
DEPS_letsplot: "lets-plot>=4.5.0"
jobs:
generate:
# Run on label trigger OR workflow_dispatch
if: >
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'issues' && startsWith(github.event.label.name, 'generate:'))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: write
id-token: write
outputs:
success: ${{ steps.result.outputs.success }}
pr_number: ${{ steps.pr.outputs.pr_number }}
steps:
# ========================================================================
# Setup: Extract inputs and validate
# ========================================================================
- name: Extract inputs
id: inputs
env:
LABEL_NAME: ${{ github.event.label.name }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# From workflow_dispatch
SPEC_ID="${{ inputs.specification_id }}"
LIBRARY="${{ inputs.library }}"
ISSUE="${{ inputs.issue_number }}"
else
# From label trigger: generate:{library}
LIBRARY=$(echo "$LABEL_NAME" | sed 's/^generate://')
# Extract spec ID from issue title: [spec-id] ...
SPEC_ID=$(echo "$ISSUE_TITLE" | sed -n 's/^\[\([a-z0-9-]*\)\].*/\1/p')
ISSUE="${{ github.event.issue.number }}"
fi
if [ -z "$SPEC_ID" ]; then
echo "::error::Could not determine specification ID"
exit 1
fi
if [ -z "$LIBRARY" ]; then
echo "::error::Could not determine library"
exit 1
fi
# Get library dependencies
DEPS_VAR="DEPS_${LIBRARY}"
DEPS="${!DEPS_VAR}"
echo "specification_id=$SPEC_ID" >> $GITHUB_OUTPUT
echo "library=$LIBRARY" >> $GITHUB_OUTPUT
echo "issue_number=$ISSUE" >> $GITHUB_OUTPUT
echo "deps=$DEPS" >> $GITHUB_OUTPUT
echo "::notice::Generating $LIBRARY for $SPEC_ID (issue: ${ISSUE:-none})"
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Read issue number from specification.yaml (fallback)
id: spec_issue
if: steps.inputs.outputs.issue_number == ''
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
run: |
SPEC_YAML="plots/${SPEC_ID}/specification.yaml"
if [ -f "$SPEC_YAML" ]; then
# Extract issue number using yq for robust YAML parsing
ISSUE=$(yq '.issue' "$SPEC_YAML" 2>/dev/null || echo "")
if [ -n "$ISSUE" ] && [ "$ISSUE" != "null" ]; then
echo "issue_number=$ISSUE" >> $GITHUB_OUTPUT
echo "::notice::Found issue #$ISSUE from specification.yaml"
else
echo "issue_number=" >> $GITHUB_OUTPUT
echo "::warning::No issue number in specification.yaml"
fi
else
echo "issue_number=" >> $GITHUB_OUTPUT
echo "::warning::specification.yaml not found"
fi
# Consolidate issue number from inputs or fallback
- name: Set final issue number
id: issue
run: |
ISSUE="${{ steps.inputs.outputs.issue_number || steps.spec_issue.outputs.issue_number }}"
echo "number=$ISSUE" >> $GITHUB_OUTPUT
if [ -n "$ISSUE" ]; then
echo "::notice::Using issue #$ISSUE for tracking"
else
echo "::warning::No issue number available - PR will not have Parent Issue link"
fi
- name: Validate specification exists
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
run: |
if [ ! -f "plots/${SPEC_ID}/specification.md" ]; then
echo "::error::Specification not found: plots/${SPEC_ID}/specification.md"
exit 1
fi
- name: Reopen issue if closed
if: steps.issue.outputs.number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE: ${{ steps.issue.outputs.number }}
run: |
# Reopen issue so it's visible during implementation
gh issue reopen "$ISSUE" 2>/dev/null || true
- name: Add pending label
if: steps.issue.outputs.number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LIBRARY: ${{ steps.inputs.outputs.library }}
ISSUE: ${{ steps.issue.outputs.number }}
run: |
gh issue edit "$ISSUE" --add-label "impl:${LIBRARY}:pending" 2>/dev/null || true
# ========================================================================
# Setup: Python and dependencies
# ========================================================================
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y pngquant
- name: Install dependencies
env:
DEPS: ${{ steps.inputs.outputs.deps }}
run: |
uv venv .venv
source .venv/bin/activate
uv pip install $DEPS pandas>=2.2.0 ruff Pillow>=10.0.0 pyyaml>=6.0
- name: Setup Chrome for Highcharts
if: steps.inputs.outputs.library == 'highcharts'
uses: browser-actions/setup-chrome@v2
with:
chrome-version: stable
# ========================================================================
# Generate: Create implementation branch and code
# ========================================================================
- name: Create implementation branch
id: branch
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
run: |
BRANCH="implementation/${SPEC_ID}/${LIBRARY}"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
# Delete branch if exists (regeneration)
git push origin --delete "$BRANCH" 2>/dev/null || true
# Create fresh branch from main
git checkout -b "$BRANCH" origin/main
echo "::notice::Created branch: $BRANCH"
- name: Check for existing implementation (regeneration)
id: existing
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
run: |
METADATA_FILE="plots/${SPEC_ID}/metadata/${LIBRARY}.yaml"
IMPL_FILE="plots/${SPEC_ID}/implementations/${LIBRARY}.py"
if [ -f "$METADATA_FILE" ] && [ -f "$IMPL_FILE" ]; then
echo "is_regeneration=true" >> $GITHUB_OUTPUT
echo "::notice::Regeneration detected - will read previous review feedback"
else
echo "is_regeneration=false" >> $GITHUB_OUTPUT
fi
- name: Ensure implementations directory exists
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
run: |
# Create implementations directory if it doesn't exist (for new specs)
mkdir -p "plots/${SPEC_ID}/implementations"
echo "::notice::Ensured implementations directory exists"
- name: Run Claude Code to generate implementation
id: claude
continue-on-error: true
timeout-minutes: 60
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model opus"
prompt: |
Read `prompts/workflow-prompts/impl-generate-claude.md` and follow those instructions.
Variables for this run:
- LIBRARY: ${{ steps.inputs.outputs.library }}
- SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
- IS_REGENERATION: ${{ steps.existing.outputs.is_regeneration }}
- name: Retry Claude (on failure)
if: steps.claude.outcome == 'failure'
id: claude_retry
timeout-minutes: 60
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model opus"
prompt: |
Read `prompts/workflow-prompts/impl-generate-claude.md` and follow those instructions.
Variables for this run:
- LIBRARY: ${{ steps.inputs.outputs.library }}
- SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
- IS_REGENERATION: ${{ steps.existing.outputs.is_regeneration }}
# ========================================================================
# Create metadata file (before PR)
# ========================================================================
- name: Create library metadata file
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
ISSUE: ${{ steps.issue.outputs.number }}
BRANCH: ${{ steps.branch.outputs.branch }}
run: |
# Save plot files before git operations (they are not committed and would be lost)
cp "plots/${SPEC_ID}/implementations/plot.png" "/tmp/plot.png" 2>/dev/null || true
cp "plots/${SPEC_ID}/implementations/plot.html" "/tmp/plot.html" 2>/dev/null || true
# Configure git auth (Claude's action configured it, but it's gone now)
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
# Sync with remote (Claude already pushed the code)
git fetch origin
# Check if remote branch exists before checkout (fixes branch-not-found error)
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
git checkout -B "$BRANCH" "origin/$BRANCH"
else
# Branch doesn't exist on remote - create fresh from main
echo "::warning::Remote branch $BRANCH not found, creating fresh from main"
git checkout -B "$BRANCH" origin/main
fi
# Restore plot files after git operations
cp "/tmp/plot.png" "plots/${SPEC_ID}/implementations/plot.png" 2>/dev/null || true
cp "/tmp/plot.html" "plots/${SPEC_ID}/implementations/plot.html" 2>/dev/null || true
# Now create metadata file
METADATA_DIR="plots/${SPEC_ID}/metadata"
METADATA_FILE="${METADATA_DIR}/${LIBRARY}.yaml"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
mkdir -p "$METADATA_DIR"
# Get Python version (e.g., "3.13.1" from "Python 3.13.1")
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
# Get library version from pip (use explicit venv path)
get_pip_version() {
.venv/bin/pip show "$1" 2>/dev/null | grep -i "^Version:" | awk '{print $2}'
}
case "$LIBRARY" in
letsplot)
LIBRARY_VERSION=$(get_pip_version "lets-plot")
;;
highcharts)
LIBRARY_VERSION=$(get_pip_version "highcharts-core")
;;
*)
LIBRARY_VERSION=$(get_pip_version "$LIBRARY")
;;
esac
# Fallback if version is empty
if [ -z "$LIBRARY_VERSION" ]; then
echo "::warning::Could not get version for $LIBRARY, trying alternative method"
# Map library names to Python module names
case "$LIBRARY" in
letsplot) PYTHON_MODULE="lets_plot" ;;
plotnine) PYTHON_MODULE="plotnine" ;;
highcharts) PYTHON_MODULE="highcharts_core" ;;
*) PYTHON_MODULE="$LIBRARY" ;;
esac
LIBRARY_VERSION=$(.venv/bin/python -c "import $PYTHON_MODULE; print(getattr($PYTHON_MODULE, '__version__', 'unknown'))" 2>/dev/null || echo "unknown")
fi
echo "::notice::Library version: $LIBRARY = $LIBRARY_VERSION"
# Determine preview_html for interactive libraries
PREVIEW_HTML="null"
case "$LIBRARY" in
plotly|bokeh|altair|highcharts|pygal|letsplot)
PREVIEW_HTML="https://storage.googleapis.com/pyplots-images/plots/${SPEC_ID}/${LIBRARY}/plot.html"
;;
esac
# Write metadata file using Python for proper YAML formatting
# Pass all variables inline to avoid export/env issues
.venv/bin/python3 -c "
import yaml
lib = '$LIBRARY'
spec = '$SPEC_ID'
ts = '$TIMESTAMP'
run_id = ${{ github.run_id }}
issue = int('$ISSUE' or '0')
py_ver = '$PYTHON_VERSION'
lib_ver = '$LIBRARY_VERSION'
preview_html = '$PREVIEW_HTML'
metadata_file = '$METADATA_FILE'
data = {
'library': lib,
'specification_id': spec,
'created': ts,
'updated': ts,
'generated_by': 'claude-opus-4-5-20251101',
'workflow_run': run_id,
'issue': issue,
'python_version': py_ver,
'library_version': lib_ver,
'preview_url': f'https://storage.googleapis.com/pyplots-images/plots/{spec}/{lib}/plot.png',
'preview_html': preview_html if preview_html != 'null' else None,
'quality_score': None,
'review': {'strengths': [], 'weaknesses': []}
}
with open(metadata_file, 'w') as f:
f.write(f'# Per-library metadata for {lib} implementation of {spec}\n')
f.write('# Auto-generated by impl-generate.yml\n\n')
yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
"
# Commit and push metadata (implementation already committed by Claude)
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
IMPL_FILE="plots/${SPEC_ID}/implementations/${LIBRARY}.py"
# Verify implementation file exists in the repository (Claude should have committed it)
if ! git ls-files --error-unmatch "$IMPL_FILE" >/dev/null 2>&1; then
echo "::error::Implementation file not found in repository - cannot commit"
echo "::error::Expected implementation at: $IMPL_FILE"
echo "::error::This indicates Claude failed to create the implementation file"
exit 1
fi
# Add metadata file
git add "$METADATA_FILE"
# Verify metadata file is staged
if ! git diff --cached --name-only | grep -q "$(basename "$METADATA_FILE")"; then
echo "::error::Metadata file not staged - cannot commit"
echo "::error::This indicates the metadata file was not added correctly"
exit 1
fi
git commit -m "chore(${LIBRARY}): add metadata for ${SPEC_ID}"
git push origin "$BRANCH"
echo "::notice::Created metadata file: $METADATA_FILE (py${PYTHON_VERSION}, ${LIBRARY}==${LIBRARY_VERSION})"
echo "::notice::Implementation verified present, metadata committed"
# ========================================================================
# Process images: optimize + thumbnail
# ========================================================================
- name: Process plot images
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
run: |
IMPL_DIR="plots/${SPEC_ID}/implementations"
if [ ! -f "$IMPL_DIR/plot.png" ]; then
echo "::error::No plot.png found - implementation failed to generate image"
exit 1
fi
source .venv/bin/activate
# Optimize PNG and generate responsive variants (400/800/1200 x png/webp + full webp)
python -m core.images process \
"$IMPL_DIR/plot.png" \
"$IMPL_DIR/plot.png"
python -m core.images responsive \
"$IMPL_DIR/plot.png" \
"$IMPL_DIR/"
echo "::notice::Processed images: optimized + responsive variants created"
ls -la "$IMPL_DIR/"
# ========================================================================
# Create PR
# ========================================================================
- name: Create Pull Request
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
ISSUE: ${{ steps.issue.outputs.number }}
BRANCH: ${{ steps.branch.outputs.branch }}
run: |
# Check if PR already exists
EXISTING_PR=$(gh pr list --head "$BRANCH" --base main --json number -q '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "pr_number=$EXISTING_PR" >> $GITHUB_OUTPUT
echo "pr_exists=true" >> $GITHUB_OUTPUT
echo "::notice::Using existing PR #$EXISTING_PR"
exit 0
fi
# Create PR body
BODY="## Implementation: \`${SPEC_ID}\` - ${LIBRARY}
Implements the **${LIBRARY}** version of \`${SPEC_ID}\`.
**File:** \`plots/${SPEC_ID}/implementations/${LIBRARY}.py\`"
if [ -n "$ISSUE" ]; then
BODY="${BODY}
**Parent Issue:** #${ISSUE}"
fi
BODY="${BODY}
---
:robot: *[impl-generate workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
# Create PR
PR_URL=$(gh pr create --base main --head "$BRANCH" \
--title "feat(${LIBRARY}): implement ${SPEC_ID}" \
--body "$BODY")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_exists=true" >> $GITHUB_OUTPUT
echo "::notice::Created PR #$PR_NUMBER"
# ========================================================================
# Upload to GCS Staging
# ========================================================================
- name: Upload to GCS Staging
id: gcs
if: steps.pr.outputs.pr_exists == 'true'
env:
GCS_CREDENTIALS: ${{ secrets.GCS_CREDENTIALS }}
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
run: |
IMPL_DIR="plots/${SPEC_ID}/implementations"
STAGING_PATH="gs://pyplots-images/staging/${SPEC_ID}/${LIBRARY}"
PUBLIC_URL="https://storage.googleapis.com/pyplots-images/staging/${SPEC_ID}/${LIBRARY}"
if [ -z "$GCS_CREDENTIALS" ]; then
echo "::warning::GCS_CREDENTIALS not configured - skipping upload"
echo "uploaded=false" >> $GITHUB_OUTPUT
exit 0
fi
# Authenticate
echo "$GCS_CREDENTIALS" > /tmp/gcs-key.json
gcloud auth activate-service-account --key-file=/tmp/gcs-key.json
# Upload all plot images (original + responsive variants)
if [ -f "$IMPL_DIR/plot.png" ]; then
gsutil -m -h "Cache-Control:public, max-age=604800" cp "$IMPL_DIR"/plot*.png "$IMPL_DIR"/plot*.webp "${STAGING_PATH}/"
gsutil -m acl ch -u AllUsers:R "${STAGING_PATH}/plot*" 2>/dev/null || true
echo "png_url=${PUBLIC_URL}/plot.png" >> $GITHUB_OUTPUT
echo "uploaded=true" >> $GITHUB_OUTPUT
echo "::notice::Uploaded plot.png + responsive variants (PNG + WebP)"
else
echo "::error::No plot.png found - cannot continue without image"
exit 1
fi
# Upload HTML (interactive libraries)
if [ -f "$IMPL_DIR/plot.html" ]; then
gsutil -h "Cache-Control:public, max-age=604800" cp "$IMPL_DIR/plot.html" "${STAGING_PATH}/plot.html"
gsutil acl ch -u AllUsers:R "${STAGING_PATH}/plot.html" 2>/dev/null || true
echo "html_url=${PUBLIC_URL}/plot.html" >> $GITHUB_OUTPUT
fi
rm -f /tmp/gcs-key.json
# ========================================================================
# Post preview and trigger review
# ========================================================================
- name: Post preview to issue
if: steps.issue.outputs.number != '' && steps.gcs.outputs.uploaded == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
ISSUE: ${{ steps.issue.outputs.number }}
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
run: |
# Use staging URLs (available immediately after upload)
PNG_URL="https://storage.googleapis.com/pyplots-images/staging/${SPEC_ID}/${LIBRARY}/plot.png"
HTML_URL=""
# Set HTML_URL for interactive libraries
case "$LIBRARY" in
plotly|bokeh|altair|highcharts|pygal|letsplot)
HTML_URL="https://storage.googleapis.com/pyplots-images/staging/${SPEC_ID}/${LIBRARY}/plot.html"
;;
esac
BODY="## :art: ${LIBRARY} Preview
![${LIBRARY} plot](${PNG_URL})
**PR:** #${PR_NUMBER}"
if [ -n "$HTML_URL" ]; then
BODY="${BODY}
**Interactive:** [Open HTML](${HTML_URL})"
fi
BODY="${BODY}
---
:robot: *[impl-generate](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
gh issue comment "$ISSUE" --body "$BODY"
- name: Trigger review workflow
if: steps.pr.outputs.pr_exists == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
run: |
# Use repository_dispatch as workaround for workflow_dispatch caching issue
gh api repos/${{ github.repository }}/dispatches \
-f event_type=review-pr \
-f 'client_payload[pr_number]='"$PR_NUMBER"
echo "::notice::Triggered impl-review.yml via repository_dispatch for PR #$PR_NUMBER"
- name: Determine result
id: result
run: |
if [ "${{ steps.pr.outputs.pr_exists }}" == "true" ]; then
echo "success=true" >> $GITHUB_OUTPUT
else
echo "success=false" >> $GITHUB_OUTPUT
fi
# ========================================================================
# Failure handling: Track failures via comments and auto-retry
# ========================================================================
- name: Handle generation failure
if: failure() && steps.issue.outputs.number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
ISSUE: ${{ steps.issue.outputs.number }}
run: |
echo "::notice::Handling generation failure for $LIBRARY/$SPEC_ID"
# Count previous failures via hidden marker comments (more reliable than workflow runs)
MARKER="<!-- impl-fail:${SPEC_ID}:${LIBRARY} -->"
FAILURE_COUNT=$(gh api "repos/${{ github.repository }}/issues/${ISSUE}/comments" \
--jq "[.[] | select(.body | contains(\"$MARKER\"))] | length" 2>/dev/null || echo "0")
echo "::notice::Previous failures for ${LIBRARY}/${SPEC_ID}: $FAILURE_COUNT"
# After 1 previous failure (= this is attempt 2) → mark as failed
if [ "$FAILURE_COUNT" -ge 1 ]; then
echo "::warning::Marking $LIBRARY as failed after 2 generation attempts"
# Create failed label if needed
gh label create "impl:${LIBRARY}:failed" --color "d73a4a" \
--description "${LIBRARY} implementation failed" 2>/dev/null || true
# Remove stale labels and add failed
gh issue edit "$ISSUE" \
--remove-label "generate:${LIBRARY},impl:${LIBRARY}:pending" \
--add-label "impl:${LIBRARY}:failed" 2>/dev/null || true
# Post final failure comment with marker
gh issue comment "$ISSUE" --body "${MARKER}
## :x: ${LIBRARY} Failed (Attempt 2/2)
The **${LIBRARY}** implementation for \`${SPEC_ID}\` failed after 2 attempts.
**Reason:** Claude Code failed to create the implementation file.
To retry manually:
\`\`\`
gh workflow run impl-generate.yml -f specification_id=${SPEC_ID} -f library=${LIBRARY} -f issue_number=${ISSUE}
\`\`\`
---
:robot: *[impl-generate](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
else
# First failure → post comment with marker and auto-retry
gh issue comment "$ISSUE" --body "${MARKER}
## :warning: ${LIBRARY} Generation Failed (Attempt 1/2)
First attempt failed. Automatically retrying...
---
:robot: *[impl-generate](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*"
# Clean up generate label before retry
gh issue edit "$ISSUE" --remove-label "generate:${LIBRARY}" 2>/dev/null || true
# Automatic retry via workflow_dispatch
gh workflow run impl-generate.yml \
-f specification_id="${SPEC_ID}" \
-f library="${LIBRARY}" \
-f issue_number="${ISSUE}"
echo "::notice::Triggered automatic retry for ${LIBRARY}/${SPEC_ID}"
fi