mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
1214 lines
106 KiB
Markdown
1214 lines
106 KiB
Markdown
# Процесс работы над House Plan
|
||
|
||
> **Статус документа: канон** (редакция 2026-08-13, ролевое уточнение
|
||
> 2026-09-13). Решения владельца, на
|
||
> которых он стоит: прямые коммиты в `dev` **без PR** · канон статуса — **метки**,
|
||
> имена английские · лёгкий трек **включён** · автор и ревьюер — независимые
|
||
> агенты/сессии · любой агент может взять любую роль · инфраструктурные задачи
|
||
> входят в общий флоу сразу на `S7-code-review`.
|
||
>
|
||
> **Область действия:** обязателен для владельца и для любого агента. Читается
|
||
> сразу после `docs/SCOPE.md` и `AGENTS.md`, до `docs/STATUS.md`. Живёт в
|
||
> репозитории: до августа 2026 канон лежал только в папке владельца, и свежий клон
|
||
> его не содержал вовсе.
|
||
>
|
||
> **Приоритет источников.** Канонический бэклог — GitHub Issues; статус живёт в
|
||
> метках и больше нигде: Project v2 не используется. При расхождении
|
||
> документации с GitHub побеждает GitHub. При расхождении этого документа с
|
||
> `.github/workflows/*.yml` и `scripts/*` побеждает **фактическая автоматизация**:
|
||
> она исполняется, а описание — нет. Расхождение при этом не игнорируется, а
|
||
> заводится issue с меткой `process`.
|
||
>
|
||
> При расхождении процесса и привычки побеждает процесс.
|
||
|
||
---
|
||
|
||
## 1. Основное правило
|
||
|
||
**Изменение продуктового кода без issue запрещено.** Код меняется только тогда,
|
||
когда issue существует и находится в статусе «Готово к разработке» или дальше.
|
||
Исключения — только §11, и каждое оставляет след.
|
||
|
||
Правило работает лишь при точной границе «продуктового кода», иначе спор
|
||
переносится на границу:
|
||
|
||
| Класс | Что входит | Нужен ли issue |
|
||
|---|---|---|
|
||
| **A. Продукт** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, `src/i18n/*.json`, `custom_components/**/translations/*` | **Да, обязательно.** Только из «Готово к разработке» или дальше |
|
||
| **B. Гейты и инструменты** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, весь `.github/**`, `.githooks/**`, `rollup.config.mjs`, `tsconfig*.json`, `package.json`, `package-lock.json`, `pytest.ini`, `.gitignore`, `.gitattributes` | **Да.** Может использовать issue того изменения, которое покрывает; самостоятельная работа над гейтом получает свой issue (тип `tech-debt`) |
|
||
| **C. Документация** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md`, `CONTRIBUTING.md`, `PROCESS*.md`, `LICENSE`, `(CODE\|SPEC)-REVIEW-*.md` | Документирование A/B в том же коммите — часть DoD своего issue. Самостоятельная работа над документацией — свой issue |
|
||
| **D. Сгенерированное** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` (копия стенда `demo/srv/assets/houseplan-card.js` с #255 не коммитится вовсе) | Никогда не меняется само по себе. Коммит **только** класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью |
|
||
|
||
Практический смысл таблицы: «я только поправил тест» и «я только пересобрал
|
||
бандл» перестают быть лазейками.
|
||
|
||
Классы неупорядочены, но при пересечении путей **D сильнее A**: собранный бандл
|
||
лежит внутри `custom_components/houseplan/frontend/`, и без этого правила он
|
||
считался бы продуктовым исходником.
|
||
|
||
**Инфраструктурная задача использует ускоренный вход в общий флоу** (решение
|
||
владельца 2026-09-13, issue #562). Признак механический: **ни одного файла класса
|
||
A**. Любой агент может сразу реализовать её по issue и в ветке
|
||
`issue/<NN>-<slug>`, без аналитики, ТЗ, ревью ТЗ и статусов `S1`…`S6`. Когда
|
||
материал готов, локальные гейты зелёные и ветка запушена, исполнитель ставит
|
||
`S7-code-review`. Дальше действует тот же контроллер, что для продуктового кода:
|
||
зелёное ревью сливает проверенный материал в `dev` и ставит `S8-merged`, а
|
||
замечания или неудавшееся слияние возвращают задачу в `S6-in-progress`; после
|
||
исправлений она снова идёт в `S7-code-review`.
|
||
|
||
Отсутствие ТЗ не означает отсутствие проверки. Для инфраструктуры обязательны
|
||
issue, терминальные трейлеры, соразмерные изменению зелёные гейты, хендофф с
|
||
доказательствами и независимое код-ревью. Модель или имя агента процессом не
|
||
предписываются.
|
||
|
||
Задача, задевающая класс A хотя бы одним файлом, инфраструктурной **не
|
||
является** и идёт полным флоу. «В основном инфраструктурная» не бывает: иначе
|
||
это дорога, по которой продуктовые правки минуют ревью. Признак задан через
|
||
класс файлов, а не через самоощущение исполнителя, именно поэтому.
|
||
|
||
---
|
||
|
||
## 2. Жизненный цикл
|
||
|
||
Восемь рабочих статусов и два служебных. Полный маршрут ниже относится к
|
||
продуктовым задачам. Фазы тестирования в цикле сознательно
|
||
**нет**: найденные позже дефекты заводятся отдельными issue и проходят цикл
|
||
заново. Issue закрывается после выпуска беты.
|
||
|
||
```
|
||
S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||
→ S6-in-progress → S7-code-review ⟲ → S8-merged → закрыт при выпуске беты
|
||
|
||
служебные: blocked (поверх статуса) rejected (закрыт)
|
||
⟲ — возврат на правки, не более 4 циклов (§4), на лёгком и коротком треке 2
|
||
короткий трек (`trivial`, §5.1) идёт S2-analysis → S5-ready, минуя S3 и S4
|
||
инфраструктурный трек (§1): без S → S7-code-review ⟲ S6-in-progress → S8-merged
|
||
```
|
||
|
||
Переходы `S4-spec-review` и `S7-code-review` выполняются **автоматически**: метка
|
||
порождает событие, событие запускает ревью (§10.4). Остальные ставит исполнитель.
|
||
|
||
### 2.1 Новое — заведение задачи
|
||
|
||
- **Кто:** любой — владелец, агент, пользователь (Telegram, GitHub).
|
||
- **Вход:** проблема в пользовательских терминах; как проявляется или зачем нужно.
|
||
Решение **не требуется** и не приветствуется.
|
||
- **Запрещено:** ставить приоритет, оценивать, писать ТЗ, начинать код.
|
||
|
||
### 2.2 Аналитика и оценка
|
||
|
||
Задача разбирается — и разобранная **сама идёт дальше**. Умолчание изменено
|
||
решением владельца 2026-08-14: раньше аналитика ждала подтверждения по каждому
|
||
пункту, и большинство ожиданий ничего не меняло — issue в основном описаны
|
||
однозначно.
|
||
|
||
- **Кто:** агент-аналитик. Владелец не утверждает переход — он правит асинхронно.
|
||
- **Чек-лист**, результат — комментарием в issue:
|
||
1. дубликаты проверены (ссылки на похожие issue);
|
||
2. в скоупе по `docs/SCOPE.md` и `docs/TOUCH-SUPPORT.md`;
|
||
3. **пользовательская ценность 1–10** и **ценность для разработки** — что
|
||
упрощает или разблокирует;
|
||
4. **сложность и риск 1–10** — трудоёмкость плюс вероятность задеть смежное;
|
||
5. приоритет **P1/P2/P3**;
|
||
6. тип: баг / фича / техдолг;
|
||
7. затронутые поверхности (модули, диалоги, бэкенд, i18n);
|
||
8. трек — **по умолчанию `small`** (§5). Если задача идёт полным треком,
|
||
называется критерий §5, который она не проходит: «обычный трек» без
|
||
названного критерия обоснованием не является.
|
||
- **Оценки и приоритет ставятся метками сразу, согласие не запрашивается.**
|
||
Комментарий аналитики — уведомление, а не запрос: **молчание владельца —
|
||
согласие**, несогласие он выражает правкой меток или комментарием, и это не
|
||
останавливает работу. Право отклонить задачу (`rejected`) остаётся за
|
||
владельцем на любой стадии.
|
||
- **Вопросов владельцу на этом этапе нет.** Единственный класс вопросов, который
|
||
вообще задаётся владельцу, — продуктовые (§7.1: что человек видит или делает,
|
||
объём видимых изменений), и их место — этап ТЗ, пачкой, с вариантами по
|
||
умолчанию и `blocked`. Вопрос, который можно отложить до ТЗ, не задаётся в
|
||
аналитике; вопрос, не блокирующий написание ТЗ, не задаётся вовсе — вместо
|
||
него в ТЗ пишется блок принятых предположений.
|
||
- **Выход:** `S3-spec` — переход выполняет сам аналитик, не дожидаясь ответа.
|
||
Либо, при явном конфликте со `SCOPE.md`, — предложение отклонить с причиной:
|
||
это единственный случай, когда аналитика останавливается и ждёт владельца.
|
||
|
||
### 2.3 ТЗ в работе — написание ТЗ
|
||
|
||
- **Кто:** автор ТЗ, назначает себя. Статус означает «занято».
|
||
- **Артефакт:** **тело issue**, раздел `## ТЗ` (решение владельца 2026-09-10,
|
||
#517). Файл в `docs/specs/` не создаётся ни на одном треке: каталог — архив
|
||
ТЗ до этой даты, и задачи, у которых файл уже есть, доживают по старой схеме.
|
||
Доказуемость («вердикт вынесен на этом тексте») держит конвейер: в блок якорей
|
||
документа ревью пишется `sha256` нормализованного тела, и правка ТЗ после
|
||
зелёного ревью ТЗ приходит ревьюеру кода находкой, а не тишиной.
|
||
- **Выход:** полная первая редакция по §7.
|
||
|
||
### 2.4 ТЗ на ревью
|
||
|
||
- **Ревьюер ≠ автор.** Ревьюер получает issue и ТЗ, без устных пояснений автора.
|
||
Его задача — не согласиться, а найти, где ТЗ не выполнимо или не проверяемо.
|
||
- **Артефакт:** `docs/reviews/SPEC-REVIEW-<NN>-r<N>.md`, вердикт
|
||
зелёный / жёлтый / красный. Лёгкий трек — комментарий в issue.
|
||
- **High-находки блокируют.** Medium **в скоупе задачи** чинится в текущем
|
||
issue: без High это жёлтый вердикт, автор правит ТЗ, фикс проходит повторный
|
||
цикл. Medium **вне скоупа** — отдельный issue: чужой скоуп в этой задаче не
|
||
правится. «Оставили в тексте ревью» не считается закрытием ни для одной
|
||
(решение владельца 2026-08-19, #202: отдельный issue дороже правки на месте).
|
||
Low либо правится, либо снимается решением ревьюера с записью.
|
||
- **Выход:** «Готово к разработке» либо возврат в «ТЗ в работе» — не более
|
||
4 циклов (§4). Второй и последующие циклы разбираются по дельте (§2.10).
|
||
|
||
### 2.5 Готово к разработке (DoR)
|
||
|
||
Не работа, а **очередь**: единственный статус, из которого можно трогать код.
|
||
Все пункты обязательны:
|
||
|
||
- ТЗ существует, ревью ТЗ зелёное, ссылки issue ↔ ТЗ на месте;
|
||
- **AC1…ACn** — пронумерованные проверяемые критерии приёмки; у каждого указано,
|
||
чем он доказывается: `unit` / `backend` / `smoke` / `golden` / «ревью кода»;
|
||
- перечислены затронутые файлы и модули;
|
||
- i18n: ключи en + ru перечислены;
|
||
- миграция и compatibility-поля решены по `docs/CONFIG-COMPATIBILITY.md`;
|
||
- влияние на производительность и бюджеты названо (или явно «нет»);
|
||
- влияние на touch по `docs/TOUCH-SUPPORT.md` (View и киоск — блокирующие);
|
||
- release-артефакты названы: changelog RU+EN, документация, golden/скриншоты,
|
||
performance/security — либо явное «нет»;
|
||
- **откат**: как выключить или вернуть назад (флаг Labs, обратная миграция);
|
||
- открытых продуктовых вопросов нет; риски перечислены.
|
||
|
||
Если хоть один пункт не выполнен — статус не «Готово к разработке», как бы ни
|
||
хотелось начать.
|
||
|
||
### 2.6 В разработке — реализация
|
||
|
||
- **Занятие (claim):** назначить себя, поставить метку, комментарий
|
||
«Взял: <роль> · сессия <id> · ветка `issue/<NN>-<slug>`».
|
||
- **WIP-лимиты:** не более **1** issue в «В разработке» на исполнителя, не более
|
||
**3** одновременно на цикл релиза, не более **2** в «Код-ревью».
|
||
- **Трассируемость:** ветка `issue/<NN>-<slug>`; каждый коммит несёт трейлеры
|
||
`Issue: #<NN>` и `User-Visible: yes|no`.
|
||
- **Автотесты — часть реализации, а не отдельная фаза.** Каждый AC, помеченный
|
||
`unit`/`backend`/`smoke`/`golden`, получает свою проверку здесь же.
|
||
«Тестирование вне жизненного цикла» означает отсутствие фазы ручного
|
||
тестирования, а не отсутствие тестов.
|
||
- **Скоуп не расширяется.** Найденное по пути становится новым issue в «Новое».
|
||
Если находка блокирует — текущий issue уходит в «Заблокировано» со ссылкой.
|
||
Попутных правок «раз уж я здесь» не бывает.
|
||
- **Документация — в том же коммите,** что и поведение (действующая политика
|
||
`docs/STATUS.md`): changelog RU+EN для пользовательского, `STATUS.md` для
|
||
состояния, `DEVELOPMENT.md` для новых грабель, `ARCHITECTURE.md` для дизайна.
|
||
- **Выход:** локальный гейт зелёный (§8), хендофф-комментарий (§7.2).
|
||
|
||
### 2.7 Код-ревью
|
||
|
||
- **Ревьюер ≠ исполнитель**, свежая сессия без контекста реализации.
|
||
- **Артефакт:** `docs/reviews/CODE-REVIEW-<tag|NN>-r<N>.md` в действующем
|
||
формате: скоуп, как проверялось (таблица гейтов с результатами), находки
|
||
High/Medium/Low с воспроизведением, что проверено и корректно, чего не проверял.
|
||
- **Ревьюер отвечает за AC.** Раз ручного тестирования в цикле нет, именно ревью
|
||
кода отвечает на вопрос «оно вообще работает»: каждый AC либо доказан
|
||
автотестом — и ревьюер убедился, что **тест умеет падать**, — либо разобран по
|
||
коду с явной записью «проверено чтением, не исполнением».
|
||
- **Защитный AC доказывается таблицей «чем краснеет» (#435).** Для каждого AC,
|
||
заявляющего защиту — валидация, гард, лимит, отказ, инвариант, — в документе
|
||
ревью обязательна строка из трёх столбцов: **AC · чем доказан** (точная
|
||
команда или имя теста) **· чем краснеет** — мутация, снятая защита или
|
||
отрицательная проба, с результатом прогона. Пустой третий столбец — находка
|
||
Medium, а не примечание.
|
||
|
||
«Тест умеет падать» без названной мутации и её вывода доказательством не
|
||
является. Аудит v1.71.0-beta.1 нашёл пять контрактов #51 и #423, где тест
|
||
оставался зелёным на снятой защите; все пять прошли код-ревью как доказанные,
|
||
а два теста были записаны в закрытие coverage-ratchet под именами, обещавшими
|
||
то, чего они не проверяли (#430).
|
||
|
||
Мутант в `scripts/mutation-gate.mjs` обязателен, когда защита живёт в
|
||
продуктовом коде и проверяется дорогим гейтом (смок, бэкенд, golden): там
|
||
ревьюер не воспроизведёт отрицательный прогон второй раз. Для чистых юнитов
|
||
достаточно прогона со снятой защитой, приведённого в документе.
|
||
|
||
Считаются **защитные AC без названного свидетеля**, а не мутанты на
|
||
подсистему: у #421 мутанты были, и дыра всё равно проехала. «Сколько мутантов
|
||
принесла задача» остаётся признаком — у #423 их ноль, и именно у #423 нашёлся
|
||
тест, спрашивавший регулярку, находит ли она подстроку, которую сам же и
|
||
вырезал.
|
||
|
||
Правило не распространяется на AC, не заявляющие защиту (расположение, текст,
|
||
формат вывода): там свидетель — обычное сравнение ожидаемого с фактическим, и
|
||
третий столбец превратился бы в ритуал. И не отменяет «проверено чтением»:
|
||
тогда во втором столбце стоит «чтением», а не имя теста, и читатель ревью
|
||
видит разницу.
|
||
- **High блокируют.** Medium **в скоупе задачи** чинится в текущем issue:
|
||
без High это жёлтый вердикт и возврат автору, фикс проходит повторный цикл.
|
||
Medium **вне скоупа** — отдельный issue (#202).
|
||
- **Вердикт привязан к SHA (#312).** Все числа и факты отчёта сверяются с
|
||
`git rev-parse HEAD` непосредственно перед подведением итогов, а не с SHA,
|
||
зафиксированным в начале разбора: во время ревью в ветку может прилететь
|
||
fix-up. Серверный стопор — шаг слияния конвейера сверяет вершину ветки с
|
||
SHA материала ревью (допустим только собственный doc-коммит публикации
|
||
поверх) и при расхождении отменяет слияние с возвратом в `S6-in-progress`.
|
||
- **Выход:** очередь на пре-релиз либо возврат в «В разработке», не более
|
||
4 циклов (§4). Второй и последующие циклы разбираются по дельте (§2.10).
|
||
|
||
### 2.8 Закрытие после выпуска беты
|
||
|
||
- **Вход:** изменение вошло в опубликованную бету/RC, CI Validate зелёный на
|
||
**точном SHA** тега (промоушен-правило: ни одна фича не попадает в стабильный
|
||
релиз, не побывав в бете).
|
||
- **Закрывает** релиз-менеджер, не исполнитель. Комментарий закрытия: тег беты,
|
||
ссылка на прогон CI, ссылка на бюллетень changelog.
|
||
- **Стабильный релиз статусов не двигает** — issue уже закрыты; релизный коммит
|
||
promotion-only, changelog ссылается на закрытые issue.
|
||
- **Что приходит потом:** дефект, найденный на стенде, дома или пользователем, —
|
||
**новый issue** типа «баг» со ссылкой на исходный. Исходный не переоткрывается.
|
||
|
||
### 2.9 Заблокировано / Отклонено
|
||
|
||
- **Заблокировано:** обязательна ссылка на блокирующий issue или внешнюю причину
|
||
и дата пересмотра. Без причины статус не ставится.
|
||
- **Отклонено:** закрытие с записанной причиной (вне скоупа, дубликат, цена не
|
||
оправдана). Тихое закрытие без причины запрещено.
|
||
|
||
### 2.10 Повторный раунд ревью — объём по дельте
|
||
|
||
Решение владельца 2026-08-19 (issue #214). Относится и к ревью ТЗ, и к
|
||
код-ревью, начиная со второго цикла.
|
||
|
||
**Предмет повторного раунда — дельта, а не задача целиком.** Раньше объём
|
||
разбора не был оговорён, промпт ревьюера для всех раундов был одинаковым, и
|
||
повторный цикл заново выводил продуктовую рамку и перепроверял AC, которых
|
||
правка не касалась: r2 по #150 стоил полного прогона конвейера ради одной
|
||
строки в тестовой фикстуре.
|
||
|
||
Порядок:
|
||
|
||
1. найти вердикт предыдущего раунда и **материал, на котором он получен**.
|
||
Материал объявлен блоком «Материал раунда» в конце документа предыдущего
|
||
раунда: конвейер дописывает туда SHA ветки, **дерево** материала и **блоб**
|
||
каждого ТЗ вместе с командами поиска (issue #416). Блок машинный — править
|
||
его руками не нужно и не следует;
|
||
2. объявить дельту: `git diff <тот SHA>..HEAD` для кода, дифф файла ТЗ либо тела
|
||
issue для этапа ТЗ.
|
||
|
||
**Если SHA не резолвится — это не находка, а обычное дело.** Ветку задачи
|
||
между раундами перебазируют, сквошат или удаляют, и SHA умирает: по корпусу
|
||
ревью таких объявлений 98 из 804. Материал в этом случае берётся по якорям,
|
||
которые ребейз не меняет, потому что адресуются содержимым:
|
||
|
||
```
|
||
git log --all --format='%H %T' | grep <дерево>
|
||
git log --all --find-object=<блоб> -- <путь к ТЗ>
|
||
```
|
||
|
||
Находкой остаётся другое: **SHA, мёртвый уже в момент публикации отчёта** —
|
||
он означает, что значение сняли до `amend` или `rebase` и не сверили перед
|
||
выводом, как требует §7.2. Это отличие не теоретическое: на #403 оба
|
||
источника, автор и ревьюер, независимо назвали один и тот же осиротевший
|
||
SHA, и следующий раунд восстанавливал коммит по содержимому диффа руками
|
||
(issue #413). Конвейер теперь такую публикацию останавливает сам;
|
||
3. по каждой находке предыдущего раунда показать, **чем именно она закрыта** —
|
||
строкой кода или текста, а не заявлением автора;
|
||
4. заново проверять только те AC, чьё доказательство дельта задевает;
|
||
5. **раздел «Унаследовано из r<N−1>»** обязателен: что принято без повторной
|
||
проверки, со ссылкой на документ того раунда и его материал. Без перечня
|
||
сокращение превращается в молчаливое доверие.
|
||
|
||
Дешёвые гейты (`typecheck`, `test`, `build` со сверкой копий бандла) гоняются в
|
||
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
|
||
|
||
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
|
||
`dev` (после ребейза это другой код, §7.2), смена контракта поведения, задета
|
||
новая подсистема, либо объём дельты сопоставим с исходной задачей.
|
||
|
||
Сокращается объём **разбора, а не строгость**: правка по замечанию способна
|
||
сломать AC, который предыдущий раунд признал выполненным — так появилась
|
||
регрессия #102. Граница не «только находки», а «находки плюс всё, до чего
|
||
дотягивается дельта».
|
||
|
||
---
|
||
|
||
## 3. Правила
|
||
|
||
Продолжение черновика владельца. Каждое правило проверяемо — глазами или машиной.
|
||
|
||
1. **Никаких изменений в код, если нет issue** и он не помечен «Готово к
|
||
разработке» или дальше.
|
||
2. **Issue не может быть взят в разработку**, пока у него нет ТЗ с зелёным ревью,
|
||
пронумерованных AC с указанием доказательства и назначенного исполнителя.
|
||
3. **Issue не может быть взят дважды.** Занятие фиксируется назначением, меткой и
|
||
комментарием с именем ветки. У одного исполнителя одновременно не более одного
|
||
issue в разработке.
|
||
4. **Статус меняется до действия, а не после.** Взял — поставил метку; отдал на
|
||
ревью — поставил метку. Метка, поставленная задним числом, — дефект процесса.
|
||
5. **Ровно одна метка статуса** на issue в любой момент. Ноль или две — дефект,
|
||
еженедельная гигиена его показывает.
|
||
6. **Автор не ревьюит своё** — ни ТЗ, ни код. Никто не переводит свою работу через
|
||
ревью-гейт.
|
||
7. **Ревью возвращает не более 4 раз.** Пятый заход — решение владельца: разделить,
|
||
отклонить или арбитраж (§4).
|
||
8. **High блокирует. Medium в скоупе чинится в текущем issue** (без High —
|
||
жёлтый вердикт и повторный цикл); Medium вне скоупа становится отдельным
|
||
issue (#202). Low либо правится, либо снимается решением ревьюера с записью
|
||
в документе.
|
||
9. **Скоуп не расширяется.** Всё найденное вне ТЗ — новый issue, а не попутная
|
||
правка. Блокирующая находка отправляет текущий issue в «Заблокировано».
|
||
10. **Каждый коммит класса A и B несёт трейлер `Issue: #NN`**, ветка называется
|
||
`issue/NN-slug`, а `User-Visible: yes` требует правок в **обоих** changelog в
|
||
том же коммите. После `cherry-pick -x` служебная строка `(cherry picked
|
||
from ...)` должна оставаться выше финального блока трейлеров: перед push
|
||
проверяем порядок через `git show -s --format=full HEAD`.
|
||
11. **Документация — в том же коммите, что поведение.** Отдельным «допишу потом»
|
||
коммитом документация не бывает.
|
||
12. **Сгенерированное не коммитится само по себе.** Только релизный промоушен или
|
||
принятие эталонов со ссылкой на прогон CI.
|
||
13. **Golden-эталоны принимаются только** `npm run golden:accept -- --reviewed` по
|
||
полному Linux-артефакту. Принятие ради зелёного CI — нарушение процесса.
|
||
14. **Issue закрывается после выпуска беты** с зелёным CI на точном SHA. Не
|
||
раньше, не «по факту наличия кода», не исполнителем.
|
||
15. **Закрытый issue не переоткрывается.** Новый дефект — новый issue со ссылкой.
|
||
16. **Стабильный релиз — promotion-only:** версии, сгенерированные бандлы,
|
||
changelog и release-метаданные. Продуктового кода там нет.
|
||
17. **История `dev` не перезаписывается.** На неё ссылаются теги. Нарушение
|
||
исправляется следующим коммитом плюс issue с меткой `process` — не
|
||
force-push'ем.
|
||
18. **AC доказывает автотест или запись ревьюера.** Фразы «проверил локально, всё
|
||
работает» в процессе не существует: либо тест, который умеет падать, либо
|
||
честное «проверено чтением, не исполнением».
|
||
19. **Параллельных бэклогов нет.** Планы, разборы и приоритеты живут в issue;
|
||
файловые отчёты — разовые и датированные.
|
||
20. **Аварийный хотфикс — только решением владельца** и только по §11.2.
|
||
|
||
---
|
||
|
||
## 4. Лимит циклов ревью: 4
|
||
|
||
Оба ревью-гейта возвращают задачу на правки не более **4 раз**.
|
||
|
||
- **Что считается циклом:** отправка на ревью → вердикт с блокирующими находками
|
||
→ возврат. Уточняющий вопрос без вердикта циклом не считается.
|
||
- **Зелёный вердикт цикла не образует** и бюджет не тратит (решение владельца
|
||
2026-08-20, issue #227): он ничего не вернул на правки. Практический случай —
|
||
зелёное ревью, слияние которого не удалось: конвейер сам предписывает ребейз и
|
||
возврат метки, и этот заход не должен наказываться. Раньше счётчик считал все
|
||
вердикты подряд, и на #225 последовательность жёлтый → зелёный → ребейз дала
|
||
`review-4` на задаче с зелёным ревью и зелёным CI.
|
||
- **Заход и цикл — разные величины.** Заход — сколько раз ревью отработало; он
|
||
виден в имени документа (`-r1`, `-r2`, …) и нужен, чтобы два документа не
|
||
затёрли друг друга. Цикл — единица бюджета §4. Заходов законно бывает больше,
|
||
чем циклов, поэтому порог проверки 7 в `scripts/process-gate.mjs` выше лимита
|
||
циклов (шесть документов = четыре цикла плюс два ребейза).
|
||
- Метка `review-4` ставится, когда исчерпан **бюджет циклов**; конвейер снимать
|
||
её не вправе — это решение владельца. Если бюджет пересчитан и оказался ниже
|
||
лимита, конвейер сообщает пересчёт, но метку не трогает.
|
||
- **Исчерпание лимита — не «пятая попытка», а разбор.** Задача уходит владельцу,
|
||
решение одно из трёх:
|
||
1. **разделить** — issue закрывается как «заменён», вместо него 2–3 меньших с
|
||
ясным скоупом (частый настоящий диагноз: ТЗ было слишком большим);
|
||
2. **отклонить** — цена решения оказалась выше ценности;
|
||
3. **арбитраж владельца** — владелец фиксирует решение в issue, оно принимается
|
||
как есть; несогласие ревьюера записывается, но не блокирует.
|
||
- **Граница между «циклом» и «новым багом»:** до закрытия беты находка ревьюера —
|
||
возврат на правки; после закрытия — новый issue. Иначе лимит 4 обходится
|
||
заведением issue вместо возврата.
|
||
- Для лёгкого трека лимит ревью ТЗ — **2** цикла: задача на три часа, которую
|
||
переписывают трижды, лёгкой не была.
|
||
|
||
---
|
||
|
||
## 5. Лёгкий трек (метка `small`) — путь по умолчанию
|
||
|
||
Умолчание изменено решением владельца 2026-08-27, issue #338. Прежде полный трек
|
||
был бесплатен, а выбор лёгкого требовал обоснования. Фактическая цена: **2.9
|
||
ревью-документа на задачу** в среднем и до шести на одну issue (#329, #316,
|
||
#290) — при том что Medium-находки всё равно чинятся в той же задаче, без
|
||
отдельного цикла.
|
||
|
||
**Порог не изменился.** Критерии ниже те же и по-прежнему обязательны все
|
||
одновременно. Изменилась сторона доказательства: теперь обосновывается не выбор
|
||
лёгкого трека, а отказ от него — в `S2-analysis` называется критерий, который
|
||
задача не проходит. Полный трек остаётся тем, чем был, для геометрии, миграций
|
||
конфига и публичных контрактов: там критерии нарушаются сами, и назвать
|
||
нарушенный несложно.
|
||
|
||
Инверсия умолчания не отменяет ничего из §5 ниже и ничего из §4: бюджет четырёх
|
||
циклов, арбитраж владельца, обязательность ТЗ на полном треке и правило «ревью
|
||
до мержа» остаются как были. Меняется только стоимость пути по умолчанию.
|
||
|
||
**Критерии — все одновременно; нарушенный называется явно:**
|
||
|
||
- сложность и риск ≤ 3;
|
||
- одна поверхность (один диалог, один модуль, один эндпоинт);
|
||
- нет миграции конфига и новых compatibility-полей;
|
||
- нет нового UX-контракта — меняется поведение в рамках уже описанного;
|
||
- нет влияния на производительность и на touch-контракт.
|
||
|
||
**Что упрощается:**
|
||
|
||
- ТЗ короче: проблема · контракт · AC1…ACn с доказательством · откат
|
||
(в теле issue, как и на полном треке с 2026-09-10);
|
||
- ревью ТЗ — комментарий второго агента, отдельный документ не нужен;
|
||
- лимит ревью ТЗ — 2 цикла.
|
||
|
||
**Что не упрощается:** issue, оценка, статусы, трейлеры коммитов, changelog,
|
||
**код-ревью и его документ**, закрытие после беты. Код-ревью не пропускается
|
||
никогда — именно оно в этом процессе заменяет тестирование. Единственное
|
||
исключение — починка упавшего предрелизного гейта, §11.4.
|
||
|
||
Если по ходу выясняется, что критерий нарушен (появилась миграция, задело второй
|
||
модуль) — метка `small` снимается, issue возвращается в `S3-spec` и получает
|
||
нормальный файл ТЗ. Это не провал, это ранняя диагностика.
|
||
|
||
### 5.1 Короткий трек (метка `trivial`)
|
||
|
||
Решение владельца 2026-08-13, issue #128. Лёгкий трек делает ТЗ дешёвым; короткий
|
||
обходится без него совсем.
|
||
|
||
**Маршрут:** `S1-new` → `S2-analysis` → `S5-ready` → `S6-in-progress` →
|
||
`S7-code-review` → `S8-merged`. Стадии `S3-spec` и `S4-spec-review` пропускаются.
|
||
|
||
`S2-analysis` остаётся: это комментарий, а не прогон CI, и именно там владелец
|
||
решает приоритет и ценность. AC пишет автор в теле issue при переводе в
|
||
`S5-ready` — до перехода, иначе ревьюеру нечего будет сверять.
|
||
|
||
**Критерии, все обязательны:**
|
||
|
||
- тип `bug`;
|
||
- правка ограничена одной поверхностью, нового UX-контракта нет;
|
||
- нет миграции конфига, новых ключей i18n, влияния на перф и touch;
|
||
- AC выражаются тремя проверяемыми утверждениями или меньше;
|
||
- **ожидаемое поведение уже зафиксировано** — в `docs/USER-GUIDE.ru.md`, в
|
||
каноническом документе подсистемы либо однозначно в самом отчёте. Решать нечего.
|
||
Если есть что решать, это `S3-spec`, и никакая экономия этого не отменяет.
|
||
|
||
Метка ставится в `S2-analysis` вместе с остальными оценками, одним комментарием,
|
||
где владелец утверждает и приоритет.
|
||
|
||
**Что не упрощается:** issue, оценка, статусы, трейлеры, changelog и **код-ревью**.
|
||
Лимит циклов код-ревью — 2, как на лёгком треке.
|
||
|
||
Если по ходу выясняется, что критерий нарушен, метка снимается и issue уходит в
|
||
`S3-spec` за нормальным ТЗ. Как и на лёгком треке, это не провал, а ранняя
|
||
диагностика.
|
||
|
||
**Чем этот трек опасен.** Он убирает единственное место, где решение проверялось
|
||
до написания кода. Признак «решать нечего» держит всю конструкцию, и его нельзя
|
||
подтверждать ощущением — только ссылкой на уже зафиксированное поведение.
|
||
|
||
---
|
||
|
||
## 6. Роли
|
||
|
||
Один агент может исполнять несколько ролей в разных issue, но **не две роли в
|
||
одном артефакте**.
|
||
|
||
| Роль | Делает | Не имеет права |
|
||
|---|---|---|
|
||
| Аналитик | разбор, оценки, поверхности | окончательно ставить приоритет |
|
||
| Автор ТЗ | раздел `## ТЗ` в теле issue | ревьюить своё ТЗ |
|
||
| Ревьюер ТЗ | `docs/reviews/SPEC-REVIEW-NN-rN.md` | править ТЗ вместо автора |
|
||
| Разработчик | код, автотесты, документация, changelog | ревьюить свой код, принимать golden |
|
||
| Ревьюер кода | `docs/reviews/CODE-REVIEW-*-rN.md`, проверка AC | править продуктовый код |
|
||
| Релиз-менеджер | пре-релиз, стабильный релиз, закрытие issue | добавлять код в релизный коммит |
|
||
| Владелец | приоритет, ценность, скоуп, отклонение, арбитраж, хотфикс | — |
|
||
|
||
**Правило разделения:** ревьюер работает состязательно. Ему передаётся тег или
|
||
диапазон коммитов и ТЗ — не рассказ автора о том, как всё хорошо.
|
||
|
||
**Роли не закреплены за моделями или именами агентов** (решение владельца
|
||
2026-09-13, issue #562). Codex, Claude или любой другой доступный агент может быть
|
||
аналитиком, автором ТЗ, разработчиком, автором инфраструктурной задачи или
|
||
релиз-инженером по прямой команде владельца.
|
||
|
||
Разделение относится к артефакту: **автор и ревьюер — разные агенты/сессии**.
|
||
Модель может совпадать, но ревьюер начинает без контекста реализации и не ставит
|
||
вердикт собственной работе. Ревью ТЗ и код-ревью также идут в независимых
|
||
сессиях: ревьюер кода не должен приходить с контекстом обсуждения ТЗ.
|
||
|
||
Владелец сохраняет исключительные решения о приоритете, ценности, продуктовом
|
||
скоупе, отклонении, арбитраже, закрытии issue и команде на выпуск.
|
||
|
||
---
|
||
|
||
## 7. Артефакты и трассируемость
|
||
|
||
### 7.1 Цепочка
|
||
|
||
```
|
||
issue #NN
|
||
↔ ТЗ тело issue, раздел `## ТЗ` (хеш тела — в якорях ревью)
|
||
↔ ревью ТЗ docs/reviews/SPEC-REVIEW-NN-rN.md (или комментарий при `small`)
|
||
↔ ветка issue/NN-slug
|
||
↔ коммиты трейлеры Issue: #NN · User-Visible: yes|no
|
||
↔ ревью кода docs/reviews/CODE-REVIEW-<tag|NN>-rN.md
|
||
↔ changelog бюллетень RU+EN со ссылкой на #NN
|
||
↔ бета тег, зелёный CI на точном SHA → закрытие
|
||
```
|
||
|
||
Обязательные разделы ТЗ: **сценарий** · **что человек увидит до и после** ·
|
||
проблема · скоуп и **не-скоуп** · контракт поведения · UX · модель данных и
|
||
миграция · i18n · критерии приёмки AC1…ACn с указанием доказательства · план
|
||
автотестов · риски · откат · release-артефакты.
|
||
|
||
Два первых раздела — продуктовые, и они идут первыми не случайно. **Сценарий:**
|
||
какая персона (`docs/SCOPE.md`), на какой поверхности, в какой момент это
|
||
встретит. **Что человек увидит:** одной фразой, без терминов реализации. ТЗ,
|
||
которое не может ответить на эти два вопроса, описывает работу, а не изменение
|
||
продукта.
|
||
|
||
**Размытое место не додумывается, а выносится владельцу.** Догадка, записанная
|
||
как факт, — худший вид дефекта: она проходит ревью, потому что выглядит решением.
|
||
|
||
Но спрашивать обо всём нельзя: владелец один, и анкета из двадцати пунктов хуже
|
||
угадывания. Порог такой (решение владельца 2026-08-13).
|
||
|
||
**Владельцу задаются только продуктовые вопросы** — что человек видит или делает
|
||
и какой объём видимых изменений входит в этот issue. Поведение в пограничном
|
||
случае; какая из персон важнее в конфликте; что считать приемлемой деградацией;
|
||
относится ли смежное поведение сюда или становится отдельной задачей.
|
||
|
||
**Всё, чего пользователь не наблюдает, агенты решают сами** либо согласовывают
|
||
между собой: где хранится состояние, в каком модуле стоит гвард, именование,
|
||
раскладка файлов, стратегия тестов, механика миграции. Решение записывается явным
|
||
блоком в конце ТЗ — «принято предположительно, поменять свободно», и ревьюер
|
||
вправе его оспорить. Технический спор автора и ревьюера решается вердиктом, а не
|
||
владельцем; до него он доходит только при исчерпании лимита циклов (§4).
|
||
|
||
**Смешанный вопрос делится, а не эскалируется целиком.** «Где живёт это
|
||
состояние» — техническое. «Переживает ли оно перезагрузку страницы и общее ли оно
|
||
для всех экранов» — продуктовое.
|
||
|
||
Вопросы задаются **одним комментарием, пачкой**, каждый в форме: что неясно ·
|
||
что изменится от ответа · **предлагаемый вариант по умолчанию**. Вопрос с готовым
|
||
вариантом стоит владельцу пяти секунд, вопрос без него — пяти минут. Пока ждём
|
||
ответа, issue остаётся в `S3-spec` и получает `blocked`: статус не подменяется,
|
||
`blocked` его дополняет, иначе конвейер считает задачу в работе, а она стоит.
|
||
|
||
### 7.2 Шаблоны комментариев
|
||
|
||
Короткие и однообразные, чтобы читались и человеком, и машиной.
|
||
|
||
- **Аналитика:** `Оценка: ценность N/10 · сложность N/10 · P<1-3> · тип ·
|
||
поверхности: … · дубликаты: … · лёгкий трек: да/нет`
|
||
- **Занятие:** `Взял: <роль> · сессия <id> · ветка issue/NN-slug`
|
||
- **Хендофф:** `Сделано: … · Файлы: … · Гейты: <команда → результат> ·
|
||
НЕ сделано: … · Риски: … · Следующий статус: … · Новые issue: #…`
|
||
- **Вердикт ревью:** `Вердикт: зелёный/жёлтый/красный · заход r<N> ·
|
||
блокирующих циклов K/<лимит> · High: N · Medium: N → в задаче | #… ·
|
||
Документ: docs/reviews/…`
|
||
(«→ #…» — только у Medium вне скоупа; находки в скоупе возвращаются автору.
|
||
Заход — номер прогона ревью, K — израсходованный бюджет §4: зелёные вердикты
|
||
его не тратят, поэтому заход и K расходятся, #227)
|
||
- **Закрытие:** `Выпущено в <тег беты> · CI: <ссылка> · Changelog: <ссылка>`
|
||
|
||
**Вперёд двигает только зелёный вердикт.** Жёлтый и красный возвращают автору;
|
||
разница между ними содержательна для человека, но не для маршрута. Первая
|
||
редакция конвейера (§10.4) пропускала жёлтый при `High: 0`, и первый же живой
|
||
прогон показал, почему это неверно: жёлтый там означал, что AC описывает неверное
|
||
изменение контракта — реализовать такое ТЗ значило бы сделать ошибку по инструкции.
|
||
|
||
### 7.3 Расхождения с текущим состоянием, которые надо закрыть
|
||
|
||
1. ✅ **Статус ТЗ дублировал статус issue.** Закрыто 2026-09-10 (#517): ТЗ живёт
|
||
в теле issue, `docs/specs/` — архив, индекс с колонкой «Статус ТЗ» удалён
|
||
вместе с самой таблицей. Статус задачи — только метка `S*`.
|
||
2. **Ревью до релиза 1.62 живут вне репозитория.** Документы `CODE-REVIEW-*.md` и
|
||
`SPEC-REVIEW-*.md` за прежний период лежат в папке владельца, и переносить их
|
||
задним числом смысла нет: они описывают код, которого уже нет. Новые документы
|
||
ревью кладёт в `docs/reviews/` сам конвейер, в ветку задачи.
|
||
|
||
---
|
||
|
||
## 8. Гейты
|
||
|
||
**Локальный гейт перед выходом из «В разработке»** — минимальный набор,
|
||
покрывающий изменённые поверхности (действующее правило владельца):
|
||
|
||
```
|
||
npx tsc --noEmit
|
||
npm test
|
||
npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js \
|
||
# копия стенда собирается `npm run bundle:sync`, в репозитории её нет (#255)
|
||
node scripts/smoke-select.mjs --base origin/dev --head HEAD # какие смоки относятся к диффу
|
||
node demo/smoke_<целевые>.mjs
|
||
node scripts/no-new-any.mjs --base origin/dev --head HEAD # новый код не добавляет any
|
||
npm run golden:verify # если менялся визуал
|
||
node scripts/check-docs.mjs # если менялся src/**
|
||
node scripts/model-invariants.mjs --config <экспорт> # если правилась геометрия или ссылки
|
||
python -m pytest tests_backend -q # py3.14 как в CI (npm run toolchain:check), если менялся бэкенд
|
||
npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
||
&& python tests_backend/junction_parity.py --build-dir=test-build/junction-parity
|
||
# если менялось одно из зеркал junction limits
|
||
```
|
||
|
||
**Новый код не добавляет `any`** (#342). В `src/**` уже 1034 вхождения явного
|
||
`any` в 49 файлах; перетипизировать это одним заходом — месяц риска ради нуля
|
||
пользовательской ценности, поэтому долг снимается при плановом извлечении
|
||
подсистем (#425, прежний #34), а не разовой заменой. Гейт `scripts/no-new-any.mjs` судит
|
||
**только добавленные строки**: существующий долг на нетронутой строке законен,
|
||
правка строки со старым `any` — новая ответственность. Исключение объявляется на
|
||
той же строке, `// any-ok: <конкретная причина>`; голый маркер и причины вида
|
||
«todo» не проходят. Текст разбирается парсером TypeScript, поэтому слово «any» в
|
||
комментарии, строке или идентификаторе ложных срабатываний не даёт.
|
||
|
||
**Объём гейтов на код-ревью соразмерен задаче** (issue #127). Всегда:
|
||
`typecheck`, `npm test`, `npm run build` со сверкой трёх копий бандла, а при
|
||
любом diff'е по `src/**` — ещё и `node scripts/check-docs.mjs`. По
|
||
необходимости, определяемой diff'ом и AC: браузерные смоки (сколько их —
|
||
считает `ls demo/smoke_*.mjs | wc -l`, вшитое число здесь трижды отставало от
|
||
дерева; прогон всех уместен только когда задача задевает всё; какие относятся к
|
||
диффу, печатает
|
||
`node scripts/smoke-select.mjs --base origin/dev --head HEAD`, и его вывод
|
||
прикладывается к ревью вместе с решением по каждой строке), `golden:verify` при изменении видимого
|
||
результата, `pytest tests_backend` при правках в Python, performance-профили при
|
||
названном в AC влиянии. **Полные наборы — предрелизный гейт, а не гейт ревью.**
|
||
|
||
Скриншоты снимаются **только** джобой `Docs screenshots` (`workflow_dispatch`) и
|
||
принимаются локально: `npm run docs:accept -- --reviewed --from=<распакованный
|
||
артефакт>` (#246). Съёмка на своей машине даёт байтово другой PNG при том же
|
||
кадре, и набор из «не того» браузера переписывает все десять файлов без единого
|
||
содержательного изменения. Приёмка отказывает, если кандидат снят не с этого
|
||
дерева, не тем капчуром, не называет свой Chromium или неполон; коммит делает
|
||
человек.
|
||
|
||
Когда правка `src/**` кадров не меняет — а это большинство правок — CI-цикл не
|
||
нужен (#512): `npm run docs:accept -- --identical` снимает кадры локально,
|
||
декодирует оба набора в Chromium и при нуле отличающихся пикселей во всех
|
||
кадрах обновляет только отпечаток исходников в `screenshots.json`; байты
|
||
закоммиченных PNG, их sha, браузер и упаковщик съёмки остаются прежними. Хотя бы
|
||
один отличающийся пиксель — отказ с перечнем кадров и штатный путь через артефакт.
|
||
|
||
`check-docs` стоит в обязательной части не по важности, а по механике: отпечаток
|
||
скриншотов документации считается по всему `src/**`, поэтому **любая** правка
|
||
фронтенда делает его устаревшим. Выборка «по diff и AC» здесь не работает — diff
|
||
всегда попадает, и решать нечего. Цена пропуска измерена: скриншоты не
|
||
пересняли в #230 и #234, и `dev` стоял с красным job `docs`, пока это не нашли
|
||
при следующей задаче (#237). Пересъёмка — `npm run build && node
|
||
demo/docs/capture.mjs`, коммит вместе с задачей.
|
||
|
||
**Перф-смок в Validate зависит от диффа** (#473). Два glow-профиля
|
||
гоняются всегда; при правке `src/iso-*` добавляется `large-house-isometric-v1`,
|
||
при правке `src/live-*`, `src/render-*`, `houseplan-render-lifecycle.ts`,
|
||
`houseplan-card.ts` — `large-house-interaction-v1`, оба по три образца против
|
||
абсолютных потолков `hardMaxMs` полных профилей (`budgets-*-smoke.json`).
|
||
Это гейт на «в разы», а не «на проценты»: регрессия #160 (первый кадр 9 870 мс
|
||
против потолка 3 500) ловится ещё в ревью, а не предрелизным гейтом под тегом.
|
||
Классификацию делает `scripts/classify-changes.mjs`, набор профилей входит в
|
||
ключ переиспользования `performance_smoke`. Ревьюер по-прежнему принимает
|
||
зелёный Validate на SHA как подтверждение дешёвых гейтов — смок его часть.
|
||
|
||
Условие честности такого сужения: ревьюер обязан перечислить, какие гейты прогнал,
|
||
какие нет и почему. Непрогнанный гейт становится видимым решением, а не молчаливым
|
||
пропуском.
|
||
|
||
**Одно число — один источник.** Любая величина, которую пользователь видит
|
||
дважды — превью против записи, подпись против площади, подсветка инструмента
|
||
против сохранённого значения, — обязана считаться в одном месте. Три дефекта
|
||
подряд имели ровно эту причину: #234 (резинка показывала 12 см, запись хранила
|
||
24), #233 (подпись мерила по осевым линиям, площадь рядом — по полу) и способ,
|
||
которым #234 нашли (подсветка «Толщины» врала согласованно с записью). Ревьюер
|
||
отвечает на вопрос прямо: какое число в этом диффе видно дважды и один ли у него
|
||
источник. Механическая часть правила закреплена тестом
|
||
`test/single-source-numbers.test.mjs` — строку с единицей измерения собирает
|
||
только канонический форматтер; смысловая часть остаётся за ревью.
|
||
|
||
**Гейт беты** (условие закрытия issue): CI Validate зелёный на точном SHA тега.
|
||
|
||
Часть гейтов запускается только здесь, то есть **после** пройденного код-ревью.
|
||
Упавший предрелизный гейт автор чинит и повторно прогоняет; зелёный прогон
|
||
достаточен для продолжения релиза, повторное код-ревью не требуется — §11.4.
|
||
|
||
**Гейт стабильного релиза:** полный локальный прогон плюс Validate и Full
|
||
Performance зелёные на точном SHA, плюс зелёный E2E на реальном Home Assistant:
|
||
`release.yml` сам запускает `e2e.yml` в `houseplan-e2e` на SHA кандидата и ждёт
|
||
его зелёного (#514, #540); установочные ассеты публикуются только после всех
|
||
гейтов и только этим workflow — релиз, опубликованный руками, возвращается в
|
||
черновик до их прохождения (#540); статусов issue не касается.
|
||
|
||
---
|
||
|
||
## 9. Метки — канонический статус
|
||
|
||
Статус читается из меток: их видно в списке issue, их читает любой токен с
|
||
доступом к Issues, и по ним же работает конвейер — смена метки порождает событие
|
||
(§10.4). **Project v2 не используется** (решение владельца 2026-08-14): второе
|
||
представление статуса рядом с метками требовало отдельного скоупа токена,
|
||
синхронизации и внимания, а давало вид доски. Два источника одного факта
|
||
расходятся — это уже случалось с колонкой «Статус ТЗ» в `docs/specs/README.md`.
|
||
|
||
**Имена меток английские** (решение владельца 2026-08-12). Русские имена в этом
|
||
документе были только на бумаге; репозиторий с самого начала жил на английских.
|
||
|
||
| Метка | Статус |
|
||
|---|---|
|
||
| `S1-new` | Новое, не разобрано |
|
||
| `S2-analysis` | Аналитика и оценка |
|
||
| `S3-spec` | ТЗ в работе |
|
||
| `S4-spec-review` | ТЗ на ревью |
|
||
| `S5-ready` | Готово к разработке — единственный статус, из которого можно начать трогать код |
|
||
| `S6-in-progress` | В разработке, занято исполнителем |
|
||
| `S7-code-review` | Код-ревью |
|
||
| `S8-merged` | Ревью пройдено, код в `dev`, ждёт беты. Issue закрывается пачкой при выпуске |
|
||
| `blocked` | Ждём внешнего или владельца, **поверх** статусной метки |
|
||
| `rejected` | Отклонено, issue закрыт |
|
||
|
||
Модификаторы: `small` (лёгкий трек, сложность ≤3), `trivial` (короткий трек,
|
||
§5.1), `hotfix`, `process`, `review-4`; приоритет `P1`/`P2`/`P3`; тип `bug`/`feature`/`tech-debt`.
|
||
Тематические метки (`polish`, `infra`, `tests`, `docs`, `security`, `vacuum`)
|
||
ортогональны процессу.
|
||
|
||
Инварианты: продуктовая задача в процессе несёт **ровно одну `S*`-метку**.
|
||
Инфраструктурная задача может не иметь `S*` во время первоначальной реализации;
|
||
с первого `S7-code-review` на неё действует тот же инвариант ровно одной метки.
|
||
Закрытый issue статусных меток не несёт; `blocked` не заменяет статус, а дополняет
|
||
его.
|
||
|
||
**Чужой issue берётся в работу так же, как свой — после явного решения
|
||
владельца** (решение владельца 2026-08-13, уточнено в тот же день). Репозиторий
|
||
публичный, отчёты заводят и посторонние; проверка стоит **на входе**, а не на
|
||
каждом шаге.
|
||
|
||
Для продуктовой задачи входом служит присвоение первой статусной метки. Для
|
||
инфраструктурной — явное назначение владельцем; до готовности к первому
|
||
код-ревью она может оставаться без `S*`. Как только продуктовая задача вошла в
|
||
полный маршрут либо инфраструктурная получила `S7-code-review`, **кто её завёл,
|
||
дальше не имеет значения** — статусы, ревью и лимиты работают одинаково.
|
||
|
||
Присвоение метки и есть то самое явное решение, причём проверенное платформой:
|
||
метки может ставить только тот, у кого есть право записи в репозиторий. Прежняя
|
||
редакция требовала переоформлять чужой отчёт своим issue со ссылкой на исходный;
|
||
это оказалось работой впустую — на #123 к моменту отказа ТЗ уже было написано.
|
||
|
||
`S8-merged` появился позже остальных и закрывает разрыв, который раньше
|
||
закрывался памятью человека: код принят, но бета ещё не вышла, и issue закрывать
|
||
рано. Без него принятая задача либо висела в `S7-code-review`, либо закрывалась
|
||
досрочно.
|
||
|
||
---
|
||
|
||
## 10. Механизация при прямых коммитах в `dev`
|
||
|
||
Решение владельца — работать без PR. Значит, GitHub не может ничего заблокировать
|
||
на своей стороне: **основной гейт переезжает на клиента, CI остаётся страховкой.**
|
||
|
||
### 10.1 Хуки, которые невозможно забыть поставить
|
||
|
||
`.githooks/` в репозитории, `core.hooksPath` выставляется автоматически при
|
||
установке зависимостей:
|
||
|
||
```json
|
||
"scripts": { "prepare": "node scripts/install-hooks.mjs" }
|
||
```
|
||
|
||
`npm ci` вызывает `prepare` сам — значит, хуки появляются в каждом окружении,
|
||
включая свежий контейнер облачного агента, без отдельного шага в инструкции.
|
||
|
||
- **`commit-msg`** — есть, работает. Отклоняет коммит без терминального
|
||
`Issue: #NN`, требует ровно один `User-Visible: yes|no`, а для коммитов,
|
||
трогающих `demo/golden/baselines/**`, — `Release:` плюс `Baseline-Reviewed:`.
|
||
Реализация — `scripts/validate-commit-provenance.mjs`, тот же скрипт вызывается
|
||
job `provenance` в `validate.yml`.
|
||
- **`pre-push`** — есть, работает. Прогоняет `scripts/process-gate.mjs` по каждому
|
||
пушимому ref и останавливает push при нарушении. Это и есть блокирующий гейт
|
||
вместо PR. Удаление ветки и теги пропускаются: в первом случае проверять нечего,
|
||
во втором коммит уже проверен, когда его пушили. Для новой ветки диапазон
|
||
считается от `merge-base` с `origin/dev`, а не от начала истории — иначе в него
|
||
попали бы все нарушения, совершённые до появления гейта.
|
||
|
||
При возврате `main` в `dev` диапазон merge-коммита содержит второй родитель —
|
||
уже опубликованные в `main` коммиты с закрытыми issue. Для destination `dev`
|
||
общий скрипт pre-push/CI исключает только SHA, доказанно достижимые из
|
||
`origin/main`; сам merge и новые post-merge коммиты остаются под всеми
|
||
проверками. На `main`, beta/issue-ветки и обычный push в `dev` это исключение
|
||
не распространяется (issue #155).
|
||
|
||
Проверка статуса issue требует `gh`, поэтому при его отсутствии хук печатает
|
||
предупреждение и выполняет только офлайн-часть. Это сознательная уступка: хук,
|
||
который не работает в самолёте, отключают целиком, а строгий проход всё равно
|
||
делает CI.
|
||
|
||
**Хук обязан быть исполняемым, и это тише всего ломается.** Git **молча** не
|
||
запускает файл без бита `+x`: гейт сообщает об успехе тем, что его нет. Проверено
|
||
на настоящем push — при `644` от гейта ноль строк и push проходит, при `755` он
|
||
останавливается.
|
||
|
||
Через GitHub API режим не выставляется: файл, отправленный так, приезжает
|
||
`100644`. Поэтому `scripts/install-hooks.mjs` восстанавливает бит при каждой
|
||
установке зависимостей, а `assertHookMode` дополнительно проверяет бит
|
||
`.githooks/commit-msg` в индексе. Правится вручную:
|
||
`git update-index --chmod=+x .githooks/<хук>`.
|
||
|
||
### 10.2 Что проверяет `process-gate.mjs`
|
||
|
||
Реализовано, `scripts/process-gate.mjs`, issue #105. Офлайн, без GitHub API:
|
||
|
||
1. трейлер `Issue: #NN` у каждого коммита класса A/B, допускается несколько;
|
||
2. имя ветки `issue/NN-slug` соответствует трейлерам;
|
||
3. у класса A есть ТЗ: раздел `## ТЗ` или хотя бы один `AC1` в теле issue —
|
||
либо архивный `docs/specs/NN-*.md` у задачи до 2026-09-10. Офлайн тела нет,
|
||
и проверка молчит; с `--issues` — предупреждение (настоящий рубеж — ревью ТЗ).
|
||
Добавление нового файла в `docs/specs/**` тоже предупреждение: каталог
|
||
заморожен (#517);
|
||
4. `User-Visible: yes` → правки в обоих changelog в том же коммите;
|
||
5. коммит только класса D невалиден без `Release: vX.Y.Z` либо
|
||
`Baseline-Reviewed: <ссылка на прогон CI>`;
|
||
6. релизный коммит не содержит изменений в `src/` и `custom_components/**/*.py`;
|
||
7. документов ревью на один issue не больше четырёх (`-r1`…`-r4`).
|
||
|
||
С токеном GitHub:
|
||
|
||
8. `--issues` тянет каждый упомянутый issue и требует метку из
|
||
{`S5-ready`, `S6-in-progress`, `S7-code-review`, `S8-merged`}; закрытый,
|
||
недоступный или помеченный `blocked` — отказ (**fail closed**).
|
||
|
||
Три оговорки к проверке 8 выяснились при реализации.
|
||
|
||
**`S8-merged` входит в множество**, хотя по смыслу задача уже принята. Причина
|
||
механическая: конвейер (§10.4) сливает ветку в `dev` **раньше**, чем ставит метку,
|
||
Validate стартует от этого push и успевает прочитать issue уже в `S8-merged`.
|
||
Строгое множество красило бы каждую принятую задачу. Локальная строгость
|
||
возвращается флагом `--no-merged`.
|
||
|
||
**Статус спрашивается только у коммитов класса A/B.** Правило №1 говорит о
|
||
продуктовом коде и инструментах, а не о документации. Иначе краснел бы каждый
|
||
документ ревью: он ложится в ветку задачи, пока та в `S4-spec-review` или
|
||
`S7-code-review`, то есть заведомо вне рабочего множества.
|
||
|
||
**При продвижении в `main` не перепроверяются коммиты, уже достижимые из
|
||
prerelease-тега.** После выпуска беты их issue по §2.8 должны быть закрыты, а
|
||
stable fast-forward снова включает эти коммиты в диапазон `old-main..candidate`.
|
||
Pre-push передаёт целевую remote ref через `--target-ref`, а Validate — через
|
||
`TARGET_REF`; оба исключают только уже опубликованную prerelease-историю. Любой
|
||
post-beta коммит остаётся в проверке и по закрытому issue отклоняется fail-closed.
|
||
|
||
Не реализовано и остаётся долгом:
|
||
|
||
9. `npm run release:prerelease -- --issues=…` не проверяет, есть ли у issue
|
||
зелёный вердикт код-ревью.
|
||
|
||
Закрытие вошедших issue автоматизировано (#120, #547). При старте беты текущая
|
||
очередь S8 служит только списком кандидатов: `RELEASE-MEMBERSHIP.json` оставляет
|
||
из неё лишь номера с доказанным `Issue: #NN` в Git-диапазоне зафиксированного
|
||
SHA. Manifest публикуется и входит в `SHA256SUMS`; свежая очередь S8 после
|
||
публикации не перечитывается. Общий для workflow и локальной команды bookkeeping
|
||
идемпотентно добавляет один маркированный release-комментарий, снимает все
|
||
статусные метки и закрывает issue. Поэтому повтор после сбоя продолжает manifest,
|
||
даже если метка уже снята или release уже public; автор issue на membership не
|
||
влияет.
|
||
|
||
### 10.3 Страховка и разбор
|
||
|
||
- **`process-gate.mjs` — job `process-gate` в `validate.yml`**, без `needs`:
|
||
краснеет сам и не роняет остальные. При прямом push проверка догоняющая: код уже
|
||
в `dev`, CI краснеет после. Это принятая цена отказа от PR: `pre-push` ловит
|
||
нарушение до отправки, а этот job — то, что прошло мимо хука, включая
|
||
`--no-verify` и окружение без установленных зависимостей.
|
||
- **Нарушение не откатывается force-push'ем** (правило 17): исправляющий коммит
|
||
плюс issue с меткой `process`. Починить надо проверку, а не только симптом.
|
||
- **Еженедельная гигиена** (workflow): issue в `S1-new` дольше 14 дней и в
|
||
`S6-in-progress` дольше 7; issue класса A в `S5-ready` без ТЗ; issue с нулём или
|
||
двумя `S*`-метками; коммиты без трейлера за неделю — **цель 0**; rework rate и
|
||
число issue, дошедших до `review-4`; **баги, заведённые после закрытия беты** —
|
||
прямая цена отказа от фазы тестирования.
|
||
|
||
### 10.4 Событийный конвейер: метка как триггер
|
||
|
||
`.github/workflows/process.yml`, issue #114. Смена статусной метки — не запись в
|
||
журнал, а **сообщение**: она порождает событие, событие запускает следующий шаг.
|
||
|
||
```
|
||
S4-spec-review → ревью ТЗ → S5-ready либо возврат в S3-spec
|
||
S7-code-review → код-ревью → слияние в dev → S8-merged либо возврат в S6-in-progress
|
||
```
|
||
|
||
Текущая техническая реализация независимого ревьюера —
|
||
`anthropics/claude-code-action`; это деталь автоматизации, а не закрепление роли
|
||
или вида задач за Claude. Ревьюер читает `docs/SCOPE.md`, `AGENTS.md`, этот
|
||
документ и тело issue, публикует разбор комментарием, заводит issue на
|
||
Medium-находки вне скоупа задачи (#202), кладёт документ в `docs/reviews/` ветки
|
||
задачи и возвращает вердикт структурированным JSON. **Метку переставляет отдельный
|
||
детерминированный шаг по вердикту, а не модель.**
|
||
|
||
Четыре вещи, без которых конвейер молча не работает:
|
||
|
||
1. метки переставляет **PAT**, а не `GITHUB_TOKEN`: GitHub намеренно не порождает
|
||
события от `GITHUB_TOKEN`, чтобы не было циклов, и цепочка обрывалась бы после
|
||
первого шага без ошибок в логах;
|
||
2. `process.yml` обязан лежать в **ветке по умолчанию**: для события `issues`
|
||
GitHub берёт workflow только оттуда, независимо от содержимого `dev`;
|
||
3. слияние в `dev` происходит **до** простановки `S8-merged`, иначе метка врёт в
|
||
промежутке — она утверждает, что код в `dev`;
|
||
4. многострочный текст внутри `run:` — только через heredoc: строка с нулевым
|
||
отступом обрывает блок YAML, и скрипт обрезается без ошибки парсера.
|
||
|
||
**Ревью не начинается на красном коде** (#510). После фиксации материала конвейер
|
||
запускает Validate с мутантами по диффу на этом SHA (`scripts/validate-gate.mjs`:
|
||
`workflow_dispatch validate.yml -f mutants=true`) и ждёт его до 45 минут. Красный или
|
||
пропавший прогон возвращает задачу в `S6-in-progress` с комментарием и ссылкой —
|
||
код никто не читал, цикл ревью не израсходован. Мутанты по диффу вообще бегут
|
||
только по запросу: на кандидате ревью, кандидате слияния (#492), кандидате беты,
|
||
в ночном прогоне и на PR; обычный push обходится дешёвыми гейтами (~3 минуты).
|
||
За 08–09.09 мутанты на каждом промежуточном пуше стоили 48 из 56 часов
|
||
job-минут Validate и в основном отменялись следующим пушем.
|
||
|
||
Каждый раунд ревью платит только за то, что в нём изменилось (#518). Свидетель
|
||
судится по **области своего якоря** — строкам патча плюс сорок строк с каждой
|
||
стороны (`ANCHOR_RADIUS_LINES`): и в отпечатке журнала (#481), и в отборе по
|
||
диффу, который читает ханки `git diff --unified=0`. Сторона гарда осталась
|
||
файловой: у гарда якоря нет. Неоднозначный якорь и непрочитанные ханки дают
|
||
прежний широкий ответ — незнание не доказательство. Приближение того же класса,
|
||
что и сам отбор по диффу; нижняя граница — ночной полный гейт (#513). Шард
|
||
считает свой план до установки окружения и при пустом плане не платит за
|
||
npm ci, Python и Chromium, оставаясь исполненной job: доказательство гейта
|
||
требует успешной job, а не пропущенной.
|
||
|
||
**Один хендофф — один пуш.** Перед пушем — локальный `node scripts/process-gate.mjs
|
||
--issues` при доступном `gh` (хук без `gh` статус issue не проверяет и молчит);
|
||
после `S7-code-review` в ветку не пушить, пока не пришёл вердикт или возврат: пуш
|
||
поверх идущего ревью отменяет его и стоит 10–20 минут раннера, а после фиксации
|
||
материала — ещё и слияние (#312). `S7` ставится один раз на заход, не после
|
||
каждого фикса CI: красный Validate конвейер вернёт сам.
|
||
|
||
**Автор обязан дождаться вердикта, а не заканчивать сессию.** Ревью идёт от десяти
|
||
минут до сорока пяти. Отчёт «передал на ревью» останавливает конвейер там, где он
|
||
мог идти сам: вердикт придёт, а подхватить его будет некому. У агента нет часов —
|
||
он существует только в момент своего хода, поэтому ожидание это опрос: раз в 90
|
||
секунд, не более 30 попыток — `node scripts/wait-verdict.mjs --issue NN` делает его
|
||
детерминированно и говорит только при смене состояния (#496). Смотреть на метку, а
|
||
не на комментарий: метка и есть состояние. Комментарии конвейера до последнего
|
||
применения `S4`/`S7` считаются историческим baseline, а уже опубликованный исход
|
||
текущего раунда доставляется сразу при первом опросе (#546). При `blocked` не
|
||
ждать — задача ждёт владельца.
|
||
|
||
**После прогона ревью метка меняется всегда.** Инвариант появился не сразу: первая
|
||
редакция при конфликте слияния оставляла метку на месте, и это оказалось тупиком —
|
||
автор ждёт смену метки, метка не менялась, и он тридцать раз опрашивал впустую,
|
||
чтобы отчитаться «лимит исчерпан» при зелёном вердикте. Состояние, из которого
|
||
никто не может выйти и о котором никто не узнает, для конвейера хуже громкой
|
||
ошибки.
|
||
|
||
**Ветка приводится к `dev` до ревью, а не после** (#257). Раньше ревью читало ветку
|
||
как есть, а слияние делало ребейз — проверенный SHA и слитый SHA были разными
|
||
коммитами. Пока расхождение с `dev` текстовое, ребейз упирается в конфликт и это
|
||
видно; смысловое расхождение git склеивает молча, и в `dev` уезжает комбинация,
|
||
которую ревьюер не читал. Именно так пришёл регресс #234. Шаг перед ревью делает
|
||
одно из трёх:
|
||
|
||
- ветка уже содержит весь `dev` — ничего;
|
||
- отстала и ребейзится чисто — ребейз, `push --force-with-lease`, ревью по
|
||
приведённому состоянию. Факт ребейза передаётся в промпт, чтобы сработало
|
||
правило §7.2 о полном разборе вместо дельты;
|
||
- конфликт — возврат в `S6-in-progress` **до** запуска ревью. Цикл при этом не
|
||
расходуется: код никто не читал, вердикта нет.
|
||
|
||
Проверка стоит до ревью не только ради совпадения SHA. Конфликт всё равно вернул бы
|
||
задачу, но обнаруживался он после сорока пяти минут работы ревьюера и потраченных
|
||
лимитов подписки, хотя виден за пять секунд до них.
|
||
|
||
`--force-with-lease` здесь обязателен с явным ожидаемым значением: между чтением
|
||
ветки и пушем автор мог запушить коммит, и слепой `--force` потерял бы его молча.
|
||
Расхождение lease — падение прогона, а не предупреждение.
|
||
|
||
**В `dev` уезжает точный кандидат, и только проверенный** (#492,
|
||
`scripts/merge-candidate.mjs`). Ревью длится десятки минут, `dev` за это время
|
||
двигается; ребейз после вердикта даёт дерево, которого никто не видел, — а чистый
|
||
ребейз ничего не доказывает: соседняя правка в `dev` меняет поведение без единого
|
||
конфликта. Шаг слияния поэтому:
|
||
|
||
- сверяет вершину ветки с материалом ревью (#312) — иначе `S6-in-progress`;
|
||
- если `dev` не двигался — push с `--force-with-lease` на текущую вершину;
|
||
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
|
||
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
|
||
к другому диффу не применим, §7.2), публикация кандидата в ветку задачи, запуск
|
||
Validate с мутантами на ней (#510) и ожидание зелёного dispatch-прогона **на этом
|
||
SHA** — push-прогон мутантов не несёт — и только затем push в `dev` с lease на ту
|
||
вершину, поверх которой кандидат собран. Отклонённый lease — `dev` двинулся снова
|
||
— новая попытка; после третьей — `S6-in-progress` с комментарием;
|
||
- красный Validate на кандидате или прогон, не появившийся за три минуты, —
|
||
`S6-in-progress` с ссылкой; `S8-merged` ставится только после push.
|
||
|
||
Проверка кандидата — обычный Validate ветки: лёгкий набор плюс диффозависимые
|
||
гейты. Тяжёлые гейты остаются за кандидатом релиза (#479): слияние не превращает
|
||
каждое движение `dev` в двадцатиминутный прогон, а проверяет ровно то, что
|
||
проверил бы пуш той же дельты.
|
||
|
||
Поэтому зелёное код-ревью с неудавшимся слиянием ведёт не в `S8-merged`, а в
|
||
`S6-in-progress`: работа действительно вернулась к автору, только осталась не
|
||
правка кода, а ребейз. Вердикт при этом в силе, переделывать нечего. После ребейза
|
||
метка `S7-code-review` возвращается и ревью идёт заново — не формальность:
|
||
после ребейза на ушедший вперёд `dev` это другой код.
|
||
|
||
Если метка не сменилась, значит упал сам прогон, а не работа: смотреть логи и
|
||
сообщать владельцу, а не продолжать опрос.
|
||
|
||
**Конвейер — идемпотентный контроллер, а событие лишь будит его** (#499). Guard
|
||
читает метки issue текущими, а не из снимка события: прогон мог простоять в очереди,
|
||
пока владелец снял метку — отозванный запрос не исполняется, и комментария об этом
|
||
нет. Конвейер запускают только `S4-spec-review` и `S7-code-review`; остальные метки
|
||
не создают ни одной job и не входят в concurrency-группу issue — прежде любая
|
||
посторонняя метка вытесняла ожидающий запуск ревью. Зелёный вердикт применяется
|
||
повторно **без вызова модели**, если последний документ этапа несёт записанный
|
||
конвейером вердикт `green` с High 0 и дерево материала не изменилось ни в одном
|
||
файле вне `docs/reviews/**` (сравнивает `git diff` по содержимому). Ребейз, правка
|
||
теста, фикстуры или ТЗ дают отличие дерева и полный разбор — правило §7.2 не
|
||
ослабляется, оно просто не касается дерева, которое уже читали.
|
||
|
||
Цикл считается **по этапу**: вердикт по ТЗ не расходует бюджет код-ревью. Раньше
|
||
считались все вердикты подряд, и первое код-ревью #89 получило `r2/4`.
|
||
|
||
---
|
||
|
||
## 11. Исключения
|
||
|
||
### 11.1 Лёгкий трек
|
||
|
||
См. §5 — это не исключение из правила №1, а более дешёвый путь по тем же статусам.
|
||
|
||
### 11.2 Аварийный хотфикс (метка `hotfix`, решение владельца)
|
||
|
||
Разрешено писать код до появления issue. Обязательно:
|
||
|
||
- issue создан в **той же сессии до коммита**, метка `hotfix`;
|
||
- ТЗ «как сделано» + раздел «почему нельзя было ждать»;
|
||
- в течение 24 часов задача ретроспективно проходит код-ревью;
|
||
- аварийность названа явно в релизном хендоффе (действующее правило `AGENTS.md`).
|
||
|
||
### 11.3 Гигиена репозитория
|
||
|
||
Механические изменения без изменения поведения (форматирование, мёртвые файлы)
|
||
идут под квартальный umbrella-issue «Гигиена репозитория»; каждый коммит
|
||
ссылается на него. Трассируемость 1:1 сохраняется.
|
||
|
||
### 11.4 Починка предрелизных гейтов без повторного код-ревью
|
||
|
||
Решение владельца 2026-08-13.
|
||
|
||
В цикле реализации гоняется только лёгкий набор — typecheck, unit, build (§8).
|
||
Golden, браузерные смоки, performance и полный HA-харнесс запускаются перед бетой,
|
||
то есть **после** того, как код-ревью пройдено и issue в `S8-merged`. Часть
|
||
проблем физически не может быть найдена раньше.
|
||
|
||
**Если предрелизный гейт упал, автор правит, повторно прогоняет упавшее, и
|
||
зелёного прогона достаточно, чтобы релиз продолжился.** Issue остаётся в
|
||
`S8-merged` и на повторное код-ревью не отправляется.
|
||
|
||
Причина: полный цикл ревью в момент выпуска стоит дороже, чем риск, который он
|
||
здесь снимает. Гейт уже назвал дефект точно, а исправление проверяется тем же
|
||
гейтом — то есть проверка объективна и не зависит от чьего-либо суждения.
|
||
|
||
**Что при этом обязательно:**
|
||
|
||
- прогон упавшего гейта записан в issue: **точная команда и её результат**.
|
||
«Verified» без команды доказательством не является (§8);
|
||
- трейлеры на коммите как обычно, `Issue: #NN` того же issue;
|
||
- при `User-Visible: yes` — правки в оба changelog в том же коммите;
|
||
- эталоны golden принимаются только через `npm run golden:accept -- --reviewed`
|
||
на полном артефакте Linux CI. «Чтобы гейт позеленел» основанием не является.
|
||
|
||
**Границы, за которыми исключение не действует.** Оно про починку названного
|
||
гейтом дефекта, а не про продолжение разработки под видом починки. Правка идёт
|
||
обычным путём — новым issue либо возвратом в `S6-in-progress` — если она:
|
||
|
||
- меняет контракт поведения или добавляет пользователю что-то новое;
|
||
- задевает подсистему, которой в исходной задаче не было;
|
||
- по объёму сопоставима с самой задачей;
|
||
- меняет сам гейт вместо кода — правка теста, чтобы он перестал падать, это не
|
||
починка, а сокрытие. Исключение — когда дефект **в фикстуре** и это доказано
|
||
разбором, как на #89: солнце на азимуте 180° и единственное окно на северной
|
||
стене, поэтому луч честно не строился.
|
||
|
||
Границу определяет автор, и здесь процесс сознательно отдаёт ему то, что в
|
||
остальных местах не доверяет — оценку собственной работы. Плата за скорость в
|
||
единственной точке, где цикл ревью стоит дороже всего. Компенсируется тем, что
|
||
запись в issue публична и релиз-менеджер видит, что именно было сделано перед
|
||
выпуском.
|
||
|
||
Это исключение из правила «код-ревью не пропускается никогда» (§5, §7.1) —
|
||
единственное, и относится только к окну между `S8-merged` и выпуском.
|
||
|
||
---
|
||
|
||
## 12. Запрещено
|
||
|
||
- код без issue или из статуса раньше «Готово к разработке»;
|
||
- ТЗ, написанное после кода (кроме §11.2, и тогда с пометкой «как сделано»);
|
||
- ревью своей работы; перевод своей работы через ревью-гейт;
|
||
- пятый цикл ревью вместо разбора по §4;
|
||
- заведение issue вместо возврата на правки, чтобы обойти лимит циклов;
|
||
- принятие golden-эталонов ради зелёного CI или по частичному артефакту;
|
||
- закрытие issue до выпуска беты с зелёным CI;
|
||
- переоткрытие закрытого issue вместо нового бага;
|
||
- Medium-находки, оставленные как TODO в документе ревью: в скоупе — чинятся
|
||
в текущем issue, вне скоупа — становятся отдельным (#202);
|
||
- **параллельные бэклоги** в файлах (`BACKLOG-*.md`, «планы» в docs);
|
||
- ревью-документы вне репозитория;
|
||
- попутные правки «раз уж я здесь»;
|
||
- фича или материальное изменение поведения в стабильном релиз-коммите;
|
||
- force-push в `dev`;
|
||
- ручное копирование на домашний инстанс.
|
||
|
||
**Нарушение процесса — тоже issue** (метка `process`): если правило удалось
|
||
нарушить незаметно, виновата проверка.
|
||
|
||
---
|
||
|
||
## 13. Внедрение
|
||
|
||
Состояние на 2026-08-13.
|
||
|
||
1. ✅ **Метки созданы, бэклог размечен.** У всех открытых issue владельца ровно
|
||
одна `S*`-метка, инварианты чистые.
|
||
2. ✅ **Колонка «Статус ТЗ» убрана** — вместе со всем индексом: `docs/specs/`
|
||
стал архивом, ТЗ переехало в тело issue (#517, 2026-09-10). Перенос старых
|
||
документов ревью в `docs/reviews/` отменён: они описывают код, которого уже нет.
|
||
3. ✅ **Гейт написан** — `scripts/process-gate.mjs` плюс job в `validate.yml`,
|
||
issue #105. Прошёл **вне** флоу как инфраструктурная задача (§1, issue #118), а
|
||
не через ТЗ и ревью, как предполагала прежняя редакция этого пункта.
|
||
4. ✅ **Долг ревью списан решением владельца.** Беты `beta.2`…`beta.10` сделаны по
|
||
прежнему процессу и не пересматриваются. Точка отсчёта — релиз 1.62.0; отсчёт
|
||
начинается с первой беты следующей линии.
|
||
5. ⏳ Завести issue на находку «смок `visual_continuity` не умеет падать» — это
|
||
ровно тот класс дефектов, который в процессе без ручного тестирования стоит
|
||
дороже всего.
|
||
6. ✅ `BACKLOG-2026-08-11.md` — разовый отчёт, решения живут в issue.
|
||
7. ✅ `AGENTS.md` переписан целиком, шире блока §14.
|
||
8. ✅ **Канон перенесён в репозиторий** (issue #112). До этого полный процесс жил
|
||
только в папке владельца, а в репозитории лежал файл на 51 строку про трейлеры
|
||
коммитов — из свежего клона канон не был виден вообще.
|
||
9. ✅ **`pre-push` написан** (§10.1, issue #121). Блокирующая проверка на клиенте
|
||
есть; обойти её можно только `--no-verify`, и тогда то же найдёт CI.
|
||
|
||
---
|
||
|
||
## 14. Блок для AGENTS.md
|
||
|
||
```markdown
|
||
## Процесс: код только через issue
|
||
|
||
Изменение продуктового кода без issue запрещено. Код меняется только из статуса
|
||
«Готово к разработке» или дальше. Полные правила, критерии статусов и гейты —
|
||
`docs/PROCESS.md`, читать до начала работы.
|
||
|
||
Жизненный цикл (статус = метка issue): `S1-new` → `S2-analysis` → `S3-spec` →
|
||
`S4-spec-review` → `S5-ready` → `S6-in-progress` → `S7-code-review` → `S8-merged`
|
||
→ закрытие пачкой при выпуске беты. Оба ревью возвращают на правки не более 4
|
||
циклов; пятый заход — разбор у владельца (разделить / отклонить / арбитраж).
|
||
|
||
Инфраструктурная задача (ни одного файла класса A) делается сразу любым агентом:
|
||
без `S*` → `S7-code-review` ↔ `S6-in-progress` → `S8-merged`. ТЗ и ревью ТЗ нет,
|
||
код-ревью обязательно.
|
||
|
||
Ревью запускается **само** от меток `S4-spec-review` и `S7-code-review` и идёт до
|
||
45 минут. Поставив такую метку, автор не заканчивает работу, а ждёт смены метки
|
||
опросом и продолжает по тому, чем она стала.
|
||
|
||
- ветка `issue/<NN>-<slug>`, коммиты с трейлерами `Issue: #NN` и `User-Visible: yes|no`;
|
||
- работаем прямыми коммитами в `dev`, без PR: блокирующий гейт — локальный
|
||
`pre-push` (ставится автоматически через `npm ci`), CI — страховка. Force-push
|
||
в `dev` запрещён;
|
||
- автор ≠ ревьюер, ни для ТЗ, ни для кода;
|
||
- фазы ручного тестирования нет: автотесты пишутся в реализации, AC проверяет
|
||
код-ревью, найденные позже дефекты — новые issue типа «баг»;
|
||
- мелкие задачи (метка `small`, сложность ≤3): ТЗ в теле issue, ревью ТЗ
|
||
комментарием, код-ревью — как обычно;
|
||
- найденное вне скоупа — новый issue, а не попутная правка;
|
||
- issue закрывает релиз-менеджер после выпуска беты, не исполнитель.
|
||
```
|