目次

1.はじめに
2.実装計画ファイルの出力先について
3.スクリプトとhooksの設定について
4.hooksを動かしてみた
5.おわりに

1.はじめに

Plan Mode とは Gemini CLI が実装を始める前に調査・計画のプロセスを踏み、ユーザーとの合意を経て実装を行うモードとなります。

この際、ユーザーとの合意を経た後、実装計画ファイルが作成されます。
デフォルトだと ~/.gemini/tmp/{project}/{session-id}/plans/ に保存されます。

この実装計画ファイルですが、隠しフォルダ内に格納している状態だと手軽に参照し難いため、hooks 機能を活用することで Obsidian の保管庫にコピーし、参照しやすい形とする方法を紹介します。

※ そもそも Gemini CLI の Plan Mode について知りたい方は、弊社ブログ Gemini CLIのPlan Modeを使ってみた を参照ください。

環境情報

  • macOS:Tahoe 26.3.1
  • Node.js のバージョン( node --version ):24.11.1
  • Gemini CLI のバージョン( gemini --version ):0.37.1

2.実装計画ファイルの出力先について

公式ドキュメントの Custom plan directory and policies より、実装計画ファイルはデフォルトでは ~/.gemini/tmp/{project}/{session-id}/plans/ に保存されます。

ちなみに、この tmp フォルダ内にあるプロジェクトやセッションに関連するデータ(実装計画書を含む)はデフォルト設定の場合、30日後に自動的に削除されます。(公式ドキュメントの general.sessionRetention.maxAge 参照)
30日より長くしたい、逆に短くしたいのであれば、settings.json にて明示的に設定を入れる必要があります。

もし、別手段で長期的に保存したいのであれば、以下2つの方法がございます。

  • 対象プロジェクト内に実装計画ファイルの保管先をこのプロジェクト内とする設定を定義した settings.json ファイルを作成し、実装計画ファイルをそのプロジェクトの Git リポジトリで管理する。
  • 対象プロジェクト内に実装計画ファイルの保管先をこのプロジェクト内とする設定を定義した settings.json ファイルを作成し、その上で hooks 機能を活用することで、GCS などのストレージサービスに実装計画ファイルを保管する。(公式ドキュメントの Example: Archive approved plans to GCS (AfterTool) 参照)

組織やチームの管理ポリシーとして実装計画ファイルを厳密に管理したい場合、この2つの方法が考えられるかと思います。

このような厳密な管理ポリシーがある/ないに関わらず、作成した実装計画ファイルを個人管理している Obsidian の保管庫にひとまとめにし、参照しやすい形を作りたいケースもあるかと思います。

後続では、実装計画ファイルの保管先はデフォルトとしたまま、hooks 機能を活用する形で、指定の Obsidian の保管庫に実装計画ファイルをコピーする方法(一例)を紹介します。

3.スクリプトとhooksの設定について

hooks で使用するスクリプト(実装計画ファイルのコピーを実施する)を用意し、それを settings.json の hooks の指定イベント箇所のコマンドとして設定します。

スクリプトを ~/.gemini/scripts/ に作成します。
ファイル名は copy-plan-to-obsidian.sh とします。(スクリプト内の保存先ディレクトリの作成とコメントしている箇所の変数にてコピー先を指定します)

#!/usr/bin/env bash

# AfterToolフックはstdinにJSON(tool_input/tool_response等)を渡す
input=$(cat -)

# exit_plan_mode成功時のllmContentから計画書の絶対パスを抽出
# 文字列から /Users/ で始まる .md ファイルのパスを正規表現で抽出する
plan_path=$(echo "$input" | jq -r '.tool_response.llmContent' | grep -oE '/Users/[^ ]+\.md' | head -n 1)

# 計画書が存在する場合のみコピー処理を実行
if [ -f "$plan_path" ]; then
  # プロジェクトルートのパス(GEMINI_PROJECT_DIR)からプロジェクト名を取得(環境変数がない場合はカレントディレクトリ名)
  project_name=$(basename "${GEMINI_PROJECT_DIR:-$(pwd)}")

  # 日本時間(Asia/Tokyo)で日付と時刻のディレクトリ/スタンプを作成
  date_dir=$(TZ=Asia/Tokyo date +%Y%m%d)
  time_stamp=$(TZ=Asia/Tokyo date +%H%M%S)

  # 保存先ディレクトリの作成
  dest_dir="/Users/xxx/Documents/obsidian_保管庫/gemini-cli-plans/${project_name}/${date_dir}"
  mkdir -p "$dest_dir"

  # コピー元ファイル名を取得しそこに時刻を付与する形でコピーする
  base_filename=$(basename "$plan_path")
  cp "$plan_path" "${dest_dir}/${time_stamp}_${base_filename}"
fi

# 常に allow を返す(フックの失敗で CLI を止めないため)
echo '{"decision": "allow"}'

ファイルを作成したら chmod +x ~/.gemini/scripts/copy-plan-to-obsidian.sh で実行権限を付与します。
このスクリプトが実行されることで以下のコピー先の構成でフォルダとファイルが作成されます。

## コピー元
~/.gemini/tmp/
└── {プロジェクト名}/
    └── {セッションID}/
        └── plans/
            └── xxx-plan.md  # Gemini CLIが作成した実装計画ファイル

## コピー先(イメージ)
~/Documents/obsidian_保管庫/
└── gemini-cli-plans/
    └── my-project/          # GEMINI_PROJECT_DIRから取得したプロジェクト名
        └── 20260412/        # 実行日
            └── 063940_xxx-plan.md  # コピーした実装計画ファイル

上記のスクリプトは hooks で実行するので settings.json を編集します。
グローバルに利用したいので ~/.gemini/settings.json に設定します。

hooks のセクションを作り、AfterTool を設け、先ほど作成したスクリプトをコマンドとして指定します。

"hooks": {
    "AfterTool": [
      {
        "matcher": "exit_plan_mode",
        "hooks": [
          {
            "name": "copy-plan-to-obsidian",
            "type": "command",
            "command": "~/.gemini/scripts/copy-plan-to-obsidian.sh",
            "description": "Plan Modeの計画書をObsidianの保管庫にコピーする"
          }
        ]
      }
    ]
  }

matcher を exit_plan_mode とすることで、Plan Mode にて exit_plan_mode ツール(実装計画が最終決定し、レビューのために提示、実施開始の承認を求める)が呼ばれた時のみ hooks が稼働しスクリプトが実行される形としています。

4.hooksを動かしてみた

Gemini CLI にて Plan Mode を実行してみます。
実行内容としては、弊社ブログ Gemini CLIのPlan Modeを使ってみた と同様とします。

Terraform の AWS ネットワークリソースのコードに対し、後から NACL の設定要件が追加されたというシナリオで、この要件追加部分を Plan Mode で行います。

本ブログの趣旨は hooks を活用した実装計画ファイルのコピー方法の紹介となるので、Plan Mode で作成された実装計画ファイルが追加要件を満たしているか、実装計画ファイルを元に変更、作成された AWS リソースや Terraform コードが追加要件を満たしているかの確認は省略します。

Plan Mode の実行に伴い実装計画ファイルが ~/.gemini/tmp/{project}/{session-id}/plans/ に作成され、それが hooks の稼働に伴い指定箇所にコピーされるかを確認します。

Gemini CLI を起動し /plan コマンドで Plan Mode とします。

ユーザープロンプトに以下を入力し、やり取りを開始します。

このプロジェクトにはAWSのネットワークリソースのTerraformコードが記載されています。追加要件として、NACLの設定が必要となりました。 まずはプロジェクトの構成とREADME.mdの仕様書を確認してください。

プロジェクトの構成を確認した上で NACL の設定追加方針に関する質問が出力されました。

NACL の追加方針をまとめた Markdown ファイルをプロジェクトに追加し、その内容に基づく対応とする指示をユーザープロンプトに入力します。

NACL_要件.mdファイルを追加したので、これに基づいた変更としてください。

実装方針が提案されましたが、確認事項が表示されているので回答します。

回答を反映した実装戦略が提示されたので承認します。

承認すると実装計画が表示されるので、こちらも承認し進めていきます。

承認すると、実装計画ファイルの絶対パスが出力されました。

実装計画に基づく対応が実施され、タスク完了の旨が出力されました。

では、本題である実装計画ファイルが作成され、指定箇所にコピーされているかを見てみます。

まずコピー元である ~/.gemini/tmp/{project}/{session-id}/plans/ を確認すると、以下の通り nacl_implementation.md という名前で実装計画ファイルが作成されています。

$ pwd
/Users/xxx/.gemini/tmp/hooks/d9dcfd72-d1b4-47d5-9cb0-e96121f10399/plans
$ ls
nacl_implementation.md
$ cat nacl_implementation.md 
# NACL実装プラン

## 目的
`NACL_要件.md` に基づき、VPC内の各サブネット種別(Public, Protected, Private)に対して個別のNetwork ACL (NACL) を設定し、厳格な通信制限を実装する。

## 鍵となるファイル
- `modules/vpc/main.tf`: NACLリソース(`aws_network_acl`)、ルール(`aws_network_acl_rule`)、関連付け(`aws_network_acl_association`)を追加。

## 実装手順

### 1. NACLリソースの作成
`modules/vpc/main.tf` に以下のNACLリソースを追加する。
- `aws_network_acl.public`
- `aws_network_acl.protected`
- `aws_network_acl.private`

### 2. NACLルールの追加
`aws_network_acl_rule` を使用して要件を実装する。
- **Public NACL ルール**:
  - Inbound: TCP 443 (0.0.0.0/0), TCP 1024-65535 (0.0.0.0/0)
  - Outbound: TCP 80 (Protected CIDRs), TCP 443 (0.0.0.0/0), TCP 1024-65535 (0.0.0.0/0)
- **Protected NACL ルール**:
  - Inbound: TCP 80 (Public CIDRs), TCP 32768-61000 (0.0.0.0/0)
  - Outbound: TCP 443 (0.0.0.0/0), TCP 3306 (Private CIDRs), TCP 1024-65535 (Public CIDRs)
- **Private NACL ルール**:
  - Inbound: TCP 3306 (Protected CIDRs)
  - Outbound: TCP 32768-61000 (Protected CIDRs)

### 3. サブネットへの関連付け
`aws_network_acl_association` を使用して、既存のサブネットと新しいNACLを関連付ける。

## 検証方法
- `terraform validate` による構文チェック。
- `terraform plan` を実行し、既存リソースの破壊(再作成)が発生せず、意図したリソースが追加されることを確認する。

続いてコピー先を確認すると、スクリプトで指定したフォルダに 085115_nacl_implementation.md という名前で実装計画ファイルが作成されています。

$ pwd
/Users/xxx/Documents/obsidian_保管庫/gemini-cli-plans/hooks検証/20260412
$ ls
085115_nacl_implementation.md
$ cat 085115_nacl_implementation.md 
# NACL実装プラン

## 目的
`NACL_要件.md` に基づき、VPC内の各サブネット種別(Public, Protected, Private)に対して個別のNetwork ACL (NACL) を設定し、厳格な通信制限を実装する。

## 鍵となるファイル
- `modules/vpc/main.tf`: NACLリソース(`aws_network_acl`)、ルール(`aws_network_acl_rule`)、関連付け(`aws_network_acl_association`)を追加。

## 実装手順

### 1. NACLリソースの作成
`modules/vpc/main.tf` に以下のNACLリソースを追加する。
- `aws_network_acl.public`
- `aws_network_acl.protected`
- `aws_network_acl.private`

### 2. NACLルールの追加
`aws_network_acl_rule` を使用して要件を実装する。
- **Public NACL ルール**:
  - Inbound: TCP 443 (0.0.0.0/0), TCP 1024-65535 (0.0.0.0/0)
  - Outbound: TCP 80 (Protected CIDRs), TCP 443 (0.0.0.0/0), TCP 1024-65535 (0.0.0.0/0)
- **Protected NACL ルール**:
  - Inbound: TCP 80 (Public CIDRs), TCP 32768-61000 (0.0.0.0/0)
  - Outbound: TCP 443 (0.0.0.0/0), TCP 3306 (Private CIDRs), TCP 1024-65535 (Public CIDRs)
- **Private NACL ルール**:
  - Inbound: TCP 3306 (Protected CIDRs)
  - Outbound: TCP 32768-61000 (Protected CIDRs)

### 3. サブネットへの関連付け
`aws_network_acl_association` を使用して、既存のサブネットと新しいNACLを関連付ける。

## 検証方法
- `terraform validate` による構文チェック。
- `terraform plan` を実行し、既存リソースの破壊(再作成)が発生せず、意図したリソースが追加されることを確認する。

Obsidian 上でもスクリプトでコピーしてきた実装計画ファイルを参照できました。

念の為、コピー元ファイルとコピー先ファイルで diff コマンドを実行しましたが、差分は出力されず、内容が同一であることが確認できました。

$ diff \
>   /Users/xxx/.gemini/tmp/hooks/d9dcfd72-d1b4-47d5-9cb0-e96121f10399/plans/nacl_implementation.md \
>   /Users/xxx/Documents/obsidian_保管庫/gemini-cli-plans/hooks検証/20260412/085115_nacl_implementation.md
$

5.おわりに

Gemini CLI の hooks 機能を活用し、Plan Mode 実行時に作成される実装計画ファイルを指定箇所にコピーする方法を紹介しました。

実際に hooks を設定した上で Plan Mode を使ってみましたが、体感としてPlan Mode の挙動に影響はございませんでした。

今回使用したスクリプトはあくまで一例となります。
また、実装計画ファイルをローカルでなく組織やチームポリシーとして全体管理したい場合は Git の活用やクラウド上への保管などを検討ください。