From f3af982304efa054ad68e94346b2d078d28374e3 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Tue, 13 Jan 2026 07:29:08 -0800 Subject: [PATCH 1/4] 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 --- .../development/skills/gitea_pr_review.md | 464 +++++++++++------- 1 file changed, 284 insertions(+), 180 deletions(-) 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 -- 2.49.1 From 3a2773dccec6b967b84a77bb8d6bfc0cc08e4737 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Tue, 13 Jan 2026 07:55:43 -0800 Subject: [PATCH 2/4] refactor(skills): Make gitea_pr_review action-oriented Transform the skill from a comment-reply tool to a PR-progress driver: - Categorize comments by actionability (actionable/question/acknowledged/blocked) - Launch parallel subagents to make code changes for actionable comments - Commit and push changes before posting summary - Post single summary comment describing all actions taken - Add nix prerequisite note as requested in review Addresses review comments: - gitea_pr_review.md:12 - Added nix+nixpkgs note to prerequisites - gitea_pr_review.md:50 - Acknowledged CI variant as separate skill --- .../development/skills/gitea_pr_review.md | 391 ++++++++++-------- 1 file changed, 224 insertions(+), 167 deletions(-) diff --git a/home/roles/development/skills/gitea_pr_review.md b/home/roles/development/skills/gitea_pr_review.md index 52ae5a3..5867d87 100644 --- a/home/roles/development/skills/gitea_pr_review.md +++ b/home/roles/development/skills/gitea_pr_review.md @@ -1,17 +1,27 @@ --- -description: Manage and respond to Gitea/Forgejo PR review comments +description: Address Gitea/Forgejo PR review comments with code changes --- # Gitea PR Review -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. +You are tasked with **addressing** PR review comments by making code changes, then summarizing what was done. This skill drives PR progress, not just conversation. + +## Philosophy + +**Comments are work items, not conversation starters.** + +When a reviewer leaves a comment, they're identifying something that needs attention. This skill: +1. Categorizes comments by actionability +2. Makes code changes to address actionable comments +3. Commits and pushes those changes +4. Posts a single summary comment describing what was done ## 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) +- **Nix users**: All tools available via nixpkgs (`nix-shell -p tea`) ## Initial Setup @@ -23,26 +33,25 @@ When this command is invoked: 2. **Verify required tools are available**: ```bash - which tea && which yq + which tea ``` - If either tool is missing: + If tea is missing: ``` - Error: Required tools not found. - - Missing: {tea|yq|both} + Error: `tea` CLI not found. Please install: - - tea: https://gitea.com/gitea/tea - - yq: Available via package manager (nix, brew, apt) + - Nix: nix-shell -p tea + - Other: https://gitea.com/gitea/tea ``` - **STOP** if tools are missing. + **STOP** if tea is 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) + # Read tea config (it's YAML but simple enough to grep) + TEA_CONFIG="$HOME/.config/tea/config.yml" + GITEA_URL=$(grep -A1 'logins:' "$TEA_CONFIG" | grep 'url:' | head -1 | sed 's/.*url: //') + TOKEN=$(grep -A5 'logins:' "$TEA_CONFIG" | grep 'token:' | head -1 | sed 's/.*token: //') ``` If config is missing or invalid: @@ -50,27 +59,31 @@ When this command is invoked: 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 + 1. Install 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**: +5. **Ensure we're on the PR branch**: + ```bash + CURRENT_BRANCH=$(git branch --show-current) + # Verify this branch corresponds to the PR ``` - Fetching PR review comments for PR #{PR_NUMBER}... + +6. **Respond with**: + ``` + Addressing PR review comments for PR #{PR_NUMBER}... Repository: {OWNER}/{REPO} + Branch: {CURRENT_BRANCH} Gitea URL: {GITEA_URL} ``` @@ -79,10 +92,7 @@ When this command is invoked: If no PR number is provided, detect from the current branch: ```bash -# 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" ``` @@ -94,201 +104,223 @@ 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 +### Step 1: Fetch and Parse Comments -Fetch all reviews for the PR: +Fetch all reviews and their comments: ```bash +# Fetch reviews curl -s -H "Authorization: token $TOKEN" \ "$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" -``` -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) - -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: - -```bash -REVIEW_ID="{review_id}" +# For each review, fetch comments 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: - +If no reviews found: ``` -## Review by {reviewer} ({state}) +No reviews found for PR #{PR_NUMBER}. +Nothing to address. +``` +**STOP** here. -### Comment 1: {file_path}:{line} -{comment_body} +### Step 2: Categorize Comments -### Comment 2: {file_path}:{line} -{comment_body} +For each comment, categorize it as one of: ---- +| Category | Description | Action | +|----------|-------------|--------| +| **actionable** | Requests a code change, addition, or fix | Launch subagent to make change | +| **question** | Asks for clarification or explanation | Include answer in summary | +| **acknowledged** | FYI, self-resolved, or "no action needed" noted | Note in summary | +| **blocked** | Requires external input or is out of scope | Flag for user | + +**Categorization heuristics**: +- Contains "add", "change", "fix", "update", "consider adding", "should be" → **actionable** +- Contains "?" or "why", "how", "what" → **question** +- Contains "no need to update", "will be separate", "acknowledged" → **acknowledged** +- Contains "discuss", "later", "out of scope", "blocked by" → **blocked** + +Display the categorization: +``` +## Comment Analysis + +### Actionable (will make changes): +1. {file}:{line} - "{comment_summary}" → Will add nix note to prerequisites + +### Questions (will answer in summary): +2. {file}:{line} - "{comment_summary}" → Explain CI token approach + +### Acknowledged (no action needed): +3. {file}:{line} - "{comment_summary}" → Reviewer noted separate skill + +### Blocked (needs input): +(none) ``` -If there are general review comments (without inline location), show them as: -``` -### General Review Comment -{review_body} -``` +### Step 3: User Confirmation -### Step 4: Select Comments to Address - -Use `AskUserQuestion` to let the user choose which comments to address: +Use `AskUserQuestion` to confirm the plan: ``` -Which review comments would you like to address? +I've categorized {N} comments. My plan: -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) +**Will make changes for:** +- {file}:{line}: {planned_change} -Enter your choice (comma-separated for multiple, e.g., "1,2"): +**Will explain in summary:** +- {file}:{line}: {planned_explanation} + +**No action needed:** +- {file}:{line}: {reason} + +Proceed with this plan? ``` -- 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 +Options: +1. **Proceed** - Execute the plan +2. **Modify** - Let user adjust categorization +3. **Cancel** - Exit without changes -### Step 5: Compose Replies +### Step 4: Address Actionable Comments (Parallel Subagents) -For each selected comment: +For each actionable comment, launch a subagent using the Task tool: -1. **Show the full context**: - ``` - ## Addressing comment on {file_path}:{line} +``` +Launch Task subagent with: +- subagent_type: "general-purpose" +- prompt: | + You are addressing a PR review comment. Make the requested change and nothing else. - **Original comment by {reviewer}:** - > {full_comment_body} + **File**: {file_path} + **Line**: {line_number} + **Comment**: {comment_body} + **Diff context**: + ``` + {diff_hunk} + ``` - **Current code at that location:** - ``` + Instructions: + 1. Read the file to understand context + 2. Make the minimal change to address the comment + 3. Do NOT commit - just make the edit + 4. Report what you changed - Read the file and show relevant code around the line: + Be precise. Only change what's needed to address this specific comment. +``` + +**Important**: Launch actionable comment subagents in parallel when they touch different files. For comments on the same file, run sequentially to avoid conflicts. + +Wait for all subagents to complete and collect their results. + +### Step 5: Commit and Push + +After all subagents complete: + +1. **Stage changes**: ```bash - # Show 5 lines of context around the commented line - sed -n '{line-5},{line+5}p' {file_path} + git add -A ``` -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 +2. **Create commit with summary**: + ```bash + git commit -m "Address PR review comments -3. **Ask user to confirm or modify** using `AskUserQuestion`: - ``` - **Proposed response:** + Changes made: + - {file1}: {change_summary} + - {file2}: {change_summary} - {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? + Addresses comments from review by {reviewer}" ``` - - If "Edit response" (2): Ask "Please provide your response text:" and use that instead - - If "Skip" (3): Move to next comment +3. **Push to remote**: + ```bash + git push + ``` -### Step 6: Post Replies +### Step 6: Post Summary Comment -Post each confirmed reply using `tea comment` with file:line context: +Post a single comment summarizing all actions taken: ```bash -tea comment $PR_NUMBER "Re: @{reviewer}'s comment on \`{file_path}:{line}\` +tea comment $PR_NUMBER "$(cat <<'EOF' +## Review Comments Addressed -{response_body}" +**Changes made** (commit {SHORT_SHA}): +- `{file1}:{line}`: {what_was_changed} +- `{file2}:{line}`: {what_was_changed} + +**Responses to questions**: +- `{file3}:{line}`: {answer_to_question} + +**Acknowledged** (no action needed): +- `{file4}:{line}`: {reason_no_action} + +--- +*Automated response via /gitea_pr_review* +EOF +)" ``` -**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. +### Step 7: Final Summary -After posting: -``` -Posted reply for {file_path}:{line} -``` - -### Step 7: Summary - -After processing all selected comments: +Display to user: ``` -## Summary +## PR Review Complete -Posted {N} replies to PR #{PR_NUMBER}: +**Commit**: {SHA} +**Changes**: {N} files modified -- {file_path}:{line} - Response posted -- {file_path}:{line} - Response posted -- {file_path}:{line} - Skipped +### Actions Taken: +- [x] {file1}:{line} - Added nix prerequisite note +- [x] {file2}:{line} - Explained CI approach in comment +- [ ] {file3}:{line} - Acknowledged (separate skill) -You can view the PR at: {GITEA_URL}/{OWNER}/{REPO}/pulls/{PR_NUMBER} +**Comment posted**: {comment_url} -**Note**: Replies are posted as top-level comments with file:line context. -For true inline thread replies, use the Gitea web UI. +PR URL: {GITEA_URL}/{OWNER}/{REPO}/pulls/{PR_NUMBER} ``` ## Error Handling -### API request failed +### Subagent failed to make change +If a subagent fails: ``` -Error fetching reviews: {error_message} +Warning: Could not address comment on {file}:{line} -Please check: -- Your token is valid and not expired -- The repository exists and you have access -- The Gitea instance is reachable +Reason: {error} -HTTP Status: {status_code} -Response: {response_body} +Options: +1. Skip this comment and continue +2. Retry with manual guidance +3. Abort all changes ``` -### tea comment failed +### Push failed ``` -Error posting comment: {error_message} +Error pushing changes: {error} -The comment was not posted. You can try: -1. Post manually via Gitea web UI -2. Retry the comment +Your changes are committed locally. You may need to: +1. Pull and resolve conflicts: git pull --rebase +2. Push again: git push +``` -Comment text was: -{comment_text} +### No actionable comments + +If all comments are questions/acknowledged: +``` +No code changes needed. + +All comments are either questions or acknowledged items. +Posting summary comment with explanations... ``` ## API Reference @@ -299,7 +331,7 @@ Comment text was: |--------|--------|----------| | List reviews | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` | | Get review comments | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments` | -| Create issue comment | POST | `/api/v1/repos/{owner}/{repo}/issues/{index}/comments` (via `tea comment`) | +| Create issue comment | POST | via `tea comment` | ### Review States @@ -310,39 +342,64 @@ Comment text was: ## Limitations -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. +1. **Thread replies**: Gitea API doesn't support inline thread replies. We post a single summary comment instead. -2. **Authentication**: Uses the token from tea config. Session-based auth (for true thread replies) is not supported. +2. **Complex changes**: For comments requiring significant refactoring, the subagent may need guidance. The skill will flag these as "blocked" for user input. -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. +3. **Merge conflicts**: If the branch is behind, you may need to rebase before changes can be pushed. -## Example Invocation +## Example Session ``` -User: /gitea_pr_review 5 -Assistant: Fetching PR review comments for PR #5... +User: /gitea_pr_review 26 +Assistant: Addressing PR review comments for PR #26... Repository: johno/nixos-configs +Branch: bead/nixos-configs-vru Gitea URL: https://git.johnogle.info -## Review by johno (REQUEST_CHANGES) +## Comment Analysis -### Comment 1: home/roles/base-linux/default.nix:15 -Consider adding a comment explaining why this option is needed. +### Actionable (will make changes): +1. gitea_pr_review.md:12 - "could we indicate nix+nixpkgs satisfies this?" + → Will add note that nix users can get tools via nixpkgs ---- +### Questions (will answer in summary): +(none) -Which review comments would you like to address? +### Acknowledged (no action needed): +2. gitea_pr_review.md:50 - "we eventually want to run this in CI..." + → Reviewer noted this will be a separate skill -1. home/roles/base-linux/default.nix:15 - "Consider adding a comment..." -2. All comments -3. None (just viewing) +Proceed with this plan? [Proceed] -Enter your choice: +Launching subagent to address comment 1... +[Subagent completes edit] + +Committing changes... +[abc1234] Address PR review comments + +Pushing to remote... +Done. + +Posting summary comment... + +## PR Review Complete + +**Commit**: abc1234 +**Changes**: 1 file modified + +### Actions Taken: +- [x] gitea_pr_review.md:12 - Added nix prerequisite note +- [ ] gitea_pr_review.md:50 - Acknowledged (separate skill) + +**Comment posted**: https://git.johnogle.info/.../pulls/26#issuecomment-XXX + +PR URL: https://git.johnogle.info/johno/nixos-configs/pulls/26 ``` ## See Also - `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 +- `/beads_workflow` for full development workflow -- 2.49.1 From 2c91cd23be5f5bf7b1c5d42ce8dae662355f6a1a Mon Sep 17 00:00:00 2001 From: John Ogle Date: Tue, 13 Jan 2026 08:02:04 -0800 Subject: [PATCH 3/4] fix(skills): Address PR review comments for gitea_pr_review - Use `nix run nixpkgs#tea` instead of nix-shell (preferred for ad-hoc) - Add reviewer tagging in summary comment with cc @mentions - Filter out dismissed reviews and resolved comments in workflow - Add note about notifying reviewers of changes --- .../development/skills/gitea_pr_review.md | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/home/roles/development/skills/gitea_pr_review.md b/home/roles/development/skills/gitea_pr_review.md index 5867d87..c338875 100644 --- a/home/roles/development/skills/gitea_pr_review.md +++ b/home/roles/development/skills/gitea_pr_review.md @@ -21,7 +21,7 @@ When a reviewer leaves a comment, they're identifying something that needs atten - `tea` CLI configured with a Gitea/Forgejo instance - Access token from tea config: `~/.config/tea/config.yml` - Repository must be a Gitea/Forgejo remote (not GitHub) -- **Nix users**: All tools available via nixpkgs (`nix-shell -p tea`) +- **Nix users**: All tools available via nixpkgs (`nix run nixpkgs#tea`) ## Initial Setup @@ -41,7 +41,7 @@ When this command is invoked: Error: `tea` CLI not found. Please install: - - Nix: nix-shell -p tea + - Nix: nix run nixpkgs#tea - Other: https://gitea.com/gitea/tea ``` **STOP** if tea is missing. @@ -113,18 +113,26 @@ Would you like to: Fetch all reviews and their comments: ```bash -# Fetch reviews +# Fetch reviews (filter out dismissed reviews) curl -s -H "Authorization: token $TOKEN" \ - "$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" + "$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" \ + | jq '[.[] | select(.dismissed != true)]' # For each review, fetch comments curl -s -H "Authorization: token $TOKEN" \ "$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" ``` -If no reviews found: +**Filter resolved comments**: When processing comments, skip any that have been marked as resolved. Check the `resolver` field in the comment response - if it's not null, the comment has been resolved and should be skipped. + +```bash +# Example: Filter to only unresolved comments +jq '[.[] | select(.resolver == null)]' ``` -No reviews found for PR #{PR_NUMBER}. + +If no reviews found or all comments are resolved: +``` +No unresolved reviews found for PR #{PR_NUMBER}. Nothing to address. ``` **STOP** here. @@ -251,6 +259,8 @@ Post a single comment summarizing all actions taken: tea comment $PR_NUMBER "$(cat <<'EOF' ## Review Comments Addressed +cc @{reviewer1} @{reviewer2} + **Changes made** (commit {SHORT_SHA}): - `{file1}:{line}`: {what_was_changed} - `{file2}:{line}`: {what_was_changed} @@ -282,11 +292,14 @@ Display to user: - [x] {file2}:{line} - Explained CI approach in comment - [ ] {file3}:{line} - Acknowledged (separate skill) +**Reviewers tagged**: @{reviewer1}, @{reviewer2} **Comment posted**: {comment_url} PR URL: {GITEA_URL}/{OWNER}/{REPO}/pulls/{PR_NUMBER} ``` +**Note**: When posting the summary comment, tag all reviewers who left comments so they receive notifications about the changes. + ## Error Handling ### Subagent failed to make change -- 2.49.1 From 214368da4b2bbc4a8fb73e23ccbe97cb0e3cf7b8 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Tue, 13 Jan 2026 09:02:40 -0800 Subject: [PATCH 4/4] docs(skills): Add shell command patterns for Claude Code reliability --- .../development/skills/gitea_pr_review.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/home/roles/development/skills/gitea_pr_review.md b/home/roles/development/skills/gitea_pr_review.md index c338875..92b5295 100644 --- a/home/roles/development/skills/gitea_pr_review.md +++ b/home/roles/development/skills/gitea_pr_review.md @@ -353,6 +353,60 @@ Posting summary comment with explanations... - `APPROVE` - Approving the changes - `REQUEST_CHANGES` - Requesting changes before merge +## Shell Command Patterns + +Claude Code's bash execution has quirks. Use these patterns for reliability: + +### curl requests + +**DO** - Use single quotes for URL and header separately: +```bash +curl -s 'https://git.example.com/api/v1/repos/owner/repo/pulls/1/reviews' \ + -H 'Authorization: token YOUR_TOKEN_HERE' | jq . +``` + +**DON'T** - Variable expansion in `-H` flag often fails: +```bash +# This may fail with "blank argument" errors +curl -s -H "Authorization: token $TOKEN" "$URL" +``` + +### Iterating over reviews + +**DO** - Run separate commands for each review ID: +```bash +echo "=== Review 4 ===" && curl -s 'URL/reviews/4/comments' -H 'Authorization: token ...' | jq . +echo "=== Review 5 ===" && curl -s 'URL/reviews/5/comments' -H 'Authorization: token ...' | jq . +``` + +**DON'T** - For loops with multiline bodies often fail: +```bash +# This may cause syntax errors +for id in 4 5 6; do + curl -s "URL/reviews/$id/comments" +done +``` + +### tea comment + +**DO** - Use single-quoted string for comment body: +```bash +tea comment 26 '## Summary + +Changes made: +- Item 1 +- Item 2' +``` + +**DON'T** - Heredocs may hang or timeout: +```bash +# This may hang indefinitely +tea comment 26 "$(cat <<'EOF' +... +EOF +)" +``` + ## Limitations 1. **Thread replies**: Gitea API doesn't support inline thread replies. We post a single summary comment instead. -- 2.49.1