Любой AI-инструмент умеет генерировать commit message. Проблема в том, что он генерирует что-то разумное — но не то, что принято в вашем проекте: не знает ваш формат с тикетами, не вытаскивает номер задачи из ветки, не учитывает какие типы у вас разрешены.
В этой статье я покажу как один раз описать правила своего проекта так, чтобы AI следовал им предсказуемо — каждый раз. Основной пример на Claude Code, но паттерн и готовый скрипт переносятся на любой инструмент: Cursor, Copilot Chat, git hook с API-вызовом.
Проблема, которую не решает commitlint
Commitlint — отличный инструмент. Он ловит коммиты неправильного формата и не даст смержить ветку с wip или асдф в истории. Но он не помогает их написать.
Он говорит «неправильно» — и возвращает тебя к пустой строке. Сформулировать правильное сообщение всё равно надо самому.
В конце рабочего дня, когда ты только что пофиксил хитрый баг в маппере — это отдельная мыслительная задача после нескольких часов другой мыслительной работы. Поэтому даже в проектах с commitlint история нередко выглядит так:
git log --oneline a3f1c2e fix: fixed 9bd04a1 feat: wip c782d3f refactor: changes 4e910bb fix: bug 2a33f0c chore: update
Формат формально соблюдён. Commitlint доволен. История бесполезна.
Вместо того что должно быть:
git log --oneline a3f1c2e DEV-1677/feat(discrepancies): реализовать полнотекстовый поиск ТОРГ-2 9bd04a1 DEV-1676/feat(auth): добавить JWT-аутентификацию с mock-режимом для разработки c782d3f DEV-1704/fix(delivery): исправить фильтрацию поставок по статусу 4e910bb DEV-1698/refactor(etrn): упростить маппер статусов электронной накладной 2a33f0c DEV-1690/test(acceptance): добавить интеграционные тесты приёмки
Как я к этому пришёл
Проблема не новая, решений много. Я прошёл через несколько:
commitizen — первое что попробовал. Интерактивный wizard в консоли задаёт вопросы: тип? scope? описание? Звучит удобно, но на практике замедляет: ты и так знаешь что хочешь написать, просто не хочешь думать о синтаксисе. Плюс под наш нестандартный формат с тикетом в префиксе он не гнётся без плясок с конфигом.
Git hook + локальная LLM — видел статью на Хабре, попробовал. Работает, но требует настройки на каждой машине и зависит от того, какая модель стоит локально. При смене ноутбука надо всё поднимать заново.
IDE плагины (JetBrains AI, GitLens) — кнопка "Generate commit message" есть. Жмёшь — получаешь что-то вроде feat: update delivery service. Формат проекта не знает, тикет не вытаскивает, каждый раз дописываешь руками.
Готовые скилы из Claude Directory — когда начал плотно работать с Claude Code, нашёл готовый /commit в маркетплейсе. Тот же результат: Conventional Commits по умолчанию, без понятия о нашем формате с тикетами.
Просто спрашивать Claude — "напиши коммит для этих изменений" работает, но непредсказуемо: иногда одна строка, иногда три абзаца с объяснениями, иногда в markdown-блоке. Нельзя просто скопировать.
У всех этих решений одна общая проблема: они не знают о правилах конкретного проекта. Генерируют что-то разумное — но не то, что ожидает ваш commitlint и ваши коллеги. Решение — один раз явно описать правила своего проекта и получать предсказуемый результат каждый раз.
Соглашения по коммитам
Чтобы было понятно о чём речь — вот формат с которым я работаю:
<ticket>/<type>(<scope>): <description>
<ticket>— номер задачи из трекера:DEV-123,PROJ-42<type>—feat,fix,refactor,test,ci,docs,style,perf,build,hotfix<scope>— модуль или слой:delivery,auth,goods<description>— повелительное наклонение, строчная буква, без точки, на русском
Правила зафиксированы в .git-commit-template — он открывается в редакторе при ручном git commit. Этот же алгоритм мы реализуем для AI-assisted пути.
Формат несложный, но каждый раз требует: вспомнить номер задачи из имени ветки, выбрать правильный тип, сформулировать повелительное наклонение, не забыть про строчную букву. Это ~10 секунд думать — каждый коммит, каждый день.
Что такое скилы в Claude Code
Claude Code — AI-ассистент в CLI, который работает прямо в терминале. Скилы — это его расширение: markdown-файл с инструкциями, который живёт в ~/.claude/skills/<name>/SKILL.md и вызывается командой /name.
Это не плагин и не скрипт. Это структурированный prompt с жёстким алгоритмом — предсказуемый, версионируемый, командный.
Ключевое отличие от "просто спросить модель": скил каждый раз выполняет одни и те же шаги в одном и том же порядке и выдаёт результат в одном и том же формате. Это воспроизводимо.
Почему не просто CLAUDE.md?
Резонный вопрос: а зачем отдельный скил, если можно добавить правила коммитов прямо в CLAUDE.md?
Можно. Но разница в том, как работает каждый из инструментов.
CLAUDE.md — это фоновый контекст: файл загружается в каждый разговор и описывает проект в целом — архитектуру, соглашения, стек. Это инструкции "как думать об этом коде". Если туда добавить пошаговый алгоритм генерации коммита, он будет лежать мёртвым грузом в каждом запросе — когда вы просите объяснить функцию, исправить баг, написать тест.
Скил — это явный вызов: /commit-msg говорит модели "сейчас делаем только это, по этому алгоритму". Нет борьбы за внимание с архитектурными правилами и описанием стека. Алгоритм выполняется полностью, в нужном порядке, без оговорок.
Ещё один аргумент: скил — единица распространения. Его можно поставить из маркетплейса, передать коллеге одним файлом, опубликовать с версией. CLAUDE.md — это файл проекта, он не путешествует отдельно.
Практическое правило: CLAUDE.md описывает что есть в проекте, скил описывает что сделать прямо сейчас.
Демо
Шаг 1 — смотрим что изменилось:
$ git status On branch feature/DEV-2041-delivery-pagination Changes not staged for commit: modified: src/digital_delivery/infrastructures/db/repositories/delivery.py modified: src/digital_delivery/application/use_cases/list_deliveries.py modified: src/digital_delivery/presentation/api/rest/v1/controllers/delivery_controller.py modified: src/digital_delivery/presentation/api/rest/v1/schemas/delivery.py
Четыре файла, три слоя архитектуры. Формулировать руками — надо держать в голове что именно менялось.
Шаг 2 — запускаем скил:
> /commit-msg
Агент последовательно читает контекст — это видно в интерфейсе Claude Code:
● bash: git branch --show-current ● bash: git diff HEAD
Читает реальный код, а не только имена файлов — поэтому понимает смысл изменения, а не просто перечисляет что поменялось.
Шаг 3 — результат:
DEV-2041/feat(delivery): добавить пагинацию в список поставок
Одна строка. Копируешь, вставляешь:
git commit -m "DEV-2041/feat(delivery): добавить пагинацию в список поставок"
Полный исходник скила
Файл ~/.claude/skills/commit-msg/SKILL.md:
--- name: commit-msg description: > Генерирует готовое git commit message по изменениям текущей ветки в формате проектного шаблона: <ticket>/<type>(<scope>): <description>. Использовать когда: "напиши commit message", "придумай коммит", "suggest a commit", /commit-msg. --- # commit-msg Сгенерировать точное commit message по формату проекта. ## Шаги 1. Получить контекст изменений: git branch --show-current git diff HEAD git diff --cached Если оба diff пусты — посмотреть на untracked файлы в git status. 2. Определить ticket из имени ветки: паттерн [A-Z]+-\d+ - feature/DD-123-add-pagination → DD-123 - fix/PROJ-7-broken-mapper → PROJ-7 - Если не найден — писать без префикса: <type>(<scope>): <description> 3. Проанализировать изменения: что добавлено, удалено, изменено и каков смысл изменения, а не только какие строки поменялись. 4. Выбрать тип: feat — новый функционал fix — исправление ошибки refactor — рефакторинг без изменения поведения test — тесты ci — CI/CD, скрипты, пайплайны docs — документация style — форматирование, codestyle perf — оптимизация производительности build / chore — сборка, зависимости, конфиги hotfix / revert — критические исправления или откат 5. Составить описание: - Повелительное наклонение: «добавить», не «добавлено» - Строчная первая буква, без точки в конце - На русском языке - Не более ~72 символов включая весь префикс 6. Вывести ТОЛЬКО результат — без объяснений, без markdown-блоков, без «вот твоё сообщение:». Просто текст для копирования. - Одно изменение: одна строка commit message. - Два или более несвязанных изменения: перечислить найденные изменения, предложить разбить на отдельные коммиты и для каждого выдать готовое сообщение.
Ещё два случая из практики
Уточнение формулировки
Первый вариант не нравится — слишком общий:
> перефразируй, акцент на том что mock только для dev-окружения DEV-1676/feat(auth): добавить JWT с автоматическим mock-режимом вне production
Это диалог, не одноразовый prompt. Можно итерировать.
Грязный diff — самый ценный сценарий
Ты работал над фичей и по пути исправил несвязанный баг. В diff — два разных изменения. Сам бы ты, скорее всего, написал один коммит и не заметил.
Агент видит diff целиком и реагирует иначе:
> /commit-msg В diff обнаружено два несвязанных изменения. Рекомендую разбить на два коммита — добавьте файлы каждого изменения отдельно через git add. 1. DEV-1677/feat(discrepancies): реализовать полнотекстовый поиск ТОРГ-2 2. DEV-1677/fix(delivery): исправить обработку пустого списка позиций
Адаптация под свой проект за 5 минут
Скил — текстовый файл. Правила проекта прописываются в SKILL.md явно — всегда понятно чему именно он следует, менять легко.
Ниже примеры того, что вписать в секцию ## Шаги вместо наших правил — под разные командные форматы:
Conventional Commits, английский:
Формат: <type>(<scope>): <description> Язык: английский, imperative mood ("add", "fix", "remove") Тикет: не используется
Jira в скобках:
Формат: [PROJ-123] <type>(<scope>): <description> Тикет: в квадратных скобках в начале, из имени ветки
Автоопределение языка по истории:
Посмотреть последние 5 коммитов через git log --oneline. Если коммиты на русском — описание на русском. Если на английском — на английском.
Принудительный тип по ветке:
Если ветка начинается с hotfix/ — тип всегда hotfix, игнорировать остальные правила выбора типа.
Правила прописываются один раз — если конвенции команды изменились, обновляешь SKILL.md тем же PR.
Идея работает без Claude Code
SKILL.md — это обычный текстовый файл. Внутри — пронумерованный алгоритм на человеческом языке. Ничего специфичного для Claude Code в нём нет.
Если вы работаете в другом инструменте, тот же подход переносится напрямую:
Cursor / Copilot Chat / любой чат с LLM — скопируйте содержимое секции ## Шаги как системный промпт или вставьте в начало запроса. Модель выполнит те же шаги.
Git hook + API — если хотите автоматику при каждом git commit, оберните алгоритм в commit-msg hook с curl-вызовом к любому LLM API:
#!/bin/sh DIFF=$(git diff --cached) BRANCH=$(git branch --show-current) SKILL=$(cat "$(git rev-parse --show-toplevel)/.claude/skills/commit-msg/SKILL.md") COMMIT_MSG=$(jq -n \ --arg skill "$SKILL" \ --arg branch "$BRANCH" \ --arg diff "$DIFF" \ '{model:"gpt-4o",messages:[ {role:"system",content:$skill}, {role:"user",content:"Branch: \($branch)\n\nDiff:\n\($diff)"} ]}' \ | curl -s https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d @- \ | jq -r '.choices[0].message.content') echo "$COMMIT_MSG" > "$1"
Положите скрипт в .git/hooks/commit-msg и сделайте исполняемым:
chmod +x .git/hooks/commit-msg
При следующем git commit hook запустится автоматически и запишет сгенерированное сообщение.
Локальная модель (Ollama) — та же схема, только URL http://localhost:11434.
Скил в ~/.claude/skills/ — удобная форма упаковки для Claude Code. Но суть переносима: один раз описать правила проекта как чеклист, получать предсказуемый результат в любом инструменте.
Командное использование: скил как часть репозитория
Скил можно положить прямо в репо:
.claude/ skills/ commit-msg/ SKILL.md
Тогда:
Все разработчики получают одинаковое поведение без настройки
Изменение формата коммитов = PR в
SKILL.md, обсуждается как любой кодНовый человек в команде сразу пишет коммиты правильно
Это меняет природу инструмента: из личного помощника он становится частью командных конвенций.
Побочный эффект: дисциплина именования веток
Скил вытаскивает номер тикета из имени ветки. Если ветка называется fix-bug или my-feature — тикета нет, скил пишет коммит без префикса.
Это создаёт мягкое давление называть ветки правильно:
feature/DEV-123-add-search → DEV-123/feat(search): ... ✓ fix/PROJ-7-broken-mapper → PROJ-7/fix(mapper): ... ✓ my-branch → feat(<scope>): ... (тикет потерян)
Никакого линтера на имена веток не нужно. Разработчик сам быстро замечает связь: назвал ветку правильно — скил работает полностью, назвал абы как — тикет в коммите пропал, придётся дописывать руками. Инструмент воспитывает привычку, не требуя её принудительно.
Ограничения
Не заменяет commitlint на CI. Скил помогает написать правильно, но не гарантирует это. Линтер всё равно нужен как страховка.
Большой грязный diff. На 500+ строках из несвязанных файлов формулировка может получиться общей. Лучше коммитить небольшими порциями.
Требует Claude Code в базовом варианте. Альтернативы — см. раздел «Идея работает без Claude Code».
Нет автозапуска в варианте с Claude Code. Скил вызывается вручную; для автоматики при каждом
git commitиспользуйте git hook — пример есть в разделе «Идея работает без Claude Code».
Итог
Коммит-сообщение — маленькая задача, которая повторяется сотни раз в год. Каждый раз несколько секунд на то, чтобы вспомнить формат, извлечь номер тикета из имени ветки, сформулировать повелительное наклонение. Скил убирает эту нагрузку и делает это воспроизводимо.
Главная идея не в том, что "Claude Code умеет генерировать коммиты". Главная идея — один раз описать правила своего проекта как пронумерованный чеклист и получать предсказуемый результат: в Claude Code через /commit-msg, в Cursor через системный промпт, в любом CI через git hook с API-вызовом.
Void-Cowboy
так то хуки существуют наравне с гитом - сам всю сознательную жизнь их юзаю, сначала по работе а потом как то втянулся везде
а в рамках AI - как по мне позволять комиттить агенту это худшее решение. Он сделал, ты сам просмотрел, проверил и знакомитил проконтролировал что ушло и что изменилось.
агенты иногда такую чушь порят что в процессе вычитки еше на столько же времени правок уйдет что бы уменьшить итоговое количество изменений в половину. А ведь они еще и херни сотворить могут - скажешь ему не меняй ничего дальше конкретной папки и конкретного коммита, а он возьмёт и ребайзнет изменения прошлым числом. И именно такой кейс отследить можно только читая поток процесса что он пишет внимательно (понимая что он пишет) или если коммитов не много, ветка одна и ты понимаешь что ранее тут не было такого функционала.
относительно текста коммитов - так же сомнительно. Хотя для задач где вообще плевать и красивый коммит будет плюсом то неронка спасет, но в остальном комитты в первую очередь для себя самого пишутся, а не какая-то глупая обязаловка на работе.
zzzkorn Автор
скил не коммитит и не пушит — он генерирует строку текста которую вы вставляете сами, контроль полностью ваш
Коммиты для себя — окей, пока ты один и помнишь что делал. Но приходишь через полгода, смотришь git blame и видишь fix: fixed. Что фиксил? Зачем? Уже не помнишь. Идёшь копать Jira.
Или онбординг: новый человек читает историю чтобы понять как рос модуль — видит feat: wip, feat: wip 2, fix: fix. Это не история, это мусор. Нормальные коммиты — это документация которую не надо писать отдельно.
Ну и git bisect — ищешь где сломалось, бинарный поиск по истории. С осмысленными коммитами понимаешь что смотреть. С fix: bug — чекаутишь вслепую и угадываешь.
«Для себя» — это и есть аргумент за хорошие коммиты, а не против. Себе через полгода тоже скажешь спасиб
Void-Cowboy
вот про это я и говорю что для себя. Сначала пишется fix: fixed а потом "кто ж так коммиты пишет что ничего не понятно"
за копипаст - есть прям плагины для jetbrains что добавляют кнопку в окно коммита и можно даже без копипастом генерить нажатием по фактическим измененниям только
юмор в том что вы сами в моменте знаете что это был фикс проблемы которая вылезла при расширении диапазона, а нейронка вам напишет что был расширен диапазон, включая зависимости. То есть через пол года другая команда придет с вопросом почему у них падает и вы по коммитам не сможете увидеть что была такая проблема.
zzzkorn Автор
Согласен — нейронка видит diff, но не знает что за ним стоит. Баг в проде, звонок от клиента, edge case который три часа искали — этот контекст есть только у вас в голове. Она напишет «расширен диапазон», а не «исправлена ошибка при выходе за границы диапазона». И коммит с неправильным «почему» через полгода введёт в заблуждение так же как отсутствие коммита вообще.
Но в статье есть пример именно про это, просто он подан не так явно.
Итерация перефразирет, акцент на том что mock только для dev-окружения — это не про красоту формулировки. Это про то что вы даёте контекст который не видно из diff, а инструмент упаковывает его в нужный формат. Схема не «нейронка угадывает почему» — а «вы знаете почему, говорите это одной фразой, получаете готовый коммит». Понимание остаётся у вас, рутина уходит.
Скил не заменяет мышление — он убирает механику. Тикет из ветки, правильный тип, строчная буква, без точки — это то что неудобно держать в голове каждый раз именно потому что вы в этот момент думаете о содержании, а не о синтаксисе.
Про плагины JetBrains — справедливо, они тоже закрывают часть задачи. Но встроенный плагин не знает ваш формат с тикетом в префиксе — и не научишь без кастомного промпта который надо прописывать на каждой машине отдельно. Скил живёт в репозитории, подхватывается при клоне, работает одинаково у всей команды и переносится на любой инструмент — Cursor, хук, API-вызов — из коробки.