9393 - name : Prepare auto optimization payload
9494 id : auto_payload
9595 run : |
96- python3 scripts/prepare_auto_optimization_pr.py \
97- --issue-context-file data/output/auto_optimization/issue_context.json \
98- --output-dir data/output/auto_optimization >> "$GITHUB_OUTPUT"
96+ 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"
9997
10098 - name : Append task summary
10199 run : cat data/output/auto_optimization/task_summary.md >> "$GITHUB_STEP_SUMMARY"
@@ -115,7 +113,7 @@ jobs:
115113 if [ -f data/output/auto_optimization/skip_reason.txt ]; then
116114 cat data/output/auto_optimization/skip_reason.txt >> "$GITHUB_STEP_SUMMARY"
117115 else
118- echo "No eligible low-risk auto-pr-safe tasks were found; skipping draft PR generation ." >> "$GITHUB_STEP_SUMMARY"
116+ echo "No eligible low-risk auto-pr-safe tasks were found; skipping automation ." >> "$GITHUB_STEP_SUMMARY"
119117 fi
120118
121119 - name : Prepare automation branch
@@ -137,15 +135,17 @@ jobs:
137135 claude_args : --max-turns 8
138136 prompt : |
139137 Do not ask for additional approval.
140- Do not create a pull request yourself. The workflow will handle git, draft PR creation, and CI dispatch .
138+ Do not create a pull request yourself. The workflow will handle git, PR creation, CI dispatch, and post- CI merge .
141139 Only implement the low-risk tasks explicitly marked `[auto-pr-safe]`.
142140 Ignore any medium-risk or high-risk tasks.
143141 You are working inside CryptoStrategies, the shared strategy-logic repository.
144- Prefer minimal changes in shared helpers, documentation, and tests.
145- Avoid changing shared production strategy behavior unless the task remains clearly low-risk and local.
146- If the selected low-risk tasks do not map cleanly to this repository, leave the working tree unchanged.
142+ Prefer minimal changes in documentation, report wording, validation, shadow/challenger plumbing, instrumentation, and tests.
143+ Do not change production selector logic or ranking behavior from this issue alone.
144+ If an eligible task is marked `experiment-only`, keep the change non-production.
145+ Never edit files under src/ in this automation step.
146+ If the selected low-risk tasks already appear implemented on the current main branch, leave the working tree unchanged.
147147 Do not use Bash in this workflow. Limit yourself to file edits and repository-local reasoning.
148- The workflow will run CI after the draft PR is created.
148+ The workflow will run CI after the PR is created.
149149
150150 ## Issue Title
151151 ${{ steps.issue_context.outputs.issue_title }}
@@ -163,32 +163,107 @@ jobs:
163163 echo "has_changes=true" >> "$GITHUB_OUTPUT"
164164 fi
165165
166+ - name : Evaluate merge guardrails
167+ id : merge_guard
168+ if : steps.changes.outputs.has_changes == 'true'
169+ run : |
170+ git diff --name-only --relative > data/output/auto_optimization/changed_files.txt
171+ python3 - <<'PY'
172+ import json
173+ import os
174+ from pathlib import Path
175+
176+ from scripts.prepare_auto_optimization_pr import evaluate_changed_files
177+
178+ output_dir = Path("data/output/auto_optimization")
179+ payload = json.loads((output_dir / "payload.json").read_text(encoding="utf-8"))
180+ changed_files = [
181+ line.strip()
182+ for line in (output_dir / "changed_files.txt").read_text(encoding="utf-8").splitlines()
183+ if line.strip()
184+ ]
185+ guard = evaluate_changed_files(changed_files)
186+ merge_ready = bool(payload.get("task_level_auto_merge_allowed")) and bool(guard["allowed"])
187+ if not payload.get("task_level_auto_merge_allowed"):
188+ reason = "task_level_guard"
189+ elif not guard["allowed"]:
190+ reason = "sensitive_changed_files"
191+ else:
192+ reason = "ready"
193+
194+ summary_lines = [
195+ "## Merge Guardrails",
196+ f"- Task-level auto-merge eligible: `{ 'yes' if payload.get('task_level_auto_merge_allowed') else 'no' }`",
197+ f"- Changed files reviewed: `{len(changed_files)}`",
198+ f"- Sensitive files touched: `{len(guard['blocked_files'])}`",
199+ ]
200+ if guard["blocked_files"]:
201+ summary_lines.append("")
202+ summary_lines.append("### Sensitive changed files")
203+ summary_lines.extend(f"- `{path}`" for path in guard["blocked_files"])
204+ if merge_ready:
205+ summary_lines.extend(["", "Ready PR is allowed; the follow-up auto-merge workflow may merge after CI succeeds."])
206+ else:
207+ summary_lines.extend(["", f"PR will stay draft. Guard reason: `{reason}`"])
208+
209+ (output_dir / "guard_summary.md").write_text("\n".join(summary_lines).strip() + "\n", encoding="utf-8")
210+ with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
211+ print(f"merge_ready={'true' if merge_ready else 'false'}", file=output)
212+ print(f"guard_reason={reason}", file=output)
213+ print(f"blocked_file_count={len(guard['blocked_files'])}", file=output)
214+ PY
215+
216+ - name : Append merge guard summary
217+ if : steps.changes.outputs.has_changes == 'true'
218+ run : cat data/output/auto_optimization/guard_summary.md >> "$GITHUB_STEP_SUMMARY"
219+
166220 - name : Commit and push automation branch
167221 if : steps.changes.outputs.has_changes == 'true'
168222 run : |
169223 git add -A
170224 git commit -m "${{ steps.auto_payload.outputs.commit_message }}"
171225 git push --force-with-lease origin "${{ steps.auto_payload.outputs.branch_name }}"
172226
173- - name : Create or update draft PR
174- id : draft_pr
227+ - name : Create or update automation PR
228+ id : automation_pr
175229 if : steps.changes.outputs.has_changes == 'true'
176230 env :
177231 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
178232 run : |
179233 BRANCH_NAME="${{ steps.auto_payload.outputs.branch_name }}"
180234 PR_TITLE="${{ steps.auto_payload.outputs.pr_title }}"
181235 PR_BODY_FILE="${{ steps.auto_payload.outputs.pr_body_file }}"
236+ MERGE_READY="${{ steps.merge_guard.outputs.merge_ready }}"
182237 EXISTING_PR_NUMBER=$(gh pr list --state open --head "${BRANCH_NAME}" --json number --jq '.[0].number // empty')
183238 if [ -n "${EXISTING_PR_NUMBER}" ]; then
184239 gh pr edit "${EXISTING_PR_NUMBER}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}"
240+ if [ "${MERGE_READY}" = "true" ]; then
241+ gh pr ready "${EXISTING_PR_NUMBER}" || true
242+ PR_STATE="ready_for_review"
243+ else
244+ gh pr ready "${EXISTING_PR_NUMBER}" --undo || true
245+ PR_STATE="draft"
246+ fi
185247 PR_URL=$(gh pr view "${EXISTING_PR_NUMBER}" --json url --jq '.url')
248+ PR_NUMBER="${EXISTING_PR_NUMBER}"
186249 echo "pr_action=updated" >> "$GITHUB_OUTPUT"
187250 else
188- PR_URL=$(gh pr create --draft --base main --head "${BRANCH_NAME}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}")
251+ CREATE_ARGS=(--base main --head "${BRANCH_NAME}" --title "${PR_TITLE}" --body-file "${PR_BODY_FILE}")
252+ if [ "${MERGE_READY}" != "true" ]; then
253+ CREATE_ARGS=(--draft "${CREATE_ARGS[@]}")
254+ fi
255+ PR_URL=$(gh pr create "${CREATE_ARGS[@]}")
256+ PR_NUMBER=$(gh pr view "${PR_URL}" --json number --jq '.number')
257+ if [ "${MERGE_READY}" = "true" ]; then
258+ PR_STATE="ready_for_review"
259+ else
260+ PR_STATE="draft"
261+ fi
189262 echo "pr_action=created" >> "$GITHUB_OUTPUT"
190263 fi
191264 echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
265+ echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
266+ echo "pr_state=${PR_STATE}" >> "$GITHUB_OUTPUT"
192267
193268 - name : Dispatch CI workflow on automation branch
194269 if : steps.changes.outputs.has_changes == 'true'
@@ -202,10 +277,15 @@ jobs:
202277 if [ "${{ steps.changes.outputs.has_changes }}" = "true" ]; then
203278 {
204279 echo ""
205- echo "## Draft PR Result"
206- echo "- Draft PR ${{ steps.draft_pr.outputs.pr_action }}: ${{ steps.draft_pr.outputs.pr_url }}"
280+ echo "## Automation PR Result"
281+ echo "- PR ${{ steps.automation_pr.outputs.pr_action }}: ${{ steps.automation_pr.outputs.pr_url }}"
282+ echo "- PR state: `${{ steps.automation_pr.outputs.pr_state }}`"
283+ echo "- Guard reason: `${{ steps.merge_guard.outputs.guard_reason }}`"
207284 echo "- CI workflow dispatched on branch: `${{ steps.auto_payload.outputs.branch_name }}`"
208285 } >> "$GITHUB_STEP_SUMMARY"
286+ if [ "${{ steps.merge_guard.outputs.merge_ready }}" = "true" ]; then
287+ echo "Auto-merge will be handled only after a successful CI workflow run." >> "$GITHUB_STEP_SUMMARY"
288+ fi
209289 else
210290 echo "No code changes were produced for the selected low-risk tasks." >> "$GITHUB_STEP_SUMMARY"
211291 fi
0 commit comments