
Claude Code のレビュースキルを自分のものではないリポジトリ向けに作る — セルフレビュー・PR 本文・差分のハンク
Claude Code のリリーススキルを、マージもタグ付けもできないリポジトリへ移植した記録。何が削られ、なぜ git diff HEAD ならコミット前にレビューできて、無視ルールをどこに置くのか。
目次
はじめに
このブログには /release というスキルがあり、キーボードに触れずにリリース一式を実行します。GitHub の Issue を作り、ブランチを切り、検証し、差分をレビューし、プルリクエストを開き、squash マージし、main にタグを打ち、リリースノートを公開するところまでです。先週、仕事のモノレポで同じものに手を伸ばそうとして、最初の一歩で止まりました。あのリポジトリは組織のものであって、私のものではありません。自分の作業を自分でマージすることはなく、タグも打たず、リリースの間隔も他の人が決めることです。
得られたのは、削った先に残るのは小さいリリーススキルではないという発見でした。それは別の成果物です。生み出すものがマージではなくなり、承認の前に同僚が読む引き継ぎ用のドキュメントになるからです。本記事では、どの手順が外れ、何がそれに代わり、所有権がなくなって初めて見えた 3 つの設計判断を整理します。
リリーススキルはリポジトリを所有している前提で書かれている
このブログのリリーススキルは、4 つのフェーズにまたがる 17 の番号付き手順を実行します。書き出してみると、その多くはエンジニアリングというより所有権の主張でした。
| 手順 | 必要なもの |
|---|---|
| GitHub の Issue 作成、マイルストーン設定 | Issue の書き込み権限と、自分のものであるマイルストーン運用 |
package.json のバージョン更新 |
バージョン番号を決める権限 |
CHANGELOG.md の [Unreleased] を昇格 |
CHANGELOG の形式を決める権限 |
| プルリクエストの squash マージ | デフォルトブランチへのマージ権限 |
main へのタグ付けとタグの push |
ref への書き込み権限 |
| GitHub リリースの作成 | リリースを打つ権限 |
| released-in コメントを付けて Issue をクローズ | 再び Issue の書き込み権限 |
17 手順のうち 7 つは、コードを 1 行も検討しないうちに消えます。 残る 10 は実際に仕事をしていたほうです。現状の把握、検証、レビュー、ブランチ、コミット、push、そして報告です。
リリーススキルが 1 節まるごと使って擁護しているタグ順序のルールは、よい例です。squash マージは差分を main 上の新しいコミットとして再生し、元のブランチを捨てるため、マージ前に作ったタグは main の祖先ではないコミットを指し、git describe は二度とそれを見つけられません。これは確かに微妙な罠であり、そして自分がタグを一切打たないリポジトリでは完全に無関係です。 リリーススキルが苦労して得た知識のほとんどはこの形をしています。正しく、学ぶのに高くつき、そして自分が管理するリポジトリにしか効きません。
他人の書き込み権限を必要とする手順を削る
残ったものは 6 つのフェーズで動きます。状態の把握、差分のセルフレビュー、リモートが動いていないかの確認、検証、そしてブランチとコミットと push、最後にレビュー用ドキュメントの作成です。最後のフェーズは、リリーススキルにはそもそも存在しませんでした。
自分のリポジトリでは、成功した実行の出力はタグと公開済みのリリースです。チームのリポジトリでは 出力は 2 つの Markdown ドキュメント になります。プルリクエストの本文と、差分をハンク単位で説明した文書です。私は過去のプルリクエストから実物を 2 つエージェントに渡し、テンプレートを考案するのではなくそれを形式の仕様として扱うよう指示しました。あの 2 つはすでにレビューを通過した形をしていたからです。読む順に 5 つの節、すなわち問題と根本原因の追跡、実際に何を走らせたかの検証表、ファイル単位ではなく変更単位で整理した作業内容、その変更が何を犠牲にするか、そして補足です。
スキルは git push の後で止まります。 git remote get-url origin から比較用の URL を組み立てて渡すだけで、gh は一度も呼びません。プルリクエストは私自身が立てます。これは回避策として妥協した制限ではなく、プルリクエストを開くことが技術的な行為であると同時に社会的な行為でもあるリポジトリにおいて、正しい振る舞いです。
「コミットなしでどうやってレビューするのか」
エージェントがコミット前のセルフレビューを提案してきたとき、私は反対しました。カテゴリの取り違えに見えたのです。レビューは差分を読むもので、差分はコミットが生むもの、だからコミット前にレビューするというのは何もレビューしないことに聞こえました。
答えは、git diff HEAD がステージ済みと未ステージの変更をまとめて、直前のコミットと比較するということでした。待つべきものは何もありません。どのコマンドを読むかは、いま何が進行中かだけで決まります。
これは一見よりも効いてきます。レビューがコミットの後に走ると、そこで出た指摘はすべて 2 つ目のコミットとして着地するしかなく、ブランチには「レビュー指摘対応」のコミットが生えます。人間のレビュアーはそれを読んだうえで頭の中から捨てなければなりません。レビューを先に走らせるということは、修正が元の作業と区別できなくなるということです。 実際に一体だからです。レビュアーは、変更とその正誤表ではなく、1 つのまとまった変更を読みます。
仕組みについては私が間違っていましたが、その反論には出す価値がありました。順序を、前提ではなく根拠のあるものにしたからです。
check:fix はツリーを書き換えるので、コミットより前に走る
このモノレポは Biome を使っており、リポジトリが標準としているスクリプトは check:fix、つまり biome check --write です。これは問題を報告しません。ファイルを書き換えます。
この一点がフェーズ順序を確定させます。 検証はコミットの後には置けません。整形による変更がワーキングツリーに取り残され、それを回収するために 2 つ目のコミットが必要になるからです。読み取り専用の check に差し替えることもできません。整形は文句を言うものではなく適用するもの、というのがこのリポジトリ自身の慣習だからです。したがって順序は強制されます。レビュー、修正の適用、検証、コミット、push、ドキュメントの作成です。
エージェントは、毎回フルのスイートを回すのではなく変更が触れたワークスペースを検出することを提案し、私はそれを採りました。対応付けは変更されたパスからパッケージのフィルタへの機械的なもので、1 つだけエージェントが主張した例外があり、それは正しいと考えています。共有の common パッケージ配下の変更は、全体の検証へ格上げされます。 3 つのアプリケーションはいずれもそのパッケージのビルド出力を利用しているため、型を変更しても common の中では問題なくコンパイルされ、利用側の tsc で壊れます。検出を狭くすると、ビルドが通らない変更に対して緑を報告してしまいます。
初回は驚くので書いておく細かい点が 1 つあります。client と admin のビルドはすでに無視されているディレクトリへ出力し、server のビルドも同様です。フル検証を走らせても git status はきれいなままです。 そこにビルド出力が現れたら、それはビルドが賑やかに成功した印ではなく、パスのどこかがおかしいという意味になります。
無視ルールは .gitignore ではなく .git/info/exclude へ
生成される 2 つのドキュメントはどこかに置く必要があり、素直な答えはリポジトリ内の作業用ディレクトリと .gitignore への 1 行です。その答えは、リリース手順が間違っていたのと同じ理由で間違っています。
.gitignore は追跡対象のファイルです。 個人の作業用ディレクトリをそこに足すということは、レビュアーが読まされる差分が自分のプルリクエストに入り、自分のものではないリポジトリに 1 行がコミットされ、自分のローカル都合をクローンする全員に押し付けるということです。どれも望んだことではありません。私が欲しかったのは下書きを置く場所です。
.git/info/exclude は同じ構文で同じ効果を持ち、クローンごとに閉じていて追跡されません。
echo '.ignore/' >> .git/info/exclude確認の仕方は他の無視ルールと同じで、git check-ignore はどのファイルがそのルールを供給したかまで教えてくれます。
$ git check-ignore -v .ignore/pr-body.md.git/info/exclude:8:.ignore/ .ignore/pr-body.md同じファイルは、結局スキル自体も抱えることになりました。このリポジトリにおいて .claude/ は個人用のディレクトリではありません。 すでに共有のエージェントとスラッシュコマンドを追跡しており、その下に新しいフォルダを置けば、チームが取り込むべきものとして読まれます。一人の好みを符号化したワークフロースキルはそれには当たりません。少なくとも、誰かが求める前は。
$ tail -3 .git/info/excludescripts/.ignore/.claude/skills/review3 つの項目、そのどれ 1 つとして他人がレビューしなければならない行ではありません。
引き換えに、このルールは持ち運べません。 他の誰のクローンにもなく、私自身がクローンし直せばそこにもありません。Web のフォームに貼り付けて忘れられるための下書きを置く作業用ディレクトリにとって、それは正しい取引です。そしてタグを打たないという判断と同じものです。リポジトリが自分のものでないなら、既定はそこに痕跡を残さないことです。
コードベースを名指しするチェックリストは、汎用のものが見つけないものを見つける
「バグを探して」と指示されたレビューエージェントは、バグではないもののリストを出してきます。 エージェントはチェックリストをリポジトリ自身から導出しており、そのうち存在価値のある観点は、外側からは推測できなかったものです。
痕跡を残さないブロック
いちばん強い例は、server 側のコンテンツブロックのリデューサーです。ブロックのリストを走査し、それぞれをコールバックマップ経由でディスパッチしますが、そこでは text のハンドラを除くすべてのハンドラが省略可能 で、各分岐は「このブロック種別であり、かつそれに対応するコールバックが存在する」というガードで守られています。
したがって、あるブロック種別のハンドラを一度も定義しなかったプロバイダ連携は、そのブロックを無言で捨てます。例外は飛びません。ログも出ません。 内容がモデルに届かないだけで、失敗は後になって、壊れた会話や原因の見えない API のバリデーションエラーとして表面化します。
この形はこのコードベースですでに 2 回、出荷済みのバグを生んでいます。ですから、対応するハンドラを全プロバイダに用意しないまま新しいブロック種別が増えることは、重大度の高い指摘に値します。汎用のレビュアーがこれを探しに行くことは決してありません。 コードのどこにも、間違いに見える箇所がないからです。
どのファイルがルートを登録するかが、誰が到達できるかを決める
バックエンドはルートをプレフィックス配下に登録しており、認証ミドルウェアを持つのはそのうち 2 つだけです。オブジェクトストレージやデータベースに触れるハンドラは、その 2 つのプレフィックスの下では安全で、それ以外の場所では公開状態で到達可能になります。つまり 「どのファイルがこのルートを登録しているか」は、整理上の問いではなくセキュリティ上の問い です。
このリポジトリのルーティングのエントリポイントには、まさにそう書かれたコメントがすでに置かれています。すでにあるコメントをレビュー観点として符号化するのは費用ゼロで、実際に効く唯一の間違いを捕まえます。
チェックリストが報告しないと決めていること
同じくらい有用なのが、チェックリストが除外する指摘のリストです。どれも下流の何かによってすでに保証されているからです。
| 報告しないもの | すでに処理しているもの |
|---|---|
| 整形、import の順序、クォートの種類 | 2 フェーズ後に書き換える check:fix |
未使用の import、const にすべき let |
同じフェーズの Biome の lint ルール |
| 型エラー | 各ワークスペースのビルドに含まれる tsc |
これらを報告するレビューは、そもそもプルリクエストまで届かなかった指摘に読み手の注意を使わせ、人間の判断が要る指摘をその下に埋めています。
言語設定は 1 つのつもりが 2 つだった
私は、生成されるコンテンツをすべて同僚が読む言語にするよう頼みました。エージェントは同意する前にリポジトリの履歴を確認し、矛盾を持って戻ってきました。ログにあるコミットはすべて英語で、Conventional Commits の形式であり、それは他の全員の分も含めてです。 別の言語のコミットメッセージは、そこで唯一の異物になるところでした。
そこで、分割は好みではなく読者で決まりました。レビュー用ドキュメントはレビュアーに合わせます。人が一度読んで何かを決めるものだからです。コミットメッセージは英語のままにします。レビューより長く残り、全員のものである共有のログに加わるからです。私は「言語」を 1 つの設定として扱っていましたが、それは 2 つでした。 そしてエージェントがそれに気づいたのは、私の指示ではなくログを読んだからです。
ここは自分だけでは辿り着かなかった部分であり、誰が何をしたかは正確に書く価値があります。スコープ、形式の仕様、そして最終判断はすべて私のもので、私に見えていなかった矛盾を見つけるのはエージェントの仕事でした。
自分の例を収められなかったテンプレート
1 つの失敗は、修正が素直なほうではないので記録しておく価値があります。
2 つのドキュメントテンプレートには実例が含まれ、その実例自体がコードフェンスです。 ```markdown のブロックの中に ```diff のブロックがあると、内側のフェンスで終端してしまいます。エージェントの最初の試みは、内側のフェンスそれぞれの前にゼロ幅スペースを置くことでした。見た目には何も描画されず、パーサーの照合を止めます。動きはします。そして、他人が読むファイルの中に不可視の文字を撒き散らします。
それを取り除くほうは、すんなりとはいきませんでした。
perl -i -pe 's/\x{200b}//g' templates/diff-explanation.mdこのコマンドは終了コード 0 を返し、何も変更しません。 -CSD がないと Perl はファイルを UTF-8 ではなくバイト列として読むため、\x{200b} は実際にファイルにある 3 バイトの並びに一致しません。手がかりは、エラーを 1 つも報告しなかった置換のあとで grep -c がまだ 4 件を数えていたことでした。
remaining: 4修正は 2 つ、どちらも小さなものです。perl -CSD は置換にバイトではなく文字を見せます。そして外側のフェンスを ~~~ にしました。Markdown はこれを代替のフェンス記号として受け付けるので、内側のバッククォートのフェンスをエスケープする必要がなくなり、ファイルには不可視の文字が 1 つも残りません。2 つ目の修正は、1 つ目が最初から不要だったことを意味します。 この種の問題が取る、いつもの形です。
まとめ
リリーススキルを自分のものではないリポジトリへ移植する作業は、1 つの追加を伴う引き算でした。17 手順のうち 7 つは所有権を主張していたために外れました。Issue、マイルストーン、バージョン番号、CHANGELOG の権限、マージ、タグ、リリースです。それらに代わって入ったのはドキュメント作成のフェーズです。マージの緑のチェックではなく、プルリクエストの本文を読む同僚が最後の一歩になったからです。
所有権がなくなって初めて見えた判断が 3 つあります。レビューはコミットより前に走ります。git diff HEAD がそれを可能にし、修正を元のコミットに畳み込めばレビュアーが読む変更は 1 つで済むからです。検証もコミットより前に走ります。このリポジトリのフォーマッタが報告ではなく書き込みを行うからです。そして作業用ディレクトリは .gitignore ではなく .git/info/exclude で無視します。追跡される無視ファイルは、他人のリポジトリで自分が変更するものがもう 1 つ増えるということだからです。
一般化できる形があるとすれば、こうです。ワークフロースキルは手順の集合であると同じくらい、権限の集合を符号化しています。 そして移設を生き延びる手順は、はじめから権限の話ではなかったものです。

