Add mise task to list unresolved PR comments #40
2 changed files with 124 additions and 2 deletions
Add mise task to list unresolved PR comments
- New pr-comments task queries Forge API for review comments - Filters to show only comments without a resolver (unresolved) - Updated CLAUDE.md to use this task after user reviews PRs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
commit
e64f74ab8d
|
|
@ -27,7 +27,11 @@ tea pr create --title "Description of change" --description "$(cat <<'EOF'
|
|||
EOF
|
||||
)"
|
||||
```
|
||||
The user will review your work as you go, and will merge the pr as the last step in the process, even after deploying.
|
||||
The user will review your work as you go, and will merge the PR as the last step in the process, even after deploying. After the user reviews the PR and leaves comments, check for unresolved comments with:
|
||||
```fish
|
||||
mise run pr-comments <pr_number>
|
||||
```
|
||||
Address each unresolved comment before proceeding. The user will resolve comments on the Forge UI as they are addressed.
|
||||
|
||||
3. Always keep the zk cards up to date with any changes, and suggest new links to new cards whenever appropriate. Refer back to the zk docs often during the process of planning and making corrections to ensure accuracy, and if you make a mistake, figure out a way to guard against it using the zk.
|
||||
|
||||
|
|
@ -60,7 +64,7 @@ The user will review your work as you go, and will merge the pr as the last step
|
|||
|
||||
### Kubernetes Services (via ArgoCD)
|
||||
|
||||
Most services are migrating to Kubernetes. These are managed via ArgoCD using the app-of-apps pattern:
|
||||
Most services run on `k8s.tail8d86e.ts.net`, via minikube on indri. They are managed via ArgoCD using the app-of-apps pattern:
|
||||
|
||||
- **Application definitions**: `argocd/apps/<service>.yaml`
|
||||
- **Manifests**: `argocd/manifests/<service>/`
|
||||
|
|
|
|||
118
mise-tasks/pr-comments
Executable file
118
mise-tasks/pr-comments
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env -S uv run --script
|
||||
|
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# dependencies = ["httpx>=0.27.0", "rich>=13.0.0"]
|
||||
# ///
|
||||
#MISE description="List unresolved comments on a PR"
|
||||
#USAGE arg "<pr_number>" help="Pull request number"
|
||||
"""Fetch and display unresolved review comments on a pull request.
|
||||
|
||||
This script queries the Forge (Gitea) API to find all review comments that
|
||||
have not been resolved on a given PR. A comment is considered unresolved
|
||||
if its 'resolver' field is null.
|
||||
|
||||
Usage: mise run pr-comments <pr_number>
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
from rich.text import Text
|
||||
|
||||
FORGE_API_BASE = "https://forge.tail8d86e.ts.net/api/v1"
|
||||
REPO_OWNER = "eblume"
|
||||
REPO_NAME = "blumeops"
|
||||
|
||||
|
||||
def get_reviews(client: httpx.Client, pr_number: int) -> list[dict]:
|
||||
"""Get all reviews for a pull request."""
|
||||
response = client.get(
|
||||
f"{FORGE_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/pulls/{pr_number}/reviews"
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_review_comments(client: httpx.Client, pr_number: int, review_id: int) -> list[dict]:
|
||||
"""Get all comments for a specific review."""
|
||||
response = client.get(
|
||||
f"{FORGE_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/pulls/{pr_number}/reviews/{review_id}/comments"
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
console = Console()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
console.print("[red]Error:[/red] Please provide a PR number")
|
||||
console.print("Usage: mise run pr-comments <pr_number>")
|
||||
return 1
|
||||
|
||||
try:
|
||||
pr_number = int(sys.argv[1])
|
||||
except ValueError:
|
||||
console.print(f"[red]Error:[/red] '{sys.argv[1]}' is not a valid PR number")
|
||||
return 1
|
||||
|
||||
unresolved_comments: list[tuple[dict, dict]] = [] # (review, comment) pairs
|
||||
|
||||
with httpx.Client() as client:
|
||||
# Get all reviews
|
||||
try:
|
||||
reviews = get_reviews(client, pr_number)
|
||||
except httpx.HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
console.print(f"[red]Error:[/red] PR #{pr_number} not found")
|
||||
else:
|
||||
console.print(f"[red]Error:[/red] API request failed: {e}")
|
||||
return 1
|
||||
|
||||
# For each review, get comments and filter to unresolved
|
||||
for review in reviews:
|
||||
try:
|
||||
comments = get_review_comments(client, pr_number, review["id"])
|
||||
except httpx.HTTPStatusError:
|
||||
continue # Skip reviews we can't fetch comments for
|
||||
|
||||
for comment in comments:
|
||||
if comment.get("resolver") is None:
|
||||
unresolved_comments.append((review, comment))
|
||||
|
||||
if not unresolved_comments:
|
||||
console.print(f"[green]No unresolved comments on PR #{pr_number}[/green]")
|
||||
return 0
|
||||
|
||||
# Display unresolved comments
|
||||
console.print(f"[bold]Unresolved Comments on PR #{pr_number}[/bold] ({len(unresolved_comments)} comments)")
|
||||
console.print("=" * 60)
|
||||
console.print()
|
||||
|
||||
for review, comment in unresolved_comments:
|
||||
# Header with file path and reviewer
|
||||
header = Text()
|
||||
path = comment.get("path", "unknown file")
|
||||
reviewer = review.get("user", {}).get("login", "unknown")
|
||||
header.append(f"{path}", style="bold cyan")
|
||||
header.append(f" (by {reviewer})")
|
||||
console.print(header)
|
||||
|
||||
# Comment body
|
||||
body = comment.get("body", "").strip()
|
||||
for line in body.split("\n"):
|
||||
console.print(f" {line}")
|
||||
|
||||
# Link to comment
|
||||
html_url = comment.get("html_url", "")
|
||||
if html_url:
|
||||
console.print(f" [dim]{html_url}[/dim]")
|
||||
|
||||
console.print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue
pat yourself on the back, claude, this was good