From 5921fa5e52c2e7b4ec5593b73d2ea7c72528eb3c Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:32:25 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 12 ++ README.md | 3 + commands/address-review-comments.md | 97 ++++++++++++++ .../get-review-comments.sh | 126 ++++++++++++++++++ .../reply-review-comment.sh | 76 +++++++++++ plugin.lock.json | 53 ++++++++ 6 files changed, 367 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/address-review-comments.md create mode 100644 commands/address-review-comments/get-review-comments.sh create mode 100644 commands/address-review-comments/reply-review-comment.sh create mode 100644 plugin.lock.json diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..ebe5033 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "pr-review-action-flow", + "description": "GitHub の PR レビューコメントを取得・精査し、修正と返信まで Claude Code から行うためのプラグインです。", + "version": "0.0.0-2025.11.28", + "author": { + "name": "hatayama", + "email": "zhongweili@tubi.tv" + }, + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..03db3b2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# pr-review-action-flow + +GitHub の PR レビューコメントを取得・精査し、修正と返信まで Claude Code から行うためのプラグインです。 diff --git a/commands/address-review-comments.md b/commands/address-review-comments.md new file mode 100644 index 0000000..e2b8ae6 --- /dev/null +++ b/commands/address-review-comments.md @@ -0,0 +1,97 @@ +--- +description: "PRのレビューコメントを確認し、妥当性を判断して適切に対応します" +allowed_tools: Bash(commands/address-review-comments/*), Bash(git:*), Bash(gh:*), Read, Edit, Write, AskUserQuestion +model: sonnet +--- + +PRのレビューコメントを確認し、適切な対応を行います。 + +## コマンド形式 + +``` +/address-review-comments [GitHub username] +``` + +- **PR URL**: 対応するPRのURL(必須) +- **GitHub username**: 使用するGitHubアカウント名(オプション) + - 指定した場合、自動的に `gh auth switch --user ` を実行 + +### 使用例 + +```bash +# デフォルトアカウントで実行 +/address-review-comments https://github.com/your-org/your-repo/pull/13249 + +# user-a アカウントに切り替えて実行 +/address-review-comments https://github.com/your-org/your-repo/pull/13249 user-a + +# user-b アカウントに切り替えて実行 +/address-review-comments https://github.com/your-org/your-repo/pull/13249 user-b +``` + +## 基本フロー + +0. **GitHubアカウントの切り替え**(username指定時のみ) + ```bash + gh auth switch --user + ``` + - 指定されたGitHubアカウントに切り替え + +1. **未解決レビューコメントの取得** + ```bash + commands/address-review-comments/get-review-comments.sh + ``` + - 未解決のレビューコメント一覧を取得 + +2. **各コメントの精査と妥当性判断** + - 各コメントの内容を読み、技術的妥当性を判断 + - ```suggestion ブロックがあっても、その内容が適切かを必ず確認 + - ✅ **妥当な指摘**: 修正を適用(ステップ3へ) + - ❌ **不当な指摘**: 理由をユーザーに説明(ステップ4へ) + +3. **妥当な指摘への対応** + - Edit/Write ツールを使って該当箇所を修正 + - 修正内容をユーザーに提示 + - **👤 ユーザー確認を取る**: 「この修正で commit・push してよいか?」 + - OK が出たら commit・push を実行 + - `reply-review-comment.sh` で返信(該当コミットSHAを含める) + +4. **不当な指摘への対応** + - 不当と判断した理由をユーザーに説明 + - ユーザーの承認を得てから、`reply-review-comment.sh` で返信 + +## 重要な注意事項 + +- ✅ **AI が自分で判断して Edit ツールで修正を適用する** +- ✅ **```suggestion ブロックの内容も無批判に受け入れない** +- ✅ **必ずユーザー確認を経てから commit・push する** +- ✅ **レビューコメントを無批判に受け入れない** + +## 実行前の準備 + +gh コマンドの認証が設定されていることを確認してください: + +```bash +gh auth status || gh auth login +``` + +**注意**: アカウント切り替えが必要な場合は、コマンドの2つ目の引数にGitHubユーザー名を指定してください。 + +このコマンドは、リポジトリのルートに `commands/address-review-comments/` が存在することを前提としています。 + +## レビューコメントへの返信 + +修正を commit・push した後、レビューコメントに返信: + +```bash +commands/address-review-comments/reply-review-comment.sh "返信本文" +``` + +返信メッセージには必ず以下を含めること: +- 該当コミットのSHA(短縮版) +- 何を修正したかの説明 + +## 開発者向け情報 +### スクリプト構成 +- **get-review-comments.sh**: 未解決コメント取得 +- **reply-review-comment.sh**: レビューコメント返信 diff --git a/commands/address-review-comments/get-review-comments.sh b/commands/address-review-comments/get-review-comments.sh new file mode 100644 index 0000000..42c2a7d --- /dev/null +++ b/commands/address-review-comments/get-review-comments.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +# resolve-review.sh +# 指定した PR の未解決レビューコメントを GraphQL で取得し、 +# resolve-review 系 slash command 用に 1 行 1 JSON オブジェクトとして標準出力に返すヘルパースクリプト。 + +set -eu + +if [ "$#" -eq 0 ]; then + printf 'Usage: %s \n' "$(basename "$0")" >&2 + exit 1 +fi + +readonly INPUT="$1" + +OWNER="${OWNER:-}" +REPO="${REPO:-}" + +ensure_repo_context() { + if [ -n "$OWNER" ] && [ -n "$REPO" ]; then + return + fi + + repo_payload="$(gh repo view --json owner,name --jq '.owner.login + " " + .name' 2>/dev/null || true)" + if [ -z "$repo_payload" ]; then + return + fi + + repo_owner=${repo_payload%% *} + repo_name=${repo_payload##* } + + if [ -z "$OWNER" ] && [ -n "$repo_owner" ]; then + OWNER="$repo_owner" + fi + + if [ -z "$REPO" ] && [ -n "$repo_name" ]; then + REPO="$repo_name" + fi +} + +ensure_repo_context + +PR="" + +case "$INPUT" in + http://*|https://*) + parsed="$(printf '%s' "$INPUT" | sed -E 's#^https?://github\\.com/([^/]+)/([^/]+)/pull/([0-9]+).*$#\1 \2 \3#')" + if [ -n "$parsed" ]; then + IFS=' ' read -r parsed_owner parsed_repo parsed_number </dev/null || true)" + fi + ;; + *) + PR="$INPUT" + ;; +esac + +if [ -z "$OWNER" ] || [ -z "$REPO" ]; then + printf 'owner/repo を特定できませんでした。OWNER/REPO を環境変数で指定してください\n' >&2 + exit 1 +fi + +if [ -z "$PR" ]; then + printf 'PR を特定できませんでした。PR番号またはURLを指定してください\n' >&2 + exit 1 +fi + +gh api graphql \ + -F owner="$OWNER" -F repo="$REPO" -F pr="$PR" \ + -f query=' +query($owner:String!, $repo:String!, $pr:Int!){ + repository(owner:$owner, name:$repo){ + pullRequest(number:$pr){ + reviewThreads(first: 100){ + nodes{ + isResolved + isOutdated + resolvedBy { login } + comments(first: 100){ + nodes{ + databaseId + author { login } + path + body + line + originalLine + createdAt + } + } + } + } + } + } +}' \ + --jq ' +.data.repository.pullRequest.reviewThreads.nodes[] +| select(.isResolved | not) +| . as $t +| $t.comments.nodes[] +| { + resolved: $t.isResolved, + outdated: $t.isOutdated, + resolvedBy: ($t.resolvedBy.login // null), + id: .databaseId, + user: .author.login, + path: .path, + line: (.line // .originalLine), + body: .body, + createdAt + }' + + diff --git a/commands/address-review-comments/reply-review-comment.sh b/commands/address-review-comments/reply-review-comment.sh new file mode 100644 index 0000000..c06233e --- /dev/null +++ b/commands/address-review-comments/reply-review-comment.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +# PR のレビューコメントスレッドに GitHub REST API で返信するヘルパースクリプト。 +# resolve-review-run.sh から呼び出され、元コメント ID からスレッドのルートコメントを特定して返信を投稿する。 + +set -eu + +usage() { + printf 'Usage: %s \n' "$(basename "$0")" >&2 + printf 'Example: %s 123456 \"LGTM です\"\n' "$(basename "$0")" >&2 +} + +if [ "$#" -lt 2 ]; then + usage + exit 1 +fi + +readonly COMMENT_ID="$1" +shift + +readonly MESSAGE="$*" + +if [ -z "$MESSAGE" ]; then + printf '返信本文を指定してください\n' >&2 + exit 1 +fi + +OWNER="${OWNER:-}" +REPO="${REPO:-}" + +if [ -z "$OWNER" ]; then + OWNER="$(gh repo view --json owner -q .owner.login 2>/dev/null || true)" +fi + +if [ -z "$REPO" ]; then + REPO="$(gh repo view --json name -q .name 2>/dev/null || true)" +fi + +if [ -z "$OWNER" ] || [ -z "$REPO" ]; then + printf 'owner/repo を特定できませんでした。OWNER/REPO を環境変数で指定してください\n' >&2 + exit 1 +fi + +comment_info="$(gh api \"repos/$OWNER/$REPO/pulls/comments/$COMMENT_ID\" 2>/dev/null || true)" + +if [ -z "$comment_info" ]; then + printf 'コメント %s の取得に失敗しました。gh api のログを確認してください\n' \"$COMMENT_ID\" >&2 + exit 1 +fi + +root_comment_id=\"$(printf '%s' \"$comment_info\" | jq -r '(.in_reply_to_id // .id)')\" + +if [ -z \"$root_comment_id\" ] || [ \"$root_comment_id\" = \"null\" ]; then + printf 'コメント %s を特定できませんでした。ID が正しいか確認してください\n' \"$COMMENT_ID\" >&2 + exit 1 +fi + +pull_request_url=\"$(printf '%s' \"$comment_info\" | jq -r '.pull_request_url')\" +pull_number=\"$(printf '%s' \"$pull_request_url\" | sed -E 's#.*/pulls/([0-9]+)$#\1#')\" + +if [ -z \"$pull_number\" ] || [ \"$pull_number\" = \"null\" ]; then + printf 'PR番号を特定できませんでした\n' >&2 + exit 1 +fi + +if ! reply_url=\"$(gh api \ + --method POST \ + \"repos/$OWNER/$REPO/pulls/$pull_number/comments/$root_comment_id/replies\" \ + --raw-field body=\"$MESSAGE\" \ + --jq '.html_url')\" ; then + printf '返信投稿に失敗しました。gh api のログを確認してください\n' >&2 + exit 1 +fi + +printf '返信を投稿しました: %s\n' \"$reply_url\" + diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..f8e8941 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,53 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:hatayama/PRReviewActionFlow:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "97804fe2d3766533631bf7367440ba855bc2fecd", + "treeHash": "a9869e2946351b1c99d6c046c2399980daea731052ca5e4b82437eefdba96603", + "generatedAt": "2025-11-28T10:17:23.108705Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "pr-review-action-flow", + "description": "GitHub の PR レビューコメントを取得・精査し、修正と返信まで Claude Code から行うためのプラグインです。", + "version": null + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "c8ad2a044dda48dc84c5d06da355ff4d42a269af392e6d3e3ce32b9279e58012" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "aae455fc2deea72e7e968147e5319fc9756bd62df8a546e9ee3e908c6b49aaf0" + }, + { + "path": "commands/address-review-comments.md", + "sha256": "9afaf2297fd05959f9ca6f119549366145a076f90b50702894a1d70af5627040" + }, + { + "path": "commands/address-review-comments/reply-review-comment.sh", + "sha256": "d0b46b8fd3e624c203a7768c91c80256e1d8e2f4a0a902200b800ada5d406205" + }, + { + "path": "commands/address-review-comments/get-review-comments.sh", + "sha256": "2895dbfb3017203a764ff9565e2558cc35f556ba41d8008f38263f22faf37442" + } + ], + "dirSha256": "a9869e2946351b1c99d6c046c2399980daea731052ca5e4b82437eefdba96603" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file