71 KiB
Процесс работы над House Plan
Статус документа: канон (редакция 2026-08-13). Решения владельца, на которых он стоит: прямые коммиты в
devбез PR · канон статуса — метки, имена английские · лёгкий трек включён · автор и ревьюер — разные модели · инфраструктурные задачи идут вне флоу.Область действия: обязателен для владельца и для любого агента. Читается сразу после
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/srv/assets/houseplan-card.js, demo/golden/baselines/** |
Никогда не меняется само по себе. Коммит только класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью |
Практический смысл таблицы: «я только поправил тест» и «я только пересобрал бандл» перестают быть лазейками.
Классы неупорядочены, но при пересечении путей D сильнее A: собранный бандл
лежит внутри custom_components/houseplan/frontend/, и без этого правила он
считался бы продуктовым исходником.
Инфраструктурная задача идёт вне флоу (решение владельца 2026-08-13,
issue #118). Признак механический: ни одного файла класса A. Такая задача
делается без ТЗ, ревью ТЗ, код-ревью и без прохода по статусам — флоу построен
для изменений, у которых есть персона и видимое поведение, а в инфраструктуре ТЗ
пересказывало бы очевидное, и автор с ревьюером оказались бы одной ролью.
Проверкой служат гейты и CI. Обязательным остаётся issue, трейлеры и зелёные
typecheck, test, build.
Задача, задевающая класс 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
Переходы S4-spec-review и S7-code-review выполняются автоматически: метка
порождает событие, событие запускает ревью (§10.4). Остальные ставит исполнитель.
2.1 Новое — заведение задачи
- Кто: любой — владелец, агент, пользователь (Telegram, GitHub).
- Вход: проблема в пользовательских терминах; как проявляется или зачем нужно. Решение не требуется и не приветствуется.
- Запрещено: ставить приоритет, оценивать, писать ТЗ, начинать код.
2.2 Аналитика и оценка
Задача разбирается — и разобранная сама идёт дальше. Умолчание изменено решением владельца 2026-08-14: раньше аналитика ждала подтверждения по каждому пункту, и большинство ожиданий ничего не меняло — issue в основном описаны однозначно.
- Кто: агент-аналитик. Владелец не утверждает переход — он правит асинхронно.
- Чек-лист, результат — комментарием в issue:
- дубликаты проверены (ссылки на похожие issue);
- в скоупе по
docs/SCOPE.mdиdocs/TOUCH-SUPPORT.md; - пользовательская ценность 1–10 и ценность для разработки — что упрощает или разблокирует;
- сложность и риск 1–10 — трудоёмкость плюс вероятность задеть смежное;
- приоритет P1/P2/P3;
- тип: баг / фича / техдолг;
- затронутые поверхности (модули, диалоги, бэкенд, i18n);
- трек: обычный /
small/trivialпо критериям §5 и §5.1.
- Оценки и приоритет ставятся метками сразу, согласие не запрашивается.
Комментарий аналитики — уведомление, а не запрос: молчание владельца —
согласие, несогласие он выражает правкой меток или комментарием, и это не
останавливает работу. Право отклонить задачу (
rejected) остаётся за владельцем на любой стадии. - Вопросов владельцу на этом этапе нет. Единственный класс вопросов, который
вообще задаётся владельцу, — продуктовые (§7.1: что человек видит или делает,
объём видимых изменений), и их место — этап ТЗ, пачкой, с вариантами по
умолчанию и
blocked. Вопрос, который можно отложить до ТЗ, не задаётся в аналитике; вопрос, не блокирующий написание ТЗ, не задаётся вовсе — вместо него в ТЗ пишется блок принятых предположений. - Выход:
S3-spec— переход выполняет сам аналитик, не дожидаясь ответа. Либо, при явном конфликте соSCOPE.md, — предложение отклонить с причиной: это единственный случай, когда аналитика останавливается и ждёт владельца.
2.3 ТЗ в работе — написание ТЗ
- Кто: автор ТЗ, назначает себя. Статус означает «занято».
- Артефакт:
docs/specs/<NN>-<slug>.md, гдеNN— номер issue. Многоэтапная задача:<NN>-<slug>-stage<N>.md. - Лёгкий трек: ТЗ пишется в теле issue, файл не создаётся (§5).
- Выход: полная первая редакция по §7.
2.4 ТЗ на ревью
- Ревьюер ≠ автор. Ревьюер получает issue и ТЗ, без устных пояснений автора. Его задача — не согласиться, а найти, где ТЗ не выполнимо или не проверяемо.
- Артефакт:
docs/reviews/SPEC-REVIEW-<NN>-r<N>.md, вердикт зелёный / жёлтый / красный. Лёгкий трек — комментарий в issue. - High-находки блокируют. Medium/Low — либо правятся, либо становятся отдельными issue со ссылкой; «оставили в тексте ревью» не считается закрытием.
- Выход: «Готово к разработке» либо возврат в «ТЗ в работе» — не более 4 циклов (§4).
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-артефакты по правилу
docs/specs/README.md(changelog RU+EN, документация, golden/скриншоты, performance/security); - откат: как выключить или вернуть назад (флаг Labs, обратная миграция);
- открытых продуктовых вопросов нет; риски перечислены.
Если хоть один пункт не выполнен — статус не «Готово к разработке», как бы ни хотелось начать.
2.6 В разработке — реализация
- Занятие (claim): назначить себя, поставить метку, комментарий
«Взял: <роль> · сессия · ветка
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 либо доказан автотестом — и ревьюер убедился, что тест умеет падать, — либо разобран по коду с явной записью «проверено чтением, не исполнением».
- High блокируют. Medium обязаны превратиться в issue.
- Выход: очередь на пре-релиз либо возврат в «В разработке», не более 4 циклов (§4).
2.8 Закрытие после выпуска беты
- Вход: изменение вошло в опубликованную бету/RC, CI Validate зелёный на точном SHA тега (промоушен-правило: ни одна фича не попадает в стабильный релиз, не побывав в бете).
- Закрывает релиз-менеджер, не исполнитель. Комментарий закрытия: тег беты, ссылка на прогон CI, ссылка на бюллетень changelog.
- Стабильный релиз статусов не двигает — issue уже закрыты; релизный коммит promotion-only, changelog ссылается на закрытые issue.
- Что приходит потом: дефект, найденный на стенде, дома или пользователем, — новый issue типа «баг» со ссылкой на исходный. Исходный не переоткрывается.
2.9 Заблокировано / Отклонено
- Заблокировано: обязательна ссылка на блокирующий issue или внешнюю причину и дата пересмотра. Без причины статус не ставится.
- Отклонено: закрытие с записанной причиной (вне скоупа, дубликат, цена не оправдана). Тихое закрытие без причины запрещено.
3. Правила
Продолжение черновика владельца. Каждое правило проверяемо — глазами или машиной.
- Никаких изменений в код, если нет issue и он не помечен «Готово к разработке» или дальше.
- Issue не может быть взят в разработку, пока у него нет ТЗ с зелёным ревью, пронумерованных AC с указанием доказательства и назначенного исполнителя.
- Issue не может быть взят дважды. Занятие фиксируется назначением, меткой и комментарием с именем ветки. У одного исполнителя одновременно не более одного issue в разработке.
- Статус меняется до действия, а не после. Взял — поставил метку; отдал на ревью — поставил метку. Метка, поставленная задним числом, — дефект процесса.
- Ровно одна метка статуса на issue в любой момент. Ноль или две — дефект, еженедельная гигиена его показывает.
- Автор не ревьюит своё — ни ТЗ, ни код. Никто не переводит свою работу через ревью-гейт.
- Ревью возвращает не более 4 раз. Пятый заход — решение владельца: разделить, отклонить или арбитраж (§4).
- High блокирует. Medium становится issue. Low либо правится, либо снимается решением ревьюера с записью в документе.
- Скоуп не расширяется. Всё найденное вне ТЗ — новый issue, а не попутная правка. Блокирующая находка отправляет текущий issue в «Заблокировано».
- Каждый коммит класса A и B несёт трейлер
Issue: #NN, ветка называетсяissue/NN-slug, аUser-Visible: yesтребует правок в обоих changelog в том же коммите. - Документация — в том же коммите, что поведение. Отдельным «допишу потом» коммитом документация не бывает.
- Сгенерированное не коммитится само по себе. Только релизный промоушен или принятие эталонов со ссылкой на прогон CI.
- Golden-эталоны принимаются только
npm run golden:accept -- --reviewedпо полному Linux-артефакту. Принятие ради зелёного CI — нарушение процесса. - Issue закрывается после выпуска беты с зелёным CI на точном SHA. Не раньше, не «по факту наличия кода», не исполнителем.
- Закрытый issue не переоткрывается. Новый дефект — новый issue со ссылкой.
- Стабильный релиз — promotion-only: версии, сгенерированные бандлы, changelog и release-метаданные. Продуктового кода там нет.
- История
devне перезаписывается. На неё ссылаются теги. Нарушение исправляется следующим коммитом плюс issue с меткойprocess— не force-push'ем. - AC доказывает автотест или запись ревьюера. Фразы «проверил локально, всё работает» в процессе не существует: либо тест, который умеет падать, либо честное «проверено чтением, не исполнением».
- Параллельных бэклогов нет. Планы, разборы и приоритеты живут в issue; файловые отчёты — разовые и датированные.
- Аварийный хотфикс — только решением владельца и только по §11.2.
4. Лимит циклов ревью: 4
Оба ревью-гейта возвращают задачу на правки не более 4 раз. Счётчик виден в
имени документа: -r1 … -r4; на четвёртом заходе ставится метка review-4.
- Что считается циклом: отправка на ревью → вердикт с блокирующими находками → возврат. Уточняющий вопрос без вердикта циклом не считается.
- Исчерпание лимита — не «пятая попытка», а разбор. Задача уходит владельцу,
решение одно из трёх:
- разделить — issue закрывается как «заменён», вместо него 2–3 меньших с ясным скоупом (частый настоящий диагноз: ТЗ было слишком большим);
- отклонить — цена решения оказалась выше ценности;
- арбитраж владельца — владелец фиксирует решение в issue, оно принимается как есть; несогласие ревьюера записывается, но не блокирует.
- Граница между «циклом» и «новым багом»: до закрытия беты находка ревьюера — возврат на правки; после закрытия — новый issue. Иначе лимит 4 обходится заведением issue вместо возврата.
- Для лёгкого трека лимит ревью ТЗ — 2 цикла: задача на три часа, которую переписывают трижды, лёгкой не была.
5. Лёгкий трек (метка small)
Критерии — все одновременно:
- сложность и риск ≤ 3;
- одна поверхность (один диалог, один модуль, один эндпоинт);
- нет миграции конфига и новых compatibility-полей;
- нет нового UX-контракта — меняется поведение в рамках уже описанного;
- нет влияния на производительность и на touch-контракт.
Что упрощается:
- ТЗ пишется в теле issue по шаблону: проблема · контракт · AC1…ACn с
доказательством · откат. Файл в
docs/specs/не создаётся; - ревью ТЗ — комментарий второго агента, отдельный документ не нужен;
- лимит ревью ТЗ — 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, но не две роли в одном артефакте.
| Роль | Делает | Не имеет права |
|---|---|---|
| Аналитик | разбор, оценки, поверхности | окончательно ставить приоритет |
| Автор ТЗ | docs/specs/NN-*.md или ТЗ в issue |
ревьюить своё ТЗ |
| Ревьюер ТЗ | docs/reviews/SPEC-REVIEW-NN-rN.md |
править ТЗ вместо автора |
| Разработчик | код, автотесты, документация, changelog | ревьюить свой код, принимать golden |
| Ревьюер кода | docs/reviews/CODE-REVIEW-*-rN.md, проверка AC |
править продуктовый код |
| Релиз-менеджер | пре-релиз, стабильный релиз, закрытие issue | добавлять код в релизный коммит |
| Владелец | приоритет, ценность, скоуп, отклонение, арбитраж, хотфикс | — |
Правило разделения: ревьюер работает состязательно. Ему передаётся тег или диапазон коммитов и ТЗ — не рассказ автора о том, как всё хорошо.
Роли закреплены за исполнителями (решение владельца 2026-08-12):
| Исполнитель | Роли |
|---|---|
| Codex | аналитик, автор ТЗ, разработчик, релиз-инженер по команде владельца |
| Claude | ревьюер ТЗ, ревьюер кода, вся инфраструктура и дистрибуция |
| Владелец | приоритет, скоуп, арбитраж, закрытие issue, команда на выпуск |
Автор и ревьюер — разные модели, и это сильнее требования «другая сессия»: одна модель, читая свой же артефакт заново, повторяет свои же слепые пятна.
Ревью ТЗ и код-ревью держатся в разных сессиях Claude: ревьюер кода не должен приходить с контекстом того, как обсуждали ТЗ.
7. Артефакты и трассируемость
7.1 Цепочка
issue #NN
↔ ТЗ docs/specs/NN-slug.md (или тело issue при `small`)
↔ ревью ТЗ 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>/<лимит> · High: N · Medium: N → #… · Документ: docs/reviews/… - Закрытие:
Выпущено в <тег беты> · CI: <ссылка> · Changelog: <ссылка>
Вперёд двигает только зелёный вердикт. Жёлтый и красный возвращают автору;
разница между ними содержательна для человека, но не для маршрута. Первая
редакция конвейера (§10.4) пропускала жёлтый при High: 0, и первый же живой
прогон показал, почему это неверно: жёлтый там означал, что AC описывает неверное
изменение контракта — реализовать такое ТЗ значило бы сделать ошибку по инструкции.
7.3 Расхождения с текущим состоянием, которые надо закрыть
- Статус ТЗ дублирует статус issue.
docs/specs/README.mdдержит колонку «Статус ТЗ» со своим словарём («черновик решения», «в реализации», «реализовано»). Два источника статуса уже расходятся. Колонку убрать, оставить таблицу «issue ↔ ТЗ». - Ревью до релиза 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 \
&& cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
node demo/smoke_<целевые>.mjs
npm run golden:verify # если менялся визуал
python -m pytest tests_backend -q # py3.13, если менялся бэкенд
Объём гейтов на код-ревью соразмерен задаче (issue #127). Всегда:
typecheck, npm test, npm run build со сверкой трёх копий бандла. По
необходимости, определяемой diff'ом и AC: браузерные смоки (их 127 — прогон всех
уместен только когда задача задевает всё), golden:verify при изменении видимого
результата, pytest tests_backend при правках в Python, performance-профили при
названном в AC влиянии. Полные наборы — предрелизный гейт, а не гейт ревью.
Условие честности такого сужения: ревьюер обязан перечислить, какие гейты прогнал, какие нет и почему. Непрогнанный гейт становится видимым решением, а не молчаливым пропуском.
Гейт беты (условие закрытия issue): CI Validate зелёный на точном SHA тега.
Часть гейтов запускается только здесь, то есть после пройденного код-ревью. Упавший предрелизный гейт автор чинит и повторно прогоняет; зелёный прогон достаточен для продолжения релиза, повторное код-ревью не требуется — §11.4.
Гейт стабильного релиза: полный локальный прогон плюс Validate и Full Performance зелёные на точном SHA; статусов 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*-метка на открытом issue; закрытый issue статусных
меток не несёт; blocked не заменяет статус, а дополняет его.
Чужой issue берётся в работу так же, как свой — после явного решения владельца (решение владельца 2026-08-13, уточнено в тот же день). Репозиторий публичный, отчёты заводят и посторонние; проверка стоит на входе, а не на каждом шаге.
Входом служит присвоение первой статусной метки: пока меток нет, issue вне процесса и инварианты на него не распространяются. Как только метка стоит, задача в работе, и кто её завёл, дальше не имеет значения — статусы, ревью и лимиты работают одинаково.
Присвоение метки и есть то самое явное решение, причём проверенное платформой: метки может ставить только тот, у кого есть право записи в репозиторий. Прежняя редакция требовала переоформлять чужой отчёт своим issue со ссылкой на исходный; это оказалось работой впустую — на #123 к моменту отказа ТЗ уже было написано.
S8-merged появился позже остальных и закрывает разрыв, который раньше
закрывался памятью человека: код принят, но бета ещё не вышла, и issue закрывать
рано. Без него принятая задача либо висела в S7-code-review, либо закрывалась
досрочно.
10. Механизация при прямых коммитах в dev
Решение владельца — работать без PR. Значит, GitHub не может ничего заблокировать на своей стороне: основной гейт переезжает на клиента, CI остаётся страховкой.
10.1 Хуки, которые невозможно забыть поставить
.githooks/ в репозитории, core.hooksPath выставляется автоматически при
установке зависимостей:
"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, тот же скрипт вызывается jobprovenanceвvalidate.yml. -
pre-push— есть, работает. Прогоняетscripts/process-gate.mjsпо каждому пушимому ref и останавливает push при нарушении. Это и есть блокирующий гейт вместо PR. Удаление ветки и теги пропускаются: в первом случае проверять нечего, во втором коммит уже проверен, когда его пушили. Для новой ветки диапазон считается отmerge-baseсorigin/dev, а не от начала истории — иначе в него попали бы все нарушения, совершённые до появления гейта.При возврате
mainвdevдиапазон merge-коммита содержит второй родитель — уже опубликованные вmainкоммиты с закрытыми issue. Для destinationdevобщий скрипт 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:
- трейлер
Issue: #NNу каждого коммита класса A/B, допускается несколько; - имя ветки
issue/NN-slugсоответствует трейлерам; - для класса A существует
docs/specs/NN-*.md— или issue помеченsmall. Офлайн это предупреждение: лёгкий трек держит ТЗ в теле issue, и без чтения меток «ТЗ в issue» неотличимо от «ТЗ не написано». С--issues— отказ; User-Visible: yes→ правки в обоих changelog в том же коммите;- коммит только класса D невалиден без
Release: vX.Y.ZлибоBaseline-Reviewed: <ссылка на прогон CI>; - релизный коммит не содержит изменений в
src/иcustom_components/**/*.py; - документов ревью на один issue не больше четырёх (
-r1…-r4).
С токеном GitHub:
--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.
Не реализовано и остаётся долгом:
npm run release:prerelease -- --issues=…не проверяет, есть ли у issue зелёный вердикт код-ревью;- закрытие issue и снятие статусных меток при публикации беты делаются руками —
node process-labels/apply.mjs cleanup --apply, а неpublish-prerelease.yml. Пропуск этого шага уже ломал инвариант «закрытый issue без статусной метки».
10.3 Страховка и разбор
process-gate.mjs— jobprocess-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. Он читает docs/SCOPE.md, AGENTS.md,
этот документ и тело issue, публикует разбор комментарием, заводит issue на каждую
Medium-находку, кладёт документ в docs/reviews/ ветки задачи и возвращает вердикт
структурированным JSON. Метку переставляет отдельный детерминированный шаг по
вердикту, а не модель.
Четыре вещи, без которых конвейер молча не работает:
- метки переставляет PAT, а не
GITHUB_TOKEN: GitHub намеренно не порождает события отGITHUB_TOKEN, чтобы не было циклов, и цепочка обрывалась бы после первого шага без ошибок в логах; process.ymlобязан лежать в ветке по умолчанию: для событияissuesGitHub берёт workflow только оттуда, независимо от содержимогоdev;- слияние в
devпроисходит до простановкиS8-merged, иначе метка врёт в промежутке — она утверждает, что код вdev; - многострочный текст внутри
run:— только через heredoc: строка с нулевым отступом обрывает блок YAML, и скрипт обрезается без ошибки парсера.
Автор обязан дождаться вердикта, а не заканчивать сессию. Ревью идёт от десяти
минут до сорока пяти. Отчёт «передал на ревью» останавливает конвейер там, где он
мог идти сам: вердикт придёт, а подхватить его будет некому. У агента нет часов —
он существует только в момент своего хода, поэтому ожидание это опрос: раз в 90
секунд, не более 30 попыток. Смотреть на метку, а не на комментарий: метка и есть
состояние. При blocked не ждать — задача ждёт владельца.
После прогона ревью метка меняется всегда. Инвариант появился не сразу: первая редакция при конфликте слияния оставляла метку на месте, и это оказалось тупиком — автор ждёт смену метки, метка не менялась, и он тридцать раз опрашивал впустую, чтобы отчитаться «лимит исчерпан» при зелёном вердикте. Состояние, из которого никто не может выйти и о котором никто не узнает, для конвейера хуже громкой ошибки.
Поэтому зелёное код-ревью с неудавшимся слиянием ведёт не в S8-merged, а в
S6-in-progress: работа действительно вернулась к автору, только осталась не
правка кода, а ребейз. Вердикт при этом в силе, переделывать нечего. После ребейза
метка S7-code-review возвращается и ревью идёт заново — не формальность:
после ребейза на ушедший вперёд dev это другой код.
Если метка не сменилась, значит упал сам прогон, а не работа: смотреть логи и сообщать владельцу, а не продолжать опрос.
Цикл считается по этапу: вердикт по ТЗ не расходует бюджет код-ревью. Раньше
считались все вердикты подряд, и первое код-ревью #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 в документе ревью;
- параллельные бэклоги в файлах (
BACKLOG-*.md, «планы» в docs); - ревью-документы вне репозитория;
- попутные правки «раз уж я здесь»;
- фича или материальное изменение поведения в стабильном релиз-коммите;
- force-push в
dev; - ручное копирование на домашний инстанс.
Нарушение процесса — тоже issue (метка process): если правило удалось
нарушить незаметно, виновата проверка.
13. Внедрение
Состояние на 2026-08-13.
- ✅ Метки созданы, бэклог размечен. У всех открытых issue владельца ровно
одна
S*-метка, инварианты чистые. - ⏳ Колонку «Статус ТЗ» из
docs/specs/README.mdубрать — не сделано, §7.3 п.1. Перенос старых документов ревью вdocs/reviews/отменён: они описывают код, которого уже нет. - ✅ Гейт написан —
scripts/process-gate.mjsплюс job вvalidate.yml, issue #105. Прошёл вне флоу как инфраструктурная задача (§1, issue #118), а не через ТЗ и ревью, как предполагала прежняя редакция этого пункта. - ✅ Долг ревью списан решением владельца. Беты
beta.2…beta.10сделаны по прежнему процессу и не пересматриваются. Точка отсчёта — релиз 1.62.0; отсчёт начинается с первой беты следующей линии. - ⏳ Завести issue на находку «смок
visual_continuityне умеет падать» — это ровно тот класс дефектов, который в процессе без ручного тестирования стоит дороже всего. - ✅
BACKLOG-2026-08-11.md— разовый отчёт, решения живут в issue. - ✅
AGENTS.mdпереписан целиком, шире блока §14. - ✅ Канон перенесён в репозиторий (issue #112). До этого полный процесс жил только в папке владельца, а в репозитории лежал файл на 51 строку про трейлеры коммитов — из свежего клона канон не был виден вообще.
- ✅
pre-pushнаписан (§10.1, issue #121). Блокирующая проверка на клиенте есть; обойти её можно только--no-verify, и тогда то же найдёт CI.
14. Блок для AGENTS.md
## Процесс: код только через 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
циклов; пятый заход — разбор у владельца (разделить / отклонить / арбитраж).
Ревью запускается **само** от меток `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 закрывает релиз-менеджер после выпуска беты, не исполнитель.