Что понадобилось, чтобы доверять менеджеру пакетов, который написал Claude

Переведено с английского с помощью ИИ. Читать оригинал

Я не написал и даже не прочитал ни одной строки кода, пока делал jpm — менеджер пакетов для JavaScript, который работает в проектах на npm, pnpm, yarn и bun. Причём я даже попросил ИИ написать собственные модули TLS, криптографии и HTTP, чтобы сделать его меньше. Поначалу это был самый настоящий вайб-кодинг. И готово всё было за полдня. Ни я, ни кто-либо другой не должен был доверять ему в таком состоянии, но потом я взял то, что знаю за 27+ лет работы профессиональным разработчиком, заставил его прогнать себя на существующих тестовых наборах, а потом пять дней писать тесты и жёстко тестировать себя, чтобы сделать его надёжнее.

О себе

За свою карьеру я пользовался многими языками программирования, но в основном высокоуровневыми, вроде C#, Elixir и JavaScript. Низкоуровневый язык я не использовал больше чем для пары функций с тех пор, как ребёнком учил C и летом помогал отцу у него на работе с низкоуровневыми контроллерами дисков. Так что освоить Rust и написать на нём проект на 60k+ строк за короткое время я бы не смог. Зато я хорошо узнал, как работают npm, pnpm и bun, потому что профессионально использую Node.js ещё с Node.js 0.8 (2012). А ещё я знаю, сколько на самом деле нужно тестировать софт и как важно это автоматизировать, а не делать один раз. Все эти agile-ребята, которые твердили мне сначала писать тесты, оказались правы — это окупилось!

Почему jpm

Я смотрел подкаст Syntax на YouTube с ведущими Wes, Scott и CJ, и в начале выпуска они говорили про upm — быстрый и очень маленький менеджер пакетов, но написанный на JavaScript. Первое, о чём я подумал: насчёт размера — неправда, ведь к нему нужно прибавить среду выполнения Node.js или какую-то другую. Его написал Pooya, и CJ показывал бенчмарки из проекта, где видно, что он быстрый, но мне просто захотелось проверить, сможем ли мы сделать быстрее. Pooya — хороший инженер, который делает много хороших инструментов для экосистемы JavaScript, так что я знал, что это будет непросто. И наконец, я уже какое-то время пользовался моделью Opus 5.5, и она меня просто поражала. Вот я и подумал: раз уж столько других инструментов уже написано на Rust (в подкасте над этим даже шутили), может, и у меня получится сделать на Rust по-настоящему маленький и быстрый менеджер пакетов.

В подкасте ведущие отвечали на вопросы своей аудитории про ИИ — те самые вопросы, которые сейчас задают все. Может ли он создавать настоящий софт или только демки? Стоит ли инженерам волноваться или радоваться? Большинство считает, что ИИ может сделать что угодно с первой попытки. Спойлер: может. Просто результаты так себе, если им не руководить и не понимать, как разрабатывать софт.

Полдня

Так что в тот день, когда я заканчивал с основной работой, я открыл новую вкладку в терминале и спросил Claude, что нужно, чтобы переписать upm как CLI на Rust. Я прочитал сводку, дал добро, и он взялся за дело. Кажется, ушло минут 45, и у него была рабочая версия на куче сторонних Rust-крейтов, из-за которых она была гораздо больше, и протестирована она была только на маленьком package.json. Чтобы её уменьшить, я попросил разобрать, что именно делает её такой большой, и к концу того вечера у меня были собственные модули HTTP, JSON, криптографии и TLS и намного меньший бинарник. Я велел ему писать только те части, которые нам нужны, но и жёстко их тестировать — тестами других проектов, где получится, или нашими собственными, плюс немного фаззинга. Я хотел быть уверен, что они надёжные.

Пять дней

Вот эту часть все, кто «вайб-кодит» проекты, и опускают. Да, у меня всё работало на маленьком package.json под Linux в WSL2 на Windows, но я не проверял ни Mac, ни Windows, ни по-настоящему большие проекты. Поэтому я попросил его написать скрипт для укрепления, который брал 100, а в итоге 166 больших публичных проектов и пытался их установить и собрать. Потом установить и собрать их тем менеджером пакетов, который они используют, и сравнить. Включая запуск их тестов, где это было возможно, потому что некоторым тестам нужны были секреты или другая настройка. И ещё делать всё это в командной строке Windows и в PowerShell, а на своём Mac я запустил агента, чтобы он делал то же самое на Mac. И через пять дней и множество решений о том, что поддерживать и как, у нас был 261 смёрженный PR и надёжный, хорошо протестированный менеджер пакетов.

Как выглядело руководство

Так что код я не писал, но я руководил и принимал кучу мелких решений. Например, Claude говорил, что более быстрый формат lockfile сэкономит всего несколько ms, но я всё равно попросил это изменить. Ещё Claude хотел сделать глобальное хранилище опцией, которую нужно включать вручную, но я спросил: зачем, если им могут пользоваться 99% пакетов? — и оно стало поведением по умолчанию. Ещё я не хотел, чтобы он запускал скрипты, включая скрипты установки зависимостей, если вы явно их не разрешили. Логика была такая: сейчас большую часть работы делает ИИ, так что прежде чем одобрить скрипты пакета, я могу просто попросить его сначала проверить их на проблемы.

Было и два случая, когда Claude всерьёз со мной спорил, но оба раза я настоял на своём.

Первый — TLS. Я хотел уменьшить бинарник, а крейты TLS и криптографии составляли большую его часть. Claude сказал, что это именно тот код, который никогда не пишут сами. Я ответил, что это старое инженерное мышление. Мы на Rust, так что ошибки работы с памятью, стоявшие за вещами вроде Heartbleed, исключены, и свою версию мы можем тестировать так же жёстко, если не лучше. Он согласился, что преувеличил риск, и расписал, какие тестовые наборы это докажут (Wycheproof, векторы NIST, x509-limbo, BoGo). Я до сих пор сомневаюсь, хорошая это идея или нет, а Claude любит поддакивать и говорить то, что вы хотите услышать, так что его слова о том, что он преувеличил риск, вполне могли быть просто желанием угодить. Он проходит кучу тестов, включая тесты, сделанные на основе реальных багов из других проектов. Я точно знаю, что теперь доверял бы ему гораздо больше, кто бы его ни написал.

Второй — HTTP/2. Мне хотелось больше скорости, и я спросил про него. Claude сначала проверил его с curl и сказал, что делать его не стоит, потому что вся установка jpm по HTTP/1.1 уже была быстрее, чем одна только загрузка через curl по HTTP/2. Я всё равно надавил. curl должен обрабатывать все случаи, а нам нужно только много GET-запросов к одному реестру. Чтобы обогнать bun, надо мыслить нестандартно. Он согласился, и примерно через два часа у него был рабочий клиент.

Потом мы это измерили. На моём домашнем интернете HTTP/2 был примерно на 17% быстрее. На раннерах GitHub он шёл вровень или проигрывал, на холодной установке nuxt был до 2 раз медленнее и на больших tarball-архивах потреблял на 8–15% больше CPU. Именно в CI установки стоят реального времени и денег, а ещё он добавлял к бинарнику 60 KB. Так что я попросил его удалить все 3 671 строку. Тут я был неправ, и цифры мне это показали.

Бывало и наоборот. jpm может сам установить вам Node, если хотите, и я хотел спрятать проверку подписи Node за флагом, потому что думал, что она замедлит установку. Claude намерил от 15 до 19 ms и сказал, что флаг означает лишь одно: его никто не включит. Теперь она включена по умолчанию, и я этому рад, потому что это добавляет безопасности.

Почему я доверяю коду, который никто не набирал

Если коротко: Wycheproof, node-semver, собственные тесты pnpm, yarn и bun и 166 репозиториев, на которых мы его проверяли. Некоторые баги, которые они нашли по пути:

  • Собственные тесты node-semver нашли 25 случаев, где jpm с ним расходился, например принимал ^9007199254740991.0.0 как диапазон.
  • Из тестов npm-package-arg поначалу совпадало только 117 из 154. a@a.tar.gz читался как тег.
  • Сканирование безопасности показало, что .npmrc склонированного репозитория мог отправить ваш токен реестра через свой собственный прокси. PoC перехватил Bearer USER-SECRET-TOKEN. Теперь .npmrc проекта больше не может снизить безопасность.
  • На реальных репозиториях число ссылок на зависимости, где установка jpm отличалась от pnpm, упало с 1 320 до 183, а от npm — с 614 до 14.
  • Под macOS jpm вообще не компилировался, пока я не протестировал его на macOS.

А ещё за столько лет в разработке я знаю, что ничего не бывает без багов, какими бы ни были тесты и тестирование. И доверие нужно заслужить, поэтому я собираюсь и дальше использовать его во всех своих проектах и поддерживать исправления, даже присланные ИИ. Это не значит, что я буду принимать их все. Они должны тестами доказать, что действительно что-то исправляют. Суть в том, что я не собираюсь отключать PR, но разбираться с ними мне будет помогать ИИ.

Где мы сейчас

Мы довели исполняемый файл до 2 MB, и он не всегда самый быстрый, но в числе лидеров. А ещё он один из самых безопасных: не запускает скрипты без одобрения. Не обновляет пакеты до последней версии, если ей нет хотя бы суток. И защита вокруг .npmrc.

Вот один из графиков, который показывает, насколько быстрыми мы можем быть на раннере GitHub Actions по сравнению с другими менеджерами пакетов:

Холодная установка nuxt (591 пакет) на GitHub CI, Linux, медиана 5 запусков: jpm 1,22 s, bun 1,28 s, pnpm 1,72 s, aube 3,00 s, upm 3,52 s, deno 7,64 s, yarn 9,63 s, npm 18,54 s

Все машины, фазы и проекты — на getjpm.sh.

Где он не самый быстрый

Скажу честно: бенчмарк маленький, всего три репозитория. И мы не всегда самые быстрые. Например, на Windows на nuxt нас убивают проверки Defender. На macOS bun и aube выигрывают на тёплых установках nuxt. Так что, если конечная цель — скорость, есть куда расти. Мы прогнали установку 166 проектов на Linux, macOS и Windows, плюс ещё больше случайных на Linux, чтобы искать баги, но не тестировали производительность каждого из них в CI и на машинах.

jpm идеален? Нет. Как и ничто из того, что я написал руками за 27 лет, и ничто из того, что вообще когда-либо написал человек. Идеального не бывает. Бывает достаточно хорошо, бывает отлично, и бывает протестировано достаточно, чтобы знать, что из этого у вас.

Попробуйте: curl -fsSL https://getjpm.sh | sh на macOS и Linux или irm https://getjpm.sh/install.ps1 | iex на Windows, а потом запустите jpm install в любом проекте. Если найдёте баг, создайте issue на GitHub.