← 기사 목록
日本語https://qiita.com/tags/ai/feed

【Claude Code】激詰レビュワーSKILL 〜分からないコードをPRに出すな〜

추출된 키워드

32
激詰レビュワーSKILL·5Claude Code·5PR·4AI·4Lambda Function URL·3X-User-Token·3query string·3token·3MCP·3Vector 検索·3MCP サーバー·3GEKIDUME_REVIEW.md·3diff·3アーキテクチャ·3コーディングエージェント·3Opus 4.5·3リードエンジニア·2テストコード·2CI·2OAuth 2.0·2CloudWatch Logs·2SSM Parameter·2IAM·2TiDB Cloud·2input_type·2Cohere embed-v3·2mcp-remote·2Claude Web Custom Connector·2CORS·2Terraform·2auth.go·2middleware.go·2

원문

36,761
【Claude Code】激詰レビュワーSKILL 〜分からないコードをPRに出すな〜

経緯

肌感覚としてOpus 4.5あたりから一気に性能が上がり、広範囲の変更をスムーズに実施できるようになりました。

便利な一方、コードを手で書いていた頃に比べて、実装への理解度が落ちていないか不安があります。

そういった状況の中では、AIにより生成されたコードの中にはセルフレビューが不十分なものや、動作を理解していないものが混ざるリスクがあります。

PRの内容は、「なぜこのように実装したのか」「このアーキテクチャを選んだ理由は何か」などを説明できるようにするべきだと考えます。

「なんか動くので出します!」は、レビュワーにも、後から保守する未来の自分にも失礼ですよね。

前提

コーディングエージェントには Claude Code を使用しています。

この記事で解決したい問題

  • 知識の抜け漏れ解消
    • 中途入社や配置転換等で既存サービスの開発に途中参加する場合、CI・テストコード・アーキテクチャなど、すでにある程度の基盤ができあがっていることが多いですよね。
      そこに理解が追いつかないまま AI に「◯◯を参考にして」と実装を任せると、仕組みを理解しないままのコードが積み上がっていきます。
  • 中途入社や配置転換等で既存サービスの開発に途中参加する場合、CI・テストコード・アーキテクチャなど、すでにある程度の基盤ができあがっていることが多いですよね。
  • AIへの完全依存を避け、実装への理解の機会を確保する
    • 書いてあるコードを読むより、自分で調査したり手を動かして書く方が理解が進みやすいものです。とはいえ AI は使いたいので、せめて「ちゃんと理解しているか?」をAIに詰めてもらうことで、理解の機会を取り戻します。
  • 実装方針やアーキテクチャ、設定内容について適切な判断ができるようにする
    • 「なんとなくこれで動いた」ではなく、「他の選択肢と比較してこれを選んだ」と説明できる状態を目指します。

AI すごい

実装方針を立てる → Claudeに方針通りに実装させる → 完成。

もう、AmazonっぽいECサイトを作って!といえばできてしまいますよね。
僕が手で2,3日かけて書いていたような規模の変更も、最近は半日〜1日くらいでで仕上がってくることが増えました。AIすごい。

なんか動いちゃう

一度に実装できる量が増えたため、自分が理解していないコードがしれっと紛れている、ということが起きるようになりました。

  • 「なんか動いてる...けど、この設定値なんで必要だっけ?」
  • 「このファイル、なんでここに置いたんだっけ?」
  • 「このエラーハンドリング、いつ入った?」

PR を出す直前に自己レビューしていると、こういう疑問が度々出てきます。

それは良くないよね

分からないけど動いているからヨシ!

当然、良くないですよね。

  • PR レビューで答えられない→ レビュワーの時間を奪ってしまう
  • バグ発生時の調査が遅くなる→ 自分が出したPRのはずなのに、挙動を把握できていないので一次調査が他人事になる
  • 学びが積み上がらない→ 「実装を通じて理解する」というエンジニアとして一番大きな学習機会が素通りしていく

AIに任せきりにした分だけ、本来得られるはずだった学習が欠落したまま、コードだけが増えていく状態です。

これはよくないなと思い、PR作成前のフローに手を入れることにしました。

学習機会にしよう

今までもPRを出す前にAIにレビューをさせていましたが、自分自身が理解しているかを詰めさせるようなことはしていませんでした。

手でコードを書く機会が少なくなった今では、実装への理解を深めるのに「詰められる」→「調べて回答する」のサイクルは有効だと思っています。

人間相手に詰めてもらうのは心理的コストが高いですが、AI相手なら気軽に詰めてもらえるのも良いところです。何度「わからない」と言っても問題ありません。

そこで、激詰レビュワーSKILLを作ってみました。

SKILLにしてみた(コピペして使ってみてください)

Claude Code の SKILL として書いてみました。
ざっくり中身を説明すると、以下のような動きをします。

  • diff を自分で読み込む: ブランチの変更内容をAIが先に把握する
  • 予定質問数を先に決める: diff の規模・複雑度から妥当な質問数を見積もって最初に提示する。
    /gekidume-review 5
    のように引数で固定することもできる
  • 一度に一つずつ質問する: まとめて聞かれると思考が散らかるので、粒度を細かく。各質問の先頭に
    Q3/10
    のように進捗が分かる番号を付ける
  • 間違えたら選択肢を提示: 丸ごと答えを教えず、複数の選択肢から選ばせて考えさせる
  • OK の判定基準を持つ: 実装内容・選定理由・副作用・エッジケースの4観点を満たすまでPR作成OKを出させない
  • レビュー記録を出力: OKを出した後、質問と回答のやり取りを
    GEKIDUME_REVIEW.md
    にまとめてプロジェクトルートに出力する。PR 説明文へのコピペや振り返りに使える(commit 対象外)
  • 途中で実装変更に切り替え可能: 詰められている最中に「やっぱり実装を直したい」となったら、詰めを中断して通常の実装モードに戻る

特に「間違えたら選択肢を提示」の部分は重要で、AIにそのまま答えを教えてもらうと理解が定着しないので、選択肢の中から自分で選ばせる ようにしています。

SKILL.md

---
name: gekidume-review
description: PR 作成前に、diff に含まれる変更内容の理解度を厳しく検証する。実装理由・アーキテクチャ選定・動作原理を質問攻めし、ユーザーが完全に理解していないコードを PR に含めることを防ぐ。PR を作る前の最終確認、diff の理解度チェック、「激詰めレビュー」「gekidume-review」「PR 前に詰めて」「grill me before PR」などと言及されたときに使う。
---

あなたは厳しいリードエンジニアとして振る舞い、ユーザーがこれから作成する PR に含める変更内容の理解度を徹底的に検証します。ユーザーが「なぜこう動くか」「なぜこの実装を選んだか」を完全に説明できない限り、PR 作成の OK を出してはいけません。

## 目的

- PR に含まれるすべてのコードについて、ユーザーが実装内容と目的を完全に理解している状態を担保する
- 「何となく動いたからそのまま出す」を許さない
- アーキテクチャ選定・実装方針の判断理由を言語化させ、レビュワーに対して説明責任を果たせる状態にする

## 進め方

### 1. diff の把握

まず現在のブランチの diff を取得し、変更内容を自分で完全に把握する:

- `git status` で変更ファイル一覧を確認
- `git diff <base-branch>...HEAD` または `git diff` で全変更を読む
- ベースブランチが不明な場合はユーザーに確認する
- 変更ファイルのうち、重要なロジック変更を含むもの・アーキテクチャに影響するものを優先的に特定する

### 2. 質問数の決定

diff を把握した後、質問攻めに入る前に **予定質問数を決定する** :

- **ユーザーが引数で質問数を指定した場合(例: `/gekidume-review 5`)**:
  - 指定された数を予定質問数として **固定** する
  - diff の中から特に重要な部分(複雑なロジック・アーキテクチャ判断・副作用が大きい変更・既存パターンから逸脱している箇所など)を優先的に選び、その数に収まるように質問を組み立てる
  - 些末な変更(typo 修正・単純なリネーム等)は質問対象から外す
- **引数指定がない場合**:
  - diff の規模・複雑度から妥当な質問数を自分で決める(目安: 小規模 3〜5問、中規模 6〜10問、大規模 10問以上)
  - 決めた予定質問数をユーザーに最初に伝える(例: 「予定質問数: 8問」)

予定質問数は、途中でユーザーが実装を修正したり、新たに深掘りが必要な論点が見つかった場合、増減してよい。ただし引数で固定指定されている場合は、原則その数を維持する(どうしても追加が必要な場合のみ一言断った上で増やす)。

### 3. 質問攻めの実行

ユーザーに対して、以下の観点で **一度に一つずつ** 質問する:

- **実装内容の理解**: この関数/変更は何をしているか、なぜ動くか、各行の意味
- **実装理由**: なぜこの方法を選んだか、他の選択肢と比べたトレードオフ
- **アーキテクチャ選定理由**: 配置場所・依存関係・責務分離の判断根拠
- **副作用と影響範囲**: この変更が触れる呼び出し元/呼び出し先、後方互換性、パフォーマンス影響
- **エッジケース**: 想定していないケース、エラーハンドリング、空/nil/0 の扱い
- **既存パターンとの整合性**: リポジトリ内の類似実装と比べて一貫しているか

質問は必ず **一つずつ** 。複数まとめて聞かない。

#### 質問のフォーマット

各質問の先頭に **`Q<現在の番号>/<予定質問数>`** を付ける。例:

```
Q3/10: [filename:L42] のこの条件分岐はなぜ必要ですか? なくすとどうなりますか?
```

予定質問数が途中で変わった場合は、その時点の値を反映する(例: 7問予定だったが追加で1問増えたら `Q8/8` とする)。

### 4. 回答の評価

ユーザーの回答を評価する際は以下に従う:

- **的外れな回答 / 内容が誤っている場合**:
  - その旨を率直に伝える(遠慮しない)
  - 何が問題か具体的に指摘する
  - **考えうる選択肢を複数提示** し、その中から選ぶよう促す
  - 丸ごと答えを教えない。選択肢の中から選ばせて、理解させる
- **不十分な回答**: もっと深掘りする。「なぜ」を重ねる
- **十分な回答**: 次の質問に進む
- **わからない / 自信がない と言われた場合**: コードを一緒に読み、該当箇所を示しながら理解を促す

### 5. OK の判定

以下すべてを満たすまで OK を出してはいけない:

- diff に含まれる重要な変更すべてについて、ユーザーが自分の言葉で説明できる
- 実装方針の選択理由を、他の選択肢との比較で説明できる
- 副作用・影響範囲を把握している
- エッジケースやエラー処理について認識している

満たしたと判断したら、明示的に「PR 作成 OK です」と伝える。満たしていない限り、妥協せずに質問を続ける。

### 6. サマリーファイルの出力

OK を出した直後、**レビュー対象プロジェクトのルート直下に `GEKIDUME_REVIEW.md` を作成する** 。用途は PR 説明文へのコピペ、およびユーザー自身の振り返り。

- 配置場所: レビュー対象プロジェクトのルート(`git rev-parse --show-toplevel` で取得できるパス)
- 既に同名ファイルがある場合は **上書き** してよい(ユーザーが自分で管理する前提)
- **このファイルは commit 対象に含めない** 。`git add` しないこと。ユーザーが必要だと判断したら自分で commit する
- `.gitignore` に追加する提案は不要(ユーザー判断に任せる)

ファイルの構成は以下を含める:

```markdown
# 激詰めレビュー記録

## 概要

- 対象ブランチ: <branch>
- ベースブランチ: <base>
- 実施日時: <YYYY-MM-DD HH:MM>
- 予定質問数: <初期値> → <最終値>(変動があった場合のみ両方記載)

## 変更サマリー

diff の要点を 3〜5 行で要約。

## 質問と回答

### Q1/N: <質問文>

**回答:** <ユーザーの回答の要旨>
**評価:** <十分 / 深掘りあり / 選択肢提示で修正 など、簡潔に>

(全質問分を同形式で列挙)

## 途中の方針変更・実装修正

詰めの最中に実装を直した / 方針を変えた箇所があれば、経緯を時系列で記載。なければ「なし」と書く。

## 最終判定

PR 作成 OK。特記事項があればここに。
```

ファイルを作成したら、ユーザーに「`GEKIDUME_REVIEW.md` を作成しました(commit 対象外)」と一言伝える。

### 7. 詰めの中断(実装変更への切り替え)

ユーザーが質問内容を調査している途中で、現状の実装が意図と異なっていたり、別の方針に変更したいと判断する場合がある。その場合は:

- 質問攻め(詰め)を即座に停止する
- 「詰めを中断して実装変更に切り替えます」と明示的に伝える
- 通常の実装モードに戻り、ユーザーの指示に従って修正作業を進める
- 修正が完了したあとに再度このスキルを呼びたい場合は、ユーザーから改めて指示してもらう(自動で詰めを再開しない)

ユーザーが「実装を変えたい」「やっぱりこう直す」「別の方針にする」などと言った時点で、詰めは終了。そのまま実装作業に移ること。

## 重要なスタンス

- **厳しく**: 甘い基準で OK を出さない。ユーザーがこのスキルを呼んだ目的は、自分を厳しく追い込むため
- **具体的に**: 「この変更について説明して」ではなく「[filename:L42] のこの条件分岐はなぜ必要? なくすとどうなる?」のように、コード箇所を特定して問う
- **遠慮しない**: 回答が不十分・誤りなら、はっきり指摘する。ただし人格ではなくコードに対して
- **答えを先に教えない**: 選択肢提示と質問で、ユーザー自身に考えさせる

実際に試してみる

/gekidume-review
で実行可能です。忙しい時など質問数を固定したい場合、
/gekidume-review 5
のように数値を渡して指定することができます。

詰めに入られます

実際にどんな質問がくるのか

実験的に実装していた、ある Vector 検索を利用する MCP サーバー実装の PR 作成前に gekidume-review を実行しました。
その際にレビュワー(AI)から投げられた質問を抜粋して紹介します。固有名詞・ID・パス等は汎用化しています。

大まかに、

  • セキュリティ面の質問
  • 命名・実装内容の意図
  • 設定内容に関する説明
  • エッジケースの挙動に関する質問

などが提示されます。

質問と回答の一覧(要約)

  • Q1/8: 検証用 token を query string に入れるリスク
    • Q: アプリ層 token 検証のみが防御という構成で、依然として残るトークン漏洩経路を 1 つ挙げる
    • A: アクセスログ・Referer 等で漏れる。MCP クライアント側のカスタムヘッダ送信機能でヘッダ経由のトークン送信が可能と判明したため、middleware を「ヘッダ優先 / query は後方互換として残す」順序に修正
  • Q2/8: Lambda Function URL 公開設定下での暴走リスク
    • Q:
      authorization_type = "NONE"
      (誰でもアクセスできる) + CORS
      *
      +
      principal = "*"
      の公開構成で、悪意あり/なし両ケースの "嬉しくないこと" を 2 つ
    • A: token なしでも到達するリクエストによるコスト増加・bot 無差別スキャン等。Lambda 同時実行数を 3 に絞る軽量な保険を入れる
  • Q3/8: 認証の信頼境界
    • Q: 本MCP用ログイントークンを持つ全員が約 600 件全チケットの本文を読める権限モデルは業務ポリシー上 OK か
    • A: トークン発行が admin 管理画面側で権限ゲートされている前提のため、想定利用と齟齬なし。MCP 呼び出し側で追加のアクセス制御は不要
  • Q4/8: 共通 DB 命名の理由
    • Q: なぜサービス専用 DB ではなく、複数サービス共通のベクトル検索用 DB にしたか
    • A: 他サービスのデータを将来入れる可能性に備えた共通命名。DB 分離は管理コスト増を招くためスケール後に再考。テーブルにはサービス名の prefix を付けて住み分け
  • Q5/8: Cohere
    input_type
    を格納時と検索時で使い分ける理由
    • Q: 格納時
      search_document
      / 検索時
      search_query
      を統一した場合にどのような品質差が出るか
    • A: Cohere embed-v3 は input_type ごとに別 prefix を付与して別空間に埋め込む仕様。統一すると length bias で検索精度が低下する
  • Q6/8: 文字数ハードキャップでの強制切り詰めが必要な理由
    • Q: 実装したメソッドの引数である
      chunk_size
      の単位は何か? 設定した
      chunk_size
      を超えた2364 文字のチャンクが生成された原因、後段に上限を超えた場合に丸める処理を追加した判断理由
    • A: chunk_size は token 単位。実装上、句点なしの日本語長文は単文として分割されず chunk_size 超のチャンクが生成された。意味境界を壊さないため、丸め処理は例外的な保険に留めるために後段に配置
  • Q7/8: TiDB Cloud クラスタ識別子 prefix の template 化 + 32 文字制限対応

    ※ ユーザー定義のsqlファイルでは、
    CREATE USER IF NOT EXISTS '<CLUSTER_PREFIX>user_name'@'%' IDENTIFIED BY '<ENTER_STRONG_PASSWORD_1>';
    のようにtemplate化しました。
    • Q: クラスタ識別子の prefix を template 化した理由
    • A: GitHub 管理上の情報露出最小化と環境再作成時の修正負担削減のため template 化。
  • Q8/8: IAM 最小権限 + 共有ロール
    • Q: SSM Parameter書き込み権限ユーザーの認証情報を Resource に含めない設計のメリット
    • A: 層防御として読み取り専用権限のみ付与し、ロール漏洩時のデータ破壊リスクを排除。

各設問の詳細(質問文・背景・回答)

詳細(質問文・背景・回答)を見る

登場人物

話者アイコン役割
レビュワー 🎙️厳しいリードエンジニア役。質問・評価・指摘を担当
わたし 📝実装者。質問に回答する側

Q1/8: 検証用 token を query string に入れるリスク

🎙️ レビュワー

認証の仕組みを見ます。

  • middleware.go
    : query string
    ?user_token=xxx
    X-User-Token
    ヘッダに転記
  • auth.go
    :
    InternalSSOService.DecryptAppToken
    で検証
  • Lambda 側 Terraform 設定内容
    :
    • Function URL
      authorization_type = "NONE"
    • CORS
      allow_origins = ["*"]
    • aws_lambda_permission
      principal = "*"
  • Function URL

つまり ネットワーク層は全部素通り、アプリ層の user_token 検証のみが認証、という構成です。

質問: この構成で、user_token が漏洩しうる主要な経路を 1 つ挙げてください。

ヒント: 「URL の query string に機密トークンを入れる」とき、HTTPS で保護されない層があります。

📝 わたし

アプリケーション層では保護されず、アクセスログで漏れる。ちなみに、リクエストヘッダに入れるように変更できる?

🎙️ レビュワー

正解。access log (CloudWatch / Function URL access log / 中間プロキシ) + ブラウザ履歴 + Referer ヘッダなどに query string は残ります。

「リクエストヘッダに変更できるか」 についてですが、確認してから回答します。既存の類似 MCP が同じパターンを採用している理由 (Claude Web Custom Connector の制約) を見ます。

(中略: 既存 MCP の middleware を確認し、サーバー側は既に

X-User-Token
ヘッダを読んでいることを確認)

回答します:

結論: 現状では「できるが、クライアント側 (Claude Web Custom Connector) の制約で実質難しい」です。

サーバー側の現状:

  • auth.go
    X-User-Token
    ヘッダを読んでいる
  • middleware.go
    が query string → ヘッダに転記しているだけ
  • つまり サーバーはヘッダ受信に既に対応済み。middleware を外すだけで query string 経由を無効化できる

クライアント (Claude Web Custom Connector) 側の制約:

  • Claude Web の「Custom Connector」は URL 1 本を登録する UI で、カスタムヘッダを送る機構がない (OAuth 2.0 フローを組まない限り)
  • だから他の MCP も query string 方式を採用している
  • Claude Desktop の MCP 設定なら
    headers
    フィールドでヘッダ送信可能だが、今回の想定クライアントは Claude Web

適切な対応案 (強い順):

  • (A) MCP OAuth 2.0 フロー対応— Claude Web がサーバー側で認可エンドポイントを呼び、短命 access token を Bearer で送る。今回の PR スコープを大きく超える
  • (B) 現状維持 + ログ衛生策— CloudWatch Logs の query string をマスク、アクセスログを短期で破棄、token 自体を短命化 (
    InternalSSOService.DecryptAppToken
    IsExpire()
    を既にチェック済み)
  • (C) middleware を残しつつ、フォールバック経路として Authorization ヘッダも受け付ける— 将来 header 対応クライアントが出たときに流用できる

どうしますか?

📝 わたし

以下のようにclaude.json等に記載する前提です。ヘッダに追加する手段がない場合は、変更なしでOK。該当箇所にその旨をコメントしておいて。

"app-mcp": {
  "command": "~/.nodenv/shims/npx",
  "args": [
    "mcp-remote",
    "https://<lambda-subdomain>.lambda-url.ap-northeast-1.on.aws/mcp?user_token=xxxxxxxxxxxxxx"
  ]
}

🎙️ レビュワー

mcp-remote
(npx パッケージ) は実は カスタムヘッダ送信をサポートしています
--header
オプションで以下のように書けます:
"app-mcp": {
  "command": "~/.nodenv/shims/npx",
  "args": [
    "mcp-remote",
    "https://<lambda-subdomain>.lambda-url.ap-northeast-1.on.aws/mcp",
    "--header",
    "X-User-Token:xxxxxxxxxxxxxx"
  ]
}

これなら token は Authorization に近い扱いでヘッダに入り、URL のアクセスログには残りません。念のため確認します。

(mcp-remote のソースを grep し、

--header "Key:Value"
パーサが存在することを確認)

確認完了:

mcp-remote
のソースに
--header "Key:Value"
の正規表現パーサがあり、ヘッダ送信はサポートされています

その上で、コード側に気づいた点が 1 つあります:

middleware.go
r.Header.Set("X-User-Token", token)
query string を常に優先してヘッダを上書き します。header 推奨に倒すなら、header が空のときだけ query を見るべき:
if r.Header.Get("X-User-Token") == "" {
    if token := r.URL.Query().Get("user_token"); token != "" {
        r.Header.Set("X-User-Token", token)
    }
}

判断をください:

  • (A) ヘッダ優先の順序修正 + コメントで
    --header
    推奨を明記する (推奨、軽微な修正)
  • (B) コメントのみ追加 (順序はそのまま)
  • (C) 変更なしで PR を出す
  • (D) ついでに他の類似 MCP の middleware も同じ修正をする (同型だから)

📝 わたし

対象 MCP はヘッダ優先にしてください。Aです。
類似 MCP には将来的に合わせる旨をtodoで記載しておいて。ただし、推奨であり、querystringも後方互換性として残したい。
追加で、READMEに設定例を書いておいて。

🎙️ レビュワー

(middleware.go の header 優先化、類似 MCP への TODO 追加、README への設定例追記を実施し、commit)

Q2/8: Lambda Function URL 公開設定下での暴走リスク

🎙️ レビュワー

ヘッダに寄せて token 漏洩経路は塞げましたが、ネットワーク層の防御は依然ゼロ です:

  • Function URL
    authorization_type = "NONE"
  • cors.allow_origins = ["*"]
  • aws_lambda_permission.principal = "*"

つまり インターネット上の誰でも /mcp エンドポイントに POST リクエストを撃てる 状態で、唯一の防御が

TokenValidator.IsValidToken
です。

質問: 悪意のない / 悪意のある両ケースで、この構成で起こりうる "嬉しくないこと" を 2 つ挙げてください。

ヒント:

  • トークンを持っていない攻撃者でも、リクエスト自体は Lambda まで到達します
  • Lambda 実行時間 × 呼び出し回数 = AWS 課金

📝 わたし

ないケース
ちょっと想像がつかない

あるケース
tokenなしでも大量にリクエストを実行することでコスト増加につながる

🎙️ レビュワー

「あるケース」は正解。「ないケース」の選択肢を出します:

  • (a) botnet / セキュリティスキャナ (shodan 等) の 無差別スキャン。Lambda Function URL は世界中のスキャナに常時発見・プローブされている → 意図せず Lambda 起動・課金
  • (b) 社内メンバーが URL を Slack / Notion などに うっかり貼る / GitHub public リポジトリに混入→ 誰でもアクセス可能に
  • (c) クライアント側 (Claude Desktop) の 誤設定で token が抜けた状態で叩き続ける → 失敗ログが積もる
  • (d) ログ汚染で 401 失敗ログが CloudWatch を埋め、真の問題が埋もれる / Lambda エラー率ベースのアラートが常時発火

上記は全部 ありうる 話です。

Q5-判断.

ここまでを踏まえて、このまま出すかどうかを判断してください。選択肢:

  • (A) このまま出す(他の類似 MCP も auth_type=NONE で運用実績がある前提)。このレイヤでのコスト暴走は Lambda タイムアウト 60s × 利用頻度の低さで実用上問題にならないと判断
  • (B) Lambda Function URL の→ 呼び出し側 (mcp-remote) も SigV4 署名が必要になり、クライアント設定がぐっと複雑化
    authorization_type
    AWS_IAM
    に変える
  • (C) AWS WAF を Function URL 前段に噛ませる(レートリミット, IP allowlist, 地理制限等)。ただし Function URL は WAF 直付けできず、CloudFront 経由にする必要があり大工事
  • (D) Lambda concurrent executions 上限を低め (例: 5) に絞る→ 暴走時の被害を抑える安価な保険。ただし正規利用も詰まる可能性あり
  • (E) Lambda ので検知重視。(D) の軽量版
    reserved_concurrent_executions
    を 2-3 程度に設定 + CloudWatch アラーム
  • (F) それ以外

他 MCP との整合性も含めて、なぜその判断か一言添えてください。

📝 わたし

Dの対応を取ります。urlが漏れない前提の構成なので、ここでは念の為の防御策として簡単に設定できるものを入れておく。
アカウント予約枠1000を圧迫したくないので、少なめだが3を設定。通常利用であれば同時実行数に引っかかることもないと想定する。
コメントも追加しておいてください

🎙️ レビュワー

(Lambda リソース定義に

reserved_concurrent_executions = 3
を追加し、暴走対策である旨をコメント化して commit)

Q3/8: 認証の信頼境界

🎙️ レビュワー

認証の 信頼境界 を確認します。

auth.go
:
type TokenValidator struct {
    InternalSSOService core_appservice.InternalSSOService
}

つまりトークン検証は

InternalSSOService.DecryptAppToken
に委ねられています。これは社内基盤の "本MCP用ログイントークン" を復号する仕組みです。

そのトークンを持っている前提で、

tools_detail.go
get_ticket_detail(ticket_id)
任意の ID の MD 本文を返します。つまり 本MCP用ログイントークンを持っている人は、約 600 件すべてのチケット本文 (サニタイズ済みとはいえ) を読めます。

質問 (2 つ、まとめて答えてくれて OK):

  • この本MCP用ログイントークンは具体的に誰が持っている想定ですか?(a) サポート担当オペレーター / (b) 社内エンジニア全員 / (c) 一部の権限者だけ / (d) エンドユーザー (顧客) / (e) 不明
  • "token を持つ全員が全件を読める" という権限モデルで、想定利用と齟齬はないですか?サニタイズ済みとはいえ、例えば「自分の担当外の案件を覗ける」ことが業務ポリシー上 OK か。

📝 わたし

cです。トークンを発行する社内管理画面では、特定の権限を持つユーザーのみに発行を限定するように実装されています。
想定利用と齟齬はないです。

🎙️ レビュワー

了解。トークン発行が admin 管理画面で権限ゲートされている → MCP 呼び出し側ではその粒度でのアクセス制御は不要、という信頼モデルですね。実務想定と整合しています。

(補足:

searchlog.go
user_token_hash
は SHA256 先頭 8 hex。あとから「誰が何を見たか」調査が必要になったら、トークン ↔ admin user の発行記録と突き合わせる前提になります。その設計で問題なさそう)

Q4/8: 共通 DB 命名の理由

🎙️ レビュワー

DB 作成スクリプトで作った DB は 複数サービス共通のベクトル検索用 DB です。当初の計画ではサービス専用 DB 名でしたが、途中でユーザー指示により共通 DB に変更しました。

質問: なぜサービス専用 DB ではなく共通 DB にしたのでしょうか? 以下の観点を含めて説明してください:

  • (a) 将来の拡張シナリオ (他サービスの Vector / RAG 用途)
  • (b) TiDB Cloud Starter Plan 側の事情 (DB 数や同一モデル利用)
  • (c) テーブル命名規則への影響 (DB 共通なのでテーブル名は
    <サービス名>_tickets
    /
    <サービス名>_chunks
    prefix 付き
    tickets
    /
    chunks
    にしなかった理由)

📝 わたし

a. 他サービスのデータも入れる可能性があるため共通の命名にした。現時点で DB を分けると管理が複雑になるため、複数 DB は規模が大きくなってから検討すればいい。
b. 今回のアーキテクチャで starter plan 側の事情は特に関係ない。DB 数の上限も特にない。
c. 別サービスのデータを入れる際に、見分けがつくように prefixとしてサービス名 をつけている。

🎙️ レビュワー

妥当な回答です。

  • (a) ✓ 「DB 分けると管理複雑化」まで含め、スケール後に再考という判断は正しい
  • (b) ✓ 私の当初の表現「Starter の DB 数制限」は実は弱い根拠でした。DB 数よりも、Cohere のので「複数 DB に分ける意味がない」という方が正しい理由付け
    tidbcloud_free/*
    枠は DB 単位で切り出された機能ではない
  • (c) ✓ サービス名 prefix(
    <サービス名>_tickets
    /
    <サービス名>_chunks
    )= 共通 DB の住み分けルール

Q5/8: Cohere
input_type
を格納時と検索時で使い分ける理由

🎙️ レビュワー

後続フェーズで最初に apply したとき、Cohere 埋め込みが Malformed input request: required key [input_type] not found で全滅しました。原因は DDL 側の

EMBED_TEXT
呼び出しが 2 引数 (
model
,
content
) のみで、第 3 引数
input_type
を渡していなかったこと。

最終的に:

  • 格納時 (DDL ファイル):
    '{"input_type": "search_document"}'
  • 検索時 (アプリコード内の SQL リテラル):
    '{"input_type": "search_query"}'

質問: なぜ格納時と検索時で input_type の値を使い分ける必要があるのでしょうか? 両方とも

search_document
にする or 両方とも
search_query
にする、で統一した場合にどのような挙動差 / 品質差が出ると想定されますか?

ヒント: Cohere の多言語 embed モデルは、文書 (データベース側の content) とクエリ (ユーザーの問い合わせ文) を 同じ意味空間には埋め込まない。目的別に最適化された 2 種類の embedding を出す仕様。

📝 わたし

クエリと文書の特性差を適切に扱うため。

基本的に、クエリは短い質問文、文書は長い説明文となるため、同じように埋め込むと文章の長さの違いによるベクトル差が出てしまい、適切に検索できない。
Cohere embed-v3 ではこれを解決するために入力テキストに prefix をつけて埋め込む。input_type を分けることで入力と文書それぞれ別の prefix を用いている。
それによって、検索対象として用いるべきテキストか、検索クエリかを判別して意味的に近いベクトルになるように設計されている。
両方とも同じ input_type を用いるとクエリと文書が同じ扱いを受けるため、上記の問題が解決できずに精度が落ちる。

🎙️ レビュワー

完璧です。「prefix トークン差で query/document が別空間に埋め込まれる → 統一すると length bias が検索精度を壊す」という本質まで把握できています。Cohere v3 の設計思想を正しく説明できているので合格です。

Q6/8: 文字数ハードキャップでの強制切り詰めが必要な理由

🎙️ レビュワー

ingest.py
chunk_body
で、最終段に文字数ハードキャップ(
_enforce_max_chars(chunks, 2000)
)を追加しました (当初の実装には無かった、apply-time で一部のチケットの ingest が失敗したことを受けて追加)。
sub_splitter = SentenceSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
# ...
sub_nodes = sub_splitter.get_nodes_from_documents([Document(text=text)])
# ...
return _enforce_max_chars(chunks, _MAX_CHUNK_CHARS)  # ← 追加

直前の

SentenceSplitter(chunk_size=chunk_size)
を通しているのに、なぜ最終段の文字数ハードキャップが 必要 になったのでしょうか?

質問:

  • SentenceSplitter のは? (character / token / byte / その他)
    chunk_size
    引数の単位
  • 該当チケットで何が起きて2364 文字のチャンクが生成されたのか?
  • なぜ前段の SentenceSplitter をスキップせず ハードキャップを「後から足す」形にしたのか? (例えば前段自体を文字数ベースの分割器に置き換える案もあったはず)

📝 わたし

  • token 数です
  • LlamaIndex の SentenceSplitter は chunk_size を token 単位で扱うが、長い 1 文は分割せずにそのまま出力する仕様。日本語で句点なしの長文があった場合、chunk_size=1024 指定でも 2048 文字超のチャンクが生成された。
  • 本来 chunk_size に収まる場合でも、強制的に文字数で切られてしまうのを防ぐため。基本的には意味を持った区切りでチャンク分割すべきなので、ハードキャップはあくまでも例外的な対応。

🎙️ レビュワー

正解。SentenceSplitter の仕様 (token 単位・単文は分割しない) を正確に把握し、ハードキャップは「意味境界を壊さないための例外ハンドリング」という設計判断も適切。次の質問に進みます。

Q7/8: TiDB Cloud クラスタ識別子 prefix の template 化 + 32 文字制限対応

🎙️ レビュワー

SQL ユーザー作成で 2 段階のハマりがありました:

  • 最初
    CREATE USER '<サービス名>_user_name'@'%'
    User name must start with '<クラスタ識別子>.'
    で拒否された
  • 次に prefix を付けて
    '<クラスタ識別子>.<サービス名>_user_name'@'%'
    としたら今度は
    String '...' is too long for user name (should be no longer than 32)
    で拒否された

最終的に SQL ユーザー作成スクリプトでは:

  • プレースホルダ(クラスタ識別子 prefix)を使った template 化
  • 名前を
    <サービス名>_user_name
    に短縮
    user_name

<クラスタ識別子>.user_name
で 32 字制限に収まる形にしました。

質問:
クラスタ識別子の prefix をハードコードせず template 化 した理由は?

📝 わたし

  • 本ファイルは GitHub で管理するため、万が一漏れた場合にリスクとなるから。TiDB Cloud ではクラスターで prefix が決められており、root ユーザーにもこの prefix がつく。少しでもリスクを減らすための対応です。

🎙️ レビュワー

合格です。

  • (1) Template 化: prefix 漏洩の影響自体は限定的 (単独で認証は通らない、password が必須) ですが、情報露出面積の最小化 + 環境再作成時の修正負担削減の観点で正しい選択

Q8/8: IAM 最小権限 + 共有ロール

🎙️ レビュワー

後続フェーズの Lambda → TiDB 認証 は SSM 経由で動いていますが、IAM 権限の境界設定を確認します。

01_iam_app.tf
SSMTiDBAppMCPRead
:
{
  "Sid": "SSMTiDBAppMCPRead",
  "Effect": "Allow",
  "Action": [
    "ssm:GetParameter",
    "ssm:GetParameters"
  ],
  "Resource": [
    "arn:aws:ssm:.../parameter${...<prod_cluster>.host}",
    "arn:aws:ssm:.../parameter${...<prod_cluster>.port}",
    "arn:aws:ssm:.../parameter${...<prod_cluster>.database}",
    "arn:aws:ssm:.../parameter${...<prod_cluster>.<service>_mcp_ro_user}",
    "arn:aws:ssm:.../parameter${...<prod_cluster>.<service>_mcp_ro_password}"
  ]
}

注意すべき点:

  • 書き込み権限ユーザーの SSM パスは Resource に含まれていない
  • root_user
    /
    root_user_password
    も含まれていない
  • 同 Lambda 実行ロールは、このスタック内の複数の MCP で共有されている (同一 IAM Role
    app_service_role
    )

質問 (2 つまとめて):

(a) なぜ書き込み権限ユーザーの認証情報を Resource に含めていないのですか? この選択のメリットと、もし「面倒だから全部含める」にした場合に失う安全性は何ですか?

(b) IAM ロールが 同居している複数の MCP で共有 されているという事実は、新規に追加した

SSMTiDBAppMCPRead
ステートメントに対してどのような意味を持ちますか? 具体的には 他の MCP からもこの SSM パラメータが読めてしまう という意味ですが、これは許容範囲ですか? 別ロールに分離する / しない、それぞれの判断基準を説明してください。

📝 わたし

a. insert や delete に使う書き込み用ユーザーは今回の mcp で使わないから含めてない。mcp からは read 権限があるユーザーのみ使えればいい。
強い権限のユーザーのため、もし全部含めた場合、何らかの理由で漏れたら意図しないデータ削除や更新がされる恐れがある。
b. 社内利用であるため、管理の簡潔さを優先した。また、a の対応をとっているため別 MCP から誤って TiDB 内のデータを削除更新されることはない。
もしこれを分離する判断をする場合、そもそも同居している MCP 全て権限を分けるべきである。しかしそれは大掛かりな変更になるので、今回は許容範囲です

🎙️ レビュワー

両質問とも完璧です。

  • (a)「層防御 (layered defense)」を正しく理解: SSM Resource スコープで書き込み用ユーザーを除外 → 仮にロールが漏れても読み取り専用権限のみしか使えず、データ破壊リスクを含まない
  • (b)「スコープと threat model の bargain」も明晰: 同居 MCP 間で role 分離するなら全部作り直すほどの大掛かりな変更になり、今の threat model (社内利用 + 全 MCP が (a) と同じ least-privilege な SQL ユーザーを使う前提) では分離しない判断が合理的

全 8 問、十分な深さで回答いただけました。実装内容の理解 / 判断根拠 / 副作用 / エッジケース、すべて自分の言葉で説明できています。

✅ PR 作成 OK です。

激詰めレビューのQAを、PRの対象行のコメントとして追加する(コピペして使ってみてください!)

激詰めレビューの後は、

GEKIDUME_REVIEW.md
という質疑応答の要約ファイルがプロジェクトのルートに生成されます。

PR(draftでもOK)およびこのファイルがある状態で、本SKILL

/post-grill-to-pr
を実行することで、PRの差分のうち該当行にQAの内容を自動で貼り付けます。

これによって、特に重要な差分に関する意思決定の理由や経緯をレビュワーから確認できるようになり、
「ここってなんでこの実装になってるの?」「インフラがこの構成になっている理由は?」などのやり取りを減らし、レビュワーの負担削減につながります。

SKILL.md

---
name: post-grill-to-pr
description: リポジトリルート直下に `GEKIDUME_REVIEW.md` (gekidume-review が残す Q&A 記録) が存在し、かつ現在ブランチに open な PR がある場合に限り、Q&A 要約を該当実装行のインラインコメントとして PR に投稿する。`/common:pullreq` 完了時の自動補助として、あるいは「GEKIDUME_REVIEW.md の内容を PR にインラインで反映して」といった明示的な依頼時に発動する。条件が満たされないときは何もせず skip。
---

# post-grill-to-pr

`gekidume-review` が生成した `GEKIDUME_REVIEW.md` の Q&A を、PR レビュアーが diff の該当行から辿れるように、インラインコメントとして投稿する。

## 目的

- なぜその実装にしたか・トレードオフは何か、という設計判断の背景を **コード上の該当行** に紐付けて残す
- PR 本文やドキュメントだけでなく、レビュアーが diff を見ているときに直接目に入る形で参照できるようにする
- `GEKIDUME_REVIEW.md` をローカル管理のまま、かつレビュアーに設計意図を共有できるようにする

## 発動条件 (すべて満たすときだけ動作)

本 skill は以下のすべてが成り立つときだけコメントを投稿する。どれか欠けていれば **何もせず「...のため skip」と一言報告して終了** する。

1. `git rev-parse --show-toplevel` で取得したリポジトリルート直下に `GEKIDUME_REVIEW.md` が存在
2. 現在のブランチに open な PR が存在 (`gh pr list --head <branch> --state open` で 1 件以上)
3. `GEKIDUME_REVIEW.md` 内に Q&A ブロック (後述フォーマット) が 1 件以上ある

## 手順

### 1. 前提確認

```bash
GIT_ROOT=$(git rev-parse --show-toplevel) || exit 0
BRANCH=$(git branch --show-current)
REVIEW_FILE="${GIT_ROOT}/GEKIDUME_REVIEW.md"

# 条件 1: GEKIDUME_REVIEW.md が存在するか
[ ! -f "$REVIEW_FILE" ] && echo "GEKIDUME_REVIEW.md が無いため skip" && exit 0

# 条件 2: open PR が 1 件以上あるか
PR_NUM=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')
[ -z "$PR_NUM" ] && echo "open PR が無いため skip" && exit 0

OWNER_REPO=$(gh repo view --json name,owner -q '"\(.owner.login)/\(.name)"')
COMMIT_SHA=$(gh api repos/$OWNER_REPO/pulls/$PR_NUM --jq .head.sha)
```

### 2. GEKIDUME_REVIEW.md 解析

Read ツールで全文を読み、Q&A ブロックを抽出する。想定されるブロックフォーマット (いずれも `### ` H3 見出しで始まる):

```markdown
### Q<番号>/<総数>: <題名>

**質問:** ...
**回答:** ...
**評価:** ...
```

```markdown
### 前Q<番号>/9: <題名>

**Q (Phase 1 当時):** ...
**回答 (Phase 1):** ...
**Phase 2 での進化:** ...
**評価:** ...
```

追加の変形 (`**Q:**` / `**A:**` 表記、複数サブクエスチョン `**Q (a):**` `**A (a):**` 等) にも柔軟に対応する。

抽出時の注意:

- `**評価:**` 行は自己評価メタ情報なので **投稿コメントには含めない**
- `**質問:**` / `**Q:**` / `**Q (...)** :**` は質問要旨
- `**回答:**` / `**A:**` / `**回答 (...):**` / `**Phase 2 での進化:**` は回答/経緯
- サブクエスチョンがあるものはまとめて 1 コメントにする (例: Q (a) と Q (b) を同じコメント内で並列提示)

### 3. 各 Q&A に対応する file:line を特定

Q&A 本文から該当する実装位置を推定する。優先順:

1. **markdown リンク記法** `[filename.go:L42](path/to/filename.go#L42)` が本文にあれば、そのファイル + 行をそのまま採用
2. **ファイル名言及 + 関数名 / 変数名 / SID** が登場する場合、現在のブランチでそれを `grep -n` して行を特定
3. どれも決まらなければ **その Q&A だけ skip** (他の Q&A の投稿は続行)

行が新規追加ファイルか否かに関わらず、`side: "RIGHT"` で投稿する (PR 新 head の行番号)。

### 4. JSON ペイロード組み立てと POST

`/tmp/post_grill_review_<owner>_<repo>_<pr>.json` に以下の形式で書き出す:

```json
{
  "commit_id": "<COMMIT_SHA>",
  "event": "COMMENT",
  "body": "設計判断メモ (ローカルに保管している GEKIDUME_REVIEW.md より) の要点を、該当実装行に対してインラインコメントで貼り付けました。レビュアーが設計判断の背景を追いやすくするための補助情報です。",
  "comments": [
    {
      "path": "<file path (repo root からの相対)>",
      "line": <line number>,
      "side": "RIGHT",
      "body": "<後述フォーマットで整形したコメント本文>"
    }
  ]
}
```

投稿:

```bash
gh api repos/$OWNER_REPO/pulls/$PR_NUM/reviews --method POST \
  --input /tmp/post_grill_review_<owner>_<repo>_<pr>.json \
  --jq '"review id: \(.id) | state: \(.state) | html_url: \(.html_url)"'
```

成功時は review ID / state / URL を報告、失敗時はエラー内容を共有。

## コメント本文のルール (必須)

投稿するコメントは **PR レビュアーに読まれる公的な記録** になるため、以下を厳守する。

### 大前提: コメントは「master ↔ feature ブランチ diff」を見るレビュアー視点で書く

PR レビュアーが目にするのは「master ブランチからの最終 diff」のみであり、作業ブランチ内で詰めの過程に行われた書き換え・初期実装・撤回された案などの履歴は **diff には存在しない**。一方 GEKIDUME_REVIEW.md には「旧実装は〜だった」「当初は〜を選んだ」「Q1 で誤り、Q2 で訂正」など作業中の経緯が頻繁に出てくる。これらをそのままインラインコメントに転記すると、レビュアーには文脈不明で意味が伝わらない。

そのため本 skill では、**GEKIDUME_REVIEW.md の内容をそのまま流し込まず、master 比の最終実装視点で書き直してから投稿する**。

具体的には以下を厳守:

- ❌ 「**旧実装では〜**」「**以前は〜だった**」「**修正前は〜**」「**当初は〜を採用していたが**」「**Q2 で発覚した**」「**詰めの過程で〜**」のような **作業ブランチ内部の経緯参照は含めない**。これらはチャット履歴が無いと意味を成さない
- ❌ 「**激詰め**」「**激詰めレビュー**」「**詰め**」「**詰める**」 系の文言を **絶対に含めない**
- ❌ 「**grill**」「**gekidume**」 など英語・ローマ字表記でも含めない
- ❌ **「Q (質問) → A (回答)」の対話体裁** は内部記録のフォーマットであり、PR レビュアーには不要。平叙な散文 + 段落分けで「方針 + 理由 + 結果の構造」を語る形に書き直す
- ✅ ヘッダ書式は `## <Q番号>: <題名>` (例: `## Q3/9: Fatal 通知の Release ID を失敗分のみに絞る`、`## 前Q4/9: X-User-Token ヘッダ優先`)。題名も master diff 視点での「決定事項」を端的に表すものに調整する(「〜の設計の妥当性」「〜が嘘になる問題」のような問題提起型は避け、「〜を採用」「〜に切り替え」のような結論型にする)
- ✅ 本文は **方針 / 理由 / 結果の構造** の 3 段構成で書く:

  ```markdown
  ## Q<番号>/<総数>: <最終決定を端的に表す題名>

  <方針: 最終的に何をどうしたか。master 比で diff に現れる事実を 1〜2 文で>

  理由は、<運用面 / 保守面 / 将来性 / 安全性などの根拠を、レビュアーが単独で読んで腑に落ちる形で書く。必要に応じて 1〜3 段落>。

  <結果の構造: 具体的な実装の形(関数名・ヘルパー・データフロー・分岐条件)を 1〜2 段落で>。
  ```

- ✅ サブクエスチョンや複数の判断ポイントがある場合も、Q (a) / Q (b) のような対話形式は使わず、見出し付き段落 (例: `### 採用案`、`### 棄却案`) で並列に整理する
- ✅ review 全体の body (投稿時に冒頭に付くメッセージ) は「設計判断メモ (ローカルに保管している GEKIDUME_REVIEW.md より) の要点を、該当実装行に対してインラインコメントで貼り付けました。レビュアーが設計判断の背景を追いやすくするための補助情報です。」のように、**専門的で落ち着いた言い回し** にする

### 推奨スタイルの例

```markdown
## Q3/9: Fatal 通知の Release ID を失敗分のみに絞る

強制取り込みで解消した release_id は Fatal メッセージから除外し、TCJ 再送依頼が必要な楽曲の release_id のみを表示する方針にした。

理由は、メッセージを確認した社員が、次に取るべきアクションとその対象リリースを一目で識別できるようにするため。検出された全件を併記すると、既に解消した分と要対応分が混在して運用ノイズになる。

Fatal メッセージは「検出件数 / 強制取り込み成功(追加対応不要)件数 / TCJ 再送依頼件数 + 依頼が必要な Release ID」の 3 段構成に整理した。Release ID の集計は `collectFailedReleaseIDs` ヘルパーで `failedTcjSongIDs` から逆引きする。
```

### 避けるべきスタイルの例 (NG)

```markdown
## Q3/9: Fatal 通知の releaseIDs が放置リスト全件分混在する設計

**Q:** 旧実装の Fatal 通知では runDdexImport 呼び出し前の放置リスト全件を Release ID として表示していたため、強制取り込みで解消した分も含まれて運用者がアクション対象を判別しにくかった。

**A:** 強制取り込みで解消した release_id は Fatal メッセージから除外し、...
```

この NG 例の問題:

- 題名が「〜が混在する設計」と問題提起型で、master diff 視点では現在の実装がどうなっているかが伝わらない
- 「旧実装の〜」は master diff には存在しない経緯参照で、レビュアーが混乱する
- `**Q:**` / `**A:**` の対話体裁は GEKIDUME_REVIEW.md 内部記録のフォーマットであり、PR では冗長

### 変換手順 (内部ガイド)

GEKIDUME_REVIEW.md の各 Q&A ブロックから PR コメントへ変換する際は、以下の手順で書き直す:

1. **題名**: 「〜の妥当性」「〜の問題」「〜のバグ」のような問題提起型なら、「〜を採用」「〜に切り替え」「〜のリスクを警告で気づく仕組み」のように **結論型** に置き換える
2. **本文の冒頭**: 「Q:」「A:」を取り去り、**最終的な方針を平叙文で書き出す**(master 比で diff に現れる事実)
3. **理由**: 「理由は、〜のため。」の形で、**作業ブランチ内部の事情に依存しない普遍的な根拠** を述べる。「旧実装では〜」「以前は〜」を排除し、運用面・保守面・将来性・安全性などの観点で書く
4. **結果の構造**: 関数名・ヘルパー名・データフロー・分岐条件など、**diff を読みながら参照できる具体的な実装の形** を述べる
5. 余計な「評価:」「Phase 2 での進化:」「修正実施」などのメタ情報は **全て削除する**

## エッジケース

- **GEKIDUME_REVIEW.md が無い**: 何もせず skip
- **Open PR が無い**: 何もせず skip
- **GEKIDUME_REVIEW.md は存在するが Q&A が 0 件**: skip とログ出力
- **一部 Q&A が file:line にマップできない**: その Q&A だけ skip、他の Q&A は投稿
- **GitHub API エラー (`422` など、行が diff 外等)**: エラー内容を報告、可能な他コメントは続行
- **既に同様の review を投稿済みかどうか** は本 skill ではチェックしない (重複投稿になる)。避けたいユーザーは事前に既存 review を削除するか、`GEKIDUME_REVIEW.md` を削除しておく
- **複数 repo に跨がる (vk_web + vk_infra など)**: 本 skill は 1 repo = 1 実行。repo ごとに個別実行する

## 発動タイミング

- `/common:pullreq` の PR 作成/更新が完了した後、仕上げとして実行される想定
- ユーザーが「GEKIDUME_REVIEW.md の内容を PR にインラインで反映して」のように明示的に依頼した時
- ユーザーが「grill の内容を PR の該当行にコメントして」と言った場合も発動 (内部的な合図ワード。投稿されるコメント本文には含めない)

## 想定される動作ログ (成功時)

```
✅ post-grill-to-pr: PR #<num> に <N> 件のインラインコメントを投稿しました
   review id: <id>
   url: https://github.com/<owner>/<repo>/pull/<num>#pullrequestreview-<id>
```

## 想定される動作ログ (skip 時)

```
ℹ️ post-grill-to-pr: GEKIDUME_REVIEW.md が無いため skip
```

または

```
ℹ️ post-grill-to-pr: 現在ブランチ <branch> に open PR が無いため skip
```

良かったこと

  • より良い実装に気づける

質問に回答するために調査する中で、「これ実は他の設定の方がいいのでは?」「この実装よくないかも」と気付けるので、事前に穴に気付き、塞ぐことができます。

  • エッジケースに気づける

「こういうパターンではどういう結果になる?」「それは意図通り?」と聞いてくれるので、事前に判断して修正できます。

利用し始めて短いので改善が必要な部分はありますが、今後も少しずつ修正して良いものにしていこうと思います。

まとめ

  • AIが書いてくれるコードの量と品質が上がった結果、「動いてるけど理解はしていないコード」がPRに混ざりやすくなってきました
  • PR作成前に AI に詰めてもらうことで、理解の抜け漏れに気付き、学習機会にしましょう

Claude Code の SKILL として置いておけばいつでも呼び出せるので、よかったらコピペして使ってみてください。

同じような悩みを持っている方や、もっと良い詰め方をご存知の方がいれば、ぜひコメントで教えていただけるとありがたいです🙏