diff --git a/plugins/sentry-skills/skills/iterate-pr/SKILL.md b/plugins/sentry-skills/skills/iterate-pr/SKILL.md index d94a943..6277d3a 100644 --- a/plugins/sentry-skills/skills/iterate-pr/SKILL.md +++ b/plugins/sentry-skills/skills/iterate-pr/SKILL.md @@ -18,10 +18,12 @@ Continuously iterate on the current branch until all CI checks pass and review f Fetches CI check status and extracts failure snippets from logs. ```bash -uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py [--pr NUMBER] +uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py [--pr NUMBER] [--summary] ``` -Returns JSON: +Use `--summary` for a compact human-readable view (ideal for quick status checks and polling). Omit for full JSON when you need to process individual check details. + +Returns JSON (without `--summary`): ```json { "pr": {"number": 123, "branch": "feat/foo"}, @@ -38,10 +40,12 @@ Returns JSON: Fetches and categorizes PR review feedback using the [LOGAF scale](https://develop.sentry.dev/engineering-practices/code-review/#logaf-scale). ```bash -uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py [--pr NUMBER] +uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py [--pr NUMBER] [--summary] ``` -Returns JSON with feedback categorized as: +Use `--summary` for a compact human-readable view. Omit for full JSON when you need to process individual feedback items. + +Returns JSON (without `--summary`) with feedback categorized as: - `high` - Must address before merge (`h:`, blocker, changes requested) - `medium` - Should address (`m:`, standard feedback) - `low` - Optional (`l:`, nit, style, suggestion) @@ -150,15 +154,15 @@ git push Poll CI status and review feedback in a loop instead of blocking: -1. Run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py` to get current CI status +1. Run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py --summary` to get current CI status 2. If all checks passed → proceed to exit conditions -3. If any checks failed (none pending) → return to step 5 +3. If any checks failed (none pending) → run without `--summary` for full failure details, then return to step 5 4. If checks are still pending: - a. Run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py` for new review feedback - b. Address any new high/medium feedback immediately (same as step 3) + a. Run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py --summary` for new review feedback + b. Address any new high/medium feedback immediately (run without `--summary` to get full details) c. If changes were needed, commit and push (this restarts CI), then continue polling d. Sleep 30 seconds, then repeat from sub-step 1 -5. After all checks pass, do a final feedback check: `sleep 10`, then run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py`. Address any new high/medium feedback — if changes are needed, return to step 6. +5. After all checks pass, do a final feedback check: `sleep 10`, then run `uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py --summary`. Address any new high/medium feedback — if changes are needed, return to step 6. ### 8. Repeat diff --git a/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_checks.py b/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_checks.py index 03d1edf..bd7b27c 100755 --- a/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_checks.py +++ b/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_checks.py @@ -168,9 +168,40 @@ def get_run_logs(run_id: int) -> str | None: return None +def format_summary(output: dict[str, Any]) -> str: + """Format a compact summary of check status.""" + s = output["summary"] + pr = output["pr"] + lines = [f"PR #{pr['number']} ({pr['branch']}) — {s['passed']}/{s['total']} passed"] + + if s["failed"]: + lines.append(f" {s['failed']} failed, {s['pending']} pending") + elif s["pending"]: + lines.append(f" {s['pending']} pending") + else: + lines.append(" All checks passed") + + non_pass = [c for c in output["checks"] if c["status"] != "pass"] + for c in non_pass: + status = c["status"].upper() + line = f" [{status}] {c['name']}" + if c.get("run_id"): + line += f" (run {c['run_id']})" + lines.append(line) + if c.get("log_snippet"): + snippet_lines = c["log_snippet"].split("\n")[:5] + for sl in snippet_lines: + lines.append(f" {sl}") + if len(c["log_snippet"].split("\n")) > 5: + lines.append(f" ... ({len(c['log_snippet'].split(chr(10)))} lines total)") + + return "\n".join(lines) + + def main(): parser = argparse.ArgumentParser(description="Fetch PR CI checks with failure snippets") parser.add_argument("--pr", type=int, help="PR number (defaults to current branch PR)") + parser.add_argument("--summary", action="store_true", help="Output compact summary instead of full JSON") args = parser.parse_args() # Get PR info @@ -235,7 +266,10 @@ def main(): "checks": processed_checks, } - print(json.dumps(output, indent=2)) + if args.summary: + print(format_summary(output)) + else: + print(json.dumps(output, indent=2)) if __name__ == "__main__": diff --git a/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_feedback.py b/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_feedback.py index 80517d3..84fcfac 100755 --- a/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_feedback.py +++ b/plugins/sentry-skills/skills/iterate-pr/scripts/fetch_pr_feedback.py @@ -307,9 +307,42 @@ def extract_feedback_item( return item +def format_summary(output: dict[str, Any]) -> str: + """Format a compact summary of PR feedback.""" + s = output["summary"] + pr = output["pr"] + lines = [f"PR #{pr['number']} — {s['needs_attention']} need attention ({s['high']} high, {s['medium']} medium, {s['low']} low)"] + + if pr.get("review_decision"): + lines.append(f" Review: {pr['review_decision']}") + + if output.get("action_required"): + lines.append(f" Action: {output['action_required']}") + + feedback = output["feedback"] + for priority in ("high", "medium", "low"): + for item in feedback[priority]: + location = "" + if item.get("path"): + location = f" in {item['path']}" + if item.get("line"): + location += f":{item['line']}" + bot_tag = " [review-bot]" if item.get("review_bot") else "" + lines.append(f" [{priority.upper()}] @{item['author']}{bot_tag}{location}") + lines.append(f" {item['body'][:120]}") + + if s["resolved"]: + lines.append(f" ({s['resolved']} resolved threads skipped)") + if s["bot_comments"]: + lines.append(f" ({s['bot_comments']} bot comments skipped)") + + return "\n".join(lines) + + def main(): parser = argparse.ArgumentParser(description="Fetch and categorize PR feedback") parser.add_argument("--pr", type=int, help="PR number (defaults to current branch PR)") + parser.add_argument("--summary", action="store_true", help="Output compact summary instead of full JSON") args = parser.parse_args() # Get repo info @@ -468,7 +501,10 @@ def main(): else: output["action_required"] = None - print(json.dumps(output, indent=2)) + if args.summary: + print(format_summary(output)) + else: + print(json.dumps(output, indent=2)) if __name__ == "__main__":