128 KiB
Процесс работы над House Plan
Статус документа: канон (редакция 2026-08-13, ролевое уточнение 2026-09-13). Решения владельца, на которых он стоит: прямые коммиты в
devбез PR · канон статуса — метки, имена английские · лёгкий трек включён · автор и ревьюер — независимые агенты/сессии · любой агент может взять любую роль · инфраструктурные задачи входят в общий флоу сразу наS7-code-review.Область действия: обязателен для владельца и для любого агента. Читается сразу после
docs/SCOPE.mdиAGENTS.md, доdocs/STATUS.md. Живёт в репозитории: до августа 2026 канон лежал только в папке владельца, и свежий клон его не содержал вовсе.Ролевые конспекты (#634):
docs/process/AUTHOR.mdиdocs/process/REVIEWER.md— выжимки этого файла со ссылками на его разделы. Автор и ревьюер входят через них (порядок чтения по роли —AGENTS.md) и открывают раздел канона, когда пункт конспекта касается текущего шага. Правил конспекты не добавляют; при расхождении побеждает этот файл. Ссылки и ключевые формулировки конспектов сверяетtest/process-digests.test.mjs, поэтому правка формулировки здесь правит и конспект тем же коммитом.Приоритет источников. Канонический бэклог — GitHub Issues; статус живёт в метках и больше нигде: Project v2 не используется. При расхождении документации с GitHub побеждает GitHub. Этот файл — единственный полный канон процесса в репозитории;
AGENTS.md— его короткое обязательное резюме, а внешнийCODEX-RUNBOOK.md— только маршрутизатор к канону и историческим инструкциям. Текущие версии, состояние конкретных issue, runtime pins и списки jobs не копируются в производную прозу: они читаются из своих исполняемых источников. При расхождении этого документа с.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:
- дубликаты проверены (ссылки на похожие issue);
- в скоупе по
docs/SCOPE.mdиdocs/TOUCH-SUPPORT.md; - пользовательская ценность 1–10 и ценность для разработки — что упрощает или разблокирует;
- сложность и риск 1–10 — трудоёмкость плюс вероятность задеть смежное;
- приоритет P1/P2/P3;
- тип: баг / фича / техдолг;
- затронутые поверхности (модули, диалоги, бэкенд, i18n);
- трек — по умолчанию
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): назначить себя, поставить метку, комментарий
«Взял: <роль> · сессия · ветка
issue/<NN>-<slug>». - WIP-лимиты: не более 1 issue в «В разработке» на исполнителя, не более 3 одновременно на цикл релиза, не более 2 в «Код-ревью».
- Трассируемость: ветка
issue/<NN>-<slug>; каждый коммит несёт трейлерыIssue: #<NN>иUser-Visible: yes|no. - Автотесты — часть реализации, а не отдельная фаза. Каждый AC, помеченный
unit/backend/smoke/golden, получает свою проверку здесь же. «Тестирование вне жизненного цикла» означает отсутствие фазы ручного тестирования, а не отсутствие тестов. - Приёмка проверяет результат для человека, а не строки реализации. Для изменённой поверхности автор выбирает обычный сценарий и самый рискованный применимый соседний случай; у каждого должен быть наблюдаемый oracle — что пользователь видит, может сделать или что система отказывается делать. При выборе случаев коротко пройти шесть классов риска: async (порядок, отмена, устаревший ответ); данные и права (пусто, нет связи, несколько источников, отказ); геометрия (границы, стыки, трансформации); визуал (промежуточный кадр, тема, zoom/DPR); объём данных и performance; host/input (HA, кэш, mouse/touch/ keyboard). Неприменимое так и отмечается; проверка имени метода или строки исходника пользовательским oracle не считается.
- Скоуп не расширяется. Найденное по пути становится новым 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 либо доказан автотестом — и ревьюер убедился, что тест умеет падать, — либо разобран по коду с явной записью «проверено чтением, не исполнением». Чтение кода выявляет риски, но не доказывает наблюдаемый пользовательский результат; если соразмерный исполнимый oracle возможен, его отсутствие — находка. Ревьюер отдельно сверяет применимые классы риска из §2.6 и не выдаёт запуск гейта за проверку сценария, которого в гейте нет.
-
Защитный 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. -
Контракты по монолиту — исполнением, не regex по тексту (#624). Новое утверждение о
src/houseplan-card.tsилиsrc/houseplan-editor-runtime.tsдоказывается экспортом функции и её вызовом вtest-build, а не поиском строки в исходнике: текстовый якорь краснеет на переносе метода без единой регрессии, и это делает вынос дороже, чем оставить монолит как есть. Список тестов, читающих монолит как текст, заморожен (test/monolith-text-anchors.test.mjs, 54 файла на 23.09.2026) и может только уменьшаться; новое имя в нём — находка ревью, а не запись в список. Связность монолита измеряется шестью числами (scripts/monolith-metrics.mjs: делегаты, члены порта,host., приватные члены порта и харнесса, байтыdist/), база —scripts/monolith-baseline.json; гейтnpm run lint:unused(вgate:smallи Validate после сборки) красит рост любого из них и любой мёртвый код поnoUnusedLocalsвне порта и харнесса. Снижение фиксируется тем же коммитом (node scripts/unused-locals-gate.mjs --update); рост — только с записью в issue задачи и правкой базы в том же коммите. -
Смок входит в сценарий через публичную поверхность (#629). DOM с контрактными хуками, события HA и фикстуры, тестовый фасад
window.__hpTest(docs/TESTING.md, «Тестовый фасад и приватное состояние»). Приватное поле карточки — только для чтения в ассертах. Новая запись в него без// private-ok: <конкретная причина>— находка ревью, даже если гейтno-new-private-writesеё не увидел (мутация через вызов, запись через псевдоним). -
Выход: очередь на пре-релиз либо возврат в «В разработке», не более 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 стоил полного прогона конвейера ради одной строки в тестовой фикстуре.
Порядок:
-
найти вердикт предыдущего раунда и материал, на котором он получен. Материал объявлен блоком «Материал раунда» в конце документа предыдущего раунда: конвейер дописывает туда SHA ветки, дерево материала и блоб каждого ТЗ вместе с командами поиска (issue #416). Блок машинный — править его руками не нужно и не следует;
-
объявить дельту:
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). Конвейер теперь такую публикацию останавливает сам; -
по каждой находке предыдущего раунда показать, чем именно она закрыта — строкой кода или текста, а не заявлением автора;
-
заново проверять только те AC, чьё доказательство дельта задевает;
-
раздел «Унаследовано из r<N−1>» обязателен: что принято без повторной проверки, со ссылкой на документ того раунда и его материал. Без перечня сокращение превращается в молчаливое доверие.
Индекс документов ревью — docs/reviews/INDEX.md (#635): одна строка на
документ — issue, этап, раунд, вердикт, число High/Medium, заголовки находок,
файлы из находок (искать по имени файла: grep form-kit docs/reviews/INDEX.md).
Файл генерируется node scripts/reviews-index.mjs и пересобирается конвейером
тем же коммитом, что публикует документ, а также после каждого его ребейза
(приведение к dev перед ревью, слияние кандидата — --commit-if-stale, коммит
класса C); руками не правится. Конфликт ребейза, в котором все пути —
INDEX.md, отказом не считается (#643): scripts/rebase-generated.mjs
пересобирает индекс по каталогу на остановке и продолжает ребейз — так делают
приведение к dev, слияние кандидата и авторский rebase-on-dev.mjs; индекс
вместе с любым другим путём — прежний отказ с перечнем файлов. Шаг Validate
«индекс ревью совпадает с каталогом» красит push в dev, где INDEX.md
расходится с каталогом (на issue-ветках не судится: их переписывает конвейер).
Правка docs/reviews/
руками — перенос в legacy/, удаление — сопровождается
node scripts/reviews-index.mjs в том же коммите. Прежде чем брать
задачу по подсистеме, стоит прочитать её строки в индексе: что находили и чем
закрывали — там, а не в тысяче файлов. Уроки, пережившие свою задачу,
собираются в docs/LESSONS.md с датой и ссылкой на источник.
Хранение документов (решение владельца 23.09, #635): в docs/reviews/
лежат все раунды всех задач текущей линии — предыдущие раунды нужны ссылкам
«Унаследовано из r<N−1>» и якорям материала. При стабильном релизе документы
задач, вошедших в него (RELEASE-MEMBERSHIP.json линии), переносятся в
legacy/reviews/<vX.Y.Z>/ одним коммитом класса C; индекс пересобирается и
перечисляет только живые. Перенос — часть чеклиста стабильного релиза, не
отдельная задача.
Дешёвые гейты (typecheck, test, build со сверкой копий бандла) гоняются в
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
Разбор остаётся полным, если дельта не локальна: ребейз на ушедший вперёд
dev (после ребейза это другой код, §7.2), смена контракта поведения, задета
новая подсистема, либо объём дельты сопоставим с исходной задачей.
Сокращается объём разбора, а не строгость: правка по замечанию способна сломать AC, который предыдущий раунд признал выполненным — так появилась регрессия #102. Граница не «только находки», а «находки плюс всё, до чего дотягивается дельта».
3. Правила
Продолжение черновика владельца. Каждое правило проверяемо — глазами или машиной.
- Никаких изменений в код, если нет issue и он не помечен «Готово к разработке» или дальше.
- Issue не может быть взят в разработку, пока у него нет ТЗ с зелёным ревью, пронумерованных AC с указанием доказательства и назначенного исполнителя.
- Issue не может быть взят дважды. Занятие фиксируется назначением, меткой и комментарием с именем ветки. У одного исполнителя одновременно не более одного issue в разработке.
- Статус меняется до действия, а не после. Взял — поставил метку; отдал на ревью — поставил метку. Метка, поставленная задним числом, — дефект процесса.
- Ровно одна метка статуса на issue в любой момент. Ноль или две — дефект, еженедельная гигиена его показывает.
- Автор не ревьюит своё — ни ТЗ, ни код. Никто не переводит свою работу через ревью-гейт.
- Ревью возвращает не более 4 раз. Пятый заход — решение владельца: разделить, отклонить или арбитраж (§4).
- High блокирует. Medium в скоупе чинится в текущем issue (без High — жёлтый вердикт и повторный цикл); Medium вне скоупа становится отдельным issue (#202). Low либо правится, либо снимается решением ревьюера с записью в документе.
- Скоуп не расширяется. Всё найденное вне ТЗ — новый issue, а не попутная правка. Блокирующая находка отправляет текущий issue в «Заблокировано».
- Каждый коммит класса A и B несёт трейлер
Issue: #NN, ветка называетсяissue/NN-slug, аUser-Visible: yesтребует правок в обоих changelog в том же коммите. Послеcherry-pick -xслужебная строка(cherry picked from ...)должна оставаться выше финального блока трейлеров: перед push проверяем порядок черезgit show -s --format=full HEAD. - Документация — в том же коммите, что поведение. Отдельным «допишу потом» коммитом документация не бывает.
- Сгенерированное не коммитится само по себе. Только релизный промоушен или принятие эталонов с доказательством ревью: URL Linux CI run либо хеш аттестованного WSL-артефакта.
- Golden-эталоны принимаются только
npm run golden:accept -- --reviewedпо полному Linux-артефакту: либо GitHub CI, либоnpm run golden:wsl:captureв WSL/ext4 с clean опубликованным SHA и машинно-проверяемым паспортом. Второй путь убирает только первый ожидаемо красный CI-прогон; полный GitHub Validate на точном SHA коммита с эталонами остаётся обязательным. Принятие ради зелёного CI — нарушение процесса. - Issue закрывается после выпуска беты с зелёным CI на точном SHA. Не раньше, не «по факту наличия кода», не исполнителем.
- Закрытый issue не переоткрывается. Новый дефект — новый issue со ссылкой.
- Стабильный релиз — promotion-only: версии, сгенерированные бандлы, changelog и release-метаданные. Продуктового кода там нет.
- История
devне перезаписывается. На неё ссылаются теги. Нарушение исправляется следующим коммитом плюс issue с меткойprocess— не force-push'ем. - AC доказывает автотест или запись ревьюера. Фразы «проверил локально, всё работает» в процессе не существует: либо тест, который умеет падать, либо честное «проверено чтением, не исполнением».
- Параллельных бэклогов нет. Планы, разборы и приоритеты живут в issue; файловые отчёты — разовые и датированные.
- Аварийный хотфикс — только решением владельца и только по §11.2.
4. Лимит циклов ревью: 4
Оба ревью-гейта возвращают задачу на правки не более 4 раз.
- Что считается циклом: отправка на ревью → вердикт с блокирующими находками → возврат. Уточняющий вопрос без вердикта циклом не считается.
- Зелёный вердикт цикла не образует и бюджет не тратит (решение владельца
2026-08-20, issue #227): он ничего не вернул на правки. Практический случай —
зелёное ревью, слияние которого не удалось: конвейер сам предписывает ребейз и
возврат метки, и этот заход не должен наказываться. Раньше счётчик считал все
вердикты подряд, и на #225 последовательность жёлтый → зелёный → ребейз дала
review-4на задаче с зелёным ревью и зелёным CI. - Заход и цикл — разные величины. Заход — сколько раз ревью отработало; он
виден в имени документа (
-r1,-r2, …) и нужен, чтобы два документа не затёрли друг друга. Цикл — единица бюджета §4. Заходов законно бывает больше, чем циклов, поэтому порог проверки 7 вscripts/process-gate.mjsвыше лимита циклов (шесть документов = четыре цикла плюс два ребейза). - Метка
review-4ставится, когда исчерпан бюджет циклов; конвейер снимать её не вправе — это решение владельца. Если бюджет пересчитан и оказался ниже лимита, конвейер сообщает пересчёт, но метку не трогает. - Исчерпание лимита — не «пятая попытка», а разбор. Задача уходит владельцу,
решение одно из трёх:
- разделить — issue закрывается как «заменён», вместо него 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 и получает
полное ТЗ в теле issue по §7.1. Это не провал, это ранняя диагностика.
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 Расхождения с текущим состоянием, которые надо закрыть
- ✅ Статус ТЗ дублировал статус issue. Закрыто 2026-09-10 (#517): ТЗ живёт
в теле issue,
docs/specs/— архив, индекс с колонкой «Статус ТЗ» удалён вместе с самой таблицей. Статус задачи — только меткаS*. - Ревью до релиза 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 выставляется автоматически при
установке зависимостей:
"scripts": { "prepare": "node scripts/install-hooks.mjs" }
npm ci вызывает prepare сам — значит, хуки появляются в каждом окружении,
включая свежий контейнер облачного агента, без отдельного шага в инструкции.
-
commit-msg— есть, работает. Отклоняет коммит без терминальногоIssue: #NN, требует ровно одинUser-Visible: yes|no, а для коммитов, трогающихdemo/golden/baselines/**, —Release:плюс ровно один источник:Baseline-Reviewed: <URL GitHub run>либоBaseline-Reviewed-Local: sha256:<хеш аттестации>. Локальный хеш обязан совпадать сlocalAttestation.sha256в принятом индексе. Реализация —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 есть ТЗ: раздел
## ТЗили хотя бы одинAC1в теле issue — либо архивныйdocs/specs/NN-*.mdу задачи до 2026-09-10. Офлайн тела нет, и проверка молчит; с--issues— предупреждение (настоящий рубеж — ревью ТЗ). Добавление нового файла вdocs/specs/**тоже предупреждение: каталог заморожен (#517); User-Visible: yes→ правки в обоих changelog в том же коммите;- коммит только класса D невалиден без
Release: vX.Y.Z,Baseline-Reviewed: <ссылка на прогон CI>либоBaseline-Reviewed-Local: sha256:<хеш аттестации>; - релизный коммит не содержит изменений в
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 автоматизировано (#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— 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 (тело — _process.yml, #623), 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,
конспект docs/process/REVIEWER.md с разделами этого документа по его ссылкам
(#634) и тело issue, публикует разбор комментарием, заводит issue на
Medium-находки вне скоупа задачи (#202), кладёт документ в docs/reviews/ ветки
задачи и возвращает вердикт структурированным JSON. Метку переставляет отдельная
детерминированная стадия по вердикту, а не модель.
Четыре вещи, без которых конвейер молча не работает:
- метки переставляет PAT, а не
GITHUB_TOKEN: GitHub намеренно не порождает события отGITHUB_TOKEN, чтобы не было циклов, и цепочка обрывалась бы после первого шага без ошибок в логах; - для события
issuesGitHub берёт workflow только из ветки по умолчанию (main), независимо от содержимогоdev. Поэтому вmainлежит тонкийprocess.yml— триггер, run-name, потолок прав, — а тело_process.ymlон вызывает по ссылке@dev(#623). Правило ниже, «Workflow из ветки по умолчанию», — общее для всех таких файлов; - слияние в
devпроисходит до простановкиS8-merged, иначе метка врёт в промежутке — она утверждает, что код вdev; - многострочный текст внутри
run:— только через heredoc: строка с нулевым отступом обрывает блок YAML, и скрипт обрезается без ошибки парсера.
Workflow из ветки по умолчанию: тонкий файл и тело из dev (#623). Для
событий issues, schedule и workflow_run GitHub исполняет workflow из
main. Таких файлов шесть: process.yml, process-resume.yml,
process-reconcile.yml, mutation-gate.yml, nightly.yml,
process-metrics.yml. Каждый — тонкий вызывающий: триггеры, run-name,
права, concurrency и одна job uses: Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev с secrets: inherit.
Тело _<имя>.yml читается из dev в момент запуска, поэтому правка
конвейера — один коммит в dev, зеркало в main и возврат main в dev
перед промоушеном не нужны. Потолок прав вызывающей job равен объединению
прав job тела: вызываемый workflow права только сужает, и каждая job тела
получает прежний минимум (#556). Тонкий файл меняется, только когда меняются
триггеры, входы ручного запуска или потолок прав; тогда он зеркалится в
main, и preflight workflow_sync в validate.yml держит копии равными —
сверяются ровно эти шесть файлов, список держит
test/default-branch-workflows.test.mjs. performance.yml в список не входит:
по расписанию он судит main собственным телом из main.
Ревью не начинается на красном коде (#510). После фиксации материала конвейер
запускает Validate с мутантами по диффу на этом SHA (scripts/validate-gate.mjs:
workflow_dispatch validate.yml -f mutants=true). Ждёт его не раннер, а событие
(#636): подготовка убеждается, что dispatch встал на материал, кладёт запечатанный
маркер ожидания review-pending-… и завершается; по завершении Validate
process-resume.yml (workflow_run) переставляет метку S7-code-review, и новый
прогон конвейера находит завершённый dispatch сразу. Страховка на потерянное
событие — process-reconcile.yml: успешный прогон подготовки с маркером и уже
завершённым Validate он будит повторной меткой, без маркера — как прежде, только
диагностика. Будить без маркера нельзя: это второй вызов модели. Красный или
пропавший прогон возвращает задачу в S6-in-progress с комментарием и ссылкой —
код никто не читал, цикл ревью не израсходован. Мутанты по диффу вообще бегут
только по явному запросу: на кандидате ревью, кандидате слияния (#492) — оба
диспатчат Validate с mutants=true — и на PR, где Validate единственный сигнал;
обычный push обходится дешёвыми гейтами (~3 минуты). За 08–09.09 мутанты на
каждом промежуточном пуше стоили 48 из 56 часов job-минут Validate и в основном
отменялись следующим пушем. Кандидат беты (Release:), full=true и ночь
мутантов не запрашивают (#601, решение владельца 20.09): мутационный гейт
проверяет тесты, а не продукт (#513), к бете каждая задача прогнана им дважды —
на ревью и на слитом после ребейза кандидате, — а ночью идёт полный реестр
(mutation-gate.yml, 01:00 UTC). Релизный гейт (#541) требует полного Validate,
но не mutant-jobs; для ревью и слияния шесть исполненных mutant-jobs остаются
обязательными.
Ожидание gates, работа модели и публикация/интеграция — три независимых jobs (#551) с отдельными бюджетами 55, 45 и 55 минут. Поэтому долгий Validate не съедает время модели (с #636 — и не занимает раннер: до этого подготовка спала ≈ 28 минут на раунд при 10–12 минутах работы модели), а ожидание кандидата после зелёного вердикта не обрывает готовый review. Между jobs передаётся запечатанный artifact: run/attempt, issue, этап, раунд, branch, SHA/tree материала, якоря ТЗ и результат Validate. Получатель сверяет полный набор файлов, SHA-256 и все поля с outputs предыдущей стадии; неполный, чужой или устаревший результат fail-closed не публикуется и не разрешает merge. Timeout/cancel/failure называет конкретную стадию и оставляет метку на месте; если модель не запускалась, цикл ревью не расходуется. Длительности всех трёх стадий печатаются отдельной таблицей в summary прогона.
Каждый раунд ревью платит только за то, что в нём изменилось (#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
секунд, не более 110 попыток (запас на три независимых бюджета #551) —
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 это другой код.
Если метка не сменилась, значит упал сам прогон, а не работа: смотреть логи и сообщать владельцу, а не продолжать опрос.
Очередь S4/S7 сверяется отдельным bounded controller (#555). Workflow
process-reconcile.yml раз в полчаса делает один снимок открытых задач и
завершается — активного polling одинакового состояния и вызова модели на каждый
тик нет. Он сопоставляет последнюю постановку S4/S7 с run по номеру задачи и
этапу; если стадия успела подготовить материал, дополнительно проверяет
запечатанные run/attempt, SHA/tree, список блобов ТЗ и номер раунда. Текущий
blocked, review-4 или снятая review-метка всегда сильнее старого события.
Автоматически и не более одного раза повторно применяется только сама review-метка после доказанно
потерянного события либо transient-исхода до получения запечатанного результата
(cancelled, timed_out, stale, startup_failure, skipped). Это не применяет
вердикт и не расходует цикл: обычный конвейер заново читает актуальные labels и
материал. Здоровый running run не трогается. Failure guard, неизвестная связь,
чужой material/stage/attempt, success без смены метки и сбой после появления
review-result, а также потеря повторного события получают один дедуплицированный диагностический комментарий и
эскалацию человеку — второй вызов модели или S8 по догадке запрещены. Перед любой
записью controller перечитывает состояние; итог каждого прохода публикуется как
houseplan-process-reconcile/v1 artifact.
Конвейер — идемпотентный контроллер, а событие лишь будит его (#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-артефакте GitHub CI либо полном аттестованном WSL-артефакте; после локальной приёмки полный GitHub Validate на точном финальном SHA всё равно обязателен. «Чтобы гейт позеленел» основанием не является.
Границы, за которыми исключение не действует. Оно про починку названного
гейтом дефекта, а не про продолжение разработки под видом починки. Правка идёт
обычным путём — новым issue либо возвратом в S6-in-progress — если она:
- меняет контракт поведения или добавляет пользователю что-то новое;
- задевает подсистему, которой в исходной задаче не было;
- по объёму сопоставима с самой задачей;
- меняет сам гейт вместо кода — правка теста, чтобы он перестал падать, это не починка, а сокрытие. Исключение — когда дефект в фикстуре и это доказано разбором, как на #89: солнце на азимуте 180° и единственное окно на северной стене, поэтому луч честно не строился.
Границу определяет автор, и здесь процесс сознательно отдаёт ему то, что в остальных местах не доверяет — оценку собственной работы. Плата за скорость в единственной точке, где цикл ревью стоит дороже всего. Компенсируется тем, что запись в issue публична и релиз-менеджер видит, что именно было сделано перед выпуском.
Это исключение из правила «код-ревью не пропускается никогда» (§5, §7.1) —
единственное, и относится только к окну между S8-merged и выпуском.
11.5 Независимое ревью линии перед стабильным релизом
Решение владельца 2026-09-25, issue #638.
Зачем. Инкрементальное ревью судит дифф задачи против её ТЗ, а не
поверхность против пользователя. Пять раундов по эпику #591 не нашли того, что
нашли четыре независимых ревью перед аудитом 22.09: невидимый после крестика HA
диалог (#607), кламп по символу (#608), маршруты робота при импорте (#611),
детерминированный отказ релизного гейта (#619). Ни ветка ha-dialog, ни
посимвольный ввод не входили ни в один AC.
Шаг. Перед каждым стабильным релизом — одно ревью поверхностей, изменённых всей линией бет, «с нуля»:
- вход — issue линии, доказанные трейлерами
Issue: #NNв диапазоне «прошлый стабильный тег..кандидат» (тот же построитель и та же схема, чтоRELEASE-MEMBERSHIP.jsonбеты, #547; метка S8 доказательством не является), и изменённые продуктовые файлы. Собирает ихscripts/release-review.mjs prepare; - без ТЗ и без документов раундов: основа суждения —
docs/SCOPE.mdиdocs/USER-GUIDE.ru.md. Проверка исполнением: бандл, стенд и смоки, пиннутая фикстураha-dialog(#505) там, где есть диалоги, посимвольный ввод, настоящие Escape и крестик; для каждой поверхности — обычный сценарий и самый рискованный соседний (§2.6, шесть классов риска); - выход —
docs/reviews/RELEASE-REVIEW-vX.Y.Z.mdвdev, находки High/Medium/Low с воспроизведением.
Исполнитель — модель в CI, .github/workflows/release-review.yml: три job
(вход, модель, публикация), модель без единого права на запись, документ, его
машинный блок, индекс и коммит — детерминированный шаг. Независимость
обеспечена построением: сессия свежая, ТЗ и раунды ей не даются.
Выпуск не блокирует. release.yml ставит ревью в очередь job
independent-review сразу после закрепления SHA кандидата — параллельно
гейтам; ни один job выпуска от него не зависит, его отказ — предупреждение.
Документ — рекомендация: владелец берёт находки в работу (issue в очередь
следующей беты) либо оставляет без действий. Автоматически находки в issue
не превращаются.
Повторный запуск на тот же тег модель не тратит, если документ уже в dev
(force=true — переснять). Ручной запуск:
gh workflow run release-review.yml --ref dev -f tag=vX.Y.Z [-f candidate=<sha>].
Беты шаг пропускают. Первый прогон — линия v1.78.0.
11.6 Повторные Validate на одном SHA перед релизом
Решение владельца 2026-09-26, issue #656.
Релизный гейт рассматривает прогоны одного SHA от нового к старому. Отменённый
прогон и proof, который не соответствует запрошенной политике (например,
лёгкий вместо полного), вердиктом не являются: гейт проходит мимо них к
следующему совместимому proof. Среди совместимых полных прогонов решает
новейший. Поэтому поздний полный failed, missing или pending блокирует
более ранний зелёный proof; новый полный зелёный прогон может обновить старый
красный.
Это fail-closed правило. Content-addressed proof доказывает, что конкретный прогон относится к кандидату, но не даёт старому зелёному прогону права скрыть более позднюю проверку той же политики, которая нашла отказ. Чтобы продолжить выпуск после такого отказа, исправляют причину и получают новый совместимый полный зелёный прогон на том же финальном SHA либо на новом SHA кандидата.
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.
- ✅ Метки созданы, бэклог размечен. У всех открытых issue владельца ровно
одна
S*-метка, инварианты чистые. - ✅ Колонка «Статус ТЗ» убрана — вместе со всем индексом:
docs/specs/стал архивом, ТЗ переехало в тело issue (#517, 2026-09-10). Перенос старых документов ревью в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
циклов; пятый заход — разбор у владельца (разделить / отклонить / арбитраж).
Инфраструктурная задача (ни одного файла класса A) делается сразу любым агентом:
без `S*` → `S7-code-review` ↔ `S6-in-progress` → `S8-merged`. ТЗ и ревью ТЗ нет,
код-ревью обязательно.
Ревью запускается **само** от меток `S4-spec-review` и `S7-code-review`.
Deterministic prerequisites, модель и интеграция имеют отдельные пределы 55/45/55
минут; обычно стадии заканчиваются существенно раньше. Поставив такую метку, автор
не заканчивает работу, а ждёт смены метки опросом и продолжает по тому, чем она
стала.
- ветка `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 закрывает релиз-менеджер после выпуска беты, не исполнитель.