Add mise task to list unresolved PR comments (#40)
## Summary - New `pr-comments` mise task queries Forge API for unresolved review comments on a PR - Task takes a PR number as argument and displays all comments without a resolver - Updated CLAUDE.md to include using this task after user reviews PRs ## Deployment and Testing - [x] Tested task on PR #39 (shows no unresolved comments since all were resolved) - [x] Tested error handling with non-existent PR #9999 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.tail8d86e.ts.net/eblume/blumeops/pulls/40
This commit is contained in:
parent
7ec98210a9
commit
2e7ca8a5ff
2 changed files with 124 additions and 2 deletions
|
|
@ -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