From 6b592c13c07c64afba1e11f6daaa70adcea3cd72 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:35:30 +0000 Subject: [PATCH] docs: review document for #502 Issue: #502 User-Visible: no --- docs/reviews/SPEC-REVIEW-502-r1.md | 184 +++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-502-r1.md diff --git a/docs/reviews/SPEC-REVIEW-502-r1.md b/docs/reviews/SPEC-REVIEW-502-r1.md new file mode 100644 index 00000000..29c5ee08 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-502-r1.md @@ -0,0 +1,184 @@ +# SPEC-REVIEW-502-r1 + +Issue: #502 — «i18n-dead-keys: широкий паттерн `r.+` маскирует мёртвые ключи» +Этап: spec (PROCESS.md §2.4), лёгкий трек (`small`), ТЗ в теле issue. +Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лимит §4 для лёгкого трека). +SHA рабочей копии на момент ревью: `72764cff906d8e27996e638ced8b708251e24657`. + +## Скоуп + +Класс B, один файл `test/i18n-dead-keys.test.mjs` (опционально новый +`test/helpers/i18n-consumers.mjs`). Продуктового кода, i18n-словарей, UX, +миграций задача не касается; `User-Visible: no`. Цель: сузить генератор +«динамических потребителей» в гейте мёртвых i18n-ключей так, чтобы паттерн вида +`^r.+$` (рождённый из `'r' + Date.now().toString(36)`) не маскировал реальные +мёртвые ключи семейства `radar.*`, `room.*`, `run.*` и т. п. + +## Как проверялось + +1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (§1–§10, включая §2.4, + §2.10, §5, §7.1). +2. Прочитано тело issue #502 целиком и оба комментария (S2-анализ, передача на + ревью). Нашедшая задачу цепочка — код-ревью #485 r2 — тоже сверена. +3. Прочитан текущий `test/i18n-dead-keys.test.mjs` на `dev` построчно — + подтверждено, что `expressionPattern` для `BinaryExpression` с `+` + действительно собирает регулярку без требования точки в статической части, + и что `'r' + Date.now().toString(36)` в `src/houseplan-editor-runtime.ts` + (id черновика) даёт ровно `^r.+$`, как написано в issue. +4. Проверено текущее состояние `radar.bad_references`: `grep` показал, что + ключ уже используется как строковый литерал в `src/radar-setup.ts:334` + (`... : message === 'bad_references' ? 'radar.bad_references' : ...`), + т.е. дефект из #485 r2, который и породил эту задачу, на `dev` уже устранён + (коммит `e9fb255a fix: close radar review gaps`), а #485 закрыт. +5. **Эмпирическая проверка выполнимости AC4** (гейт зелёный на реальном дереве + после сужения) — не полагаясь на заявление автора. Собран одноразовый + скрипт, реализующий текущий `expressionPattern` один в один плюс критерий + контракта п.1 («статическая часть содержит `.`») как фильтр + `isKeyShapedPattern`, прогнан на актуальном `src/**` и словарях + `src/i18n/en.json` + `src/i18n/support/en.json` (полный листинг результата + сохранён в сессии, не коммитился, репозиторий не менялся): + + ``` + OLD unused count: 0 [] + NEW unused count: 0 [] + ``` + + Узкий фильтр отбрасывает несколько десятков динамических паттернов (CSS/HTML + шаблоны в `src/day-cycle-render.ts`, `src/backdrop-pick.ts`, + `src/decor-image-editor.ts`, `src/color.ts`, `src/coincident-partitions.ts` и + др. — они никогда не были осмысленными i18n-потребителями, просто случайно + совпадали под старым широким критерием), но ни один из них не превращает ни + один ключ словаря в мёртвый: результат «0 unused» не меняется. Вывод: AC4 — + не догадка, а проверяемо достижимое требование уже на сегодняшнем дереве, при + разумной трактовке контракта п.1–2. +6. Дешёвые гейты (диф не тронут — smoke/golden/backend не запускались, они не + нужны для ревью ТЗ): + - `npx tsc --noEmit` — зелёный (0 ошибок), выполнен на `dev` без изменений. + Смысл: подтвердить, что рабочая копия сама по себе в порядке перед разбором + ТЗ; продуктовый код в этом раунде не менялся, полный `npm test`/`npm run + build` для ревью **спецификации** избыточны (диф — только тело issue). + +## Находки + +Находок High или Medium (в скоупе или вне него) не обнаружено. + +### Low — снимается с записью + +**L1. Раздел «Зависимости» описывает уже не актуальную ситуацию.** +Файл: тело issue #502 (раздел «Зависимости»). +Текст предполагает, что `radar.bad_references` может быть всё ещё мёртвым на +момент реализации и тогда сужённый гейт «покраснеет на нём», требуя ждать +слияния #485. Фактически #485 уже закрыт и слит, ключ уже используется в +`src/radar-setup.ts:334` — зависимость исполнена ещё до передачи ТЗ на ревью. +Раздел не создаёт риска (он написан как условие «если … то», а не как факт) и +не блокирует переход в `S5-ready`: реализатору достаточно перечитать текущее +состояние `dev` перед стартом, что он обязан сделать в любом случае. Снимаю без +правки текста — исправлять формулировку ради контроля версий issue не имеет +смысла, реализатор увидит актуальное состояние сам. + +**L2. Место объявления `DYNAMIC_KEY_FAMILIES` не названо явно.** +Контракт п.3 вводит константу `DYNAMIC_KEY_FAMILIES`, «принятый предположительно» +блок разносит чистые функции в `test/helpers/i18n-consumers.mjs`, а сам гейт +остаётся в `test/i18n-dead-keys.test.mjs` — но не сказано, в каком из двух +файлов будет жить сама константа-список семей. Это техническая, не +продуктовая деталь (расположение файла, PROCESS §7.1: «место, где стоит +гвард» — решает исполнитель), и она не единообразна ни с чем, что требовало бы +одного конкретного ответа. Не блокирует; исполнитель решает свободно. + +## Проверка контракта на непротиворечивость и проверяемость + +- **П.1 (точка в статической части)** — проверяемо: AC1 и AC2 дают конкретные + примеры входов и ожидаемых `true`/`false`, юнит на синтетическом AST. + Убедился (см. «Как проверялось», п.6), что действующая реализация + `expressionPattern` уже собирает `source`/`dynamic` в форме, из которой этот + фильтр вычисляется механически (строка минус все `.+`-вставки содержит `.`). + Однозначно. +- **П.2 (открытый хвост допустим, если после точки)** — это уточнение к п.1, + не отдельный независимый фильтр; пример `.+\.title` корректно иллюстрирует + разрешённый случай, а `^r.+$` — прямое следствие того, что паттерн из п.1 + уже отсеян (в нём нет точки вовсе), а не отдельно запрещённый случай «начала + с хвоста». Формулировка чуть избыточна («паттерн … невозможен по построению + п.1» — верно и прямо сказано в тексте, противоречия нет), Low не завожу. +- **П.3 (явный список семей + непустая причина + запись без покрытия — ошибка + теста)** — проверяемо AC4, оба условия (непустая причина, покрытие ≥1 ключа) + разнесены по разным ветвям мутации в третьем столбце таблицы AC. +- **П.4 (реально мёртвые ключи не маскируются, уходят отдельным issue Codex)** + — граница со скоупом задачи прочерчена верно: класс A (словари) не + затрагивается этим issue, что совпадает с заявленным классом B. Единственный + известный на момент ТЗ случай (`radar.bad_references`) уже не актуален (см. + L1); эмпирическая проверка (п.5 выше) показывает, что на сегодняшнем дереве + других случаев нет — контракт не вступает в противоречие с AC4 «зелёный гейт + на реальном дереве». +- **П.5 (форма сообщения об ошибке не меняется)** — тривиально проверяемо + визуальным сравнением текста в тесте, доказательство не требуется отдельным + AC (описательное требование к диагностике, не к защите). + +Утверждений, поданных как факт о продуктовом поведении без пометки +«предположение», не найдено — блок «Принято предположительно» присутствует, +покрывает единственное содержательное техническое решение (критерий «похоже +на ключ» = наличие точки, и альтернатива, которую отвергли, названа явно). +Продуктовых вопросов владельцу в тексте нет и не требуется: гейт не наблюдаем +пользователем (персонами J1–J7 из `docs/SCOPE.md` этот тест не встречается +вовсе), задача не расширяет и не сужает видимое поведение продукта. + +## Что проверено и корректно + +- Класс изменений (B), `User-Visible: no`, отсутствие миграций/i18n/UX/perf/ + touch-влияния — заявлено верно и подтверждено чтением диффа не требуется, + т.к. диффа кода ещё нет; сам факт, что единственный целевой файл лежит в + `test/**`, подтверждён структурой репозитория. +- Формат AC1–AC5: каждый несёт способ доказательства и (кроме AC5, + честно помеченного «не защита») мутацию, которой он красне́ет — это ровно + требование §2.7 на будущий код-ревью, заранее подготовленное автором, + снижает риск возврата на код-ревью. +- Откат — одним revert-коммитом, без затронутого продуктового кода; + минимально достаточен для класса B. +- Трек `small`: все пять критериев §5 действительно выполняются одновременно + (одна поверхность — один тестовый файл; ни миграции, ни compatibility-полей; + ни нового UX-контракта — потому что UX-контракта нет вовсе; ни perf/touch- + влияния). Полный трек не требовался бы ни по одному критерию. +- Эмпирическая проверка достижимости AC4 (раздел «Как проверялось», п.5) — + сильнее, чем «ревьюер поверил автору»: воспроизведена логика контракта на + реальном src-дереве, а не пересказана. + +## Чего не проверял + +- Полный `npm test` / `npm run build` / `npm run bundle:sync` — не нужны на + этапе ревью спецификации: продуктового и тестового кода ещё нет, диф — + только текст issue. Будут обязательны на код-ревью (§7.2), включая + дисциплину «тест умеет падать» по мутациям, которые сам автор уже назвал + в таблице AC. +- Браузерные смоки, golden, backend pytest, performance-профили — не + применимо: задача не трогает `src/**`, `custom_components/**/*.py` и не + меняет видимый результат. +- Точная реализация `isKeyShapedPattern`/`unusedKeys` как кода — не пишется на + этом этапе (ревьюер ТЗ не правит продуктовый/тестовый код); эмпирическая + симуляция в разделе «Как проверялось» использована только чтобы проверить + ВЫПОЛНИМОСТЬ контракта, а не чтобы заменить будущий код-ревью. + +## Вердикт + +Зелёный. Two Low-находки (L1, L2) сняты с записью выше, без правки текста +issue — они не мешают переходу в `S5-ready` и не создают риска для реализации. +High и Medium (в скоупе или вне него) не найдено. + +--- + +## Материал раунда + +- Issue: #502, тело + 2 комментария (S2-анализ и передача на `S4-spec-review`), + оба от `Matysh`, 2026-09-09. +- ТЗ живёт в теле issue (лёгкий трек), файла в `docs/specs/` нет и не должно + быть. +- SHA рабочей копии на момент вывода вердикта: `72764cff906d8e27996e638ced8b708251e24657` + (сверено непосредственно перед подведением итогов). + +--- + + + +## Материал раунда + +- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`. +- Вердикт конвейера: `green` · High 0