19 KiB
CODE-REVIEW-635-r3
- Issue: #635 «Индекс документов ревью, уроки, правило хранения раундов»
- Этап: code (PROCESS.md §2.7)
- Заход: r3 · блокирующих циклов израсходовано 2 из 4 (лимит 4)
- Материал:
b38bd0c8b5dcce2e17673ee70e0df2c28903f163(рабочая копия репозитория была на нём;git fetch/checkoutне выполнялись).
Скоуп
r2 (CODE-REVIEW-635-r2.md, материал 3e96b37656b9ce49cd56e1aa2f65ebf0808f113b) закрыл вердикт жёлтым с H1 (docs/reviews/INDEX.md, зафиксированный в материале, устарел на собственном --check — ребейз ветки принёс документы #625, снимок индекса их не знал) и M1 (parseFindings/firstParagraph-фолбэк подставлял хвост перенесённой строки буллета вместо текста находки, минимум 11 документов).
В этом репозитории точный SHA 3e96b376… из комментариев issue отсутствует (окружение ревьюера перезатирает историю раунда при перезаходах конвейера — ожидаемо, см. хендофф r2 про 3719a06d→22dea36c), но по дереву и сообщениям коммитов делта видна однозначно: коммит d9bd5a44 — тот самый код r2 (совпадает по содержимому и описанию с материалом r2-вердикта), следующий за ним f855cee8 — публикация документа CODE-REVIEW-635-r2.md, и далее три коммита ровно этого раунда:
49bae62e—--commit-if-staleвreviews-index.mjs+ вызовы изprocess.yml/merge-candidate.mjs(H1);firstParagraphвместо хвоста строки (M1); мутантreviews-index-paragraph-tail;PROCESS.md§2.10.c9f8b50c— перенос гейта свежести из юнит-теста в предполётный шаг Validate (pushвdevonly), т.к. юнит-тест «байтовой свежести» красился бы на любом раунде, покаprocess.ymlне отзеркалирован — это следствие механики самого конвейера, не дефект решения H1.b38bd0c8— шестой сигнал предполётного вердикта вtest/validate-workflow.test.mjs(свидетель на список шагов #336).
git diff d9bd5a44..HEAD (за вычетом самого CODE-REVIEW-635-r2.md) — 10 файлов, 322/-101 строк: scripts/reviews-index.mjs, test/reviews-index.test.mjs, scripts/merge-candidate.mjs, test/merge-candidate.test.mjs, scripts/mutation-registry.mjs, PROCESS.md, .github/workflows/process.yml, .github/workflows/validate.yml, test/validate-workflow.test.mjs, docs/reviews/INDEX.md (пересборка). Это ровно код, который переписывался для закрытия H1/M1, — разбор в этом раунде полный по нему. docs/LESSONS.md, правило хранения раундов в PROCESS.md §2.10 (кроме дополненного абзаца про индекс/гейт), parseDocName, parseVerdict/parseCounts (кроме затронутой numberedItems) делтой не задеты — унаследованы из r1/r2 (раздел ниже).
Как проверялось
- Прочитан полный
git diff d9bd5a44..HEADпо каждому файлу дельты (reviews-index.mjs, оба workflow,merge-candidate.mjs,mutation-registry.mjs,PROCESS.md, оба тестовых файла делты). node --test test/reviews-index.test.mjs— 12/12 pass (9 унаследованных + 3 новых: свидетель на проводку шага Validate,firstParagraph-фикстура,commitIfStale).node --test test/merge-candidate.test.mjs test/validate-workflow.test.mjs— 38/38 pass, включая расширенный#516 AC1, который гоняет реальный сценарий «кандидат после ребейза несёт свежий INDEX.md с обоими документами, коммит индекса — вершинаdev».node scripts/reviews-index.mjs --dir=docs/reviews --checkна самом материале ревью (без модификаций рабочей копии) — «свеж». Это прямое опровержение H1 r2 на текущем SHA: та же команда, которая на3e96b376печатала «устарел», здесь молчит.- Пересчитан заголовок
INDEX.mdнезависимо от скрипта:ls docs/reviews/*.md | grep -v INDEX.md | wc -l→ 1000, шапка файла заявляет «Документов: 1000» — совпадает буквально, не только по--check. - Мутант
reviews-index-paragraph-tailпроверен вручную: патчcurrent = [line]вместо накопления абзаца → целевой тест#635 r2: первый абзацпадает с ожидаемой ошибкой (обрывок'пусто). Не эскалирую…'вместо целого абзаца), файл восстановлен (git status— чисто после). Тест умеет падать. - Прогнал
parseFindingsна всех 11 документах, названных в M1 r2 (CODE-REVIEW-485-r4/r1/r2,258-r1,514-r3,476-r1,304-r1,SPEC-REVIEW-199-r1,309-r1,152-r1,419-r1) черезnode --input-type=module -e: во всех текст теперь начинается с начала предложения, ни одного обрывка «пусто)» или висячей кавычки. - Скан по всему каталогу (987 находок во всех документах, не только в 11 названных) — не нашёл искажённых обрывков (единственное совпадение моей грубой эвристики оказалось ложным:
--variants=60— легитимный флаг, начинающийся с-, не буллет). - Для новой находки Low (ниже) — прямое сравнение поведения регэкспа с удалённой веткой
нет\b: пересчиталparseFindingsпо всему каталогу с патчем и без — оба раза 987 находок, 12/12 тестов проходят в обоих случаях; файл восстановлен.
Находки
Low — firstParagraph: ветка нет\b в фильтре мёртвая из-за ASCII-only \b в JS-регэкспах, расходится с собственным doc-комментарием
Файл: scripts/reviews-index.mjs:135 (комментарий), :149 (код).
Комментарий к firstParagraph утверждает: «абзац, начинающийся с «не найдено»/«нет», — не находка». Код:
if (NOTHING_RE.test(text) || /^(?:\**(?:High|Medium|Low)\**\s*)?(?:не найдено|не обнаружено|нет находок|нет\b|отсутству)/i.test(text)) return null;
\b в JS без флага /u — граница между \w (ASCII-класс) и не-\w; кириллические буквы в \w не входят, поэтому у «нет» все три буквы не-словесные, и \b после «т» никогда не совпадает, если дальше идёт пробел, точка, запятая или любая другая кириллица — то есть в реальном тексте почти всегда:
$ node -e "console.log(/нет\b/i.test('нет проблем'), /нет\b/i.test('нет.'), /нет\b/i.test('нет'))"
false false false
Ветка нет\b совпадает только если сразу после «нет» идёт ASCII-буква/цифра (/нет\b/i.test('нетx') → true) — случай, которого в живых документах нет. Проверено на всём каталоге: с веткой и без неё parseFindings даёт одинаковые 987 находок на 999 документах, 12/12 тестов проходят в обоих вариантах — ветка не меняет ни одного результата, то есть мертва не гипотетически, а по факту на всём материале.
Важно: это не регресс и не риск для AC. Будь \b кириллически-осведомлённым (как задумано комментарием), фильтр стал бы агрессивнее и мог задеть настоящие находки, начинающиеся со слова «Нет» как обычного русского слова, а не маркера «не найдено» — собственный тест этого раунда содержит именно такой случай ('Нет golden-сцены для радара (…)', должен остаться находкой) и по счастливой случайности не ловится сломанной веткой. Так что текущее поведение практически безопасно, но комментарий вводит в заблуждение о том, что код делает, а мёртвая альтернатива в регэкспе — балласт.
Серьёзность: Low. Не блокирует, AC не затрагивает, на материале ревью не имеет наблюдаемого эффекта (подтверждено пересчётом всего каталога). Снимается с записью: либо убрать нет\b из регэкспа как недостижимую ветку, либо поправить комментарий, чтобы не заявлять то, чего код не делает. Оставляю на усмотрение следующего касания этого файла — блокировать раунд из-за неё нет оснований.
Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
H1 — docs/reviews/INDEX.md, зафиксированный в материале ревью, устарел на собственном --check (ребейз принёс документы #625, снимок не пересобран) |
commitIfStale() в scripts/reviews-index.mjs (новый экспорт, --commit-if-stale --issue=NN): пересобирает индекс и коммитит его коммитом конвейера, если он разошёлся с каталогом; вызывается в process.yml сразу после успешного git rebase origin/dev, до push, и в scripts/merge-candidate.mjs rebaseOnto() после ребейза кандидата слияния. Гейт свежести перенесён с несуществующего CI-шага на предполётную проверку validate.yml (id: reviews_index, только push в dev, где свежесть держит конвейер) |
scripts/reviews-index.mjs:316-349 (commitIfStale), .github/workflows/process.yml:428 (вызов после rebase), .github/workflows/validate.yml:95-108 (шаг + вердикт предполёта); тесты test/reviews-index.test.mjs (свидетель на проводку в обоих местах, --commit-if-stale на временном git-репо — коммитит только при расхождении, рабочая копия чистая), test/merge-candidate.test.mjs #516 AC1 (реальный сценарий: dev после слияния несёт индекс с обоими документами, коммит индекса — вершина); на материале ревью node scripts/reviews-index.mjs --dir=docs/reviews --check → «свеж» (проверено лично, п. 4 выше), заголовок индекса «Документов: 1000» совпадает с фактическим числом файлов (п. 5) |
| M1 — фолбэк «первая строка тела блока» вырезал буллет-маркер и подставлял хвост перенесённой строки вместо текста находки (11+ документов, включая с ненулевым Medium) | firstParagraph() — тело секции разбивается на абзацы (перенесённые строки склеиваются до пустой строки), берётся первый непустой, не служебный (не начинается с () абзац целиком; маркер буллета и код **M1.** снимаются; мета-фразы «не найдено»/«не обнаружено»/«нет находок»/«отсутству» — не находка. numberedItems тоже забирает перенесённые строки того же пункта |
scripts/reviews-index.mjs:127-155 (firstParagraph), :157-170 (numberedItems с продолжением строк); мутант reviews-index-paragraph-tail (scripts/mutation-registry.mjs) — поймано вручную в этом раунде (п. 6); все 11 документов, названных в r2, перепроверены лично — текст находок теперь начинается с начала предложения (п. 7); полный скан каталога — 987 находок, без искажённых обрывков (п. 8) |
Унаследовано из r1/r2 (без повторного разбора — дельта r3 этого не касается)
- AC3 / PROCESS.md §2.10, правило хранения раундов — записано и принято r1 (
CODE-REVIEW-635-r1.md); в дельте r3 PROCESS.md правится только в части, описывающей индекс/гейт свежести (см.git diff d9bd5a44..HEAD -- PROCESS.md), сам параграф про перенос вlegacy/при стабильном релизе не тронут. docs/LESSONS.md(12 датированных уроков) — не в дельте r3, принято по r1.parseDocName,parseVerdict,parseCounts, старые форматы заголовков находок,parseFiles— не менялись в дельте r3 (diffscripts/reviews-index.mjsэтого раунда — только новыеfirstParagraph,commitIfStale, правкаnumberedItemsпод перенос строк); приняты по r1/r2, включая ручную проверку мутантаreviews-index-counts-first-matchв r2.- Мутанты
reviews-index-skips-self-check,reviews-index-verdict-substring,reviews-index-counts-first-match— не менялись в дельте r3, приняты по r1/r2 (там же поймано вручную). .github/workflows/process.yml, интеграция публикации документа ревью тем же коммитом — не изменена в этой части дельты (изменение делты — добавленный вызов--commit-if-staleдо push, отдельная строка), принято по r1.
Гейты — что прогнал, что нет
| Гейт | Статус | Почему |
|---|---|---|
node --test test/reviews-index.test.mjs |
прогнал, 12/12 | дешёвый, прямое покрытие дельты (H1 и M1) |
node --test test/merge-candidate.test.mjs test/validate-workflow.test.mjs |
прогнал, 38/38 | оба файла в дельте r3 (новые/изменённые тесты #516 AC1, свидетель шестого сигнала) |
node scripts/reviews-index.mjs --dir=docs/reviews --check |
прогнал сам, «свеж» | это и есть проверка закрытия H1; на материале r2 та же команда падала |
Мутант reviews-index-paragraph-tail |
прогнал вручную (патч + тест) | ловится, файл восстановлен |
Скан parseFindings по всем 999 документам каталога |
прогнал сам (дважды: с/без ветки нет\b) |
обнаружил Low-находку; подтвердил отсутствие других обрывков после фикса M1 |
npx tsc --noEmit, npm test (полный), npm run build |
не гонял | зелёный Validate на этом SHA (run упомянут в задании ревью); diff не касается .ts/src/** (проверено: git diff --name-only d9bd5a44..HEAD — пусто по ^src/ и \.ts$) |
node scripts/check-docs.mjs |
не гонял | diff не заходит в src/** |
npm run invariants, pytest tests_backend |
не нужны | геометрия и Python не затронуты |
demo/smoke_*.mjs, smoke-select.mjs |
не гонял | фронтенд-диффа нет |
npm run golden:verify |
не нужен | рендер не затронут |
Вердикт
Зелёный. High: 0, Medium: 0. Оба High/Medium r2 закрыты и перепроверены на этом материале не только тестами, но и прямым воспроизведением (--check свеж, 11 документов из M1 перечитаны, счётчик документов сходится с файловой системой, мутант reviews-index-paragraph-tail ловится). Единственная новая находка — Low (мёртвая ветка регэкспа нет\b, инертна на всём каталоге) — снимается с записью, не блокирует.
Материал раунда
- Ветка:
issue/635-reviews-index, коммитb38bd0c8b5dc— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
090de0dbb1ce4879630530052d644fb071608de1git log --all --format='%H %T' | grep 090de0dbb1ce - Тело issue:
6ae105e700c9b3bc91974f4e25052237ef671f31d5e59ee5a0841cbaa1bb26a6 - Вердикт конвейера:
green· High 0