mirror of
https://github.com/ivuorinen/dotfiles.git
synced 2026-01-26 11:14:08 +00:00
feat(bin): x-pr-comments - fetch GitHub PR comments
This commit is contained in:
357
local/bin/x-pr-comments
Executable file
357
local/bin/x-pr-comments
Executable file
@@ -0,0 +1,357 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# x-pr-comments - Fetch GitHub Pull Request comments and review suggestions
|
||||
# Copyright (c) 2025 - Licensed under MIT
|
||||
#
|
||||
# Usage:
|
||||
# x-pr-comments <pr-number> # When run inside a git repository
|
||||
# x-pr-comments <github-pr-url> # Direct GitHub PR URL
|
||||
# x-pr-comments -h|--help # Show this help
|
||||
#
|
||||
# Examples:
|
||||
# x-pr-comments 1 # PR #1 in current repo
|
||||
# x-pr-comments https://github.com/user/repo/pull/1
|
||||
#
|
||||
# Requirements:
|
||||
# - gh CLI tool installed and authenticated
|
||||
# - Internet connection for GitHub API access
|
||||
#
|
||||
# Output:
|
||||
# Structured list of PR comments and file change requests with LLM processing directions
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly BLUE='\033[0;34m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Show usage information
|
||||
show_usage()
|
||||
{
|
||||
sed -n '3,20p' "$0" | sed 's/^# //' | sed 's/^#//'
|
||||
}
|
||||
|
||||
# Log functions
|
||||
log_error()
|
||||
{
|
||||
echo -e "${RED}ERROR:${NC} $1" >&2
|
||||
}
|
||||
log_warn()
|
||||
{
|
||||
echo -e "${YELLOW}WARN:${NC} $1" >&2
|
||||
}
|
||||
log_info()
|
||||
{
|
||||
if [[ "${INFO:-0}" == "1" ]]; then
|
||||
echo -e "${GREEN}INFO:${NC} $1" >&2
|
||||
fi
|
||||
}
|
||||
log_debug()
|
||||
{
|
||||
if [[ "${DEBUG:-0}" == "1" ]]; then
|
||||
echo -e "${BLUE}DEBUG:${NC} $1" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Filter out CodeRabbit comments containing "Addressed in commit"
|
||||
filter_coderabbit_addressed_comments()
|
||||
{
|
||||
local input_data="$1"
|
||||
local is_wrapped="$2" # true for {comments: [...]}, false for [...]
|
||||
|
||||
local jq_filter='select(
|
||||
(.user.login | contains("coderabbit") | not) or
|
||||
(.body | contains("Addressed in commit") | not)
|
||||
)'
|
||||
|
||||
if [[ "$is_wrapped" == "true" ]]; then
|
||||
echo "$input_data" | jq "{comments: [.comments[] | $jq_filter]}" 2>/dev/null || echo "$input_data"
|
||||
else
|
||||
echo "$input_data" | jq "[.[] | $jq_filter]" 2>/dev/null || echo "$input_data"
|
||||
fi
|
||||
}
|
||||
|
||||
# Fetch and filter API data with consistent logging
|
||||
fetch_and_filter_data()
|
||||
{
|
||||
local endpoint="$1"
|
||||
local data_name="$2"
|
||||
local is_wrapped="$3" # true/false
|
||||
|
||||
local data
|
||||
data=$(gh api "$endpoint" 2>/dev/null || echo "[]")
|
||||
|
||||
if [[ "$is_wrapped" == "true" ]]; then
|
||||
data=$(echo "$data" | jq '{comments: .}' 2>/dev/null || echo '{"comments":[]}')
|
||||
fi
|
||||
|
||||
data=$(filter_coderabbit_addressed_comments "$data" "$is_wrapped")
|
||||
|
||||
local count_field="length"
|
||||
[[ "$is_wrapped" == "true" ]] && count_field=".comments | length"
|
||||
|
||||
local count
|
||||
count=$(echo "$data" | jq -r "$count_field" 2>/dev/null || echo "0")
|
||||
log_debug "$data_name count: $count"
|
||||
|
||||
echo "$data"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Format file-specific comments grouped by review
|
||||
format_grouped_review_comments()
|
||||
{
|
||||
local review_comments="$1"
|
||||
local reviews="$2"
|
||||
local repo="$3"
|
||||
|
||||
local count
|
||||
count=$(echo "$review_comments" | jq -r 'length' 2>/dev/null || echo "0")
|
||||
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
echo "No file-specific comments found."
|
||||
return
|
||||
fi
|
||||
|
||||
# Group comments by review ID and format them
|
||||
echo "$review_comments" | jq -r --argjson reviews "$reviews" '
|
||||
group_by(.pull_request_review_id) | .[] |
|
||||
. as $comments |
|
||||
($reviews[] | select(.id == $comments[0].pull_request_review_id)) as $review |
|
||||
"### Review by \($review.user.login) (\($review.submitted_at)) [\($review.state)]
|
||||
|
||||
Review ID: \($review.id) - API: gh api /repos/'"$repo"'/pulls/1/reviews/\($review.id)
|
||||
|
||||
" + ([.[] | "
|
||||
#### Comment ID: \(.id)
|
||||
|
||||
- **File:** \(.path)
|
||||
- **Commit:** \(.commit_id)
|
||||
- **Author:** \(.user.login) (\(.user.type))
|
||||
- **Lines:** \(if .start_line then "\(.start_line)-\(.line // "N/A")" else "\(.line // "N/A")" end) (original: \(.original_line // "N/A"))
|
||||
- **Position:** \(.position // "N/A") (original: \(.original_position // "N/A"))
|
||||
- **Subject Type:** \(.subject_type // "N/A")
|
||||
- **API:** gh api /repos/'"$repo"'/pulls/comments/\(.id)
|
||||
|
||||
**Body:**
|
||||
\(.body)
|
||||
|
||||
"] | join("")) + "
|
||||
---
|
||||
"
|
||||
' 2>/dev/null || {
|
||||
log_debug "Error grouping review comments by review ID"
|
||||
echo "Error parsing grouped review comments."
|
||||
}
|
||||
}
|
||||
|
||||
# Check if gh CLI is available
|
||||
check_dependencies()
|
||||
{
|
||||
if ! command -v gh &> /dev/null; then
|
||||
log_error "GitHub CLI (gh) is not installed. Please install it first:"
|
||||
log_error " https://cli.github.com/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! gh auth status &> /dev/null; then
|
||||
log_error "GitHub CLI is not authenticated. Run: gh auth login"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Get repository info from git remote
|
||||
get_repo_info()
|
||||
{
|
||||
if ! git rev-parse --is-inside-work-tree &> /dev/null; then
|
||||
log_error "Not inside a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local remote_url
|
||||
remote_url=$(git remote get-url origin 2> /dev/null || echo "")
|
||||
|
||||
if [[ -z "$remote_url" ]]; then
|
||||
log_error "No origin remote found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Parse GitHub URL (both HTTPS and SSH formats)
|
||||
if [[ "$remote_url" =~ github\.com[:/]([^/]+)/([^/]+)(\.git)?$ ]]; then
|
||||
local repo_name="${BASH_REMATCH[2]}"
|
||||
# Remove .git suffix if present
|
||||
repo_name="${repo_name%.git}"
|
||||
echo "${BASH_REMATCH[1]}/${repo_name}"
|
||||
else
|
||||
log_error "Remote URL is not a GitHub repository: $remote_url"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse GitHub PR URL to extract owner/repo/pr-number
|
||||
parse_github_url()
|
||||
{
|
||||
local url="$1"
|
||||
|
||||
if [[ "$url" =~ github\.com/([^/]+)/([^/]+)/pull/([0-9]+) ]]; then
|
||||
echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"
|
||||
else
|
||||
log_error "Invalid GitHub PR URL format: $url"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Format output with LLM directions
|
||||
format_output()
|
||||
{
|
||||
local repo="$1"
|
||||
local pr_number="$2"
|
||||
local pr_info="$3"
|
||||
local review_comments="$4"
|
||||
local reviews="$5"
|
||||
|
||||
# Header and instructions
|
||||
cat << EOF
|
||||
# GitHub PR Comments Analysis Report
|
||||
|
||||
## LLM Processing Instructions
|
||||
|
||||
You are analyzing review comments and change requests from GitHub Pull Request #$pr_number in repository $repo.
|
||||
|
||||
**Your tasks:**
|
||||
1. **Review Analysis**: For each review, understand the reviewer's overall feedback and concerns
|
||||
2. **Change Request Validation**: Check if each file-specific comment is still relevant to the current codebase
|
||||
3. **Priority Assessment**: Rank change requests by importance and impact on code quality
|
||||
4. **Implementation Planning**: Create actionable tasks for addressing valid change requests
|
||||
5. **Pattern Recognition**: Identify recurring issues across different reviews
|
||||
|
||||
**Tools to use:**
|
||||
- \`find\`, \`cat\`, \`rg\` commands and available tools to examine current codebase
|
||||
- File system tools to verify mentioned files exist and check current state
|
||||
- \`gh pr diff $pr_number\` to see what changes are being reviewed
|
||||
|
||||
## Pull Request Information
|
||||
|
||||
EOF
|
||||
|
||||
# PR information
|
||||
echo "$pr_info" | jq -r '"**Title:** \(.title)
|
||||
**State:** \(.state)
|
||||
**URL:** \(.url)
|
||||
**Number:** '"$pr_number"'
|
||||
**Repository:** '"$repo"'
|
||||
"' 2>/dev/null || {
|
||||
echo "**Error:** Could not parse PR information"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Review Comments Section
|
||||
echo -e "\n## Review Comments and Change Requests\n"
|
||||
|
||||
format_grouped_review_comments "$review_comments" "$reviews" "$repo"
|
||||
|
||||
# Footer
|
||||
cat << EOF
|
||||
|
||||
## Next Steps for LLM Analysis
|
||||
|
||||
1. **Analyze and verify all comments, including nitpick**
|
||||
- Consider all comments from code reviewers as change requests
|
||||
- Analyze all "Outside diff range comments" as change requests
|
||||
- All Copilot and CodeRabbit comments and change requests MUST BE analyzed
|
||||
- Do not trust blindly the comments, validate them against current codebase
|
||||
|
||||
2. **Validate change requests:**
|
||||
- Check if mentioned files exist and match current state
|
||||
- Verify if suggestions are still applicable
|
||||
- Identify any already-addressed issues
|
||||
|
||||
3. **Generate actionable implementation plan:**
|
||||
- Prioritized list of valid change requests
|
||||
- Grouped by file/area for efficient implementation
|
||||
- Clear next steps for addressing reviewer feedback
|
||||
- All valid change requests MUST BE handled, even if low priority
|
||||
|
||||
4. **Use todo lists and memory tools to track progress*
|
||||
- All valid change requests MUST BE handled
|
||||
- Keep handling them until each of them are handled
|
||||
|
||||
5. **Identify patterns and recurring issues:**
|
||||
- Highlight common themes across reviews
|
||||
- Suggest broader improvements to code quality and practices
|
||||
- Suggest LLM instructions to avoid similar issues in future
|
||||
EOF
|
||||
}
|
||||
|
||||
# Fetch and display PR data directly
|
||||
fetch_and_display_pr_data()
|
||||
{
|
||||
local repo="$1"
|
||||
local pr_number="$2"
|
||||
|
||||
log_info "Fetching PR #$pr_number from $repo..."
|
||||
|
||||
# Get PR basic info
|
||||
local pr_info
|
||||
pr_info=$(gh pr view "$pr_number" --repo "$repo" --json title,state,url 2> /dev/null) || {
|
||||
log_error "Failed to fetch PR #$pr_number from $repo"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Fetch review data using helper function
|
||||
local review_comments reviews
|
||||
review_comments=$(fetch_and_filter_data "/repos/$repo/pulls/$pr_number/comments" "Review comments" "false")
|
||||
reviews=$(fetch_and_filter_data "/repos/$repo/pulls/$pr_number/reviews" "Reviews" "false")
|
||||
|
||||
# Display formatted output
|
||||
format_output "$repo" "$pr_number" "$pr_info" "$review_comments" "$reviews"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
{
|
||||
local repo=""
|
||||
local pr_number=""
|
||||
|
||||
# Parse arguments
|
||||
case "${1:-}" in
|
||||
-h | --help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
"")
|
||||
log_error "Missing argument. Provide PR number or GitHub URL."
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
https://github.com/*)
|
||||
local parsed
|
||||
parsed=$(parse_github_url "$1") || exit 1
|
||||
read -r repo pr_number <<< "$parsed"
|
||||
;;
|
||||
[0-9]*)
|
||||
repo=$(get_repo_info) || exit 1
|
||||
pr_number="$1"
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid argument: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
check_dependencies
|
||||
|
||||
log_debug "Repository: $repo"
|
||||
log_debug "PR Number: $pr_number"
|
||||
|
||||
# Fetch and display data
|
||||
fetch_and_display_pr_data "$repo" "$pr_number"
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
30
local/bin/x-pr-comments.md
Normal file
30
local/bin/x-pr-comments.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# x-pr-comments
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
x-pr-comments <pr-number> # When run inside a git repository
|
||||
x-pr-comments <github-pr-url> # Direct GitHub PR URL
|
||||
x-pr-comments -h|--help # Show help
|
||||
```
|
||||
|
||||
Fetches GitHub Pull Request comments and review suggestions, formats them
|
||||
for LLM analysis with processing instructions and API endpoints for detailed
|
||||
examination.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
x-pr-comments 1 # PR #1 in current repo
|
||||
x-pr-comments https://github.com/user/repo/pull/1
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- Internet connection for GitHub API access
|
||||
- Git repository context for PR number usage
|
||||
|
||||
<!-- vim: set ft=markdown spell spelllang=en_us cc=80 : -->
|
||||
Reference in New Issue
Block a user