Files
houseplan-card/docs/reviews/SPEC-REVIEW-371-r2.md
T
2026-08-29 10:13:09 +00:00

16 KiB
Raw Blame History

SPEC-REVIEW-371-r2

Issue: #371 — Add French localization files Трек: small (лёгкий). Заход r2, блокирующих циклов израсходовано 1 из 2 (израсходован на жёлтый вердикт r1; зелёный вердикт цикла не тратит — см. §4 PROCESS.md и правило #227). Ревьюер: Claude (роль «ревьюер ТЗ»), автор ревизии: Codex (комментарий-ревизия от 2026-08-29T10:08:21Z). Материал: тело issue #371 на момент разбора (текущая версия, с примечанием «Ревизия 2 (по SPEC-REVIEW-371-r1)» в конце тела) — без файла в docs/specs/, соответствует метке small.

Скоуп раунда

Это ревью округа r2 — по правилам §2.9 объём разбора идёт по дельте, а не заново.

Дельта r1→r2 — это правка тела issue, объявленная автором в комментарии-ревизии и в примечании внизу тела:

  1. К2 дополнен абзацем «Плюс (r1-M1)»: французская ветка в entryFallbackPlugin (#353) и расширение smoke entry_stale третьим локальным прогоном.
  2. AC1 дополнен абзацем «Плюс (r1-M2)»: французские аналоги двух German-персональных тестов из test/i18n.test.mjs — глоссарий продуктовых терминов и скан на артефакты не-перевода (кириллица + allow-list явных совпадений с английским).

Больше нигде текст issue не менялся: К1, К3, К4, AC2–AC5, откат, трейлер User-Visible и продуктовая классификация трека — та же строка символов, что была на r1. Дельта локальна (правка двух абзацев ради двух точечных технических пробелов), новой подсистемы не задевает, контракт поведения не меняет объёма — полный разбор с нуля не требуется по критерию §2.9.

Как проверялось

  1. Найден вердикт r1 (жёлтый, 0 High / 2 Medium в скоупе — M1, M2) и текст документа docs/reviews/SPEC-REVIEW-371-r1.md, зафиксировавший SHA сверки кода — 914bb4ed (dev на момент r1).
  2. Получено текущее тело issue #371 (gh issue view 371 --json body,comments) и сверено построчно с текстом, процитированным в SPEC-REVIEW-371-r1.md, чтобы выделить именно дельту, а не пересказ автора о ней.
  3. Для M1 — прочитан scripts/bundle-manifest.mjs:192-224 (entryFallbackPlugin) на текущем dev (dd591a6c, код в этом файле не менялся со времени r1 — issue #371 ещё не имеет кода в репозитории). Подтверждено: ternary строк 208-212 действительно обрывается на ru/de и молча уходит в английский текст для всех остальных языков — ровно та дыра, которую r1 назвал. Новый абзац К2 закрывает её точным описанием ветки (l.startsWith('fr') перед финальным else) с готовым французским текстом в том же стиле, что немецкий и русский варианты (тот же формат: «что случилось — что делать (Ctrl+F5)»).
  4. Для M1 (smoke) — прочитан demo/smoke_entry_stale.mjs целиком. Подтверждено: текущий смок гоняет только en-US/ru-RU (де-локаль в этом конкретном смоке не проверяется вовсе, несмотря на то что у неё есть своя ветка в коде) — формулировка ревизии «третья локаль в существующем цикле en/ru» точна, а не приблизительна.
  5. Для M2 — прочитан test/i18n.test.mjs:327-370, оба German-персональных теста (German catalog keeps the product glossary..., German values equal to English are explicitly reviewed). Подтверждено: это точечные assert’ы конкретных строк плюс регэксп-скан на кириллицу/сентинелы и allow-list точных совпадений с en.json, ни один не читает LANGUAGE_REGISTRY. Новый абзац AC1 обещает French-аналоги той же конструкции (глоссарий ключевых терминов + запрет кириллицы + allow-list совпадений, список которого «собирается по факту словаря» — так же, как собирался немецкий allow-list по факту немецкого словаря, а не заранее).
  6. Проверено, что остальные разделы (К1, К3, К4, AC2-AC5, откат, User-Visible) не изменились с r1 — значит, находки r1 вне M1/M2 (L1, L2) и весь блок «что проверено и корректно» из r1 переносятся без повторной проверки (см. «Унаследовано из r1» ниже).
  7. Гейты (tsc/test/build/bundle:budget/check-docs) не запускались — этап spec, кода French в репозитории по-прежнему нет (ls src/i18n/ не изменился с r1: только de.json/de.ts/en.json/ru.json/registry.ts/language-runtime.ts), гнать нечего — то же основание, что было в r1.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
M1 — entryFallbackPlugin хардкодит ru/de в тексте плашки устаревшего entry (#353), French останется без ветки и молча получит английский текст; ни один AC это не покрывает К2 дополнен точной веткой l.startsWith('fr') с готовым французским текстом в формате существующих веток + smoke entry_stale явно расширяется fr-прогоном как третьей локалью Тело issue, абзац К2, предложение «Плюс (r1-M1): entryFallbackPlugin (#353) несёт локализованную плашку… добавляется французская ветка… и smoke entry_stale расширяется fr-прогоном (третья локаль в существующем цикле en/ru)»
M2 — AC1 заявляет «существующий тестовый механизм расширяется на fr» как единый факт, но два German-персональных теста (глоссарий + скан на артефакты) не зациклены по реестру и не расширяются автоматически AC1 дополнен явным обязательством завести French-аналоги обоих тестов: глоссарий продуктовых терминов + скан на кириллицу/артефакты не-перевода с allow-list Тело issue, абзац AC1, предложение «Плюс (r1-M2) — аналоги двух German-персональных тестов: (а) французский глоссарий… (б) скан на артефакты не-перевода…»
L1 (Low, снята ревьюером в r1 без правки) Не требовала правки текста — снята с записью в r1 SPEC-REVIEW-371-r1.md, раздел L1
L2 (Low, снята ревьюером в r1 без правки) Не требовала правки текста — снята с записью в r1 SPEC-REVIEW-371-r1.md, раздел L2

Обе Medium-находки закрыты содержательно, не декларативно: формулировки ревизии указывают конкретную ветку кода (с точным местом вставки и текстом), конкретный смок и конкретную тестовую конструкцию, а не общие слова «учтём» / «поправим». Технической проверкой (сверка с реальным кодом bundle-manifest.mjs и i18n.test.mjs, п. 3-5 выше) подтверждено, что оба обещания реализуемы буквально так, как написаны, и действительно закрывают тот пробел, который был назван в r1.

Унаследовано из r1

Без повторной проверки в этом раунде принято всё, что не входит в дельту r1→r2 — документ docs/reviews/SPEC-REVIEW-371-r1.md, сверка на SHA 914bb4ed (dev на момент r1; код с тех пор не менялся, issue ещё не реализована):

  • Классификация трека small по пяти критериям §5 (единственная точка расширения — реестр локалей #62, спроектированный под этот случай; одна поверхность; нет миграций конфига; нет нового UX-контракта; нет влияния на perf/touch) — раздел «Что проверено и корректно» r1.
  • К1 (файлы, арифметика вклада 1026+121−21=1126, backend-паритет 6/6).
  • К2 в заявленной на r1 части (registry.ts: loadFrench/токен/запись — точная копия loadGerman; bundle-manifest.mjs: _role, localeRoots, DE_RETRY_ASSET_TOKEN → парный FR-токен и regex (de|fr)-).
  • К3 (авто-выбор по BCP-47: resolveLanguageCode уже обобщён, юнит de-DE/de-AT/de-CH доказывает механизм без доп. кода для fr-FR/fr-CA/fr-BE/fr-CH).
  • К4 (доки: абзац auto-языка в USER-GUIDE строится по существующему шаблону; changelog).
  • AC2-AC5 в исходной формулировке r1 (юнит реестра, смок AC3 как лёгкий клон smoke_german_locale, бюджет AC4, мутационный тест AC5).
  • Продуктовая рамка: локализация не входит буквальной строкой в J1-J7 docs/SCOPE.md, но прецедент #62/#348 уже прошёл этот вопрос — инфраструктура спроектирована под добавление языков, German принят на ней с P1, открывать вопрос заново на четвёртом языке того же пайплайна избыточно (перепроверено: docs/SCOPE.md по-прежнему не содержит строки про локализацию как отдельный job, см. п. «Как проверялось» этого документа — J1-J7 сверены заново, т.к. это дешёвая проверка, но вывод не изменился).
  • Вывод «догадок, выданных за решение, не найдено» — дельта r1→r2 тоже проверена на этот критерий заново (см. ниже) и вывод тот же.

Проверка дельты на новые находки

Прочитаны оба добавленных абзаца ещё раз с вопросом «не появилась ли новая находка вместе с закрытием старой»:

  • Порядок веток в ternary (ru → de → fr → en) ревизия не оговаривает явно, но это не требует решения: проверка идёт по несовпадающим префиксам (ru/de/fr), порядок веток не влияет на результат — не находка, а деталь реализации без развилки.
  • Ревизия не переносит в French-тест конкретные «сентинел»-маркеры German-теста (ZXQPH/QXZ/⟦HP, test/i18n.test.mjs:335) — это не пробел: маркеры проверены, нигде в кодовой базе не используются как общий контракт (grep по .mjs/.ts/.js вне node_modules даёт единственное вхождение — в самом German-тесте), это специфичный для истории German-словаря артефакт, а не воспроизводимая для French проверка. Формулировка ревизии «скан на артефакты не-перевода: кириллица… запрещена» покрывает содержательно значимую часть (реальный риск для контрибьютора, часть рабочего процесса которого проходит на русском) без копирования частности одного раунда.
  • Новый текст не вводит противоречий с K1/K3/K4/AC2-AC5 и не расширяет скоуп задачи (не добавляет пятый язык, не трогает LanguageRuntime, не меняет видимое поведение сверх «French — четвёртый пункт в существующем пикере»).

Новых находок в дельте не обнаружено.

Вердикт

Зелёный: 0 High, 0 Medium. Обе Medium-находки r1 закрыты содержательно и проверяемо; новых находок дельта не породила. Классификация трека, продуктовая рамка и остальной контракт наследуются из r1 без изменений. ТЗ готово к переходу на код-ревью.

Чего не проверял

  • Не открывал приложенный контрибьютором zip-архив и не проверял содержимое French словарей — файлов ещё нет в репозитории (то же основание, что в r1).
  • Не запускал npm run typecheck/test/build/bundle:budget/check-docs — класс A не тронут, кода этой задачи в репозитории ещё нет; фиксировать зелёный прогон не на чем на этапе spec.
  • Не оценивал лингвистическое качество французских строк ни в присланном словаре, ни в новых французских фразах ревизии (текст плашки entryFallbackPlugin) — вне компетенции ревью ТЗ; owner явно оставил финальную вычитку носителю языка постфактум (комментарий от 2026-08-29T09:58:31Z).