diff --git a/home/roles/development/skills/gitea_pr_review.md b/home/roles/development/skills/gitea_pr_review.md index cda6221..52ae5a3 100644 --- a/home/roles/development/skills/gitea_pr_review.md +++ b/home/roles/development/skills/gitea_pr_review.md @@ -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 + which tea && which yq + ``` + + 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 + # 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) + ``` + + If config is missing or invalid: + ``` + Error: Could not read tea config at ~/.config/tea/config.yml + + 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. + +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 -# 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 +# Get current branch name +CURRENT_BRANCH=$(git branch --show-current) + +# List PRs and find one matching the current branch +tea pr list --fields index,head --output simple | grep "$CURRENT_BRANCH" ``` -Or if you have a specific login name: -```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 +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? ``` -## Commands +- If option 1: Ask for the PR number and continue +- If option 2: End the skill with "No PR to review. Exiting." -### 1. List PR Review Comments +## Workflow -Fetch all reviews and their comments for a PR: +### Step 1: Fetch Reviews + +Fetch all reviews for the PR: ```bash -# Set environment variables -GITEA_URL="https://git.johnogle.info" -TOKEN="" -OWNER="" -REPO="" -PR_NUMBER="" - -# Get all reviews for the PR 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="" -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 +If no reviews found: +``` +No reviews found for PR #{PR_NUMBER}. -### Reading and Responding to Reviews +This PR has no review comments to respond to. +``` +**STOP** here. -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" +### Step 2: Fetch Review Comments + +For each review with state `COMMENT` or `REQUEST_CHANGES`, fetch inline comments: + +```bash +REVIEW_ID="{review_id}" +curl -s -H "Authorization: token $TOKEN" \ + "$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" +``` + +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} + +--- +``` + +If there are general review comments (without inline location), show them as: +``` +### General Review Comment +{review_body} +``` + +### 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:** ``` -2. **List all pending review comments**: + Read the file and show relevant code around the line: ```bash - # Get reviews - 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"' + # Show 5 lines of context around the commented line + sed -n '{line-5},{line+5}p' {file_path} ``` -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---"' +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? ``` -4. **Respond using top-level comment** (most reliable): - ```bash - tea comment $PR_NUMBER "Addressing review feedback: + - If "Edit response" (2): Ask "Please provide your response text:" and use that instead + - If "Skip" (3): Move to next comment - - File \`path/to/file.py\` line 10: Fixed the issue by... - - File \`other/file.py\` line 25: Updated as suggested..." - ``` +### 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 \ No newline at end of file