17 KiB
Код-ревью #592 · заход r2
SHA материала: 1403fb440b572cec04d2a82a23e29f72bfbeb73f. Ветка:
issue/592-extract-dialogs. Заход r2, блокирующих циклов израсходовано 1 из 4.
Предыдущий раунд: r1, вердикт жёлтый, документ
docs/reviews/CODE-REVIEW-592-r1.md, материал 61c74a70128a29871547519750bae32695f6e56a
(коммиты b76f3e57 + 61c74a70). SHA в вердикте r1 назван прямо (в отличие
от типичного пробела #227) — дельта считается от него без дополнительного
розыска.
Дельта r1 → r2
git diff --stat 61c74a70..1403fb44 -- . ':!dist' ':!custom_components/houseplan/frontend' ':!docs/reviews'
scripts/mutation-registry.mjs | 13 ++---
scripts/no-new-any.mjs | 121 +++++++++++++++++++++++++++++++++---------
test/no-new-any.test.mjs | 102 +++++++++++++++++++++++++----------
3 files changed, 176 insertions(+), 60 deletions(-)
Плюс некодовый коммит d2443298 (docs: review document for #592, добавляет
сам документ r1 в дерево). src/**, test/core-file-budget.test.mjs,
test/editor-dialog-modules.test.mjs, test/houseplan-source.mjs,
test/space-dialog.test.mjs, dist/**, custom_components/houseplan/frontend/**
между r1 и r2 не менялись (проверено git diff --stat по каждому пути —
пусто). Дельта локальна: один коммит 1403fb44, три файла, ровно правка
Medium-находки r1 (M1). Оснований для полного разбора (ребейз на ушедший
вперёд dev, смена контракта, новая подсистема, объём дельты сравним с
исходной задачей) нет — разбор по дельте.
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
M1 (Medium, в скоупе): no-new-any.mjs признавал перенесённой любую добавленную строку, если где-то в любом файле того же диффа нашлась дословно совпадающая удалённая строка — без привязки к паре файлов реального переноса. Демонстрировался рабочий обход: несвязанная уборка удаляет строку с any, новый файл добавляет текстуально совпадающую — гейт молчит. |
Сопоставление переписано на уровне непрерывных блоков ≥ MOVED_BLOCK_MIN (5) строк подряд, привязанных к конкретному файлу-источнику (sourcePath из --- a/...), а не к произвольному месту диффа. Одиночное совпадение больше не засчитывается. Добавлен тест, воспроизводящий ровно сценарий обхода из r1, и мутант заменён так, чтобы явно проверять эту дыру. |
scripts/no-new-any.mjs:152-250 (новая movedLinesByFile, MOVED_BLOCK_MIN); тест test/no-new-any.test.mjs:172-197 — «#592 одиночное совпадение переносом не считается (M1)» — использует буквально тот же пример (<span>${this.host._t(k as any)}</span>), что и в тексте находки r1; мутант scripts/mutation-registry.mjs переименован в no-new-any-forgives-a-single-matching-line, патч опускает MOVED_BLOCK_MIN 5→1, что и есть найденная дыра. |
Проверено исполнением, не на слово автора:
node --test test/no-new-any.test.mjsна1403fb44→ 15/15 pass, включая новый тест M1.node scripts/mutation-gate.mjs --check→ весь реестрok, в том числеno-new-any-forgives-a-single-matching-line(мутант применён,guardпокраснел, патч возвращён — дисциплина «мутант умеет убивать» соблюдена).node scripts/no-new-any.mjs --base origin/dev --head HEADна реальном диффе задачи → «Новых any нет», 1296 из 1395 добавленных строк признаны перенесёнными (было 1297 в r1 при построчном сопоставлении — минус одна строка, и это ровно то случайное совпадение, из-за которого M1 и была найдена; коммит-сообщение1403fb44называет то же число).
M1 закрыта фактически, не декларативно: старый обход (пример из r1,
room-settings-dialog.ts:66 / space-settings-dialog.ts:240) воспроизведён
как регрессионный тест и красит гейт при откате блокового порога до 1 строки.
Унаследовано из r1
Всё, что дельта не задевает, принимается без повторной проверки — документ
docs/reviews/CODE-REVIEW-592-r1.md, SHA 61c74a70128a29871547519750bae32695f6e56a:
- Побайтовая идентичность переноса всех четырёх диалогов (AC1/AC2, К1) —
дельта r2 не трогает ни
src/editors/*.ts, ниhouseplan-editor-runtime.ts. - AC2 (golden, 15 названных эталонов — 0 расхождений; два незаявленных кадра
device-icon-state-table-{light,dark}— предсуществующий дрейф Chromium 152/151, отделён экспериментом на чистомorigin/dev) — рендер не задет. - AC3 (потолок
houseplan-editor-runtime.ts,test/core-file-budget.test.mjs) — файл не менялся. - AC4 (состояние не переехало,
test/editor-dialog-modules.test.mjs, тест проверен мутацией) — файл не менялся. - AC5 (ленивый граф,
bundle:budget) —dist/**иcustom_components/houseplan/frontend/**между r1 и r2 не менялись (провереноgit diff --stat, пусто). - AC6 (якоря мутантов, кроме самого переименованного в этом раунде) — реестр не менялся, кроме записи M1 (see выше).
- AC7 (полный набор
npm test/gate-small) — состав тестов внеtest/no-new-any.test.mjsне менялся. - Смоки AC1 (10 поимённо +
smoke_value_face_source) и выборка 18 смоков по диалогам —demo/**в дельте r2 не тронут (git diff --statпусто). - Три отклонения от ТЗ, разобранные автором в хендоффе (сборщик
test/houseplan-source.mjs,test/space-dialog.test.mjs, имена файлов) — файлы не менялись. - Трейлеры коммитов
b76f3e57/61c74a70и отсутствие правок в CHANGELOG — не переоценивались повторно.
Как проверялось (дельта r2)
Прочитан diff трёх файлов целиком (git diff 61c74a70..1403fb44 -- scripts/mutation-registry.mjs scripts/no-new-any.mjs test/no-new-any.test.mjs),
включая новую реализацию movedLinesByFile построчно:
- парсинг
---/+++теперь требует пробел после трёх дефисов (точнее прежнегоraw.startsWith('---'), который ловил любые три дефиса); - удалённые строки группируются по
sourcePath(файл, из которого строка реально удалена), а не по произвольному ключу — это и есть привязка к паре файлов, которой не хватало в r1; - добавленные строки группируются в непрерывные по номеру строки куски
(
runs), затем для каждого куска ищется наибольший по длине непрерывный блок совпадения среди ещё не потраченных (spent) удалённых строк любого файла-источника; засчитывается только если длина ≥MOVED_BLOCK_MIN; - бюджет ведётся позиционно (
spend/isSpent), поэтому повторная вставка того же блока (тест «один удалённый кусок оплачивает ровно одну вставку») корректно не покрывается дважды.
Три ручных прогона (перечислены выше в разделе «Закрытие раунда r1») плюс чтение реализации — основной объём проверки этого раунда.
Что не является находкой
Разбирал один пограничный случай парсинга: требование raw.startsWith('--- ')
(с пробелом) технически может ошибочно принять за заголовок диффа удалённую
строку кода, буквально начинающуюся с "-- " (например, -- comment →
в диффе --- comment). В TypeScript-файлах, к которым единственно применяется
гейт (isProductTypeScript, src/**/*.ts), такая строка кода практически
невозможна (-- не встречается как первый токен строки вне декремента, а тот
не пишется с пробелом после). Прежняя реализация имела ту же двусмысленность
(raw.startsWith('---'), ещё шире) и просто молча роняла такую строку из
подсчёта — по факту новая версия строже прежней, а не слабее. Не поднимаю это
до находки: нет реалистичного сценария воспроизведения на продуктовом коде,
и это не тот класс дыры, которую M1 описывала (единичное текстовое совпадение
между несвязанными файлами) — блоковый порог её по-прежнему закрывает.
Находки
Нет новых находок этого раунда. M1 (Medium, r1) закрыта — см. таблицу выше.
Что проверено и корректно
- M1 закрыта по существу: регрессионный тест воспроизводит ровно задокументированный в r1 обход и падает без фикса (проверено мутантом, не декларативно).
- Блоковый порог (
MOVED_BLOCK_MIN = 5) обоснован в комментарии и коммит-сообщении: случайное совпадение пяти строк подряд между несвязанными файлами практически невозможно даже при 887 явныхanyв базе; настоящий перенос подсистемы по определению состоит из таких кусков. - Привязка удалённых строк к файлу-источнику (
sourcePath) убирает и вторую часть проблемы r1 — сопоставление больше не глобальное по всему диффу без привязки к файлу, хотя поиск блока всё ещё ищет по всем файлам-источникам диффа (это осознанный выбор: перенос может тянуть из нескольких старых мест — задокументировано в комментарии), что при пороге в 5 строк не открывает находку M1 заново. - Бюджет позиционный (
spend/isSpent), не мультимножественный как в r1: тест на повторную вставку блока подтверждает, что вторая копия остаётся новым кодом. - Реальный дифф задачи по-прежнему проходит гейт («Новых any нет»), дельта в количестве признанных перенесёнными строк (1297→1296) — ожидаемая и объяснённая автором (одна случайная строка перестала засчитываться).
- Мутант переименован и переориентирован на найденную дыру, а не на
соседнюю;
mutation-gate.mjs --checkподтверждает, что весь реестр, включая его, жив. - Трейлеры
1403fb44/d2443298:Issue: #592,User-Visible: no— корректно, правка гейт-инструментария без изменения продукта, правок CHANGELOG не требуется. - Изменения — класс B (
scripts/**,test/**), переиспользуют номер #592 по правилу AGENTS.md, отдельного issue не требуют.
Чего не проверял
npx tsc --noEmit,npm run build(со сверкой бандла),npm run bundle:budget,golden:verify,python -m pytest tests_backend,npm run invariants, полный наборdemo/smoke_*.mjs,check-docs.mjs— дельта r2 не трогаетsrc/**,dist/**,custom_components/**/*.py, геометрию/layout/marker.space/open_spans, рендер илиdemo/**; эти гейты и не запускались в r1 сверх необходимого, и здесь дельта их не задевает. Validate на этом SHA (1403fb44) зелёный (прогон 35385526275, назван в контексте раунда) —tsc/npm test/npm run buildподтверждены им, повторно не гонял.npm testцеликом (2770+ тестов) не перегонял ради факта — целевой файлtest/no-new-any.test.mjsпрогнан отдельно (node --test, 15/15), он же часть полного набора, Validate на этом SHA подтверждает, что остальной набор зелёный.- Смоки — не запускал ни одного: дельта не трогает
demo/**и не меняет видимое поведение;smoke-select.mjsне запускал по той же причине (нет изменённых символов продукта в дельте, только dev-гейт).
Вердикт
Зелёный. Дельта r1→r2 — точечная правка единственной Medium-находки r1, проверена исполнением (тест воспроизводит ровно заявленный обход и умеет падать; мутант подтверждён; реальный дифф задачи по-прежнему проходит гейт). Новых находок нет. Всё, что дельта не касается, унаследовано из r1 по перечню выше.
Материал раунда
- Ветка:
issue/592-extract-dialogs, коммит1403fb440b572cec04d2a82a23e29f72bfbeb73f. - Предыдущий раунд: r1, документ
docs/reviews/CODE-REVIEW-592-r1.md, SHA61c74a70128a29871547519750bae32695f6e56a.
Материал раунда
- Ветка:
issue/592-extract-dialogs, коммит1403fb440b57— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
fde11c38b97a36045e7148f01f2f459c8a2b88a3git log --all --format='%H %T' | grep fde11c38b97a - Тело issue:
b105f7b3eb3f70575ee5751d1a917940da949981400e342e9cb133153057f673 - Вердикт конвейера:
green· High 0