DL-6: Comprehensive Story Outlines & Plot Threads #43
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: Copilot Automation | |
| # ============================================================================= | |
| # COPILOT AUTOMATION LOOP | |
| # ============================================================================= | |
| # | |
| # This workflow automates the full development cycle with GitHub Copilot: | |
| # | |
| # ┌─────────────────────────────────────────────────────────────────────────┐ | |
| # │ │ | |
| # │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ | |
| # │ │ Queue │───▶│ Copilot │───▶│ CI │───▶│ Review │ │ | |
| # │ │ Issue │ │ Opens │ │ Tests │ │ PR │ │ | |
| # │ └──────────┘ │ PR │ └────┬─────┘ └────┬─────┘ │ | |
| # │ ▲ └──────────┘ │ │ │ | |
| # │ │ │ │ │ | |
| # │ │ ┌────▼─────┐ │ │ | |
| # │ │ │ Failed? │ │ │ | |
| # │ │ └────┬─────┘ │ │ | |
| # │ │ │ │ │ | |
| # │ │ ┌──────────┐ YES │ NO │ │ | |
| # │ │ │ Copilot │◀─────────┤ │ │ | |
| # │ │ │ Fixes │ │ │ │ | |
| # │ │ │ Code │──────────┘ │ │ | |
| # │ │ └──────────┘ │ │ | |
| # │ │ ▲ │ │ | |
| # │ │ │ (max 3 attempts) │ │ | |
| # │ │ │ │ │ | |
| # │ │ ┌────┴─────┐ │ │ | |
| # │ │ │ Still │ ┌────▼─────┐ │ | |
| # │ │ │ Failing? │────YES─────────────▶│ Human │ │ | |
| # │ │ └──────────┘ │ Review │ │ | |
| # │ │ └────┬─────┘ │ | |
| # │ │ │ │ | |
| # │ ┌────┴─────┐ ┌──────────┐ ┌──────────┐ │ │ | |
| # │ │ Update │◀───│ Merge │◀───│ Approved │◀────────┘ │ | |
| # │ │ Status │ │ PR │ │ ? │ │ | |
| # │ └──────────┘ └──────────┘ └──────────┘ │ | |
| # │ │ │ | |
| # │ └───────────────────────────────────────────────────────────────┘ | |
| # │ (loop to next issue) | |
| # │ | |
| # └─────────────────────────────────────────────────────────────────────────┘ | |
| # | |
| # JOBS: | |
| # 1. queue-issues - Find ready issues (dependencies met), label for Copilot | |
| # 2. pause-copilot - Budget protection: remove labels to pause queue | |
| # 3. show-status - Display current queue and budget status | |
| # 4. copilot-review - Request Copilot review on PRs | |
| # 5. auto-merge - Enable auto-merge when approved | |
| # 6. update-on-merge - Update YAML status, queue next issue | |
| # 7. scheduled-queue - Daily check to keep queue populated | |
| # | |
| # NOTE: CI failure handling moved to ci-failure-handler.yml (separate workflow) | |
| # | |
| # BUDGET PROTECTION: | |
| # - Tracks Copilot PRs per month | |
| # - Pauses queue when limit reached | |
| # - Escalates to human after 3 failed fix attempts | |
| # | |
| on: | |
| # Manual trigger to queue next issues | |
| workflow_dispatch: | |
| inputs: | |
| action: | |
| description: 'Action to perform' | |
| required: true | |
| default: 'queue' | |
| type: choice | |
| options: | |
| - queue # Find and label ready issues | |
| - pause # Remove copilot labels (budget protection) | |
| - status # Show current queue status | |
| max_issues: | |
| description: 'Max issues to queue' | |
| required: false | |
| default: '1' | |
| type: string | |
| # Auto-trigger on schedule | |
| schedule: | |
| - cron: '0 8 * * 1-5' # Weekdays at 8am UTC | |
| # Trigger review when Copilot opens a PR | |
| pull_request: | |
| types: [opened, synchronize, ready_for_review] | |
| # Trigger status update on merge | |
| pull_request_target: | |
| types: [closed] | |
| env: | |
| PROJECT_NUMBER: 1 | |
| PROJECT_OWNER: spuentesp | |
| MAX_COPILOT_PRS_PER_MONTH: 30 # Budget protection | |
| jobs: | |
| # ============================================================ | |
| # JOB 1: Queue issues for Copilot | |
| # ============================================================ | |
| queue-issues: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'queue' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: pip install pyyaml | |
| - name: Check budget | |
| id: budget | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Count Copilot PRs this month | |
| MONTH_START=$(date -d "$(date +%Y-%m-01)" +%Y-%m-%d) | |
| COUNT=$(gh pr list \ | |
| --author "app/github-copilot" \ | |
| --state all \ | |
| --search "created:>=$MONTH_START" \ | |
| --json number \ | |
| --jq 'length' 2>/dev/null || echo "0") | |
| echo "Copilot PRs this month: $COUNT / $MAX_COPILOT_PRS_PER_MONTH" | |
| if [ "$COUNT" -ge "$MAX_COPILOT_PRS_PER_MONTH" ]; then | |
| echo "budget_ok=false" >> $GITHUB_OUTPUT | |
| echo "::warning::Monthly Copilot budget exhausted!" | |
| else | |
| echo "budget_ok=true" >> $GITHUB_OUTPUT | |
| REMAINING=$((MAX_COPILOT_PRS_PER_MONTH - COUNT)) | |
| echo "remaining=$REMAINING" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Queue ready issues | |
| if: steps.budget.outputs.budget_ok == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MAX_ISSUES: ${{ github.event.inputs.max_issues }} | |
| run: | | |
| python3 << 'EOF' | |
| import os | |
| import json | |
| import subprocess | |
| from pathlib import Path | |
| import yaml | |
| def run_gh(args): | |
| result = subprocess.run( | |
| ["gh"] + args, | |
| capture_output=True, text=True | |
| ) | |
| return result.stdout.strip() | |
| def get_use_cases(): | |
| """Load all YAML use cases.""" | |
| cases = {} | |
| for f in Path("docs/use-cases").rglob("*.yml"): | |
| try: | |
| data = yaml.safe_load(f.read_text()) | |
| if data and "id" in data: | |
| cases[data["id"]] = data | |
| except Exception as e: | |
| print(f"Warning: Could not parse {f}: {e}") | |
| return cases | |
| def get_existing_prs(): | |
| """Get list of open PRs to avoid duplicate work.""" | |
| result = run_gh(["pr", "list", "--state", "open", "--json", "title"]) | |
| prs = json.loads(result) if result else [] | |
| return [pr["title"] for pr in prs] | |
| def get_issues_with_label(label): | |
| """Get issues already labeled.""" | |
| result = run_gh(["issue", "list", "--label", label, "--state", "open", "--json", "number,title"]) | |
| return json.loads(result) if result else [] | |
| def find_ready_issues(cases): | |
| """Find issues whose dependencies are all done.""" | |
| ready = [] | |
| for id, case in cases.items(): | |
| status = case.get("status", "todo") | |
| if status == "done": | |
| continue | |
| deps = case.get("depends_on", []) | |
| deps_done = all( | |
| cases.get(d, {}).get("status") == "done" | |
| for d in deps | |
| ) | |
| if deps_done: | |
| priority = case.get("priority", "medium") | |
| ready.append((priority, id, case)) | |
| # Sort by priority | |
| priority_order = {"critical": 0, "high": 1, "medium": 2, "low": 3} | |
| ready.sort(key=lambda x: priority_order.get(x[0], 2)) | |
| return ready | |
| def find_github_issue(use_case_id): | |
| """Find GitHub issue number for a use case.""" | |
| result = run_gh([ | |
| "issue", "list", | |
| "--search", f'"{use_case_id}" in:title', | |
| "--state", "open", | |
| "--json", "number,title", | |
| "--limit", "5" | |
| ]) | |
| issues = json.loads(result) if result else [] | |
| for issue in issues: | |
| if issue["title"].startswith(f"{use_case_id}:"): | |
| return issue["number"] | |
| return None | |
| def label_issue(issue_num, labels): | |
| """Add labels to an issue.""" | |
| for label in labels: | |
| run_gh(["issue", "edit", str(issue_num), "--add-label", label]) | |
| def assign_copilot(issue_num): | |
| """Assign Copilot to an issue and trigger it to start.""" | |
| # Assign Copilot | |
| run_gh(["issue", "edit", str(issue_num), "--add-assignee", "Copilot"]) | |
| # Comment to trigger Copilot | |
| run_gh(["issue", "comment", str(issue_num), "--body", | |
| "@copilot please implement this issue following the acceptance criteria and implementation details."]) | |
| # Main logic | |
| cases = get_use_cases() | |
| print(f"Loaded {len(cases)} use cases") | |
| ready = find_ready_issues(cases) | |
| print(f"Found {len(ready)} ready issues (dependencies met)") | |
| existing_prs = get_existing_prs() | |
| already_labeled = get_issues_with_label("copilot") | |
| max_to_queue = int(os.environ.get("MAX_ISSUES", "1")) | |
| queued = 0 | |
| for priority, use_case_id, case in ready: | |
| if queued >= max_to_queue: | |
| break | |
| # Skip if PR already exists | |
| if any(use_case_id in pr for pr in existing_prs): | |
| print(f" Skipping {use_case_id}: PR already exists") | |
| continue | |
| # Skip if already labeled | |
| if any(use_case_id in issue.get("title", "") for issue in already_labeled): | |
| print(f" Skipping {use_case_id}: already labeled for Copilot") | |
| continue | |
| issue_num = find_github_issue(use_case_id) | |
| if issue_num: | |
| print(f" Assigning #{issue_num} ({use_case_id}) to Copilot") | |
| label_issue(issue_num, ["copilot", "ready-for-copilot"]) | |
| assign_copilot(issue_num) | |
| queued += 1 | |
| else: | |
| print(f" Warning: Could not find GitHub issue for {use_case_id}") | |
| print(f"\nQueued {queued} issues for Copilot") | |
| EOF | |
| # ============================================================ | |
| # JOB 2: Pause Copilot (budget protection) | |
| # ============================================================ | |
| pause-copilot: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'pause' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Remove Copilot labels | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Removing 'copilot' and 'ready-for-copilot' labels..." | |
| gh issue list --label "copilot" --state open --json number --jq '.[].number' | \ | |
| xargs -I{} gh issue edit {} --remove-label "copilot" --remove-label "ready-for-copilot" 2>/dev/null || true | |
| echo "Copilot queue paused" | |
| # ============================================================ | |
| # JOB 3: Show queue status | |
| # ============================================================ | |
| show-status: | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'status' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Show status | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "=== Copilot Queue Status ===" | |
| echo "" | |
| echo "Issues labeled for Copilot:" | |
| gh issue list --label "copilot" --state open --json number,title --jq '.[] | " #\(.number): \(.title)"' | |
| echo "" | |
| echo "Open Copilot PRs:" | |
| gh pr list --author "app/github-copilot" --state open --json number,title --jq '.[] | " #\(.number): \(.title)"' || echo " None" | |
| echo "" | |
| MONTH_START=$(date -d "$(date +%Y-%m-01)" +%Y-%m-%d) | |
| COUNT=$(gh pr list --author "app/github-copilot" --state all --search "created:>=$MONTH_START" --json number --jq 'length' 2>/dev/null || echo "0") | |
| echo "Copilot PRs this month: $COUNT / $MAX_COPILOT_PRS_PER_MONTH" | |
| # ============================================================ | |
| # JOB 4: Auto-approve and review Copilot PRs | |
| # ============================================================ | |
| copilot-review: | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.draft == false && | |
| github.event.action != 'closed' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check if Copilot PR needs approval | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| AUTHOR="${{ github.event.pull_request.user.login }}" | |
| # Check if this is a Copilot PR (by author or label) | |
| LABELS=$(gh pr view "$PR_NUM" --json labels --jq '.labels[].name' 2>/dev/null || echo "") | |
| if echo "$LABELS" | grep -q "copilot"; then | |
| echo "is_copilot_pr=true" >> $GITHUB_OUTPUT | |
| elif [[ "$AUTHOR" == *"copilot"* ]] || [[ "$AUTHOR" == *"bot"* ]]; then | |
| echo "is_copilot_pr=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_copilot_pr=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Check if already approved | |
| REVIEW_STATE=$(gh pr view "$PR_NUM" --json reviewDecision --jq '.reviewDecision' 2>/dev/null || echo "") | |
| if [ "$REVIEW_STATE" = "APPROVED" ]; then | |
| echo "already_approved=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "already_approved=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Wait for CI to pass before approving | |
| if: steps.check.outputs.is_copilot_pr == 'true' && steps.check.outputs.already_approved == 'false' | |
| id: wait-validate | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| SHA="${{ github.event.pull_request.head.sha }}" | |
| echo "Waiting for validate check to pass..." | |
| # Wait up to 5 minutes for validate to complete | |
| for i in {1..30}; do | |
| # Check specifically for the validate job | |
| VALIDATE_STATUS=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ | |
| --jq '.check_runs[] | select(.name == "validate") | .conclusion' 2>/dev/null || echo "pending") | |
| echo " Attempt $i: validate=$VALIDATE_STATUS" | |
| if [ "$VALIDATE_STATUS" = "success" ]; then | |
| echo "Validate passed!" | |
| echo "ci_passed=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| elif [ "$VALIDATE_STATUS" = "failure" ]; then | |
| echo "Validate failed, skipping approval" | |
| echo "ci_passed=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| sleep 10 | |
| done | |
| echo "Timeout waiting for validate" | |
| echo "ci_passed=false" >> $GITHUB_OUTPUT | |
| - name: Auto-approve Copilot PR | |
| if: | | |
| steps.check.outputs.is_copilot_pr == 'true' && | |
| steps.check.outputs.already_approved == 'false' && | |
| steps.wait-validate.outputs.ci_passed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| echo "Auto-approving Copilot PR #$PR_NUM" | |
| # Approve the PR | |
| gh pr review "$PR_NUM" --approve --body "✅ **Auto-approved by CI bot** | |
| This PR was created by GitHub Copilot and has passed all CI checks: | |
| - ✅ Layer boundary validation | |
| - ✅ Linting (ruff, black) | |
| - ✅ Type checking (mypy) | |
| - ✅ Unit tests with coverage | |
| Auto-merge will proceed if enabled." | |
| - name: Request Copilot code review | |
| if: steps.check.outputs.is_copilot_pr == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| # Add label and request Copilot review for additional feedback | |
| gh pr edit "$PR_NUM" --add-label "copilot-review-requested" 2>/dev/null || true | |
| # Add a comment to trigger Copilot review | |
| gh pr comment "$PR_NUM" --body "@github-copilot please review this PR and check: | |
| 1. Code follows the patterns in CLAUDE.md and ARCHITECTURE.md | |
| 2. Tests cover the acceptance criteria | |
| 3. No layer boundary violations | |
| 4. Authority rules are respected (CanonKeeper for Neo4j writes)" 2>/dev/null || true | |
| # ============================================================ | |
| # JOB 5: Auto-merge approved Copilot PRs | |
| # ============================================================ | |
| auto-merge: | |
| needs: [copilot-review] | |
| if: | | |
| github.event_name == 'pull_request' && | |
| (github.event.action == 'synchronize' || github.event.action == 'ready_for_review') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Wait for approval and merge | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| # Check if PR has copilot label | |
| LABELS=$(gh pr view "$PR_NUM" --json labels --jq '.labels[].name' 2>/dev/null || echo "") | |
| if ! echo "$LABELS" | grep -q "copilot"; then | |
| echo "Not a Copilot PR, skipping auto-merge" | |
| exit 0 | |
| fi | |
| # Wait a bit for the approval to be registered | |
| sleep 5 | |
| # Check review status | |
| REVIEW_STATE=$(gh pr view "$PR_NUM" --json reviewDecision --jq '.reviewDecision' 2>/dev/null || echo "") | |
| if [ "$REVIEW_STATE" != "APPROVED" ]; then | |
| echo "PR not yet approved (state: $REVIEW_STATE), skipping" | |
| exit 0 | |
| fi | |
| # Check if CI passed | |
| SHA="${{ github.event.pull_request.head.sha }}" | |
| VALIDATE_STATUS=$(gh api repos/${{ github.repository }}/commits/$SHA/check-runs \ | |
| --jq '.check_runs[] | select(.name == "validate") | .conclusion' 2>/dev/null || echo "pending") | |
| if [ "$VALIDATE_STATUS" != "success" ]; then | |
| echo "Validate not passed yet (status: $VALIDATE_STATUS), skipping" | |
| exit 0 | |
| fi | |
| echo "All conditions met, merging PR #$PR_NUM" | |
| # Try auto-merge first, fall back to direct merge | |
| if ! gh pr merge "$PR_NUM" --squash --auto 2>/dev/null; then | |
| echo "Auto-merge not available, trying direct merge..." | |
| gh pr merge "$PR_NUM" --squash --admin || echo "Could not merge, may need manual intervention" | |
| fi | |
| # ============================================================ | |
| # JOB 6: Update status on merge | |
| # ============================================================ | |
| update-on-merge: | |
| if: | | |
| github.event_name == 'pull_request_target' && | |
| github.event.action == 'closed' && | |
| github.event.pull_request.merged == true | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract use case ID | |
| id: extract | |
| run: | | |
| TITLE="${{ github.event.pull_request.title }}" | |
| # Extract ID like "DL-24" or "P-1" from PR title | |
| ID=$(echo "$TITLE" | grep -oE '^[A-Z]+-[0-9]+' || echo "") | |
| if [ -n "$ID" ]; then | |
| echo "use_case_id=$ID" >> $GITHUB_OUTPUT | |
| echo "Found use case: $ID" | |
| else | |
| echo "No use case ID found in title" | |
| echo "use_case_id=" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update YAML status to done | |
| if: steps.extract.outputs.use_case_id != '' | |
| env: | |
| USE_CASE_ID: ${{ steps.extract.outputs.use_case_id }} | |
| run: | | |
| # Find the YAML file | |
| FILE=$(find docs/use-cases -name "${USE_CASE_ID}.yml" -o -name "${USE_CASE_ID}.yaml" | head -1) | |
| if [ -n "$FILE" ] && [ -f "$FILE" ]; then | |
| echo "Updating $FILE to status: done" | |
| sed -i 's/status: "todo"/status: "done"/' "$FILE" | |
| sed -i "s/status: 'todo'/status: 'done'/" "$FILE" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add "$FILE" | |
| git commit -m "chore: mark ${USE_CASE_ID} as done [skip ci]" || echo "No changes to commit" | |
| git push || echo "Nothing to push" | |
| else | |
| echo "YAML file not found for $USE_CASE_ID" | |
| fi | |
| - name: Remove Copilot labels from issue | |
| if: steps.extract.outputs.use_case_id != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| USE_CASE_ID: ${{ steps.extract.outputs.use_case_id }} | |
| run: | | |
| # Find and clean up the issue | |
| ISSUE_NUM=$(gh issue list \ | |
| --search "\"$USE_CASE_ID\" in:title" \ | |
| --state open \ | |
| --json number \ | |
| --jq '.[0].number' 2>/dev/null || echo "") | |
| if [ -n "$ISSUE_NUM" ]; then | |
| gh issue edit "$ISSUE_NUM" \ | |
| --remove-label "copilot" \ | |
| --remove-label "ready-for-copilot" \ | |
| --remove-label "copilot-review-requested" 2>/dev/null || true | |
| fi | |
| - name: Queue next issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Triggering queue for next issue..." | |
| gh workflow run copilot-automation.yml -f action=queue -f max_issues=1 || echo "Could not trigger next queue" | |
| # ============================================================ | |
| # JOB 7: Scheduled queue check | |
| # ============================================================ | |
| scheduled-queue: | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check and queue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Check budget first | |
| MONTH_START=$(date -d "$(date +%Y-%m-01)" +%Y-%m-%d) | |
| COUNT=$(gh pr list \ | |
| --author "app/github-copilot" \ | |
| --state all \ | |
| --search "created:>=$MONTH_START" \ | |
| --json number \ | |
| --jq 'length' 2>/dev/null || echo "0") | |
| if [ "$COUNT" -ge "$MAX_COPILOT_PRS_PER_MONTH" ]; then | |
| echo "Budget exhausted, skipping queue" | |
| exit 0 | |
| fi | |
| # Check if there are already labeled issues | |
| LABELED=$(gh issue list --label "copilot" --state open --json number --jq 'length') | |
| if [ "$LABELED" -eq 0 ]; then | |
| echo "No issues in queue, triggering queue action" | |
| gh workflow run copilot-automation.yml -f action=queue -f max_issues=1 | |
| else | |
| echo "Already have $LABELED issues in queue" | |
| fi |