Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ai_review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ jobs:
--output-file data/output/ai_review/final_review.md \
--primary-title "Claude Primary Review"

- name: Build final AI review payload
if: steps.claude_review.outcome == 'success'
run: |
python3 scripts/build_ai_review_payload.py \
--source-repo "${GITHUB_REPOSITORY}" \
--review-kind upstream_selector \
--issue-context-file data/output/ai_review/issue_context.json \
--secondary-review-file data/output/ai_review/secondary_review.json \
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--output-file data/output/ai_review/final_review_payload.json

- name: Upload AI review artifact
if: steps.claude_review.outcome == 'success'
uses: actions/upload-artifact@v7
Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/monthly_optimization_planner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Monthly Optimization Planner

"on":
workflow_dispatch:
inputs:
upstream_run_id:
description: "AI review run id from CryptoLeaderRotation"
required: true
downstream_run_id:
description: "AI review run id from BinancePlatform"
required: true
downstream_repo:
description: "Downstream execution repo"
required: true
default: "QuantStrategyLab/BinancePlatform"

jobs:
planner:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
issues: write

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download upstream AI review artifact
run: |
python3 scripts/download_ai_review_artifact.py \
--repo "${GITHUB_REPOSITORY}" \
--run-id "${{ inputs.upstream_run_id }}" \
--output-dir data/input/upstream \
--token-env GITHUB_TOKEN
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Download downstream AI review artifact
run: |
python3 scripts/download_ai_review_artifact.py \
--repo "${{ inputs.downstream_repo }}" \
--run-id "${{ inputs.downstream_run_id }}" \
--output-dir data/input/downstream \
--token-env CROSS_REPO_GITHUB_TOKEN
env:
CROSS_REPO_GITHUB_TOKEN: ${{ secrets.CROSS_REPO_GITHUB_TOKEN }}

- name: Build monthly optimization plan
run: |
python3 scripts/build_monthly_optimization_plan.py \
--upstream-review-file data/input/upstream/final_review_payload.json \
--downstream-review-file data/input/downstream/final_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: Upload planner artifact
uses: actions/upload-artifact@v7
with:
name: monthly-optimization-plan-${{ inputs.upstream_run_id }}-${{ inputs.downstream_run_id }}
path: data/output/monthly_optimization/

- name: Create monthly optimization issue
run: |
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92 changes: 92 additions & 0 deletions scripts/build_ai_review_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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",
"execution_runtime": "execution_runtime_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())
173 changes: 173 additions & 0 deletions scripts/build_monthly_optimization_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
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 = ["CryptoLeaderRotation", "CryptoStrategies", "BinancePlatform"]


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]:
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": action["owner_repo"],
"title": action["title"],
"risk_level": action["risk_level"],
"auto_pr_safe": bool(action.get("auto_pr_safe")),
"experiment_only": bool(action.get("experiment_only")),
"summary": action["summary"],
}


def build_plan(upstream_review: dict[str, Any], downstream_review: dict[str, Any]) -> dict[str, Any]:
source_reviews = [upstream_review, downstream_review]
normalized_actions = [
normalize_action(review, action)
for review in source_reviews
for action in review.get("recommended_actions", [])
]
repo_groups: dict[str, list[dict[str, Any]]] = defaultdict(list)
for action in normalized_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 normalized_actions if action["auto_pr_safe"] and action["risk_level"] == "low"]
experiment_candidates = [action for action in normalized_actions if action["experiment_only"]]
human_review_required = [
action for action in normalized_actions if (not action["auto_pr_safe"]) or action["risk_level"] != "low"
]
operator_focus = [
f"{review['source_repo']}: {review['summary']}"
for review in source_reviews
]

highest_review_risk = highest_risk([
{"risk_level": upstream_review["risk_level"]},
{"risk_level": downstream_review["risk_level"]},
])

return {
"schema_version": SCHEMA_VERSION,
"generated_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"source_reviews": source_reviews,
"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),
"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'])}`",
"",
"## 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"])

return "\n".join(lines).strip() + "\n"


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Build the monthly optimization plan by combining upstream and downstream AI review payloads.",
)
parser.add_argument("--upstream-review-file", required=True, type=Path)
parser.add_argument("--downstream-review-file", required=True, type=Path)
parser.add_argument("--output-dir", required=True, type=Path)
return parser.parse_args()


def main() -> int:
args = parse_args()
upstream_review = json.loads(args.upstream_review_file.read_text(encoding="utf-8"))
downstream_review = json.loads(args.downstream_review_file.read_text(encoding="utf-8"))
plan = build_plan(upstream_review, downstream_review)
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())
Loading