From 89709cec6ec52453accdd6a9378de316de47884b Mon Sep 17 00:00:00 2001 From: Pinghua Date: Wed, 10 Jun 2026 15:56:43 +0800 Subject: [PATCH] change stdout --- scripts/review_pr.py | 73 +++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/scripts/review_pr.py b/scripts/review_pr.py index fca864a..579dc1c 100644 --- a/scripts/review_pr.py +++ b/scripts/review_pr.py @@ -15,10 +15,12 @@ import argparse import json import os import re +import shlex import shutil import subprocess import sys import threading +import time import urllib.error import urllib.request @@ -316,43 +318,61 @@ def build_prompt(meta, files, diff_path, agent_md, impact_report_path=None): return prompt -def run_claude(claude_bin, project_root, prompt, extra_dir=None): - """Run claude -p, return stdout.""" - cmd = [ - claude_bin, "-p", - "--permission-mode", "acceptEdits", - "--verbose", - ] +def run_claude(claude_bin, project_root, prompt, extra_dir=None, prompt_file=None): + """Run claude -p, return stdout. Prompt is fed via ``< file`` redirect.""" + cmd = [claude_bin, "-p", "--permission-mode", "bypassPermissions"] if extra_dir: cmd += ["--add-dir", extra_dir] - cmd.append(prompt) - print(f"[review_pr] Running: {' '.join(cmd[:4])} ...", file=sys.stderr) + # Write prompt to temp file, then feed via shell redirect to avoid arg-too-long + if not prompt_file: + prompt_file = os.path.join(project_root, ".reviews", "_prompt.txt") + os.makedirs(os.path.dirname(prompt_file), exist_ok=True) + with open(prompt_file, "w", encoding="utf-8") as f: + f.write(prompt) + + shell_cmd = f"{shlex.join(cmd)} < {shlex.quote(prompt_file)}" + print(f"[review_pr] Command: {shell_cmd}", file=sys.stderr) proc = subprocess.Popen( - cmd, + shell_cmd, cwd=project_root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", + shell=True, ) - def _stream(pipe, label, dest_lines): + def _stream(pipe, prefix, dest_lines): for line in pipe: line = line.rstrip("\n") - if line: - print(f"[{label}] {line}", file=sys.stderr) dest_lines.append(line) - - stderr_lines = [] - t = threading.Thread(target=_stream, args=(proc.stderr, "claude:stderr", stderr_lines)) - t.start() + if line.strip(): + print(f"[{prefix}] {line}", file=sys.stderr) stdout_lines = [] - _stream(proc.stdout, "claude", stdout_lines) + stderr_lines = [] - t.join(timeout=10) - proc.wait(timeout=600) + heartbeat_stop = threading.Event() + def _heartbeat(start): + while not heartbeat_stop.is_set(): + heartbeat_stop.wait(30) + if not heartbeat_stop.is_set(): + elapsed = int(time.time() - start) + print(f"[review_pr] ... still running ({elapsed}s elapsed)", file=sys.stderr) + t_beat = threading.Thread(target=_heartbeat, args=(time.time(),)) + t_beat.start() + + t_out = threading.Thread(target=_stream, args=(proc.stdout, "claude", stdout_lines)) + t_err = threading.Thread(target=_stream, args=(proc.stderr, "claude:stderr", stderr_lines)) + t_out.start() + t_err.start() + + t_out.join(timeout=600) + heartbeat_stop.set() + t_err.join(timeout=10) + t_beat.join(timeout=5) + proc.wait(timeout=10) if proc.returncode != 0: print(f"Claude exited with code {proc.returncode}", file=sys.stderr) @@ -367,7 +387,7 @@ def extract_json_from_output(stdout): match = re.search(r"```json\s*([\s\S]*)\s*```", stdout) if not match: print("ERROR: No ```json block found in Claude output.", file=sys.stderr) - print(stdout[:3000], file=sys.stderr) + print(stdout[-3000:], file=sys.stderr) sys.exit(1) try: @@ -526,9 +546,11 @@ def main(): print(f" Diff: {len(diff_text)} chars, ~{diff_text.count(chr(10))} lines") # ── 4. Write diff inside project_root so claude CLI can read it ───── + repo_slug = args.target_repo.replace("/", "_") + prefix = f"{repo_slug}_pr{args.pr}" diff_dir = os.path.join(project_root, ".reviews") os.makedirs(diff_dir, exist_ok=True) - diff_path = os.path.join(diff_dir, f"pr{args.pr}.diff") + diff_path = os.path.join(diff_dir, f"{prefix}.diff") with open(diff_path, "w", encoding="utf-8") as f: f.write(diff_text) print(f" Diff saved to: {diff_path}") @@ -544,18 +566,19 @@ def main(): symbols = _extract_symbols(diff_text) print(f"[review_pr] Extracted {len(symbols)} changed symbol(s): {', '.join(symbols) if symbols else '(none)'}") report = _build_impact_report(repo_dir, changed_file_names, symbols) - impact_report_path = os.path.join(diff_dir, f"pr{args.pr}_impact.md") + impact_report_path = os.path.join(diff_dir, f"{prefix}_impact.md") with open(impact_report_path, "w", encoding="utf-8") as f: f.write(report) print(f" Impact report saved to: {impact_report_path}") else: print("[review_pr] No --repo-dir provided — skipping global impact analysis") + prompt_file = os.path.join(diff_dir, f"{prefix}_prompt.txt") try: # ── 5. Build prompt & run Claude ───────────────────────────────── prompt = build_prompt(meta, files, diff_path, agent_md, impact_report_path) print("[review_pr] Invoking Claude for analysis...") - stdout = run_claude(claude_bin, project_root, prompt, extra_dir=repo_dir) + stdout = run_claude(claude_bin, project_root, prompt, extra_dir=repo_dir, prompt_file=prompt_file) # ── 6. Parse Claude output ─────────────────────────────────────── review = extract_json_from_output(stdout) @@ -573,6 +596,8 @@ def main(): # Clean up temp files if os.path.exists(diff_path): os.unlink(diff_path) + if os.path.exists(prompt_file): + os.unlink(prompt_file) if impact_report_path and os.path.exists(impact_report_path): os.unlink(impact_report_path)