diff --git a/.ai/conventions/engineering-workflow.md b/.ai/conventions/engineering-workflow.md index cee2219..8d6df08 100644 --- a/.ai/conventions/engineering-workflow.md +++ b/.ai/conventions/engineering-workflow.md @@ -42,8 +42,15 @@ Issue и PR выбирай `hermes-agent`; незанятую задачу не `refactor`, docs → `docs`, test → `test`, ci → `ci`, chore → `chore`, performance → `perf`. Номер обязателен для новых рабочих веток; короткий английский slug в kebab-case. Ветки `main`/`develop` не место для прямой -разработки. Release/hotfix, обратная синхронизация, Renovate и исторические -ветки подчиняются существующему release/automation процессу, а не переименовываются. +разработки. Release, Renovate и исторические ветки подчиняются существующему +release/automation процессу, а не переименовываются. + +Рабочий PR сливается squash или rebase-merge; merge-коммит не используется. +`main` обновляется только fast-forward из `develop`, поэтому `origin/main` +всегда предок `origin/develop`. Срочный fix — обычная ветка `fix/*` от +`develop`; отдельного hotfix-пути от `main` в обычном процессе нет. +Merge policy, версии, теги и исключения принадлежат +[процессу релиза](../../README.md#релизы-и-ветки). ## Commit Conventions @@ -140,17 +147,19 @@ Frontend/backend запускают их как jobs в едином `verify.yml ## Exceptions and Recovery Срочный hotfix: немедленно завести/найти Issue, затем исправление в -`release/hotfix-` от актуального `main` по существующему release process; -проверки и PR в `main`, затем вернуть результат в `develop`. Связать Issue -в PR и сопровождающем коммите; специальная release-ветка исключена из обычного -шаблона имён. Не отменять Issue или проверки из-за срочности. +обычной ветке `fix/-` от актуального `develop`, PR в `develop` +и релиз по существующему процессу. Отдельного пути от `main` нет: он сделал бы +`main` не-предком `develop`. Если `develop` содержит незаконченную работу, +быстрого фикса нет; обход требует явного решения и описан в +[процессе релиза](../../README.md#ограничения-модели). Связать Issue +в PR и сопровождающем коммите. Не отменять Issue или проверки из-за срочности. Если CI красный или PR устарел — исправить в той же ветке и перепроверить актуальный HEAD; если AC изменились — обновить Issue и при SDD вернуть change к затронутому approval gate. При недоступной Gitea продолжать только анализ. Изменение PR title после зелёного CI: проверить, что check пересчитан; если `edited` trigger у существующих checks отсутствует, reviewer проверяет текущий title вручную. Новые правила не применяются к историческим коммитам -и автоматической обратной синхронизации релиза. +и merge-коммитам, уже находящимся в истории. Примеры: фича с новым поведением — Issue → согласованный OpenSpec change → ветки Apply → PR каждого affected repo → archive → закрытие Issue. Локальный @@ -158,5 +167,5 @@ bugfix — Issue → `fix/124-invalid-balance` → регрессия → PR → Cross-repo контракт — одна Issue с общими AC, `backend` и `frontend` PR с `WealthLedger/main#27` и взаимными ссылками; если независимые acceptance/owners, отдельные связанные Issues. Рефакторинг без изменения поведения — lightweight -`refactor/125-simplify-api-client`. Hotfix — Issue → `release/hotfix-*` от -`main` → PR/release → back-merge в `develop` → закрытие. +`refactor/125-simplify-api-client`. Hotfix — Issue → `fix/*` от `develop` → +PR в `develop` → релиз → закрытие. diff --git a/.ai/conventions/git.md b/.ai/conventions/git.md index 2843ab5..1d85b8b 100644 --- a/.ai/conventions/git.md +++ b/.ai/conventions/git.md @@ -16,7 +16,11 @@ - Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или перенос опубликованных тегов как обычную очистку. Для опасной операции нужны явная необходимость, сохранение данных и соответствующая авторизация. -- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop, - release-конфигурация требуют чтения +- Обычный PR сливается squash или rebase-merge; merge-коммит не используется. + `main` обновляется только fast-forward из `develop`, поэтому + `origin/main` всегда предок `origin/develop`. Проверка: + `git merge-base --is-ancestor origin/main origin/develop`. +- Release, hotfix и release-конфигурация требуют чтения [процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy, версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай. + Отдельной синхронизации `main → develop` в обычном процессе нет. diff --git a/README.md b/README.md index 53aabba..f8d7e04 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,24 @@ T-Bank. Полные команды и ограничения принадлеж `main` хранит принятые релизы, `develop` — интеграцию разработки. Новая задача: отдельная [issue-linked ветка](.ai/conventions/engineering-workflow.md#git-branching) от свежего `develop`, затем PR в `develop`. -В `main` напрямую не пушить. Обычные PR можно squash-merge; релизные PR и -обратную синхронизацию `main` с `develop` сливать с сохранением истории. +В `main` напрямую не пушить. Рабочие PR сливать squash или rebase-merge; +merge-коммиты запрещены. `main` получает изменения только fast-forward из +`develop`, поэтому история обеих веток линейна: + +```text +/- --squash--> develop --ff-only--> main --> tag vX.Y.Z +``` + +Инвариант: `origin/main` всегда предок `origin/develop`. Проверка в любом repo: + +```bash +git fetch -q origin +git merge-base --is-ancestor origin/main origin/develop && echo ok || echo BROKEN +``` + +Инвариант нарушается только осознанным исключением +([Ограничения модели](#ограничения-модели)); после него `develop` возвращают +в согласованное состояние до следующего релиза. Commits и заголовки PR — Conventional Commits. `feat:` повышает minor, `fix:`/`perf:` — patch, `!` или `BREAKING CHANGE:` — major, в том числе до 1.0. @@ -174,8 +190,8 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша ### Подготовка релиза 1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`. - Проверить, что текущий `develop` содержит последний релиз из `main`. - Создать `release/` от проверенного `develop` и выполнить `npm ci`. + Проверить инвариант. Создать `release/X.Y.Z` от проверенного `develop` + и выполнить `npm ci`. 2. Проверить локальные входы и публичные artifacts выпускаемого репозитория. Совместимость развёрнутых участников согласовать отдельно через публичные контракты; CI не скачивает соседние checkout и не требует их SHA. @@ -186,19 +202,20 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша наличие коммитов. Теги и соответствие версии последнему принятому тегу проверяются вручную; уже подготовленную версию повторно не повышать. 4. Проверить diff релизного коммита и штатные gates. Отправить только релизную - ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком + ветку: `git push -u origin HEAD`. Открыть PR в **`develop`** с заголовком `chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root; `verify`, `security`, `commits` в остальных repo) - выполнить **fast-forward-only merge**. Если `main` изменился, сначала - согласовать историю и повторить проверку кандидата; не обходить защиту. -5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный - коммит, версия и lockfile совпадают. На этом SHA создать аннотированный тег: + выполнить **fast-forward-only merge**. Если `develop` изменился, обновить + релизную ветку и повторить проверку кандидата; не обходить защиту. +5. Открыть PR `develop → main` с тем же заголовком и слить + **fast-forward-only**. `main` получает ровно проверенный релизный коммит; + merge-коммит здесь невозможен по определению ff. +6. Получить `main` и убедиться, что его HEAD — проверенный релизный коммит, + версия и lockfile совпадают. На этом SHA создать аннотированный тег: `git tag -a vX.Y.Z -m "Release X.Y.Z"`, затем отправить только этот тег: `git push origin refs/tags/vX.Y.Z`. Повторно release-it - на `main` не запускать: он уже подготовил версию. -6. Через PR `main → develop` вернуть релизный коммит в разработку без squash - и rebase. Убедиться, что `develop` содержит тег. После первого выпуска - `develop` создаётся непосредственно от релизного SHA. + на `main` не запускать: он уже подготовил версию. Обратная синхронизация + не нужна: релизный коммит попал в `develop` на шаге 4. Перед публикацией тегов сравнить local SHA с удалённым `main`; PR и проверки должны относиться к этому кандидату. Опубликованные теги не перемещать. @@ -212,11 +229,20 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша Исторические неконвенциональные commits не переписываются; первый changelog требует просмотра человеком. Дальнейшие версии выбираются по commits. -### Hotfix, проверки и защита +### Ограничения модели -Срочный fix начинается от `main`, проходит проверки и релизный PR из -`release/hotfix-` в `main`, затем обязательно возвращается в `develop`. -Если ломается публичный контракт, major сохраняется и для hotfix. +Freeze релиза не поддерживается: кандидат нельзя заморозить и параллельно +принимать работу в `develop`. Пока открыт релизный PR, `develop` не двигают. + +Срочный fix — обычная ветка `fix/-` от `develop` с последующим +релизом. Отдельного пути от `main` нет: он сделал бы `main` не-предком +`develop` и заблокировал следующий ff. Если `develop` содержит незаконченную +работу, быстрого фикса нет — это цена отказа от release-ветки в `main`. +Исключение требует явного решения: `release/hotfix-` от `main`, +PR в `main`, тег, затем merge `main → develop` для восстановления инварианта. +Такой релиз оставляет merge-коммит в `develop`; cherry-pick из `develop` +в hotfix-ветку порождает дубль коммита при последующем rebase и не +применяется. Если ломается публичный контракт, major сохраняется и для hotfix. Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера, сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному @@ -227,12 +253,17 @@ snapshot. Docs проверяет внутренние ссылки; внешн доступ к root для выпуска другого репозитория не требуется. В Gitea для `main` и `develop` нужны запрет прямого push и force-push, -обязательные проверки: в root только `commits`, в остальных repo — -`verify`, `security`, `commits`; также нужен запрет обхода правил -администратором. Требование отдельного одобрения review не вводится для -разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже -отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу -релиза; их удаление или перенос не входит в обычный процесс. +блокировка устаревшей ветки перед merge, обязательные проверки: в root только +`commits`, в остальных repo — `verify`, `security`, `commits`; также нужен +запрет обхода правил администратором. Требование отдельного одобрения review +не вводится для разработки одним владельцем. Ветка по умолчанию — `develop`; +Renovate тоже отправляет PR в `develop`. Теги `v*` защищены, создание +разрешается владельцу релиза; их удаление или перенос не входит в обычный процесс. + +Линейность истории Gitea отдельной опцией не выражает, её обеспечивают +разрешённые merge styles репозитория: merge-коммиты и rebase с `--no-ff` +выключены, доступны squash, rebase-merge и fast-forward-only. Стиль по +умолчанию — squash, обновление ветки — rebase, ветка после merge удаляется. В root нет собственного test/verify runner и release hooks. Release-it и commitlint используются напрямую. Остальные репозитории сохраняют свои gates.