Auto Optimization Draft PR #7
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: Auto Optimization Draft PR | |
| "on": | |
| issues: | |
| types: [opened, edited, reopened, labeled] | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: "Monthly optimization task issue number" | |
| required: true | |
| jobs: | |
| auto-pr: | |
| if: contains(github.event.issue.labels.*.name, 'monthly-optimization-task') || inputs.issue_number != '' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check automation prerequisites | |
| id: prereqs | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| mkdir -p data/output/auto_optimization | |
| if [ -z "${ANTHROPIC_API_KEY}" ]; then | |
| echo "has_anthropic_key=false" >> "$GITHUB_OUTPUT" | |
| echo "Claude automation skipped: ANTHROPIC_API_KEY is not configured for this repo." > data/output/auto_optimization/skip_reason.txt | |
| else | |
| echo "has_anthropic_key=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Load issue context | |
| id: issue_context | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import urllib.request | |
| from pathlib import Path | |
| repo = os.environ["GITHUB_REPOSITORY"] | |
| issue_number = os.environ["ISSUE_NUMBER"] | |
| token = os.environ["GITHUB_TOKEN"] | |
| api_url = f"https://api.github.com/repos/{repo}/issues/{issue_number}" | |
| request = urllib.request.Request( | |
| api_url, | |
| headers={ | |
| "Accept": "application/vnd.github+json", | |
| "Authorization": f"Bearer {token}", | |
| "X-GitHub-Api-Version": "2022-11-28", | |
| "User-Agent": "auto-optimization-pr", | |
| }, | |
| ) | |
| with urllib.request.urlopen(request) as response: | |
| issue = json.load(response) | |
| issue_context = { | |
| "number": issue["number"], | |
| "title": issue["title"], | |
| "body": issue.get("body", ""), | |
| } | |
| output_dir = Path("data/output/auto_optimization") | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| (output_dir / "issue_context.json").write_text( | |
| json.dumps(issue_context, ensure_ascii=False, indent=2) + "\n", | |
| encoding="utf-8", | |
| ) | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: | |
| print("issue_title<<EOF", file=output) | |
| print(issue_context["title"], file=output) | |
| print("EOF", file=output) | |
| print("issue_body<<EOF", file=output) | |
| print(issue_context["body"], file=output) | |
| print("EOF", file=output) | |
| PY | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| ISSUE_NUMBER: ${{ inputs.issue_number || github.event.issue.number }} | |
| - name: Prepare auto optimization payload | |
| id: auto_payload | |
| run: | | |
| python3 scripts/prepare_auto_optimization_pr.py --issue-context-file data/output/auto_optimization/issue_context.json --output-dir data/output/auto_optimization >> "$GITHUB_OUTPUT" | |
| - name: Append task summary | |
| run: cat data/output/auto_optimization/task_summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Export selected task summary | |
| id: selected_tasks | |
| run: | | |
| { | |
| echo "task_summary<<EOF" | |
| cat data/output/auto_optimization/task_summary.md | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Append skip reason | |
| if: steps.prereqs.outputs.has_anthropic_key != 'true' || steps.auto_payload.outputs.should_run != 'true' | |
| run: | | |
| if [ -f data/output/auto_optimization/skip_reason.txt ]; then | |
| cat data/output/auto_optimization/skip_reason.txt >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "No eligible low-risk auto-pr-safe tasks were found; skipping automation." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Prepare automation branch | |
| if: steps.prereqs.outputs.has_anthropic_key == 'true' && steps.auto_payload.outputs.should_run == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git checkout -B "${{ steps.auto_payload.outputs.branch_name }}" | |
| - name: Run Claude auto optimization | |
| if: steps.prereqs.outputs.has_anthropic_key == 'true' && steps.auto_payload.outputs.should_run == 'true' | |
| timeout-minutes: 15 | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| use_bedrock: false | |
| use_vertex: false | |
| claude_args: --max-turns 8 | |
| prompt: | | |
| Do not ask for additional approval. | |
| Do not create a pull request yourself. The workflow will handle git, PR creation, CI dispatch, and post-CI merge. | |
| Only implement the low-risk tasks explicitly marked `[auto-pr-safe]`. | |
| Ignore any medium-risk or high-risk tasks. | |
| You are working inside CryptoStrategies, the shared strategy-logic repository. | |
| Prefer minimal changes in documentation, report wording, validation, shadow/challenger plumbing, instrumentation, and tests. | |
| Do not change production selector logic or ranking behavior from this issue alone. | |
| If an eligible task is marked `experiment-only`, keep the change non-production. | |
| Never edit files under src/ in this automation step. | |
| If the selected low-risk tasks already appear implemented on the current main branch, leave the working tree unchanged. | |
| Do not use Bash in this workflow. Limit yourself to file edits and repository-local reasoning. | |
| The workflow will run CI after the PR is created. | |
| ## Issue Title | |
| ${{ steps.issue_context.outputs.issue_title }} | |
| ## Selected Low-Risk Tasks | |
| ${{ steps.selected_tasks.outputs.task_summary }} | |
| - name: Detect changes | |
| id: changes | |
| if: steps.prereqs.outputs.has_anthropic_key == 'true' && steps.auto_payload.outputs.should_run == 'true' | |
| run: | | |
| if git diff --quiet; then | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Evaluate merge guardrails | |
| id: merge_guard | |
| if: steps.changes.outputs.has_changes == 'true' | |
| run: | | |
| git diff --name-only --relative > data/output/auto_optimization/changed_files.txt | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| from scripts.prepare_auto_optimization_pr import evaluate_changed_files | |
| output_dir = Path("data/output/auto_optimization") | |
| payload = json.loads((output_dir / "payload.json").read_text(encoding="utf-8")) | |
| changed_files = [ | |
| line.strip() | |
| for line in (output_dir / "changed_files.txt").read_text(encoding="utf-8").splitlines() | |
| if line.strip() | |
| ] | |
| guard = evaluate_changed_files(changed_files) | |
| merge_ready = bool(payload.get("task_level_auto_merge_allowed")) and bool(guard["allowed"]) | |
| if not payload.get("task_level_auto_merge_allowed"): | |
| reason = "task_level_guard" | |
| elif not guard["allowed"]: | |
| reason = "sensitive_changed_files" | |
| else: | |
| reason = "ready" | |
| summary_lines = [ | |
| "## Merge Guardrails", | |
| f"- Task-level auto-merge eligible: `{ 'yes' if payload.get('task_level_auto_merge_allowed') else 'no' }`", | |
| f"- Changed files reviewed: `{len(changed_files)}`", | |
| f"- Sensitive files touched: `{len(guard['blocked_files'])}`", | |
| ] | |
| if guard["blocked_files"]: | |
| summary_lines.append("") | |
| summary_lines.append("### Sensitive changed files") | |
| summary_lines.extend(f"- `{path}`" for path in guard["blocked_files"]) | |
| if merge_ready: | |
| summary_lines.extend(["", "Ready PR is allowed; the follow-up auto-merge workflow may merge after CI succeeds."]) | |
| else: | |
| summary_lines.extend(["", f"PR will stay draft. Guard reason: `{reason}`"]) | |
| (output_dir / "guard_summary.md").write_text("\n".join(summary_lines).strip() + "\n", encoding="utf-8") | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: | |
| print(f"merge_ready={'true' if merge_ready else 'false'}", file=output) | |
| print(f"guard_reason={reason}", file=output) | |
| print(f"blocked_file_count={len(guard['blocked_files'])}", file=output) | |
| PY | |
| - name: Append merge guard summary | |
| if: steps.changes.outputs.has_changes == 'true' | |
| run: cat data/output/auto_optimization/guard_summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Commit and push automation branch | |
| if: steps.changes.outputs.has_changes == 'true' | |
| run: | | |
| git add -A | |
| git commit -m "${{ steps.auto_payload.outputs.commit_message }}" | |
| git push --force-with-lease origin "${{ steps.auto_payload.outputs.branch_name }}" | |
| - name: Create or update automation PR | |
| id: automation_pr | |
| if: steps.changes.outputs.has_changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| BRANCH_NAME="${{ steps.auto_payload.outputs.branch_name }}" | |
| PR_TITLE="${{ steps.auto_payload.outputs.pr_title }}" | |
| PR_BODY_FILE="${{ steps.auto_payload.outputs.pr_body_file }}" | |
| MERGE_READY="${{ steps.merge_guard.outputs.merge_ready }}" | |
| EXISTING_PR_NUMBER=$(gh pr list --state open --head "${BRANCH_NAME}" --json number --jq '.[0].number // empty') | |
| if [ -n "${EXISTING_PR_NUMBER}" ]; then | |
| gh pr edit "${EXISTING_PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}" | |
| if [ "${MERGE_READY}" = "true" ]; then | |
| gh pr ready "${EXISTING_PR_NUMBER}" || true | |
| PR_STATE="ready_for_review" | |
| else | |
| gh pr ready "${EXISTING_PR_NUMBER}" --undo || true | |
| PR_STATE="draft" | |
| fi | |
| PR_URL=$(gh pr view "${EXISTING_PR_NUMBER}" --json url --jq '.url') | |
| PR_NUMBER="${EXISTING_PR_NUMBER}" | |
| echo "pr_action=updated" >> "$GITHUB_OUTPUT" | |
| else | |
| CREATE_ARGS=(--base main --head "${BRANCH_NAME}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}") | |
| if [ "${MERGE_READY}" != "true" ]; then | |
| CREATE_ARGS=(--draft "${CREATE_ARGS[@]}") | |
| fi | |
| PR_URL=$(gh pr create "${CREATE_ARGS[@]}") | |
| PR_NUMBER=$(gh pr view "${PR_URL}" --json number --jq '.number') | |
| if [ "${MERGE_READY}" = "true" ]; then | |
| PR_STATE="ready_for_review" | |
| else | |
| PR_STATE="draft" | |
| fi | |
| echo "pr_action=created" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT" | |
| echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" | |
| echo "pr_state=${PR_STATE}" >> "$GITHUB_OUTPUT" | |
| - name: Dispatch CI workflow on automation branch | |
| if: steps.changes.outputs.has_changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh workflow run ci.yml --ref "${{ steps.auto_payload.outputs.branch_name }}" | |
| - name: Append automation result | |
| if: steps.prereqs.outputs.has_anthropic_key == 'true' && steps.auto_payload.outputs.should_run == 'true' | |
| run: | | |
| if [ "${{ steps.changes.outputs.has_changes }}" = "true" ]; then | |
| { | |
| echo "" | |
| echo "## Automation PR Result" | |
| echo "- PR ${{ steps.automation_pr.outputs.pr_action }}: ${{ steps.automation_pr.outputs.pr_url }}" | |
| echo "- PR state: `${{ steps.automation_pr.outputs.pr_state }}`" | |
| echo "- Guard reason: `${{ steps.merge_guard.outputs.guard_reason }}`" | |
| echo "- CI workflow dispatched on branch: `${{ steps.auto_payload.outputs.branch_name }}`" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| if [ "${{ steps.merge_guard.outputs.merge_ready }}" = "true" ]; then | |
| echo "Auto-merge will be handled only after a successful CI workflow run." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| else | |
| echo "No code changes were produced for the selected low-risk tasks." >> "$GITHUB_STEP_SUMMARY" | |
| fi |