Files
houseplan-card/PROCESS.md
T
Matysh 024cdc0d94 feat: an outsider's issue is worked like any other once admitted
The guard refused to review any issue the owner had not filed himself. The rule
was meant to keep malformed outside reports out of the pipeline, but it checked at
every step instead of at the entrance, and it duplicated a guarantee the platform
already gives: only someone with write access can apply a label. Applying the
first status label is the owner's explicit decision, and it is the only place the
question belongs.

So the author check is gone. While an issue carries no status label it sits
outside the process and the invariants do not apply; once labelled, the task is in
flight and who filed it stops mattering.

The old rule also cost real work. On #123 an outside bug report had been analysed
and specified before the guard turned it away in nine seconds, and the remedy on
offer was to refile the same thing as the owner's own issue.

Issue: #114
User-Visible: no
2026-08-13 20:49:04 +03:00

64 KiB
Raw Blame History

Процесс работы над 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

Переходы S4-spec-review и S7-code-review выполняются автоматически: метка порождает событие, событие запускает ревью (§10.4). Остальные ставит исполнитель.

2.1 Новое — заведение задачи

  • Кто: любой — владелец, агент, пользователь (Telegram, GitHub).
  • Вход: проблема в пользовательских терминах; как проявляется или зачем нужно. Решение не требуется и не приветствуется.
  • Запрещено: ставить приоритет, оценивать, писать ТЗ, начинать код.

2.2 Аналитика и оценка

Задача разбирается, продуктовое «да» ещё не дано.

  • Кто: агент-аналитик готовит, владелец решает.
  • Чек-лист, результат — комментарием в issue:
    1. дубликаты проверены (ссылки на похожие issue);
    2. в скоупе по docs/SCOPE.md и docs/TOUCH-SUPPORT.md;
    3. пользовательская ценность 1–10 и ценность для разработки — что упрощает или разблокирует;
    4. сложность и риск 1–10 — трудоёмкость плюс вероятность задеть смежное;
    5. приоритет P1/P2/P3;
    6. тип: баг / фича / техдолг;
    7. затронутые поверхности (модули, диалоги, бэкенд, i18n);
    8. лёгкий трек — да/нет по критериям §5.
  • Приоритет и ценность — поля владельца. Агент предлагает, владелец утверждает; иначе агенты приоритизируют сами и P1 разрастается.
  • Выход: «ТЗ в работе» либо «Отклонено» с записанной причиной.

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. Правила

Продолжение черновика владельца. Каждое правило проверяемо — глазами или машиной.

  1. Никаких изменений в код, если нет issue и он не помечен «Готово к разработке» или дальше.
  2. Issue не может быть взят в разработку, пока у него нет ТЗ с зелёным ревью, пронумерованных AC с указанием доказательства и назначенного исполнителя.
  3. Issue не может быть взят дважды. Занятие фиксируется назначением, меткой и комментарием с именем ветки. У одного исполнителя одновременно не более одного issue в разработке.
  4. Статус меняется до действия, а не после. Взял — поставил метку; отдал на ревью — поставил метку. Метка, поставленная задним числом, — дефект процесса.
  5. Ровно одна метка статуса на issue в любой момент. Ноль или две — дефект, еженедельная гигиена его показывает.
  6. Автор не ревьюит своё — ни ТЗ, ни код. Никто не переводит свою работу через ревью-гейт.
  7. Ревью возвращает не более 4 раз. Пятый заход — решение владельца: разделить, отклонить или арбитраж (§4).
  8. High блокирует. Medium становится issue. Low либо правится, либо снимается решением ревьюера с записью в документе.
  9. Скоуп не расширяется. Всё найденное вне ТЗ — новый issue, а не попутная правка. Блокирующая находка отправляет текущий issue в «Заблокировано».
  10. Каждый коммит класса A и B несёт трейлер Issue: #NN, ветка называется issue/NN-slug, а User-Visible: yes требует правок в обоих changelog в том же коммите.
  11. Документация — в том же коммите, что поведение. Отдельным «допишу потом» коммитом документация не бывает.
  12. Сгенерированное не коммитится само по себе. Только релизный промоушен или принятие эталонов со ссылкой на прогон CI.
  13. Golden-эталоны принимаются только npm run golden:accept -- --reviewed по полному Linux-артефакту. Принятие ради зелёного CI — нарушение процесса.
  14. Issue закрывается после выпуска беты с зелёным CI на точном SHA. Не раньше, не «по факту наличия кода», не исполнителем.
  15. Закрытый issue не переоткрывается. Новый дефект — новый issue со ссылкой.
  16. Стабильный релиз — promotion-only: версии, сгенерированные бандлы, changelog и release-метаданные. Продуктового кода там нет.
  17. История dev не перезаписывается. На неё ссылаются теги. Нарушение исправляется следующим коммитом плюс issue с меткой process — не force-push'ем.
  18. AC доказывает автотест или запись ревьюера. Фразы «проверил локально, всё работает» в процессе не существует: либо тест, который умеет падать, либо честное «проверено чтением, не исполнением».
  19. Параллельных бэклогов нет. Планы, разборы и приоритеты живут в issue; файловые отчёты — разовые и датированные.
  20. Аварийный хотфикс — только решением владельца и только по §11.2.

4. Лимит циклов ревью: 4

Оба ревью-гейта возвращают задачу на правки не более 4 раз. Счётчик виден в имени документа: -r1 … -r4; на четвёртом заходе ставится метка review-4.

  • Что считается циклом: отправка на ревью → вердикт с блокирующими находками → возврат. Уточняющий вопрос без вердикта циклом не считается.
  • Исчерпание лимита — не «пятая попытка», а разбор. Задача уходит владельцу, решение одно из трёх:
    1. разделить — issue закрывается как «заменён», вместо него 2–3 меньших с ясным скоупом (частый настоящий диагноз: ТЗ было слишком большим);
    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 и получает нормальный файл ТЗ. Это не провал, это ранняя диагностика.


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 Расхождения с текущим состоянием, которые надо закрыть

  1. Статус ТЗ дублирует статус issue. docs/specs/README.md держит колонку «Статус ТЗ» со своим словарём («черновик решения», «в реализации», «реализовано»). Два источника статуса уже расходятся. Колонку убрать, оставить таблицу «issue ↔ ТЗ».
  2. Ревью до релиза 1.62 живут вне репозитория. Документы CODE-REVIEW-*.md и SPEC-REVIEW-*.md за прежний период лежат в папке владельца, и переносить их задним числом смысла нет: они описывают код, которого уже нет. Новые документы ревью кладёт в docs/reviews/ сам конвейер, в ветку задачи.

8. Гейты

Локальный гейт перед выходом из «В разработке» — минимальный набор, покрывающий изменённые поверхности (действующее правило владельца):

npx tsc --noEmit
npm test
npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js \
              && 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): CI Validate зелёный на точном SHA тега.

Часть гейтов запускается только здесь, то есть после пройденного код-ревью. Упавший предрелизный гейт автор чинит и повторно прогоняет; зелёный прогон достаточен для продолжения релиза, повторное код-ревью не требуется — §11.4.

Гейт стабильного релиза: полный локальный прогон плюс Validate и Full Performance зелёные на точном SHA; статусов issue не касается.


9. Метки — канонический статус

Статус читается из меток: их видно в списке issue и их читает любой токен с доступом к Issues, в отличие от Project v2, который требует отдельного скоупа. Project v2 остаётся человеческим представлением и синхронизируется по меткам.

Имена меток английские (решение владельца 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), 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, тот же скрипт вызывается job provenance в validate.yml.

  • pre-push — есть, работает. Прогоняет scripts/process-gate.mjs по каждому пушимому ref и останавливает push при нарушении. Это и есть блокирующий гейт вместо PR. Удаление ветки и теги пропускаются: в первом случае проверять нечего, во втором коммит уже проверен, когда его пушили. Для новой ветки диапазон считается от merge-base с origin/dev, а не от начала истории — иначе в него попали бы все нарушения, совершённые до появления гейта.

    Проверка статуса issue требует gh, поэтому при его отсутствии хук печатает предупреждение и выполняет только офлайн-часть. Это сознательная уступка: хук, который не работает в самолёте, отключают целиком, а строгий проход всё равно делает CI.

Хук обязан быть исполняемым, и это тише всего ломается. Git молча не запускает файл без бита +x: гейт сообщает об успехе тем, что его нет. Проверено на настоящем push — при 644 от гейта ноль строк и push проходит, при 755 он останавливается.

Через GitHub API режим не выставляется: файл, отправленный так, приезжает 100644. Поэтому scripts/install-hooks.mjs восстанавливает бит при каждой установке зависимостей, а assertHookMode дополнительно проверяет бит .githooks/commit-msg в индексе. Правится вручную: git update-index --chmod=+x .githooks/<хук>.

10.2 Что проверяет process-gate.mjs

Реализовано, scripts/process-gate.mjs, issue #105. Офлайн, без GitHub API:

  1. трейлер Issue: #NN у каждого коммита класса A/B, допускается несколько;
  2. имя ветки issue/NN-slug соответствует трейлерам;
  3. для класса A существует docs/specs/NN-*.md — или issue помечен small. Офлайн это предупреждение: лёгкий трек держит ТЗ в теле issue, и без чтения меток «ТЗ в issue» неотличимо от «ТЗ не написано». С --issues — отказ;
  4. User-Visible: yes → правки в обоих changelog в том же коммите;
  5. коммит только класса D невалиден без Release: vX.Y.Z либо Baseline-Reviewed: <ссылка на прогон CI>;
  6. релизный коммит не содержит изменений в src/ и custom_components/**/*.py;
  7. документов ревью на один issue не больше четырёх (-r1…-r4).

С токеном GitHub:

  1. --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, то есть заведомо вне рабочего множества.

Не реализовано и остаётся долгом:

  1. npm run release:prerelease -- --issues=… не проверяет, есть ли у issue зелёный вердикт код-ревью;
  2. закрытие issue и снятие статусных меток при публикации беты делаются руками — node process-labels/apply.mjs cleanup --apply, а не publish-prerelease.yml. Пропуск этого шага уже ломал инвариант «закрытый issue без статусной метки».

10.3 Страховка и разбор

  • process-gate.mjs — job process-gate в validate.yml, без needs: краснеет сам и не роняет остальные. При прямом push проверка догоняющая: код уже в dev, CI краснеет после. Это принятая цена отказа от PR: pre-push ловит нарушение до отправки, а этот job — то, что прошло мимо хука, включая --no-verify и окружение без установленных зависимостей.
  • Нарушение не откатывается force-push'ем (правило 17): исправляющий коммит плюс issue с меткой process. Починить надо проверку, а не только симптом.
  • Еженедельная гигиена (workflow): issue в S1-new дольше 14 дней и в S6-in-progress дольше 7; issue класса A в S5-ready без ТЗ; issue с нулём или двумя S*-метками; коммиты без трейлера за неделю — цель 0; rework rate и число issue, дошедших до review-4; баги, заведённые после закрытия беты — прямая цена отказа от фазы тестирования.

10.4 Событийный конвейер: метка как триггер

.github/workflows/process.yml, issue #114. Смена статусной метки — не запись в журнал, а сообщение: она порождает событие, событие запускает следующий шаг.

S4-spec-review → ревью ТЗ  → S5-ready  либо возврат в S3-spec
S7-code-review → код-ревью → слияние в dev → S8-merged либо возврат в S6-in-progress

Ревьюер — anthropics/claude-code-action. Он читает docs/SCOPE.md, AGENTS.md, этот документ и тело issue, публикует разбор комментарием, заводит issue на каждую Medium-находку, кладёт документ в docs/reviews/ ветки задачи и возвращает вердикт структурированным JSON. Метку переставляет отдельный детерминированный шаг по вердикту, а не модель.

Четыре вещи, без которых конвейер молча не работает:

  1. метки переставляет PAT, а не GITHUB_TOKEN: GitHub намеренно не порождает события от GITHUB_TOKEN, чтобы не было циклов, и цепочка обрывалась бы после первого шага без ошибок в логах;
  2. process.yml обязан лежать в ветке по умолчанию: для события issues GitHub берёт workflow только оттуда, независимо от содержимого dev;
  3. слияние в dev происходит до простановки S8-merged, иначе метка врёт в промежутке — она утверждает, что код в dev;
  4. многострочный текст внутри run: — только через heredoc: строка с нулевым отступом обрывает блок YAML, и скрипт обрезается без ошибки парсера.

Автор обязан дождаться вердикта, а не заканчивать сессию. Ревью идёт от десяти минут до сорока пяти. Отчёт «передал на ревью» останавливает конвейер там, где он мог идти сам: вердикт придёт, а подхватить его будет некому. У агента нет часов — он существует только в момент своего хода, поэтому ожидание это опрос: раз в 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.

  1. ✅ Метки созданы, бэклог размечен. У всех открытых issue владельца ровно одна S*-метка, инварианты чистые.
  2. ⏳ Колонку «Статус ТЗ» из docs/specs/README.md убрать — не сделано, §7.3 п.1. Перенос старых документов ревью в docs/reviews/ отменён: они описывают код, которого уже нет.
  3. ✅ Гейт написан — scripts/process-gate.mjs плюс job в validate.yml, issue #105. Прошёл вне флоу как инфраструктурная задача (§1, issue #118), а не через ТЗ и ревью, как предполагала прежняя редакция этого пункта.
  4. ✅ Долг ревью списан решением владельца. Беты beta.2…beta.10 сделаны по прежнему процессу и не пересматриваются. Точка отсчёта — релиз 1.62.0; отсчёт начинается с первой беты следующей линии.
  5. ⏳ Завести issue на находку «смок visual_continuity не умеет падать» — это ровно тот класс дефектов, который в процессе без ручного тестирования стоит дороже всего.
  6. ✅ BACKLOG-2026-08-11.md — разовый отчёт, решения живут в issue.
  7. ✅ AGENTS.md переписан целиком, шире блока §14.
  8. ✅ Канон перенесён в репозиторий (issue #112). До этого полный процесс жил только в папке владельца, а в репозитории лежал файл на 51 строку про трейлеры коммитов — из свежего клона канон не был виден вообще.
  9. ✅ pre-push написан (§10.1, issue #121). Блокирующая проверка на клиенте есть; обойти её можно только --no-verify, и тогда то же найдёт CI.

14. Блок для AGENTS.md

## Процесс: код только через 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 закрывает релиз-менеджер после выпуска беты, не исполнитель.