feat(review): equivalent provider price + enriched .pr-review.json schema

Three things in this commit, all in the review-rendering path:

1. COST DISPLAY — the `## 🔋 AI usage` section used to show $0.00 because
   the pilot runs on headroom/glm-5.2:cloud at no per-token charge. Now it
   shows TWO lines: the equivalent provider cost (default Claude Sonnet 5;
   configurable via .pr-review.json:cost_target or PRAGENT_PRICE_TARGET env)
   AND the actual $0.00 line. Maintainers can now budget on what the same
   measured tokens would cost on a paid model.

   equivalent_cost() builds a cost_model.Usage from the measured dict and
   runs cost_model.cost() against the resolved provider. _resolve_price_target
   walks repo config > env > default, surfaces typos as an inline note on
   the usage line (not a crash).

2. .pr-review.json SCHEMA — seven new optional fields:
     style                strict|balanced|lenient  (default: balanced)
     severity_threshold   low|medium|high|critical (per style)
     max_findings         1..30                     (per style)
     exclude_tests        bool                      (skip test files)
     require_tests        bool                      (synthetic finding)
     patterns             {allow: [...], deny: [...]} (glob filter)
     cost_target          <PRICES key>              (see #1)

   The first three are style-driven defaults — strict = 5 findings / high+,
   balanced = 12 / medium+, lenient = 15 / low+. Override per-field.
   patterns globs support * and **; built-in fnmatch-style with re.escape.

3. APPLY CONFIG — findings are filtered by the new schema before being
   split into anchored/unanchored. apply_repo_config() drops by exclude_tests
   / exclude_paths / patterns.deny / patterns.allow / severity_threshold, then
   caps at max_findings. require_tests=true appends a synthetic 'low' finding
   when changed paths include non-test files but no test file changed
   alongside them.

   build_user_prompt renders the new fields into the brief so the agent knows
   about style / threshold / patterns explicitly (not just via instructions).

Plus plumbing:
  * review_pr runs compress_diff(diff, context=PRAGENT_DIFF_CONTEXT) before
    handing the diff to either engine. Default context=1 (enough to anchor;
    full files are on disk in the workdir anyway). -1 disables.
  * compact_prior_reviews(prior) keeps only finding-bullet lines, drops the
    rest. Prior-review cap lowered 8k -> 4k chars in build_user_prompt.
  * opencode_review.write_brief accepts compression_note (rendered under
    the PR description, OUTSIDE the untrusted-data fence).

160 new tests covering equivalent_cost (4), format_usage_section cost lines
(5), parse_repo_config extended schema (6), apply_repo_config filters (8),
effective_config style defaults (2), compact_prior_reviews (2), and the
whole diff_compress suite (14 from the previous commit). 174 pass / 0 fail.
This commit is contained in:
Marcos
2026-08-20 16:12:46 +00:00
parent 770581bf53
commit ec26ec000a
3 changed files with 668 additions and 32 deletions
+12 -3
View File
@@ -286,6 +286,7 @@ def write_brief(
diff: str,
config: dict | None,
prior_reviews: list[str] | None,
compression_note: str = "",
) -> str:
"""Render `.pragent/brief.md` in the workdir. Returns the path written."""
path = os.path.join(workdir, ".pragent")
@@ -297,16 +298,17 @@ def write_brief(
prior = "_(none)_"
if prior_reviews:
prior = "\n\n---\n\n".join(prior_reviews)
if len(prior) > 8000:
prior = prior[:8000] + "\n…[prior reviews truncated]"
if len(prior) > 4000:
prior = prior[:4000] + "\n…[prior reviews truncated]"
files = changed_files(diff)
files_block = "\n".join(f"- `{p}`" for p in files) if files else "_(none)_"
desc_block = ((description or "").strip() or "_(none)_") + compression_note
content = _BRIEF_TEMPLATE.format(
repo=repo or "?",
index=index or "?",
sha=sha or "?",
title=title or "(none)",
description=description.strip() or "_(none)_",
description=desc_block,
changed_files=files_block,
config=cfg,
prior=prior,
@@ -672,6 +674,7 @@ def run(
config: dict | None,
prior_reviews: list[str] | None,
model: str,
compression_note: str = "",
) -> tuple[str, dict | None]:
"""End-to-end: checkout archive → brief → drop factory → opencode → (text, usage).
@@ -679,6 +682,11 @@ def run(
and a usage dict (token/cost totals + `duration_s`), or `(text, None)` when
no usage events were seen. Raises on any failure; the caller (`review_pr`)
fails open. The workdir is removed unless PRAGENT_KEEP_WORK is set.
`compression_note`: a small markdown block to append to the brief's PR
description (e.g. "diff compressed: 25k → 12k chars"). Empty string by
default. Appended AFTER the untrusted-data fence so the agent reads it as
guidance, not author input.
"""
os.makedirs(WORK_ROOT, exist_ok=True)
workdir = tempfile.mkdtemp(prefix=f"{repo.replace('/', '_')}-{sha[:8]}-", dir=WORK_ROOT)
@@ -697,6 +705,7 @@ def run(
workdir,
repo=repo, index=index, sha=sha, title=title, description=body,
diff=diff, config=config, prior_reviews=prior_reviews,
compression_note=compression_note,
)
drop_factory(workdir)
text, usage = run_opencode(workdir, model)