susumuis_notes

SourceTree派の僕がAIにコミットメッセージ書かせるのをやってみたけど

最近AIにコミットメッセージを書かせるのが流行ってるらしいので僕もやってみたい。

だけど僕はGitを使う時はSourceTreeで使うことに決めている。
コマンドを使っても良いが、ソースを扱う時はGUIで見たほうが色んな情報が視界に入って大局的に見えると思うから、このポリシーを変えるつもりはない。

そんな僕の作業ログ。

作戦1: コミットだけgitコマンドを使う

まずはSourceTreeでStageだけしてgit commitコマンドは手で実行しようとした。

やめた。
git commitコマンドはメッセージなしで保存するとcommitをabortする。
初めからメッセージが書いてあると「やっぱやめた」と思ったときにコミットされてしまうことがあった。
それに、AI呼び出しのために、git commitコマンドが一瞬時間かかって気持ち悪い。

僕の使い方にはフィットしない。

作戦2: SourceTreeカスタムアクション

スクリプトを ~/bin/ai-auto-commit として作成。(内容は後述)

SourceTree > 設定 > カスタムアクション で以下を登録:

項目
メニューキャプション AI Commit Message
スクリプト /bin/bash
パラメータ ~/bin/ai-commit-msg.sh

使い方:

  1. SourceTreeで変更をステージ
  2. カスタムアクション「AI Commit Message」を実行
  3. クリップボードに自動コピーされる
  4. メッセージ欄に Cmd+V で貼り付け
  5. 内容を確認・編集してコミット

だめだった。
何故かクリップボードにコピーされない。
どうやらSourceTreeのカスタムアクションはサンドボックスで実行されるようだ。
それにカスタムアクションをメニューから選ぶのはめんどくさい。

作戦3: stageしてからコミットする前に自分でコマンドを入力する

% ai-commit-msg           
README.mdのdocker runコマンドを修正

- XXXコマンドにXXX名を追加
- コマンドの可読性向上を図るための改行

--- クリップボードにコピーしました。SourceTreeのメッセージ欄に貼り付けてください ---
(確認: README.mdのdocker runコマンドを修正...)

~/bin/にパスを通したら ai- で補完される。

export PATH="$HOME/bin:$PATH"

これでいいや。

ai-auto-commit.sh の内容

AIが作ったコードなので内容はよく読んでない。トークンとか含まれてなさそうだし大丈夫だと思うので公開するけど仕様は自己責任で

#!/bin/bash
# ai-commit-msg.sh: ステージ済みの差分からAIコミットメッセージを生成し、クリップボードにコピー
#
# 使い方:
#   SourceTree カスタムアクション or ターミナルから直接実行
#   .git/hooks/ai-commit-msg.sh

export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"

# gh CLI の存在確認
if ! command -v gh &>/dev/null; then
    echo "ERROR: gh CLI が見つかりません" >&2
    exit 1
fi

# ステージ済みの差分を取得
DIFF=$(git diff --cached --stat --diff-algorithm=minimal -p)

if [ -z "$DIFF" ]; then
    echo "ERROR: ステージ済みの変更がありません" >&2
    exit 1
fi

# 差分が大きすぎる場合は stat のみにする
DIFF_LINES=$(echo "$DIFF" | wc -l)
if [ "$DIFF_LINES" -gt 300 ]; then
    DIFF=$(git diff --cached --stat)
    DIFF_NOTE="(差分が大きいため stat のみ送信)"
fi

SYSTEM_PROMPT="あなたはGitのコミットメッセージを生成するアシスタントです。
以下のルールに従ってください:
- 1行目: 変更内容を簡潔に要約(日本語、50文字以内目安)
- 2行目: 空行
- 3行目以降: 必要に応じて詳細を箇条書き(日本語)
- Conventional Commits の prefix は不要
- コミットメッセージのみを出力し、それ以外は何も出力しない"

USER_PROMPT="以下のgit diffからコミットメッセージを生成してください。${DIFF_NOTE}

${DIFF}"

json_escape() {
    /usr/bin/python3 -c "import json,sys; print(json.dumps(sys.stdin.read()))" <<< "$1"
}

SYSTEM_ESCAPED=$(json_escape "$SYSTEM_PROMPT")
USER_ESCAPED=$(json_escape "$USER_PROMPT")

BODY=$(cat <<EOF
{
  "model": "gpt-4o-mini",
  "messages": [
    {"role": "system", "content": ${SYSTEM_ESCAPED}},
    {"role": "user", "content": ${USER_ESCAPED}}
  ],
  "temperature": 0.3
}
EOF
)

GH_TOKEN=$(gh auth token 2>/dev/null)
if [ -z "$GH_TOKEN" ]; then
    echo "ERROR: gh auth token の取得に失敗しました" >&2
    exit 1
fi

RESPONSE=$(curl -s \
    -H "Authorization: Bearer ${GH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$BODY" \
    https://models.inference.ai.azure.com/chat/completions 2>&1)

AI_MESSAGE=$(/usr/bin/python3 -c "
import json, sys
try:
    data = json.load(sys.stdin)
    print(data['choices'][0]['message']['content'].strip())
except Exception as e:
    print(f'ERROR: {e}', file=sys.stderr)
    sys.exit(1)
" <<< "$RESPONSE" 2>/dev/null)

if [ $? -eq 0 ] && [ -n "$AI_MESSAGE" ]; then
    /usr/bin/pbcopy <<< "$AI_MESSAGE"
    echo "$AI_MESSAGE"
    echo ""
    echo "--- クリップボードにコピーしました。SourceTreeのメッセージ欄に貼り付けてください ---"
    echo "(確認: $(pbpaste | head -1)...)"
else
    echo "ERROR: メッセージ生成に失敗しました" >&2
    exit 1
fi