💡

[Gemini CLI] コードレビュー用のGemini CLIカスタムコマンドを作る

に公開

こんにちは、Geminiは「ジェミニ」と発音する派のサントリーこと大橋です。

少し前になりますがGemini CLIでCustom Command(カスタムコマンド)がサポートされました。

https://cloud.google.com/blog/topics/developers-practitioners/gemini-cli-custom-slash-commands?hl=en

今回はコードレビュー用のGemini CLIカスタムコマンドを作成してみたいと思います。

背景

私のAIエージェントの使い方。

このセクションは読み飛ばして構いません

私は普段JetbrainsのJunieをコーディングエージェントとして利用しています。
そしてサブと言うか設計相談や仕様書の作成などはGemini CLIを利用しています。

これはJunieはコーディングエージェントとしてはとても優秀で気にいっているのですが、
コード生成する場合と、質問をする場合でUI上でボタンを押してモードの切り替えが必要で、
モードの切り替えを忘れてコード生成のつもりが質問になってしまったり、その逆が発生するのが煩わしく、Junieにはコード生成だけに注力してもらっています。

そしてコンソールでGemini CLIを立ち上げて、汎用エージェントとして常駐してもらい、大きなコンテキストウィンドウを利用して、大量のコードからコード傾向を見た設計をしてもらったり、Web検索を含めた質問の回答や、MCPなどを利用して外部ドキュメントから要件書を作成してもらったりして、Junieのインプットドキュメントを作成させています。

課題: JunieとGemini間の連携

JunieからGeminiへコードレビューなどを行う場合はGemini CLIの-pオプションを利用しgemini -p "prompt" を利用すれば行えます。
例えばJunieのstyleguides.md(いわゆるAgents.md)に以下のように書いておくと、コード生成後にGeminiへコードレビューなどを依頼できるようになります。

コードを生成後は `“gemini -p "path/to/file をコードレビューしてください"` とコマンドで実行し、Geminiにコードレビューを依頼しその結果を利用してコードを修正してください。

ただし-pオプションを用いた場合、大量のプロンプトを渡すには少し使いづらいです。コードレビューであれば、レビューの観点や、出力フォーマット、対象コードのリストアップなどシステム指示として記述したい内容は多くあります。

カスタムコマンド

こういった課題に対して使えるのが今回追加された「カスタムコマンド」です。

カスタムコマンドとは

カスタムコマンドはその名の通りGemini CLIで再利用可能なプロンプトを定義し、再利用可能な個人用ショートカットを作成できる機能です。カスタムコマンドを呼び出すには、あらかじめ設定したコマンド名のスラッシュコマンド(/comamand hoge fuga)を利用します。

個人向けのプロジェクトに依存しないユーザーコマンド(グローバル)や、プロジェクト固有のプロジェクトコマンド(ローカル)を作成することができます。

カスタムコマンドの基本

Geminiのドキュメントを翻訳しつつカスタムコマンドの基本について記載していきます。

https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/commands.md

カスタムコマンドの作成方法(ファイル置き場と優先順位)

カスタムコマンドを作成するには専用のtomlファイル(コマンド定義ファイル)を作成し以下の場所に配置します。

  • ユーザーコマンド: ~/.gemini/commands
  • プロジェクトコマンド: <your-project-root>/.gemini/commands/

ユーザーコマンドとプロジェクトコマンドで同一コマンド名が存在した場合は、プロジェクトコマンドが優先して呼び出されます。

カスタムコマンドのコマンド名とネームスペース

カスタムコマンドのコマンド名は、上記のコマンドディレクトリからの相対パスによって決定されます。サブディレクトリは名前空間付きコマンドを作成するために使用され、パス区切り文字 (/ または ) はコロン (:) に変換されます。

例えば ~/.gemini/commands/test.toml にあるファイルは、コマンド /test になります。
<project>/.gemini/commands/git/commit.toml にあるファイルは、名前空間付きコマンド /git:commit になります。

カスタムコマンドのコマンド定義ファイル(TOMLファイル)フォーマット

コマンド定義ファイルはTOML形式で記述し、.toml ファイル拡張子を使用する必要があります。

  • 必須フィールド
    • prompt (文字列): コマンド実行時に Gemini モデルに送信されるプロンプト。1行または複数行の文字列を指定できます。
  • オプションフィールド
    • description (文字列): コマンドの動作を簡潔に説明する 1行テキスト。このテキストは、/help メニューでコマンドの横に表示されます。このフィールドを省略した場合は、ファイル名から一般的な説明が生成されます。

カスタムコマンドの引数

カスタムコマンドは、引数を処理するための2つの機能をサポートしています。
Gemini CLIは、カスタムコマンドのプロンプトの内容に基づいて適切な機能を自動的に選択します。

1. {{args}}を利用した Context-Aware Injection(文脈に応じた自動挿入)

プロンプトに特殊なプレースホルダ {{args}} が含まれている場合、Gemini CLI はそのプレースホルダをユーザーがコマンド名の後に入力したテキストに置き換えます。

このインジェクションの動作は、使用場所によって異なります。

A. Raw Injection (シェルコマンド外)

プロンプト本体で使用する場合、引数はユーザーが入力したとおりに正確にインジェクションされます。

例 git/fix.toml
description = "Generates a fix for a given issue."
prompt = "Please provide a code fix for the issue described here: {{args}}."

仮に Gemini CLIで /git:fix "ほげふが"と実行すると、GeminiはPlease provide a code fix for the issue described here: ほげふが.というプロンプトを受け取ることになります。

B. 引数のシェルコマンド内での利用

{{args}}!{...}(シェルインジェクションブロック) 内で使用すると、引数は置換前に自動的にエスケープされます。これにより、シェルコマンドに引数を安全に渡すことができ、結果として得られるコマンドの構文が正確で安全であると同時に、コマンドインジェクションの脆弱性を防止できます。

例 grep-code.toml
prompt = """
Please summarize the findings for the pattern `{{args}}`.

Search Results:
!{grep -r {{args}} .}
"""

ここで /grep-code It's complicated を実行した場合

  1. Gemini CLI は {{args}}!{...} の外側と内側の両方で使用されていることを認識します。
    • 外側: 最初の {{args}} はそのまま It's complicated に置き換えられます。
    • 内側: 2 番目の {{args}} はエスケープされた値に置き換えられます。 (例: Linux の場合: "It's complicated")
  2. 実行されるコマンドは grep -r "It's complicated" ..となる
  3. Gemini CLIが実行前にコマンドを確認するプロンプトを表示
  4. プロンプトが送信

2. Default Argument Handling (通常の引数処理)

プロンプトに{{args}}が存在しない場合、Gemini CLIは引数の処理に以下の動作をします。

コマンドに引数を指定した場合(例:/mycommand arg1)
CLI は入力したコマンド全体をプロンプトの末尾に2つの改行で区切って追加します。これにより、モデルは元の命令と、指定した特定の引数の両方を認識できます。

引数を指定しない場合(例:/mycommand)
プロンプトは何も追加されずにそのままモデルに送信されます。

プロンプトに直接入力したコマンドの全文が追加されるので、コマンド引数のパース処理(というより解釈)をGeminiに委ねることができます。

通常のCLIコマンドを作った事がある方の感覚ですと、オプション引数や位置引数をパースする処理を記述する必要があるように思われるかもしれません。(私はそうでした。)
Geminiのカスタムコマンドの場合はこの引数の解釈処理をGeminiに任せることができるのです。

例 changelog.toml
# In: <project>/.gemini/commands/changelog.toml
# Invoked via: /changelog 1.2.0 added "Support for default argument parsing."

description = "Adds a new entry to the project's CHANGELOG.md file."
prompt = """
# Task: Update Changelog

You are an expert maintainer of this software project. A user has invoked a command to add a new entry to the changelog.

**The user's raw command is appended below your instructions.**

Your task is to parse the `<version>`, `<change_type>`, and `<message>` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file.

## Expected Format
The command follows this format: `/changelog <version> <type> <message>`
- `<type>` must be one of: "added", "changed", "fixed", "removed".

## Behavior
1. Read the `CHANGELOG.md` file.
2. Find the section for the specified `<version>`.
3. Add the `<message>` under the correct `<type>` heading.
4. If the version or type section doesn't exist, create it.
5. Adhere strictly to the "Keep a Changelog" format.
"""

この例では、モデルの役割を定義し、ユーザーの入力を見つける場所を説明し、予想される形式と動作を指定して、作成する方法を記しています。

このファイルを配置して、/changelog 1.2.0 added "New feature" を実行すると、Geminiに送信される最終プロンプトは、元のプロンプトの後に 2つの改行と入力したコマンドが続きます。

Geminiはプロンプトと追加された入力コマンドを解釈し、処理を行います。

3. !{...}を使用したシェルコマンドの実行

プロンプト内で直接シェルコマンドを実行し、その出力を注入することで、コマンドを動的に実行できます。これは、ファイルの内容の読み取りや Git のステータス確認など、ローカル環境からコンテキストを収集するのに最適です。

4. @{...}を使用したファイルコンテンツのインジェクション

@{...} 構文を使用すると、ファイルの内容やディレクトリの一覧をプロンプトに直接埋め込むことができます。これは、特定のファイルを操作するコマンドを作成する場合に利用できます。

実践: コードレビューカスタムコマンド(/codereview)

ではここまでの知識を使用して、カスタムコマンドを作成してみます。

/codereviewの仕様

コードレビューカスタムコマンドは以下のような仕様とします。

  • git diffを用いて修正したファイルの一覧と修正内容を取得してコードレビューを行う。
    • 引数がなければ git diff origin/main HEADを用いる
    • 位置引数があった場合はそれらを利用して git diff ARG1 ARG2を実行する
  • いくつかオプション引数を受け取る
    • --requirements path/to/requirements.mdがあった場合は要件ファイルとして先に読み込む
    • --design path/to/design.mdがあった場合は設計ファイルとして先に読み込む
      • これらのファイルがあった場合はこれらのファイルを前提としてコードレビューを行う
    • --output FILE_PATHが存在する場合はレビュー結果は FILE_PATHにファイルとして出力 存在しない場合は標準出力に出力
  • レビュー観点
    • レビュー観点はbest_practice.mdを参考とする
    • コードの読みやすさ、重複したコードの有無、セキュリティ上の懸念、潜在的なバグを含んでいないか、不要なコードはないか、要件などに対してテストコードが足りているか、パフォーマンスなどを確認する。
  • 出力
    • フォーマット
      • 日本語、GitHub Flavored Markdown 形式で出力
      • 特に指定がなければ以下を出力
        * 1. この修正の概要
        * 2. レビュー内容
        * 2-1. 指摘箇所(ファイル名、行番号)
        * 2-2. 指摘点
        * 2-3. 修正方法
        * 2-4. AI Agent向けのコード修正用プロンプト

例:

  • 最小パターン
    • /codereview
  • ブランチ指定
    • /codereview origin/main feature/hogefuga
  • ファイル出力
    • /codereview --output review-result.md
  • 参照ファイル指定
    • /codereview --requirements path/to/requirements.md --design path/to/design.md --output review-result.md origin/main feature/hogefuga

/codereviewの実装

以下のtomlファイルを .gemini/commands/codereview.tomlとして配置します。

.gemini/commands/codereview.toml
# 実行コマンド: /codereview base_hash target_hash
description = "引数で渡されたベースハッシュ(base_hash)と、ターゲットハッシュ(target_hash)からgit diffを行い、その結果からコードレビューを行います。"

prompt = """
# 役割
あなたはこのプロジェクトの熟練コードレビューアーです。
本プロジェクトのコミッターはすべてこのプロジェクトに精通しており、技術力/知識も十分に高いです。

# タスク: コードのレビュー
コマンドに対する引数の第一引数と第二引数の git diff を取得して、プルリクエストに対するコードレビューを行ってください。
コマンドに対する引数がない場合や、git diffのパラメータとして適切でない場合は `git diff origin/main HEAD`でdiffを取得してください。

--requirements path/to/requirements.md が渡されている場合は対象のファイルは要件を定義したファイルです。
--design path/to/design.md が渡されている場合は対象のファイルは設計を定義したファイルです。
それぞれのファイルを読み込んで、要件や設計を理解した上で、`git diff`の結果を用いてレビュー対象のファイルと変更点を取得しコードレビューを行ってください。

# 出力先

コマンドに対する引数に`--output FILE_PATH`が存在する場合はレビュー結果は FILE_PATHにファイルとして出力してください。
存在しない場合は標準出力に出力してください。

# 出力フォーマット

コードレビューは

1. この修正の概要
2. レビュー内容
    2-1. 指摘箇所(ファイル名、行番号)
    2-2. 指摘点
    2-3. 修正方法
    2-4. AI Agent向けのコード修正用プロンプト

について日本語で記載してください。

- 指摘はできる限り簡潔にわかりやすく記載してください。
- コードを出力する場合はできる限り簡潔で短いコードになるように注意してください。
- コメントはすべて日本語で実施してください。
- GitHub Flavored Markdown 形式で出力してください。

# レビュー観点

- コードレビュー対象のファイルを読み込み、必要であれば似た役割のコードを参照し、コードの書き方に大きな差異が無いか確認してください。
- 要件ファイルや、設計ファイルが渡されている場合はそれらの要件、設計からズレがないかを確認してください。
- コードの読みやすさ、重複したコードの有無、セキュリティ上の懸念、潜在的なバグを含んでいないか、不要なコードはないか、要件などに対してテストコードが足りているか、パフォーマンスなどを確認してください。

# プロジェクト内のベストプラクティス

@{best_practice.md}

"""

※プロンプトはもっと洗練できると思います。あまりプロンプトエンジニアリングとしては参考にしないでください。まとめに参考となるプロンプトのリンクを記載しています。

/codereviewの実行

geminiコマンドを実行してプロンプトとして指示するか、cliで直接gemini -p "/codereview"で実行します。
なお、JunieなどのAIエージェントから呼び出す場合はGeminiがコマンド実行の許可を求めるため、--yoloオプションなどを追加して、事前にコマンド実行の許可を与えておくことをおすすめします。

まとめ

Gemini CLIのカスタムコマンドについて解説しました。
今回はコードレビューカスタムコマンドを作成しましたが、自分でコードを書く場合でも、コーディングエージェントでコードを作成する場合でも、一度レビューが入ることでコード品質が格段に向上します。
またコーディングエージェントのコード生成フローに組み込むことで、毎度呼び出す手間もなくなり非常に楽です。

カスタムコマンドであれば、ある程度量があるプロンプトであってもファイルに指示として残すことができ、そのプロンプトをショートカットで呼び出せるので作業の省力化にも繋がります。
またコマンド引数をGeminiが解釈してくれるので、プロンプト内で柔軟に指示することができます。

なおコードレビューのプロンプトについては、Githubなどを探すといくつか良さそうなものが出てきます。以下のようにGemini CLIの前身のようなdevaiのコードレビュー用のプロンプトも参考になりますので、探してみると良いと思います。

https://github.com/GoogleCloudPlatform/genai-for-developers/blob/main/devai-cli/src/devai/commands/review.py

それでは良きGemini CLIライフを

Discussion