Compare commits

...

6 Commits

Author SHA1 Message Date
a14ff9be4d fix(flake): Remove duplicate home-manager imports from wixos and zix790prors
Some checks failed
CI / check (pull_request) Successful in 5m36s
CI / check (push) Has been cancelled
The nixosModules list already includes inputs.home-manager.nixosModules.home-manager,
so these individual configuration imports were redundant.
2026-01-13 16:37:41 -08:00
90217ec85a fix(ci): Use full Gitea URL for composite action
All checks were successful
CI / check (push) Successful in 7m29s
Gitea Actions defaults to GitHub for short-form action references.
Use full URL to reference actions from the same Gitea instance.
2026-01-13 15:58:20 -08:00
f99f4069f0 feat(ci): Add Gitea Actions workflow with Nix caching
Some checks failed
CI / check (push) Failing after 1s
Uses johno/gitea-actions/nix-setup composite action for:
- Nix installation via DeterminateSystems/nix-installer-action
- Nix store caching via actions/cache@v4
- Per-repo cache isolation based on flake.lock hash
2026-01-13 15:50:54 -08:00
320a2d3738 refactor: Move import_gitea_issues to user-level skill
Moves from project-level (.claude/commands/) to user-level
(home/roles/development/skills/) so it's available across all projects
via Home Manager activation.

Bead: nixos-configs-g72
2026-01-13 15:37:59 -08:00
92b6cfb710 fix(common): Add ghostty terminfo for SSH compatibility
Installs ghostty.terminfo on all NixOS machines so tmux works
when SSH'ing from a Ghostty terminal.
2026-01-13 14:09:57 -08:00
996fb86ed8 [nixos-configs-vru] Add skill for responding to Gitea PR review comments (#26)
## Summary
- Rewrote gitea_pr_review.md as a comprehensive interactive skill
- Accepts PR number as argument or auto-detects from current branch
- Reads Gitea config from tea CLI config file
- Fetches and displays review comments via REST API
- Interactive comment selection via AskUserQuestion
- Posts replies via `tea comment` with file:line context

## Bead Reference
Implements bead: nixos-configs-vru

## Changes
- Rewritten `home/roles/development/skills/gitea_pr_review.md` (+259/-155 lines)

## Testing
Please leave a review comment on this PR so we can test the skill!

## Limitations
- Thread replies are posted as top-level comments (Gitea API limitation)
- Uses first login from tea config

Reviewed-on: #26
Co-authored-by: John Ogle <john@ogle.fyi>
Co-committed-by: John Ogle <john@ogle.fyi>
2026-01-13 09:08:17 -08:00
5 changed files with 406 additions and 161 deletions

18
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,18 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@main
- name: Check flake
run: nix flake check

View File

@@ -162,7 +162,6 @@
modules = nixosModules ++ [
nixos-wsl.nixosModules.default
./machines/wixos/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };
@@ -174,7 +173,6 @@
system = "x86_64-linux";
modules = nixosModules ++ [
./machines/zix790prors/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.users.johno = import ./home/home-desktop.nix;
home-manager.extraSpecialArgs = { inherit system; };

View File

@@ -1,217 +1,350 @@
---
description: Manage and respond to Gitea/Forgejo PR review comments
description: Address Gitea/Forgejo PR review comments with code changes
---
# 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 **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
- 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 run nixpkgs#tea`)
## 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
```
If tea is missing:
```
Error: `tea` CLI not found.
Please install:
- Nix: nix run nixpkgs#tea
- Other: https://gitea.com/gitea/tea
```
**STOP** if tea is missing.
3. **Extract configuration from tea config**:
```bash
# 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:
```
Error: Could not read tea config at ~/.config/tea/config.yml
Please ensure `tea` is installed and configured:
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
REMOTE_URL=$(git remote get-url origin)
# Parse owner and repo from URL (handles both SSH and HTTPS)
OWNER=$(echo "$REMOTE_URL" | sed -E 's#.*[:/]([^/]+)/[^/]+\.git$#\1#')
REPO=$(echo "$REMOTE_URL" | sed -E 's#.*/([^/]+)\.git$#\1#')
```
5. **Ensure we're on the PR branch**:
```bash
CURRENT_BRANCH=$(git branch --show-current)
# Verify this branch corresponds to the PR
```
6. **Respond with**:
```
Addressing PR review comments for PR #{PR_NUMBER}...
Repository: {OWNER}/{REPO}
Branch: {CURRENT_BRANCH}
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
CURRENT_BRANCH=$(git branch --show-current)
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
```
## Commands
## Workflow
### 1. List PR Review Comments
### Step 1: Fetch and Parse Comments
Fetch all reviews and their comments for a PR:
Fetch all reviews and their comments:
```bash
# Set environment variables
GITEA_URL="https://git.johnogle.info"
TOKEN="<your-token>"
OWNER="<repo-owner>"
REPO="<repo-name>"
PR_NUMBER="<pr-number>"
# Get all reviews for the PR
# Fetch reviews (filter out dismissed reviews)
curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | jq
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" \
| jq '[.[] | select(.dismissed != true)]'
# Get comments for a specific review
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" | jq
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments"
```
### 2. View All Review Comments (Combined)
**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
# 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
# Example: Filter to only unresolved comments
jq '[.[] | select(.resolver == null)]'
```
### 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:
If no reviews found or all comments are resolved:
```
POST /{owner}/{repo}/pulls/{pr_number}/files/reviews/comments
Content-Type: multipart/form-data
No unresolved reviews found for PR #{PR_NUMBER}.
Nothing to address.
```
**STOP** here.
### Step 2: Categorize Comments
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)
```
**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)
### Step 3: User Confirmation
**Authentication Challenge:**
This endpoint requires session-based authentication, not API tokens. Options:
Use `AskUserQuestion` to confirm the plan:
#### Option A: Use Browser Session (Recommended)
```
I've categorized {N} comments. My plan:
1. Log in to Gitea in your browser
2. Open browser developer tools and copy cookies
3. Use the session cookies with curl
**Will make changes for:**
- {file}:{line}: {planned_change}
```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[^"]+')
**Will explain in summary:**
- {file}:{line}: {planned_explanation}
# 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"
**No action needed:**
- {file}:{line}: {reason}
Proceed with this plan?
```
#### Option B: Create Top-Level Comment (Fallback)
Options:
1. **Proceed** - Execute the plan
2. **Modify** - Let user adjust categorization
3. **Cancel** - Exit without changes
If thread replies are not critical, use the API to create a top-level comment:
### Step 4: Address Actionable Comments (Parallel Subagents)
For each actionable comment, launch a subagent using the Task tool:
```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"
```
Launch Task subagent with:
- subagent_type: "general-purpose"
- prompt: |
You are addressing a PR review comment. Make the requested change and nothing else.
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`
## 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"
**File**: {file_path}
**Line**: {line_number}
**Comment**: {comment_body}
**Diff context**:
```
{diff_hunk}
```
2. **List all pending review comments**:
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
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
# 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"'
git add -A
```
3. **Get detailed comments for a review**:
2. **Create commit with summary**:
```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---"'
git commit -m "Address PR review comments
Changes made:
- {file1}: {change_summary}
- {file2}: {change_summary}
Addresses comments from review by {reviewer}"
```
4. **Respond using top-level comment** (most reliable):
3. **Push to remote**:
```bash
tea comment $PR_NUMBER "Addressing review feedback:
- File \`path/to/file.py\` line 10: Fixed the issue by...
- File \`other/file.py\` line 25: Updated as suggested..."
git push
```
### Step 6: Post Summary Comment
Post a single comment summarizing all actions taken:
```bash
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}
**Responses to questions**:
- `{file3}:{line}`: {answer_to_question}
**Acknowledged** (no action needed):
- `{file4}:{line}`: {reason_no_action}
---
*Automated response via /gitea_pr_review*
EOF
)"
```
### Step 7: Final Summary
Display to user:
```
## PR Review Complete
**Commit**: {SHA}
**Changes**: {N} files modified
### Actions Taken:
- [x] {file1}:{line} - Added nix prerequisite note
- [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
If a subagent fails:
```
Warning: Could not address comment on {file}:{line}
Reason: {error}
Options:
1. Skip this comment and continue
2. Retry with manual guidance
3. Abort all changes
```
### Push failed
```
Error pushing changes: {error}
Your changes are committed locally. You may need to:
1. Pull and resolve conflicts: git pull --rebase
2. Push again: git push
```
### 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
### 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 | via `tea comment` |
### Review States
@@ -220,25 +353,120 @@ Event types: `COMMENT`, `APPROVE`, `REQUEST_CHANGES`
- `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**: 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**: Gitea API doesn't support inline thread replies. We post a single summary comment instead.
2. **CSRF tokens**: The web endpoint for thread replies requires CSRF tokens, which expire and need to be fetched from the page.
2. **Complex changes**: For comments requiring significant refactoring, the subagent may need guidance. The skill will flag these as "blocked" for user input.
3. **Session auth**: API tokens work for REST API but not for web endpoints that require session cookies.
3. **Merge conflicts**: If the branch is behind, you may need to rebase before changes can be pushed.
## Tips
## Example Session
- 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 26
Assistant: Addressing PR review comments for PR #26...
Repository: johno/nixos-configs
Branch: bead/nixos-configs-vru
Gitea URL: https://git.johnogle.info
## Comment Analysis
### 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)
### 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
Proceed with this plan? [Proceed]
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
- 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

View File

@@ -8,6 +8,7 @@
environment.systemPackages = with pkgs; [
git
glances
ghostty.terminfo # So tmux works when SSH'ing from ghostty
pciutils
tree
usbutils