Любой 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-123PROJ-42

  • <type> — featfixrefactortestcidocsstyleperfbuildhotfix

  • <scope> — модуль или слой: deliveryauthgoods

  • <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-вызовом.

Комментарии (6)


  1. Void-Cowboy
    07.06.2026 17:51

    так то хуки существуют наравне с гитом - сам всю сознательную жизнь их юзаю, сначала по работе а потом как то втянулся везде

    а в рамках AI - как по мне позволять комиттить агенту это худшее решение. Он сделал, ты сам просмотрел, проверил и знакомитил проконтролировал что ушло и что изменилось.

    агенты иногда такую чушь порят что в процессе вычитки еше на столько же времени правок уйдет что бы уменьшить итоговое количество изменений в половину. А ведь они еще и херни сотворить могут - скажешь ему не меняй ничего дальше конкретной папки и конкретного коммита, а он возьмёт и ребайзнет изменения прошлым числом. И именно такой кейс отследить можно только читая поток процесса что он пишет внимательно (понимая что он пишет) или если коммитов не много, ветка одна и ты понимаешь что ранее тут не было такого функционала.

    относительно текста коммитов - так же сомнительно. Хотя для задач где вообще плевать и красивый коммит будет плюсом то неронка спасет, но в остальном комитты в первую очередь для себя самого пишутся, а не какая-то глупая обязаловка на работе.


    1. zzzkorn Автор
      07.06.2026 17:51

      скил не коммитит и не пушит — он генерирует строку текста которую вы вставляете сами, контроль полностью ваш

      относительно текста коммитов - так же сомнительно. Хотя для задач где вообще плевать и красивый коммит будет плюсом то неронка спасет, но в остальном комитты в первую очередь для себя самого пишутся, а не какая-то глупая обязаловка на работе.

      Коммиты для себя — окей, пока ты один и помнишь что делал. Но приходишь через полгода, смотришь git blame и видишь fix: fixed. Что фиксил? Зачем? Уже не помнишь. Идёшь копать Jira.

      Или онбординг: новый человек читает историю чтобы понять как рос модуль — видит feat: wip, feat: wip 2, fix: fix. Это не история, это мусор. Нормальные коммиты — это документация которую не надо писать отдельно.

      Ну и git bisect — ищешь где сломалось, бинарный поиск по истории. С осмысленными коммитами понимаешь что смотреть. С fix: bug — чекаутишь вслепую и угадываешь.

      «Для себя» — это и есть аргумент за хорошие коммиты, а не против. Себе через полгода тоже скажешь спасиб


      1. Void-Cowboy
        07.06.2026 17:51

        вот про это я и говорю что для себя. Сначала пишется fix: fixed а потом "кто ж так коммиты пишет что ничего не понятно"

        за копипаст - есть прям плагины для jetbrains что добавляют кнопку в окно коммита и можно даже без копипастом генерить нажатием по фактическим измененниям только

        юмор в том что вы сами в моменте знаете что это был фикс проблемы которая вылезла при расширении диапазона, а нейронка вам напишет что был расширен диапазон, включая зависимости. То есть через пол года другая команда придет с вопросом почему у них падает и вы по коммитам не сможете увидеть что была такая проблема.


        1. zzzkorn Автор
          07.06.2026 17:51

          Согласен — нейронка видит diff, но не знает что за ним стоит. Баг в проде, звонок от клиента, edge case который три часа искали — этот контекст есть только у вас в голове. Она напишет «расширен диапазон», а не «исправлена ошибка при выходе за границы диапазона». И коммит с неправильным «почему» через полгода введёт в заблуждение так же как отсутствие коммита вообще.

          Но в статье есть пример именно про это, просто он подан не так явно.

          > перефразируй, акцент на том что mock только для dev-окружения
          
          DEV-1676/feat(auth): добавить JWT с автоматическим mock-режимом вне production

          Итерация перефразирет, акцент на том что mock только для dev-окружения — это не про красоту формулировки. Это про то что вы даёте контекст который не видно из diff, а инструмент упаковывает его в нужный формат. Схема не «нейронка угадывает почему» — а «вы знаете почему, говорите это одной фразой, получаете готовый коммит». Понимание остаётся у вас, рутина уходит.

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

          Про плагины JetBrains — справедливо, они тоже закрывают часть задачи. Но встроенный плагин не знает ваш формат с тикетом в префиксе — и не научишь без кастомного промпта который надо прописывать на каждой машине отдельно. Скил живёт в репозитории, подхватывается при клоне, работает одинаково у всей команды и переносится на любой инструмент — Cursor, хук, API-вызов — из коробки.


  1. Revertis
    07.06.2026 17:51

    Спасибо за наводку! Попробую у себя поиспользовать.


    1. zzzkorn Автор
      07.06.2026 17:51

      Пожалуйста) Пользуйтесь!