Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions plugins/sentry-skills/skills/iterate-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ Returns JSON 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)
- `bot` - Automated comments (Codecov, Sentry, etc.)
- `bot` - Informational automated comments (Codecov, Dependabot, etc.)
- `resolved` - Already resolved threads

Review bot feedback (from Sentry, Warden, Cursor, Bugbot, CodeQL, etc.) appears in `high`/`medium`/`low` with `review_bot: true` — it is NOT placed in the `bot` bucket.

## Workflow

### 1. Identify PR
Expand All @@ -62,7 +64,7 @@ Stop if no PR exists for the current branch.

Run `${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.py` to get structured failure data.

**Wait if pending:** If bot-related checks (sentry, codecov, cursor, bugbot, seer) are still running, wait before proceeding—they may post additional feedback.
**Wait if pending:** If review bot checks (sentry, warden, cursor, bugbot, seer, codeql) are still running, wait before proceeding—they post actionable feedback that must be evaluated. Informational bots (codecov) are not worth waiting for.

### 3. Fix CI Failures

Expand All @@ -83,6 +85,11 @@ Run `${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.py` to get categorized feedb
- `high` - must address (blockers, security, changes requested)
- `medium` - should address (standard feedback)

This includes review bot feedback (items with `review_bot: true`). Treat it the same as human feedback:
- Real issue found → fix it
- False positive → skip, but explain why in a brief comment
- Never silently ignore review bot feedback — always verify the finding

**Prompt user for selection:**
- `low` - present numbered list and ask which to address:

Expand All @@ -97,7 +104,7 @@ Which would you like to address? (e.g., "1,3" or "all" or "none")

**Skip silently:**
- `resolved` threads
- `bot` comments (informational only)
- `bot` comments (informational only — Codecov, Dependabot, etc.)

### 6. Commit and Push

Expand All @@ -119,7 +126,7 @@ Return to step 2 if CI failed or new feedback appeared.

## Exit Conditions

**Success:** All checks pass, no unaddressed high/medium feedback, user has decided on low-priority items.
**Success:** All checks pass, no unaddressed high/medium feedback (including review bot findings), user has decided on low-priority items.

**Ask for help:** Same failure after 3 attempts, feedback needs clarification, infrastructure issues.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
- high: Must address before merge (h:, blocker, changes requested)
- medium: Should address (m:, standard feedback)
- low: Optional suggestions (l:, nit, style)
- bot: Automated comments (Codecov, Sentry bot, etc.)
- bot: Informational automated comments (Codecov, Dependabot, etc.)
- resolved: Already resolved threads

Bot classification:
- Review bots (Sentry, Warden, Cursor, Bugbot, etc.) provide actionable code
feedback. Their comments are categorized by content into high/medium/low with
a ``review_bot: true`` flag — they are NOT placed in the ``bot`` bucket.
- Info bots (Codecov, Dependabot, Renovate, etc.) post status reports and are
placed in the ``bot`` bucket for silent skipping.
"""
from __future__ import annotations

Expand All @@ -29,22 +36,32 @@
from typing import Any


# Known bot usernames and patterns
BOT_PATTERNS = [
r"(?i)bot$",
r"(?i)^codecov",
# Bots that provide actionable code review feedback (security issues, lint
# violations, bugs). Their comments are categorized by content, not skipped.
REVIEW_BOT_PATTERNS = [
r"(?i)^sentry",
r"(?i)^warden",
r"(?i)^cursor",
r"(?i)^bugbot",
r"(?i)^seer",
r"(?i)^copilot",
r"(?i)^codex",
r"(?i)^claude",
r"(?i)^codeql",
]

# Bots that post informational status reports (coverage, dependency updates).
# These are placed in the ``bot`` bucket and skipped silently.
INFO_BOT_PATTERNS = [
r"(?i)^codecov",
r"(?i)^dependabot",
r"(?i)^renovate",
r"(?i)^github-actions",
r"(?i)^mergify",
r"(?i)^semantic-release",
r"(?i)^sonarcloud",
r"(?i)^snyk",
r"(?i)^cursor",
r"(?i)^bugbot",
r"(?i)^seer",
r"(?i)^copilot",
r"(?i)bot$",
r"(?i)\[bot\]$",
]

Expand Down Expand Up @@ -82,12 +99,19 @@ def get_pr_info(pr_number: int | None = None) -> dict[str, Any] | None:
return run_gh(args)


def is_review_bot(username: str) -> bool:
"""Check if username matches a review bot that posts actionable feedback."""
return any(re.search(p, username) for p in REVIEW_BOT_PATTERNS)


def is_info_bot(username: str) -> bool:
"""Check if username matches an informational bot (skip silently)."""
return any(re.search(p, username) for p in INFO_BOT_PATTERNS)


def is_bot(username: str) -> bool:
"""Check if username matches known bot patterns."""
for pattern in BOT_PATTERNS:
if re.search(pattern, username):
return True
return False
"""Check if username matches any known bot pattern."""
return is_review_bot(username) or is_info_bot(username)


def get_review_comments(owner: str, repo: str, pr_number: int) -> list[dict[str, Any]]:
Expand Down Expand Up @@ -193,7 +217,9 @@ def categorize_comment(comment: dict[str, Any], body: str) -> str:
"""
author = comment.get("author", {}).get("login", "") or comment.get("user", {}).get("login", "")

if is_bot(author):
# Info bots are skipped silently; review bots fall through to content
# categorization so their actionable feedback is not lost.
if is_info_bot(author) and not is_review_bot(author):
return "bot"

# Check for explicit LOGAF markers first
Expand Down Expand Up @@ -249,6 +275,7 @@ def extract_feedback_item(
url: str | None = None,
is_resolved: bool = False,
is_outdated: bool = False,
review_bot: bool = False,
) -> dict[str, Any]:
"""Create a standardized feedback item."""
# Truncate long bodies for summary
Expand All @@ -271,6 +298,8 @@ def extract_feedback_item(
item["resolved"] = True
if is_outdated:
item["outdated"] = True
if review_bot:
item["review_bot"] = True

return item

Expand Down Expand Up @@ -357,7 +386,11 @@ def main():

if is_resolved:
feedback["resolved"].append(item)
elif is_bot(author):
elif is_review_bot(author):
category = categorize_comment(first_comment, body)
item["review_bot"] = True
feedback[category].append(item)
elif is_info_bot(author):
feedback["bot"].append(item)
else:
category = categorize_comment(first_comment, body)
Expand All @@ -384,12 +417,23 @@ def main():
url=comment.get("html_url"),
)

if is_bot(author):
if is_review_bot(author):
category = categorize_comment(comment, body)
item["review_bot"] = True
feedback[category].append(item)
elif is_info_bot(author):
feedback["bot"].append(item)
else:
category = categorize_comment(comment, body)
feedback[category].append(item)

# Count review bot items across priority buckets
review_bot_count = sum(
1 for bucket in ("high", "medium", "low")
for item in feedback[bucket]
if item.get("review_bot")
)

# Build output
output = {
"pr": {
Expand All @@ -404,6 +448,7 @@ def main():
"low": len(feedback["low"]),
"bot_comments": len(feedback["bot"]),
"resolved": len(feedback["resolved"]),
"review_bot_feedback": review_bot_count,
"needs_attention": len(feedback["high"]) + len(feedback["medium"]),
},
"feedback": feedback,
Expand Down