Add mise task to list unresolved PR comments #40

Merged
eblume merged 1 commit from feature/pr-comments-task into main 2026-01-21 19:14:27 -08:00
2 changed files with 124 additions and 2 deletions
Showing only changes of commit e64f74ab8d - Show all commits

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>
Erich Blume 2026-01-21 19:13:05 -08:00

View file

@ -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
View file

@ -0,0 +1,118 @@
#!/usr/bin/env -S uv run --script

pat yourself on the back, claude, this was good

pat yourself on the back, claude, this was good
# /// 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())