docs(process): канон без противоречий, вход автора короче (#701)

Сверка PROCESS.md, ролевых выжимок, AGENTS.md, TESTING.md, CONTRIBUTING.md
и скриптов по 26 найденным расхождениям (D1–D26): трейлеры по классам
изменений, gate:small как единственный источник состава, пороги ревью,
путь реестра мутантов, golden по ci:golden, порядок чтения промпта ревью.

- scripts/change-classes.mjs: классы A/B/C/D — один модуль для
  process-gate и проверки трейлеров.
- commit-msg: коммит только с файлами класса C (документация) трейлеров
  не требует; указанные трейлеры по-прежнему проверяются.
- Маршрут автора без docs/STATUS.md: 5345 → 4703 слова.
- Промпт ревью читает SCOPE → AGENTS → REVIEWER, как ROUTES.reviewer.

Issue: #701
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-29 00:30:04 +03:00
parent 224d0106fd
commit 8dcc1cad4e
21 changed files with 232 additions and 111 deletions
+3 -3
View File
@@ -1152,15 +1152,15 @@ jobs:
ограничитель: «features are built, improved and accepted only
if they serve a job listed here». Первый вопрос к задаче —
какую строку Core user jobs она закрывает.
2. docs/process/REVIEWER.md — обязанности ревьюера: позиция,
2. AGENTS.md — карта пакета, правило №1, классы изменений, треки,
трейлеры и ожидание вердикта; сами правила — по его ссылкам.
3. docs/process/REVIEWER.md — обязанности ревьюера: позиция,
ревью ТЗ, код-ревью, объём гейтов, повторный раунд, находки и
вердикт. Раздел PROCESS.md по ссылке открывай, когда пункт
касается твоего решения; при расхождении прав PROCESS.md. Если
файла в материале нет — читай PROCESS.md §2.4, §2.7, §2.10,
§4, §7.2, §8, §12. Задача правит сам конвейер, гейты или
процесс — PROCESS.md целиком, §10 в первую очередь.
3. AGENTS.md — карта пакета, правило №1, классы изменений, треки,
трейлеры и ожидание вердикта; сами правила — по его ссылкам.
4. Тело issue #${{ github.event.issue.number }} и все комментарии.
5. Если меняется видимое поведение — docs/USER-GUIDE.ru.md:
терминология интерфейса берётся оттуда, а не изобретается.
+6 -3
View File
@@ -29,7 +29,9 @@ leak interactions into View. For work that changes visible behaviour, also read
route and `test/entry-cost.test.mjs` keeps this list equal to its routes:
- author (analysis, spec, implementation, infrastructure): `docs/SCOPE.md` →
`AGENTS.md` → `docs/process/AUTHOR.md` → `docs/STATUS.md`;
`AGENTS.md` → `docs/process/AUTHOR.md`, then the task packet
(`node scripts/task-packet.mjs --issue NN`); the status snapshot
(docs/STATUS.md) only when resuming a session or preparing a release;
- reviewer (spec or code): `docs/SCOPE.md` → `AGENTS.md` →
`docs/process/REVIEWER.md`, then the issue body and its comments;
- changing the pipeline, the gates or the process itself: `docs/SCOPE.md` →
@@ -100,8 +102,9 @@ batched comment with a proposed default for each question and `blocked` on top o
## Commits and branches
Hooks install themselves on `npm ci` (`prepare` → `scripts/install-hooks.mjs`);
`git config core.hooksPath` must print `.githooks`. Every non-merge commit
carries **terminal** trailers:
`git config core.hooksPath` must print `.githooks`. Every non-merge commit that
touches anything outside class C (docs) carries **terminal** trailers; a
docs-only commit needs none (`PROCESS.md` §3 п.10, #701):
```text
Issue: #123
+5 -4
View File
@@ -94,12 +94,11 @@ issue, but they do not replace it or maintain a separate checklist.
```bash
git clone --filter=blob:none https://github.com/Matysh/houseplan-card && cd houseplan-card
npm ci # frontend toolchain
npm ci # frontend toolchain; `prepare` installs .githooks
npm run typecheck # tsc --noEmit (strict)
npm test # node:test — pure logic, i18n parity, tap-action security
npm run build # tsc + rollup → dist/houseplan-card.js
pip install pytest voluptuous && python -m pytest tests_backend -q # pure backend tests
npm install # also installs .githooks through the prepare script
pip install -r tests_backend/requirements.txt && python -m pytest tests_backend -q # CI pins, HA harness included
```
### Why `--filter=blob:none` (#345)
@@ -127,7 +126,9 @@ Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable
## Ground rules
- **Docs in the same commit**: CHANGELOG entry for user-visible changes;
`docs/STATUS.md` for state changes; `docs/DEVELOPMENT.md` for new gotchas.
`docs/ARCHITECTURE.md` for changes to the data model, WS API or coordinate
system; `docs/STATUS.md` for state changes; `docs/DEVELOPMENT.md` for new
gotchas. A docs-only commit needs no trailers (`PROCESS.md` §3 п.10).
- Every UI string goes through `src/i18n/<lang>.json`; follow the
[Translations](#translations) flow for registry and backend parity.
- The committed bundle changes only in a release candidate: `npm run
+50 -23
View File
@@ -7,8 +7,10 @@
> агенты/сессии · любой агент может взять любую роль · инфраструктурные задачи
> входят в общий флоу сразу на `S7-code-review`.
>
> **Область действия:** обязателен для владельца и для любого агента. Читается
> сразу после `docs/SCOPE.md` и `AGENTS.md`, до `docs/STATUS.md`. Живёт в
> **Область действия:** обязателен для владельца и для любого агента. Целиком
> его читает тот, кто правит конвейер, гейты или сам процесс, — сразу после
> `docs/SCOPE.md` и `AGENTS.md`, до `docs/STATUS.md`; автор и ревьюер входят через
> ролевые конспекты ниже и открывают раздел канона по ссылке (#634, #701). Живёт в
> репозитории: до августа 2026 канон лежал только в папке владельца, и свежий клон
> его не содержал вовсе.
>
@@ -24,8 +26,9 @@
> метках и больше нигде: Project v2 не используется. При расхождении
> документации с GitHub побеждает GitHub. Этот файл — единственный полный канон
> процесса в репозитории; `AGENTS.md` — его короткое обязательное резюме, а
> внешний `CODEX-RUNBOOK.md` — только маршрутизатор к канону и историческим
> инструкциям. Текущие версии, состояние конкретных issue, runtime pins и списки
> внешние `CODEX-RUNBOOK.md` и `CLAUDE.md` папки владельца — только маршрутизаторы
> к канону и историческим инструкциям: правил в них нет, и в замер цены входа
> (`scripts/entry-cost.mjs`) они не входят — CI их не видит (#701). Текущие версии, состояние конкретных issue, runtime pins и списки
> jobs не копируются в производную прозу: они читаются из своих исполняемых
> источников.
> При расхождении этого документа с `.github/workflows/*.yml` и `scripts/*`
@@ -201,8 +204,9 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
«Взял: <роль> · сессия <id> · ветка `issue/<NN>-<slug>`».
- **WIP-лимиты:** не более **1** issue в «В разработке» на исполнителя, не более
**3** одновременно на цикл релиза, не более **2** в «Код-ревью».
- **Трассируемость:** ветка `issue/<NN>-<slug>`; каждый коммит несёт трейлеры
`Issue: #<NN>` и `User-Visible: yes|no`.
- **Трассируемость:** ветка `issue/<NN>-<slug>`; каждый коммит с файлами классов A, B или D несёт трейлеры
`Issue: #<NN>` и `User-Visible: yes|no`; коммит только из документации — без
трейлеров (§3 п.10, #701).
- **Автотесты — часть реализации, а не отдельная фаза.** Каждый AC, помеченный
`unit`/`backend`/`smoke`/`golden`, получает свою проверку здесь же.
«Тестирование вне жизненного цикла» означает отсутствие фазы ручного
@@ -251,7 +255,7 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
а два теста были записаны в закрытие coverage-ratchet под именами, обещавшими
то, чего они не проверяли (#430).
Мутант в `scripts/mutation-gate.mjs` обязателен, когда защита живёт в
Мутант в реестре `scripts/mutation-registry.mjs` обязателен, когда защита живёт в
продуктовом коде и проверяется дорогим гейтом (смок, бэкенд, golden): там
ревьюер не воспроизведёт отрицательный прогон второй раз. Для чистых юнитов
достаточно прогона со снятой защитой, приведённого в документе.
@@ -385,7 +389,7 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
fast-forward (`--commit-if-stale`, коммит класса C) — и публикацией документа ревью ТЗ
прямо в `dev`. В ветке задачи индекс не пересобирается — ни при приведении к
dev, ни при публикации документа код-ревью: иначе две параллельные задачи
конфликтуют на нём по построению. Руками не правится. Конфликт ребейза, в котором **все** пути —
конфликтуют на нём по построению. Сам `INDEX.md` руками не правится никогда. Конфликт ребейза, в котором **все** пути —
`INDEX.md`, отказом не считается (#643): `scripts/rebase-generated.mjs`
пересобирает индекс по каталогу на остановке и продолжает ребейз — так делают
приведение к dev, слияние кандидата и авторский `rebase-on-dev.mjs`; индекс
@@ -401,9 +405,10 @@ Validate кандидата. Сегодня пять чисел исходник
patch-id кандидата слияния: вердикт к работе задачи остаётся в силе. Шаг Validate
«индекс ревью совпадает с каталогом» красит push в `dev`, где `INDEX.md`
расходится с каталогом (на issue-ветках не судится: их переписывает конвейер).
Правка `docs/reviews/`
руками — перенос в `legacy/`, удаление — сопровождается
`node scripts/reviews-index.mjs` в том же коммите. Прежде чем брать
Ручная правка каталога
`docs/reviews/` — перенос документов в `legacy/`, удаление — сопровождается
пересборкой индекса `node scripts/reviews-index.mjs` в том же коммите: индекс
меняет генератор, а не рука. Прежде чем брать
задачу по подсистеме, стоит прочитать её строки в индексе: что находили и чем
закрывали — там, а не в тысяче файлов. Уроки, пережившие свою задачу,
собираются в `docs/LESSONS.md` с датой и ссылкой на источник.
@@ -448,8 +453,10 @@ patch-id кандидата слияния: вердикт к работе за
1. **Никаких изменений в код, если нет issue** и он не помечен «Готово к
разработке» или дальше.
2. **Issue не может быть взят в разработку**, пока у него нет ТЗ с зелёным ревью,
пронумерованных AC с указанием доказательства и назначенного исполнителя.
2. **Issue не может быть взят в разработку**, пока у него нет ТЗ в объёме трека
(§5: на `track:ask` — с зелёным ревью ТЗ; на `show` — до трёх AC; на `ship` —
строка «что меняется и чем проверить»), доказательства для каждого AC и
назначенного исполнителя. Инфраструктурная задача ТЗ не пишет (§1).
3. **Issue не может быть взят дважды.** Занятие фиксируется назначением, меткой и
комментарием с именем ветки. У одного исполнителя одновременно не более одного
issue в разработке.
@@ -469,14 +476,17 @@ patch-id кандидата слияния: вердикт к работе за
правка. Блокирующая находка отправляет текущий issue в «Заблокировано».
10. **Каждый коммит класса A и B несёт трейлер `Issue: #NN`**, ветка называется
`issue/NN-slug`, а `User-Visible: yes` требует правок в **обоих** changelog в
том же коммите. После `cherry-pick -x` служебная строка `(cherry picked
том же коммите. Документационный коммит — только файлы класса C — трейлеров
не требует (#701, как `skip issue` у CPython); хук `commit-msg`, CI и
`process-gate` судят одинаково. Коммит класса D несёт `Release:` (п.12). После `cherry-pick -x` служебная строка `(cherry picked
from ...)` должна оставаться выше финального блока трейлеров: перед push
проверяем порядок через `git show -s --format=full HEAD`.
11. **Документация — в том же коммите, что поведение.** Отдельным «допишу потом»
коммитом документация не бывает.
12. **Сгенерированное не коммитится само по себе.** Только релизный промоушен или
принятие эталонов с доказательством ревью: URL Linux CI run либо хеш
аттестованного WSL-артефакта.
принятие эталонов с доказательством ревью: трейлер `Release:` и ровно один
источник — URL Linux CI run (`Baseline-Reviewed:`) либо хеш аттестованного
WSL-артефакта (`Baseline-Reviewed-Local:`), §10.1.
13. **Golden-эталоны принимаются только** `npm run golden:accept -- --reviewed` по
полному Linux-артефакту: либо GitHub CI, либо `npm run golden:wsl:capture` в
WSL/ext4 с clean опубликованным SHA и машинно-проверяемым паспортом. Второй
@@ -516,8 +526,9 @@ patch-id кандидата слияния: вердикт к работе за
- **Заход и цикл — разные величины.** Заход — сколько раз ревью отработало; он
виден в имени документа (`-r1`, `-r2`, …) и нужен, чтобы два документа не
затёрли друг друга. Цикл — единица бюджета §4. Заходов законно бывает больше,
чем циклов, поэтому порог проверки 7 в `scripts/process-gate.mjs` выше лимита
циклов (шесть документов = четыре цикла плюс два ребейза).
чем циклов, поэтому порог проверки №7 в `scripts/process-gate.mjs`
(`REVIEW_DOC_LIMIT` — шесть документов одного вида на issue) выше лимита
циклов: четыре цикла плюс два ребейза.
- Метка `review-4` ставится, когда исчерпан **бюджет циклов**; конвейер снимать
её не вправе — это решение владельца. Если бюджет пересчитан и оказался ниже
лимита, конвейер сообщает пересчёт, но метку не трогает.
@@ -628,7 +639,7 @@ patch-id кандидата слияния: вердикт к работе за
| Аналитик | разбор, оценки, поверхности | окончательно ставить приоритет |
| Автор ТЗ | раздел `## ТЗ` в теле issue | ревьюить своё ТЗ |
| Ревьюер ТЗ | `docs/reviews/SPEC-REVIEW-NN-rN.md` | править ТЗ вместо автора |
| Разработчик | код, автотесты, документация, changelog | ревьюить свой код, принимать golden |
| Разработчик | код, автотесты, документация, changelog | ревьюить свой код; принимать golden — кроме сдвига своей задачи с меткой `ci:golden` по §3 п.13 (§8, #697) |
| Ревьюер кода | `docs/reviews/CODE-REVIEW-*-rN.md`, проверка AC | править продуктовый код |
| Релиз-менеджер | пре-релиз, стабильный релиз, закрытие issue | добавлять код в релизный коммит |
| Владелец | приоритет, ценность, скоуп, отклонение, арбитраж, хотфикс | — |
@@ -733,9 +744,16 @@ issue #NN
## 8. Гейты
**Локальный гейт перед выходом из «В разработке»** — минимальный набор,
покрывающий изменённые поверхности (действующее правило владельца):
покрывающий изменённые поверхности (действующее правило владельца).
Обязательную часть исполняет одна команда, `npm run gate:small`
(`scripts/gate-small.mjs` — единственный источник её состава, #701): сборка с
typecheck, юниты, целостность и бюджет бандла, `no-new-any`, запрет синхронного
чтения layout в render, запрет записи смоков в приватное состояние,
`lint:unused` и вывод `smoke-select`. Команды ниже — то же самое по отдельности
плюс то, что по диффу и AC:
```
npm run gate:small # обязательная часть одной командой
npx tsc --noEmit
npm test
npm run build && node scripts/bundle-policy.mjs --verify HEAD
@@ -779,7 +797,8 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
(`workflow_dispatch`, только артефакт) с приёмкой вручную: `npm run docs:accept --
--reviewed --from=<распакованный артефакт>` (#246). Ручной путь остаётся
релиз-менеджеру, если бот недоступен. Съёмка на своей машине даёт байтово другой PNG при том же
кадре, и набор из «не того» браузера переписывает все десять файлов без единого
кадре, и набор из «не того» браузера переписывает все кадры (их число — в
`demo/docs/screenshots.mjs`) без единого
содержательного изменения. Приёмка отказывает, если кандидат снят не с этого
дерева, не тем капчуром, не называет свой Chromium или неполон; коммит бота
проверяет релиз-менеджер, коммит ручной приёмки делает человек.
@@ -938,7 +957,8 @@ Performance зелёные на точном SHA, плюс зелёный E2E н
включая свежий контейнер облачного агента, без отдельного шага в инструкции.
- **`commit-msg`** — есть, работает. Отклоняет коммит без терминального
`Issue: #NN`, требует ровно один `User-Visible: yes|no`, а для коммитов,
`Issue: #NN`, требует ровно один `User-Visible: yes|no` (коммит только из
файлов класса C — без трейлеров, §3 п.10, #701), а для коммитов,
трогающих `demo/golden/baselines/**`, — `Release:` плюс ровно один источник:
`Baseline-Reviewed: <URL GitHub run>` либо
`Baseline-Reviewed-Local: sha256:<хеш аттестации>`. Локальный хеш обязан
@@ -991,7 +1011,9 @@ Performance зелёные на точном SHA, плюс зелёный E2E н
`Baseline-Reviewed: <ссылка на прогон CI>` либо
`Baseline-Reviewed-Local: sha256:<хеш аттестации>`;
6. релизный коммит не содержит изменений в `src/` и `custom_components/**/*.py`;
7. документов ревью на один issue не больше четырёх (`-r1`…`-r4`).
7. документов ревью одного вида (`SPEC-REVIEW`, `CODE-REVIEW`) на один issue
не больше шести (`-r1`…`-r6`, `REVIEW_DOC_LIMIT`): четыре цикла плюс два
ребейза (§4).
С токеном GitHub:
@@ -1012,6 +1034,11 @@ Validate стартует от этого push и успевает прочит
документ ревью: он ложится в ветку задачи, пока та в `S4-spec-review` или
`S7-code-review`, то есть заведомо вне рабочего множества.
**Инфраструктурный диапазон статуса не требует** (#562, §1): если в диапазоне нет
ни одного файла класса A, задача ещё не вошла в поток — она войдёт в него сразу на
`S7-code-review`, — и отсутствие S-метки не отказ. Признак механический, по
диффу, а не по метке `infra`.
**При продвижении в `main` не перепроверяются коммиты, уже достижимые из
prerelease-тега.** После выпуска беты их issue по §2.8 должны быть закрыты, а
stable fast-forward снова включает эти коммиты в диапазон `old-main..candidate`.
+4 -1
View File
@@ -330,7 +330,10 @@ the `dev` branch. Access to the owner's instances is not documented here.
- The card module URL contains `?v=<VERSION from const.py>`. Browsers keep the ES module in
memory cache: after deploying new JS **bump VERSION in const.py and restart HA**,
otherwise a plain F5 will keep the old version.
otherwise a plain F5 will keep the old version. This is a deployment step — a
release candidate or your own local stand. An ordinary task commit bumps neither
`VERSION` nor the committed bundle: both change only in a commit with a
`Release:` trailer (#657, `PROCESS.md` §1).
- After a page reload the HA frontend (with kiosk-mode) sometimes leaves the view empty
("InvalidStateError: Transition was aborted", hui-view is not created for 1–2 min).
Cured by repeating the SPA navigation: pushState + a location-changed event, or just waiting.
+4 -3
View File
@@ -3,8 +3,9 @@
Датированные выводы, пережившие свою задачу (#635). Каждый — одной мыслью, со
ссылкой на источник: документ ревью, issue, аудит. Полный след решений — в
`docs/reviews/INDEX.md` и комментариях issue; здесь только то, что меняет
поведение на следующей задаче. Конспекты ролей (#634) и промпт ревьюера
ссылаются сюда.
поведение на следующей задаче. Читается перед задачей в затронутой подсистеме
вместе с её строками в `docs/reviews/INDEX.md` (PROCESS.md, «Индекс документов
ревью»); конспекты ролей сюда не ведут — урок, ставший правилом, живёт в каноне.
| Дата | Урок | Источник |
|---|---|---|
@@ -12,7 +13,7 @@
| 2026-09-06 | Число смоков/тестов не живёт в тексте: считается командой (`npm run inventory`). | #472, PROCESS.md |
| 2026-09-09 | Мутанты по диффу — на кандидате, не на каждом push: 48 из 56 часов job-минут за два дня ушли в отменённые прогоны. | #510 |
| 2026-09-13 | Ненулевой exit гарда ≠ «мутант пойман»: setup-failure, invalid-mutation и survived различаются; засчитывается только assertion-killed. | #558, #568 |
| 2026-09-18 | Ревью на не принятых эталонах — потерянный цикл: `golden:verify` ревьюер гоняет лично, эталоны принимаются до `S7`. | CODE-REVIEW-598-r1 H1 |
| 2026-09-18 | Ревью на не принятых эталонах — потерянный цикл: `golden:verify` ревьюер гоняет лично, эталоны принимаются до `S7`. С #697 — только у задачи с `ci:golden`; сдвиг остальных принимает бот на `dev` перед бетой. | CODE-REVIEW-598-r1 H1, #697 |
| 2026-09-20 | Стенд ≠ Home Assistant: нативный `<dialog>` и `ha-dialog` — разные ветки; свидетель для реального HA — фикстура #505, а не golden стенда. | аудит 22.09 A/H1, #607, #609 |
| 2026-09-20 | Контролируемое поле без `live()` показывает не то число, что в состоянии; клампить на коммите (`change`), не на каждом символе. | аудит 22.09 A/H2, #608 |
| 2026-09-21 | Лог без итоговой строки — обрыв, а не «ok»: отчёт судит по маркеру завершения и исходу шага, не по отсутствию `FAIL`. | #604 |
+4 -2
View File
@@ -3,8 +3,10 @@
> The current state for a resuming session: a generated snapshot, the current
> cycle and the standing decisions that explain it. Rules are not here — the
> process is `PROCESS.md`, release mechanics are `docs/DEVELOPMENT.md` › Release,
> and task scope and status live in GitHub Issues and their labels. Update a row
> in the same commit as the change it describes (`PROCESS.md` §2.6).
> and task scope and status live in GitHub Issues and their labels. The snapshot
> below is generated — never edit it by hand; the prose sections after it are
> edited by hand, a row in the same commit as the change it describes
> (`PROCESS.md` §2.6).
## Snapshot
+16 -7
View File
@@ -28,10 +28,12 @@ golden, а то, чего автоматика не видит, собрано
подложки, Glow и декора проверяет пустую страницу; солнце с азимутом, при
котором луч не достигает единственного окна (#89), — та же ошибка в
геометрии.
4. **Тест, охраняющий механизм, сопровождается мутантом** в
`scripts/mutation-registry.mjs`: 2–5 строк патча, воспроизводящего поломку,
против которой тест заведён, и тест обязан на ней краснеть. Чистым функциям
с обычными юнитами мутант не нужен.
4. **Мутант в `scripts/mutation-registry.mjs` обязателен, когда защита живёт в
продуктовом коде и проверяется дорогим гейтом** (смок, бэкенд, golden):
2–5 строк патча, воспроизводящего поломку, против которой тест заведён, и
тест обязан на ней краснеть. Для чистых юнитов достаточно прогона со снятой
защитой, приведённого в документе ревью. Правило одно — PROCESS.md §2.7
(#701); на треке `show` отсутствие мутанта — Low (§10.4).
5. **Тавтологический ассерт — читающий то же свойство, которое код только что
выставил, — не пишется вовсе.** Он может упасть только при удалении строки,
но не при её неработоспособности.
@@ -45,7 +47,8 @@ golden, а то, чего автоматика не видит, собрано
Проверка: `node scripts/mutation-gate.mjs --check` — якоря патчей живы;
полный прогон — workflow `mutation-gate.yml` (шесть чересполосных шардов
`--shard=i/6` — при четырёх шард упёрся в потолок 60 минут на 810 мутантах, #604;
`--shard=i/6` — при четырёх шард упёрся в потолок 60 минут, #604; число мутантов
считает `npm run inventory`, а не этот текст;
один зафиксированный commit/tree для всего прогона; каждый артефакт несёт
identity и исход шага прогона, а отдельный агрегатор fail-closed отвергает
смешанные, неполные или прерванные по таймауту evidence даже при частичном
@@ -250,6 +253,12 @@ c._drag = { id, sx, sy }; // private-ok: #NNN состояние жеста, о
## Локальный набор перед пушем (#343)
Обязательный локальный набор — `npm run gate:small`; его состав живёт в
`scripts/gate-small.mjs` и нигде больше не переписывается (#701). Хук `pre-push`
гоняет его сам для веток задач — таблица «В хуке» ниже. Ручной
`node scripts/pre-push-gate.mjs` из этого раздела — расширенный прогон:
типы, юниты, смоки и мутанты по диффу.
Красный CI — дорогой способ узнать о проблеме: пять минут ожидания, а при
код-ревью ещё и лишний раунд. Прецедент назван в задаче: находка r2-H1 в #329
стоила целого раунда и ловилась локальным `npm test`.
@@ -736,8 +745,8 @@ See `demo/performance/README.md` for commands and the budget-review contract.
Остальные пункты с пометкой `[manual]` описывали обычное поведение режимов,
редакторов, диалогов, правил значков и `houseplan-space-card`. Для них нужен
смок, а не ручная проверка: задача, которая трогает такую поверхность без
смока, пишет его в том же коммите (правило `[auto: …]` выше). Пункты
`[manual]` разделов ниже остаются как есть.
смока, пишет его в том же коммите (правило `[auto: …]` выше). Оставшиеся пункты
`[manual]` разделов выше остаются как есть.
## Release regression quickies
+11 -10
View File
@@ -94,12 +94,14 @@
## Реализация (`S6-in-progress`)
- Занятие: `Взял: <роль> · сессия <id> · ветка issue/NN-slug`; WIP — одна
задача в разработке на исполнителя
задача в разработке на исполнителя, не больше трёх на цикл релиза и двух в
код-ревью
([§2.6](../../PROCESS.md#26-в-разработке--реализация),
[§7.2](../../PROCESS.md#72-шаблоны-комментариев)).
- Ветка `issue/<NN>-<slug>`; каждый коммит несёт трейлеры `Issue: #<NN>` и
`User-Visible: yes|no`. `User-Visible: yes` требует правок в обоих changelog
в том же коммите. После `cherry-pick -x` трейлеры остаются последним блоком
- Ветка `issue/<NN>-<slug>`; каждый коммит с файлами классов A, B или D несёт
трейлеры `Issue: #<NN>` и `User-Visible: yes|no`, коммит только из
документации — без трейлеров. `User-Visible: yes` требует правок в обоих
changelog в том же коммите. После `cherry-pick -x` трейлеры остаются последним блоком
([§2.6](../../PROCESS.md#26-в-разработке--реализация),
[§3 п.10](../../PROCESS.md#3-правила)).
- Автотесты — часть реализации: каждый AC с пометкой `unit` / `backend` /
@@ -137,13 +139,12 @@
## Гейты перед хендоффом
- Минимальный набор по изменённым поверхностям: `npx tsc --noEmit`,
`npm test`, `npm run build` + `bundle-policy --verify`, `smoke-select` и
целевые смоки, `no-new-any`; по диффу — `model-invariants`,
- Обязательная часть — `npm run gate:small`: его состав живёт в
`scripts/gate-small.mjs` и нигде не переписывается. По диффу и AC сверх
него — целевые смоки из вывода `smoke-select`, `model-invariants`,
`pytest tests_backend`, junction parity; `golden:verify` — только с меткой
`ci:golden`. Команды —
в каноне ([§8](../../PROCESS.md#8-гейты)); `npm run gate:small` собирает
обязательную часть (`docs/TESTING.md`, «Локальный набор перед пушем»).
`ci:golden` ([§8](../../PROCESS.md#8-гейты); `docs/TESTING.md`, «Локальный
набор перед пушем»).
- Бандл в коммит задачи не идёт: сборка переписывает отслеживаемый `dist/`,
перед коммитом — `npm run bundle:clean`; хук `commit-msg` отклоняет пути
бандла без трейлера `Release:` (#657, [§1](../../PROCESS.md#1-основное-правило)).
+4 -3
View File
@@ -83,9 +83,10 @@
([§8](../../PROCESS.md#8-гейты)).
- Условие честности сужения: ревьюер обязан перечислить, какие гейты прогнал,
какие нет и почему ([§8](../../PROCESS.md#8-гейты)).
- Зелёный `pytest tests_backend` без Home Assistant скипает `test_ha_*.py` и
ничего не доказывает — это «чего не проверял» (`docs/TESTING.md`;
[§8](../../PROCESS.md#8-гейты)).
- Зелёный `pytest tests_backend` без Home Assistant `test_ha_*.py` не
собирает вовсе — их нет ни в `passed`, ни в `skipped` (строка `HA harness NOT
collected`), и такой прогон про HA ничего не доказывает — это «чего не
проверял» (`docs/TESTING.md`; [§8](../../PROCESS.md#8-гейты)).
## Повторный раунд
+4 -9
View File
@@ -8,13 +8,8 @@
Здесь остались только ТЗ, на которые ссылаются живые код, тесты и документы (ADR, `ISOMETRIC.md`, `SUN.md`, `RADAR.md`, `LIGHT.md`, `DECOR-EDITOR.md`, support-relay), и те, на которые ссылаются они сами. Остальные ТЗ выпущенных задач перенесены в [`legacy/specs/`](../../legacy/specs/) (#682): историю не переписываем, ссылки из документов ревью ведут по SHA и живут дальше. Каталог нужен `scripts/task-packet.mjs` и проверке 3 `scripts/process-gate.mjs`.
## Обязательные release-артефакты ТЗ
## Правила ТЗ
Если задача меняет пользовательское поведение, её ТЗ обязано явно перечислить:
- записи в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- затронутую пользовательскую документацию;
- требуемые screenshots/golden и способ их review, если меняется визуал;
- release/performance/security artifacts, если они входят в acceptance gate.
Отсутствие этого раздела не означает, что документация необязательна. Для чистого refactoring ТЗ должно прямо зафиксировать отсутствие пользовательских изменений и перечислить технические доказательства безопасного поведения.
Здесь не живут (#701): архив правил не задаёт. Обязательные разделы ТЗ, включая
release-артефакты (changelog RU+EN, документация, скриншоты и golden, perf и
security, если они входят в приёмку), — `PROCESS.md` §7.1 и DoR `S5-ready` (§2).
+43
View File
@@ -0,0 +1,43 @@
// Классы изменений, PROCESS.md §1 — одна таблица на все гейты (#701).
//
// Вынесена из process-gate.mjs: её читает и `validate-commit-provenance.mjs`
// (хук commit-msg), которому process-gate сам импортирует — общий модуль
// снимает круговую зависимость.
// Порядок важен: D проверяется первым, иначе собранный бандл попадёт в A,
// а demo/golden/baselines — в B.
const CLASS_D = [
/^dist\//,
/^custom_components\/houseplan\/frontend\//,
/^demo\/srv\/assets\/houseplan-card\.js$/,
/^demo\/golden\/baselines\//,
];
const CLASS_A = [
/^src\//,
/^custom_components\/houseplan\/.*\.py$/,
/^hacs\.json$/,
/^custom_components\/.*\/manifest\.json$/,
/^custom_components\/.*\/translations\//,
];
const CLASS_B = [
/^test\//, /^tests_backend\//, /^demo\//, /^scripts\//,
/^\.github\//, /^\.githooks\//, /^rollup\.config\.mjs$/, /^tsconfig.*\.json$/,
/^package(-lock)?\.json$/, /^pytest\.ini$/, /^\.gitignore$/, /^\.gitattributes$/,
// Пины toolchain — производные от validate.yml (#496), конфиг сборки.
/^\.nvmrc$/, /^\.python-version$/,
];
const CLASS_C = [
/^docs\//, /^README/, /^CHANGELOG/, /^AGENTS\.md$/, /^LICENSE$/,
/^CONTRIBUTING\.md$/, /^PROCESS.*\.md$/, /^(CODE|SPEC)-REVIEW-.*\.md$/,
// #682: архив выпущенного — документы ревью и ТЗ прошлых линий. Только
// Markdown; исполняемого там нет (#678 вынес всё прочее из дерева).
/^legacy\//,
];
export function classify(path) {
if (CLASS_D.some((r) => r.test(path))) return 'D';
if (CLASS_A.some((r) => r.test(path))) return 'A';
if (CLASS_B.some((r) => r.test(path))) return 'B';
if (CLASS_C.some((r) => r.test(path))) return 'C';
return '?';
}
+4 -1
View File
@@ -25,9 +25,12 @@ const ROOT = fileURLToPath(new URL('..', import.meta.url));
/** Маршруты входа по роли. `budget: null` — только замер, без порога. */
export const ROUTES = Object.freeze({
// #701: `docs/STATUS.md` ушёл из входа автора — снимок версий и цикла нужен,
// когда сессия возобновляет работу или готовит релиз, а задачу ведёт её пакет
// (`task-packet.mjs`). Минус 691 слово на каждом входе.
author: {
budget: 12000, // AC1 #634
files: ['docs/SCOPE.md', 'AGENTS.md', 'docs/process/AUTHOR.md', 'docs/STATUS.md'],
files: ['docs/SCOPE.md', 'AGENTS.md', 'docs/process/AUTHOR.md'],
},
reviewer: {
budget: 9000,
+2 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env node
// Локальный гейт лёгкого трека одной командой (#479): `npm run gate:small`.
//
// PROCESS §8 перечисляет автору шесть команд, и в #476 они гонялись
// Этот файл — единственный источник состава обязательной части §8 (#701):
// канон и конспекты его не переписывают, а называют. В #476 команды гонялись
// последовательно, вперемешку с гейтами, к задаче не относящимися. Здесь
// обязательная часть §8 начинается параллельно — сборка с typecheck, «новый
// код не добавляет any», выбор смоков по диффу. Юниты читают свежий `dist`,
+23
View File
@@ -8158,6 +8158,29 @@ const MUTANT_DEFINITIONS = [
replace: ' .hdr > .head { flex-wrap: wrap; padding: 5px 8px; gap: 6px; }',
}],
},
// #701: документационный коммит трейлеров не требует — и только он.
{
id: 'docs-only-commit-needs-trailers-again',
guard: 'node --test --test-name-pattern="#701" test/commit-provenance.test.mjs',
because: '#701 (PROCESS §3 п.10): rule #1 guards product code, not a typo in a guide; a docs-only '
+ 'commit carries no Issue/User-Visible trailers',
patches: [{
file: 'scripts/validate-commit-provenance.mjs',
find: ' const exempt = isDocsOnlyCommit(changedFiles) && !issues.length && !visible.length;',
replace: ' const exempt = false; // mutant: every commit needs trailers',
}],
},
{
id: 'docs-only-exemption-leaks-to-code',
guard: 'node --test --test-name-pattern="#701" test/commit-provenance.test.mjs',
because: '#701: one file outside class C makes the commit subject to rule #1 again; the exemption '
+ 'must not cover a commit that also touches src/**',
patches: [{
file: 'scripts/validate-commit-provenance.mjs',
find: " return changedFiles.length > 0 && changedFiles.every((file) => classify(file.replaceAll('\\\\', '/')) === 'C');",
replace: " return changedFiles.length > 0 && changedFiles.some((file) => classify(file.replaceAll('\\\\', '/')) === 'C'); // mutant",
}],
},
{
id: 'header-menu-drops-pdf',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+5 -37
View File
@@ -39,36 +39,11 @@ import { fileURLToPath } from 'node:url';
import { resolveValidationRange } from './validate-commit-provenance.mjs';
// --- классы изменений, PROCESS.md §1 ---
// Порядок важен: D проверяется первым, иначе собранный бандл попадёт в A,
// а demo/golden/baselines — в B.
const CLASS_D = [
/^dist\//,
/^custom_components\/houseplan\/frontend\//,
/^demo\/srv\/assets\/houseplan-card\.js$/,
/^demo\/golden\/baselines\//,
];
const CLASS_A = [
/^src\//,
/^custom_components\/houseplan\/.*\.py$/,
/^hacs\.json$/,
/^custom_components\/.*\/manifest\.json$/,
/^custom_components\/.*\/translations\//,
];
const CLASS_B = [
/^test\//, /^tests_backend\//, /^demo\//, /^scripts\//,
/^\.github\//, /^\.githooks\//, /^rollup\.config\.mjs$/, /^tsconfig.*\.json$/,
/^package(-lock)?\.json$/, /^pytest\.ini$/, /^\.gitignore$/, /^\.gitattributes$/,
// Пины toolchain — производные от validate.yml (#496), конфиг сборки.
/^\.nvmrc$/, /^\.python-version$/,
];
const CLASS_C = [
/^docs\//, /^README/, /^CHANGELOG/, /^AGENTS\.md$/, /^LICENSE$/,
/^CONTRIBUTING\.md$/, /^PROCESS.*\.md$/, /^(CODE|SPEC)-REVIEW-.*\.md$/,
// #682: архив выпущенного — документы ревью и ТЗ прошлых линий. Только
// Markdown; исполняемого там нет (#678 вынес всё прочее из дерева).
/^legacy\//,
];
// Классы изменений (PROCESS.md §1) живут в change-classes.mjs (#701): их
// читает и хук commit-msg, который судит, нужен ли коммиту трейлер.
import { classify } from './change-classes.mjs';
export { classify };
const CHANGELOGS = ['docs/CHANGELOG.md', 'docs/CHANGELOG.ru.md'];
@@ -90,13 +65,6 @@ export const RULES = {
10: 'DoR по моменту коммита',
};
export function classify(path) {
if (CLASS_D.some((r) => r.test(path))) return 'D';
if (CLASS_A.some((r) => r.test(path))) return 'A';
if (CLASS_B.some((r) => r.test(path))) return 'B';
if (CLASS_C.some((r) => r.test(path))) return 'C';
return '?';
}
// --- разбор коммитов ---
// Тело коммита многострочное, поэтому поля режутся не по переводам строк:
+16 -3
View File
@@ -4,6 +4,7 @@ import { basename } from 'node:path';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { bundleCommitErrors } from './bundle-policy.mjs';
import { classify } from './change-classes.mjs';
const TRAILER = /^([A-Za-z][A-Za-z0-9-]*):\s*(.*?)\s*$/;
export const ENFORCEMENT_BOUNDARY = '8e2973fa7a7cb1a80204ff95ecf3f2d7c36ed2ce';
@@ -52,15 +53,27 @@ export function terminalTrailers(message) {
return out;
}
/**
* #701 (PROCESS.md §3 п.10): документационный коммит — только файлы класса C —
* трейлеров не требует, как `skip issue` у CPython. Правило №1 охраняет
* продуктовый код, а не опечатку в гайде. Пустой список файлов (сообщение без
* `--staged`) — не документационный коммит: судить нечем, правило прежнее.
* Трейлеры, если они есть, судятся всегда.
*/
export function isDocsOnlyCommit(changedFiles = []) {
return changedFiles.length > 0 && changedFiles.every((file) => classify(file.replaceAll('\\', '/')) === 'C');
}
export function validateCommitMessage(message, changedFiles = [], { baselineIndex = undefined, authorDate = null } = {}) {
const trailers = terminalTrailers(message);
const errors = [];
const issues = trailers.get('Issue') || [];
if (!issues.length || issues.some((value) => !/^#[1-9][0-9]*$/.test(value))) {
const visible = trailers.get('User-Visible') || [];
const exempt = isDocsOnlyCommit(changedFiles) && !issues.length && !visible.length;
if (!exempt && (!issues.length || issues.some((value) => !/^#[1-9][0-9]*$/.test(value)))) {
errors.push("missing or invalid terminal 'Issue: #<positive number>' trailer");
}
const visible = trailers.get('User-Visible') || [];
if (visible.length !== 1 || !/^(yes|no)$/.test(visible[0])) {
if (!exempt && (visible.length !== 1 || !/^(yes|no)$/.test(visible[0]))) {
errors.push("expected exactly one terminal 'User-Visible: yes|no' trailer");
}
const normalizedFiles = changedFiles.map((file) => file.replaceAll('\\', '/'));
+16
View File
@@ -105,3 +105,19 @@ test('the audited beta.2 baseline exception is exact and golden-only', () => {
assert.equal(validateHistoricalCommit(`${audited.slice(0, -1)}2`, message, changed).length, 2);
assert.match(validateHistoricalCommit(audited, 'Update baseline', changed)[0], /Issue/);
});
// #701 (PROCESS.md §3 п.10): трейлеры — только на коммитах с продуктовыми и
// инфраструктурными файлами; документационный коммит их не требует.
test('#701: документационный коммит (только класс C) трейлеров не требует', () => {
assert.deepEqual(validateCommitMessage('docs: fix a typo in the guide', ['docs/USER-GUIDE.md', 'README.md']), []);
assert.deepEqual(validateCommitMessage('docs: changelog wording', ['docs/CHANGELOG.md']), []);
// Хоть один файл вне класса C — прежнее правило.
assert.equal(validateCommitMessage('fix: x', ['docs/USER-GUIDE.md', 'src/card.ts']).length, 2);
assert.equal(validateCommitMessage('test: x', ['test/a.test.mjs']).length, 2);
assert.equal(validateCommitMessage('build: x', ['dist/houseplan-card.js']).length >= 2, true);
// Судить нечем — не документационный коммит.
assert.equal(validateCommitMessage('docs: typo', []).length, 2);
// Трейлер, если он есть, судится всегда: кривой номер — ошибка и в docs-коммите.
assert.equal(validateCommitMessage('docs: typo\n\nIssue: #x', ['docs/a.md']).length, 2);
assert.deepEqual(validateCommitMessage('docs: typo\n\nIssue: #9\nUser-Visible: no', ['docs/a.md']), []);
});
+10
View File
@@ -44,3 +44,13 @@ test('#634 entry-cost: AGENTS.md называет те же маршруты в
assert.deepEqual(route('reviewer'), ROUTES.reviewer.files);
assert.deepEqual(route('changing the pipeline'), ROUTES.canon.files);
});
test('#701 D14: промпт ревьюера читает маршрут reviewer в том же порядке, что AGENTS.md', async () => {
const { readFileSync } = await import('node:fs');
const { ROUTES } = await import('../scripts/entry-cost.mjs');
const workflow = readFileSync(new URL('../.github/workflows/_process.yml', import.meta.url), 'utf8');
const start = workflow.indexOf('Прочитай в этом порядке, прежде чем судить:');
assert.ok(start > 0, 'нумерованный порядок чтения в промпте найден');
const items = [...workflow.slice(start, start + 3000).matchAll(/^\s+(\d)\. (\S+)/gm)].slice(0, 3).map((m) => m[2]);
assert.deepEqual(items, ROUTES.reviewer.files);
});
+1
View File
@@ -134,6 +134,7 @@ const HOOK_FILES = [
'scripts/pre-push-gate.mjs',
'scripts/branch-state.mjs',
'scripts/process-gate.mjs',
'scripts/change-classes.mjs', // #701: классы изменений — общие для гейта и трейлеров
'scripts/validate-commit-provenance.mjs',
'scripts/bundle-policy.mjs', // #657: правило бандла в проверке происхождения
'scripts/bundle-tree.mjs',
+1 -1
View File
@@ -50,7 +50,7 @@ const KEY_RULES = {
['5-треки-ship-show-ask--метка-владельца', 'ожидаемое поведение уже зафиксировано'],
['71-цепочка', 'Владельцу задаются только продуктовые вопросы'],
['71-цепочка', 'issue остаётся в `S3-spec` и получает `blocked`'],
['26-в-разработке--реализация', 'каждый коммит несёт трейлеры `Issue: #<NN>` и `User-Visible: yes|no`'],
['26-в-разработке--реализация', 'каждый коммит с файлами классов A, B или D несёт трейлеры `Issue: #<NN>` и `User-Visible: yes|no`'],
['3-правила', '`User-Visible: yes` требует правок в обоих changelog в том же коммите'],
['26-в-разработке--реализация', 'шесть классов риска'],
['26-в-разработке--реализация', 'Скоуп не расширяется'],