Files
houseplan-card/docs/reviews/CODE-REVIEW-425-r1.md
T
2026-09-02 21:51:06 +00:00

170 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW-425-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/425
- Ветка: `issue/425-core-file-budget`, материал: `git diff origin/dev...HEAD` на SHA `d4ecace6`
- ТЗ: `docs/specs/425-core-file-budget.md` (полный трек), спек-ревью зелёное на r2
(`docs/reviews/SPEC-REVIEW-425-r2.md`)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
## Скоуп
Диапазон `origin/dev..HEAD` — 5 коммитов:
```
4eae3398 docs: specify the core file budget gate (#425)
3df0f164 docs: review document for #425
84b5f63c docs: address the core budget spec review (#425)
f399ad54 docs: review document for #425
d4ecace6 test: cap the two frontend cores with a ratchet (#425)
```
Единственный коммит с продуктовым воздействием на гейты — `d4ecace6`:
```
docs/TESTING.md | 14 ++++++
scripts/mutation-gate.mjs | 13 +++++
test/core-file-budget.test.mjs | 109 ++++++++++++++++++++++++++++++
```
Остальные — спек-документы и ревью-документы предыдущего этапа (класс C), к
код-ревью не относятся содержательно. Диф не трогает `src/**`,
`custom_components/**`, `demo/**` — класс B (гейты и инструменты), issue
переиспользован как разрешено таблицей классов. `User-Visible: no` на каждом
коммите корректно: продуктовый код не менялся.
## Как проверялось
| Гейт | Команда | Результат |
|---|---|---|
| typecheck | `npx tsc --noEmit` | зелёный, 0 ошибок |
| unit (полный) | `npm test` | 1778 тестов, 1777 pass, 1 skip (`issue 281 private exact fixture...` — существующий условный skip, не связан с #425), 0 fail |
| build + сверка копии | `npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` | бандл собран, побайтовое совпадение; копия `demo/srv/assets/**` не коммитится (#255) и не сравнивается |
| целевой тест | `node --test test/core-file-budget.test.mjs` | 7/7 pass |
| мутант (штатный раннер) | `node scripts/mutation-gate.mjs --id=core-budget-ignores-growth` | `ok core-budget-ignores-growth: тест покраснел, как обязан` — поймано 1 из 1 |
| проверка якорей мутанта | `node scripts/mutation-gate.mjs --check` | `ok core-budget-ignores-growth` — патч применим к текущему коду |
| выбор браузерных смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Исполняемого frontend-диффа нет (`src/**/*.ts` не тронут). Browser-smoke этим диффом не выбираются — выбирать нечего.» |
| process-gate (офлайн) | `node scripts/process-gate.mjs --base origin/dev --head HEAD` | «гейт пройден, предупреждений 0» |
| фактические размеры ядер | `node -e "…split('\n').length"` на обоих файлах | `houseplan-card.ts` = 13659, `houseplan-editor-runtime.ts` = 14323 — совпадает с `CAPS` в тесте |
### Чего не проверял и почему
- `node scripts/check-docs.mjs` — не запускал: диф не трогает `src/**`, условие
«любой diff по `src/**` требует check-docs» не выполнено.
- Полный прогон `node scripts/mutation-gate.mjs` (все мутанты) — не гонял:
дорогой прогон, задача трогает ровно один новый мутант, который проверен
адресно через `--id`. Попутно замечено (не находка этой задачи): полный
прогон в этом окружении падает на несвязанном бэкенд-мутанте из-за
отсутствующего `pytest` (`No module named pytest`) — окружение без Python-
венды, не регрессия #425.
- `golden:verify`, `smoke_*.mjs`, `pytest tests_backend`, performance-профили —
диф не меняет визуал, DOM, Python-код или производительность; AC их не
называют. `smoke-select.mjs` независимо подтвердил «выбирать нечего».
- `node scripts/model-invariants.mjs` — диф не касается геометрии, толщины стен,
`layout`, `marker.space`, `open_spans`.
- Ручное тестирование в браузере — задача не создаёт видимого поведения
(`User-Visible: no`, продуктовый код не тронут), пункт неприменим по
определению задачи.
## Разбор по AC
- **AC1** (рост ядра выше потолка роняет `npm test` с числом). Доказано
исполнением, не чтением: мутант `core-budget-ignores-growth`
(`scripts/mutation-gate.mjs:936-948`) добавляет 400 строк в
`src/houseplan-card.ts` перед `export class HouseplanCard`, что больше люфта
(250). Прогон `node scripts/mutation-gate.mjs --id=core-budget-ignores-growth`
дал «тест покраснел, как обязан» — тест реально умеет падать, а не только
умеет проходить. Текст сообщения называет файл, потолок и превышение
(`test/core-file-budget.test.mjs:41-47`), проверено юнитом
«рост выше потолка становится нарушением с числом» (строки 67-72).
- **AC2** (уменьшение больше чем на 250 строк тоже роняет тест, с требованием
опустить потолок). Доказано юнитом «заметное уменьшение требует опустить
потолок» (`:74-79`): проверяет `kind==='shrank'`, `under===300`, текст
содержит «Опустите потолок». Чтением подтверждено, что тот же путь исполнения
используется и на реальных ядрах (`measure` вызывается один раз в верхнем
тесте, `coreBudgetViolations` — чистая функция без ветвления по источнику
данных).
- **AC3** (изменение в пределах люфта тест не трогает). Доказано юнитами
«изменение в пределах люфта не трогает никого» и «границы включительно»
(`:81-92`) — отдельно проверены точки `cap`, `cap-slack`, `cap+1`, `cap-slack-1`,
то есть обе границы включительно и оба соседних шага исключительно.
- **AC4** (потолки — числа в тесте, не вычисление от текущего размера).
Доказательство по ТЗ — «ревью кода плюс юнит на то, что функция принимает
потолки аргументом». Юнит «потолки — числа в этом файле…» (`:99-109`)
парсит блок `const CAPS` из исходника теста и проверяет regex-отсутствие
`measure|readFileSync|process.env` внутри блока — то есть механически
исключает как раз тот способ обмана, который ТЗ называет неприемлемым
(потолок, вычисленный от текущего размера). Прочитано чтением: `CAPS`
(`:21-24`) — действительно литерал из двух чисел, `coreBudgetViolations`
(`:33-57`) получает `caps` и `slack` параметрами и не читает файлы и `env`
сама; чтение файлов инкапсулировано в отдельной `measure` (`:59`), вызываемой
только в интеграционном тесте верхнего уровня. Проверено чтением, не
исполнением, в части «архитектурного» инварианта (что функция не может
прочитать файл сама), исполнением — в части regex-проверки текста.
- **AC5** (гейт судит только два ядра, остальные файлы не ограничены). Доказано
юнитом «потолки заданы для двух ядер и ни для чего больше» (`:94-97`),
сверяющим точный список ключей `CAPS`. Чтением подтверждено, что других мест
в `src/**`, ссылающихся на `CAPS`/`coreBudgetViolations`, нет (единственный
потребитель — сам файл теста).
Все пять AC доказаны: AC1 — мутантом, исполненным лично (не на слово автора);
AC2/AC3/AC5 — юнитами, прогнанными лично; AC4 — комбинацией юнита и чтения кода
с явной пометкой, какая часть доказана как.
## Соответствие ТЗ и процессу
- Числа `CAPS` (`13659`, `14323`) совпадают с фактическим размером файлов на
этом SHA, измеренным независимо (`split('\n').length`), что и есть мера,
описанная в комментарии над `CAPS` (`:14-16`) и совпадающая с мерой в
`measure()` (`:59`) — единственный источник числа, нет второго измерения той
же величины другим способом (правило «одно число — один источник» неприменимо
буквально, т.к. величина инженерная и не видна пользователю, но принцип
соблюдён: потолок и измерение используют одну и ту же функцию подсчёта строк).
- `docs/TESTING.md` дополнен разделом с объяснением, что делать при красном
гейте — соответствует Release-артефактам ТЗ (только `TESTING.md`,
changelog не требуется при `User-Visible: no`).
- Расхождение чисел в теле issue (таблица «dev: 13658») с `CAPS` («13659») —
не дефект: таблица в issue использует `wc -l` (мера issue), тест — мера
`split('\n').length`; автор явно проговорил это расхождение в хендофф-
комментарии и держит одну меру в коде, что и требовалось. Само число в
тексте issue — историческая иллюстрация, не контрактное значение.
- Скоуп ТЗ («один тест с потолками и храповиком, мутант, строка в
`docs/TESTING.md`») выдержан ровно, попутных правок нет.
- SCOPE.md: задача инженерная, `User-Visible: no`, не обязана закрывать
Core user job — прецедент (#337 bundle-budget, #85 mutation-gate) уже принят
ревью ТЗ на r1, не переоткрываю.
## Находки
Нет находок High или Medium. Low не обнаружено сверх уже снятого на этапе ТЗ.
Одна стилистическая заметка, не являющаяся дефектом (не заводится ни как Low,
ни как issue): пункт `docs/TESTING.md:3593` оформлен чек-боксом `- [ ]`, хотя
проверка полностью автоматическая и уже входит в `npm test` — соседние
чек-боксы в этом файле смешивают ручные и автоматические проверки, так что
формат не расходится с текущей конвенцией документа.
## Материал раунда
- Ветка: `issue/425-core-file-budget`
- SHA на момент вывода: `d4ecace6` (сверено непосредственно перед подведением
итогов, см. `git rev-parse HEAD` в начале сессии — совпадает с `HEAD`
диапазона `origin/dev..HEAD`, использованного во всех командах выше)
- Дерево материала: полный диф `origin/dev...HEAD`, 6 файлов изменено (см.
таблицу выше)
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/425-core-file-budget`, коммит `d4ecace64c51` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `226386b38b1cb661fcb45b2c8f01a568b954a70a`
```
git log --all --format='%H %T' | grep 226386b38b1c
```
- ТЗ `docs/specs/425-core-file-budget.md`, блоб `0469a1eec26759469cf469c87136556935a1f629`
```
git log --all --find-object=0469a1eec26759469cf469c87136556935a1f629 -- docs/specs/425-core-file-budget.md
```