以下の内容はhttps://tech.algomatic.jp/entry/2025/08/14/175747より取得しました。


Claude Code hooksで始めるPromptOps:チームで意図を残す仕組み作り

こんにちは。Algomatic AI Transformation(AX) の柗村@yu_mattznです。

私は7月にAIプロダクトエンジニアとして入社し、今はネオデザインAIの開発責任者をしています。プロダクト出身でAIについての深い知見はないため、日々AIエンジニアの方々の投稿からキャッチアップできて最高な環境です。

今回は、Claude Codeをチームで使う際、プロンプトを共有するのが今後大事になってくるのではないか?と考えたきっかけとその取り組みを紹介します。

Claude Codeを導入してみて

Claude Codeが出た時、部署では一瞬で使用を許可していただきました。(爆速。感謝)

使ってみて、フロントもバックエンドも一通して開発させることができ、実装スピードは跳ね上がりましたが、バックエンドは自身が使用してきた言語ではなかったのもあり、コードレビューがボトルネックになりました。

そこで、いかに並行開発しスピードを出すか、より、どうやってレビューをしやすくするか、を考えるようになりました。 (もちろんAIにレビューさせる前提で、どこまでレビューするかはプロダクトや組織によるとは思いますが、完全Vibe codingは立場上できませんでした。)

コーディングの意図が分からないとレビューがしづらい

Claude Codeに実装させてpull requestを出す -> GitHubのpull request画面でレビューをする。他のメンバーのコードも同時にレビューしていて、自分の体感ではAIで書かせたコードのレビュー時間の方が短くできました。

他のメンバーのコードレビューでもAIが書いたコードレビューでも、pull requestは基本的には「何をしたか(What)」が記述されます。しかし、AIに私がコードを書かせた場合、レビューする側(つまり私)は、その背景にある「なぜこれが必要だったのか(Why)」を理解しながらプロンプトに打ち込んでいます。自分の意図がプロンプトに込められており、目で処理を追いながら、動作の度に止めたり再開したりの試行錯誤をしている時もあるので、大体のコードは頭に入っています。

人のコードに対してはGitHub上でのコミュニケーションで捕捉していると思います。

この問題は、Vibe codingで生まれたプロトタイプをプロダクトに昇格させるような、引き継ぎの場面でより顕著になりました。

  • 技術選定の背景がわからない(ex. なぜプロダクト1はNext.jsで、プロダクト2はReact+Viteなのか?)
  • ドキュメント化されていない「お作法」や「秘伝のタレ」のようなコードが存在する
  • 変更を加えたくても、意図が不明なため影響範囲が読めず、修正が怖い

コードレビューや運用保守において、表面的な「What:どういう変更か」だけでなく、そのコードが生まれた背景にある「Why:なぜこのコードが生まれたか」、つまり開発者が込めた意図を知りたい瞬間って結構あると思います。

これだけだと、CLAUDE.mdにコーディングの意図を含めてコメントやGitのコミットメッセージに書くように指示をして終わりですがもう一つ別の観点で。

AIコーディングエージェントの使用Tipsの共有・伝承

ペアプロ・モブプロっていいですよね。VSCodeの便利な使い方や、Excelのショートカットなどでも、普段意識することないレベルに使い込まれた技能を知ることができ、地味なんだけど生産性がとても上がったことを覚えています。

Claude Codeのように丸っと頼めるコーディングエージェントを使用していて、設定などは共有できるとしても、どういうプロンプトを打って開発させているのか、をチームで共有し、学ぶことに価値があるのではないか?と思うようになりました。

使用したプロンプトを残して共有する

上二つの観点から、私は「AIに与えたプロンプトを残し、共有することが今後大事になってくるのではないか」と考えました。

最初はCLAUDE.mdに書いたプロンプトをファイルに記載するように頼んでいたのですが、たまに無視するし、トークンを節約してるのか英語で書いたりと散々でした。

Claude Code hooks

そんな時に出たのがClaude Code hooks。特定のイベント後に確定でアクションを起こせます。最初はClaude Codeが終了した際に、発行されるStopイベントを利用していましたが、ユーザーが自ら止めた時に動かなかったり、プロンプト自体を取ることができなかったのでtranscript_pathに貯まるログファイルから抽出する手間があったりしたんですが、

最近しれっと追加されたUserPromptSubmitイベント。 このイベントは、ユーザーがプロンプトを叩くたびに発行され、InputのkeyにPromptが存在していて取り回しもしやすいです。

今回はUserPromptSubmitを使って、pull requestの詳細に使用したプロンプトが全て記載されるようにした仕組みを紹介します。 (ファイルとしてそのまま残すのも考えましたが、AIが開発時にノイズになったり、ディレクトリに残ってるのも微妙か?と考え、pull requestに全て乗るように作りました)

Step 1: Claude Code hooksでプロンプトを保存する

以下のスクリプトを.claude/scripts/save_prompt.shとして配置します。このスクリプトは、プロンプトが実行されるたびに、その内容をプロジェクトルートの.claude/prompts.logファイルに追記します。

#!/bin/bash
set -euo pipefail

# プロジェクトルートを取得(Claude Codeがcdで動き回るため固定)
repo_root=$(git rev-parse --show-toplevel)

# ログファイルのパス設定(プロジェクトの.claudeディレクトリ内)
LOG_DIR="$repo_root/.claude"
LOG_FILE="$LOG_DIR/prompts.log"

# 標準入力からJSONを読み込む
input=$(cat)

# jqを使ってプロンプトとセッションIDを抽出
if command -v jq >/dev/null 2>&1; then
  prompt=$(echo "$input" | jq -r '.prompt // "No prompt"')
  session_id=$(echo "$input" | jq -r '.session_id // "unknown"')
  cwd=$(echo "$input" | jq -r '.cwd // "unknown"')
else
  echo "❌ jqがインストールされていません。jqをインストールしてください。" >&2
  exit 1
fi

# タイムスタンプ
timestamp=$(date '+%Y-%m-%d %H:%M:%S')

# ログに追記
{
  echo "[$timestamp]"
  echo "Session ID: $session_id"
  echo "Working Directory: $cwd"
  echo "Prompt: $prompt"
  echo "=========================================='"
  echo ""
} >> "$LOG_FILE"

echo "✅ プロンプトをログに記録しました: $LOG_FILE" >&2
exit 0

これを.claude/settings.jsonに記載します。

{
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "hooks": [
        {
            "event": "UserPromptSubmit",
            "script": ".claude/save_prompt.sh"
        }
    ]
}

.claude/settings.jsonを共有していてhooksを書いてる場合、チームメンバーは許可する・しないに関わらず、スクリプトが実施されてしまうので、お気をつけて・・・! (OSSをインストールしてClaude Codeを使用する際にもお気をつけて)

Claude Codeを最初起動する時の注意書き

Step 2: githooksでプロンプトをコミットメッセージに含める

ログファイルにプロンプトを記録するだけでは、どのプロンプトがどの変更に対応するのかわかりません。そこで、git commitを実行する際に、記録されたプロンプトを自動でコミットメッセージに含めるようにします。

Gitのprepare-commit-msgフックを利用します。以下の内容を.git/hooks/prepare-commit-msgとして保存し、実行権限を付与してください。

#!/bin/bash

# prepare-commit-msg hook
# コミットメッセージファイル、コミットタイプ、SHA1を受け取る
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

# プロンプトログファイル
PROMPTS_LOG=".claude/prompts.log"

# prompts.logが存在する場合のみ処理を実行
if [ -f "$PROMPTS_LOG" ]; then
    # 既存のコミットメッセージを一時保存
    TEMP_MSG=$(cat "$COMMIT_MSG_FILE")
    
    # prompts.logから全プロンプトを抽出(重複を除去)
    PROMPTS=$(grep "^Prompt: " "$PROMPTS_LOG" 2>/dev/null | sed 's/^Prompt: //' | sort -u)
    
    # プロンプトが見つかった場合のみ追加
    if [ -n "$PROMPTS" ]; then
        # プロンプトを整形
        ADDITIONAL_MSG="## Claude Code プロンプト履歴"$'\n'
        while IFS= read -r prompt; do
            ADDITIONAL_MSG+="- $prompt"$'\n'
        done <<< "$PROMPTS"
        
        # 新しいメッセージを作成(既存のメッセージ + プロンプト履歴)
        cat > "$COMMIT_MSG_FILE" << EOF
$TEMP_MSG

$ADDITIONAL_MSG
EOF
    fi
    
    # prompts.logを削除
    rm "$PROMPTS_LOG"
    echo "✓ $PROMPTS_LOG を削除しました"
fi

exit 0

Step 3: GitHub Actionsでpull requestに集約する

pull requestが作成・更新されたタイミングで、そこに含まれる全てのコミットからプロンプト履歴を抽出し、PRのdescriptionに自動で追記するGitHub Actionsのワークフローを作成します。

.github/workflows/update_pr.ymlとして以下のファイルを作成します。

name: Update PR with User Instructions

on:
  pull_request:
    types: [opened, synchronize]
  workflow_dispatch:

permissions:
  pull-requests: write
  contents: read

jobs:
  update-pr-description:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Update PR description with user instructions
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const pr = context.payload.pull_request;
            if (!pr) {
              console.log('No PR found in context');
              return;
            }

            const { data: commits } = await github.rest.pulls.listCommits({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: pr.number
            });

            const userInstructions = new Map();
            let totalCommits = 0;
            let commitsWithInstructions = 0;

            for (const commit of commits) {
              totalCommits++;
              
              const lines = commit.commit.message.split('\n');
              let inPromptsSection = false;
              const instructionLines = [];

              for (const line of lines) {
                if (line.includes('## Claude Code プロンプト履歴')) {
                  inPromptsSection = true;
                  continue;
                }
                if (inPromptsSection && line.startsWith('##')) {
                  inPromptsSection = false;
                }
                if (inPromptsSection && line.trim().startsWith('- ')) {
                  const instruction = line.trim().replace(/^-\s*/, '').trim();
                  if (instruction) {
                    instructionLines.push(instruction);
                  }
                }
              }

              if (instructionLines.length > 0) {
                commitsWithInstructions++;
                for (const instruction of instructionLines) {
                  userInstructions.set(instruction, (userInstructions.get(instruction) || 0) + 1);
                }
              }
            }

            let commitSection = '## 📝 ユーザー指示履歴\n\n';
            if (userInstructions.size === 0) {
              commitSection += `このPRには${totalCommits}件のコミットがありますが、明確なユーザー指示は含まれていません。\n`;
            } else {
              commitSection += `総コミット数: ${totalCommits}件(うち${commitsWithInstructions}件にユーザー指示あり)\n\n`;
              commitSection += '### ユーザー指示一覧:\n\n';
              const sortedInstructions = Array.from(userInstructions.entries()).sort((a, b) => b[1] - a[1]);
              for (const [instruction, count] of sortedInstructions) {
                commitSection += `- ${instruction}${count > 1 ? ` (${count}回)` : ''}\n`;
              }
            }

            const currentBody = pr.body || '';
            const commitSectionRegex = /## 📝 (?:コミット履歴|ユーザー指示履歴)[\s\S]*?(?=\n## |$)/g;
            let newBody = currentBody.replace(commitSectionRegex, commitSection.trim());

            if (newBody === currentBody) {
              newBody = currentBody + '\n\n' + commitSection;
            }

            await github.rest.pulls.update({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: pr.number,
              body: newBody
            });

結果

この仕組みを導入することで、pull requestには以下のような「ユーザー指示履歴」が自動で追加されるようになります。

実際の画面

レビュアーは、コードの変更がどのような意図で行われたのかを把握できるし、チームメンバーがどのようにClaude Codeに指示を出しているかを理解し、どう使っていくのかの共有もできるようになりました。

まとめ

今回はAIが開発するのが当たり前になる世界観では、そのコードを生み出した「意図(プロンプト)」を管理し、チームで共有するかが大事になってくるのではないか、という仮説のもと、取り組みを紹介しました。


最後にAlgomaticは一緒に働くメンバーを募集しています! 私のようなプロダクトエンジニアも募集しているので、以下よりお気軽にカジュアル面談をお申し込みいただけると幸いです!

jobs.algomatic.jp




以上の内容はhttps://tech.algomatic.jp/entry/2025/08/14/175747より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14