diff --git a/.cursor/commands/commit-message.md b/.cursor/commands/commit-message.md new file mode 100644 index 0000000..35a84ff --- /dev/null +++ b/.cursor/commands/commit-message.md @@ -0,0 +1,37 @@ +# Сгенерировать commit message (EvoBGP) + +Сгенерируй сообщение коммита для **текущих staged-изменений**. Не выполняй `git commit`, если пользователь явно не просил закоммитить. + +## Обязательный workflow (MUST) + +1. Прочитай скилл [`.cursor/skills/commit-message/SKILL.md`](../skills/commit-message/SKILL.md). +2. Следуй правилу [`.cursor/rules/conventional-commits.mdc`](../rules/conventional-commits.mdc). +3. **Первым вызовом Shell** из корня репозитория: + +```powershell +powershell -NoProfile -File scripts/commit/staged-context.ps1 +``` + +4. Строй текст **только** по JSON stdout (`groups`, `stat`, `diff_excerpt`). Exit `1` → index пуст, сообщи пользователю. +5. **Запрещено** обходить скрипт через один `git diff --cached`. + +## Формат вывода + +Для каждого коммита (при auto-split — по одному блоку): + +``` +(): + +<тело на русском> +``` + +Плюс пояснение (RU): semver impact (`minor`|`patch`|`none`|`major`), почему выбран type, был ли split. + +## Semver (кратко) + +- Новая пользовательская возможность → `feat` (minor) +- Починка ожидаемого поведения / баг → `fix` (patch) +- Follow-up баги после недавнего `feat` в том же scope → **`fix`**, не `feat` +- Только перестройка без нового поведения → `refactor` (none) + +Заголовок — EN, императив, ≤72 символов. Тело — RU. diff --git a/.cursor/rules/conventional-commits.mdc b/.cursor/rules/conventional-commits.mdc index f979d7a..3a3569a 100644 --- a/.cursor/rules/conventional-commits.mdc +++ b/.cursor/rules/conventional-commits.mdc @@ -11,7 +11,13 @@ alwaysApply: false ## Триггеры (применить правило + скилл) -Любой запрос на коммит или сообщение коммита: `commit`, `коммит`, `закоммить`, `git commit`, `commit message`, `conventional commit`, `staged`, «сгенерируй коммит» — в т.ч. если это указано в плане или [AGENTS.md](../../AGENTS.md). +Любой запрос на коммит или сообщение коммита: `commit`, `коммит`, `закоммить`, `git commit`, `commit message`, `conventional commit`, `staged`, «сгенерируй коммит», **`/commit-message`** — в т.ч. если это указано в плане или [AGENTS.md](../../AGENTS.md). + +### Не путать с кнопкой ✨ в Source Control + +Команда **`cursor.generateGitCommitMessage`** (sparkle в поле commit message) **не** читает Rules, Skills и `staged-context.ps1` — только staged diff и история коммитов ([ограничение Cursor](https://forum.cursor.com/t/how-to-set-prompt-for-generate-commit-message/148606)). + +**Замена для EvoBGP:** Agent → `/commit-message` или команда [`.cursor/commands/commit-message.md`](../commands/commit-message.md). ## Обязательный запуск скрипта (MUST) @@ -41,15 +47,74 @@ powershell -NoProfile -File scripts/commit/staged-context.ps1 | type | Когда | Версия | |------|--------|--------| -| `feat` | новая функциональность | minor | -| `fix` | исправление бага | patch | +| `feat` | **новая** пользовательская возможность (раньше нельзя было) | minor | +| `fix` | восстановление **ожидаемого** поведения; баг, регрессия, падение UI | patch | | `perf` | ускорение без смены API | patch | -| `refactor` | реструктуризация без смены поведения | — | +| `refactor` | реструктуризация **без** новой возможности и **без** исправления бага | — | | `docs` | только документация | — | | `test` | тесты | — | | `ci` | CI/CD (`.gitea/`, workflows) | — | | `chore` | обслуживание, deps, `.cursor/` | — | +### Выбор type: semver, а не «красивые слова» + +**Главный вопрос:** что изменится для пользователя после релиза? + +1. Появилось **новое** действие / экран / API / настройка, которых не было → `feat` +2. То, что **должно было работать**, не работало (кнопки, диалоги, сохранение, 500) → `fix` +3. Только перестройка кода или UI на другой паттерн, поведение для пользователя то же → `refactor` +4. Ускорение без изменения контракта → `perf` + +**Не путать с формулировкой diff:** + +| В diff / задаче часто пишут | Неверный type | Верный type, если… | +|-----------------------------|---------------|---------------------| +| enhance, improve, polish UI | `feat` | …только чиним сломанное после прошлого PR → `fix` | +| refactor pages, unify tables | `feat` | …новой возможности нет, лишь перенос на AppDataTable → `refactor` | +| follow-up после feat(web) | `feat` | …исправляем баги того же экрана → `fix` | + +**Follow-up rule:** коммит сразу после `feat` в той же области, который **не добавляет** новую возможность, а устраняет дефект (effect loop, не открывается dialog, confirm не срабатывает) — **`fix`**, не `feat`. + +**Split при смешанном diff:** новая страница/flow → `feat`; отдельным коммитом правки багов → `fix`. Не объединять в один `feat`. + +**Breaking changes** — только `feat!` / `fix!` / `BREAKING CHANGE:` когда пользователь **обязан** менять конфиг, API или привычный workflow. + +### Обязательно в пояснении агенту + +При каждом предложении коммита указать: + +- **Semver impact:** `minor` | `patch` | `none` | `major` +- **Почему не другой type** (одно предложение), если diff большой или формулировка двусмысленная + +Пример неправильно / правильно: + +``` +# Плохо — patch-фикс, minor-bump +feat(web): enhance module entry dialogs and selection handling + +# Хорошо +fix(web): stop effect loop breaking module action buttons + +Исправлен effect_update_depth_exceeded и bind:open у Dialog; кнопки редактирования/удаления снова работают. +``` + +``` +# Плохо — рефакторинг без новой фичи +feat(web): migrate modules list to AppDataTable + +# Хорошо — если не было нового user-facing +refactor(web): migrate modules list to AppDataTable + +Единый паттерн таблиц; поведение списка модулей без изменений. +``` + +``` +# Хорошо feat — действительно новое +feat(web): add module create dialog on /modules + +Диалог создания модуля с POST /v1/modules; раньше создание было только через API. +``` + ## Breaking changes - Заголовок: `feat!` / `fix!` **или** в теле строка `BREAKING CHANGE:` (на английском ключевое слово) + описание impact **на русском**. @@ -96,7 +161,7 @@ powershell -NoProfile -File scripts/commit/staged-context.ps1 <тело RU> ``` -**2. Пояснение (RU):** почему выбран type; риск/impact; был ли split. +**2. Пояснение (RU):** semver impact (`minor`|`patch`|`none`|`major`); почему выбран type; риск/impact; был ли split. ## Примеры diff --git a/.cursor/skills/commit-message/SKILL.md b/.cursor/skills/commit-message/SKILL.md index 270239f..0168b88 100644 --- a/.cursor/skills/commit-message/SKILL.md +++ b/.cursor/skills/commit-message/SKILL.md @@ -2,10 +2,14 @@ name: commit-message description: >- ОБЯЗАТЕЛЬНО при commit, коммит, закоммить, commit message, conventional commit, - staged, semantic-release, «сгенерируй коммит», git commit: ПЕРВЫМ делом Shell — - scripts/commit/staged-context.ps1; затем Conventional Commit (заголовок EN, тело RU). + 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) ## Когда применять (сразу читать этот скилл) @@ -90,9 +94,24 @@ git commit -m "$( @' По `groups[].diff_excerpt`, `stat`, `files`: -- **type** — по смыслу diff (`feat` / `fix` / …), не по умолчанию `chore`. +### Шаг A — semver (до выбора type) + +| Вопрос | Если «да» → | +|--------|-------------| +| Пользователь получает **новую** возможность? | `feat` (minor) | +| Восстанавливается **ожидаемое** поведение / устранён баг? | `fix` (patch) | +| Только скорость, контракт тот же? | `perf` (patch) | +| Только структура кода/UI, поведение то же? | `refactor` (none) | + +**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** — конкретный, английский, императив. +- **summary** — конкретный, английский, императив; для `fix` — что **починено** (`fix broken …`, `prevent … loop`). - **body** — русский: что, зачем, edge cases, breaking impact. ## Вывод пользователю @@ -109,7 +128,7 @@ git commit -m "$( @' ### 2. Пояснение (RU) -- Почему выбран type/scope. +- **Semver impact:** `minor` | `patch` | `none` | `major` — и почему не другой type. - Риски и impact. - Split: сколько коммитов и почему. diff --git a/AGENTS.md b/AGENTS.md index 53c6c70..7412b9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,13 +38,15 @@ ## Коммиты (Conventional Commits) -Если пользователь просит **коммит**, **commit message**, **закоммить**, **git commit** или это следует из плана — **сразу**: +Если пользователь просит **коммит**, **commit message**, **закоммить**, **git commit**, **`/commit-message`** или это следует из плана — **сразу**: 1. Shell: `powershell -NoProfile -File scripts/commit/staged-context.ps1` (первый вызов, до текста коммита). 2. Скилл [.cursor/skills/commit-message/SKILL.md](.cursor/skills/commit-message/SKILL.md) и правило [.cursor/rules/conventional-commits.mdc](.cursor/rules/conventional-commits.mdc). Без вывода скрипта (exit 0) **не** придумывать сообщение коммита. Заголовок — EN, тело — RU; несвязанные области — auto-split (скилл). +**Кнопка ✨ Generate commit message в Source Control** skill/rule **не** использует. Для сообщений по правилам EvoBGP — Agent Chat → **`/commit-message`** (см. [.cursor/commands/commit-message.md](.cursor/commands/commit-message.md)). + ## Команды и среда - Консоль пользователя: **PowerShell**; пути в стиле `deploy\compose`. diff --git a/docs/README.md b/docs/README.md index a1d1259..e16fb33 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,7 +36,16 @@ ### Сообщения коммитов (Cursor) -После `git add` попросите агента: **«сгенерируй коммит по staged»**, **«закоммить»**, **«commit message»** — агент **обязан первым делом** запустить `scripts/commit/staged-context.ps1`, затем скилл [commit-message](../.cursor/skills/commit-message/SKILL.md) (заголовок EN, тело RU, auto-split). Просмотр групп вручную: `powershell -NoProfile -File scripts/commit/staged-context.ps1 | ConvertFrom-Json`. +После `git add`: + +| Способ | Skill + rule + `staged-context.ps1` | +|--------|-------------------------------------| +| Agent → **`/commit-message`** или «сгенерируй коммит по staged» | **Да** | +| Кнопка **✨ Generate commit message** в Source Control | **Нет** (только diff + история; [ограничение Cursor](https://forum.cursor.com/t/how-to-set-prompt-for-generate-commit-message/148606)) | + +Рекомендуемый workflow: Agent Chat → **`/commit-message`** ([команда](../.cursor/commands/commit-message.md), [скилл](../.cursor/skills/commit-message/SKILL.md), [правило](../.cursor/rules/conventional-commits.mdc)). Агент **обязан первым делом** запустить `scripts/commit/staged-context.ps1` (заголовок EN, тело RU, auto-split). + +Просмотр групп вручную: `powershell -NoProfile -File scripts/commit/staged-context.ps1 | ConvertFrom-Json`. ## Репозиторий и CI diff --git a/docs/releasing.md b/docs/releasing.md index da9ca1c..af708b5 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -13,6 +13,8 @@ EvoBGP использует [Conventional Commits](https://www.conventionalcommi Первый релиз при отсутствии git-тегов — **1.0.0**, если есть releasable-коммиты. +**Как не перепутать `feat` и `fix`:** см. раздел «Выбор type: semver, а не «красивые слова»» в [.cursor/rules/conventional-commits.mdc](../.cursor/rules/conventional-commits.mdc). Кратко: `feat` — новая возможность (minor); `fix` — починка ожидаемого поведения (patch); follow-up баги после недавнего `feat` — всегда `fix`, даже если diff большой. + Подробные правила сообщений коммитов: [.cursor/rules/conventional-commits.mdc](../.cursor/rules/conventional-commits.mdc). ## CI-пайплайн (один push в main)