Генерация commit message из diff нейросетью

Хук prepare-commit-msg и скрипт на Python, которые предлагают сообщение коммита в стиле Conventional Commits по staged diff. Вы правите его в редакторе.

5 мин · · разработка · автоматизация

Генерация commit message из diff — одна из самых простых и полезных автоматизаций для разработчика: модель читает staged-изменения и предлагает сообщение в формате Conventional Commits, а вы правите его в привычном редакторе. Ниже — скрипт на Python, git-хук, обработка больших diff и честный расчёт стоимости.

Что получится

  • git commit без -m открывает редактор, где первой строкой уже стоит черновик вроде fix(auth): handle expired refresh token.
  • git commit -m "...", merge, --amend — хук не вмешивается.
  • Если API недоступен или ответ не похож на Conventional Commits, коммит идёт как обычно, с пустым шаблоном.

Генератор подсказывает, а не решает: последнее слово остаётся за автором коммита.

Скрипт генерации

Положите файл в репозиторий как scripts/generate_commit_msg.py. Нужен pip install openai и ключ fetch_... в переменной OPENAI_API_KEY.

bash
#!/usr/bin/env python3
import os
import re
import subprocess
import sys

from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["OPENAI_API_KEY"],
)
MODEL = os.environ.get("COMMIT_MSG_MODEL", "glm-5.3-flash")
MAX_DIFF_TOKENS = 3000

Почему так:

  • diff идёт через stdin, а не аргументом: большой diff в аргументе упирается в лимит длины командной строки.
  • --stat в промпте даёт модели полный список файлов, даже если сам diff обрезан.
  • Проверка первой строки регулярным выражением. Если модель ответила рассуждением вместо сообщения, скрипт завершится с ошибкой, а хук просто ничего не подставит.
  • Модель задаётся переменной COMMIT_MSG_MODEL, чтобы сравнить варианты без правки кода.

Хук prepare-commit-msg

По документации git, prepare-commit-msg вызывается после подготовки стандартного сообщения и до запуска редактора. Второй аргумент — источник сообщения: message (при -m или -F), template, merge, squash или commit (при -c, -C, --amend). При обычном git commit он пустой — только тогда и генерируем.

Важно: файл сообщения в этот момент уже не пустой — в нём закомментированный шаблон git. Поэтому проверка «файл пустой» не подходит: черновик нужно дописать в начало файла.

Файл .githooks/prepare-commit-msg:

bash
#!/bin/sh
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"

[ -n "$COMMIT_SOURCE" ] && exit 0
[ -n "$SKIP_AI_MSG" ] && exit 0

MESSAGE=$(git diff --cached | python3 scripts/generate_commit_msg.py) || exit 0
[ -z "$MESSAGE" ] && exit 0

{ printf '%s\n' "$MESSAGE"; cat "$COMMIT_MSG_FILE"; } > "$COMMIT_MSG_FILE.tmp"
mv "$COMMIT_MSG_FILE.tmp" "$COMMIT_MSG_FILE"

Подключение для всей команды (папка .git/hooks не попадает в репозиторий, а .githooks — попадает):

bash
chmod +x .githooks/prepare-commit-msg
git config core.hooksPath .githooks

Git запускает хуки из корня рабочей копии, поэтому относительный путь scripts/generate_commit_msg.py работает из любой подпапки. Хук всегда завершается с кодом 0: ошибка генерации не должна блокировать коммит.

Ручной запуск без хука

Если хук не нужен, сгенерируйте сообщение и откройте его в редакторе перед коммитом:

bash
git diff --cached | python3 scripts/generate_commit_msg.py > /tmp/ai_msg && git commit -e -F /tmp/ai_msg

Флаг -e открывает редактор, -F берёт текст из файла. Поскольку источник здесь message, хук второй раз генерацию не запустит.

Большие diff

Обновлённый lock-файл или сгенерированный код легко дают diff на десятки тысяч строк. Отправлять всё целиком бессмысленно: выходит дорого, а сообщение коммита всё равно описывает суть, а не каждую строку. smart_truncate оставляет заголовки файлов и ханков (diff --git, @@) и сколько помещается содержимого в бюджет около 3000 токенов, остальное отрезает с пометкой.

Ещё два приёма:

  • исключайте шумные файлы прямо в git: git diff --cached -- . ':(exclude)package-lock.json';
  • для огромных коммитов честнее разбить их на несколько логических — модель тут не поможет.

Сколько это стоит

Допущения: промпт с правилами и --stat — около 300 токенов, средний diff после обрезки — около 1700, итого 2000 токенов входа; ответ — около 60 токенов. На 1000 коммитов это 2 000 000 входных и 60 000 выходных токенов.

МодельФормула на 1000 коммитовИтого
glm-5.3-flash ($0.04 / $0.04)(2 000 000 × 0.04 + 60 000 × 0.04) / 1 000 000≈ $0.08
claude-sonnet-5 ($0.3 / $0.3)(2 000 000 × 0.3 + 60 000 × 0.3) / 1 000 000≈ $0.62

Худший случай — diff упёрся в лимит 3000 токенов: около 3300 входа и 150 выхода, то есть (3300 + 150) × 0.04 / 1 000 000 ≈ $0.00014 за коммит на glm-5.3-flash.

Команда из 10 человек по 5 коммитов в день — 50 коммитов в день, около 1100 в месяц (22 рабочих дня): примерно $0.09 в месяц на glm-5.3-flash или $0.68 на claude-sonnet-5. Какая модель пишет сообщения, которые вы реже правите, проверьте сами: прогоните скрипт с COMMIT_MSG_MODEL на 20–30 последних коммитах и сравните с тем, что написали люди. Актуальный список моделей — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.

Для хука удобно завести отдельный ключ: в кабинете можно ограничить его списком разрешённых моделей и дневным лимитом в долларах — подробнее в разделе о лимитах ключей. Создайте ключ, а общий порядок подключения смотрите в быстром старте.

Частые вопросы

Хук не срабатывает. Что проверить?

  1. git config core.hooksPath указывает на папку с хуком, а файл исполняемый (chmod +x).
  2. Вы коммитите без -m: с -m источник сообщения message, и хук намеренно выходит.
  3. OPENAI_API_KEY виден в том окружении, где запускается git. GUI-клиенты и IDE часто не читают ваш .zshrc или .bashrc.
  4. Запустите скрипт вручную: git diff --cached | python3 scripts/generate_commit_msg.py — ошибка будет видна в stderr.

Как временно отключить генерацию?

SKIP_AI_MSG=1 git commit — хук проверяет эту переменную. Флаг --no-verify тут не поможет: по документации git он не отключает prepare-commit-msg, только pre-commit и commit-msg.

Модель путает feat и fix. Как поправить?

Добавьте в PROMPT правила вашей команды: «feat — новое поведение для пользователя; fix — исправление ошибки; refactor — без изменения поведения; только тесты — test; только документация — docs». Два-три примера из истории вашего репозитория работают лучше длинных объяснений.

Как подключить это через Husky?

В Husky v9 хук — обычный shell-скрипт в папке .husky, без строки подключения husky.sh из старых версий. Создайте .husky/prepare-commit-msg с тем же содержимым, что и в примере выше; core.hooksPath Husky настраивает сам.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.