mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,212 @@
|
||||
# SPEC-REVIEW-425-r1
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/425
|
||||
- ТЗ: `docs/specs/425-core-file-budget.md`, ветка `issue/425-core-file-budget`,
|
||||
SHA `4eae3398a6510ea19b403fd1ac33a5a3cc44881b` (единственный коммит поверх
|
||||
`origin/dev`, класс C: `docs: specify the core file budget gate (#425)`,
|
||||
`Issue: #425` · `User-Visible: no`).
|
||||
- Заход: r1 · трек: полный (снят с `small` автором — «метка small конвейер не
|
||||
запускает»; техническая причина смены трека вне моей компетенции, к
|
||||
содержанию ТЗ отношения не имеет).
|
||||
- Вердикт: **жёлтый**.
|
||||
|
||||
## Скоуп задачи
|
||||
|
||||
Ядро #425: заменить #34 узким, автоматизируемым ограничителем — гейт-храповик
|
||||
на размер `src/houseplan-card.ts` и `src/houseplan-editor-runtime.ts` (растёт
|
||||
выше потолка → красный тест; падает больше чем на 250 строк ниже потолка →
|
||||
тоже красный, чтобы потолок опускали, а не забывали). Только тестовая
|
||||
инфраструктура: один тест, один мутант, строка в `docs/TESTING.md`, закрытие
|
||||
#34. Продуктового кода, UX, i18n, миграций конфига задача не касается
|
||||
(`User-Visible: no`).
|
||||
|
||||
## Как проверялось
|
||||
|
||||
1. `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` — прочитаны целиком.
|
||||
2. Тело issue #425 и оба комментария (переход на `small`, затем откат на
|
||||
полный трек) — прочитаны через `gh issue view 425 --json ...` (MCP-доступ к
|
||||
`get_issue`/`get_issue_comments` не выдан в этой сессии).
|
||||
3. Тело ТЗ прочитано целиком (`docs/specs/425-core-file-budget.md`, 103
|
||||
строки).
|
||||
4. Проверено по факту, а не на слово:
|
||||
- `#34` существует, открыт, `S3-spec` — ссылка и статус в ТЗ не выдуманы;
|
||||
- текущие размеры ядер: `wc -l src/houseplan-card.ts` → 13658,
|
||||
`src/houseplan-editor-runtime.ts` → 14322 — совпадают с колонкой `dev` в
|
||||
таблице ТЗ день в день;
|
||||
- заявленные «42% всего TypeScript» — при подсчёте строками выходит 38.97%
|
||||
(27980/71793), но при подсчёте байтами (как считает `bundle-budget.mjs`
|
||||
— прецедент похожего гейта в этом же репозитории) — 42.66%
|
||||
(1312569/3076509 Б по `src/**/*.ts` на `origin/dev`). Автор посчитал
|
||||
байтами; расхождение с интуитивным «строками» — не ошибка, а другая
|
||||
единица измерения, оставляю как проверенный факт;
|
||||
- `scripts/mutation-gate.mjs`, `test/mutation-gate.test.mjs`,
|
||||
`.github/workflows/mutation-gate.yml` существуют — приём «тест
|
||||
сопровождается мутантом в `scripts/mutation-gate.mjs`» это не догадка, а
|
||||
действующая конвенция репозитория (`docs/TESTING.md`, правило 4);
|
||||
- `scripts/bundle-budget.mjs` — единственный похожий по духу гейт в
|
||||
репозитории — проверен на предмет пересечения: он про initial-view gzip
|
||||
бандла, не про строки исходника; дублирования функциональности нет;
|
||||
- `test/*.test.mjs` — гейта на размер файлов исходников сегодня нет
|
||||
(`grep` по «budget/потолок» ничего похожего не находит) — задача
|
||||
действительно новая, а не повтор существующего.
|
||||
5. `node scripts/process-gate.mjs` на диапазоне `origin/dev..HEAD` — «гейт
|
||||
пройден, предупреждений 0» (класс C, трейлеры корректны, веткование верно).
|
||||
6. Продуктового кода коммит не содержит (`git diff origin/dev...HEAD --stat`
|
||||
— один файл, `docs/specs/425-core-file-budget.md`), поэтому дешёвые гейты
|
||||
(`typecheck`/`test`/`build`) к материалу этого раунда неприменимы — они
|
||||
отвечают на вопрос «работает ли код», а кода в этом раунде нет. Зелёный
|
||||
Validate на `4eae3398` (ссылка в контексте задачи) это подтверждает
|
||||
попутно.
|
||||
|
||||
## Находки
|
||||
|
||||
### Medium-1 — нет обязательных разделов «Сценарий» и «Что человек увидит до и после» (§7.1)
|
||||
|
||||
`docs/specs/425-core-file-budget.md`, весь документ.
|
||||
|
||||
PROCESS.md §7.1 называет эти два раздела первыми не случайно: «ТЗ, которое не
|
||||
может ответить на эти два вопроса, описывает работу, а не изменение
|
||||
продукта». В документе их нет вообще — ни в явном виде, ни как «не
|
||||
применимо» (для секции «UX, модель данных, i18n» автор такую отметку сделал:
|
||||
строка 62 — «Не применимо: продуктового кода задача не касается»; для
|
||||
Сценария и «что увидит человек» — не сделал).
|
||||
|
||||
**Сценарий воспроизведения:** открыть файл и поискать заголовки «Сценарий» и
|
||||
«Что человек увидит» — их нет; DoR-чеклист §2.5 требует полный набор разделов
|
||||
§7.1 перед переводом в `S5-ready`.
|
||||
|
||||
Задача действительно не имеет пользовательской видимости (`User-Visible: no`,
|
||||
гейт CI), поэтому по существу ответ тривиален — но именно поэтому его нужно
|
||||
написать явно, тем же приёмом, что уже применён к UX-разделу: «Сценарий: не
|
||||
применимо — персона отсутствует, единственный наблюдатель контракта —
|
||||
разработчик, читающий вывод `npm test`». Без этой строки документ не проходит
|
||||
формальный чеклист §2.5, хотя по сути вопрос закрыт.
|
||||
|
||||
Серьёзность: Medium, в скоупе — правится добавлением двух коротких абзацев,
|
||||
без изменения контракта.
|
||||
|
||||
### Medium-2 — AC4 (и частично AC5) не называют способ доказательства
|
||||
|
||||
`docs/specs/425-core-file-budget.md:45-46`.
|
||||
|
||||
DoR (§2.5) требует у каждого AC «указано, чем он доказывается: unit / backend
|
||||
/ smoke / golden / «ревью кода»». AC1–AC3 это делают явно («Доказательство:
|
||||
мутант...», «Доказательство: тот же юнит»). AC4 — нет:
|
||||
|
||||
> **AC4**. Потолки записаны числами в самом тесте: любое их изменение видно в
|
||||
> дифе и проходит ревью как решение, а не как побочный эффект.
|
||||
|
||||
Способ доказательства здесь по смыслу — «ревью кода» (структурное свойство:
|
||||
константа лежит в тесте, а не читается из внешнего конфига), но текст этого
|
||||
не говорит, и план автотестов (строки 70–75) AC4 не упоминает вовсе — только
|
||||
AC1, AC2, AC3, AC5. AC5 хотя бы косвенно закрыт пунктом 5 плана
|
||||
(«потолки заданы для обоих ядер и ни для чего больше (AC5)»); у AC4 такой
|
||||
связи нет ни в тексте AC, ни в плане.
|
||||
|
||||
**Как проявится, если не поправить:** на код-ревью придётся решать самому,
|
||||
чем считать AC4 доказанным — юнитом или чтением кода; при полном треке это
|
||||
дало бы почву для расхождения между автором и ревьюером кода.
|
||||
|
||||
Серьёзность: Medium, в скоупе — правится одной строкой
|
||||
(«Доказательство: ревью кода — константы `...` лежат в теле теста, изменение
|
||||
видно в diff»).
|
||||
|
||||
### Low-1 — раздел «Откат» задублирован
|
||||
|
||||
`docs/specs/425-core-file-budget.md:48-50` и `:94-96` — два одинаковых
|
||||
раздела `## Откат` с идентичным текстом «Удаление одного тестового файла и
|
||||
одного мутанта.» (первый явно перенесён из тела issue, второй добавлен при
|
||||
расширении до полного трека, старый не убран).
|
||||
|
||||
Содержательного расхождения нет, ambiguity не создаёт. Снимаю с записью:
|
||||
чинится удалением одного из двух блоков при следующей правке ТЗ, отдельного
|
||||
цикла не требует.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- **Замена #34.** Ссылка живая, статус (`S3-spec`, открыт) соответствует
|
||||
описанию «за 25 дней не сдвинулся»; заявление не выдумано.
|
||||
- **Числа в таблице долга** (13658 / 14322 на `dev`) сверены с деревом
|
||||
напрямую — точное совпадение день в день.
|
||||
- **AC1–AC3, AC5** — однозначны, каждый привязан к конкретному проверяемому
|
||||
условию и (кроме AC5 частично) к явному способу доказательства; план
|
||||
автотестов на чистой функции `coreBudgetViolations(sizes, caps, slack)`
|
||||
описывает все ветки, включая обе границы (строка 74: «границы
|
||||
включительно») — асимметрия «рост без люфта / падение с люфтом 250 строк»
|
||||
прочитана из контракта верно и без противоречий.
|
||||
- **Мутант** соответствует действующей конвенции `docs/TESTING.md` (правило
|
||||
4): 2–5-строчный патч в реальный файл, а не в тест, тест обязан покраснеть.
|
||||
- **Не найдено ни одной догадки, выданной за факт.** Все технические
|
||||
утверждения (существование `scripts/mutation-gate.mjs`, конвенция
|
||||
мутантов, текущие размеры файлов, отсутствие похожего гейта) проверены по
|
||||
дереву репозитория, а не приняты на слово.
|
||||
- **Открытых продуктовых вопросов владельцу нет** — и не должно быть: задача
|
||||
целиком техническая, ни одного вопроса о том, что видит или делает человек,
|
||||
в ней нет.
|
||||
- **`docs/USER-GUIDE.ru.md` и канонические документы подсистем** (`SUN.md`,
|
||||
`LIGHT.md`, `CANVAS.md`, `WALL-THICKNESS.md`, `UX-MODES.md`,
|
||||
`CONFIG-COMPATIBILITY.md`, `TOUCH-SUPPORT.md`) не относятся к этой задаче —
|
||||
видимое поведение не меняется, интерфейсная терминология не используется;
|
||||
проверено тем, что задача помечена `User-Visible: no` и раздел UX ТЗ прямо
|
||||
это фиксирует.
|
||||
- **`docs/SCOPE.md` / Core user jobs.** Задача не закрывает ни одну строку
|
||||
J1–J7 — и не обязана: это не продуктовая фича, а инженерный гейт качества
|
||||
(`User-Visible: no`, класс B/C по DoD), аналогичный уже принятым в этом же
|
||||
репозитории `scripts/bundle-budget.mjs` (#337/#367) и самому
|
||||
`scripts/mutation-gate.mjs` (#85). SCOPE.md ограничивает, что становится
|
||||
видимой функцией продукта; техдолг разработки под этот ограничитель не
|
||||
подпадает. Это не находка, а явно проверенное и подтверждённое отсутствие
|
||||
конфликта.
|
||||
- **Класс изменений и трейлеры коммита r1** — `process-gate.mjs` подтвердил
|
||||
автоматически (см. «Как проверялось», п.5).
|
||||
|
||||
## Чего не проверял и почему
|
||||
|
||||
- **Дешёвые гейты кода** (`npx tsc --noEmit`, `npm test`, `npm run build`,
|
||||
сверка бандла) — не прогонял: материал раунда не содержит продуктового или
|
||||
тестового кода, только текст ТЗ; прогонять их не на чем.
|
||||
- **Мутационный гейт, golden, смоки, инварианты модели, perf-профили,
|
||||
бэкенд-тесты** — не прогонял: код гейта ещё не написан (это стадия ТЗ, не
|
||||
`S6`/`S7`), диф не трогает `src/**`, геометрию, рендер, Python или touch.
|
||||
Ни один из этих гейтов не относится к материалу этого раунда по diff'у или
|
||||
AC.
|
||||
- **`docs/specs/README.md`** — не требую внесения записи об issue #425/#34 в
|
||||
этот индекс: свежие специфицированные задачи (#422, #416) там тоже не
|
||||
зарегистрированы, реестр по факту не поддерживается синхронно (см. и
|
||||
собственную пометку PROCESS.md §7.3 п.1 о его частичном устаревании), это
|
||||
не критерий приёмки ни этой, ни соседних задач.
|
||||
- **Точность исторических чисел** (18 файлов на v1.58, 20287 строк на
|
||||
v1.66.0) — не перепроверял по тегам: это фон мотивации, не AC, ошибка в нём
|
||||
не меняет проверяемость контракта.
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/425-core-file-budget`
|
||||
- SHA: `4eae3398a6510ea19b403fd1ac33a5a3cc44881b`
|
||||
- ТЗ: `docs/specs/425-core-file-budget.md` (единственный файл в диффе с
|
||||
`origin/dev`)
|
||||
|
||||
## Вердикт
|
||||
|
||||
Жёлтый: обе находки Medium в скоупе задачи, High нет. Обе правятся точечно —
|
||||
двумя короткими разделами и одной строкой доказательства у AC4 — без
|
||||
изменения контракта, AC или объёма задачи. Low снят с записью, отдельного
|
||||
действия не требует.
|
||||
|
||||
`Вердикт: жёлтый · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 2 → в задаче · Документ: docs/reviews/SPEC-REVIEW-425-r1.md`
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/425-core-file-budget`, коммит `4eae3398a651` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `d37bea6a940c33f8197d96b008432931ad7edb8f`
|
||||
```
|
||||
git log --all --format='%H %T' | grep d37bea6a940c
|
||||
```
|
||||
- ТЗ `docs/specs/425-core-file-budget.md`, блоб `213aaf2a92ffc550f9fd87d2b4b81338a82359a6`
|
||||
```
|
||||
git log --all --find-object=213aaf2a92ffc550f9fd87d2b4b81338a82359a6 -- docs/specs/425-core-file-budget.md
|
||||
```
|
||||
Reference in New Issue
Block a user