Любой AI-инструмент умеет генерировать commit message. Проблема в том, что он генерирует что-то разумное — но не то, что принято в вашем проекте: не знает ваш формат с тикетами, не вытаскивает номер задачи из ветки, не учитывает какие типы у вас разрешены.
В этой статье я покажу как один раз описать правила своего проекта так, чтобы AI следовал им предсказуемо — каждый раз. Основной пример на Claude Code, но паттерн и готовый скрипт переносятся на любой инструмент: Cursor, Copilot Chat, git hook с API-вызовом.
Проблема, которую не решает commitlint
Commitlint — отличный инструмент. Он ловит коммиты неправильного формата и не даст смержить ветку с wip или асдф в истории. Но он не помогает их написать.
Он говорит «неправильно» — и возвращает тебя к пустой строке. Сформулировать правильное сообщение всё равно надо самому.
В конце рабочего дня, когда ты только что пофиксил хитрый баг в маппере — это отдельная мыслительная задача после нескольких часов другой мыслительной работы. Поэтому даже в проектах с commitlint история нередко выглядит так:
git log --onelinea3f1c2e fix: fixed9bd04a1 feat: wipc782d3f refactor: changes4e910bb fix: bug2a33f0c chore: update
Формат формально соблюдён. Commitlint доволен. История бесполезна.
Вместо того что должно быть:
git log --onelinea3f1c2e DEV-1677/feat(discrepancies): реализовать полнотекстовый поиск ТОРГ-29bd04a1 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 statusOn branch feature/DEV-2041-delivery-paginationChanges 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-msgdescription: > Генерирует готовое 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): реализовать полнотекстовый поиск ТОРГ-22. 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/shDIFF=$(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-вызовом.
ссылка на оригинал статьи https://habr.com/ru/articles/1044636/