Build out all 27 modules + capstone (#1)
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""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:]))
|
||||
Reference in New Issue
Block a user