docs: bring the process canon back in line with what actually runs

The canon moved into the repository in #112 and then stood still while the
process kept moving. A document that lags is worse than no document: an agent
reading it as truth acts on rules that no longer exist. It promised a pre-push
hook that was never written, named labels in Russian that the repository has
never used, listed a status set the gate no longer applies, and said nothing at
all about the event-driven pipeline — the largest mechanism the process has.

Label names are now English throughout and S8-merged is documented. Section 1
covers the configuration files the gate kept reporting as unclassified, and
records that D beats A where paths overlap, since the built bundle lives inside
custom_components/houseplan/frontend/. Section 10.1 admits that pre-push does
not exist. Section 10.2 matches ALLOWED_STATUS in scripts/process-gate.mjs,
including the two caveats that only surfaced once the pipeline ran. Section 10.4
is new and documents the four silent-failure traps that cost a working day each.

The source-of-truth order now says that actual automation outranks its own
description — this document included.

Issue: #119
User-Visible: no
This commit is contained in:
Matysh
2026-08-13 14:24:50 +03:00
parent f87d71ac18
commit 5aa8771dc3
+262 -91
View File
@@ -1,16 +1,23 @@
# Процесс работы над House Plan # Процесс работы над House Plan
> **Статус документа:** черновик 3 (2026-08-12), на согласование владельцу. > **Статус документа: канон** (редакция 2026-08-13). Решения владельца, на
> Решения владельца, зафиксированные в этой редакции: прямые коммиты в `dev` без > которых он стоит: прямые коммиты в `dev` **без PR** · канон статуса — **метки**,
> PR · канон статуса — **метки** · лёгкий трек для мелких задач **включён**. > имена английские · лёгкий трек **включён** · автор и ревьюер — разные модели ·
> инфраструктурные задачи идут **вне** флоу.
> >
> **Область действия:** обязателен для владельца и для любого агента (Cowork, > **Область действия:** обязателен для владельца и для любого агента. Читается
> Cursor Cloud, Codex, локальные сессии). Читается сразу после `AGENTS.md`, до > сразу после `docs/SCOPE.md` и `AGENTS.md`, до `docs/STATUS.md`. Живёт в
> `docs/STATUS.md`. > репозитории: до августа 2026 канон лежал только в папке владельца, и свежий клон
> его не содержал вовсе.
> >
> **Приоритет источников:** GitHub Issues + Project v2 — канонический бэклог. > **Приоритет источников.** Канонический бэклог — GitHub Issues; статус живёт в
> Статус живёт в метках issue. При расхождении документации с GitHub побеждает > метках, Project v2 остаётся человеческим представлением. При расхождении
> GitHub; при расхождении процесса и привычки побеждает процесс. > документации с GitHub побеждает GitHub. При расхождении этого документа с
> `.github/workflows/*.yml` и `scripts/*` побеждает **фактическая автоматизация**:
> она исполняется, а описание — нет. Расхождение при этом не игнорируется, а
> заводится issue с меткой `process`.
>
> При расхождении процесса и привычки побеждает процесс.
--- ---
@@ -26,29 +33,49 @@
| Класс | Что входит | Нужен ли issue | | Класс | Что входит | Нужен ли issue |
|---|---|---| |---|---|---|
| **A. Продукт** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, `src/i18n/*.json`, `custom_components/**/translations/*` | **Да, обязательно.** Только из «Готово к разработке» или дальше | | **A. Продукт** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, `src/i18n/*.json`, `custom_components/**/translations/*` | **Да, обязательно.** Только из «Готово к разработке» или дальше |
| **B. Гейты и инструменты** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | **Да.** Может использовать issue того изменения, которое покрывает; самостоятельная работа над гейтом получает свой issue (тип «техдолг») | | **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` | Документирование A/B в том же коммите — часть DoD своего issue. Самостоятельная работа над документацией — свой issue | | **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. Сгенерированное** | `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. Жизненный цикл ## 2. Жизненный цикл
Семь рабочих статусов и два служебных. Фазы тестирования в цикле сознательно Восемь рабочих статусов и два служебных. Фазы тестирования в цикле сознательно
**нет**: найденные позже дефекты заводятся отдельными issue и проходят цикл **нет**: найденные позже дефекты заводятся отдельными issue и проходят цикл
заново. Issue закрывается после выпуска беты. заново. Issue закрывается после выпуска беты.
``` ```
Новое → Аналитика и оценка → ТЗ в работе → ТЗ на ревью ⟲ → Готово к разработке → S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
→ В разработке → Код-ревью ⟲ → Закрыт (после выпуска беты) → S6-in-progress → S7-code-review ⟲ → S8-merged → закрыт при выпуске беты
служебные: Заблокировано (parking) Отклонено (закрыт) служебные: blocked (поверх статуса) rejected (закрыт)
⟲ — возврат на правки, не более 4 циклов (§4) ⟲ — возврат на правки, не более 4 циклов (§4), на лёгком треке 2
``` ```
Переходы `S4-spec-review` и `S7-code-review` выполняются **автоматически**: метка
порождает событие, событие запускает ревью (§10.4). Остальные ставит исполнитель.
### 2.1 Новое — заведение задачи ### 2.1 Новое — заведение задачи
- **Кто:** любой — владелец, агент, пользователь (Telegram, GitHub). - **Кто:** любой — владелец, агент, пользователь (Telegram, GitHub).
@@ -208,7 +235,7 @@
16. **Стабильный релиз — promotion-only:** версии, сгенерированные бандлы, 16. **Стабильный релиз — promotion-only:** версии, сгенерированные бандлы,
changelog и release-метаданные. Продуктового кода там нет. changelog и release-метаданные. Продуктового кода там нет.
17. **История `dev` не перезаписывается.** На неё ссылаются теги. Нарушение 17. **История `dev` не перезаписывается.** На неё ссылаются теги. Нарушение
исправляется следующим коммитом плюс issue с меткой `процесс` — не исправляется следующим коммитом плюс issue с меткой `process` — не
force-push'ем. force-push'ем.
18. **AC доказывает автотест или запись ревьюера.** Фразы «проверил локально, всё 18. **AC доказывает автотест или запись ревьюера.** Фразы «проверил локально, всё
работает» в процессе не существует: либо тест, который умеет падать, либо работает» в процессе не существует: либо тест, который умеет падать, либо
@@ -222,7 +249,7 @@
## 4. Лимит циклов ревью: 4 ## 4. Лимит циклов ревью: 4
Оба ревью-гейта возвращают задачу на правки не более **4 раз**. Счётчик виден в Оба ревью-гейта возвращают задачу на правки не более **4 раз**. Счётчик виден в
имени документа: `-r1` … `-r4`; на четвёртом заходе ставится метка `ревью-4`. имени документа: `-r1` … `-r4`; на четвёртом заходе ставится метка `review-4`.
- **Что считается циклом:** отправка на ревью → вердикт с блокирующими находками - **Что считается циклом:** отправка на ревью → вердикт с блокирующими находками
→ возврат. Уточняющий вопрос без вердикта циклом не считается. → возврат. Уточняющий вопрос без вердикта циклом не считается.
@@ -241,7 +268,7 @@
--- ---
## 5. Лёгкий трек (метка `малое`) ## 5. Лёгкий трек (метка `small`)
**Критерии — все одновременно:** **Критерии — все одновременно:**
@@ -263,7 +290,7 @@
никогда — именно оно в этом процессе заменяет тестирование. никогда — именно оно в этом процессе заменяет тестирование.
Если по ходу выясняется, что критерий нарушен (появилась миграция, задело второй Если по ходу выясняется, что критерий нарушен (появилась миграция, задело второй
модуль) — метка `малое` снимается, issue возвращается в «ТЗ в работе» и получает модуль) — метка `small` снимается, issue возвращается в `S3-spec` и получает
нормальный файл ТЗ. Это не провал, это ранняя диагностика. нормальный файл ТЗ. Это не провал, это ранняя диагностика.
--- ---
@@ -286,9 +313,19 @@
**Правило разделения:** ревьюер работает состязательно. Ему передаётся тег или **Правило разделения:** ревьюер работает состязательно. Ему передаётся тег или
диапазон коммитов и ТЗ — не рассказ автора о том, как всё хорошо. диапазон коммитов и ТЗ — не рассказ автора о том, как всё хорошо.
**Принято по умолчанию, поправь если не так:** ревьюер — отдельная сессия **Роли закреплены за исполнителями** (решение владельца 2026-08-12):
(Cowork / Codex / Cursor Cloud), выбор чередуется, лишь бы это была не та сессия,
что делала артефакт; релиз-менеджер — владелец. | Исполнитель | Роли |
|---|---|
| **Codex** | аналитик, автор ТЗ, разработчик, релиз-инженер по команде владельца |
| **Claude** | ревьюер ТЗ, ревьюер кода, вся инфраструктура и дистрибуция |
| **Владелец** | приоритет, скоуп, арбитраж, закрытие issue, команда на выпуск |
Автор и ревьюер — **разные модели**, и это сильнее требования «другая сессия»:
одна модель, читая свой же артефакт заново, повторяет свои же слепые пятна.
Ревью ТЗ и код-ревью держатся в **разных сессиях** Claude: ревьюер кода не должен
приходить с контекстом того, как обсуждали ТЗ.
--- ---
@@ -298,8 +335,8 @@
``` ```
issue #NN issue #NN
↔ ТЗ docs/specs/NN-slug.md (или тело issue при `малое`) ↔ ТЗ docs/specs/NN-slug.md (или тело issue при `small`)
↔ ревью ТЗ docs/reviews/SPEC-REVIEW-NN-rN.md (или комментарий при `малое`) ↔ ревью ТЗ docs/reviews/SPEC-REVIEW-NN-rN.md (или комментарий при `small`)
↔ ветка issue/NN-slug ↔ ветка issue/NN-slug
↔ коммиты трейлеры Issue: #NN · User-Visible: yes|no ↔ коммиты трейлеры Issue: #NN · User-Visible: yes|no
↔ ревью кода docs/reviews/CODE-REVIEW-<tag|NN>-rN.md ↔ ревью кода docs/reviews/CODE-REVIEW-<tag|NN>-rN.md
@@ -307,9 +344,44 @@ issue #NN
↔ бета тег, зелёный CI на точном SHA → закрытие ↔ бета тег, зелёный CI на точном SHA → закрытие
``` ```
Обязательные разделы ТЗ: проблема · скоуп и **не-скоуп** · контракт поведения · Обязательные разделы ТЗ: **сценарий** · **что человек увидит до и после** ·
UX · модель данных и миграция · i18n · критерии приёмки AC1…ACn с указанием проблема · скоуп и **не-скоуп** · контракт поведения · UX · модель данных и
доказательства · план автотестов · риски · откат · release-артефакты. миграция · i18n · критерии приёмки AC1…ACn с указанием доказательства · план
автотестов · риски · откат · release-артефакты.
Два первых раздела — продуктовые, и они идут первыми не случайно. **Сценарий:**
какая персона (`docs/SCOPE.md`), на какой поверхности, в какой момент это
встретит. **Что человек увидит:** одной фразой, без терминов реализации. ТЗ,
которое не может ответить на эти два вопроса, описывает работу, а не изменение
продукта.
**Размытое место не додумывается, а выносится владельцу.** Догадка, записанная
как факт, — худший вид дефекта: она проходит ревью, потому что выглядит решением.
Но спрашивать обо всём нельзя: владелец один, и анкета из двадцати пунктов хуже
угадывания. Порог такой (решение владельца 2026-08-13).
**Владельцу задаются только продуктовые вопросы** — что человек видит или делает
и какой объём видимых изменений входит в этот issue. Поведение в пограничном
случае; какая из персон важнее в конфликте; что считать приемлемой деградацией;
относится ли смежное поведение сюда или становится отдельной задачей.
**Всё, чего пользователь не наблюдает, агенты решают сами** либо согласовывают
между собой: где хранится состояние, в каком модуле стоит гвард, именование,
раскладка файлов, стратегия тестов, механика миграции. Решение записывается явным
блоком в конце ТЗ — «принято предположительно, поменять свободно», и ревьюер
вправе его оспорить. Технический спор автора и ревьюера решается вердиктом, а не
владельцем; до него он доходит только при исчерпании лимита циклов (§4).
**Смешанный вопрос делится, а не эскалируется целиком.** «Где живёт это
состояние» — техническое. «Переживает ли оно перезагрузку страницы и общее ли оно
для всех экранов» — продуктовое.
Вопросы задаются **одним комментарием, пачкой**, каждый в форме: что неясно ·
что изменится от ответа · **предлагаемый вариант по умолчанию**. Вопрос с готовым
вариантом стоит владельцу пяти секунд, вопрос без него — пяти минут. Пока ждём
ответа, issue остаётся в `S3-spec` и получает `blocked`: статус не подменяется,
`blocked` его дополняет, иначе конвейер считает задачу в работе, а она стоит.
### 7.2 Шаблоны комментариев ### 7.2 Шаблоны комментариев
@@ -320,20 +392,26 @@ UX · модель данных и миграция · i18n · критерии
- **Занятие:** `Взял: <роль> · сессия <id> · ветка issue/NN-slug` - **Занятие:** `Взял: <роль> · сессия <id> · ветка issue/NN-slug`
- **Хендофф:** `Сделано: … · Файлы: … · Гейты: <команда → результат> · - **Хендофф:** `Сделано: … · Файлы: … · Гейты: <команда → результат> ·
НЕ сделано: … · Риски: … · Следующий статус: … · Новые issue: #…` НЕ сделано: … · Риски: … · Следующий статус: … · Новые issue: #…`
- **Вердикт ревью:** `Вердикт: зелёный/жёлтый/красный · цикл r<N>/4 · High: N · - **Вердикт ревью:** `Вердикт: зелёный/жёлтый/красный · цикл r<N>/<лимит> ·
Medium: N → #… · Документ: docs/reviews/…` High: N · Medium: N → #… · Документ: docs/reviews/…`
- **Закрытие:** `Выпущено в <тег беты> · CI: <ссылка> · Changelog: <ссылка>` - **Закрытие:** `Выпущено в <тег беты> · CI: <ссылка> · Changelog: <ссылка>`
**Вперёд двигает только зелёный вердикт.** Жёлтый и красный возвращают автору;
разница между ними содержательна для человека, но не для маршрута. Первая
редакция конвейера (§10.4) пропускала жёлтый при `High: 0`, и первый же живой
прогон показал, почему это неверно: жёлтый там означал, что AC описывает неверное
изменение контракта — реализовать такое ТЗ значило бы сделать ошибку по инструкции.
### 7.3 Расхождения с текущим состоянием, которые надо закрыть ### 7.3 Расхождения с текущим состоянием, которые надо закрыть
1. **Ревью живут вне репозитория.** 20+ файлов `CODE-REVIEW-*.md` и 1. **Статус ТЗ дублирует статус issue.** `docs/specs/README.md` держит колонку
`SPEC-REVIEW-*.md` лежат только в личной папке владельца. Агент, пришедший
через месяц, не видит, почему решение принято именно так, и повторяет
разобранную ошибку. → `docs/reviews/`.
2. **Статус ТЗ дублирует статус issue.** `docs/specs/README.md` держит колонку
«Статус ТЗ» со своим словарём («черновик решения», «в реализации», «Статус ТЗ» со своим словарём («черновик решения», «в реализации»,
«реализовано»). Два источника статуса уже расходятся. Колонку убрать, оставить «реализовано»). Два источника статуса уже расходятся. Колонку убрать, оставить
таблицу «issue ↔ ТЗ». таблицу «issue ↔ ТЗ».
2. **Ревью до релиза 1.62 живут вне репозитория.** Документы `CODE-REVIEW-*.md` и
`SPEC-REVIEW-*.md` за прежний период лежат в папке владельца, и переносить их
задним числом смысла нет: они описывают код, которого уже нет. Новые документы
ревью кладёт в `docs/reviews/` сам конвейер, в ветку задачи.
--- ---
@@ -365,23 +443,41 @@ Performance зелёные на точном SHA; статусов issue не к
доступом к Issues, в отличие от Project v2, который требует отдельного скоупа. доступом к Issues, в отличие от Project v2, который требует отдельного скоупа.
Project v2 остаётся человеческим представлением и синхронизируется по меткам. Project v2 остаётся человеческим представлением и синхронизируется по меткам.
**Имена меток английские** (решение владельца 2026-08-12). Русские имена в этом
документе были только на бумаге; репозиторий с самого начала жил на английских.
| Метка | Статус | | Метка | Статус |
|---|---| |---|---|
| `S1-новое` | Новое | | `S1-new` | Новое, не разобрано |
| `S2-аналитика` | Аналитика и оценка | | `S2-analysis` | Аналитика и оценка |
| `S3-тз` | ТЗ в работе | | `S3-spec` | ТЗ в работе |
| `S4-тз-ревью` | ТЗ на ревью | | `S4-spec-review` | ТЗ на ревью |
| `S5-к-разработке` | Готово к разработке | | `S5-ready` | Готово к разработке — единственный статус, из которого можно начать трогать код |
| `S6-в-разработке` | В разработке | | `S6-in-progress` | В разработке, занято исполнителем |
| `S7-код-ревью` | Код-ревью | | `S7-code-review` | Код-ревью |
| `заблокировано` | Заблокировано (поверх статусной метки) | | `S8-merged` | Ревью пройдено, код в `dev`, ждёт беты. Issue закрывается пачкой при выпуске |
| `отклонено` | Отклонено, issue закрыт | | `blocked` | Ждём внешнего или владельца, **поверх** статусной метки |
| `rejected` | Отклонено, issue закрыт |
Модификаторы: `малое` (лёгкий трек), `hotfix`, `процесс`, `ревью-4`, Модификаторы: `small` (лёгкий трек, сложность ≤3), `hotfix`, `process`,
приоритет `P1`/`P2`/`P3`, тип `баг`/`фича`/`техдолг`. `review-4`; приоритет `P1`/`P2`/`P3`; тип `bug`/`feature`/`tech-debt`.
Тематические метки (`polish`, `infra`, `tests`, `docs`, `security`, `vacuum`)
ортогональны процессу.
Правила: ровно одна `S*`-метка; закрытый issue статусных меток не несёт; Инварианты: **ровно одна `S*`-метка** на открытом issue; закрытый issue статусных
`заблокировано` не заменяет статус, а дополняет его. меток не несёт; `blocked` не заменяет статус, а дополняет его.
**В процесс берутся только issue, созданные владельцем** (решение владельца
2026-08-13). Репозиторий публичный, задачи заводят и посторонние; такие issue
бывают плохо оформлены или невалидны, и статусных меток им не ставят — инварианты
на них не распространяются. Проверять `user.login`. Что делать с чужим issue:
прочитать, при необходимости переформулировать и завести **свой** со ссылкой на
исходный, либо оставить до решения владельца. Не размечать и не брать в работу.
`S8-merged` появился позже остальных и закрывает разрыв, который раньше
закрывался памятью человека: код принят, но бета ещё не вышла, и issue закрывать
рано. Без него принятая задача либо висела в `S7-code-review`, либо закрывалась
досрочно.
--- ---
@@ -402,46 +498,109 @@ Project v2 остаётся человеческим представление
`npm ci` вызывает `prepare` сам — значит, хуки появляются в каждом окружении, `npm ci` вызывает `prepare` сам — значит, хуки появляются в каждом окружении,
включая свежий контейнер облачного агента, без отдельного шага в инструкции. включая свежий контейнер облачного агента, без отдельного шага в инструкции.
- **`commit-msg`** — отклоняет коммит без `Issue: #NN`, если тронут класс A или B; - **`commit-msg`** — есть, работает. Отклоняет коммит без терминального
проверяет `User-Visible`. `Issue: #NN`, требует ровно один `User-Visible: yes|no`, а для коммитов,
- **`pre-push`** — прогоняет `scripts/process-gate.mjs` по всему пушимому трогающих `demo/golden/baselines/**`, — `Release:` плюс `Baseline-Reviewed:`.
диапазону. Это и есть блокирующий гейт вместо PR. Реализация — `scripts/validate-commit-provenance.mjs`, тот же скрипт вызывается
job `provenance` в `validate.yml`.
- **`pre-push`** — **не реализован.** Задумывался как блокирующий гейт вместо PR;
фактически блокирующей проверки на клиенте нет, и `process-gate.mjs` работает
только догоняющим job в CI (§10.3). Долг известен, отдельная задача.
Файл хука обязан быть исполняемым: `assertHookMode` проверяет бит в индексе.
Через GitHub API режим не выставляется — хук, отправленный так, приезжает
`100644`, и проверка его отвергает. Ставить `git update-index --chmod=+x`.
### 10.2 Что проверяет `process-gate.mjs` ### 10.2 Что проверяет `process-gate.mjs`
Офлайн, без GitHub API: Реализовано, `scripts/process-gate.mjs`, issue #105. Офлайн, без GitHub API:
1. трейлер `Issue: #NN` у каждого коммита класса A/B; 1. трейлер `Issue: #NN` у каждого коммита класса A/B, допускается несколько;
2. имя ветки `issue/NN-slug` соответствует трейлерам; 2. имя ветки `issue/NN-slug` соответствует трейлерам;
3. для класса A существует `docs/specs/NN*-*.md` со ссылкой на issue — **или** 3. для класса A существует `docs/specs/NN-*.md` — **или** issue помечен `small`.
issue помечен `малое` (для этого нужен этап 2, до него — исключение по списку); Офлайн это предупреждение: лёгкий трек держит ТЗ в теле issue, и без чтения
меток «ТЗ в issue» неотличимо от «ТЗ не написано». С `--issues` — отказ;
4. `User-Visible: yes` → правки в обоих changelog в том же коммите; 4. `User-Visible: yes` → правки в обоих changelog в том же коммите;
5. коммит только класса D невалиден без `Release: vX.Y.Z` либо 5. коммит только класса D невалиден без `Release: vX.Y.Z` либо
`Baseline-Reviewed: <ссылка на прогон CI>`; `Baseline-Reviewed: <ссылка на прогон CI>`;
6. релизный коммит не содержит изменений в `src/` и `custom_components/**/*.py`; 6. релизный коммит не содержит изменений в `src/` и `custom_components/**/*.py`;
7. документов ревью на один issue не больше четырёх (`-r1`…`-r4`). 7. документов ревью на один issue не больше четырёх (`-r1`…`-r4`).
С токеном GitHub (PAT уже есть у релизных скриптов): С токеном GitHub:
8. `--issues` тянет каждый упомянутый issue и требует метку из 8. `--issues` тянет каждый упомянутый issue и требует метку из
{`S5-к-разработке`, `S6-в-разработке`, `S7-код-ревью`}; закрытый или {`S5-ready`, `S6-in-progress`, `S7-code-review`, `S8-merged`}; закрытый,
отсутствующий issue — отказ (fail closed); недоступный или помеченный `blocked` — отказ (**fail closed**).
9. `npm run release:prerelease -- --issues=…` отказывается, если у issue нет
зелёного вердикта код-ревью; Две оговорки к проверке 8, обе выяснились при реализации.
10. закрытие issue и снятие статусных меток автоматизируются по факту публикации
беты — в `publish-prerelease.yml`, а не по памяти человека. **`S8-merged` входит в множество**, хотя по смыслу задача уже принята. Причина
механическая: конвейер (§10.4) сливает ветку в `dev` **раньше**, чем ставит метку,
Validate стартует от этого push и успевает прочитать issue уже в `S8-merged`.
Строгое множество красило бы каждую принятую задачу. Локальная строгость
возвращается флагом `--no-merged`.
**Статус спрашивается только у коммитов класса A/B.** Правило №1 говорит о
продуктовом коде и инструментах, а не о документации. Иначе краснел бы каждый
документ ревью: он ложится в ветку задачи, пока та в `S4-spec-review` или
`S7-code-review`, то есть заведомо вне рабочего множества.
Не реализовано и остаётся долгом:
9. `npm run release:prerelease -- --issues=…` не проверяет, есть ли у issue
зелёный вердикт код-ревью;
10. закрытие issue и снятие статусных меток при публикации беты делаются руками —
`node process-labels/apply.mjs cleanup --apply`, а не `publish-prerelease.yml`.
Пропуск этого шага уже ломал инвариант «закрытый issue без статусной метки».
### 10.3 Страховка и разбор ### 10.3 Страховка и разбор
- **Тот же `process-gate.mjs` — job в `validate.yml`.** При прямом push проверка - **`process-gate.mjs` — job `process-gate` в `validate.yml`**, без `needs`:
догоняющая: код уже в `dev`, CI краснеет после. Это принятая цена отказа от PR. краснеет сам и не роняет остальные. При прямом push проверка догоняющая: код уже
в `dev`, CI краснеет после. Это принятая цена отказа от PR — и, пока `pre-push`
не написан, единственная машинная проверка процесса.
- **Нарушение не откатывается force-push'ем** (правило 17): исправляющий коммит - **Нарушение не откатывается force-push'ем** (правило 17): исправляющий коммит
плюс issue с меткой `процесс`. Починить надо проверку, а не только симптом. плюс issue с меткой `process`. Починить надо проверку, а не только симптом.
- **Еженедельная гигиена** (workflow): issue в «Новое» дольше 14 дней и в - **Еженедельная гигиена** (workflow): issue в `S1-new` дольше 14 дней и в
«В разработке» дольше 7; issue класса A в `S5` без ТЗ; issue с нулём или двумя `S6-in-progress` дольше 7; issue класса A в `S5-ready` без ТЗ; issue с нулём или
`S*`-метками; коммиты без трейлера за неделю — **цель 0**; rework rate и число двумя `S*`-метками; коммиты без трейлера за неделю — **цель 0**; rework rate и
issue, дошедших до `ревью-4`; **баги, заведённые после закрытия беты** — прямая число 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` не ждать — задача ждёт владельца.
--- ---
@@ -486,27 +645,35 @@ Project v2 остаётся человеческим представление
- force-push в `dev`; - force-push в `dev`;
- ручное копирование на домашний инстанс. - ручное копирование на домашний инстанс.
**Нарушение процесса — тоже issue** (метка `процесс`): если правило удалось **Нарушение процесса — тоже issue** (метка `process`): если правило удалось
нарушить незаметно, виновата проверка. нарушить незаметно, виновата проверка.
--- ---
## 13. Внедрение ## 13. Внедрение
1. Создать метки §9; разметить 38 открытых issue. Всё, что по Состояние на 2026-08-13.
`docs/specs/README.md` «в реализации», но не прошло ревью, — в честный статус.
2. Перенести существующие `CODE-REVIEW-*.md` и `SPEC-REVIEW-*.md` в 1. ✅ **Метки созданы, бэклог размечен.** У всех открытых issue владельца ровно
`docs/reviews/`; убрать колонку «Статус ТЗ» из `docs/specs/README.md`. одна `S*`-метка, инварианты чистые.
3. Завести issue на сам гейт (класс B, техдолг): `scripts/process-gate.mjs`, 2. ⏳ **Колонку «Статус ТЗ» из `docs/specs/README.md` убрать** — не сделано, §7.3
`.githooks/`, `prepare`-скрипт, job в `validate.yml`. По новому процессу он п.1. Перенос старых документов ревью в `docs/reviews/` отменён: они описывают
сам обязан пройти ТЗ → ревью → реализацию. код, которого уже нет.
4. Закрыть текущий долг ревью: `beta.2…beta.4` без код-ревью, среди них две новые 3. ✅ **Гейт написан** — `scripts/process-gate.mjs` плюс job в `validate.yml`,
фичи (#90, #94). issue #105. Прошёл **вне** флоу как инфраструктурная задача (§1, issue #118), а
5. Завести issue на находку «смок `visual_continuity` не умеет падать» из разбора не через ТЗ и ревью, как предполагала прежняя редакция этого пункта.
11.08 — это ровно тот класс дефектов, который в процессе без ручного 4. ✅ **Долг ревью списан решением владельца.** Беты `beta.2`…`beta.10` сделаны по
тестирования стоит дороже всего. прежнему процессу и не пересматриваются. Точка отсчёта — релиз 1.62.0; отсчёт
6. `BACKLOG-2026-08-11.md` объявить разовым отчётом: решения — в issue. начинается с первой беты следующей линии.
7. Добавить в `AGENTS.md` блок §14. 5. ⏳ Завести issue на находку «смок `visual_continuity` не умеет падать» — это
ровно тот класс дефектов, который в процессе без ручного тестирования стоит
дороже всего.
6. ✅ `BACKLOG-2026-08-11.md` — разовый отчёт, решения живут в issue.
7. ✅ `AGENTS.md` переписан целиком, шире блока §14.
8. ✅ **Канон перенесён в репозиторий** (issue #112). До этого полный процесс жил
только в папке владельца, а в репозитории лежал файл на 51 строку про трейлеры
коммитов — из свежего клона канон не был виден вообще.
9. ⏳ **`pre-push` не написан** (§10.1). Блокирующей проверки на клиенте нет.
--- ---
@@ -519,10 +686,14 @@ Project v2 остаётся человеческим представление
«Готово к разработке» или дальше. Полные правила, критерии статусов и гейты — «Готово к разработке» или дальше. Полные правила, критерии статусов и гейты —
`docs/PROCESS.md`, читать до начала работы. `docs/PROCESS.md`, читать до начала работы.
Жизненный цикл (статус = метка issue): `S1-новое` → `S2-аналитика` → `S3-тз` → Жизненный цикл (статус = метка issue): `S1-new` → `S2-analysis` → `S3-spec` →
`S4-тз-ревью` → `S5-к-разработке` → `S6-в-разработке` → `S7-код-ревью` → `S4-spec-review` → `S5-ready` → `S6-in-progress` → `S7-code-review` → `S8-merged`
закрытие после выпуска беты. Оба ревью возвращают на правки не более 4 циклов; → закрытие пачкой при выпуске беты. Оба ревью возвращают на правки не более 4
пятый заход — разбор у владельца (разделить / отклонить / арбитраж). циклов; пятый заход — разбор у владельца (разделить / отклонить / арбитраж).
Ревью запускается **само** от меток `S4-spec-review` и `S7-code-review` и идёт до
45 минут. Поставив такую метку, автор не заканчивает работу, а ждёт смены метки
опросом и продолжает по тому, чем она стала.
- ветка `issue/<NN>-<slug>`, коммиты с трейлерами `Issue: #NN` и `User-Visible: yes|no`; - ветка `issue/<NN>-<slug>`, коммиты с трейлерами `Issue: #NN` и `User-Visible: yes|no`;
- работаем прямыми коммитами в `dev`, без PR: блокирующий гейт — локальный - работаем прямыми коммитами в `dev`, без PR: блокирующий гейт — локальный
@@ -531,7 +702,7 @@ Project v2 остаётся человеческим представление
- автор ≠ ревьюер, ни для ТЗ, ни для кода; - автор ≠ ревьюер, ни для ТЗ, ни для кода;
- фазы ручного тестирования нет: автотесты пишутся в реализации, AC проверяет - фазы ручного тестирования нет: автотесты пишутся в реализации, AC проверяет
код-ревью, найденные позже дефекты — новые issue типа «баг»; код-ревью, найденные позже дефекты — новые issue типа «баг»;
- мелкие задачи (метка `малое`, сложность ≤3): ТЗ в теле issue, ревью ТЗ - мелкие задачи (метка `small`, сложность ≤3): ТЗ в теле issue, ревью ТЗ
комментарием, код-ревью — как обычно; комментарием, код-ревью — как обычно;
- найденное вне скоупа — новый issue, а не попутная правка; - найденное вне скоупа — новый issue, а не попутная правка;
- issue закрывает релиз-менеджер после выпуска беты, не исполнитель. - issue закрывает релиз-менеджер после выпуска беты, не исполнитель.