fix: add missing __future__ import to _plugin_base.py for Python 3.9 #49
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: pyqual CI with GitHub Integration | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| branches: [main, master] | |
| issues: | |
| types: [opened, edited, labeled] | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: 'Issue number to process (optional)' | |
| required: false | |
| type: string | |
| jobs: | |
| quality-loop: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Install tools | |
| run: | | |
| pip install -e . | |
| npm install -g @anthropic-ai/claude-code || true | |
| # Install GitHub CLI | |
| curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg | |
| sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null | |
| sudo apt update && sudo apt install gh -y | |
| - name: Fetch GitHub issues as tasks | |
| id: fetch-tasks | |
| run: | | |
| echo "::group::Fetching GitHub issues" | |
| python3 -m pyqual.github_tasks --fetch-issues --label "pyqual-fix" --output .pyqual/github_tasks.json || true | |
| echo "::endgroup::" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Process triggering issue | |
| if: github.event_name == 'issues' || (github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number != '') | |
| id: process-issue | |
| run: | | |
| echo "::group::Processing triggering issue" | |
| python3 << 'EOF' | |
| import json | |
| import os | |
| from pathlib import Path | |
| # Get issue number from event or workflow_dispatch input | |
| issue_number = os.environ.get("ISSUE_NUMBER") or os.environ.get("INPUT_ISSUE_NUMBER") | |
| issue_title = os.environ.get("ISSUE_TITLE", "Unknown") | |
| issue_body = os.environ.get("ISSUE_BODY", "") | |
| if not issue_number: | |
| print("No issue number provided, skipping issue processing") | |
| sys.exit(0) | |
| print(f"Processing issue #{issue_number}: {issue_title[:60]}...") | |
| # Read existing TODO.md if present (preserves prefact-generated content) | |
| todo_path = Path("TODO.md") | |
| existing_content = "" | |
| if todo_path.exists(): | |
| existing_content = todo_path.read_text() | |
| print("Preserved existing TODO.md content from prefact") | |
| # Create GitHub issue section | |
| github_section = f"# GitHub Issue #{issue_number}\n\n" | |
| github_section += f"- [ ] #{issue_number}: {issue_title}\n\n" | |
| github_section += "### Issue Body\n" | |
| todo_body = issue_body[:500] + "..." if len(issue_body) > 500 else issue_body | |
| github_section += todo_body + "\n\n---\n*Auto-generated from GitHub issue event*\n" | |
| # Combine: GitHub issue first, then existing prefact content | |
| if existing_content: | |
| todo_content = github_section + "\n" + existing_content | |
| else: | |
| todo_content = github_section | |
| todo_path.write_text(todo_content) | |
| print(f"Created TODO.md with issue #{issue_number} + {len(existing_content)} bytes existing content") | |
| # Save issue metadata for later | |
| meta_path = Path(".pyqual/triggering_issue.json") | |
| meta_path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(meta_path, "w") as f: | |
| json.dump({ | |
| "number": int(issue_number), | |
| "title": issue_title, | |
| "body": issue_body[:1000], | |
| "action": os.environ.get("ISSUE_ACTION", "opened") | |
| }, f, indent=2) | |
| print(f"Saved issue metadata to {meta_path}") | |
| EOF | |
| echo "::endgroup::" | |
| env: | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| ISSUE_BODY: ${{ github.event.issue.body }} | |
| ISSUE_ACTION: ${{ github.event.action }} | |
| - name: Process issue with Claude Code | |
| if: github.event_name == 'issues' || (github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number != '') | |
| continue-on-error: true | |
| id: process-claude | |
| run: | | |
| echo "::group::Processing issue with Claude Code" | |
| export PATH="$HOME/.local/bin:$PATH" | |
| # Get issue details | |
| ISSUE_NUM="${{ github.event.issue.number || github.event.inputs.issue_number }}" | |
| ISSUE_TITLE="${{ github.event.issue.title }}" | |
| ISSUE_BODY="${{ github.event.issue.body }}" | |
| echo "Processing issue #$ISSUE_NUM: $ISSUE_TITLE" | |
| if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then | |
| echo "ANTHROPIC_API_KEY not set; skipping Claude Code processing" | |
| echo "changes_made=false" >> "$GITHUB_OUTPUT" | |
| else | |
| # Create prompt for Claude Code | |
| PROMPT=$(cat << 'PROMPT_EOF' | |
| You are given a GitHub issue stored in `.pyqual/triggering_issue.json`. | |
| Read that file before making any changes so you use the exact issue title, body, and number. | |
| Instructions: | |
| 1. Analyze the issue requirements carefully. | |
| 2. Make the necessary code/documentation changes. | |
| 3. Ensure all quality gates still pass after changes. | |
| 4. Do not commit - just apply changes. | |
| Apply changes directly to the repository files. | |
| PROMPT_EOF | |
| ) | |
| # Run Claude Code with the prompt | |
| if timeout 600 claude -p "$PROMPT" \ | |
| --model sonnet \ | |
| --allowedTools "Edit,Read,Write,Bash(git diff),Bash(python),Bash(pytest -x)" \ | |
| --output-format text; then | |
| echo "β Claude Code successfully processed the issue" | |
| echo "changes_made=true" >> $GITHUB_OUTPUT | |
| else | |
| rc=$? | |
| echo "β οΈ Claude Code exited with code $rc" | |
| echo "changes_made=false" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| # Check if changes were made | |
| if [ -n "$(git status --porcelain)" ]; then | |
| echo "Changes detected:" | |
| git status --short | |
| else | |
| echo "No changes made" | |
| fi | |
| echo "::endgroup::" | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run quality gate loop | |
| id: pyqual-run | |
| continue-on-error: true | |
| run: | | |
| echo "::group::Running pyqual" | |
| pyqual run --config pyqual.yaml 2>&1 | tee .pyqual/pyqual_output.log | |
| exit_code=${PIPESTATUS[0]} | |
| echo "exit_code=$exit_code" >> $GITHUB_OUTPUT | |
| exit $exit_code | |
| env: | |
| LLM_MODEL: ${{ secrets.LLM_MODEL || 'openrouter/qwen/qwen3-coder-next' }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PYQUAL_MAX_TODOS: "3" | |
| - name: Analyze failure and post comment | |
| if: steps.pyqual-run.outputs.exit_code != '0' && github.event_name == 'pull_request' | |
| run: | | |
| echo "::group::Analyzing failure" | |
| python3 << 'EOF' | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from datetime import datetime | |
| sys.path.insert(0, '.') | |
| from pyqual.github_actions import GitHubActionsReporter | |
| from pyqual.pipeline import PipelineError | |
| reporter = GitHubActionsReporter() | |
| if not reporter.is_running_in_github_actions(): | |
| print("Not in GitHub Actions, skipping comment") | |
| sys.exit(0) | |
| # Read pyqual output | |
| log_file = Path(".pyqual/pyqual_output.log") | |
| logs = log_file.read_text() if log_file.exists() else "No logs available" | |
| # Determine failure stage from logs | |
| stage = "unknown" | |
| error_msg = "Pipeline failed" | |
| suggestions = [] | |
| if "claude_fix" in logs and ("timeout" in logs.lower() or "rate limit" in logs.lower()): | |
| stage = "claude_fix" | |
| error_msg = "Claude Code fix stage timed out or hit rate limit" | |
| suggestions = [ | |
| "Try running with fewer TODO items: `PYQUAL_MAX_TODOS=3 pyqual run`", | |
| "Check ANTHROPIC_API_KEY is valid and has quota", | |
| "Consider using llx fallback instead of Claude Code" | |
| ] | |
| elif "test" in logs and "failed" in logs.lower(): | |
| stage = "test" | |
| error_msg = "Tests are failing" | |
| suggestions = [ | |
| "Run tests locally: `pytest -xvs` to see detailed output", | |
| "Check if test failures are related to your changes", | |
| "Update tests if the behavior change is intentional" | |
| ] | |
| elif "prefact" in logs: | |
| stage = "prefact" | |
| error_msg = "Prefact analysis found issues" | |
| suggestions = [ | |
| "Review prefact output in .pyqual/prefact.json", | |
| "Fix code quality issues manually or use `pyqual run` to auto-fix", | |
| "Some issues may be false positives - review carefully" | |
| ] | |
| report = reporter.generate_failure_report( | |
| stage_name=stage, | |
| error=error_msg, | |
| logs=logs, | |
| suggestions=suggestions | |
| ) | |
| success = reporter.post_pr_comment(report) | |
| print(f"Posted comment: {success}") | |
| # Create or find GitHub issue for tracking | |
| issue_title = f"[CI Fail] Stage '{stage}' failed in PR #{pr_number}" | |
| issue_body = "## CI Failure Report\n\n" | |
| issue_body += f"**Stage:** `{stage}`\n" | |
| issue_body += f"**Error:** {error_msg}\n" | |
| issue_body += f"**PR:** #{pr_number}\n" | |
| issue_body += f"**Timestamp:** {timestamp}\n\n" | |
| issue_body += "### Logs\n<details>\n<summary>Click to expand</summary>\n\n```\n" | |
| issue_body += logs[:2000] | |
| issue_body += "\n```\n</details>\n\n" | |
| issue_body += "### Next Steps\n" | |
| issue_body += "- [ ] Review the failure logs above\n" | |
| issue_body += "- [ ] Fix the underlying issue\n" | |
| issue_body += "- [ ] Re-run pyqual CI\n\n" | |
| issue_body += "---\n_Auto-generated by pyqual CI_" | |
| issue_num = reporter.ensure_issue_exists( | |
| title=issue_title, | |
| body=issue_body, | |
| labels=["ci-failure", "pyqual-fix"] | |
| ) | |
| if issue_num: | |
| print(f"GitHub issue for tracking: #{issue_num}") | |
| # Add ticket to TODO.md | |
| todo_path = Path("TODO.md") | |
| pr_number = os.environ.get("PR_NUMBER", "unknown") | |
| timestamp = datetime.now().strftime("%Y-%m-%d") | |
| todo_entry = f"\n- [ ] [CI Fail] Stage '{stage}' failed in PR #{pr_number} - {error_msg} ({timestamp})\n" | |
| if todo_path.exists(): | |
| content = todo_path.read_text() | |
| if "## CI Failures" not in content: | |
| content += "\n\n## CI Failures\n\n" | |
| content += todo_entry | |
| else: | |
| content = f"# TODO\n\n## CI Failures\n{todo_entry}" | |
| todo_path.write_text(content) | |
| print(f"Added ticket to TODO.md: {todo_entry.strip()}") | |
| EOF | |
| echo "::endgroup::" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| - name: Update GitHub issue on failure | |
| if: steps.pyqual-run.outputs.exit_code != '0' && github.event_name == 'issues' | |
| run: | | |
| python3 << 'EOF' | |
| import os | |
| import sys | |
| import json | |
| from pathlib import Path | |
| sys.path.insert(0, '.') | |
| from pyqual.github_actions import GitHubActionsReporter | |
| reporter = GitHubActionsReporter() | |
| issue_number = os.environ.get("GITHUB_ISSUE_NUMBER") | |
| if not issue_number: | |
| print("No issue number found") | |
| sys.exit(0) | |
| log_file = Path(".pyqual/pyqual_output.log") | |
| logs = log_file.read_text() if log_file.exists() else "No logs available" | |
| body = "## β Pyqual failed to process this issue\n\n" | |
| body += "The automated fix attempt failed. Please check the issue details and try again.\n\n" | |
| body += "### Error Output\n<details>\n<summary>Logs</summary>\n\n```\n" | |
| body += logs[:2000] | |
| body += "\n```\n</details>\n\n" | |
| body += "### Next Steps\n" | |
| body += "1. Review the issue description for clarity\n" | |
| body += "2. Ensure the issue has the `pyqual-fix` label\n" | |
| body += "3. Run pyqual locally: `pyqual run --max 5`\n" | |
| body += "4. Check pipeline logs in GitHub Actions\n\n" | |
| body += "---\n" | |
| sha = os.environ.get('GITHUB_SHA', 'unknown')[:8] | |
| body += f"_Failed at: {sha}_" | |
| reporter.post_issue_comment(body, int(issue_number)) | |
| # Add to TODO.md for tracking | |
| todo_path = Path("TODO.md") | |
| if todo_path.exists(): | |
| content = todo_path.read_text() | |
| if "[FAILED]" not in content: | |
| content += f"\n- [ ] [FAILED] Issue #{issue_number} - pyqual failed to fix\n" | |
| todo_path.write_text(content) | |
| EOF | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| - name: Update GitHub issue on success | |
| if: (steps.pyqual-run.outputs.exit_code == '0' && github.event_name == 'issues') || (steps.pyqual-run.outputs.exit_code == '0' && github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number != '') | |
| run: | | |
| python3 << 'EOF' | |
| import os | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, '.') | |
| from pyqual.github_actions import GitHubActionsReporter | |
| from pyqual.gates import GateSet | |
| from pyqual.config import PyqualConfig | |
| reporter = GitHubActionsReporter() | |
| # Get issue number from issues event or workflow_dispatch input | |
| issue_number = os.environ.get("GITHUB_ISSUE_NUMBER") or os.environ.get("INPUT_ISSUE_NUMBER") | |
| print(f"DEBUG: GITHUB_ISSUE_NUMBER = {os.environ.get('GITHUB_ISSUE_NUMBER')}") | |
| print(f"DEBUG: INPUT_ISSUE_NUMBER = {os.environ.get('INPUT_ISSUE_NUMBER')}") | |
| print(f"DEBUG: Final issue_number = {issue_number}") | |
| print(f"DEBUG: Reporter token available = {bool(reporter.token)}") | |
| print(f"DEBUG: Reporter repo = {reporter.repo}") | |
| if not issue_number: | |
| print("No issue number found - skipping comment and close") | |
| sys.exit(0) | |
| print(f"Processing issue #{issue_number}") | |
| # Calculate completion percentage using pyqual gates | |
| try: | |
| config = PyqualConfig.load() | |
| gate_set = GateSet(config.gates) | |
| completion_pct = gate_set.completion_percentage(Path(".")) | |
| print(f"π Ticket completion: {completion_pct:.1f}%") | |
| except Exception as e: | |
| print(f"Could not calculate completion percentage: {e}") | |
| completion_pct = None | |
| # Parse pyqual output for a simple success signal. | |
| log_file = Path(".pyqual/pyqual_output.log") | |
| pyqual_output = log_file.read_text() if log_file.exists() else "" | |
| # Check if all gates passed. | |
| all_gates_passed = "all_gates_passed: true" in pyqual_output or "result: all_gates_passed" in pyqual_output | |
| # The workflow only reaches this step when `pyqual run` exited 0. | |
| # Use the successful pipeline result as the close signal. | |
| can_close = all_gates_passed | |
| print("Quality Gates Check:") | |
| print(f" all_gates_passed: {all_gates_passed}") | |
| print(f" can_close_issue: {can_close}") | |
| # Check if changes were made. | |
| import subprocess | |
| result = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True) | |
| changes = result.stdout.strip() | |
| if can_close: | |
| sha = os.environ.get('GITHUB_SHA', 'unknown')[:8] | |
| pct_str = f"{completion_pct:.1f}%" if completion_pct else "N/A" | |
| if changes: | |
| body = "## β Task completed successfully\n\n" | |
| body += f"**Completion: {pct_str}**\n\n" | |
| body += "All quality gates passed and changes have been made.\n\n" | |
| body += "Changes have been made and pushed to the repository.\n\n" | |
| body += "### Files Modified\n<details>\n<summary>Click to see changes</summary>\n\n```\n" | |
| body += changes[:2000] | |
| body += f"\n```\n</details>\n\n" | |
| body += "---\n" | |
| body += "_Closing this issue as all requirements are met β_" | |
| else: | |
| body = "## β Task completed successfully\n\n" | |
| body += f"**Completion: {pct_str}**\n\n" | |
| body += "The pipeline ran successfully and all quality gates passed.\n\n" | |
| body += "---\n" | |
| body += "_Closing this issue as requirements are already met β_" | |
| # Post success comment. | |
| reporter.post_issue_comment(body, int(issue_number)) | |
| # Close the issue. | |
| if reporter.token and reporter.repo: | |
| import subprocess | |
| close_cmd = [ | |
| "gh", "issue", "close", str(issue_number), | |
| "--repo", reporter.repo, | |
| "--comment", "Auto-closed: All quality gates passed (coverage β₯55%, CC β€15, VALLM β₯90%)" | |
| ] | |
| result = subprocess.run( | |
| close_cmd, | |
| capture_output=True, text=True, | |
| env={**os.environ, "GH_TOKEN": reporter.token} | |
| ) | |
| if result.returncode == 0: | |
| print(f"β Closed issue #{issue_number}") | |
| else: | |
| print(f"β Failed to close issue: {result.stderr[:200]}") | |
| else: | |
| # Don't close - post status update with completion percentage. | |
| sha = os.environ.get('GITHUB_SHA', 'unknown')[:8] | |
| pct_str = f"{completion_pct:.1f}%" if completion_pct else "N/A" | |
| body = f"## π Quality Gates Status - {pct_str} Complete\n\n" | |
| body += "Pipeline completed but not all requirements met:\n\n" | |
| body += f"- **all_gates_passed:** {'β' if all_gates_passed else 'β'}\n" | |
| body += "- **pipeline:** completed, but manual review required\n\n" | |
| body += "Issue remains open - manual review required.\n\n" | |
| body += f"---\n_Processed at: {sha}_" | |
| reporter.post_issue_comment(body, int(issue_number)) | |
| print(f"β Issue #{issue_number} remains open - quality gates not fully met") | |
| # Update TODO.md to mark as done | |
| todo_path = Path("TODO.md") | |
| if todo_path.exists(): | |
| content = todo_path.read_text() | |
| if f"#{issue_number}:" in content: | |
| content = content.replace(f"- [ ] #{issue_number}:", f"- [x] #{issue_number}:") | |
| todo_path.write_text(content) | |
| print(f"Marked issue #{issue_number} as completed in TODO.md") | |
| EOF | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| INPUT_ISSUE_NUMBER: ${{ github.event.inputs.issue_number }} | |
| - name: Push changes if successful | |
| if: steps.pyqual-run.outputs.exit_code == '0' | |
| run: | | |
| if [ -n "$(git status --porcelain)" ]; then | |
| git config user.email "pyqual-bot@semcod.github.io" | |
| git config user.name "Pyqual Bot" | |
| git add -A | |
| git commit -m "fix: pyqual auto-fix [ci skip]" || true | |
| git push origin HEAD || true | |
| fi | |
| - name: Close related GitHub issues on push/PR success | |
| if: steps.pyqual-run.outputs.exit_code == '0' && (github.event_name == 'push' || github.event_name == 'pull_request') | |
| run: | | |
| echo "::group::Closing related GitHub issues" | |
| python3 << 'EOF' | |
| import os | |
| import sys | |
| import re | |
| import subprocess | |
| from pathlib import Path | |
| sys.path.insert(0, '.') | |
| from pyqual.github_actions import GitHubActionsReporter | |
| reporter = GitHubActionsReporter() | |
| if not reporter.token or not reporter.repo: | |
| print("GITHUB_TOKEN or GITHUB_REPOSITORY not set - skipping issue closure") | |
| sys.exit(0) | |
| # Parse pyqual output for metrics | |
| log_file = Path(".pyqual/pyqual_output.log") | |
| pyqual_output = log_file.read_text() if log_file.exists() else "" | |
| # Check if all gates passed | |
| all_gates_passed = "all_gates_passed: true" in pyqual_output or "result: all_gates_passed" in pyqual_output | |
| if not all_gates_passed: | |
| print("Quality gates did not pass - not auto-closing issues") | |
| sys.exit(0) | |
| # Get changed files to find related issues | |
| try: | |
| result = subprocess.run( | |
| ["git", "log", "-1", "--pretty=format:%s%n%b"], | |
| capture_output=True, text=True, timeout=10 | |
| ) | |
| commit_msg = result.stdout | |
| except Exception: | |
| commit_msg = "" | |
| # Look for issue references in commit message (e.g., "Fixes #123", "Closes #456") | |
| issue_refs = re.findall(r'(?:fixes?|closes?|resolves?)\s+#(\d+)', commit_msg, re.IGNORECASE) | |
| # Also check TODO.md for marked-as-done items | |
| todo_path = Path("TODO.md") | |
| if todo_path.exists(): | |
| content = todo_path.read_text() | |
| # Find checked items with issue references: "- [x] #123: ..." | |
| done_issues = re.findall(r'- \[x\] #(\d+):', content) | |
| issue_refs.extend(done_issues) | |
| # Deduplicate | |
| issue_refs = list(set(issue_refs)) | |
| if not issue_refs: | |
| print("No related GitHub issues found to close") | |
| sys.exit(0) | |
| print(f"Found {len(issue_refs)} issue(s) to potentially close: {issue_refs}") | |
| closed_count = 0 | |
| for issue_num in issue_refs: | |
| print(f" Processing issue #{issue_num}...") | |
| # Check if issue is still open | |
| check_result = subprocess.run( | |
| ["gh", "issue", "view", issue_num, "--repo", reporter.repo, "--json", "state"], | |
| capture_output=True, text=True, timeout=30, | |
| env={**os.environ, "GH_TOKEN": reporter.token} | |
| ) | |
| if check_result.returncode != 0: | |
| print(f" Issue #{issue_num} not found or error checking status") | |
| continue | |
| try: | |
| import json | |
| issue_data = json.loads(check_result.stdout) | |
| if issue_data.get("state") != "open": | |
| print(f" Issue #{issue_num} already closed") | |
| continue | |
| except Exception: | |
| pass | |
| # Post success comment | |
| body = "## β Automatically closed via pyqual CI\n\n" | |
| body += "All quality gates passed on the associated changes.\n\n" | |
| body += f"- **Commit:** {os.environ.get('GITHUB_SHA', 'unknown')[:8]}\n" | |
| body += "---\n_Auto-closed by pyqual push/PR workflow_" | |
| reporter.post_issue_comment(body, int(issue_num)) | |
| # Close the issue | |
| close_result = subprocess.run( | |
| ["gh", "issue", "close", issue_num, "--repo", reporter.repo, | |
| "--comment", "Auto-closed: All quality gates passed (push/PR workflow)"], | |
| capture_output=True, text=True, timeout=30, | |
| env={**os.environ, "GH_TOKEN": reporter.token} | |
| ) | |
| if close_result.returncode == 0: | |
| print(f" β Closed issue #{issue_num}") | |
| closed_count += 1 | |
| else: | |
| print(f" β οΈ Failed to close issue #{issue_num}: {close_result.stderr[:100]}") | |
| print(f"\nClosed {closed_count} issue(s)") | |
| EOF | |
| echo "::endgroup::" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pyqual-metrics | |
| path: .pyqual/ | |
| if-no-files-found: ignore | |
| - name: Fail job if pyqual failed | |
| if: steps.pyqual-run.outputs.exit_code != '0' | |
| run: | | |
| echo "::error::Pyqual pipeline failed - see logs and PR comments for details" | |
| exit 1 |