Files
ai-workflow-course/modules/24-assistive-agents/lab/reviewer.py
T
claude 2684095e2f Build out all 27 modules + capstone (#1)
Co-authored-by: claude <claude@jpaul.io>
Co-committed-by: claude <claude@jpaul.io>
2026-06-22 12:19:01 -04:00

99 lines
3.7 KiB
Python

"""Assistive AI reviewer — local simulation of a PR-reviewer bot.
This stands in for a forge-native reviewer (an app/bot triggered when a PR opens, running on a
runner from Module 19) without needing any hosted account. It does the two deterministic halves of
the job and leaves the one judgment call — what actually happens to the PR — to you.
python reviewer.py prompt # assemble the prompt: rubric + diff. Paste to your AI.
python reviewer.py apply ai-review.sample.json # ingest the AI's JSON, render it, gate it
The point of this module: the agent produces comments and a recommendation. It never approves,
never requests-changes-as-a-gate, never merges. The `apply` step ends at a HUMAN DECISION, every
time. Stdlib only — no pip install.
"""
import argparse
import json
import sys
from pathlib import Path
HERE = Path(__file__).parent
PROMPT_HEADER = """\
You are an assistive code reviewer. Follow the rubric below exactly, then review the diff that
follows it. Return ONLY the JSON object the rubric specifies — no prose before or after.
================ REVIEW RUBRIC ================
{rubric}
================ DIFF UNDER REVIEW ============
{diff}
"""
def cmd_prompt(args: argparse.Namespace) -> int:
rubric = Path(args.rubric).read_text()
diff = Path(args.patch).read_text()
print(PROMPT_HEADER.format(rubric=rubric, diff=diff))
return 0
def cmd_apply(args: argparse.Namespace) -> int:
try:
review = json.loads(Path(args.response).read_text())
except (json.JSONDecodeError, FileNotFoundError) as exc:
print(f"error: could not read a JSON review from {args.response}: {exc}")
return 1
summary = review.get("summary", "(no summary)")
recommendation = review.get("recommendation", "comment")
comments = review.get("comments", [])
print("=" * 70)
print("AI REVIEWER — first pass (advisory only)")
print("=" * 70)
print(f"\nSummary: {summary}\n")
if not comments:
print("No line comments.\n")
order = {"blocker": 0, "suggestion": 1, "nit": 2}
for c in sorted(comments, key=lambda c: order.get(c.get("severity", "nit"), 9)):
sev = c.get("severity", "nit").upper()
loc = f"{c.get('file', '?')}:{c.get('line', '?')}"
print(f" [{sev:10}] {loc}")
print(f" {c.get('comment', '')}\n")
print("-" * 70)
print(f"Agent's recommendation: {recommendation}")
print("-" * 70)
print(
"\nThis is the human decision gate. The agent did NOT merge, approve, or block.\n"
"It only commented. You decide what happens next:\n"
" - merge you read the comments, you disagree or they're addressed\n"
" - request changes you agree; push the fix on the branch and re-run\n"
" - dismiss the agent is wrong or noisy; ignore and move on\n"
"\nNothing in this repo changes until you act. That's the whole point of Module 24.\n"
)
return 0
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("prompt", help="assemble the review prompt to paste to your AI")
p.add_argument("--rubric", default=str(HERE / "review-rubric.md"))
p.add_argument("--patch", default=str(HERE / "feature.patch"))
p.set_defaults(func=cmd_prompt)
a = sub.add_parser("apply", help="ingest the AI's JSON review and render the decision gate")
a.add_argument("response", help="path to the JSON the AI returned")
a.set_defaults(func=cmd_apply)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))