Один сломанный bash‑скрипт. Семь настоящих багов — от косметических (устаревшие имена файлов в комментариях) до фатальных: сводная таблица, которая всегда показывает нули, что бы на самом деле ни произошло.
Четыре модели чинят этот скрипт вслепую, независимо друг от друга, не зная, что делают остальные три. Потом ещё семь моделей получают на вход все четыре готовых варианта починки и одно задание: собрать из них лучшее.
Забегая вперёд — ровно настолько, чтобы не испортить интригу: на старте модели расходятся куда сильнее, чем можно было бы ожидать. Ни одна модель не нашла всё. И слепые зоны у них не совпадают. Собственно, поэтому эксперимент и имеет смысл ставить, а не рассуждать о нём в теории.
Введение
В каждой задаче, которую мы отдаём языковой модели, тихо зашит вопрос, который почти никогда не задают вслух. Довериться одной модели? Пойти искать лучшую? Запустить несколько параллельно и оставить всё, что каждая из них поймала? Или — ещё на шаг дальше — отдать всю эту кучу частичных ответов очередной модели и попросить свести их воедино?
Каждый ответ по‑своему разумен. Каждый по‑своему неполон.
Смысл этой затеи — поставить эксперимент, а не гадать, чем он кончится. Подопытный один: сломанный bash‑скрипт с семью багами разной тяжести. Исследование идёт в два круга.
Круг первый: одна модель, без подсказок. Четыре модели — Sonnet 5, HY3, Qwen3-Max, DeepSeek‑V4-Flash — чинят скрипт поодиночке, каждая в своём пузыре. Это первая рамка: одна модель чинит ровно то, что заметила сама.
Лучший из четырёх. Когда четыре независимые попытки лежат рядом, следующий вопрос напрашивается сам: если бы пришлось выкатить в прод только один из четырёх скриптов, какой брать? Это разбирается в Таблице 1 — правда, «лучший» здесь окажется синонимом «самый полный», а не «полный». Даже сильнейший из четырёх оставляет живыми настоящие баги.
Круг второй: каждая модель выбирает лучшее из всех четырёх. Именно эта неполнота и делает второй круг осмысленным. Семь моделей — ling-3.0-flash, Mistral‑Medium-3.5, Nemotron-3-Super-120B, Qwen3-Max, DeepSeek‑V4-Flash, Gemini Pro, dots‑studio-3-note — получают одни и те же четыре починенных скрипта и одно и то же задание: выбрать по каждому багу тот фикс, который действительно лучший, из какого бы источника он ни пришёл, и собрать выбранное в один скрипт.
Кто на самом деле собрал лучше. Все семь уверены, что взяли лучшие куски. Ровно это второй круг и проверяет: та ли модель, чья сборка объективно сильнее, окажется и той, чья сборка выглядит сильнее? Или кто‑то другой из семерых, имея на входе ровно те же четыре скрипта, соберёт лучше очевидного фаворита? Это решается в Таблице 2, а не здесь.
Но главная находка лежит не в шапке таблиц, а под обеими. Скрипт, который просто работает, ещё не доказательство, что модель нашла всё сломанное. Скрипт, который вдобавок объясняет свою родословную — какой фикс откуда пришёл и почему выжил, — это доказательство другого, куда более редкого сорта. Разница между компетентностью и честным обращением с чужой работой — вот что эти две таблицы на самом деле вскрывают. К этому и придём в финале.
Что за скрипт
run-code.sh — это bash‑обвязка: она дёргает codegen_test.py по списку LLM, собирает по JSON‑файлу результатов на каждую модель и печатает сводную таблицу‑лидерборд. В исходнике было семь настоящих багов — от косметических до фатальных.
Чтобы понять, кто что реально починил, а не кто что заявил, каждый скрипт был:
построчно продиффен с оригиналом;
проверен синтаксически через
bash -n;запущен с заглушкой
codegen_test.py, которая выдаёт реалистичную смесь статусов (PASS,TEST_FAIL,BAD_JSON,SYNTAX_ERROR,RATE_LIMITED) — чтобы вывод сводной таблицы можно было именно осмотреть, а не вычитать глазами по коду;запущен на настоящем кривом списке моделей — чтобы проверить, действительно ли заведомо битые данные всё ещё сыплют ошибками или уже нет.
На последнем шаге в первой редакции статьи фигурировал вымышленный пресет OPENROUTER_FREE. Ниже он заменён на реальный OpenRouter‑пресет скрипта, заполненный ID моделей, которые действительно живы на бесплатном тарифе OpenRouter по состоянию на август 2026 — meta-llama/llama-3.3-70b-instruct:free, qwen/qwen3-coder:free, openai/gpt-oss-120b:free, openai/gpt-oss-20b:free и nvidia/nemotron-3-ultra-550b-a55b:free — и сверенными напрямую с блогом OpenRouter и его живым каталогом моделей. Паттерн бага от этого не меняется ни на йоту; просто теперь иллюстрация опирается на реального, проверяемого провайдера, и читатель может сходить и убедиться сам, а не верить на слово про выдуманный шлюз.
Дальше — как выглядел каждый баг, почему он важен, и уже потом две таблицы.
Баг № 1 — Битые строки данных в OpenRouter‑пресете
В исходном массиве среди рабочих строк затесались три сломанные:
OPENROUTER_FREE=( "meta-llama/llama-3.3-70b-instruct:free" # <- суффикса "|RPM|RPD" нет вообще "qwen/qwen3-coder:free|0|0" "openai/gpt-oss-120b:free|0|0" "openai/gpt-oss-20b:free|0|" # <- поле RPD пустое "nvidia/nemotron-3-ultra-550b-a55b:free|0|" # <- поле RPD пустое )
Ниже по коду цикл разбирает каждую запись:
IFS='|' read -r MODEL RPM RPD <<< "$ENTRY" ... if [ "$RPD" -gt 0 ] && ... # для битых строк RPD здесь — пустая строка
Сам скрипт от [ "" -gt 0 ] не падает (set -e тут нет), но bash на каждую битую строку исправно плюёт в stderr bash: [: : integer expression expected, а на экране вместо 0 RPM / 0 RPD зияет пустота. Не фатально — но это ровно тот сорт шума, из‑за которого люди перестают доверять выводу инструмента. Тем более неловко, что настоящий рейт‑лимит OpenRouter вообще не помодельный: это плоские 20 запросов в минуту и 50 в сутки (или 1000 в сутки при пополнении на $10) на весь аккаунт целиком. Именно поэтому каждая живая запись в этом списке по‑хорошему должна читаться как |0|0, а темп пусть задаёт SLEEP_OVERRIDE.
Как проверялось: каждый скрипт прогонялся на этом OpenRouter‑списке, а stderr грепался на integer expression expected.
Баг № 2 — Нет защитного разбора RPM/RPD
Это обобщённая версия бага № 1: цикл слепо верит, что в каждом поле |RPM|RPD лежит чистое неотрицательное целое. Никакого запасного варианта на случай опечатки, пропущенного поля или правки списка руками — а реестр бесплатных моделей OpenRouter правят руками постоянно, потому что состав ротируется: провайдеры добавляют и убирают модели.
Однострочный guard закрывает и конкретные опечатки выше, и все будущие, которые сопровождающий внесёт в следующем месяце:
case "$RPM" in ''|*[!0-9]*) RPM=0 ;; esac case "$RPD" in ''|*[!0-9]*) RPD=0 ;; esac
или проще:
RPM="${RPM:-0}" RPD="${RPD:-0}"
(Нюанс: ${VAR:-0} срабатывает и на пустую строку, а не только на неустановленную переменную — а именно пустую строку read и выдаёт для пропущенного поля. То есть этот простой однострочник нейтрализует заодно и баг № 1, вообще не трогая данные.)
Баг № 3 — Нет предполётной проверки, что codegen_test.py существует
Скрипт задаёт дефолт:
SCRIPT="${SCRIPT:-codegen_test.py}"
…но ни разу не проверяет, что файл на месте, прежде чем прокрутить цикл по 6–30 моделям и на каждой уйти в python3 "$SCRIPT" .... Если файла нет, вы получаете загадочную ошибку уже на первой итерации:
python3: can't open file 'codegen_test.py': [Errno 2] No such file or directory
…и так по разу на каждую модель, без единого намёка, что с этим делать. Двухстрочный guard перед циклом превращает эту простыню в одно внятное сообщение, после которого сразу ясно, куда идти:
if [ ! -f "$SCRIPT" ]; then echo "Missing script file: $SCRIPT" echo " Put codegen_test.py next to run-code.sh, or set SCRIPT=/path/to/codegen_test.py" exit 1 fi
Баг № 4 — Сводная таблица читает несуществующие ключи статусов (критический)
Вот этот ломает уже сам смысл инструмента. codegen_test.py пишет реальные значения статусов: PASS, TEST_FAIL, BAD_JSON, SYNTAX_ERROR, RATE_LIMITED. А исходный сводный блок делает так:
c = collections.Counter(r["status"] for r in recs) hall_rate = c["HALLUCINATION"] / n * 100 rows.append((hall_rate, -c["OK"], model, c, n, avg, min(times), max(times))) ... print(f"{model:<34} {c['OK']:>3} {c['HALLUCINATION']:>5} {other:>5} ...")
Counter на отсутствующий ключ возвращает 0 вместо ошибки. Поэтому код никогда не падает — он молча рисует бесполезную таблицу. Проверено запуском:
модель OK HALL иное галл.% min avg max ------------------------------------------------------------------------------------------ test-model-a 0 0 3 0% 2.2s 2.8s 3.1s test-model-b 0 0 3 0% 2.9s 3.4s 3.9s test-model-c 0 0 3 0% 1.1s 1.2s 1.3s
У всех моделей OK=0, HALL=0, все 100% свалены в «иное» — независимо от того, прошла модель все тесты или провалила все. Это и есть весь смысл скрипта (сравнивать модели по качеству генерации кода), и он был сломан начисто.
Правильный фикс обязан (а) знать настоящий словарь статусов и (б) разложить его по осмысленным корзинам:
OK_STATUS = {"PASS", "OK"} HALL_STATUS = {"TEST_FAIL", "HALLUCINATION", "WRONG"} BROKEN_STATUS = {"BAD_JSON", "SYNTAX_ERROR", "MALFORMED", "PARSE_ERROR", "EXEC_ERROR"} INFRA_STATUS = {"RATE_LIMITED", "TRUNCATED", "TIMEOUT", "ERROR", "HTTP_ERROR", "CONNECTION_ERROR", "API_ERROR"}
Баг № 5 — Не различаются «всё рухнуло» и «часть попыток не прошла»
if python3 "$SCRIPT" ... ; then : else echo ">>> ERROR on model: $MODEL" FAILED+=("$MODEL") fi
codegen_test.py выходит с ненулевым кодом, если провалилась хотя бы одна попытка из N — а это совершенно штатная ситуация (модель, взявшая 8 из 10, показала хороший результат, а не упала). Исходный код не отличает «эта модель взяла 8/10» от «у этой модели был неверный API‑ключ и не отработало вообще ничего».
Проверено на практике: на смеси pass/fail‑статусов под «ошибки» попадала каждая модель — включая те, что в основном проходили. Для бенчмарка это прямая дезинформация: читатель, скользнув взглядом по списку «errors», решит, что эти модели сломаны.
Фикс должен смотреть, был ли вообще записан файл результатов, прежде чем объявлять жёсткий провал:
if [ "$rc" -ne 0 ] && [ ! -s "$JSON_OUT" ]; then FAILED+=("$MODEL") # не записалось ничего — настоящее падение elif [ "$rc" -ne 0 ]; then DEGRADED+=("$MODEL") # результаты есть, просто часть попыток не прошла fi
Баг № 6 — Хрупкий парсер сводки
with open(path) as f: recs = json.load(f) ... model = recs[0]["model"]
Если хоть один JSON‑файл обрезан, пуст или лишён ключа (скажем, прогон убили посреди записи), здесь вылетает необработанное исключение и убивает сводку целиком — по всем моделям, а не только по битой. Фикс: обернуть рискованные места в try/except и использовать .get() с запасным значением вместо голого индексирования.
Баг № 7 — Устаревшая документация
Скрипт в какой‑то момент явно переименовали из run.sh в run-code.sh, но ~11 строк комментариев и одна подсказка в сообщении об ошибке так и остались нетронутыми:
# ./run.sh # free-tier Gemini, 3 attempts ... echo " HOST=https://your-host/v1 PRESET=openrouter ./run.sh"
Скопируйте это в терминал — и получите «no such file». Мелочь, но именно из таких мелочей складывается ощущение заброшенного скрипта.
Таблица 1 — Модели‑«ремонтники»
Баг | Sonnet 5 | HY3 | Qwen3-Max | DeepSeek‑V4-Flash |
|---|---|---|---|---|
№ 1 Битые строки OpenRouter‑пресета | ✅ Переписала три битые строки напрямую | ❌ Оставила как было — подтверждено запуском: всё так же сыплет | ✅ Починила через общий | ✅ Нейтрализовала через |
№ 2 Нет guard'а на RPM/RPD | — (снято, закрыто пунктом № 1) | ❌ Пропущено | ✅ Лучший вариант — явный guard на оба поля | ✅ Есть |
№ 3 Нет предполётной проверки | ❌ Пропущено | ✅ Единственная, кто добавила | ❌ Пропущено | ❌ Пропущено |
№ 4 Неверные ключи статусов в сводке | ❌ Пропущено начисто — я запустил и получил | ✅ Лучший фикс — полная разбивка OK/HALL/BROK/INFRA с внятными комментариями | ✅ Верно опознала | ⚠️ Частично — |
№ 5 Нет разделения FAILED / DEGRADED | ❌ Пропущено — при запуске каждая модель, даже с 2/3 пройденных, уезжала в «ERROR» | ✅ Починено через | ✅ Строже всех — реально считает записи в JSON и сверяет с | ❌ Пропущено — тот же провал, что и у Sonnet 5, подтверждено тестом |
№ 6 Хрупкий парсер | ✅ Обернула весь блок в | ⚠️ Частично — | ⚠️ Частично — тот же пробел, что у HY3 | ⚠️ Частично — тот же пробел |
№ 7 Устаревшие доки | ⚠️ Частично — починила блок в шапке (10 строк), пропустила подсказку в ошибке на строке 78 | ❌ Пропущено начисто | ❌ Пропущено начисто | ❌ Пропущено начисто |
Как это читать. Всех багов не нашёл никто.
Sonnet 5 — лучшая гигиена кода (доки, защитный try/except), но она прошла мимо обоих багов, от которых зависит, значит ли вывод инструмента хоть что‑нибудь (№ 4 и № 5). И это не придирка: я проверил — её сводная таблица при запуске действительно бесполезна.
HY3 построила самую продуманную логику сводки, но ни разу не перепроверила её на тех данных, которые эта сводка читает, — и потому оставила живым единственный баг в самих данных (№ 1).
Qwen3-Max — единственная, кто закрыла 4 бага из 7 начисто и без регрессий, в том числе догадалась, что общий защитный guard (№ 2) — фикс лучше, чем заплатка на три конкретные опечатки (№ 1).
Победитель: Qwen3-Max, с HY3 вплотную за ней.
И тут же в стоге сена обнаруживается первая иголка: предполётную проверку $SCRIPT (№ 3) нашла ровно одна модель из четырёх — и это не победитель. Запомните её, в финале она нам пригодится.
Круг второй, или «двое из ларца, одинаковых с лица»
Помните мультик? Вовка получает двух работников из ларца, готовых сделать за него вообще всё.
— И всё за нас делать будете?! — Ага!
Заканчивается это, как известно, тем, что двое из ларца едят за Вовку тоже. Формально — задание выполнено. По существу — сделано не то.
Ровно этот сюжет и разыгрывается во втором круге. Семь моделей получают четыре чужих готовых решения и обещают собрать из них лучшее. Одинаковые с лица: все семь выдают синтаксически валидный, работающий, уверенно выглядящий скрипт. Разница — не в том, кто справился, а в том, что каждая из них тихо съела по дороге.
Каждую из семи прогнали через bash -n, через тот же стресс‑тест с поддельными статусами и заново — на настоящих битых данных OpenRouter‑пресета.
Таблица 2 — Модели‑«сборщики»
Критерий | ling-3.0-flash | Mistral‑Medium-3.5 | Nemotron-3-Super-120B | Qwen3-Max | DeepSeek‑V4-Flash | Gemini Pro | dots‑studio-3-note |
|---|---|---|---|---|---|---|---|
№ 4 статусные корзины | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
№ 5 FAILED / DEGRADED | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
№ 1/№ 2 пресет + guard | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
№ 3 проверка | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
№ 7 чистка доков | ❌ 12 ссылок | ❌ 12 | ❌ 12 | ✅ 0 | ✅ 0 | ✅ 0 | ❌ 12 |
№ 6 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
Атрибуция фиксов | ❌ | ❌ | ❌ | ✅ таблица «фикс → источник → строка» | ⚠️ 2 из 4 | ❌ | ❌ |
Стресс‑тест пройден | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Место | 5-е | 4-е (делят) | 6-е | 1-е | 3-е | 2-е | 4-е (делят) |
Как это читать. Все семь сборщиков выдали функционально корректный скрипт — подтверждено запуском каждого, а не чтением кода. Это хорошая новость: базовые баги (№ 1–№ 5) на этом этапе — уже пройденная территория, ни одна из семи по ним не откатилась назад.
Различает их дисциплина по отношению ко всему исходному материалу, а не только к двум «очевидно важным» фиксам. Трое — ling-3.0-flash, Mistral‑Medium-3.5 и Nemotron-3-Super-120B — взяли работу HY3 и Qwen, но молча выбросили чистку документации от Sonnet 5: у всех троих остались те же 12 ссылок на ./run.sh. Туда же попадает dots‑studio-3-note: функционально крепко, тот же пробел по докам, никакой атрибуции. Ни одна из этих четырёх не объясняет, почему оставила именно то, что оставила.
У семи нянек дитя без глазу — правда, здесь недосмотрели четыре няньки из семи, а три донесли работу предшественников до финала целой.
Qwen3-Max — единственная, кто выдала настоящий аудиторский след: таблицу, где каждому фиксу сопоставлена модель‑автор и номер строки, где он теперь живёт. Вот это и есть подлинный результат задачи «собери лучшее отовсюду»: не просто рабочий код, а прослеживаемость — почему каждый кусок пережил слияние. Вплотную за ней — Gemini Pro: технически она столь же полна (кроме Qwen и DeepSeek, только она починила ещё и доки), просто без атрибуции.
Потому что слияние без атрибуции — это испорченный телефон, в котором последнее звено звучит увереннее всех предыдущих. И проверить его можно только одним способом: вернувшись к первоисточникам, которые оно не назвало.
Ключевые выводы
Ни одна модель, какой бы сильной она ни была, не ловит все баги — у каждой своя слепая зона, и дважды одинаково эти зоны не совпадают.
Сила в одной области не означает, что модель хотя бы попробует соседнюю: та, что построила лучшую логику сводки, ни разу не проверила данные, которые эта сводка читает. И наоборот.
Модель, занявшая первое место в Таблице 1, всё равно пропустила баг, который поймала модель рангом ниже. Ранг измеряет, сколько багов модель нашла, а не какие именно.
Запуск нескольких моделей параллельно и сбор всех их находок даёт больше, чем любая одна модель, — но только если тот, кто сводит результаты, сохраняет фикс миноритарного участника, а не выбрасывает его втихую.
Скрипт, отработавший без ошибок, — не доказательство, что модель нашла всё сломанное: несколько худших багов вылезли только тогда, когда скрипты реально запустили на реальных данных.
Атрибуция в задаче «собери лучшее» — не любезность, а единственный способ для тех, кто дальше по цепочке, убедиться, что единственная удачная находка слабого источника пережила слияние.
Рекомендации
Для изолированной задачи «почини баг»: у Qwen3-Max лучшее соотношение «диагноз → фикс», и она по умолчанию тяготеет к защитному коду (guard вместо заплатки по данным) даже там, где я это специально не проверял.
Для задачи «слей и согласуй несколько выходов»: снова Qwen3-Max, с Gemini Pro как сильной альтернативой, если объяснения вам не нужны — нужен только рабочий код.
Не берите ling-3.0-flash, Mistral‑Medium-3.5, Nemotron-3-Super-120B и dots‑studio-3-note именно на неконтролируемое слияние: все четыре молча выбросили фикс миноритарного участника, ничем это не отметив. Это ровно тот режим отказа, которого не хочется от инструмента, чья работа — «посмотреть на всё и собрать лучшее».
Финал: а вы, друзья, как ни садитесь…
Отойдём на шаг от кода и представим рукопись. Один аккуратный автор всегда выдаст нечто связное: скрипт Sonnet 5 читается чисто, комментарии опрятны, намерения видны построчно. Но связность — не то же самое, что правильность, и слепое пятно одиночного редактора остаётся слепым, сколько бы раз он ни перечитал собственный черновик. Именно это и случилось со сводной таблицей, которую никто не догадался запустить.
Комитет редакторов ловит больше — четвёрка ремонтников это и доказала: между ними нашлись все семь багов, каждый — кем‑то.
Но присмотритесь, кто что нашёл, и история становится острее, чем «комитет побеждает». Qwen3-Max починила четыре бага из семи начисто, возглавила обе таблицы и по любой разумной мерке была сильнейшей моделью теста. И всё равно ей не пришло в голову проверить, существует ли codegen_test.py, прежде чем крутить по нему цикл. HY3 — стоящая в Таблице 1 позади неё — догадалась. И была единственной из четырёх, кто догадался. Первое место не сделало Qwen3-Max моделью, поймавшей всё; оно сделало её моделью, поймавшей больше всех. Каждая модель здесь исправила что‑то настоящее. Ни одна не исправила всё. А баг, который пропустил итоговый победитель, лежал невостребованным в том самом скрипте, который победитель обошёл в рейтинге.
«А вы, друзья, как ни садитесь, всё в музыканты не годитесь.»
Крылов, как обычно, точен — с одной поправкой на наш случай. Беда квартета была не в том, что музыканты плохи, а в том, что они пересаживались, вместо того чтобы кто‑то послушал со стороны и сказал, что именно звучит фальшиво. Пересадка моделей — «возьмём вместо этой вон ту, она в рейтинге выше» — ровно та же смена мест. Пока никто не слушает результат снаружи и не сверяет его с партитурой, перестановка ничего не чинит.
Комитет хорош ровно настолько, насколько хорош тот, кто держит перо на финальном проходе: решает, какую правку оставить, и — что критично — готов написать об этом на полях. Шесть из семи сборщиков оставили правильные правки и промолчали о том, откуда они. Один оставил правильные правки и показал работу. Вот и вся разница между моделью, которая сливает ответы, и моделью, которой можно доверить слить их без присмотра. И именно потому, что ни одна модель, какой бы сильной она ни была, не самодостаточна, показывать источники — не любезность. Это единственный способ для того, кто идёт следом, убедиться, что единственная удачная находка слабой модели не улетела в корзину вместе с её худшими ошибками.
Заключение: разные модели, верификация, сплошная проверка
Три практических вывода, которые я забираю из эксперимента.
Нужны разные модели, а не одна лучшая. Слепые зоны не совпадают — и это не недостаток отдельных моделей, а свойство ландшафта. Единственная модель, поймавшая проверку существования файла, в общем зачёте стояла второй. Ставка на одного, даже самого сильного, исполнителя гарантированно оставляет часть багов живыми. Разнообразие здесь — не про вежливость к аутсайдерам, а про покрытие.
Нужна верификация против галлюцинаций. Модель, написавшая «исправлено», не обязательно исправила. Хуже: Counter не падает на несуществующем ключе, [ "" -gt 0 ] не роняет bash без set -e, а отчёт модели о собственной работе всегда звучит уверенно. Всё это — тихие отказы, которые чтением кода не ловятся. Ловятся только запуском: diff с оригиналом, bash -n, прогон на стенде с реалистичными статусами, прогон на заведомо кривых данных. Отчёт модели — гипотеза, а не результат.
Нужна сплошная проверка всех моделей, а не только победителя. Соблазн понятен: определить лидера и дальше смотреть только на него. Но ранг говорит, сколько багов модель нашла, и молчит о том, какие. Единственный экземпляр нужного фикса вполне может лежать у аутсайдера — и если проверять только фаворитов, эта иголка так и останется в стоге сена.
Ту самую иголку из первого круга — предполётную проверку $SCRIPT — нашла ровно одна модель из четырёх, и не победитель. Ей повезло: во втором круге её подобрали все семь сборщиков. Чистке документации от Sonnet 5 повезло меньше — четыре сборщика из семи выбросили её молча, и ни один об этом не обмолвился. Разница между двумя судьбами — чистая случайность, а не заслуга процесса. Именно поэтому смотреть надо всё: каждый вклад, каждое слияние, каждую строку, которая тихо не доехала до финального файла. И именно поэтому сборщик, приложивший таблицу «фикс → источник → строка», ценнее шести, отдавших такой же рабочий скрипт молча: он превращает поиск иголки в проверку по списку.

