pr-comment-fix: PRレビューコメント自動修正
プルリクエストからレビューコメントを取得し、各コメントを評価して、対応すべきものを修正し、commit スキル経由でコミットしてプッシュまで行う。定期実行(自動モード)とユーザーによる単独実行の両方を想定して設計されている。
モード判定
このスキルには2つの実行モードがある。ワークフローは共通だが、曖昧な状況の扱いが異なる。
| モード | 起動条件 | 動作 |
|---|---|---|
| auto | $ARGUMENTS に --auto または単独の auto を含む |
ユーザーには一切確認しない。曖昧な状況は最も安全な選択肢で自律的に解決する。 |
| manual | デフォルト(autoフラグなし) | 基本は自律的に動作する。本当にユーザーの判断が必要な場合(例: 複数コメントが矛盾する設計を提案)のみ確認する。 |
パース前に $ARGUMENTS から auto フラグを取り除く。その他のフラグ(--repo、--author <login>(複数指定可)、--include-drafts)については Step 1 を参照。
Step 1 — 対象PRの特定
このスキルは4つのターゲティングモードをサポートする。残りの引数からどのモードを適用するか決定し、処理対象のPRリストを構築する。
ターゲティングモード
| モード | 起動条件 | 処理対象 |
|---|---|---|
| single | PR番号、#N、または PR URL が指定されている |
そのPR1件のみ |
| repo | --repo フラグ指定(PR識別子なし) |
カレントリポジトリの全オープンPR |
| branch | PR識別子も --repo フラグも無し |
カレントブランチに紐づくPR(あれば) |
| filtered repo | --repo + 1個以上の --author <login> |
リポジトリ内のオープンPRのうち、指定ユーザーいずれかが作者のもの |
--author は複数指定可(例: --author alice --author bob)。PR の author.login フィールドと照合する。
自然言語引数
$ARGUMENTS は厳密なフラグ形式である必要はない。自然言語も解釈する。ユーザーは以下のように言うことがある:
自分のPRだけ/me/私の/my prs→gh api user --jq .loginでカレントユーザーのGitHub loginを解決し、--author <login>として扱うaliceとbobのPR/alice and bob→alice、bobを作者として抽出リポジトリ全体/all open PRs/repo→ repoモードに設定ドラフトも含めて/include drafts→--include-draftsを設定#123/PR 123/認証のPR→ singleモード。「認証のPR」のような曖昧な参照はgh pr listを実行してタイトルからベストマッチを選ぶが、autoモードでは推測するより失敗させる方を優先する
自然言語が使われた場合、実行開始時に解決されたスコープをエコーバックして、ユーザーがどう解釈されたか確認できるようにする(例: Scope: repo, authors=[shotaiuchi, alice], drafts=excluded)。manualモードで解釈が本当に曖昧な場合は事前に確認する。autoモードでは最も保守的な解釈を選んでログに残す。
--include-drafts フラグはドラフトPRを処理対象に含めるかを制御する。デフォルトはドラフト除外。--include-drafts 指定時はドラフトを通常PRと同じく扱う。これは single モード以外に適用される — ユーザーが特定のPRを明示的に指定した場合、ドラフト状態に関わらず処理する(明示的な指定そのものが意図表明)。
repo / filtered-repo / branch モードでのPR列挙
gh pr list を適切なフィルタ付きで使用する。下流で必要なフィールドを含める:
gh pr list \
--state open \
--json number,headRefName,baseRefName,url,isDraft,author,title \
--limit 100
その後コードで:
--include-draftsが無ければドラフトを除外--authorフラグが指定されていればauthor.login ∈ <author list>でフィルタ- branch モードでは
headRefNameがカレントブランチに一致するPRに限定
結果のリストが空の場合:
- autoモード: スコープをログに出して(例:
pr-comment-fix: 0 open PRs in scope (repo=<repo>, authors=<list>, drafts=<bool>))正常終了する。これはcron実行の正常な「やることなし」結果。 - manualモード: 空であることを報告して停止する。フィルタを暗黙に拡大しない。
single モード: 識別子のパース
| 入力 | 動作 |
|---|---|
123 または #123 |
現在のリポジトリでPR番号 123 を使用 |
https://github.com/owner/repo/pull/123 |
URLから owner、repo、番号を抽出 |
スコープ内の各PRについて、メタデータ(head ref、base ref、リポジトリ、ドラフト状態、作者)を取得しておく。コメント取得、ブランチ切り替え、プッシュで使用する。
複数PRの処理
スコープが2件以上のPRを返した場合、並列ではなく逐次処理する — 各PRはブランチチェックアウト・修正適用・コミット・プッシュを伴うため、並列化するとワーキングツリーが破綻する。各PRについて:
- PRブランチをチェックアウト:
gh pr checkout <number>(PRヘッドを追跡するローカルブランチを作成/更新する) - そのPRに対して本スキルの Step 2〜8 を実行
- Step 8 完了後、次のPRへ進む
PRのチェックアウトが失敗した場合(ローカル変更との競合など)、autoモードではそのPRをスキップして理由をログに記録、manualモードでは停止して報告する。実行終了時(中断時も含めて)は必ず元のブランチを復元する。ユーザーの作業状態を別PRブランチに残さないため:
ORIGINAL_BRANCH=$(git branch --show-current)
# ... PRを処理 ...
git checkout "$ORIGINAL_BRANCH"
ワーキングツリーに未コミット変更がある状態で複数PRフローを開始することは拒否する — 他ブランチへのチェックアウトが上書きまたはブロックされてしまうため。autoモードはログを出して終了、manualモードはユーザーにstashまたはコミットを依頼する。
Step 2 — 全種類のコメントを取得
プルリクエストのフィードバックは3つの異なるAPIに分散している。漏れがないよう全て取得すること。resolved/outdated コメントも含めて取得する — 「解決済み」スレッドにも未対応のサブポイントが残っている可能性があるため、ユーザーは全件考慮を望んでいる。
# 1. インラインレビューコメント(行に紐づく、スレッド化されている)
gh api repos/<owner>/<repo>/pulls/<num>/comments --paginate
# 2. レビューサマリー本文(Approved / Changes Requested / Commented)
gh api repos/<owner>/<repo>/pulls/<num>/reviews --paginate
# 3. Issue形式のPR会話コメント
gh api repos/<owner>/<repo>/issues/<num>/comments --paginate
すべてのコメントを以下の内部構造に正規化する:
{
"id": "<api id>",
"source": "inline | review_body | issue",
"author": "<login>",
"author_type": "user | bot",
"path": "<ファイルパスまたはnull>",
"line": "<行番号またはnull>",
"body": "<本文>",
"created_at": "<iso>",
"in_reply_to": "<idまたはnull>",
"resolved": true | false,
"outdated": true | false,
"html_url": "<リンク>"
}
スレッドにグループ化されたインラインコメントは、スレッド全体を1単位として扱い、リプライまで読んだ上で対応を決める。
Step 3 — 既処理コメントのフィルタリング
このスキルを繰り返し(特に定期実行で)安全に動かすために、すでに対応済みのコメントは再処理しないようにする。「対応済み」の判定は次の方法で行う。
- gitログ検索: 各コメントについて、最近のコミットメッセージがその
html_urlまたはidを参照しているかを確認する。commitスキルはチケットIDをメッセージに書き込み、本スキルは Step 6 でコメントURLを追記するので、過去の実行を以下で発見できる:git log --since="60 days ago" --grep="<コメントidまたは短縮url>" --oneline - マッチしたらスキップ: コメントを参照するコミットがカレントブランチ上に存在する場合、それ以上評価せずスキップする。
このフィルタリングは分類の前に行い、対応済みコメントは評価対象にすら入れない。
Step 4 — 残りのコメントを分類
各コメント(またはスレッド)について、コード変更が必要かどうかを判断し、以下のいずれかに分類する。
| クラス | 定義 | アクション |
|---|---|---|
actionable |
コード変更、バグ修正、リネーム、リファクタ、テスト追加など具体的な変更要求 | Step 5 で修正 |
question |
説明や「なぜXにしたのか?」など説明を求める質問 | スキップ + ログ |
discussion |
具体的な変更要求のない設計議論 | スキップ + ログ |
lgtm |
承認、「looks good」、「nice work」、絵文字のみ | スキップ + ログ |
bot |
botアカウントによる投稿(author_type == "bot"、または既知のbotログイン) |
スキップ + ログ |
already_done |
現在のコード状態で既にコメントの要望が満たされている(ファイルを読んで検証) | スキップ + ログ |
unclear |
何を求められているか不明 | auto: スキップ + ログ。manual: ユーザーに確認。 |
プロジェクト方針として、非アクション系クラスはすべて静かにスキップする — 質問や議論への自動返信はしない。後でユーザーがレビューできるよう、実行ログに記録するだけに留める。
Step 5 — Actionable コメントの調査と修正
actionable の各コメントについて、(a) 何を求められているか理解し、(b) 要求された変更が正しいことを確認し、(c) 適用する必要がある。盲目的に変更しないこと — レビュアーの提案がコードベースの別の部分や別のコメントと矛盾することがある。
調査
各 actionable コメントについて:
- 参照されたコードを読む —
path:lineとその周辺コンテキスト。 - 矛盾するコメントがないか確認 — 同じ行に対して2つのコメントが異なる提案をしている場合、より新しいか、より文脈のある方に基づいて判断する。曖昧な場合は、manualモードではユーザーに確認。autoモードではより安全/保守的な選択肢を採用し、衝突をログに記録する。
- 影響範囲を追跡 — 要求された変更に波及効果はあるか? あるなら同じ修正で対処する必要がある。
複雑なケース(複数ファイル、設計変更)では、インラインで追跡せずExploreエージェントを spawn する。これによりメインコンテキストを保護できる。
修正の適用
実際のコード変更を行う。コメントが要求した内容に最小限・焦点を絞った変更とする — 過剰修正や周辺コードのリファクタはしない。各修正後に:
- プロジェクトに lint/build/test コマンドが存在し、かつ高速なら(30秒未満)実行する。遅い場合はCIに任せる。
- 修正でテストが壊れた場合、テストを通すためにテストをスキップ・改変するのではなく、実装を修正する。
修正失敗時の処理
修正が適用できない場合(コードが移動した、要求がアーキテクチャ変更レベル、合理的なリトライ後も無関係なテストが壊れる):
- autoモード: そのコメントをスキップし、理由とともに失敗をログに記録し、次のコメントに進む。実行全体を中断しない。
- manualモード: 中断してユーザーに状況を報告してから先へ進む。
Step 6 — commit スキル経由でコミット
Step 5 のすべての修正適用後(または論理的グループごとに、下記参照)、commit スキルを呼び出してコミットを作成する。
コミットのタイミング
commit スキルの自然な分割動作を活用して修正を論理的なコミットに分ける — 同スキルはステージ済み変更を分析し、意味ごとにグループ化する。手順:
- Step 5 の修正をすべてステージする:
git add -A(ただしこのスキルが変更したファイルのみに限定する。無関係なユーザー作業を巻き込まないよう、修正対象を逐次トラッキングする)。 - Skill ツール経由で
commitスキルを呼び出す:Skill(skill: "commit")。
コミットメッセージへの注釈
commit スキルが各コミットを作成した直後に、対応したコメントへのリンクをトレーラーとして追加する。これにより Step 3 の「既処理」フィルタが次回実行時に機能する。
各コミット直後に git commit --amend でトレーラーブロックを追加する:
Addresses-PR-Comment: <html_url 1>
Addresses-PR-Comment: <html_url 2>
(コメント1件につき1行。トレーラー名は git log --grep で検索可能。)
auto / manual でのID扱い
commit スキルはブランチ名やPRからチケットIDを既に判定するので、そのまま渡すだけでよい — 本スキルで特別な扱いは不要。理由:
- autoモードでは、
commitスキルがIDを判定できない場合通常はユーザーに確認する。しかしこのスキルは自律実行のため、ブロックではなくID無しで進めるよう commit スキルに指示する。サポートしていれば Skill ツールの引数でSkill(skill: "commit", args: "--auto")のように渡す。サポートしていなければ呼び出し前にIDを事前解決して引数で渡す。 - manualモードでは
commitを通常通り動作させる — 最終手段としてのみユーザーに確認する設計になっており、本スキルの「本当に必要な時だけ確認する」方針と一致する。
commit スキルが --auto をサポートしていない場合は、commit 呼び出し前に PR番号 / ブランチのチケットIDを自分で検出し、同じソースから commit にも自動検出させる方式にフォールバックする。
Step 7 — プッシュ
コミットが成功したら、リモート追跡ブランチへ無条件にプッシュする:
git push
プロジェクト方針として、本スキルは保護チェックなしで常にカレントブランチへプッシュする。upstream が無いために git push が失敗したら、明示的に設定する:
git push -u origin <カレントブランチ>
リモートに新しいコミットがあって(誰かがpushして)プッシュが拒否された場合、autoモードでは実行を中断し衝突をログに残す — force-push や自動rebaseはしない。manualモードではユーザーに状況を報告してどう進めるか確認する。
Step 8 — 実行サマリー
すべて完了したら簡潔なレポートを書く。autoモードでもレポートは出力する — スケジュールジョブのログに残る。
複数PRを処理した場合は、PRごとにセクションを出力した上でトップレベルの集計ブロックを付ける。単一PR実行では集計ブロックは省略してよい。
# PR Comment Fix Run
**スコープ**: single | repo | branch | filtered repo (authors=<list>, drafts=<bool>)
**モード**: auto | manual
**処理PR数**: N(チェックアウト失敗で M 件スキップ)
## PR #123 — <タイトル>
**ブランチ**: <head ref>
### 修正済み (N)
| コメント | ファイル | コミット |
|---------|------|--------|
| <短縮url> | path:line | <commit sha> |
### スキップ (M)
| コメント | クラス | 理由 |
|---------|-------|------|
| <短縮url> | lgtm | 承認のみ、対応不要 |
| <短縮url> | already_done | コードが既に要望どおり |
### 失敗 (K)
| コメント | 理由 |
|---------|------|
| <短縮url> | 修正後にテストが壊れ、2回リトライ後失敗 |
### プッシュ
- N コミットを origin/<ブランチ> へプッシュ
<!-- 複数PR実行時はPRごとに繰り返す -->
autoモードでスコープ内の対応すべきコメントがゼロ件だった場合も、スケジュールジョブのハートビート用に1行サマリーを出力する: pr-comment-fix: scope=<repo|branch|...>, N PRs, 0 actionable comments, nothing to do.
Tips
- 冪等性は autoモードで決定的に重要。Step 3 のフィルタが無いと、同じコメントが繰り返し「修正」されてしまう。cronで運用する前に実際の実行でフィルタをテストすること。
- 複数のリプライがあるコメントスレッドでは、最新のコメントが現時点での要望を表していることが多い。古いメッセージは陳腐化していることがある。
- 「Xを検討してみては」というレビュアーの提案は「Yであるべき」より柔らかい。具体的な変更を記述している場合のみ
actionableとして扱い、それ以外はdiscussionとしてログする。 - 1つのコメントのために多くのファイルを変更したくなったら、立ち止まって考え直す — そのコメントは恐らく、このブランチ上の散漫な修正ではなく別PRに値する深い問題を指している。