From 039b591356696a43c38ce6f71153a9c56c3f4f3d Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 24 May 2026 16:49:07 +0800 Subject: [PATCH] chore: remove legacy review optimization chain --- .../workflows/auto_merge_optimization_pr.yml | 110 ------ .github/workflows/codex_pr_feedback.yml | 212 ---------- .github/workflows/experiment_validation.yml | 140 ------- .../monthly_optimization_planner.yml | 145 ------- README.md | 12 - docs/operator_runbook.md | 6 - scripts/build_ai_review_payload.py | 91 ----- scripts/build_monthly_optimization_plan.py | 254 ------------ scripts/download_ai_review_artifact.py | 77 ---- scripts/fanout_monthly_optimization_tasks.py | 361 ------------------ scripts/post_experiment_validation_comment.py | 100 ----- scripts/post_monthly_ai_review_comment.py | 134 ------- scripts/post_monthly_optimization_issue.py | 143 ------- scripts/prepare_auto_optimization_pr.py | 353 ----------------- scripts/prepare_experiment_validation.py | 122 ------ .../render_experiment_validation_summary.py | 97 ----- scripts/render_monthly_ai_review.py | 125 ------ scripts/run_openai_secondary_review.py | 214 ----------- ...st_auto_optimization_pr_workflow_config.py | 54 --- tests/test_build_ai_review_payload.py | 36 -- tests/test_build_monthly_optimization_plan.py | 151 -------- tests/test_download_ai_review_artifact.py | 25 -- ...t_experiment_validation_workflow_config.py | 30 -- .../test_fanout_monthly_optimization_tasks.py | 126 ------ ...ly_optimization_planner_workflow_config.py | 42 -- tests/test_monthly_publish_workflow_config.py | 4 + ...test_post_experiment_validation_comment.py | 22 -- tests/test_post_monthly_ai_review_comment.py | 50 --- tests/test_post_monthly_optimization_issue.py | 33 -- tests/test_prepare_auto_optimization_pr.py | 109 ------ tests/test_prepare_experiment_validation.py | 53 --- ...st_render_experiment_validation_summary.py | 43 --- tests/test_render_monthly_ai_review.py | 61 --- tests/test_run_openai_secondary_review.py | 58 --- 34 files changed, 4 insertions(+), 3589 deletions(-) delete mode 100644 .github/workflows/auto_merge_optimization_pr.yml delete mode 100644 .github/workflows/codex_pr_feedback.yml delete mode 100644 .github/workflows/experiment_validation.yml delete mode 100644 .github/workflows/monthly_optimization_planner.yml delete mode 100644 scripts/build_ai_review_payload.py delete mode 100644 scripts/build_monthly_optimization_plan.py delete mode 100644 scripts/download_ai_review_artifact.py delete mode 100644 scripts/fanout_monthly_optimization_tasks.py delete mode 100644 scripts/post_experiment_validation_comment.py delete mode 100644 scripts/post_monthly_ai_review_comment.py delete mode 100644 scripts/post_monthly_optimization_issue.py delete mode 100644 scripts/prepare_auto_optimization_pr.py delete mode 100644 scripts/prepare_experiment_validation.py delete mode 100644 scripts/render_experiment_validation_summary.py delete mode 100644 scripts/render_monthly_ai_review.py delete mode 100644 scripts/run_openai_secondary_review.py delete mode 100644 tests/test_auto_optimization_pr_workflow_config.py delete mode 100644 tests/test_build_ai_review_payload.py delete mode 100644 tests/test_build_monthly_optimization_plan.py delete mode 100644 tests/test_download_ai_review_artifact.py delete mode 100644 tests/test_experiment_validation_workflow_config.py delete mode 100644 tests/test_fanout_monthly_optimization_tasks.py delete mode 100644 tests/test_monthly_optimization_planner_workflow_config.py delete mode 100644 tests/test_post_experiment_validation_comment.py delete mode 100644 tests/test_post_monthly_ai_review_comment.py delete mode 100644 tests/test_post_monthly_optimization_issue.py delete mode 100644 tests/test_prepare_auto_optimization_pr.py delete mode 100644 tests/test_prepare_experiment_validation.py delete mode 100644 tests/test_render_experiment_validation_summary.py delete mode 100644 tests/test_render_monthly_ai_review.py delete mode 100644 tests/test_run_openai_secondary_review.py diff --git a/.github/workflows/auto_merge_optimization_pr.yml b/.github/workflows/auto_merge_optimization_pr.yml deleted file mode 100644 index 364b8da..0000000 --- a/.github/workflows/auto_merge_optimization_pr.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Auto Merge Optimization PR - -"on": - workflow_run: - workflows: ["CI"] - types: [completed] - -jobs: - auto-merge: - if: github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'codex/monthly-optimization-issue-') - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Resolve automation PR - id: pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p data/output/auto_merge - BRANCH_NAME="${{ github.event.workflow_run.head_branch }}" - PR_NUMBER=$(gh pr list --state open --head "${BRANCH_NAME}" --json number --jq '.[0].number // empty') - if [ -z "${PR_NUMBER}" ]; then - echo "No open automation PR found for ${BRANCH_NAME}." >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" - - - name: Evaluate merge eligibility - id: merge_guard - if: steps.pr.outputs.pr_number != '' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} - run: | - gh pr view "${{ steps.pr.outputs.pr_number }}" --json number,isDraft,body,url,files,labels > data/output/auto_merge/pr.json - python3 - <<'PY' - import json - import os - from pathlib import Path - - from scripts.prepare_auto_optimization_pr import evaluate_changed_files - - pr = json.loads(Path("data/output/auto_merge/pr.json").read_text(encoding="utf-8")) - body = pr.get("body") or "" - changed_files = [item.get("path", "") for item in pr.get("files", [])] - labels = {item.get("name", "") for item in pr.get("labels", [])} - branch_name = os.environ["BRANCH_NAME"] - codex_branch = branch_name.startswith("codex/monthly-optimization-issue-") - guard = evaluate_changed_files(changed_files) - has_marker = "", body) - if not match: - Path("data/output/codex_feedback/skip.txt").write_text("No source issue marker found.\n", encoding="utf-8") - raise SystemExit(0) - - issue_number = match.group(1) - comment = textwrap.dedent( - f"""\ - - ## Codex PR CI Feedback - - CI failed for the Codex remediation PR. - - - PR: {pr['url']} - - Branch: `{os.environ['BRANCH_NAME']}` - - Workflow: `{os.environ['RUN_NAME']}` - - Run: {os.environ['RUN_URL']} - - Codex should inspect the failing GitHub Actions logs, update the same PR branch, run targeted tests, and keep the PR draft until the fix is verified. - """ - ) - Path("data/output/codex_feedback/issue_number.txt").write_text(issue_number, encoding="utf-8") - Path("data/output/codex_feedback/comment.md").write_text(comment.strip() + "\n", encoding="utf-8") - PY - if [ -f data/output/codex_feedback/issue_number.txt ]; then - issue_number="$(cat data/output/codex_feedback/issue_number.txt)" - gh issue view "${issue_number}" --comments --json comments > data/output/codex_feedback/issue.json - python3 - <<'PY' - import json - import os - import textwrap - from pathlib import Path - - output_dir = Path("data/output/codex_feedback") - issue = json.loads((output_dir / "issue.json").read_text(encoding="utf-8")) - comments = [comment.get("body") or "" for comment in issue.get("comments", [])] - previous_rounds = sum(body.startswith(" - ## Codex PR Retry Limit Reached - - Automatic Codex feedback reached the retry limit. - - - Previous feedback rounds: `{previous_rounds}` - - Maximum automatic rounds: `{max_rounds}` - - The workflow removed `codex-bridge` from this issue. Please inspect the PR and re-apply the label only if another automated Codex pass is still appropriate. - """ - ) - comment_path.write_text(comment.strip() + "\n", encoding="utf-8") - (output_dir / "limit_reached").write_text("true\n", encoding="utf-8") - else: - attempt = previous_rounds + 1 - comment = comment_path.read_text(encoding="utf-8").rstrip() - comment_path.write_text( - f"{comment}\n\n- Feedback round: `{attempt}` of `{max_rounds}`\n", - encoding="utf-8", - ) - PY - if [ -f data/output/codex_feedback/limit_reached ]; then - gh issue edit "${issue_number}" --remove-label codex-bridge || true - fi - gh issue comment "${issue_number}" --body-file data/output/codex_feedback/comment.md - else - cat data/output/codex_feedback/skip.txt >> "$GITHUB_STEP_SUMMARY" - fi - - review-feedback: - if: github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'codex/monthly-optimization-issue-') - runs-on: ubuntu-latest - permissions: - issues: write - - steps: - - name: Post review feedback back to Codex issue - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_URL: ${{ github.event.pull_request.html_url }} - PR_BODY: ${{ github.event.pull_request.body }} - REVIEW_URL: ${{ github.event.review.html_url }} - REVIEW_AUTHOR: ${{ github.event.review.user.login }} - REVIEW_BODY: ${{ github.event.review.body }} - MAX_CODEX_FEEDBACK_ROUNDS: "3" - run: | - mkdir -p data/output/codex_feedback - python3 - <<'PY' - import os - import re - import textwrap - from pathlib import Path - - body = os.environ.get("PR_BODY") or "" - match = re.search(r"", body) - if not match: - Path("data/output/codex_feedback/skip.txt").write_text("No source issue marker found.\n", encoding="utf-8") - raise SystemExit(0) - - review_body = (os.environ.get("REVIEW_BODY") or "_No review body supplied._").strip() - comment = textwrap.dedent( - f"""\ - - ## Codex PR Review Feedback - - A review requested changes on the Codex remediation PR. - - - PR: {os.environ['PR_URL']} - - Reviewer: @{os.environ['REVIEW_AUTHOR']} - - Review: {os.environ['REVIEW_URL']} - - ### Review Body - - {review_body} - - Codex should update the same PR branch, address the requested changes, run targeted tests, and leave the PR draft until the fix is verified. - """ - ) - Path("data/output/codex_feedback/issue_number.txt").write_text(match.group(1), encoding="utf-8") - Path("data/output/codex_feedback/comment.md").write_text(comment.strip() + "\n", encoding="utf-8") - PY - if [ -f data/output/codex_feedback/issue_number.txt ]; then - issue_number="$(cat data/output/codex_feedback/issue_number.txt)" - gh issue view "${issue_number}" --comments --json comments > data/output/codex_feedback/issue.json - python3 - <<'PY' - import json - import os - import textwrap - from pathlib import Path - - output_dir = Path("data/output/codex_feedback") - issue = json.loads((output_dir / "issue.json").read_text(encoding="utf-8")) - comments = [comment.get("body") or "" for comment in issue.get("comments", [])] - previous_rounds = sum(body.startswith(" - ## Codex PR Retry Limit Reached - - Automatic Codex feedback reached the retry limit. - - - Previous feedback rounds: `{previous_rounds}` - - Maximum automatic rounds: `{max_rounds}` - - The workflow removed `codex-bridge` from this issue. Please inspect the PR and re-apply the label only if another automated Codex pass is still appropriate. - """ - ) - comment_path.write_text(comment.strip() + "\n", encoding="utf-8") - (output_dir / "limit_reached").write_text("true\n", encoding="utf-8") - else: - attempt = previous_rounds + 1 - comment = comment_path.read_text(encoding="utf-8").rstrip() - comment_path.write_text( - f"{comment}\n\n- Feedback round: `{attempt}` of `{max_rounds}`\n", - encoding="utf-8", - ) - PY - if [ -f data/output/codex_feedback/limit_reached ]; then - gh issue edit "${issue_number}" --remove-label codex-bridge || true - fi - gh issue comment "${issue_number}" --body-file data/output/codex_feedback/comment.md - else - cat data/output/codex_feedback/skip.txt >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.github/workflows/experiment_validation.yml b/.github/workflows/experiment_validation.yml deleted file mode 100644 index 7f2a229..0000000 --- a/.github/workflows/experiment_validation.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: Experiment Validation - -"on": - workflow_dispatch: - inputs: - issue_number: - description: "Monthly optimization task issue number" - required: true - -jobs: - validate: - runs-on: - - self-hosted - - Linux - - X64 - permissions: - contents: read - issues: write - env: - DOWNLOAD_TOP_LIQUID: ${{ vars.DOWNLOAD_TOP_LIQUID || '90' }} - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - - name: Install dependencies - run: | - set -euo pipefail - REQ_FILE="requirements-lock.txt" - if [ ! -f "$REQ_FILE" ]; then REQ_FILE="requirements.txt"; fi - python -m pip install --upgrade pip - python -m pip install -r "$REQ_FILE" - - - 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": "experiment-validation", - }, - ) - 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/experiment_validation") - 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", - ) - PY - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ inputs.issue_number }} - - - name: Prepare experiment validation payload - id: experiment_payload - run: | - python3 scripts/prepare_experiment_validation.py \ - --issue-context-file data/output/experiment_validation/issue_context.json \ - --output-dir data/output/experiment_validation >> "$GITHUB_OUTPUT" - - - name: Append task summary - run: cat data/output/experiment_validation/task_summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Append skip reason - if: steps.experiment_payload.outputs.should_run != 'true' - run: | - if [ -f data/output/experiment_validation/skip_reason.txt ]; then - cat data/output/experiment_validation/skip_reason.txt >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Download raw history for experiment validation - if: steps.experiment_payload.outputs.should_run == 'true' - run: | - python3 scripts/download_history.py --top-liquid "${DOWNLOAD_TOP_LIQUID}" --force-exchange-info - - - name: Run monthly shadow build - if: steps.experiment_payload.outputs.should_run == 'true' && steps.experiment_payload.outputs.run_shadow_build == 'true' - run: | - python3 scripts/run_monthly_shadow_build.py --skip-publish-dry-run - - - name: Run walk-forward validation - if: steps.experiment_payload.outputs.should_run == 'true' && steps.experiment_payload.outputs.run_walkforward_validation == 'true' - run: | - python3 scripts/run_walkforward_validation.py - - - name: Render validation summary - if: steps.experiment_payload.outputs.should_run == 'true' - run: | - python3 scripts/render_experiment_validation_summary.py \ - --payload-file data/output/experiment_validation/payload.json \ - --shadow-summary-file data/output/monthly_shadow_build_summary.json \ - --output-file data/output/experiment_validation/validation_summary.md - - - name: Append validation summary - if: steps.experiment_payload.outputs.should_run == 'true' - run: cat data/output/experiment_validation/validation_summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Post validation comment - if: steps.experiment_payload.outputs.should_run == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - python3 scripts/post_experiment_validation_comment.py \ - --repo "${GITHUB_REPOSITORY}" \ - --issue-number "${{ inputs.issue_number }}" \ - --review-file data/output/experiment_validation/validation_summary.md \ - --run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - - - name: Upload validation artifact - if: always() - uses: actions/upload-artifact@v7 - with: - name: experiment-validation-${{ inputs.issue_number }} - path: data/output/experiment_validation/ diff --git a/.github/workflows/monthly_optimization_planner.yml b/.github/workflows/monthly_optimization_planner.yml deleted file mode 100644 index cdb0768..0000000 --- a/.github/workflows/monthly_optimization_planner.yml +++ /dev/null @@ -1,145 +0,0 @@ -name: Monthly Optimization Planner - -"on": - workflow_dispatch: - inputs: - upstream_run_id: - description: "AI review run id from CryptoSnapshotPipelines" - required: true - -jobs: - planner: - runs-on: ubuntu-latest - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - permissions: - actions: write - contents: read - issues: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download upstream AI review artifact - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p data/input/upstream - gh run download "${{ inputs.upstream_run_id }}" \ - --repo "${GITHUB_REPOSITORY}" \ - --dir data/input/upstream - - - name: Resolve downloaded artifact paths - id: artifact_paths - run: | - UPSTREAM_DIR=$(find data/input/upstream -mindepth 1 -maxdepth 1 -type d | head -1) - if [ -z "${UPSTREAM_DIR}" ]; then - echo "Failed to resolve downloaded upstream artifact directory" >&2 - exit 1 - fi - { - echo "upstream_dir=${UPSTREAM_DIR}" - } >> "${GITHUB_OUTPUT}" - - - name: Prepare upstream review payload - run: | - python3 scripts/build_ai_review_payload.py \ - --source-repo "${GITHUB_REPOSITORY}" \ - --review-kind upstream_selector \ - --issue-context-file "${{ steps.artifact_paths.outputs.upstream_dir }}/issue_context.json" \ - --secondary-review-file "${{ steps.artifact_paths.outputs.upstream_dir }}/secondary_review.json" \ - --run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${{ inputs.upstream_run_id }}" \ - --output-file data/output/prepared/upstream_review_payload.json - - - name: Build monthly optimization plan - run: | - python3 scripts/build_monthly_optimization_plan.py \ - --upstream-review-file data/output/prepared/upstream_review_payload.json \ - --output-dir data/output/monthly_optimization - - - name: Append optimization summary - run: cat data/output/monthly_optimization/optimization_summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Create monthly optimization issue - id: optimization_issue - run: | - issue_output=$(python3 scripts/post_monthly_optimization_issue.py \ - --repo "${GITHUB_REPOSITORY}" \ - --plan-file data/output/monthly_optimization/optimization_plan.json \ - --summary-file data/output/monthly_optimization/optimization_summary.md) - echo "$issue_output" - issue_number=$(printf '%s\n' "$issue_output" | awk -F= '/^issue_number=/{print $2}' | tail -1) - if [ -z "$issue_number" ]; then - echo "Failed to capture optimization issue number" >&2 - exit 1 - fi - echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Fan out CryptoSnapshotPipelines task issue - run: | - python3 scripts/fanout_monthly_optimization_tasks.py \ - --plan-file data/output/monthly_optimization/optimization_plan.json \ - --owner-repo CryptoSnapshotPipelines \ - --repo "${GITHUB_REPOSITORY}" \ - --planner-issue-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/issues/${{ steps.optimization_issue.outputs.issue_number }}" \ - --output-file data/output/monthly_optimization/fanout/crypto_snapshot_pipelines.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Append fanout summary - run: | - python3 - <<'PY' >> "$GITHUB_STEP_SUMMARY" - import json - from pathlib import Path - - result_dir = Path("data/output/monthly_optimization/fanout") - print("\n## Repo Task Fanout") - for path in sorted(result_dir.glob("*.json")): - data = json.loads(path.read_text(encoding="utf-8")) - line = f"- **{data['owner_repo']}** `{data['status']}`" - if data.get("issue_url"): - line += f": {data['issue_url']}" - elif data.get("reason"): - line += f": {data['reason']}" - print(line) - PY - - - name: Resolve upstream experiment validation target - id: upstream_experiment_target - run: | - python3 - <<'PY' - import json - import os - from pathlib import Path - - fanout = json.loads( - Path("data/output/monthly_optimization/fanout/crypto_snapshot_pipelines.json").read_text(encoding="utf-8") - ) - plan = json.loads( - Path("data/output/monthly_optimization/optimization_plan.json").read_text(encoding="utf-8") - ) - actions = plan.get("repo_action_summary", {}).get("CryptoSnapshotPipelines", {}).get("actions", []) - should_dispatch = bool(fanout.get("issue_number")) and fanout.get("status") in {"created", "updated"} and any( - action.get("experiment_only") for action in actions - ) - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: - print(f"should_dispatch={'true' if should_dispatch else 'false'}", file=output) - print(f"issue_number={fanout.get('issue_number') or ''}", file=output) - PY - - - name: Dispatch CryptoSnapshotPipelines experiment validation - if: steps.upstream_experiment_target.outputs.should_dispatch == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh workflow run experiment_validation.yml \ - -f issue_number="${{ steps.upstream_experiment_target.outputs.issue_number }}" - - - name: Upload planner artifact - uses: actions/upload-artifact@v7 - with: - name: monthly-optimization-plan-${{ inputs.upstream_run_id }} - path: data/output/monthly_optimization/ diff --git a/README.md b/README.md index 40226de..225d29d 100644 --- a/README.md +++ b/README.md @@ -567,18 +567,6 @@ gh secret set OPENAI_API_KEY --repo QuantStrategyLab/CryptoCodexAuditBridge --bo Source-local legacy AI review workflows are intentionally not kept in this repository. Provider fallback lives in `CryptoCodexAuditBridge`, so this source repository does not need Anthropic/OpenAI secrets. -### Codex Remediation and Auto-Merge Gate - -The monthly optimization planner creates repo-scoped issues only for this snapshot repository. Low-risk non-experiment tasks marked `[auto-pr-safe]` are labeled with: - -- `monthly-optimization-task` -- `codex-bridge` -- `auto-merge-ok` - -The `codex-bridge` label is consumed by the self-hosted VPS ccbot/Codex runner. Codex should open a draft PR from `codex/monthly-optimization-issue-`, include `` in the PR body, and mark the PR ready only after targeted tests pass. The post-CI `auto_merge_optimization_pr.yml` workflow can merge Codex PRs only when the PR is non-draft, carries `auto-merge-ok`, has the expected marker, reports task-level auto-merge eligibility, and touches no guarded selector/config paths. - -If a Codex remediation PR fails CI or receives a changes-requested review, `codex_pr_feedback.yml` comments the failure or review summary back to the source `codex-bridge` issue. Because the VPS bridge re-dispatches updated issues, Codex can fix the same PR branch without manual handoff. The workflow allows up to three automatic feedback rounds; after that it removes `codex-bridge` so the issue waits for human review. - ## Dynamic Universe Logic The universe is a hard filter layer, not the final holdings set. diff --git a/docs/operator_runbook.md b/docs/operator_runbook.md index d6a7461..586954e 100644 --- a/docs/operator_runbook.md +++ b/docs/operator_runbook.md @@ -80,12 +80,6 @@ The monthly publish workflow creates a `monthly-review` issue, then dispatches ` If the bridge dispatch fails, the monthly publish workflow fails loudly. Source-local legacy AI review workflows are intentionally removed; provider fallback lives in `CryptoCodexAuditBridge`. -The monthly optimization planner may still create repo-scoped follow-up issues after AI review, but only for `CryptoSnapshotPipelines`. Low-risk non-experiment tasks marked `[auto-pr-safe]` are queued to the self-hosted VPS ccbot/Codex runner with the `codex-bridge` label. - -Codex remediation PRs must use branch `codex/monthly-optimization-issue-`, include `` in the PR body, and start as draft. The auto-merge workflow only merges after CI passes, the PR is ready for review, `auto-merge-ok` is present, task-level auto-merge eligibility is recorded, and changed files stay outside guarded selector/config paths. - -If CI fails on a Codex remediation PR, or a reviewer requests changes, `Codex PR Feedback` comments the failure or review summary back to the source `codex-bridge` issue. The issue update lets the VPS bridge dispatch Codex again to fix the same PR branch. The workflow permits up to three automatic feedback rounds, then removes `codex-bridge` and leaves the issue for human review. - ## Standard Monthly Flow 1. Refresh or verify local data: diff --git a/scripts/build_ai_review_payload.py b/scripts/build_ai_review_payload.py deleted file mode 100644 index eb37aca..0000000 --- a/scripts/build_ai_review_payload.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from pathlib import Path -from typing import Any - - -SCHEMA_VERSION = "2026-04-02" -REPO_ROLE_BY_KIND = { - "upstream_selector": "upstream_selector_review", -} - - -def build_review_payload( - *, - source_repo: str, - review_kind: str, - issue_context: dict[str, Any], - secondary_review: dict[str, Any], - run_url: str, -) -> dict[str, Any]: - if review_kind not in REPO_ROLE_BY_KIND: - raise ValueError(f"Unsupported review kind: {review_kind}") - - issue_number = int(issue_context["number"]) - issue_title = str(issue_context["title"]).strip() - issue_url = f"https://github.com/{source_repo}/issues/{issue_number}" - - return { - "schema_version": SCHEMA_VERSION, - "source_repo": source_repo, - "repo_role": REPO_ROLE_BY_KIND[review_kind], - "review_kind": review_kind, - "source_issue": { - "number": issue_number, - "title": issue_title, - "url": issue_url, - }, - "run_url": run_url.strip(), - "primary_reviewer": { - "provider": "anthropic", - "display_name": "Claude Primary Review", - }, - "secondary_reviewer": { - "provider": secondary_review["provider"], - "display_name": secondary_review["provider_display_name"], - "model": secondary_review["model"], - }, - "verdict": secondary_review["verdict"], - "risk_level": secondary_review["risk_level"], - "production_recommendation": secondary_review["production_recommendation"], - "summary": secondary_review["summary"], - "key_findings": list(secondary_review.get("key_findings", [])), - "recommended_actions": list(secondary_review.get("recommended_actions", [])), - "follow_up_checks": list(secondary_review.get("follow_up_checks", [])), - } - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Build the normalized final AI review payload used by downstream planners.", - ) - parser.add_argument("--source-repo", required=True) - parser.add_argument("--review-kind", required=True, choices=sorted(REPO_ROLE_BY_KIND)) - parser.add_argument("--issue-context-file", required=True, type=Path) - parser.add_argument("--secondary-review-file", required=True, type=Path) - parser.add_argument("--run-url", required=True) - parser.add_argument("--output-file", required=True, type=Path) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - issue_context = json.loads(args.issue_context_file.read_text(encoding="utf-8")) - secondary_review = json.loads(args.secondary_review_file.read_text(encoding="utf-8")) - payload = build_review_payload( - source_repo=args.source_repo, - review_kind=args.review_kind, - issue_context=issue_context, - secondary_review=secondary_review, - run_url=args.run_url, - ) - args.output_file.parent.mkdir(parents=True, exist_ok=True) - args.output_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - print(f"final_review_payload={args.output_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/build_monthly_optimization_plan.py b/scripts/build_monthly_optimization_plan.py deleted file mode 100644 index 88fa64f..0000000 --- a/scripts/build_monthly_optimization_plan.py +++ /dev/null @@ -1,254 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from collections import defaultdict -from datetime import UTC, datetime -from pathlib import Path -from typing import Any - - -RISK_ORDER = {"low": 0, "medium": 1, "high": 2} -SCHEMA_VERSION = "2026-04-02" -REPO_ORDER = ["CryptoSnapshotPipelines"] -MANUAL_REVIEW_PREFIXES = ( - "check ", - "review ", - "audit ", - "reconcile ", - "assess ", - "confirm ", - "determine ", - "verify ", -) - - -def _combined_action_text(action: dict[str, Any]) -> str: - return f"{action.get('title', '')} {action.get('summary', '')}".lower() - - -def _resolve_owner_repo(source_review: dict[str, Any], action: dict[str, Any]) -> str: - text = _combined_action_text(action) - - if any( - marker in text - for marker in ( - "shadow build", - "challenger", - "tie-break", - "tie break", - "equal scores", - "boundary", - "near-cutoff", - "near-cutoff", - "selection threshold", - ) - ): - return "CryptoSnapshotPipelines" - - return action["owner_repo"] - - -def _resolve_auto_pr_safe(source_review: dict[str, Any], action: dict[str, Any]) -> bool: - title = str(action.get("title", "")).strip().lower() - summary = str(action.get("summary", "")).strip().lower() - if not bool(action.get("auto_pr_safe")): - return False - - if title.startswith(MANUAL_REVIEW_PREFIXES): - return False - if any( - phrase in summary - for phrase in ( - "assess whether", - "confirm whether", - "determine whether", - "verify minimum order size", - "review logs", - "pull binance transaction history", - ) - ): - return False - return True - - -def highest_risk(actions: list[dict[str, Any]]) -> str: - if not actions: - return "low" - return max(actions, key=lambda item: RISK_ORDER.get(str(item.get("risk_level", "low")), 0)).get("risk_level", "low") - - -def sort_actions(actions: list[dict[str, Any]]) -> list[dict[str, Any]]: - return sorted( - actions, - key=lambda item: ( - RISK_ORDER.get(str(item.get("risk_level", "low")), 0), - str(item.get("title", "")), - ), - reverse=True, - ) - - -def normalize_action(source_review: dict[str, Any], action: dict[str, Any]) -> dict[str, Any]: - owner_repo = _resolve_owner_repo(source_review, action) - auto_pr_safe = _resolve_auto_pr_safe(source_review, action) - return { - "source_repo": source_review["source_repo"], - "source_issue_number": source_review["source_issue"]["number"], - "source_issue_title": source_review["source_issue"]["title"], - "source_issue_url": source_review["source_issue"]["url"], - "source_review_kind": source_review["review_kind"], - "owner_repo": owner_repo, - "title": action["title"], - "risk_level": action["risk_level"], - "auto_pr_safe": auto_pr_safe, - "experiment_only": bool(action.get("experiment_only")), - "summary": action["summary"], - } - - -def build_plan(*source_reviews: dict[str, Any]) -> dict[str, Any]: - if not source_reviews: - raise ValueError("at least one source review is required") - source_review_list = list(source_reviews) - - normalized_actions = [ - normalize_action(review, action) - for review in source_review_list - for action in review.get("recommended_actions", []) - ] - in_scope_actions = [ - action for action in normalized_actions if action["owner_repo"] in REPO_ORDER - ] - out_of_scope_actions = [ - action for action in normalized_actions if action["owner_repo"] not in REPO_ORDER - ] - repo_groups: dict[str, list[dict[str, Any]]] = defaultdict(list) - for action in in_scope_actions: - repo_groups[action["owner_repo"]].append(action) - - repo_action_summary = { - repo: { - "count": len(sort_actions(repo_groups.get(repo, []))), - "highest_risk_level": highest_risk(repo_groups.get(repo, [])), - "actions": sort_actions(repo_groups.get(repo, [])), - } - for repo in REPO_ORDER - if repo_groups.get(repo) - } - - safe_auto_pr_candidates = [action for action in in_scope_actions if action["auto_pr_safe"] and action["risk_level"] == "low"] - experiment_candidates = [action for action in in_scope_actions if action["experiment_only"]] - human_review_required = [ - action for action in in_scope_actions if (not action["auto_pr_safe"]) or action["risk_level"] != "low" - ] - operator_focus = [ - f"{review['source_repo']}: {review['summary']}" - for review in source_review_list - ] - - highest_review_risk = highest_risk([ - {"risk_level": review["risk_level"]} - for review in source_review_list - ]) - - return { - "schema_version": SCHEMA_VERSION, - "generated_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"), - "source_reviews": source_review_list, - "highest_review_risk": highest_review_risk, - "repo_action_summary": repo_action_summary, - "safe_auto_pr_candidates": sort_actions(safe_auto_pr_candidates), - "experiment_candidates": sort_actions(experiment_candidates), - "human_review_required": sort_actions(human_review_required), - "out_of_scope_actions": sort_actions(out_of_scope_actions), - "operator_focus": operator_focus, - } - - -def render_summary_markdown(plan: dict[str, Any]) -> str: - lines = [ - "# Monthly Optimization Planner", - "", - f"- Highest review risk: `{plan['highest_review_risk']}`", - f"- Safe auto-PR candidates: `{len(plan['safe_auto_pr_candidates'])}`", - f"- Experiment candidates: `{len(plan['experiment_candidates'])}`", - f"- Human review required: `{len(plan['human_review_required'])}`", - f"- Out-of-scope downstream actions: `{len(plan.get('out_of_scope_actions', []))}`", - "", - "## Source Reviews", - ] - for review in plan["source_reviews"]: - lines.extend( - [ - f"- **{review['source_repo']}** `{review['risk_level']}` / `{review['production_recommendation']}`: {review['summary']}", - f" - Source issue: [{review['source_issue']['title']}]({review['source_issue']['url']})", - f" - Run: {review['run_url']}", - ] - ) - - lines.extend(["", "## Recommended Work by Repo"]) - for repo in REPO_ORDER: - repo_summary = plan["repo_action_summary"].get(repo) - if not repo_summary: - continue - lines.extend(["", f"### {repo}"]) - for action in repo_summary["actions"]: - flags: list[str] = [] - if action["auto_pr_safe"]: - flags.append("auto-pr-safe") - if action["experiment_only"]: - flags.append("experiment-only") - flag_suffix = f" [{', '.join(flags)}]" if flags else "" - lines.append( - f"- `{action['risk_level']}` {action['title']}{flag_suffix}: {action['summary']} " - f"(from {action['source_repo']} #{action['source_issue_number']})" - ) - - if plan["operator_focus"]: - lines.extend(["", "## Operator Focus"]) - lines.extend(f"- {item}" for item in plan["operator_focus"]) - - if plan.get("out_of_scope_actions"): - lines.extend(["", "## Out-of-Scope Actions"]) - for action in plan["out_of_scope_actions"]: - lines.append( - f"- `{action['risk_level']}` {action['owner_repo']}: {action['title']} " - f"(from {action['source_repo']} #{action['source_issue_number']})" - ) - - return "\n".join(lines).strip() + "\n" - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Build the monthly optimization plan from upstream selector AI review payloads.", - ) - parser.add_argument("--upstream-review-file", required=True, type=Path) - parser.add_argument("--downstream-review-file", type=Path) - parser.add_argument("--output-dir", required=True, type=Path) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - source_reviews = [json.loads(args.upstream_review_file.read_text(encoding="utf-8"))] - if args.downstream_review_file: - source_reviews.append(json.loads(args.downstream_review_file.read_text(encoding="utf-8"))) - plan = build_plan(*source_reviews) - args.output_dir.mkdir(parents=True, exist_ok=True) - (args.output_dir / "optimization_plan.json").write_text( - json.dumps(plan, ensure_ascii=False, indent=2) + "\n", - encoding="utf-8", - ) - (args.output_dir / "optimization_summary.md").write_text( - render_summary_markdown(plan), - encoding="utf-8", - ) - print(f"optimization_plan={args.output_dir / 'optimization_plan.json'}") - print(f"optimization_summary={args.output_dir / 'optimization_summary.md'}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/download_ai_review_artifact.py b/scripts/download_ai_review_artifact.py deleted file mode 100644 index ea30562..0000000 --- a/scripts/download_ai_review_artifact.py +++ /dev/null @@ -1,77 +0,0 @@ -from __future__ import annotations - -import argparse -import io -import json -import os -import urllib.request -import zipfile -from pathlib import Path -from typing import Any - - -DEFAULT_API_URL = "https://api.github.com" -ARTIFACT_PREFIX = "ai-monthly-review-" - - -def github_request(url: str, token: str, api_url: str = DEFAULT_API_URL) -> Any: - request = urllib.request.Request( - url if url.startswith("http") else f"{api_url.rstrip('/')}/{url.lstrip('/')}", - headers={ - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "monthly-optimization-planner", - }, - ) - with urllib.request.urlopen(request) as response: - content_type = response.headers.get("Content-Type", "") - body = response.read() - if "application/json" in content_type: - charset = response.headers.get_content_charset("utf-8") - return json.loads(body.decode(charset)) - return body - - -def select_ai_review_artifact(artifacts_payload: dict[str, Any]) -> dict[str, Any]: - artifacts = artifacts_payload.get("artifacts") or [] - candidates = [artifact for artifact in artifacts if str(artifact.get("name", "")).startswith(ARTIFACT_PREFIX)] - if not candidates: - raise ValueError("No ai-monthly-review artifact found for the workflow run") - candidates.sort(key=lambda artifact: int(artifact.get("id", 0)), reverse=True) - return candidates[0] - - -def download_and_extract_artifact(*, repo: str, run_id: int, token: str, output_dir: Path) -> Path: - artifacts_payload = github_request(f"repos/{repo}/actions/runs/{run_id}/artifacts", token) - artifact = select_ai_review_artifact(artifacts_payload) - archive = github_request(artifact["archive_download_url"], token) - output_dir.mkdir(parents=True, exist_ok=True) - with zipfile.ZipFile(io.BytesIO(archive)) as handle: - handle.extractall(output_dir) - return output_dir - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Download and extract the ai-monthly-review artifact from a workflow run.", - ) - parser.add_argument("--repo", required=True) - parser.add_argument("--run-id", required=True, type=int) - parser.add_argument("--output-dir", required=True, type=Path) - parser.add_argument("--token-env", default="GITHUB_TOKEN") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - token = os.environ.get(args.token_env) - if not token: - raise SystemExit(f"{args.token_env} is required") - download_and_extract_artifact(repo=args.repo, run_id=args.run_id, token=token, output_dir=args.output_dir) - print(f"artifact_dir={args.output_dir}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/fanout_monthly_optimization_tasks.py b/scripts/fanout_monthly_optimization_tasks.py deleted file mode 100644 index db99f82..0000000 --- a/scripts/fanout_monthly_optimization_tasks.py +++ /dev/null @@ -1,361 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path -from typing import Any - - -COMMENT_MARKER_PREFIX = "" - - -def build_marker_from_body(body: str) -> str: - for line in body.splitlines(): - if line.startswith(COMMENT_MARKER_PREFIX): - return line.strip() - return "" - - -def build_issue_title(plan: dict[str, Any], owner_repo: str) -> str: - labels = [review["source_issue"]["title"].split(": ", 1)[-1] for review in plan.get("source_reviews", [])] - return f"Monthly Optimization Tasks · {owner_repo}: {' / '.join(labels)}" - - -def _repo_actions(plan: dict[str, Any], owner_repo: str) -> list[dict[str, Any]]: - repo_summary = plan.get("repo_action_summary", {}).get(owner_repo, {}) - return list(repo_summary.get("actions", [])) - - -def _has_codex_bridge_actions(actions: list[dict[str, Any]], owner_repo: str) -> bool: - if owner_repo != "CryptoSnapshotPipelines": - return False - return any( - action.get("risk_level") == "low" - and action.get("auto_pr_safe") - and not action.get("experiment_only") - for action in actions - ) - - -def issue_labels_for_actions(actions: list[dict[str, Any]], owner_repo: str) -> list[str]: - labels = [LABEL_NAME] - if _has_codex_bridge_actions(actions, owner_repo): - labels.extend([CODEX_LABEL_NAME, AUTO_MERGE_LABEL_NAME]) - return labels - - -def build_issue_body(plan: dict[str, Any], owner_repo: str, planner_issue_url: str | None = None) -> str: - actions = _repo_actions(plan, owner_repo) - repo_summary = plan.get("repo_action_summary", {}).get(owner_repo, {}) - safe_auto_pr_count = sum(1 for action in actions if action.get("auto_pr_safe")) - experiment_count = sum(1 for action in actions if action.get("experiment_only")) - lines = [ - build_marker(plan, owner_repo), - f"# Monthly Optimization Tasks · {owner_repo}", - "", - f"- Actions in this repo: `{len(actions)}`", - f"- Highest repo risk: `{repo_summary.get('highest_risk_level', 'low')}`", - f"- Safe auto-PR candidates here: `{safe_auto_pr_count}`", - f"- Experiment-only tasks here: `{experiment_count}`", - ] - if planner_issue_url: - lines.append(f"- Planner issue: {planner_issue_url}") - - if _has_codex_bridge_actions(actions, owner_repo): - lines.extend( - [ - "", - "## Codex Bridge Contract", - "", - f"- Queue label: `{CODEX_LABEL_NAME}`", - "- Runner: self-hosted VPS ccbot/Codex, not GitHub-hosted Claude Action.", - "- Implement only `low` actions explicitly marked `[auto-pr-safe]` and not `[experiment-only]`.", - "- Keep edits minimal and limited to docs, report wording, validation, workflow plumbing, instrumentation, and tests.", - "- Do not change production selector logic, ranking behavior, `src/`, or `config/` from this issue alone.", - "- Open a draft PR first from branch `codex/monthly-optimization-issue-`.", - "- Include `` in the PR body.", - "- Mark the PR ready and apply `auto-merge-ok` only after targeted tests pass and changed files stay inside the guardrails.", - ] - ) - - lines.extend(["", "## Actions"]) - for action in actions: - flags: list[str] = [] - if action.get("auto_pr_safe"): - flags.append("auto-pr-safe") - if action.get("experiment_only"): - flags.append("experiment-only") - flag_suffix = f" [{', '.join(flags)}]" if flags else "" - lines.extend( - [ - f"- [ ] `{action['risk_level']}` {action['title']}{flag_suffix}", - f" - Summary: {action['summary']}", - f" - Source: [{action['source_repo']} #{action['source_issue_number']}]({action['source_issue_url']})", - ] - ) - - return "\n".join(lines).strip() + "\n" - - -def build_closed_issue_body(plan: dict[str, Any], owner_repo: str, planner_issue_url: str | None = None) -> str: - lines = [ - build_marker(plan, owner_repo), - f"# Monthly Optimization Tasks · {owner_repo}", - "", - "No repo-scoped tasks remain in the current monthly optimization plan.", - "This issue is being closed to avoid leaving stale automation targets behind.", - ] - if planner_issue_url: - lines.extend(["", f"- Planner issue: {planner_issue_url}"]) - return "\n".join(lines).strip() + "\n" - - -def github_request(method: str, url: str, token: str, payload: dict[str, Any] | None = None) -> Any: - data = None - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "monthly-optimization-fanout", - } - if payload is not None: - data = json.dumps(payload).encode("utf-8") - headers["Content-Type"] = "application/json" - request = urllib.request.Request(url, data=data, headers=headers, method=method) - with urllib.request.urlopen(request) as response: - charset = response.headers.get_content_charset("utf-8") - raw = response.read().decode(charset) - return json.loads(raw) if raw else None - - -def ensure_label(api_url: str, repo: str, token: str, *, name: str, color: str, description: str) -> None: - label_path = urllib.parse.quote(name, safe="") - label_url = f"{api_url}/repos/{repo}/labels/{label_path}" - try: - github_request("GET", label_url, token) - except urllib.error.HTTPError as exc: - if exc.code != 404: - raise - github_request( - "POST", - f"{api_url}/repos/{repo}/labels", - token, - { - "name": name, - "color": color, - "description": description, - }, - ) - - -def ensure_labels(api_url: str, repo: str, token: str, labels: list[str]) -> None: - specs = { - LABEL_NAME: (LABEL_COLOR, LABEL_DESCRIPTION), - CODEX_LABEL_NAME: (CODEX_LABEL_COLOR, CODEX_LABEL_DESCRIPTION), - AUTO_MERGE_LABEL_NAME: (AUTO_MERGE_LABEL_COLOR, AUTO_MERGE_LABEL_DESCRIPTION), - } - for label in labels: - color, description = specs[label] - ensure_label(api_url, repo, token, name=label, color=color, description=description) - - -def upsert_issue(*, api_url: str, repo: str, token: str, title: str, body: str, labels: list[str]) -> tuple[str, int, str]: - marker = build_marker_from_body(body) - existing = find_existing_issue(api_url=api_url, repo=repo, token=token, marker=marker) - payload = {"title": title, "body": body, "labels": labels} - if existing: - github_request("PATCH", f"{api_url}/repos/{repo}/issues/{existing['number']}", token, payload) - return "updated", int(existing["number"]), str(existing["html_url"]) - created = github_request("POST", f"{api_url}/repos/{repo}/issues", token, payload) - return "created", int(created["number"]), str(created["html_url"]) - - -def find_existing_issue(*, api_url: str, repo: str, token: str, marker: str) -> dict[str, Any] | None: - issues = github_request( - "GET", - f"{api_url}/repos/{repo}/issues?state=open&labels={urllib.parse.quote(LABEL_NAME)}&per_page=100", - token, - ) - return next((issue for issue in issues if build_marker_from_body(issue.get("body", "")) == marker), None) - - -def close_existing_issue( - *, - api_url: str, - repo: str, - token: str, - title: str, - body: str, -) -> tuple[bool, int | None, str | None]: - marker = build_marker_from_body(body) - existing = find_existing_issue(api_url=api_url, repo=repo, token=token, marker=marker) - if not existing: - return False, None, None - github_request( - "PATCH", - f"{api_url}/repos/{repo}/issues/{existing['number']}", - token, - {"title": title, "body": body, "state": "closed", "labels": [LABEL_NAME]}, - ) - return True, int(existing["number"]), str(existing["html_url"]) - - -def build_result( - *, - owner_repo: str, - target_repo: str, - plan: dict[str, Any], - status: str, - issue_number: int | None = None, - issue_url: str | None = None, - reason: str | None = None, -) -> dict[str, Any]: - repo_summary = plan.get("repo_action_summary", {}).get(owner_repo, {}) - return { - "owner_repo": owner_repo, - "target_repo": target_repo, - "status": status, - "actions_count": int(repo_summary.get("count", 0)), - "highest_risk_level": repo_summary.get("highest_risk_level", "low"), - "issue_number": issue_number, - "issue_url": issue_url, - "reason": reason, - } - - -def write_result(output_file: Path, result: dict[str, Any]) -> None: - output_file.parent.mkdir(parents=True, exist_ok=True) - output_file.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Create or update repo-scoped monthly optimization task issues.", - ) - parser.add_argument("--plan-file", required=True, type=Path) - parser.add_argument("--owner-repo", required=True) - parser.add_argument("--repo", required=True) - parser.add_argument("--output-file", required=True, type=Path) - parser.add_argument("--planner-issue-url") - parser.add_argument("--api-url", default=DEFAULT_API_URL) - parser.add_argument("--allow-permission-skip", action="store_true") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - token = os.environ.get("GITHUB_TOKEN") - if not token: - print("GITHUB_TOKEN is required", file=sys.stderr) - return 1 - - plan = json.loads(args.plan_file.read_text(encoding="utf-8")) - actions = _repo_actions(plan, args.owner_repo) - if not actions: - title = build_issue_title(plan, args.owner_repo) - body = build_closed_issue_body(plan, args.owner_repo, planner_issue_url=args.planner_issue_url) - try: - closed, issue_number, issue_url = close_existing_issue( - api_url=args.api_url.rstrip("/"), - repo=args.repo, - token=token, - title=title, - body=body, - ) - result = build_result( - owner_repo=args.owner_repo, - target_repo=args.repo, - plan=plan, - status="closed_no_actions" if closed else "skipped_no_actions", - issue_number=issue_number, - issue_url=issue_url, - reason=None if closed else "No recommended actions for this repo in the current optimization plan.", - ) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - if args.allow_permission_skip and exc.code in {403, 404}: - result = build_result( - owner_repo=args.owner_repo, - target_repo=args.repo, - plan=plan, - status="skipped_permission", - reason=f"{exc.code}: {detail or 'permission denied or repo not accessible'}", - ) - write_result(args.output_file, result) - print(json.dumps(result, ensure_ascii=False)) - return 0 - print(f"GitHub API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - write_result(args.output_file, result) - print(json.dumps(result, ensure_ascii=False)) - return 0 - - title = build_issue_title(plan, args.owner_repo) - body = build_issue_body(plan, args.owner_repo, planner_issue_url=args.planner_issue_url) - labels = issue_labels_for_actions(actions, args.owner_repo) - - try: - ensure_labels(args.api_url.rstrip("/"), args.repo, token, labels) - status, issue_number, issue_url = upsert_issue( - api_url=args.api_url.rstrip("/"), - repo=args.repo, - token=token, - title=title, - body=body, - labels=labels, - ) - result = build_result( - owner_repo=args.owner_repo, - target_repo=args.repo, - plan=plan, - status=status, - issue_number=issue_number, - issue_url=issue_url, - ) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - if args.allow_permission_skip and exc.code in {403, 404}: - result = build_result( - owner_repo=args.owner_repo, - target_repo=args.repo, - plan=plan, - status="skipped_permission", - reason=f"{exc.code}: {detail or 'permission denied or repo not accessible'}", - ) - write_result(args.output_file, result) - print(json.dumps(result, ensure_ascii=False)) - return 0 - print(f"GitHub API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - - write_result(args.output_file, result) - print(json.dumps(result, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/post_experiment_validation_comment.py b/scripts/post_experiment_validation_comment.py deleted file mode 100644 index cbb38b1..0000000 --- a/scripts/post_experiment_validation_comment.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error -import urllib.request -from pathlib import Path -from typing import Any - - -COMMENT_MARKER = "" -DEFAULT_API_URL = "https://api.github.com" - - - -def build_comment_body(review_markdown: str, run_url: str | None = None) -> str: - body = f"{COMMENT_MARKER}\n## Monthly Experiment Validation\n\n{review_markdown.strip()}" - if run_url: - body += f"\n\n---\n_Generated by experiment validation workflow: {run_url}_" - return body - - - -def github_request(method: str, url: str, token: str, payload: dict[str, Any] | None = None) -> Any: - data = None - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "crypto-leader-rotation-experiment-validation", - } - if payload is not None: - data = json.dumps(payload).encode("utf-8") - headers["Content-Type"] = "application/json" - - request = urllib.request.Request(url, data=data, headers=headers, method=method) - with urllib.request.urlopen(request) as response: - charset = response.headers.get_content_charset("utf-8") - raw = response.read().decode(charset) - return json.loads(raw) if raw else None - - - -def upsert_issue_comment(*, api_url: str, repo: str, issue_number: int, token: str, body: str) -> None: - comments_url = f"{api_url}/repos/{repo}/issues/{issue_number}/comments" - comments = github_request("GET", comments_url, token) - existing = next((comment for comment in comments if COMMENT_MARKER in comment.get("body", "")), None) - if existing: - github_request( - "PATCH", - f"{api_url}/repos/{repo}/issues/comments/{existing['id']}", - token, - {"body": body}, - ) - print(f"Updated issue comment {existing['id']}") - return - - github_request("POST", comments_url, token, {"body": body}) - print(f"Created issue comment for issue #{issue_number}") - - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Upsert the monthly experiment validation comment on the task issue.") - parser.add_argument("--repo", required=True) - parser.add_argument("--issue-number", required=True, type=int) - parser.add_argument("--review-file", required=True, type=Path) - parser.add_argument("--api-url", default=DEFAULT_API_URL) - parser.add_argument("--run-url", default="") - return parser.parse_args() - - - -def main() -> int: - args = parse_args() - token = os.environ.get("GITHUB_TOKEN") - if not token: - print("GITHUB_TOKEN is required", file=sys.stderr) - return 1 - - body = build_comment_body(args.review_file.read_text(encoding="utf-8"), args.run_url or None) - try: - upsert_issue_comment( - api_url=args.api_url.rstrip("/"), - repo=args.repo, - issue_number=args.issue_number, - token=token, - body=body, - ) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - print(f"GitHub API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/post_monthly_ai_review_comment.py b/scripts/post_monthly_ai_review_comment.py deleted file mode 100644 index 28effbf..0000000 --- a/scripts/post_monthly_ai_review_comment.py +++ /dev/null @@ -1,134 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error -import urllib.request -from pathlib import Path -from typing import Any - -try: - from scripts.render_monthly_ai_review import extract_latest_assistant_text -except ModuleNotFoundError: # pragma: no cover - script execution fallback - from render_monthly_ai_review import extract_latest_assistant_text - - -COMMENT_MARKER = "" -DEFAULT_API_URL = "https://api.github.com" - - -def build_comment_body(review_markdown: str, run_url: str | None = None) -> str: - body = f"{COMMENT_MARKER}\n## AI Monthly Review\n\n{review_markdown.strip()}" - if run_url: - body += f"\n\n---\n_Generated by AI Monthly Review workflow: {run_url}_" - return body - - -def github_request( - method: str, - url: str, - token: str, - payload: dict[str, Any] | None = None, -) -> Any: - data = None - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "crypto-leader-rotation-monthly-ai-review", - } - if payload is not None: - data = json.dumps(payload).encode("utf-8") - headers["Content-Type"] = "application/json" - - request = urllib.request.Request(url, data=data, headers=headers, method=method) - with urllib.request.urlopen(request) as response: - charset = response.headers.get_content_charset("utf-8") - raw = response.read().decode(charset) - return json.loads(raw) if raw else None - - -def upsert_issue_comment( - *, - api_url: str, - repo: str, - issue_number: int, - token: str, - body: str, -) -> None: - comments_url = f"{api_url}/repos/{repo}/issues/{issue_number}/comments" - comments = github_request("GET", comments_url, token) - existing = next( - ( - comment - for comment in comments - if COMMENT_MARKER in comment.get("body", "") - ), - None, - ) - - if existing: - github_request( - "PATCH", - f"{api_url}/repos/{repo}/issues/comments/{existing['id']}", - token, - {"body": body}, - ) - print(f"Updated issue comment {existing['id']}") - return - - github_request("POST", comments_url, token, {"body": body}) - print(f"Created issue comment for issue #{issue_number}") - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Extract Claude review text from execution output and upsert an issue comment.", - ) - parser.add_argument("--repo", required=True, help="owner/repo") - parser.add_argument("--issue-number", required=True, type=int) - parser.add_argument("--execution-file", type=Path) - parser.add_argument("--review-file", type=Path) - parser.add_argument("--api-url", default=DEFAULT_API_URL) - parser.add_argument("--run-url", default="") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - token = os.environ.get("GITHUB_TOKEN") - if not token: - print("GITHUB_TOKEN is required", file=sys.stderr) - return 1 - - if args.review_file is not None: - review_markdown = args.review_file.read_text(encoding="utf-8") - elif args.execution_file is not None: - execution_log = json.loads(args.execution_file.read_text(encoding="utf-8")) - review_markdown = extract_latest_assistant_text(execution_log) - else: - print("Either --review-file or --execution-file is required", file=sys.stderr) - return 1 - - body = build_comment_body(review_markdown, args.run_url or None) - - try: - upsert_issue_comment( - api_url=args.api_url.rstrip("/"), - repo=args.repo, - issue_number=args.issue_number, - token=token, - body=body, - ) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - print(f"GitHub API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/post_monthly_optimization_issue.py b/scripts/post_monthly_optimization_issue.py deleted file mode 100644 index ebac833..0000000 --- a/scripts/post_monthly_optimization_issue.py +++ /dev/null @@ -1,143 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path -from typing import Any - - -COMMENT_MARKER_PREFIX = "" - - -def build_issue_title(plan: dict[str, Any]) -> str: - labels = [review["source_issue"]["title"].split(": ", 1)[-1] for review in plan.get("source_reviews", [])] - return f"Monthly Optimization Plan: {' / '.join(labels)}" - - -def build_issue_body(plan: dict[str, Any], summary_markdown: str) -> str: - marker = build_marker(plan) - return f"{marker}\n{summary_markdown.strip()}" - - -def github_request(method: str, url: str, token: str, payload: dict[str, Any] | None = None) -> Any: - data = None - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "monthly-optimization-planner", - } - if payload is not None: - data = json.dumps(payload).encode("utf-8") - headers["Content-Type"] = "application/json" - request = urllib.request.Request(url, data=data, headers=headers, method=method) - with urllib.request.urlopen(request) as response: - charset = response.headers.get_content_charset("utf-8") - raw = response.read().decode(charset) - return json.loads(raw) if raw else None - - -def ensure_label(api_url: str, repo: str, token: str) -> None: - label_path = urllib.parse.quote(LABEL_NAME, safe="") - label_url = f"{api_url}/repos/{repo}/labels/{label_path}" - try: - github_request("GET", label_url, token) - except urllib.error.HTTPError as exc: - if exc.code != 404: - raise - github_request( - "POST", - f"{api_url}/repos/{repo}/labels", - token, - { - "name": LABEL_NAME, - "color": LABEL_COLOR, - "description": LABEL_DESCRIPTION, - }, - ) - - -def upsert_issue(*, api_url: str, repo: str, token: str, title: str, body: str) -> int: - issues = github_request( - "GET", - f"{api_url}/repos/{repo}/issues?state=open&labels={urllib.parse.quote(LABEL_NAME)}&per_page=100", - token, - ) - existing = next((issue for issue in issues if build_marker_from_body(issue.get("body", "")) == build_marker_from_body(body)), None) - payload = {"title": title, "body": body, "labels": [LABEL_NAME]} - if existing: - github_request("PATCH", f"{api_url}/repos/{repo}/issues/{existing['number']}", token, payload) - print(f"Updated optimization issue #{existing['number']}") - return int(existing["number"]) - created = github_request("POST", f"{api_url}/repos/{repo}/issues", token, payload) - print(f"Created optimization issue #{created['number']}") - return int(created["number"]) - - -def build_marker_from_body(body: str) -> str: - for line in body.splitlines(): - if line.startswith(COMMENT_MARKER_PREFIX): - return line.strip() - return "" - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Create or update the monthly optimization issue from the planner outputs.", - ) - parser.add_argument("--repo", required=True) - parser.add_argument("--plan-file", required=True, type=Path) - parser.add_argument("--summary-file", required=True, type=Path) - parser.add_argument("--api-url", default=DEFAULT_API_URL) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - token = os.environ.get("GITHUB_TOKEN") - if not token: - print("GITHUB_TOKEN is required", file=sys.stderr) - return 1 - - plan = json.loads(args.plan_file.read_text(encoding="utf-8")) - summary_markdown = args.summary_file.read_text(encoding="utf-8") - title = build_issue_title(plan) - body = build_issue_body(plan, summary_markdown) - - try: - ensure_label(args.api_url.rstrip("/"), args.repo, token) - issue_number = upsert_issue( - api_url=args.api_url.rstrip("/"), - repo=args.repo, - token=token, - title=title, - body=body, - ) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - print(f"GitHub API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - - print(f"issue_number={issue_number}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/prepare_auto_optimization_pr.py b/scripts/prepare_auto_optimization_pr.py deleted file mode 100644 index 754ce07..0000000 --- a/scripts/prepare_auto_optimization_pr.py +++ /dev/null @@ -1,353 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import re -from pathlib import Path -from typing import Any - - -ACTION_RE = re.compile(r"^- \[ \] `(?Plow|medium|high)` (?P.+?)(?: \[(?P<flags>[^\]]+)\])?$") -SUMMARY_RE = re.compile(r"^\s+- Summary: (?P<summary>.+)$") -SOURCE_RE = re.compile(r"^\s+- Source: \[(?P<label>.+?)\]\((?P<url>[^)]+)\)$") -MARKER_PREFIX = "<!-- auto-optimization-pr:issue-" -PROJECT_ROOT = Path(__file__).resolve().parents[1] -AUTO_MERGE_SAFE_TERMS = ( - "readme", - "documentation", - "document ", - "document and", - "docs", - "operator runbook", - "workflow", - "ci", - "report wording", - "report format", - "report template", - "telemetry", - "diagnostic", - "instrumentation", - "unit test", - "tests", - "test coverage", -) -AUTO_MERGE_BLOCK_TERMS = { - "CryptoSnapshotPipelines": ( - "tie-break", - "tie break", - "ranking", - "rank labels", - "rank order", - "regime", - "selector", - "score", - "universe", - "pool membership", - "challenger", - "shadow build", - "walk-forward", - "walk forward", - ), - "CryptoStrategies": ( - "strategy", - "signal", - "allocation", - "sizing", - "ranking", - "selector", - "rotation", - "dca", - "threshold", - ), -} -SENSITIVE_PATH_PATTERNS = { - "CryptoSnapshotPipelines": ( - r"^src/", - r"^config/", - ), - "CryptoStrategies": ( - r"^src/", - ), -} - -REPO_NAME_ALIASES = { - "CryptoLeaderRotation": "CryptoSnapshotPipelines", - "crypto-codex-bridge": "CryptoSnapshotPipelines", - "CryptoSnapshotPipelinesCodexBridge": "CryptoSnapshotPipelines", -} - - -def _repo_key(repo_root: Path) -> str: - return REPO_NAME_ALIASES.get(repo_root.name, repo_root.name) - - -def parse_actions(issue_body: str) -> list[dict[str, Any]]: - actions: list[dict[str, Any]] = [] - current: dict[str, Any] | None = None - in_actions = False - - for raw_line in issue_body.splitlines(): - line = raw_line.rstrip() - if line == "## Actions": - in_actions = True - continue - if in_actions and line.startswith("## "): - break - if not in_actions: - continue - - action_match = ACTION_RE.match(line) - if action_match: - if current is not None: - actions.append(current) - flags = [flag.strip() for flag in (action_match.group("flags") or "").split(",") if flag.strip()] - current = { - "risk_level": action_match.group("risk"), - "title": action_match.group("title").strip(), - "flags": flags, - } - continue - - if current is None: - continue - - summary_match = SUMMARY_RE.match(line) - if summary_match: - current["summary"] = summary_match.group("summary").strip() - continue - - source_match = SOURCE_RE.match(line) - if source_match: - current["source_label"] = source_match.group("label").strip() - current["source_url"] = source_match.group("url").strip() - - if current is not None: - actions.append(current) - return actions - - -def _read_text(path: Path) -> str: - return path.read_text(encoding="utf-8") if path.exists() else "" - - -def _is_completed_low_risk_task(action: dict[str, Any], repo_root: Path) -> bool: - title = str(action.get("title", "")).lower() - repo_name = _repo_key(repo_root) - - if repo_name == "CryptoSnapshotPipelines": - if "shadow/challenger build generation" in title or "shadow build" in title: - workflow = _read_text(repo_root / ".github" / "workflows" / "monthly_publish.yml") - return "run_monthly_shadow_build.py" in workflow - if "tie-break" in title or "tie break" in title: - readme = _read_text(repo_root / "README.md") - runbook = _read_text(repo_root / "docs" / "operator_runbook.md") - return ( - "Monthly ranking tie-break rule for `core_major` live exports:" in readme - and "deterministic tie-break" in runbook - ) - - return False - - -def _normalized_action_text(action: dict[str, Any]) -> str: - title = str(action.get("title", "")) - summary = str(action.get("summary", "")) - return f"{title}\n{summary}".lower() - - -def classify_action_for_auto_merge(action: dict[str, Any], repo_root: Path | None = None) -> tuple[bool, str]: - repo_root = repo_root or PROJECT_ROOT - text = _normalized_action_text(action) - if not any(term in text for term in AUTO_MERGE_SAFE_TERMS): - return False, "task_not_in_auto_merge_allowlist" - - for term in AUTO_MERGE_BLOCK_TERMS.get(_repo_key(repo_root), ()): # pragma: no branch - tiny tuples - if term in text: - return False, f"guarded_keyword:{term}" - return True, "auto_merge_safe" - - -def evaluate_changed_files(changed_files: list[str], repo_root: Path | None = None) -> dict[str, Any]: - repo_root = repo_root or PROJECT_ROOT - blocked_files: list[str] = [] - patterns = tuple(re.compile(pattern) for pattern in SENSITIVE_PATH_PATTERNS.get(_repo_key(repo_root), ())) - for raw_path in changed_files: - normalized = raw_path.strip().lstrip("./") - if not normalized: - continue - if any(pattern.search(normalized) for pattern in patterns): - blocked_files.append(normalized) - return { - "allowed": not blocked_files, - "blocked_files": blocked_files, - } - - -def build_payload(issue_context: dict[str, Any], repo_root: Path | None = None) -> dict[str, Any]: - repo_root = repo_root or PROJECT_ROOT - issue_number = int(issue_context["number"]) - issue_title = str(issue_context["title"]).strip() - issue_body = str(issue_context["body"]) - parsed_actions = parse_actions(issue_body) - low_safe_actions = [ - action - for action in parsed_actions - if action["risk_level"] == "low" - and "auto-pr-safe" in action.get("flags", []) - and "experiment-only" not in action.get("flags", []) - ] - safe_actions: list[dict[str, Any]] = [] - skipped_actions: list[dict[str, Any]] = [] - for action in low_safe_actions: - if _is_completed_low_risk_task(action, repo_root): - skipped_actions.append({**action, "skip_reason": "already_implemented"}) - else: - safe_actions.append(action) - - auto_merge_candidate_actions: list[dict[str, Any]] = [] - draft_only_actions: list[dict[str, Any]] = [] - for action in safe_actions: - eligible, reason = classify_action_for_auto_merge(action, repo_root) - if eligible: - auto_merge_candidate_actions.append({**action, "auto_merge_reason": reason}) - else: - draft_only_actions.append({**action, "auto_merge_blocker": reason}) - - task_level_auto_merge_allowed = bool(safe_actions) and not draft_only_actions and bool(auto_merge_candidate_actions) - return { - "issue_number": issue_number, - "issue_title": issue_title, - "branch_name": f"automation/monthly-optimization-issue-{issue_number}", - "commit_message": f"chore: address monthly optimization issue #{issue_number}", - "pr_title": f"Auto: address monthly optimization issue #{issue_number}", - "should_run": bool(safe_actions), - "safe_task_count": len(safe_actions), - "skipped_task_count": len(skipped_actions), - "auto_merge_candidate_count": len(auto_merge_candidate_actions), - "draft_only_task_count": len(draft_only_actions), - "task_level_auto_merge_allowed": task_level_auto_merge_allowed, - "safe_actions": safe_actions, - "skipped_actions": skipped_actions, - "auto_merge_candidate_actions": auto_merge_candidate_actions, - "draft_only_actions": draft_only_actions, - } - - -def render_task_summary(payload: dict[str, Any]) -> str: - lines = [ - "# Auto Optimization Candidate Tasks", - "", - f"- Issue: #{payload['issue_number']} {payload['issue_title']}", - f"- Eligible low-risk auto-pr-safe tasks: `{payload['safe_task_count']}`", - f"- Task-level auto-merge candidates: `{payload['auto_merge_candidate_count']}`", - f"- Draft-only low-risk tasks: `{payload['draft_only_task_count']}`", - ] - if payload["skipped_actions"]: - lines.append(f"- Skipped as already implemented: `{payload['skipped_task_count']}`") - if not payload["safe_actions"]: - lines.extend(["", "No eligible low-risk [auto-pr-safe] tasks remain for automation."]) - if payload["skipped_actions"]: - lines.extend(["", "## Skipped Tasks"]) - for action in payload["skipped_actions"]: - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}", - f" - Reason: {action['skip_reason']}", - ] - ) - return "\n".join(lines).strip() + "\n" - - if payload["auto_merge_candidate_actions"]: - lines.extend(["", "## Auto-Merge Candidate Tasks"]) - for action in payload["auto_merge_candidate_actions"]: - flag_suffix = f" [{', '.join(action['flags'])}]" if action.get("flags") else "" - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}{flag_suffix}", - f" - Summary: {action.get('summary', 'No summary provided.')}", - f" - Source: {action.get('source_label', 'Unknown source')} ({action.get('source_url', 'n/a')})", - ] - ) - - if payload["draft_only_actions"]: - lines.extend(["", "## Draft-Only Tasks"]) - for action in payload["draft_only_actions"]: - flag_suffix = f" [{', '.join(action['flags'])}]" if action.get("flags") else "" - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}{flag_suffix}", - f" - Summary: {action.get('summary', 'No summary provided.')}", - f" - Auto-merge blocker: {action.get('auto_merge_blocker', 'guarded_task')}", - ] - ) - - if payload["skipped_actions"]: - lines.extend(["", "## Skipped Tasks"]) - for action in payload["skipped_actions"]: - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}", - f" - Reason: {action['skip_reason']}", - ] - ) - - return "\n".join(lines).strip() + "\n" - - -def render_pr_body(payload: dict[str, Any]) -> str: - lines = [ - f"{MARKER_PREFIX}{payload['issue_number']} -->", - "## Summary", - "This PR was generated from a monthly optimization task issue.", - "It only targets low-risk items explicitly marked `[auto-pr-safe]`.", - "", - "## Merge Policy", - f"- Task-level auto-merge eligible: `{'yes' if payload['task_level_auto_merge_allowed'] else 'no'}`", - "- High-risk guardrails remain active for selector logic, live execution, and shared strategy code paths.", - "", - "## Auto-selected tasks", - ] - for action in payload["safe_actions"]: - lines.append(f"- {action['title']}: {action.get('summary', 'No summary provided.')}") - if payload["draft_only_actions"]: - lines.extend(["", "## Draft-only tasks"]) - for action in payload["draft_only_actions"]: - lines.append(f"- {action['title']}: {action.get('auto_merge_blocker', 'guarded_task')}") - lines.extend(["", f"Refs #{payload['issue_number']}"]) - return "\n".join(lines).strip() + "\n" - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Prepare metadata for auto-generated optimization PRs.") - parser.add_argument("--issue-context-file", required=True, type=Path) - parser.add_argument("--output-dir", required=True, type=Path) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - issue_context = json.loads(args.issue_context_file.read_text(encoding="utf-8")) - payload = build_payload(issue_context) - args.output_dir.mkdir(parents=True, exist_ok=True) - payload_file = args.output_dir / "payload.json" - task_summary_file = args.output_dir / "task_summary.md" - pr_body_file = args.output_dir / "pr_body.md" - payload_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - task_summary_file.write_text(render_task_summary(payload), encoding="utf-8") - pr_body_file.write_text(render_pr_body(payload), encoding="utf-8") - print(f"should_run={'true' if payload['should_run'] else 'false'}") - print(f"issue_number={payload['issue_number']}") - print(f"branch_name={payload['branch_name']}") - print(f"commit_message={payload['commit_message']}") - print(f"pr_title={payload['pr_title']}") - print(f"safe_task_count={payload['safe_task_count']}") - print(f"auto_merge_candidate_count={payload['auto_merge_candidate_count']}") - print(f"task_level_auto_merge_allowed={'true' if payload['task_level_auto_merge_allowed'] else 'false'}") - print(f"payload_file={payload_file}") - print(f"task_summary_file={task_summary_file}") - print(f"pr_body_file={pr_body_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/prepare_experiment_validation.py b/scripts/prepare_experiment_validation.py deleted file mode 100644 index 3d9ff4d..0000000 --- a/scripts/prepare_experiment_validation.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from pathlib import Path - -try: - from scripts.prepare_auto_optimization_pr import parse_actions -except ModuleNotFoundError: # pragma: no cover - script execution fallback - from prepare_auto_optimization_pr import parse_actions - - -SHADOW_BUILD_MARKERS = ( - "shadow build", - "challenger", - "official_baseline", - "track_summary", -) -WALKFORWARD_MARKERS = ( - "walk-forward", - "walkforward", - "leader capture", - "backtest", - "validation window", -) - - -def _combined_text(action: dict[str, object]) -> str: - return f"{action.get('title', '')} {action.get('summary', '')}".lower() - - -def build_payload(issue_context: dict[str, object]) -> dict[str, object]: - issue_number = int(issue_context["number"]) - issue_title = str(issue_context["title"]).strip() - parsed_actions = parse_actions(str(issue_context.get("body", ""))) - experiment_actions = [action for action in parsed_actions if "experiment-only" in action.get("flags", [])] - run_shadow_build = any(any(marker in _combined_text(action) for marker in SHADOW_BUILD_MARKERS) for action in experiment_actions) - run_walkforward_validation = any( - any(marker in _combined_text(action) for marker in WALKFORWARD_MARKERS) for action in experiment_actions - ) - - should_run = bool(experiment_actions) and (run_shadow_build or run_walkforward_validation) - skip_reason = "" - if not experiment_actions: - skip_reason = "No experiment-only tasks were found in this monthly optimization issue." - elif not should_run: - skip_reason = "No supported upstream experiment validation target was found in the selected tasks." - - return { - "issue_number": issue_number, - "issue_title": issue_title, - "should_run": should_run, - "experiment_task_count": len(experiment_actions), - "run_shadow_build": run_shadow_build, - "run_walkforward_validation": run_walkforward_validation, - "experiment_actions": experiment_actions, - "skip_reason": skip_reason, - } - - - -def render_task_summary(payload: dict[str, object]) -> str: - lines = [ - "# Experiment Validation Candidate Tasks", - "", - f"- Issue: #{payload['issue_number']} {payload['issue_title']}", - f"- Experiment-only tasks: `{payload['experiment_task_count']}`", - f"- Shadow build selected: `{str(payload['run_shadow_build']).lower()}`", - f"- Walk-forward validation selected: `{str(payload['run_walkforward_validation']).lower()}`", - ] - actions = payload["experiment_actions"] - if not actions: - lines.extend(["", payload["skip_reason"]]) - return "\n".join(lines).strip() + "\n" - - lines.extend(["", "## Selected Tasks"]) - for action in actions: - flag_suffix = f" [{', '.join(action['flags'])}]" if action.get("flags") else "" - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}{flag_suffix}", - f" - Summary: {action.get('summary', 'No summary provided.')}", - f" - Source: {action.get('source_label', 'Unknown source')} ({action.get('source_url', 'n/a')})", - ] - ) - - if payload["skip_reason"]: - lines.extend(["", payload["skip_reason"]]) - return "\n".join(lines).strip() + "\n" - - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Prepare metadata for experiment-only monthly optimization validation.") - parser.add_argument("--issue-context-file", required=True, type=Path) - parser.add_argument("--output-dir", required=True, type=Path) - return parser.parse_args() - - - -def main() -> int: - args = parse_args() - issue_context = json.loads(args.issue_context_file.read_text(encoding="utf-8")) - payload = build_payload(issue_context) - args.output_dir.mkdir(parents=True, exist_ok=True) - payload_file = args.output_dir / "payload.json" - task_summary_file = args.output_dir / "task_summary.md" - if payload["skip_reason"]: - (args.output_dir / "skip_reason.txt").write_text(str(payload["skip_reason"]) + "\n", encoding="utf-8") - payload_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - task_summary_file.write_text(render_task_summary(payload), encoding="utf-8") - print(f"should_run={'true' if payload['should_run'] else 'false'}") - print(f"issue_number={payload['issue_number']}") - print(f"run_shadow_build={'true' if payload['run_shadow_build'] else 'false'}") - print(f"run_walkforward_validation={'true' if payload['run_walkforward_validation'] else 'false'}") - print(f"payload_file={payload_file}") - print(f"task_summary_file={task_summary_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/render_experiment_validation_summary.py b/scripts/render_experiment_validation_summary.py deleted file mode 100644 index 63efbee..0000000 --- a/scripts/render_experiment_validation_summary.py +++ /dev/null @@ -1,97 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from pathlib import Path -from typing import Any - - - -def load_optional_json(path: Path | None) -> dict[str, Any] | None: - if path is None or not path.exists(): - return None - return json.loads(path.read_text(encoding="utf-8")) - - - -def _track_line(track: dict[str, Any]) -> str: - parts = [f"`{track.get('track_id', 'unknown')}`"] - if track.get("profile_name"): - parts.append(str(track["profile_name"])) - if track.get("pool_size") is not None: - parts.append(f"pool_size={track['pool_size']}") - if track.get("release_index_path"): - parts.append(f"index={track['release_index_path']}") - return "- " + " · ".join(parts) - - - -def build_summary_markdown(payload: dict[str, Any], shadow_summary: dict[str, Any] | None) -> str: - lines = [ - "## Monthly Experiment Validation", - "", - f"- Issue: #{payload['issue_number']} {payload['issue_title']}", - f"- Experiment-only tasks: `{payload['experiment_task_count']}`", - f"- Validation executed: `{'yes' if payload['should_run'] else 'no'}`", - ] - if payload.get("skip_reason"): - lines.append(f"- Skip reason: {payload['skip_reason']}") - - actions = payload.get("experiment_actions", []) - if actions: - lines.extend(["", "### Selected Tasks"]) - for action in actions: - flags = f" [{', '.join(action['flags'])}]" if action.get("flags") else "" - lines.extend( - [ - f"- `{action['risk_level']}` {action['title']}{flags}", - f" - Summary: {action.get('summary', 'No summary provided.')}", - ] - ) - - if shadow_summary is not None: - official = shadow_summary.get("official_baseline", {}) - shadow_tracks = shadow_summary.get("shadow_candidate_tracks", {}).get("tracks", []) - lines.extend( - [ - "", - "### Validation Results", - f"- As of date: `{shadow_summary.get('as_of_date', 'unknown')}`", - f"- Official baseline version: `{official.get('version', 'unknown')}`", - f"- Official baseline mode: `{official.get('mode', 'unknown')}`", - f"- Official baseline pool size: `{official.get('pool_size', 'unknown')}`", - f"- Shadow candidate tracks generated: `{len(shadow_tracks)}`", - ] - ) - if official.get("publish_manifest_path"): - lines.append(f"- Publish manifest path: `{official['publish_manifest_path']}`") - if shadow_tracks: - lines.extend(["", "### Shadow Tracks"]) - lines.extend(_track_line(track) for track in shadow_tracks) - elif payload.get("should_run"): - lines.extend(["", "### Validation Results", "- No shadow-build summary artifact was found."]) - - return "\n".join(lines).strip() + "\n" - - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Render experiment validation markdown for the monthly optimization task issue.") - parser.add_argument("--payload-file", required=True, type=Path) - parser.add_argument("--output-file", required=True, type=Path) - parser.add_argument("--shadow-summary-file", type=Path) - return parser.parse_args() - - - -def main() -> int: - args = parse_args() - payload = json.loads(args.payload_file.read_text(encoding="utf-8")) - shadow_summary = load_optional_json(args.shadow_summary_file) - args.output_file.write_text(build_summary_markdown(payload, shadow_summary), encoding="utf-8") - print(f"summary_file={args.output_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/render_monthly_ai_review.py b/scripts/render_monthly_ai_review.py deleted file mode 100644 index 7e7cbde..0000000 --- a/scripts/render_monthly_ai_review.py +++ /dev/null @@ -1,125 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from pathlib import Path -from typing import Any - - -def extract_latest_assistant_text(execution_log: list[dict[str, Any]]) -> str: - for turn in reversed(execution_log): - if turn.get("type") != "assistant": - continue - - content_items = turn.get("message", {}).get("content", []) - text_parts = [ - item.get("text", "").strip() - for item in content_items - if item.get("type") == "text" and item.get("text", "").strip() - ] - if text_parts: - return "\n\n".join(text_parts).strip() - - raise ValueError("No assistant review text found in execution log") - - -def load_primary_review_markdown(*, execution_file: Path | None, primary_review_file: Path | None) -> str: - if primary_review_file is not None: - return primary_review_file.read_text(encoding="utf-8").strip() - if execution_file is not None: - execution_log = json.loads(execution_file.read_text(encoding="utf-8")) - return extract_latest_assistant_text(execution_log) - raise ValueError("Either execution_file or primary_review_file is required") - - -def render_secondary_review_markdown(payload: dict[str, Any]) -> str: - lines: list[str] = [ - f"## Secondary Review ({payload.get('provider_display_name', 'GPT')})", - "", - f"- Verdict: `{payload['verdict']}`", - f"- Risk Level: `{payload['risk_level']}`", - f"- Production Recommendation: `{payload['production_recommendation']}`", - f"- Summary: {payload['summary']}", - ] - - findings = [item.strip() for item in payload.get("key_findings", []) if str(item).strip()] - if findings: - lines.extend(["", "### Key Findings"]) - lines.extend(f"- {item}" for item in findings) - - actions = payload.get("recommended_actions", []) - if actions: - lines.extend(["", "### Recommended Actions"]) - for action in actions: - flags: list[str] = [] - if action.get("auto_pr_safe"): - flags.append("auto-pr-safe") - if action.get("experiment_only"): - flags.append("experiment-only") - flag_text = f" [{', '.join(flags)}]" if flags else "" - lines.append( - "- " - f"{action['title']} " - f"({action['owner_repo']}, risk={action['risk_level']}){flag_text}: {action['summary']}" - ) - - follow_up_checks = [item.strip() for item in payload.get("follow_up_checks", []) if str(item).strip()] - if follow_up_checks: - lines.extend(["", "### Follow-up Checks"]) - lines.extend(f"- {item}" for item in follow_up_checks) - - return "\n".join(lines).strip() - - -def build_full_review_markdown( - primary_review_text: str, - *, - primary_title: str, - secondary_review_payload: dict[str, Any] | None = None, -) -> str: - normalized_primary_review = primary_review_text.strip() - duplicate_title = f"## {primary_title}" - if normalized_primary_review.startswith(duplicate_title): - normalized_primary_review = normalized_primary_review[len(duplicate_title) :].lstrip() - - lines = [f"## {primary_title}", "", normalized_primary_review] - if secondary_review_payload is not None: - lines.extend(["", "---", "", render_secondary_review_markdown(secondary_review_payload)]) - return "\n".join(lines).strip() + "\n" - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Render markdown for the monthly AI review from primary and optional secondary review outputs.", - ) - parser.add_argument("--output-file", required=True, type=Path) - parser.add_argument("--execution-file", type=Path) - parser.add_argument("--primary-review-file", type=Path) - parser.add_argument("--secondary-review-file", type=Path) - parser.add_argument("--primary-title", default="Claude Primary Review") - return parser.parse_args() - - -def main() -> int: - args = parse_args() - primary_review_text = load_primary_review_markdown( - execution_file=args.execution_file, - primary_review_file=args.primary_review_file, - ) - secondary_review_payload = None - if args.secondary_review_file is not None: - secondary_review_payload = json.loads(args.secondary_review_file.read_text(encoding="utf-8")) - - markdown = build_full_review_markdown( - primary_review_text, - primary_title=args.primary_title, - secondary_review_payload=secondary_review_payload, - ) - args.output_file.parent.mkdir(parents=True, exist_ok=True) - args.output_file.write_text(markdown, encoding="utf-8") - print(f"review_markdown={args.output_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/run_openai_secondary_review.py b/scripts/run_openai_secondary_review.py deleted file mode 100644 index c0284b3..0000000 --- a/scripts/run_openai_secondary_review.py +++ /dev/null @@ -1,214 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import os -import sys -import urllib.error -import urllib.request -from pathlib import Path -from typing import Any - - -OPENAI_API_URL = "https://api.openai.com/v1/chat/completions" -SUPPORTED_REVIEW_KINDS = {"upstream_selector"} - -SECONDARY_REVIEW_SCHEMA: dict[str, Any] = { - "type": "object", - "additionalProperties": False, - "properties": { - "review_kind": {"type": "string"}, - "provider": {"type": "string"}, - "provider_display_name": {"type": "string"}, - "model": {"type": "string"}, - "verdict": {"type": "string", "enum": ["agree", "partial_agree", "disagree"]}, - "risk_level": {"type": "string", "enum": ["low", "medium", "high"]}, - "production_recommendation": { - "type": "string", - "enum": ["keep_production_as_is", "research_only", "needs_attention"], - }, - "summary": {"type": "string"}, - "key_findings": { - "type": "array", - "items": {"type": "string"}, - "minItems": 1, - "maxItems": 5, - }, - "recommended_actions": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": False, - "properties": { - "title": {"type": "string"}, - "owner_repo": { - "type": "string", - "enum": ["CryptoSnapshotPipelines", "CryptoStrategies"], - }, - "risk_level": {"type": "string", "enum": ["low", "medium", "high"]}, - "auto_pr_safe": {"type": "boolean"}, - "experiment_only": {"type": "boolean"}, - "summary": {"type": "string"}, - }, - "required": [ - "title", - "owner_repo", - "risk_level", - "auto_pr_safe", - "experiment_only", - "summary", - ], - }, - "maxItems": 5, - }, - "follow_up_checks": { - "type": "array", - "items": {"type": "string"}, - "maxItems": 5, - }, - }, - "required": [ - "review_kind", - "provider", - "provider_display_name", - "model", - "verdict", - "risk_level", - "production_recommendation", - "summary", - "key_findings", - "recommended_actions", - "follow_up_checks", - ], -} - - -def build_system_prompt(review_kind: str) -> str: - if review_kind == "upstream_selector": - return ( - "You are the independent secondary reviewer for CryptoSnapshotPipelines, an upstream selector " - "repository that publishes a monthly 5-symbol Binance Spot leader pool. Review the issue body " - "and the Claude primary review, then return only valid JSON matching the provided schema. " - "Do not simply echo Claude. Re-check whether release consistency, selector quality, " - "shadow/challenger evidence, and downstream BinancePlatform impact actually support the same conclusion. " - "Use recommended_actions for concrete next steps, and only mark auto_pr_safe=true for low-risk " - "changes like workflow, telemetry, report wording, tests, or challenger/shadow configuration." - ) - raise ValueError(f"Unsupported review kind: {review_kind}") - - -def build_user_prompt(issue_title: str, issue_body: str, primary_review_text: str) -> str: - return ( - "Independently review the monthly report below, then compare it against the Claude primary review. " - "If Claude looks too strong or too weak, say so in verdict/summary/findings.\n\n" - f"## Issue Title\n{issue_title.strip()}\n\n" - f"## Issue Body\n{issue_body.strip()}\n\n" - f"## Claude Primary Review\n{primary_review_text.strip()}\n" - ) - - -def build_request_payload( - *, - model: str, - review_kind: str, - issue_title: str, - issue_body: str, - primary_review_text: str, -) -> dict[str, Any]: - return { - "model": model, - "messages": [ - {"role": "system", "content": build_system_prompt(review_kind)}, - { - "role": "user", - "content": build_user_prompt(issue_title, issue_body, primary_review_text), - }, - ], - "response_format": { - "type": "json_schema", - "json_schema": { - "name": "secondary_monthly_review", - "strict": True, - "schema": SECONDARY_REVIEW_SCHEMA, - }, - }, - } - - -def extract_completion_content(response_payload: dict[str, Any]) -> str: - choices = response_payload.get("choices") or [] - if not choices: - raise ValueError("OpenAI response did not include choices") - - message = choices[0].get("message") or {} - content = message.get("content") - if not isinstance(content, str) or not content.strip(): - raise ValueError("OpenAI response did not include text content") - return content.strip() - - -def call_openai(payload: dict[str, Any], api_key: str) -> dict[str, Any]: - request = urllib.request.Request( - OPENAI_API_URL, - data=json.dumps(payload).encode("utf-8"), - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - method="POST", - ) - with urllib.request.urlopen(request) as response: - charset = response.headers.get_content_charset("utf-8") - return json.loads(response.read().decode(charset)) - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Run an OpenAI secondary review for the monthly AI review workflow.", - ) - parser.add_argument("--review-kind", required=True, choices=sorted(SUPPORTED_REVIEW_KINDS)) - parser.add_argument("--issue-context-file", required=True, type=Path) - parser.add_argument("--primary-review-file", required=True, type=Path) - parser.add_argument("--output-file", required=True, type=Path) - parser.add_argument("--model", default=os.environ.get("OPENAI_MODEL", "gpt-5.4-mini")) - return parser.parse_args() - - -def main() -> int: - args = parse_args() - api_key = os.environ.get("OPENAI_API_KEY") - if not api_key: - print("OPENAI_API_KEY is required", file=sys.stderr) - return 1 - - issue_context = json.loads(args.issue_context_file.read_text(encoding="utf-8")) - primary_review_text = args.primary_review_file.read_text(encoding="utf-8") - payload = build_request_payload( - model=args.model, - review_kind=args.review_kind, - issue_title=str(issue_context.get("title", "")), - issue_body=str(issue_context.get("body", "")), - primary_review_text=primary_review_text, - ) - - try: - response_payload = call_openai(payload, api_key) - except urllib.error.HTTPError as exc: - detail = exc.read().decode("utf-8", errors="replace") - print(f"OpenAI API request failed: {exc.code} {detail}", file=sys.stderr) - return 1 - - review_payload = json.loads(extract_completion_content(response_payload)) - review_payload["review_kind"] = args.review_kind - review_payload["provider"] = "openai" - review_payload["provider_display_name"] = "GPT Secondary Review" - review_payload["model"] = args.model - - args.output_file.parent.mkdir(parents=True, exist_ok=True) - args.output_file.write_text(json.dumps(review_payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - print(f"secondary_review={args.output_file}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/test_auto_optimization_pr_workflow_config.py b/tests/test_auto_optimization_pr_workflow_config.py deleted file mode 100644 index 87b370d..0000000 --- a/tests/test_auto_optimization_pr_workflow_config.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path - - -PROJECT_ROOT = Path(__file__).resolve().parents[1] -AUTO_WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "auto_optimization_pr.yml" -MERGE_WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "auto_merge_optimization_pr.yml" -FEEDBACK_WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "codex_pr_feedback.yml" -CI_WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "ci.yml" - - -class AutoOptimizationPrWorkflowConfigTests(unittest.TestCase): - def test_auto_optimization_workflow_handles_monthly_task_issues(self) -> None: - self.assertFalse(AUTO_WORKFLOW.exists()) - - def test_auto_merge_workflow_waits_for_ci_and_merges_only_safe_ready_prs(self) -> None: - workflow = MERGE_WORKFLOW.read_text(encoding="utf-8") - - self.assertIn("workflow_run:", workflow) - self.assertIn('workflows: ["CI"]', workflow) - self.assertIn("codex/monthly-optimization-issue-", workflow) - self.assertNotIn("automation/monthly-optimization-issue-", workflow) - self.assertIn("gh pr view", workflow) - self.assertIn("labels", workflow) - self.assertIn("evaluate_changed_files", workflow) - self.assertIn("Task-level auto-merge eligible: `yes`", workflow) - self.assertIn("auto-merge-ok", workflow) - self.assertIn("missing_auto_merge_label", workflow) - self.assertIn("gh pr merge", workflow) - - def test_ci_workflow_supports_manual_dispatch(self) -> None: - workflow = CI_WORKFLOW.read_text(encoding="utf-8") - self.assertIn("workflow_dispatch:", workflow) - - def test_codex_feedback_workflow_requeues_failed_ci_and_review_feedback(self) -> None: - workflow = FEEDBACK_WORKFLOW.read_text(encoding="utf-8") - - self.assertIn("workflow_run:", workflow) - self.assertIn("pull_request_review:", workflow) - self.assertIn("codex/monthly-optimization-issue-", workflow) - self.assertIn("auto-optimization-pr:issue-", workflow) - self.assertIn("gh issue comment", workflow) - self.assertIn('MAX_CODEX_FEEDBACK_ROUNDS: "3"', workflow) - self.assertIn("gh issue edit", workflow) - self.assertIn("--remove-label codex-bridge", workflow) - self.assertIn("Codex PR Retry Limit Reached", workflow) - self.assertIn("Codex PR CI Feedback", workflow) - self.assertIn("Codex PR Review Feedback", workflow) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_build_ai_review_payload.py b/tests/test_build_ai_review_payload.py deleted file mode 100644 index e5fe619..0000000 --- a/tests/test_build_ai_review_payload.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.build_ai_review_payload import SCHEMA_VERSION, build_review_payload - - -class BuildAiReviewPayloadTests(unittest.TestCase): - def test_build_review_payload_carries_standardized_root_fields(self) -> None: - payload = build_review_payload( - source_repo="QuantStrategyLab/CryptoSnapshotPipelines", - review_kind="upstream_selector", - issue_context={"number": 11, "title": "Monthly Report Review: 2026-04-01"}, - secondary_review={ - "provider": "openai", - "provider_display_name": "GPT Secondary Review", - "model": "gpt-5.4-mini", - "verdict": "agree", - "risk_level": "low", - "production_recommendation": "keep_production_as_is", - "summary": "Looks consistent.", - "key_findings": ["No blocking issue found."], - "recommended_actions": [], - "follow_up_checks": [], - }, - run_url="https://github.com/example/repo/actions/runs/1", - ) - - self.assertEqual(payload["schema_version"], SCHEMA_VERSION) - self.assertEqual(payload["repo_role"], "upstream_selector_review") - self.assertEqual(payload["source_issue"]["url"], "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11") - self.assertEqual(payload["secondary_reviewer"]["model"], "gpt-5.4-mini") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_build_monthly_optimization_plan.py b/tests/test_build_monthly_optimization_plan.py deleted file mode 100644 index ae06702..0000000 --- a/tests/test_build_monthly_optimization_plan.py +++ /dev/null @@ -1,151 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.build_monthly_optimization_plan import build_plan, render_summary_markdown - - -class BuildMonthlyOptimizationPlanTests(unittest.TestCase): - def test_build_plan_groups_in_scope_actions_by_owner_repo(self) -> None: - upstream_review = { - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "review_kind": "upstream_selector", - "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01", "url": "https://github.com/a/b/issues/11"}, - "risk_level": "medium", - "production_recommendation": "research_only", - "summary": "Need more challenger evidence.", - "recommended_actions": [ - { - "owner_repo": "CryptoSnapshotPipelines", - "title": "Add challenger breadth check", - "risk_level": "low", - "auto_pr_safe": True, - "experiment_only": True, - "summary": "Improve evidence coverage.", - } - ], - } - - plan = build_plan(upstream_review) - - self.assertEqual(plan["highest_review_risk"], "medium") - self.assertIn("CryptoSnapshotPipelines", plan["repo_action_summary"]) - self.assertEqual(len(plan["safe_auto_pr_candidates"]), 1) - self.assertEqual(len(plan["experiment_candidates"]), 1) - - def test_build_plan_keeps_non_snapshot_actions_out_of_scope(self) -> None: - upstream_review = { - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "review_kind": "upstream_selector", - "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01", "url": "https://github.com/a/b/issues/11"}, - "risk_level": "low", - "production_recommendation": "keep_production_as_is", - "summary": "Upstream is stable.", - "recommended_actions": [ - { - "owner_repo": "CryptoStrategies", - "title": "Add selector report note", - "risk_level": "low", - "auto_pr_safe": True, - "experiment_only": False, - "summary": "Document upstream selector evidence.", - }, - { - "owner_repo": "BinancePlatform", - "title": "Check DCA and rotation eligibility gates", - "risk_level": "low", - "auto_pr_safe": True, - "experiment_only": False, - "summary": "Downstream runtime follow-up should not be fanout from this planner.", - }, - ], - } - - plan = build_plan(upstream_review) - - self.assertNotIn("CryptoStrategies", plan["repo_action_summary"]) - self.assertNotIn("BinancePlatform", plan["repo_action_summary"]) - self.assertEqual( - sorted(action["owner_repo"] for action in plan["out_of_scope_actions"]), - ["BinancePlatform", "CryptoStrategies"], - ) - self.assertEqual(len(plan["safe_auto_pr_candidates"]), 0) - - def test_render_summary_markdown_mentions_source_reviews_and_repos(self) -> None: - plan = { - "highest_review_risk": "medium", - "safe_auto_pr_candidates": [{}, {}], - "experiment_candidates": [{}], - "human_review_required": [{}], - "source_reviews": [ - { - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "risk_level": "medium", - "production_recommendation": "research_only", - "summary": "Need more evidence.", - "source_issue": {"title": "Monthly Report Review: 2026-04-01", "url": "https://github.com/a/b/issues/11"}, - "run_url": "https://github.com/a/b/actions/runs/1", - } - ], - "repo_action_summary": { - "CryptoSnapshotPipelines": { - "actions": [ - { - "risk_level": "low", - "title": "Add challenger breadth check", - "summary": "Improve evidence coverage.", - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue_number": 11, - "auto_pr_safe": True, - "experiment_only": True, - } - ] - } - }, - "operator_focus": ["QuantStrategyLab/CryptoSnapshotPipelines: Need more evidence."], - } - - markdown = render_summary_markdown(plan) - - self.assertIn("# Monthly Optimization Planner", markdown) - self.assertIn("QuantStrategyLab/CryptoSnapshotPipelines", markdown) - self.assertIn("Add challenger breadth check", markdown) - self.assertIn("Operator Focus", markdown) - - def test_render_summary_mentions_out_of_scope_actions(self) -> None: - plan = { - "highest_review_risk": "low", - "safe_auto_pr_candidates": [], - "experiment_candidates": [], - "human_review_required": [], - "source_reviews": [ - { - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "risk_level": "low", - "production_recommendation": "keep_production_as_is", - "summary": "Stable.", - "source_issue": {"title": "Monthly Report Review: 2026-04-01", "url": "https://github.com/a/b/issues/11"}, - "run_url": "https://github.com/a/b/actions/runs/1", - } - ], - "repo_action_summary": {}, - "operator_focus": [], - "out_of_scope_actions": [ - { - "risk_level": "low", - "owner_repo": "BinancePlatform", - "title": "Check downstream gates", - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue_number": 11, - } - ], - } - - markdown = render_summary_markdown(plan) - - self.assertIn("Out-of-Scope Actions", markdown) - self.assertIn("BinancePlatform", markdown) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_download_ai_review_artifact.py b/tests/test_download_ai_review_artifact.py deleted file mode 100644 index b2e055d..0000000 --- a/tests/test_download_ai_review_artifact.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.download_ai_review_artifact import select_ai_review_artifact - - -class DownloadAiReviewArtifactTests(unittest.TestCase): - def test_select_ai_review_artifact_picks_latest_matching_artifact(self) -> None: - artifact = select_ai_review_artifact( - { - "artifacts": [ - {"id": 10, "name": "other-artifact"}, - {"id": 20, "name": "ai-monthly-review-9"}, - {"id": 30, "name": "ai-monthly-review-11"}, - ] - } - ) - - self.assertEqual(artifact["id"], 30) - self.assertEqual(artifact["name"], "ai-monthly-review-11") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_experiment_validation_workflow_config.py b/tests/test_experiment_validation_workflow_config.py deleted file mode 100644 index c1666bf..0000000 --- a/tests/test_experiment_validation_workflow_config.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path - - -PROJECT_ROOT = Path(__file__).resolve().parents[1] -WORKFLOW_PATH = PROJECT_ROOT / ".github" / "workflows" / "experiment_validation.yml" - - -class ExperimentValidationWorkflowConfigTests(unittest.TestCase): - def test_workflow_runs_shadow_build_and_posts_comment(self) -> None: - workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - - self.assertIn("workflow_dispatch:", workflow) - self.assertIn("issue_number:", workflow) - self.assertNotIn("types: [opened, edited, reopened, labeled]", workflow) - self.assertNotIn("github.event.issue", workflow) - self.assertIn("self-hosted", workflow) - self.assertIn("prepare_experiment_validation.py", workflow) - self.assertIn("download_history.py", workflow) - self.assertIn("run_monthly_shadow_build.py", workflow) - self.assertIn("run_walkforward_validation.py", workflow) - self.assertIn("render_experiment_validation_summary.py", workflow) - self.assertIn("post_experiment_validation_comment.py", workflow) - self.assertIn("actions/upload-artifact@v7", workflow) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_fanout_monthly_optimization_tasks.py b/tests/test_fanout_monthly_optimization_tasks.py deleted file mode 100644 index 37acfdc..0000000 --- a/tests/test_fanout_monthly_optimization_tasks.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.fanout_monthly_optimization_tasks import ( - build_closed_issue_body, - build_issue_body, - build_issue_title, - build_marker, - issue_labels_for_actions, -) - - -class FanoutMonthlyOptimizationTasksTests(unittest.TestCase): - def setUp(self) -> None: - self.plan = { - "source_reviews": [ - { - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01"}, - }, - ], - "repo_action_summary": { - "CryptoStrategies": { - "count": 2, - "highest_risk_level": "high", - "actions": [ - { - "risk_level": "high", - "title": "Review selector allocation contract", - "summary": "Keep shared strategy semantics aligned with the upstream monthly pool.", - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue_number": 11, - "source_issue_url": "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11", - "auto_pr_safe": False, - "experiment_only": False, - }, - { - "risk_level": "low", - "title": "Add strategy contract diagnostics", - "summary": "Keep selector/runtime contract assumptions visible in tests.", - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue_number": 11, - "source_issue_url": "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11", - "auto_pr_safe": True, - "experiment_only": False, - }, - ], - } - }, - } - - def test_build_marker_and_title_include_owner_repo(self) -> None: - self.assertEqual( - build_marker(self.plan, "CryptoStrategies"), - "<!-- monthly-optimization-task:CryptoStrategies:QuantStrategyLab/CryptoSnapshotPipelines#11 -->", - ) - self.assertEqual( - build_issue_title(self.plan, "CryptoStrategies"), - "Monthly Optimization Tasks · CryptoStrategies: 2026-04-01", - ) - - def test_build_issue_body_lists_repo_specific_actions_and_flags(self) -> None: - body = build_issue_body( - self.plan, - "CryptoStrategies", - planner_issue_url="https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/20", - ) - - self.assertIn("# Monthly Optimization Tasks · CryptoStrategies", body) - self.assertIn("Planner issue: https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/20", body) - self.assertIn("Actions in this repo: `2`", body) - self.assertIn("Highest repo risk: `high`", body) - self.assertIn("Review selector allocation contract", body) - self.assertIn("Add strategy contract diagnostics [auto-pr-safe]", body) - self.assertIn("Source: [QuantStrategyLab/CryptoSnapshotPipelines #11]", body) - - def test_build_closed_issue_body_marks_repo_as_resolved(self) -> None: - body = build_closed_issue_body( - self.plan, - "CryptoStrategies", - planner_issue_url="https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/20", - ) - - self.assertIn("<!-- monthly-optimization-task:CryptoStrategies:", body) - self.assertIn("No repo-scoped tasks remain", body) - self.assertIn("This issue is being closed", body) - - def test_crypto_snapshot_low_risk_tasks_are_queued_for_codex_bridge(self) -> None: - crypto_plan = { - **self.plan, - "repo_action_summary": { - "CryptoSnapshotPipelines": { - "count": 1, - "highest_risk_level": "low", - "actions": [ - { - "risk_level": "low", - "title": "Improve monthly review diagnostics", - "summary": "Keep release warnings visible in the report.", - "source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", - "source_issue_number": 11, - "source_issue_url": "https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11", - "auto_pr_safe": True, - "experiment_only": False, - } - ], - } - }, - } - - actions = crypto_plan["repo_action_summary"]["CryptoSnapshotPipelines"]["actions"] - body = build_issue_body(crypto_plan, "CryptoSnapshotPipelines") - - self.assertEqual( - issue_labels_for_actions(actions, "CryptoSnapshotPipelines"), - ["monthly-optimization-task", "codex-bridge", "auto-merge-ok"], - ) - self.assertIn("## Codex Bridge Contract", body) - self.assertIn("self-hosted VPS ccbot/Codex", body) - self.assertIn("codex/monthly-optimization-issue-<issue-number>", body) - self.assertIn("<!-- auto-optimization-pr:issue-<issue-number> -->", body) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_monthly_optimization_planner_workflow_config.py b/tests/test_monthly_optimization_planner_workflow_config.py deleted file mode 100644 index 09d3f43..0000000 --- a/tests/test_monthly_optimization_planner_workflow_config.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path - - -PROJECT_ROOT = Path(__file__).resolve().parents[1] -WORKFLOW_PATH = PROJECT_ROOT / ".github" / "workflows" / "monthly_optimization_planner.yml" - - -class MonthlyOptimizationPlannerWorkflowConfigTests(unittest.TestCase): - def test_planner_workflow_downloads_artifacts_posts_issue_and_fans_out_tasks(self) -> None: - workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - - self.assertIn("workflow_dispatch:", workflow) - self.assertIn("upstream_run_id:", workflow) - self.assertIn("actions: write", workflow) - self.assertIn("gh run download", workflow) - self.assertIn("Resolve downloaded artifact paths", workflow) - self.assertIn("Prepare upstream review payload", workflow) - self.assertIn("build_ai_review_payload.py", workflow) - self.assertIn("build_monthly_optimization_plan.py", workflow) - self.assertIn("post_monthly_optimization_issue.py", workflow) - self.assertIn("fanout_monthly_optimization_tasks.py", workflow) - self.assertIn("Fan out CryptoSnapshotPipelines task issue", workflow) - self.assertIn("Resolve upstream experiment validation target", workflow) - self.assertIn("Dispatch CryptoSnapshotPipelines experiment validation", workflow) - self.assertIn("gh workflow run experiment_validation.yml", workflow) - self.assertIn("Append fanout summary", workflow) - self.assertIn("upstream_review_payload.json", workflow) - self.assertIn("actions/upload-artifact@v7", workflow) - self.assertIn("monthly-optimization-plan-", workflow) - self.assertNotIn("CryptoStrategies", workflow) - self.assertNotIn("CROSS_REPO_GITHUB_TOKEN", workflow) - self.assertNotIn("actions/create-github-app-token@v3", workflow) - self.assertNotIn("BinancePlatform", workflow) - self.assertNotIn("downstream_run_id", workflow) - self.assertNotIn("downstream_repo", workflow) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_monthly_publish_workflow_config.py b/tests/test_monthly_publish_workflow_config.py index b4591a0..7184717 100644 --- a/tests/test_monthly_publish_workflow_config.py +++ b/tests/test_monthly_publish_workflow_config.py @@ -65,6 +65,10 @@ def test_source_local_legacy_ai_workflows_are_removed(self) -> None: self.assertFalse((workflow_dir / "ai_review.yml").exists()) self.assertFalse((workflow_dir / "auto_optimization_pr.yml").exists()) + self.assertFalse((workflow_dir / "monthly_optimization_planner.yml").exists()) + self.assertFalse((workflow_dir / "experiment_validation.yml").exists()) + self.assertFalse((workflow_dir / "auto_merge_optimization_pr.yml").exists()) + self.assertFalse((workflow_dir / "codex_pr_feedback.yml").exists()) def test_chinese_readme_matches_current_monthly_review_defaults(self) -> None: readme = README_ZH_PATH.read_text(encoding="utf-8") diff --git a/tests/test_post_experiment_validation_comment.py b/tests/test_post_experiment_validation_comment.py deleted file mode 100644 index e014810..0000000 --- a/tests/test_post_experiment_validation_comment.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.post_experiment_validation_comment import COMMENT_MARKER, build_comment_body - - -class PostExperimentValidationCommentTests(unittest.TestCase): - def test_build_comment_body_includes_marker_and_run_link(self) -> None: - body = build_comment_body( - "Validation content", - "https://github.com/example/repo/actions/runs/123", - ) - - self.assertIn(COMMENT_MARKER, body) - self.assertIn("## Monthly Experiment Validation", body) - self.assertIn("Validation content", body) - self.assertIn("actions/runs/123", body) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_post_monthly_ai_review_comment.py b/tests/test_post_monthly_ai_review_comment.py deleted file mode 100644 index 538c3b7..0000000 --- a/tests/test_post_monthly_ai_review_comment.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.post_monthly_ai_review_comment import ( - COMMENT_MARKER, - build_comment_body, - extract_latest_assistant_text, -) - - -class PostMonthlyAiReviewCommentTests(unittest.TestCase): - def test_extract_latest_assistant_text_returns_last_text_reply(self) -> None: - execution_log = [ - {"type": "system", "subtype": "init"}, - { - "type": "assistant", - "message": { - "content": [ - {"type": "text", "text": "Working on it."}, - ] - }, - }, - { - "type": "assistant", - "message": { - "content": [ - {"type": "tool_use", "name": "Read", "input": {"file_path": "x"}}, - {"type": "text", "text": "## English\nReview\n\n## 中文\n评审"}, - ] - }, - }, - {"type": "result", "subtype": "success"}, - ] - - review_text = extract_latest_assistant_text(execution_log) - - self.assertEqual(review_text, "## English\nReview\n\n## 中文\n评审") - - def test_build_comment_body_includes_marker_and_run_link(self) -> None: - body = build_comment_body("Review content", "https://github.com/example/repo/actions/runs/1") - - self.assertIn(COMMENT_MARKER, body) - self.assertIn("## AI Monthly Review", body) - self.assertIn("Review content", body) - self.assertIn("actions/runs/1", body) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_post_monthly_optimization_issue.py b/tests/test_post_monthly_optimization_issue.py deleted file mode 100644 index 04e75c2..0000000 --- a/tests/test_post_monthly_optimization_issue.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.post_monthly_optimization_issue import build_issue_body, build_issue_title, build_marker - - -class PostMonthlyOptimizationIssueTests(unittest.TestCase): - def test_build_title_and_marker_use_source_reviews(self) -> None: - plan = { - "source_reviews": [ - {"source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01"}}, - ] - } - - self.assertEqual(build_marker(plan), "<!-- monthly-optimization-plan:QuantStrategyLab/CryptoSnapshotPipelines#11 -->") - self.assertEqual(build_issue_title(plan), "Monthly Optimization Plan: 2026-04-01") - - def test_build_issue_body_prefixes_marker(self) -> None: - plan = { - "source_reviews": [ - {"source_repo": "QuantStrategyLab/CryptoSnapshotPipelines", "source_issue": {"number": 11, "title": "Monthly Report Review: 2026-04-01"}} - ] - } - - body = build_issue_body(plan, "# Monthly Optimization Planner\n") - - self.assertTrue(body.startswith("<!-- monthly-optimization-plan:")) - self.assertIn("# Monthly Optimization Planner", body) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_prepare_auto_optimization_pr.py b/tests/test_prepare_auto_optimization_pr.py deleted file mode 100644 index cfd701c..0000000 --- a/tests/test_prepare_auto_optimization_pr.py +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -import tempfile -import unittest -from pathlib import Path - -from scripts.prepare_auto_optimization_pr import build_payload, evaluate_changed_files, parse_actions, render_pr_body - - -PROJECT_ROOT = Path(__file__).resolve().parents[1] - - -class PrepareAutoOptimizationPrTests(unittest.TestCase): - def setUp(self) -> None: - self.issue_context = { - "number": 22, - "title": "Monthly Optimization Tasks · CryptoSnapshotPipelines: 2026-04-01 / 2026-03", - "body": """# Monthly Optimization Tasks · CryptoSnapshotPipelines - -## Actions -- [ ] `low` Restore monthly shadow/challenger build generation before review [auto-pr-safe] - - Summary: Ensure `official_baseline` and `challenger_topk_60` are produced each month. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11) -- [ ] `low` Document and verify tie-breaking for equal scores [auto-pr-safe] - - Summary: Confirm the secondary sort used for equal scores is deterministic, stable, and documented. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11) -- [ ] `low` Add a boundary tracker [auto-pr-safe, experiment-only] - - Summary: Track near-cutoff symbols monthly. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://github.com/QuantStrategyLab/CryptoSnapshotPipelines/issues/11) -""", - } - - def test_parse_actions_preserves_risk_flags_and_source(self) -> None: - actions = parse_actions(self.issue_context["body"]) - - self.assertEqual(len(actions), 3) - self.assertEqual(actions[0]["risk_level"], "low") - self.assertEqual(actions[0]["flags"], ["auto-pr-safe"]) - self.assertEqual(actions[2]["flags"], ["auto-pr-safe", "experiment-only"]) - self.assertEqual(actions[2]["source_label"], "QuantStrategyLab/CryptoSnapshotPipelines #11") - - def test_build_payload_skips_completed_clr_tasks_and_excludes_experiments(self) -> None: - payload = build_payload(self.issue_context, repo_root=PROJECT_ROOT) - - self.assertFalse(payload["should_run"]) - self.assertEqual(payload["safe_task_count"], 0) - self.assertEqual(payload["skipped_task_count"], 2) - self.assertEqual(payload["auto_merge_candidate_count"], 0) - self.assertFalse(payload["task_level_auto_merge_allowed"]) - self.assertEqual( - [action["title"] for action in payload["skipped_actions"]], - [ - "Restore monthly shadow/challenger build generation before review", - "Document and verify tie-breaking for equal scores", - ], - ) - - def test_build_payload_marks_readme_note_as_auto_merge_candidate(self) -> None: - issue_context = { - "number": 30, - "title": "Monthly Optimization Tasks · Sandbox", - "body": """# Monthly Optimization Tasks · Sandbox - -## Actions -- [ ] `low` Add a short README note [auto-pr-safe] - - Summary: Document a small operator-facing behavior. - - Source: [Sandbox #1](https://example.com/issues/1) -""", - } - with tempfile.TemporaryDirectory() as temp_dir: - payload = build_payload(issue_context, repo_root=Path(temp_dir)) - self.assertTrue(payload["should_run"]) - self.assertEqual(payload["safe_task_count"], 1) - self.assertEqual(payload["auto_merge_candidate_count"], 1) - self.assertEqual(payload["draft_only_task_count"], 0) - self.assertTrue(payload["task_level_auto_merge_allowed"]) - - def test_evaluate_changed_files_blocks_selector_paths(self) -> None: - allowed = evaluate_changed_files(["docs/operator_runbook.md"], repo_root=PROJECT_ROOT) - blocked = evaluate_changed_files(["src/ranking.py", "README.md"], repo_root=PROJECT_ROOT) - - self.assertTrue(allowed["allowed"]) - self.assertFalse(blocked["allowed"]) - self.assertEqual(blocked["blocked_files"], ["src/ranking.py"]) - - def test_render_pr_body_contains_merge_policy_and_issue_reference(self) -> None: - issue_context = { - "number": 30, - "title": "Monthly Optimization Tasks · Sandbox", - "body": """# Monthly Optimization Tasks · Sandbox - -## Actions -- [ ] `low` Add a short README note [auto-pr-safe] - - Summary: Document a small operator-facing behavior. - - Source: [Sandbox #1](https://example.com/issues/1) -""", - } - with tempfile.TemporaryDirectory() as temp_dir: - payload = build_payload(issue_context, repo_root=Path(temp_dir)) - body = render_pr_body(payload) - - self.assertIn("<!-- auto-optimization-pr:issue-30 -->", body) - self.assertIn("Task-level auto-merge eligible: `yes`", body) - self.assertIn("Add a short README note", body) - self.assertIn("Refs #30", body) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_prepare_experiment_validation.py b/tests/test_prepare_experiment_validation.py deleted file mode 100644 index 65eeb03..0000000 --- a/tests/test_prepare_experiment_validation.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.prepare_experiment_validation import build_payload - - -class PrepareExperimentValidationTests(unittest.TestCase): - def test_build_payload_selects_shadow_build_experiment(self) -> None: - issue_context = { - "number": 22, - "title": "Monthly Optimization Tasks · CryptoSnapshotPipelines", - "body": """# Monthly Optimization Tasks · CryptoSnapshotPipelines - -## Actions -- [ ] `low` Run monthly shadow build and archive challenger summaries [auto-pr-safe, experiment-only] - - Summary: Generate official_baseline and challenger_topk_60 coverage each month. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://example.com/11) -- [ ] `low` Document and verify tie-breaking for equal scores [auto-pr-safe] - - Summary: Confirm deterministic secondary sorting. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://example.com/11) -""", - } - - payload = build_payload(issue_context) - - self.assertTrue(payload["should_run"]) - self.assertEqual(payload["experiment_task_count"], 1) - self.assertTrue(payload["run_shadow_build"]) - self.assertFalse(payload["run_walkforward_validation"]) - - def test_build_payload_skips_when_no_experiment_tasks_exist(self) -> None: - issue_context = { - "number": 30, - "title": "Monthly Optimization Tasks · CryptoSnapshotPipelines", - "body": """# Monthly Optimization Tasks · CryptoSnapshotPipelines - -## Actions -- [ ] `low` Document and verify tie-breaking for equal scores [auto-pr-safe] - - Summary: Confirm deterministic secondary sorting. - - Source: [QuantStrategyLab/CryptoSnapshotPipelines #11](https://example.com/11) -""", - } - - payload = build_payload(issue_context) - - self.assertFalse(payload["should_run"]) - self.assertEqual(payload["experiment_task_count"], 0) - self.assertIn("No experiment-only tasks", payload["skip_reason"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_render_experiment_validation_summary.py b/tests/test_render_experiment_validation_summary.py deleted file mode 100644 index 94a0624..0000000 --- a/tests/test_render_experiment_validation_summary.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.render_experiment_validation_summary import build_summary_markdown - - -class RenderExperimentValidationSummaryTests(unittest.TestCase): - def test_build_summary_includes_shadow_track_details(self) -> None: - payload = { - "issue_number": 22, - "issue_title": "Monthly Optimization Tasks · CryptoSnapshotPipelines", - "should_run": True, - "experiment_task_count": 1, - "experiment_actions": [ - { - "risk_level": "low", - "title": "Run monthly shadow build and archive challenger summaries", - "flags": ["auto-pr-safe", "experiment-only"], - "summary": "Generate official_baseline and challenger_topk_60 coverage each month.", - } - ], - "skip_reason": "", - } - shadow_summary = { - "as_of_date": "2026-04-01", - "official_baseline": {"version": "2026-04-01-core_major", "mode": "core_major", "pool_size": 5}, - "shadow_candidate_tracks": { - "tracks": [ - {"track_id": "challenger_topk_60", "profile_name": "challenger_topk_60", "pool_size": 5} - ] - }, - } - - summary = build_summary_markdown(payload, shadow_summary) - - self.assertIn("Monthly Experiment Validation", summary) - self.assertIn("Official baseline version", summary) - self.assertIn("challenger_topk_60", summary) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_render_monthly_ai_review.py b/tests/test_render_monthly_ai_review.py deleted file mode 100644 index 55a53b9..0000000 --- a/tests/test_render_monthly_ai_review.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import annotations - -import unittest - -from scripts.render_monthly_ai_review import build_full_review_markdown, render_secondary_review_markdown - - -class RenderMonthlyAiReviewTests(unittest.TestCase): - def test_render_secondary_review_markdown_includes_actions_and_flags(self) -> None: - payload = { - "provider_display_name": "GPT Secondary Review", - "verdict": "partial_agree", - "risk_level": "medium", - "production_recommendation": "research_only", - "summary": "Evidence is directionally fine but still incomplete.", - "key_findings": ["Shadow coverage is still thin."], - "recommended_actions": [ - { - "title": "Add another challenger track", - "owner_repo": "CryptoSnapshotPipelines", - "risk_level": "low", - "auto_pr_safe": True, - "experiment_only": True, - "summary": "Improve monthly evidence before changing production.", - } - ], - "follow_up_checks": ["Compare challenger turnover before next promotion."], - } - - markdown = render_secondary_review_markdown(payload) - - self.assertIn("## Secondary Review (GPT Secondary Review)", markdown) - self.assertIn("`partial_agree`", markdown) - self.assertIn("auto-pr-safe", markdown) - self.assertIn("experiment-only", markdown) - self.assertIn("Compare challenger turnover", markdown) - - def test_build_full_review_markdown_includes_primary_and_secondary_sections(self) -> None: - markdown = build_full_review_markdown( - "## Claude Primary Review\n\n## English\nPrimary review", - primary_title="Claude Primary Review", - secondary_review_payload={ - "provider_display_name": "GPT Secondary Review", - "verdict": "agree", - "risk_level": "low", - "production_recommendation": "keep_production_as_is", - "summary": "Looks consistent.", - "key_findings": ["No blocking issue found."], - "recommended_actions": [], - "follow_up_checks": [], - }, - ) - - self.assertIn("## Claude Primary Review", markdown) - self.assertIn("## Secondary Review (GPT Secondary Review)", markdown) - self.assertIn("## English", markdown) - self.assertEqual(markdown.count("## Claude Primary Review"), 1) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_run_openai_secondary_review.py b/tests/test_run_openai_secondary_review.py deleted file mode 100644 index 0bdc66f..0000000 --- a/tests/test_run_openai_secondary_review.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -import json -import unittest - -from scripts.run_openai_secondary_review import ( - build_request_payload, - build_system_prompt, - extract_completion_content, - SECONDARY_REVIEW_SCHEMA, -) - - -class RunOpenAiSecondaryReviewTests(unittest.TestCase): - def test_build_system_prompt_for_upstream_selector_mentions_shadow_and_binanceplatform(self) -> None: - prompt = build_system_prompt("upstream_selector") - - self.assertIn("CryptoSnapshotPipelines", prompt) - self.assertIn("shadow/challenger", prompt) - self.assertIn("BinancePlatform", prompt) - - def test_build_request_payload_uses_structured_json_schema(self) -> None: - payload = build_request_payload( - model="gpt-5.4-mini", - review_kind="upstream_selector", - issue_title="Monthly Review", - issue_body="body", - primary_review_text="primary", - ) - - self.assertEqual(payload["model"], "gpt-5.4-mini") - self.assertEqual(payload["response_format"]["type"], "json_schema") - self.assertTrue(payload["response_format"]["json_schema"]["strict"]) - self.assertIn("messages", payload) - - def test_recommended_actions_are_limited_to_strategy_repos(self) -> None: - owner_repo_schema = ( - SECONDARY_REVIEW_SCHEMA["properties"]["recommended_actions"]["items"]["properties"]["owner_repo"] - ) - - self.assertEqual(owner_repo_schema["enum"], ["CryptoSnapshotPipelines", "CryptoStrategies"]) - - def test_extract_completion_content_reads_first_choice_message(self) -> None: - response_payload = { - "choices": [ - { - "message": { - "content": json.dumps({"summary": "ok"}), - } - } - ] - } - - self.assertEqual(extract_completion_content(response_payload), '{"summary": "ok"}') - - -if __name__ == "__main__": - unittest.main()