---
name: commit-message
description: >-
  ОБЯЗАТЕЛЬНО при commit, коммит, закоммить, commit message, conventional commit,
  staged, semantic-release, «сгенерируй коммит», git commit, /commit-message: ПЕРВЫМ
  делом Shell — scripts/commit/staged-context.ps1; затем Conventional Commit (заголовок EN, тело RU).
---

> **Кнопка ✨ Generate commit message в Source Control** не использует этот скилл и Rules.
> Эквивалент: Agent Chat → **`/commit-message`** или «сгенерируй коммит по staged».
> См. [docs/README.md](../../docs/README.md#сообщения-коммитов-cursor).

# Commit message (EvoBGP)

## Когда применять (сразу читать этот скилл)

Триггеры в промпте пользователя или в плане/AGENTS.md:

- `commit`, `коммит`, `закоммить`, `commit message`, `conventional commit`
- `git commit`, `staged`, `сообщение коммита`, `сгенерируй коммит`
- агент собирается выполнить `git commit` или предложить текст коммита

## Шаг 0 — ОБЯЗАТЕЛЬНО (до любого текста коммита)

**Первый вызов инструментов** в этой задаче — Shell из корня репозитория:

```powershell
powershell -NoProfile -File scripts/commit/staged-context.ps1
```

| Правило | Деталь |
|---------|--------|
| **MUST** | Запустить скрипт до генерации заголовка/тела коммита |
| **MUST NOT** | Строить сообщение только по `git diff --cached` / `git status` без скрипта |
| **MUST NOT** | Пропускать скрипт, даже если diff «и так понятен» |
| Exit `1` | Index пуст — сообщить пользователю, **не коммитить** |
| Exit `0` | Разобрать JSON stdout: `staged_count`, `groups[]` (`scope`, `files`, `stat`, `diff_excerpt`) |

Дополнительно: [.cursor/rules/conventional-commits.mdc](../../rules/conventional-commits.mdc).

## Слияние групп (одна фича)

Скрипт группирует **только по путям**. Перед split проверьте логическую связность:

**Объединить в один коммит**, если это одна задача:

- `docs/openapi.yaml` + `internal/httpapi/` (+ опционально `web/`) — один endpoint/контракт;
- `migrations/` + `internal/store/` / `repository/` — одна схема;
- правки теста рядом с кодом той же фичи.

При объединении: один scope (доминирующий пакет, часто `httpapi` или `openapi`), один type, одно тело RU.

**Auto-split** — если группы **не связаны** (например `web/` + `internal/birdfmt/` без общего смысла): **отдельный коммит на группу**, без вопроса пользователю.

## Порядок коммитов при split

1. `openapi`
2. `httpapi`, `store`, `jobs`
3. `pipeline`, `birdfmt`, `birddeploy`, `bundle`
4. `web`
5. `ci`, `deploy`, `db`
6. `docs`
7. `chore`, `cmd`

## Алгоритм auto-split (уровень файлов)

1. `git status` и `git diff --cached --stat` — зафиксировать полный список staged-файлов.
2. `git reset HEAD` — снять всё из index (working tree не трогать).
3. Для каждой (объединённой) группы по порядку выше:
   - `git add -- <paths…>`
   - Сгенерировать сообщение по правилу conventional-commits.
   - Закоммитить (см. ниже).
4. После всех коммитов — `git status` для проверки.

**Ограничение:** частичный stage одного файла с разной семантикой — предупредить; split по hunk'ам не делать; предложить разнести правки по файлам.

## Создание коммита

Только если пользователь **явно** просил закоммитить. Иначе — только вывести готовые сообщения.

```powershell
git commit -m "$( @'
<type>(<scope>): <summary in English>

<Тело на русском.>
'@ )"
```

В PowerShell для многострочного тела используйте here-string как выше или `-m` для заголовка и `-m` для тела.

Перед коммитом: `git status`, `git diff --cached` для группы — убедиться, что stage соответствует сообщению.

## Генерация текста

По `groups[].diff_excerpt`, `stat`, `files`:

### Шаг A — semver (до выбора type)

| Вопрос | Если «да» → |
|--------|-------------|
| Пользователь получает **новую** возможность? | `feat` (minor) |
| Восстанавливается **ожидаемое** поведение / устранён баг? | `fix` (patch) |
| Только скорость, контракт тот же? | `perf` (patch) |
| Только структура кода/UI, поведение то же? | `refactor` (patch) |

**Follow-up:** правки сразу после `feat` в том же scope без новой возможности → **`fix`**, не `feat` (слова *enhance/improve/refactor* в задаче не делают commit `feat`).

**Запрещено** по умолчанию ставить `feat` для «большого diff» в `web/` — type по **semver impact**, не по объёму.

### Шаг B — type, scope, текст

- **type** — результат шага A, не «chore по умолчанию» и не `feat` из-за слова enhance.
- **scope** — из JSON группы или доминирующий при merge.
- **summary** — конкретный, английский, императив; для `fix` — что **починено** (`fix broken …`, `prevent … loop`).
- **body** — русский: что, зачем, edge cases, breaking impact.

## Вывод пользователю

Для **каждого** коммита:

### 1. Готовое сообщение

```
<type>(<scope>): <summary>

<тело>
```

### 2. Пояснение (RU)

- **Semver impact:** `minor` | `patch` | `none` | `major` — и почему не другой type.
- Риски и impact.
- Split: сколько коммитов и почему.

## Не смешивать

Один commit message — одна primary intent. Не объединять несвязанный `fix` и `feat` в один заголовок.

## Ссылки

- Правило: `.cursor/rules/conventional-commits.mdc`
- Скрипт: `scripts/commit/staged-context.ps1`
- Просмотр групп: `powershell -File scripts/commit/staged-context.ps1 | ConvertFrom-Json`
