feat(skills): Rewrite gitea_pr_review as interactive skill

- Accept PR number as argument or detect from current branch
- Read Gitea config from ~/.config/tea/config.yml
- Fetch and display review comments via REST API
- Interactive comment selection via AskUserQuestion
- Post replies via tea comment with file:line context
- Add comprehensive error handling

Implements bead: nixos-configs-vru
This commit is contained in:
2026-01-13 07:29:08 -08:00
parent 47e2392a56
commit f3af982304

View File

@@ -2,216 +2,304 @@
description: Manage and respond to Gitea/Forgejo PR review comments
---
# Gitea PR Review Comments
# Gitea PR Review
This skill enables reading PR review comments and posting inline thread replies on Gitea/Forgejo instances.
You are tasked with reading PR review comments and posting replies on Gitea/Forgejo instances. This skill uses the REST API for reading comments and the `tea` CLI for posting replies.
## Prerequisites
- `tea` CLI configured with a Gitea/Forgejo instance
- `yq` installed for parsing tea config
- Access token from tea config: `~/.config/tea/config.yml`
- Repository must be a Gitea/Forgejo remote (not GitHub)
## Configuration
## Initial Setup
Get the Gitea instance URL and token from tea config:
When this command is invoked:
1. **Parse the input for PR number**:
- If a PR number is provided as argument, use it
- If no PR number, detect from current branch (see PR Detection section)
2. **Verify required tools are available**:
```bash
# Get the default login URL and token
yq -r '.logins[] | select(.name == "default") | .url' ~/.config/tea/config.yml
yq -r '.logins[] | select(.name == "default") | .token' ~/.config/tea/config.yml
which tea && which yq
```
Or if you have a specific login name:
If either tool is missing:
```
Error: Required tools not found.
Missing: {tea|yq|both}
Please install:
- tea: https://gitea.com/gitea/tea
- yq: Available via package manager (nix, brew, apt)
```
**STOP** if tools are missing.
3. **Extract configuration from tea config**:
```bash
yq -r '.logins[] | select(.name == "YOUR_LOGIN") | .url' ~/.config/tea/config.yml
yq -r '.logins[] | select(.name == "YOUR_LOGIN") | .token' ~/.config/tea/config.yml
# Get the Gitea URL and token from tea config
GITEA_URL=$(yq -r '.logins[0].url' ~/.config/tea/config.yml)
TOKEN=$(yq -r '.logins[0].token' ~/.config/tea/config.yml)
```
## Commands
If config is missing or invalid:
```
Error: Could not read tea config at ~/.config/tea/config.yml
### 1. List PR Review Comments
Please ensure `tea` is installed and configured:
1. Install tea: https://gitea.com/gitea/tea
2. Log in: tea login add --url https://your-gitea-instance --token YOUR_TOKEN
```
**STOP** if config is invalid.
Fetch all reviews and their comments for a PR:
4. **Detect repository info from git remote**:
```bash
# Get the remote URL and parse owner/repo
REMOTE_URL=$(git remote get-url origin)
# Parse owner and repo from URL (handles both SSH and HTTPS)
# Example: git@git.example.com:owner/repo.git -> owner/repo
# Example: https://git.example.com/owner/repo.git -> owner/repo
OWNER=$(echo "$REMOTE_URL" | sed -E 's#.*[:/]([^/]+)/[^/]+\.git$#\1#')
REPO=$(echo "$REMOTE_URL" | sed -E 's#.*/([^/]+)\.git$#\1#')
```
5. **Respond with**:
```
Fetching PR review comments for PR #{PR_NUMBER}...
Repository: {OWNER}/{REPO}
Gitea URL: {GITEA_URL}
```
## PR Detection
If no PR number is provided, detect from the current branch:
```bash
# Set environment variables
GITEA_URL="https://git.johnogle.info"
TOKEN="<your-token>"
OWNER="<repo-owner>"
REPO="<repo-name>"
PR_NUMBER="<pr-number>"
# Get current branch name
CURRENT_BRANCH=$(git branch --show-current)
# Get all reviews for the PR
# List PRs and find one matching the current branch
tea pr list --fields index,head --output simple | grep "$CURRENT_BRANCH"
```
If no PR exists for the current branch, use `AskUserQuestion`:
```
No PR found for branch '{CURRENT_BRANCH}'.
Would you like to:
1. Enter a PR number manually
2. Cancel
Which option?
```
- If option 1: Ask for the PR number and continue
- If option 2: End the skill with "No PR to review. Exiting."
## Workflow
### Step 1: Fetch Reviews
Fetch all reviews for the PR:
```bash
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | jq
# Get comments for a specific review
REVIEW_ID="<review-id>"
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | jq
```
### 2. View All Review Comments (Combined)
```bash
# Get all reviews and their comments in one view
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | \
jq -r '.[] | "Review \(.id) by \(.user.login): \(.state)\n Body: \(.body)"'
# For each review, show inline comments
for REVIEW_ID in $(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | jq -r '.[].id'); do
echo "=== Review $REVIEW_ID comments ==="
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | \
jq -r '.[] | "[\(.path):\(.line)] \(.body)"'
done
```
### 3. Reply to Review Comments (Web Endpoint Method)
The Gitea REST API does not support replying to review comment threads. The web UI uses a different endpoint:
```
POST /{owner}/{repo}/pulls/{pr_number}/files/reviews/comments
Content-Type: multipart/form-data
```
**Required form fields:**
- `reply`: Review ID to reply to
- `content`: The reply message
- `path`: File path
- `line`: Line number
- `side`: `proposed` or `original`
- `single_review`: `true`
- `origin`: `timeline`
- `_csrf`: CSRF token (required for web endpoint)
**Authentication Challenge:**
This endpoint requires session-based authentication, not API tokens. Options:
#### Option A: Use Browser Session (Recommended)
1. Log in to Gitea in your browser
2. Open browser developer tools and copy cookies
3. Use the session cookies with curl
```bash
# First, get CSRF token from the PR page
CSRF=$(curl -s -c cookies.txt -b cookies.txt \
"$GITEA_URL/$OWNER/$REPO/pulls/$PR_NUMBER/files" | \
grep -oP 'name="_csrf" value="\K[^"]+')
# Post the reply
curl -s -b cookies.txt \
-F "reply=$REVIEW_ID" \
-F "content=Your reply message here" \
-F "path=$FILE_PATH" \
-F "line=$LINE_NUMBER" \
-F "side=proposed" \
-F "single_review=true" \
-F "origin=timeline" \
-F "_csrf=$CSRF" \
"$GITEA_URL/$OWNER/$REPO/pulls/$PR_NUMBER/files/reviews/comments"
```
#### Option B: Create Top-Level Comment (Fallback)
If thread replies are not critical, use the API to create a top-level comment:
```bash
# Create a top-level comment mentioning the review context
curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\": \"Re: @reviewer's comment on $FILE_PATH:$LINE_NUMBER\n\nYour reply here\"}" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments"
```
Or use tea CLI:
```bash
tea comment $PR_NUMBER "Re: @reviewer's comment on $FILE_PATH:$LINE_NUMBER
Your reply here"
```
### 4. Submit a New Review
Create a new review with inline comments:
```bash
curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": "Overall review comments",
"event": "COMMENT",
"comments": [
{
"path": "path/to/file.py",
"body": "Comment on this line",
"new_position": 10
}
]
}' \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews"
```
Event types: `COMMENT`, `APPROVE`, `REQUEST_CHANGES`
Parse the response to extract:
- `id`: Review ID (needed for fetching comments)
- `user.login`: Reviewer username
- `state`: Review state (COMMENT, REQUEST_CHANGES, APPROVE, PENDING)
- `body`: Review body text (may be empty for inline-only reviews)
## Workflow Example
### Reading and Responding to Reviews
1. **Set up environment**:
```bash
export GITEA_URL=$(yq -r '.logins[] | select(.name == "default") | .url' ~/.config/tea/config.yml)
export TOKEN=$(yq -r '.logins[] | select(.name == "default") | .token' ~/.config/tea/config.yml)
export OWNER="johno"
export REPO="nixos-configs"
export PR_NUMBER="5"
If no reviews found:
```
No reviews found for PR #{PR_NUMBER}.
This PR has no review comments to respond to.
```
**STOP** here.
### Step 2: Fetch Review Comments
For each review with state `COMMENT` or `REQUEST_CHANGES`, fetch inline comments:
2. **List all pending review comments**:
```bash
# Get reviews
REVIEW_ID="{review_id}"
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | \
jq -r '.[] | select(.state == "REQUEST_CHANGES" or .state == "COMMENT") |
"Review \(.id) by \(.user.login) (\(.state)):\n\(.body)\n"'
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments"
```
3. **Get detailed comments for a review**:
```bash
REVIEW_ID="2"
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | \
jq -r '.[] | "File: \(.path):\(.line)\nComment: \(.body)\nID: \(.id)\n---"'
Parse each comment for:
- `id`: Comment ID
- `path`: File path
- `line`: Line number (may be null for file-level comments)
- `body`: Comment text
- `created_at`: Timestamp
### Step 3: Display Comments Needing Response
Format and display comments grouped by review:
```
## Review by {reviewer} ({state})
### Comment 1: {file_path}:{line}
{comment_body}
### Comment 2: {file_path}:{line}
{comment_body}
---
```
4. **Respond using top-level comment** (most reliable):
```bash
tea comment $PR_NUMBER "Addressing review feedback:
If there are general review comments (without inline location), show them as:
```
### General Review Comment
{review_body}
```
- File \`path/to/file.py\` line 10: Fixed the issue by...
- File \`other/file.py\` line 25: Updated as suggested..."
### Step 4: Select Comments to Address
Use `AskUserQuestion` to let the user choose which comments to address:
```
Which review comments would you like to address?
1. {file_path}:{line} - "{truncated_comment_first_50_chars}..."
2. {file_path}:{line} - "{truncated_comment_first_50_chars}..."
3. All comments
4. None (just viewing)
Enter your choice (comma-separated for multiple, e.g., "1,2"):
```
- If "All comments" (3): Process all comments
- If "None" (4): End with "No comments to address. You can view the PR at: {PR_URL}"
- Otherwise: Process selected comment numbers
### Step 5: Compose Replies
For each selected comment:
1. **Show the full context**:
```
## Addressing comment on {file_path}:{line}
**Original comment by {reviewer}:**
> {full_comment_body}
**Current code at that location:**
```
Read the file and show relevant code around the line:
```bash
# Show 5 lines of context around the commented line
sed -n '{line-5},{line+5}p' {file_path}
```
2. **Draft a response**:
- If the comment asks a question, answer it based on the code
- If the comment suggests a change, check if it was addressed and explain
- If the comment points out an issue, acknowledge and describe the fix or reasoning
3. **Ask user to confirm or modify** using `AskUserQuestion`:
```
**Proposed response:**
{draft_response}
Options:
1. Send as-is
2. Edit response (I'll ask for your text)
3. Skip this comment
How would you like to proceed?
```
- If "Edit response" (2): Ask "Please provide your response text:" and use that instead
- If "Skip" (3): Move to next comment
### Step 6: Post Replies
Post each confirmed reply using `tea comment` with file:line context:
```bash
tea comment $PR_NUMBER "Re: @{reviewer}'s comment on \`{file_path}:{line}\`
{response_body}"
```
**Important**: The Gitea REST API does not support replying directly to review comment threads. This skill posts top-level comments with context as a reliable workaround. For true inline thread replies, users should use the Gitea web UI.
After posting:
```
Posted reply for {file_path}:{line}
```
### Step 7: Summary
After processing all selected comments:
```
## Summary
Posted {N} replies to PR #{PR_NUMBER}:
- {file_path}:{line} - Response posted
- {file_path}:{line} - Response posted
- {file_path}:{line} - Skipped
You can view the PR at: {GITEA_URL}/{OWNER}/{REPO}/pulls/{PR_NUMBER}
**Note**: Replies are posted as top-level comments with file:line context.
For true inline thread replies, use the Gitea web UI.
```
## Error Handling
### API request failed
```
Error fetching reviews: {error_message}
Please check:
- Your token is valid and not expired
- The repository exists and you have access
- The Gitea instance is reachable
HTTP Status: {status_code}
Response: {response_body}
```
### tea comment failed
```
Error posting comment: {error_message}
The comment was not posted. You can try:
1. Post manually via Gitea web UI
2. Retry the comment
Comment text was:
{comment_text}
```
## API Reference
### Endpoints
### Endpoints Used
| Action | Method | Endpoint |
|--------|--------|----------|
| List reviews | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` |
| Get review | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
| Get review comments | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments` |
| Create review | POST | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` |
| Submit review | POST | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
| Delete review | DELETE | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
| Create issue comment | POST | `/api/v1/repos/{owner}/{repo}/issues/{index}/comments` |
| Create issue comment | POST | `/api/v1/repos/{owner}/{repo}/issues/{index}/comments` (via `tea comment`) |
### Review States
@@ -222,23 +310,39 @@ Event types: `COMMENT`, `APPROVE`, `REQUEST_CHANGES`
## Limitations
1. **Thread replies**: The Gitea REST API does not support replying directly to review comment threads. This is a known limitation. Workarounds:
- Use top-level comments with context
- Use the web UI manually for thread replies
- Implement session-based authentication to use the web endpoint
1. **Thread replies**: The Gitea REST API does not support replying directly to review comment threads. This skill posts top-level comments with file:line context as a workaround.
2. **CSRF tokens**: The web endpoint for thread replies requires CSRF tokens, which expire and need to be fetched from the page.
2. **Authentication**: Uses the token from tea config. Session-based auth (for true thread replies) is not supported.
3. **Session auth**: API tokens work for REST API but not for web endpoints that require session cookies.
3. **Single Gitea instance**: Uses the first login from tea config. If you have multiple Gitea instances, ensure the correct one is first in your config.
## Tips
## Example Invocation
- Always quote file paths and line numbers when responding via top-level comments
- Use `tea pr view $PR_NUMBER --comments` to see all comments
- Use `tea open pulls/$PR_NUMBER` to open the PR in browser for manual thread replies
- Consider using `tea pr approve $PR_NUMBER` after addressing all comments
```
User: /gitea_pr_review 5
Assistant: Fetching PR review comments for PR #5...
Repository: johno/nixos-configs
Gitea URL: https://git.johnogle.info
## Review by johno (REQUEST_CHANGES)
### Comment 1: home/roles/base-linux/default.nix:15
Consider adding a comment explaining why this option is needed.
---
Which review comments would you like to address?
1. home/roles/base-linux/default.nix:15 - "Consider adding a comment..."
2. All comments
3. None (just viewing)
Enter your choice:
```
## See Also
- Gitea API Documentation: https://docs.gitea.com/api/1.20/
- `tea` CLI: https://gitea.com/gitea/tea
- Gitea API: https://docs.gitea.com/api/
- `/beads_workflow` for full development workflow