change stdout

This commit is contained in:
Pinghua
2026-06-10 15:56:43 +08:00
parent 971ee4dfff
commit 89709cec6e
+49 -24
View File
@@ -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)