Как мигрировать 1 млн строк кода с одного языка на другой за 2 недели
- четверг, 6 августа 2026 г. в 00:00:06
До недавнего времени миграции кода, при которых рабочую кодовую базу переносят на новый язык, занимали несколько лет.
За последний месяц отдельные разработчики Anthropic с помощью Claude Fable 5, Claude Opus 4.8 и dynamic workflows перенесли десять проектов объемом от десятков до сотен тысяч строк кода. В этой статье мы разберем два примера и лучшие практики, сформировавшиеся в ходе этих проектов.
Джарред Самнер, сооснователь Bun и технический специалист Anthropic, использовал Claude Code, чтобы перенести Bun с Zig на Rust. Менее чем за две недели было создано миллион строк кода. До слияния ветки 100% существующих тестов Bun проходили в CI. После слияния обнаружилось 19 регрессий, все они уже исправлены. В июне версия на Rust вошла в состав Claude Code.
Майк Кригер, один из руководителей Anthropic Labs, за выходные перенес кодовую базу на Python в 165 000 строк TypeScript. В процессе работали сотни агентов, использовались восемь контрольных этапов, три раунда состязательного ревью и финальная проверка, которая сравнивала вывод каждой команды с результатом оригинальной версии на Python.
Новые возможности Claude Code меняют экономику давно откладываемых проектов. Ниже описан процесс из шести шагов, который мы теперь используем, он основан на выводах из этих миграций.
Главный вывод: исправлять нужно не код, а процесс, то есть цикл, который этот код создал.
При такой миграции ИИ-агенты переносят рабочую кодовую базу на новый язык или фреймворк. Вместо ручного перевода файлов инженеры формулируют правила миграции и создают циклы проверки. Затем агенты переводят, компилируют и тестируют код, пока поведение новой версии не совпадет с оригиналом. Проект, который раньше занимал годы, теперь можно завершить за несколько недель.
Прежде чем переходить к методике, стоит обсудить условия и причины миграции, потому что наши представления о таких проектах изменились.
Команды начинают миграцию, когда между запуском продукта и его нынешним состоянием меняется технологический ландшафт. Известный компромисс может превратиться в ограничение, может появиться более удачный подход, а экосистема исходной технологии может начать сокращаться.
Например, Джарред изначально выбрал Zig за производительность уровня C и радикальную простоту. Такой язык идеально подходил основателю одиночке, который, по его словам, писал Bun за год в тесной квартире в Окленде еще до появления больших языковых моделей. У этой простоты были известные компромиссы, о которых Джарред рассказал отдельно.
Перенесемся в 2026 год. CLI Bun скачивают более десяти миллионов раз в месяц, а сам инструмент активно используется внутри Claude Code.
Еще квартал назад этих компромиссов было бы недостаточно, чтобы заморозить дорожную карту и направить ресурсы на проект длиной в несколько кварталов. Смена языка может сделать систему компактнее, быстрее и безопаснее, но никто не хочет платить такую цену.
Инженерам приходилось учитывать и карьерный риск, связанный с подобными мегапроектами. Можно кварталами или годами поддерживать две параллельные кодовые базы, а затем получить лишь 90% функционального паритета и еще более серьезную проблему, чем в начале.
Теперь худший сценарий выглядит иначе: удалить ветку и попробовать снова.
У миграции все равно должно быть убедительное экономическое обоснование. Перенос миллиона строк больше не требует от трех до четырех миллионов долларов и четырех лет работы инженерной команды, но затраты по-прежнему составляют десятки или сотни тысяч долларов, иногда больше. Например, миграция Bun потребовала 5,9 миллиарда некешированных входных токенов и 690 миллионов выходных токенов. По тарифам API это около 165 000 долларов. Основная часть переноса Майка потребовала 27 миллионов токенов.

При этом причина для миграции больше не обязана быть экзистенциальной. Ее уже может оправдать год исправлений утечек памяти в журнале изменений или одно хронически узкое место.
Именно компиляция подтолкнула Майка к его проекту. Внутренний инструмент его команды поставляется пользователям в виде одного исполняемого файла. Сборка такого файла инструментами Python занимала около восьми минут на каждой платформе, в сумме матрица сборки задерживала каждый релиз на 30 минут. После переноса компиляция занимает около двух секунд, исполняемый файл запускается в шесть раз быстрее, а команда смогла отказаться от отдельного конвейера развертывания.
Если статья понравится — приглашаю в канал AI for Devs. Каждый день публикую похожие материалы: модели, агенты, практические кейсы и новости из мира AI.
Claude Fable 5 является нашей самой функциональной общедоступной моделью. Fable и Opus 4.8 особенно хорошо умеют делегировать, направлять и проверять параллельные потоки работ с субагентами, одновременно находя несколько путей к заданной цели.
Крупные миграции кода особенно хорошо подходят для таких моделей по нескольким причинам:
Работу можно распараллелить. Проект разбивается на тысячи независимых единиц, например файлы и отдельные модули. Агенты могут работать одновременно, не ожидая друг друга.
Контекст ясен и полон. Старый код служит отличной спецификацией для модели. Он же становится главным источником при создании руководства для агентов "переводчиков".
В процесс встроен арбитр. Во многих крупных кодовых базах есть набор тестов, который агенты могут использовать для проверки своей работы. Агенты показывают лучшие результаты при объективной верификации, потому что модель может сутками сверяться с эталоном без участия человека в оценке качества.
Очередь задач формируется сама. Ошибка компилятора или теста автоматически становится следующей задачей для агента.
Процесс обеспечивает единообразие и работу с пограничными случаями. Отклонениям негде спрятаться, проверяющие указывают правило для каждого замечания, поэтому нарушение превращается в элемент очереди, а не остается незаметным расхождением. Когда агент встречает пограничный случай, исправление становится правилом для всех последующих агентов.
Как будет видно дальше, и Майк, и Джарред применяли Fable на ключевых этапах миграции, особенно в роли консультанта. Такой подход сочетал разные классы моделей и помогал оптимизировать расход токенов.
Описанный ниже процесс обобщен, чтобы его можно было применять к разным языкам и сценариям. Дополнительные подробности есть в блоге Джарреда. Также доступен стартовый набор для миграции. Важно: стартовый набор представляет собой обобщенный шаблон процесса из этой статьи, конкретные миграции выполнялись не на его основе.
До начала миграции нужен надежный арбитр, иначе у проекта не будет ни условия завершения, ни меры успеха.
Арбитр должен оценивать исходный и целевой код на равных условиях. Тесты на исходном языке часто зависят от внутренних функций, которых в новой версии уже не будет.
Арбитр создается так:
Классифицируйте существующие тесты. Попросите Claude определить, какие тесты можно выразить через внешние вызовы, а какие зависят от внутренних деталей, которые не перенесутся.
Перепишите тесты так, чтобы их можно было переносить. Преобразуйте проверки внешнего поведения в утверждения, которые выполняются и для оригинала, и для новой версии. С помощью состязательных агентов убедитесь, что переписанные тесты не ослабили исходные требования.
Проверьте арбитра. Сначала запустите его на исходном коде и убедитесь, что все тесты проходят. Затем запустите на намеренно сломанном коде и убедитесь, что тесты падают. Арбитр, который не замечает поломку, арбитром не является.
У Джарреда был большой набор тестов на третьем языке, TypeScript, но для большинства проектов это редкость. При переносе с Python на TypeScript Майк создал стенд проверки паритета из семи реальных сценариев и считал ошибкой любое изменение поведения.
Перед разбором этапов стоит посмотреть на схему ниже. В основном она соответствует методике Джарреда, с ревью и контрольными точками на каждом этапе. Майк использовал похожую общую структуру и те же циклы, но прогонял миграцию целиком, корректировал правила и процесс по результатам, а затем запускал все снова. Каждый раз он отбрасывал полученный код, пока третий прогон не дал нужный результат.


На этом этапе закладывается фундамент миграции. Мы составляем реестр мест, где код придется не просто перевести, а переработать, создаем правила перевода и строим карту зависимостей, которая задает порядок реализации параллельных потоков работ.
Порядок важен: свод правил должен появиться раньше реестра расхождений. В реестр попадает то, что не покрывается стандартными решениями из правил, после чего оба документа проверяются вместе в рамках общего аудита.
Точная структура свода правил зависит от ключевых архитектурных решений, которые нужно принять в начале. Главное из них: сохранит ли новый код прежнюю структуру или будет полностью перепроектирован.
В первом случае, как у Джарреда, свод правил в основном состоит из таблиц соответствий типов и идиом двух языков, а для сложных компонентов отсылает к реестру расхождений. Во втором случае, как у Майка, это проектный документ.
Джарред создавал свод правил в диалоге с Claude, формулируя политику для каждой неоднозначной области. Кроме того, он использовал восемь субагентов, каждый из которых проверял одну из восьми категорий распространенных ошибок, выделенных на основе опыта Джарреда.
Чтобы эффективно разделить параллельную миграцию на потоки работ, нужно понимать зависимости между файлами. Тогда вы знаете, какие файлы переносить первыми и какие объединять в одну партию. В некоторых языках и кодовых базах есть явные манифесты, которые упрощают задачу. Но в старых проектах и популярных языках, таких как C, C++ и Python, зависимости часто приходится обнаруживать и картировать отдельно.
Claude Code может запустить агентов, которые создадут и выполнят детерминированный скрипт для построения карты. Промпт из набора для миграции использует рабочий процесс с циклом проверки и исправления. Важно: стартовый набор представляет собой обобщенный шаблон процесса из этой статьи, конкретные миграции выполнялись не на его основе.
Новый язык предъявляет требования, которых не было в старом. При переносе с Zig на Rust таким различием стало ручное управление памятью, C и C++ в этом отношении работают так же, как Zig.
Например:
Zig
fn readConfig(allocator: std.mem.Allocator) ![]u8 { const buf = try allocator.alloc(u8, 1024); // ...заполнение buf... return buf; // вызывающая сторона должна освободить память, но об этом говорит только комментарий } // Код вызывающей стороны, забывший `defer allocator.free(buf)`, все равно компилируется. // Утечка проявится только во время выполнения.
Rust
fn read_config() -> Vec<u8> { let buf = vec![0u8; 1024]; // ...заполнение buf... buf // владение переходит вызывающей стороне, память освобождается автоматически } // Использовать значение после передачи владения или освободить дважды не позволит компилятор. // Забыть об освобождении нельзя, отдельного вызова free нет, drop выполняется автоматически.
При переносе с Python на TypeScript главным расхождением стали интерфейсы и контракты. Python не требует заранее объявлять структуру объекта, который принимает или возвращает функция, а TypeScript требует.
Например:
Python
def register(handler): handler.setup() return handler.run({"retries": 3}) # Здесь подойдет любой объект с методами .setup() и .run(). # Чтобы выяснить, какие объекты передаются на самом деле, придется прочитать всю кодовую базу.
TypeScript
interface RunResult { ok: boolean; } interface Handler { setup(): void; run(opts: { retries: number }): Promise<RunResult>; } function register(handler: Handler): Promise<RunResult> { handler.setup(); return handler.run({ retries: 3 }); } // До успешной компиляции контракт придется зафиксировать явно.
И Джарред, и Майк создали файлы с реестрами расхождений, в которых зафиксировали неявные знания. Джарред составил реестр заранее, так мы рекомендуем поступать и здесь. Майк сначала перевел код, а затем сформировал реестр по результатам аудита. Возможно, в вашем проекте понадобятся оба подхода.
Посмотрите пример промпта Claude Code для создания реестра расхождений.

На этом шаге выполняется мини-миграция, которая служит обкаткой перед большим проектом.
Джарред поручил одному агенту перевести три файла по своду правил, второму перевести три файла в стиле опытного Rust-разработчика, а третьему сравнить результаты и создать новые правила перевода. Уже на этом этапе обнаружились две критические проблемы. Если бы миграцию сразу распараллелили на все 1 448 файлов, эти проблемы повторились бы множество раз.
Примерный промпт для такого этапа есть в стартовом наборе.
Такой стресс-тест работает только для миграций с сохранением структуры, когда два перевода одного файла можно сравнивать построчно. Если свод правил описывает перепроектирование, как у Майка, эквивалентная проверка выглядит иначе: состязательные проверяющие напрямую атакуют проектный документ, затем его проверяют с помощью одноразового сквозного прогона.
В любом случае удалите все переведенные файлы. Цель этапа состоит в улучшении правил, а не в постепенном продвижении миграции.

На оставшихся шагах используется одна и та же архитектура многоагентного цикла: реализовать, проверить, исправить.
Работу исполнителей можно передать небольшим моделям, а проверяющих оставить на крупных. Например, во время основной миграции Майк распараллелил работу между двенадцатью субагентами Claude Sonnet.
Очередь должна формироваться механически. Пакетный скрипт проверяет наличие переведенного файла на диске и по этому признаку определяет, завершена ли задача. Затем он разбивает оставшиеся файлы на партии для агентов исполнителей. Поскольку очередь каждый раз заново строится по состоянию диска, миграцию изначально можно возобновить с места остановки.
На этом этапе агенты иногда слишком осторожничают с объемом работы. Исправить это можно прямой и настойчивой инструкцией в промпте, дополненной пояснением, что ошибки на следующем шаге обнаружит компилятор.
Все, что переводчик не может уверенно обработать, помечается как // TODO(port): <причина> и переносится на шаг 4. Начиная с этого момента, списки задач формируются сами. Компилятор перечисляет ошибки, смоук тесты находят падения, набор тестов сообщает о сбоях.
Два состязательных проверяющих оценивают работу исполнителей в независимых контекстах. Если их выводы расходятся, решение принимает третий агент. Когда проверяющий снова и снова находит одну и ту же ошибку в разных файлах, исправлять каждый файл отдельно не нужно. Добавьте одно предложение в свод правил и заново сгенерируйте затронутую партию. На этом шаге свод правил продолжает расти, а код никогда не исправляется вручную в обход правил.
Здесь важно решить, где в цикле находится компилятор. Майк запускал компилятор TypeScript внутри каждого цикла, потому что проверка одной единицы занимает секунды. Джарред полностью запретил компиляцию в цикле и перенес ее на следующий шаг, потому что Cargo работает несколько минут.
К этому моменту основная тяжелая работа уже выполнена, поэтому промпты становятся короче.

Эти три шага используют одну и ту же архитектуру цикла и требуют все меньше человеческих решений, поэтому мы рассматриваем их вместе.
В зависимости от языка и масштаба миграции шаг 4 нередко сливается с шагом 3.
Если компиляция долгая или сложная, агенты могут вообще не запускать ее самостоятельно. Джарред использовал скрипт оркестратор, который один раз вызывал компилятор для всей рабочей области. Затем агенты исправители параллельно разбирали список ошибок, а состязательные агенты проверяли изменения. После этого сборка запускалась снова, цикл повторялся.
Просмотр списка ошибок помогает находить системные проблемы, требующие изменения процесса. Например, Джарред столкнулся с тысячами ошибок модулей Rust. Они проявились после исправления циклических импортов, которые допускала ленивая компиляция Zig. Он скорректировал цикл, добавив логику классификации зависимостей: какую удалить, какую переместить, а для какой перестроить границу модулей.
На шаге 5 тоже есть механический источник истины, похожий на список ошибок компилятора: падения смоук тестов. Здесь исправление цикла снова заключалось в группировке проблем по категориям. В данном случае причины объединяли по первоисточнику, затем категории изучали состязательные субагенты.
Шаг 6 завершает нашу историю: мы сравниваем поведение программ в двух кодовых базах.
Файлы уже переведены, скомпилированы и проверены смоук тестами. Теперь их нужно разбить на части и запустить набор тестов, подготовленный на предварительном этапе. Сбоями занимаются агенты исправители, которые сопоставляют упавшие тесты с обеими кодовыми базами. Состязательные проверяющие оценивают их исправления.
Следующий элемент цикла, демон сборки. Это единственный процесс, которому разрешено пересобирать исполняемый файл. Исправители записывают патчи, демон объединяет их в пакет, выполняет одну сборку, снова запускает затронутые тесты и возвращает результаты. Самая дорогая операция выполняется последовательно, вместо того чтобы несколько агентов независимо запускали ее одновременно.
Если одна и та же ошибка повторяется во многих тестах, исправление переносится выше по процессу. Вы меняете правило, которое породило ошибку, и заново генерируете только затронутые этим правилом файлы.
Подход Майка здесь особенно важен, потому что у многих разработчиков нет готового или перенесенного набора тестов. Майк попросил Claude создать небольшой скрипт, который запускал семь реальных сценариев на новой версии и исходной кодовой базе Python, а затем сравнивал результаты. Каждый неудачный сценарий получал собственного агента исправителя. Цикл продолжался, пока не прошли все семь сценариев.
Затем Майк пошел еще дальше. Claude спроектировал собственный набор сквозных тестов и четыре ночи подряд автономно запускал его, исправлял ошибки и повторял проверку. Благодаря этому нашлись мелкие дефекты, которые невозможно было предсказать заранее подготовленным списком сценариев.
Вывод: отсутствие набора тестов не блокирует этот этап. Если арбитра нельзя унаследовать, попросите Claude его создать. Исходная кодовая база в любом случае остается эталоном.
Каждый прогон учил нас тому, чего не показал предыдущий. Можно уверенно ожидать, что и ваша следующая миграция откроет нечто, чего нет в этом руководстве. Но несколько практик подтвердили свою ценность во всех проектах:
Не следуйте руководству слепо. Каждая миграция уникальна. Используйте этот материал как отправную точку и до начала работ спланируйте конкретный проект вместе с Claude.
Не сосредотачивайтесь на единичных сбоях. Это работа цикла, агенты исправители постепенно их устранят. Ваше внимание должно быть направлено на повторяющиеся закономерности.
Сделайте ревью состязательным, а проверку механической. Состязательное ревью позволяет дольше выполнять автономные задачи и часто оправдывает расход токенов. Арбитром должны быть скрипты, компилятор, сравнение результатов и набор тестов.
Не используйте крупнейшую модель для всего. Основной расход токенов приходится на циклы, поэтому проектируйте их осознанно. Небольшие модели хорошо справляются с массовой параллельной реализацией. Крупнейшую модель стоит оставить для проверяющих и задач, в которых создаются правила для других агентов.
Сосредоточьте человеческие усилия в начале. Больше всего времени требуют свод правил и стресс-тест. Дальше работа в основном сводится к обработке очередей.
Сделайте очередь механической и возобновляемой. Критерий готовности должен быть простым: выходной файл существует на диске.
Миграция Bun Джарреда уже работает в продакшене, хотя у любого переноса есть компромиссы. Например, около 4% кода на Rust находится в блоках unsafe. В основном это однострочные операции с указателями на границах с C и C++.
Но новая кодовая база измеримо лучше прежней. Исправлены все утечки памяти, которые способны обнаружить инструменты команды. В одном из тестов с 2 000 повторных сборок потребление памяти снизилось с 6 745 до 609 МБ. Исполняемый файл для Linux и Windows стал на 19% меньше. Оптимизация на границе языков ускорила HTTP-сервер и реальные задачи, включая next build и tsc, на величину от 2% до 5% в зависимости от нагрузки.
Возможно, пришло время заново оценить экономику миграции, которую вы давно откладывали. Выберите кодовую базу, с которой до сих пор мирились, и спросите Claude, как мог бы выглядеть процесс ее переноса.

Друзья! Эту статью подготовила команда ТГК «AI for Devs» — канала, где мы рассказываем про AI-агентов, плагины для IDE, делимся практическими кейсами и свежими новостями из мира ИИ. Подписывайтесь, чтобы быть в курсе и ничего не упустить!