Prompt file imported from greydragon888/real-router (
.claude/commands/bump-dep.md). Fill in{{arguments}},{{out}},{{output}}before use. Copyright stays with the author.
Проанализируй и обнови зависимость: изучи все релизы от текущей версии в проекте до актуальной в реестре, оцени влияние каждого изменения на код и конфиги проекта, предложи внедрение полезного и (при мажоре) путь миграции.
Входные данные: {{arguments}}
Формат аргументов:
- Имя зависимости (обязательно), например
tsdown. - Опционально целевая версия (
tsdown@0.23.0); по умолчанию — последняя в npm-реестре.
Диспетчер: eslint-экосистема — НЕ этот скилл. Если пакет —
eslint,@eslint/js,eslint-plugin-*,@*/eslint-plugin(напр.@vitest/eslint-plugin),eslint-config-*,typescript-eslint,@typescript-eslint/*,@stylistic/*илиeslint-import-resolver-*— останови и используй/bump-eslint. Там специализированный процесс (diff наборов правил, замер срабатываний реальным прогоном, recommended-сдвиги, flat-config, peer-совместимость) — линтер влияет через набор активных правил, а не через «трогает ли он наш код». Этот скилл — для всех ОСТАЛЬНЫХ зависимостей.
Фаза 0 — Локализация и гейт «только корневой package.json»
- Определи имя пакета (и целевую версию, если задана) из аргумента.
- Найди зависимость в корневом
package.json(dependencies/devDependencies/peerDependencies):node -e "const p=require('./package.json'); const d={...p.dependencies,...p.devDependencies,...p.peerDependencies}; console.log('<pkg>', d['<pkg>'])"- Нет в корневом
package.json— не применяй бамп, но и НЕ заканчивай на этом. Гейт защищает ДОСТАВКУ (он и есть гарантия «прошло гейт → devDep → changeset не нужен»), а не АНАЛИЗ, и большинство поверхности репозитория лежит именно за ним. Поэтому отказ обязан кончаться следующим шагом: (1) скажи, что доставкой этой зависимости владеет Dependabot — npm-запись в.github/dependabot.ymlодна,directory: "/", и pnpm-воркспейс разносит её по всем членам (проверено на PR #2166: 100 изменённых файлов вне корня); (2) если пользователь просит разбор — проведи Фазы 1–3 как обычно и выдай отчёт БЕЗ Фазы 4, честно пометив «доставка не здесь»: это ровно тот анализ, которого групповой PR не делает никогда; (3) отправь к списку ловушек группового PR в CLAUDE.md § «Grouped Dependabot PRs» (откат назад, пере-сидинг playwright, vite-плагины CodSpeed, сборщики публикуемых пакетов). Молчаливое «ПРЕКРАТИ» оставляло пользователя без пути там, где путь есть (#2168). ⚠ И сам отказ ПРИМЕНЯТЬ — условный, а не абсолютный. Гейт корня — структурная подстановка под «devDeps-only ⇒ не доставляется ⇒ changeset не нужен». Вне корня подстановки нет, но вывод может держаться напрямую, и проверяется он ОДНИМ вопросом: лежит ли пакет вpeerDependenciesкакого-нибудь публичного пакета? Проверятьdependenciesне нужно — у публичных пакетов он структурно пуст от внешнего (только@real-router/*), и это держитpublished-dependency-authority. Нет в peer'ах ⇒ это devDep ⇒ доставка инфраструктурная ⇒ применять МОЖНО, прямо вmaster. Есть в peer'ах ⇒ это опубликованный контракт ⇒ задача + ветка + changeset + PR:
Пусто — предохранитель не сработал. (Замерено на наборе из 17 некорневых: ни один не peer, все шесть, что садятся вfor d in packages/*/; do node -e "const q=require('./$d/package.json'); if(!q.private && (q.peerDependencies||{})['<pkg>']) console.log('$d')"; donepackages/*, — вdevDependencies.)
- Нет в корневом
- Зафиксируй текущую (точную, pinned) версию из корня. Версии в проекте пиннятся точно (
save-exact=true). - Определи целевую версию: из аргумента либо
npm view <pkg> version(последняя в реестре). Еслиcurrent == target— сообщи, что обновлять нечего, и заверши. - Полный список версий в диапазоне:
npm view <pkg> versions --json. - Карта использования в монорепо (консистентность): где ещё объявлена зависимость —
grep -rn "\"<pkg>\"" package.json packages/*/package.json benchmarks/package.json examples/**/package.json. Не забывайexamples/**—pnpm up -r(Фаза 4) рекурсивен и обновит пакет во ВСЕХ workspace-членах, включая примеры. syncpack держит одну версию во всех пакетах (sameRange, см.syncpack.config.mjs): если пакет pinned (не в~-исключении), бамп синхронизирует ВСЕ объявления → коммит затронет N файлов, не только корень. Посчитай ожидаемый охват ДО применения, чтобы состав коммита не был сюрпризом. ⚠ Охват бывает ШИРЕ карты, и лишнее — не твой бамп.pnpm-workspace.yamlдержит'<pkg>': $<pkg>-ссылки на корневое объявление, поэтому первый же install подтягивает к корню манифесты, отставшие по ДРУГОМУ пакету. Такие строки не откатывай (следующий install вернёт их) — но назови их в сообщении коммита отдельной фразой, иначе дифф читается как будто бампнуто два пакета. (Прогон tsx: карта дала 24 манифеста, изменилось 27 — три лишних несли@types/node, отставший от корня с чужого коммита, при зелёномlint:deps.) Проверь pnpm-патчи на пакет И ЕГО ТРАНЗИТИВЫ —patchedDependenciesвpnpm-workspace.yaml+grep patch_hash pnpm-lock.yaml. Ключ патча — ТОЧНАЯ версия, поэтому бамп делает патч неприменимым и роняетpnpm install; запись и файлpatches/*.patchудаляются в ТОЙ ЖЕ транзакции, ДОpnpm up. Патч часто висит не на бампаемом пакете, а на его зависимости (у нас: бампаем@changesets/changelog-github, патч — на@changesets/get-github-info), поэтому grep только по имени из аргумента его не находит. Ищи парную зависимость ВНЕpackage.json. Тул часто версионируется парой «npm-пакет + GitHub Action» (@changesets/cli↔changesets/action, где v2 действия — это буквально «Update to Changesets v3 packages»): грепни.github/workflows/*наuses:того же вендора/имени (<vendor>/action,<pkg>-action). Мажор одного тянет мажор другого, а второй половины в карте поpackage.jsonне видно вообще. ⚠ Открытый Dependabot-PR на мажор такого action НЕЛЬЗЯ мержить как есть: он правит только строкуuses:, а мажор обычно переименовывает входы — и неизвестныеwith:-ключи Actions игнорирует, а не отвергает, так что шаг продолжает работать на дефолтах (у нас это был бы голыйchangeset versionмимо всей пост-обработки плюс токен, тихо деградировавший до job-токена). Сверь КАЖДЫЙ передаваемый вход со спискомinputsвaction.ymlцелевого тега:gh api "repos/<vendor>/<action>/contents/action.yml?ref=<tag>" --jq '.content' | base64 -d. - Доставка — всегда инфраструктурная: без задачи и без changeset, но через PR (на
masterстоитpull_request-правило, CLAUDE.md). Гейт корня гарантирует это структурно: корневойpackage.json— devDeps-only (dependencies/peerDependenciesпусты), а публичные пакеты несут ноль внешних runtime-dependencies(всё черезworkspace:) и их внешниеpeerDependencies(react/vue/svelte/…) в корне не дублируются. Значит всё, что прошло гейт корня, — это devDep → потребителям не доставляется → changeset не нужен.- Предохранитель — для КОРНЕВОГО пакета редкость, для некорневого ВХОДНОЙ гейт (см. 0.2): прежде чем применять, проверь, не лежит ли бампаемый пакет ЕЩЁ и в
dependencies/peerDependenciesкакого-то публичного пакета (packages/*без"private": true):node -e "const p=require('./packages/<pkg-dir>/package.json'); console.log('in deps:', !!(p.dependencies||{})['<pkg>'], 'in peer:', !!(p.peerDependencies||{})['<pkg>'])"по карте использования. Если да — это доставляемое изменение: СТОП, не делай вmaster, нужны задача + отдельная ветка + changeset + PR (см. предохранитель в Фазах 4–5).
- Предохранитель — для КОРНЕВОГО пакета редкость, для некорневого ВХОДНОЙ гейт (см. 0.2): прежде чем применять, проверь, не лежит ли бампаемый пакет ЕЩЁ и в
Фаза 1 — Сбор релизов и changelog (current → target)
- Найди GitHub-репозиторий пакета:
npm view <pkg> repository.url. - Собери release notes по всем релизам строго в диапазоне
(current, target]:- GitHub releases:
gh release list -R <owner/repo>+gh release view <tag> -R <owner/repo>. ⚠ Последняя версия в npm бывает НОВЕЕ последнего тега на GitHub, и тела релизов бывают пустыми. Уdanger14.0.7 лежит в npm, аgh release listпоказывает latest14.0.6, и тега14.0.7не существует вовсе — поверив списку релизов, проанализируешь не ту target-версию. Сверьnpm view <pkg> versionсgh release list/gh api repos/<owner>/<repo>/tagsДО сбора notes; изменения версии, которой нет на GitHub, ищи в секции## Main/## UnreleasedфайлаCHANGELOG.md(проект обычно уже описал их там) либо diff'ом тарболов. И пустое телоgh release viewне значит «изменений нет» — у проектов с единымCHANGELOG.mdтела релизов пусты всегда. - Пакетное тегирование монорепо (релизы тегируются версией монорепо/продукта, а не npm-версией пакета):
gh release/CHANGELOG.mdтут бесполезны или неточны, а в тарболе CHANGELOG может вовсе отсутствовать. Тогда сравнивай по факту, а не по notes —CHANGELOG.mdсамого пакета (если есть) либо diff экспортируемого API между двумя установленными версиями (Object.keysпубличного API current vs target из /tmp). ⚠ Раскладку тарбола выясняй, а не предполагай (findпо распакованному: типы бывают вlib/,dist/,lib/types57/), и печатай счётчик со знаменателем — сколько файлов вообще попало под шаблон. Ноль из нуля файлов и ноль из четырёх выглядят одинаково, а значат противоположное: у fast-check 4.10.0grep -r '@deprecated' lib/typesдал 0 для ОБЕИХ версий (каталог —lib/types57), и «депрекаций нет» едва не ушло в отчёт о релизе, озаглавленном «deprecations ahead of v5»; реальный счёт — 28 → 116. (Для eslint-плагинов точную дельту даёт diff наборов правил — это/bump-eslint.) - Если тегирование обычное — возьми
CHANGELOG.mdсамого пакета (на GitHub или из npm-тарбола): он версионно-точный. - Тонкая CLI-обёртка над core-движком (напр.
@arethetypeswrong/cli→@arethetypeswrong/core; многие тулы split на*/cli+*/core): если CHANGELOG/notes бампаемого пакета для целевой версии — лишьUpdated dependencies → <core>@x, то реальное поведенческое изменение лежит в core. Иди за CHANGELOG/notes core-пакета (он транзитивный — может не быть в твоёмpackage.json; смотри его changeset/commit) и классифицируй по нему, не по пустому CHANGELOG обёртки. ⚠ Когда обёртка и движки — СИБЛИНГИ одного монорепо с общей версией, «иди за changelog core» не работает: он такой же пустой, потому что релизится тем же тегом. Диффай тарболы каждого сиблинга изdependenciesобёртки и смотри на тот, который держит ПРЕДМЕТ проверки, а не на тот, чьё имя звучит как ядро. У commitlint 21.2.2@commitlint/lintне изменился вовсе, а ответ дал@commitlint/rules— там реализации правил, и там тоже ноль; весь диапазон свёлся к двум опечаткам в комментариях при побайтово идентичномconfig-conventional/lib/index.js. - Инструмент с подкапотными движками (bundler/dts-генератор/компилятор со своими транзитивными движками —
tsdown→rolldown+rolldown-plugin-dts,vitest→vite, и т.п.): release notes хоста часто сворачивают весь реальный сдвиг в одну строку «Upgrade<engine>to vX» — а настоящая дельта бампа (включая breaking) лежит в движках. Сравниnpm view <pkg>@<current> dependencies --jsonvs@<target>: каждый significant сдвиг движка (minor/major транзитивногоdependencies) анализируй как ОТДЕЛЬНЫЙ релиз — иди за ЕГО changelog (gh release viewв репозитории движка), классифицируй ЕГО breaking/feat/fix против нашего пути. Частая мина: minor хоста тянет major движка (напр. смена трансформера/парсера) — формально «minor bump», по факту новый кодогенератор всего вывода. ⚠ У пакета БЕЗ зависимостей глубину бампа меряет набор КОММИТОВ, а не диффdist. Минифицированный или ре-бандленный артефакт двигает почти каждую строку при неизменной логике, и внутренний рефакторинг читается как переписанный движок: у tinybench 6.2.0dist/index.jsдал 503 изменённые строки из 1633 при одной фиче — этоrefactor: split src/utils.ts by concern(утилиты ушли в −1038 строк и разъехались по десяти файлам).gh api repos/<o>/<r>/compare/v<cur>...v<tgt> --jq '.commits[].commit.message'отвечает одним запросом и отделяетfeat/fixотchore; диффdistоставляй на потом и только как подтверждение. ⚠ Пустойdependenciesне значит «движка нет». У обёртки над НАТИВНЫМ бинарём движок едет вoptionalDependencies(<pkg>-<os>-<arch>), и пины обёртки протухают:jscpd@5.1.0пинил бинари5.0.16, то есть выбрав эту версию целью, гоняешь движок на релиз назад, считая, что бампнул (апстрим признал это багом и починил в 5.1.1). Сравнивайnpm view <pkg>@<v> optionalDependenciesпо ВСЕМ версиям диапазона, а после установки сверяй<tool> --versionс целевой — расхождение выбирает другую цель бампа. - Когда target — minor/major, а current — промежуточный patch той же серии (напр.
2.9.18 → 2.10.0), бери точную дельту черезgh api repos/<owner>/<repo>/compare/v<current>...v<target> --jq '.commits[].commit.message'. Auto-generated GitHub release notes (release-drafter / release-please / changesets) дляX.Y.0часто покрывают всё от предыдущегоX.(Y-1).0(сотни PR) и пере-перечисляют изменения, уже установленные в твоём patch —gh release view <target>их задваивает, и легко ошибочно приписать бампу фичи, которые давно в проекте.compareотдаёт ровно(current, target]. Оговорка:compareчист на уровне набора коммитов, но в монорепо на release-please/changesets окно(current, target]обычно содержит back-merge-коммитrelease vX, чьё сообщение встраивает весь changelog предыдущей версии (десяткиfix:из старой серии). Отбрасывай тело такого release-коммита (узнаётся по заголовкуrelease(...)/release vXи встроенному списку) — классифицируй только реальныеfeat/fix/perf-коммиты после него. Эвристика «уже установлено»: низкий номер PR/коммита относительноrelease <current>в notes = вышло раньше твоей версии. - Для крупных мажоров — официальный migration guide (
WebFetchна страницу релиза/доки;context7для актуальной документации библиотеки).
- GitHub releases:
- Сгруппируй изменения по версиям; для каждого помечай тип: bugfix / feature / deprecation / breaking / chore.
Анализ всегда ПОЛНЫЙ — semver-метка не определяет глубину. Не используй характер бампа (patch/minor/major) как фильтр: некоторые инструменты нарушают semver и кладут новые фичи (а иногда и breaking) в patch-релизы (например
tsdown,turbo). Поэтому читай release notes каждого релиза в диапазоне целиком, даже если сменилась только patch-версия. Не пропускай и не «пробегай» patch-релизы, не делай вывод «это просто фиксы» по номеру версии — классифицируй по фактическому содержимому notes. Фазы 2–3 применяй к находкам независимо от того, как формально сменилась версия.
Фаза 2 — Поэлементный анализ влияния (ядро скилла)
Пройди по каждому значимому изменению и сопоставь с РЕАЛЬНЫМ кодом и конфигами проекта (ищи через grep/Glob по затронутому API/опции/правилу):
Для CLI-тулинга (линтеры, сканеры, бандлеры) релевантность фильтруй не только grep'ом по API, но и по ФАКТИЧЕСКОЙ инвокации. Решает не «трогает ли изменение код в репо», а «трогает ли оно путь, которым мы этот тул запускаем»: какие флаги передаём (
--cache?--fix?), на каких ОС крутимся (dev vs CI — напр. фикс OOM/overcommit только на Windows нерелевантен на macOS+Linux), какие плагины/пресеты тула активны (адаптер ≠ приложение фреймворка). Фикс/фича пути, который мы не вызываем (флаг не передаём / плагин не активен / баг только на чужой ОС), — нерелевантен, даже если формально это bugfix или feature. Проверяй инвокацию поpackage.jsonscripts,.husky/*, CI workflow и самому конфигу тула, а не только по grep исходников. И сам конфиг тула — первый фильтр релевантности, а не последний: список форматов/языков, набор входных корней и имена ключей отсекают целые фиксы до того, как начнёшь сверять их с кодом. У jscpd 5.2.0 три фикса из четырёх снялись строчками.jscpd.json—formatбез markdown убил markdown-фикс при 22.mdпрямо в зоне скана, ключignore(а неignorePattern) убил фикс конфига, ноль.vueв корнях скана убил SFC-фикс. Читай конфиг ПЕРЕД notes, иначе разберёшь фиксы для языков, которых тул у тебя не парсит. ⚠ Тот же вопрос задай про ГЕЙТ, а не только про запуск: файл тула в корне (dangerfile.ts,*.config.ts) может входить в корневойtsconfig.jsonи при этом не проверяться НИКЕМ — turbo гоняет задачи по пакетам, а root-задачи (//#type-check) может не быть вовсе (у нас корневойtsc -p tsconfig.jsonдаже не собирается: JSX-ошибки вbenchmarks). Прежде чем писать «типы покрыты», найди задачу, которая это реально запускает; если её нет — проверяй точечно (в TS 6 этоtsc --noEmit --ignoreConfig <file>, иначе TS5112) и прогоном тула. ⚠ И отдельно — как тул читает конфиг: мажор часто меняет именно загрузчик (jiti → нативныйimport(), версия cosmiconfig, наборsearchPlaces/loaders). Установи три вещи: как называется твой конфиг, в каком он формате (ESM/CJS/TS — и что говоритtypeв корневомpackage.json), и что целевая версия делает с этим расширением — смотриloaders/searchPlacesв её dist, а не в README. Уsize-limit13 весь мажор сводится к этому:.size-limit.jsуцелел только потому, что корень — ESM.
- Bugfix: затрагивает ли он путь кода/конфиг, который мы используем?
- Снимает ли он наш воркэраунд — можно ли удалить
eslint-disable,@ts-expect-error, ручной патч,pnpm.overrides, хак? Воркэраунд бывает и пином окружения в CI (node-version,runs-on, таймаут), поставленным ради бага ТРАНЗИТИВА: сравниpnpm why -r <транзитив>до и после (у нас бамп выкинулnode-fetch@2из дерева, а с ним — основание для точечного пина Node в релизном workflow). Грепай по именам УХОДЯЩИХ транзитивов, а не только по имени бампаемого пакета. ⚠ И смотри обратный случай: нашoverridesможет заблокировать бамп. Мажор часто требует новый мажор своего движка (@babel/core,esbuild,rollup), а глобальный<N-bound, заведённый из-за ДРУГОГО потребителя, молча отдаёт ему старую версию: install проходит, тул падает в рантайме внутри чужого пакета (у нас Stryker 10 умирал в@babel/generator@8— babel-7 ядро против babel-8 генератора). СверьdependenciesЦЕЛЕВОЙ версии со спискомoverrides; на пересечении проверь, насколько bound шире своего обоснования —parent>child-сужение развязывает обе стороны (solid-цепочка объявляет^7и до 8 не дотянулась бы даже без override). ⚠ Воркэраунд снимает не только целевой релиз, но и уже УСТАНОВЛЕННЫЙ. Окно(current, target]слепо к классу «корень починен релизом, который мы приняли, а обход никто не удалил»: апстрим чинит, бамп проходит, обходной путь живёт дальше и тихо гниёт. На каждом бампе тула грепни его собственные обходы —grep -rni 'workaround\|DEPRECATED\|PENDING REMOVAL\|<pkg>/issues' scripts/ .<tooldir>/ .github/workflows/— и прочитай их докблоки: там обычно записан и сам гейт снятия. Статус названного issue проверь у апстрима (gh api repos/<o>/<r>/issues/<N> --jq '{state,state_reason}'): closed/completed значит, что работа не «бампнуть», а «удалить». ⚠ И premise'ы такого докблока пере-мерь — они протухают раньше самого файла: у нас он называл три пакета с диапазоном, который они давно переросли, и ссылался на опцию конфига, которой в конфиге нет. ⚠ Удаление доказывай КОНТРОЛЕМ, а не гейтом: зелёный прогон обхода значит и «причины нет», и «обход сломан» — сначала докажи, что он ещё умеет ловить (подсунь ему дефект), потом что ловить нечего даже при откате нашей собственной правки. - Чинит ли латентный баг у нас, позволяет ли упростить/оптимизировать существующий код или конфиг?
- Если изменение не касается нашего кода — отметь «нерелевантно» (без спекулятивных правок).
- Снимает ли он наш воркэраунд — можно ли удалить
- Feature: полезна ли она здесь? Конкретно — в каком файле/конфиге, что улучшает (читаемость, производительность, типобезопасность, DX).
- Если пользы нет — так и скажи. Не внедряем фичи «на всякий случай» (принцип «нет реального запроса/аналога — нет gap»).
- Deprecation/removal: используем ли мы устаревшее/удалённое? Где именно? Чем заменить. ⚠ Для тула с конфигом проверяй не «принимается ли опция», а читается ли она: схема валидации продолжает её принимать и после того, как движок перестал её читать. Грепни имя опции по dist движка, а не только по схеме, и в первую очередь те опции, на которые CLAUDE.md ссылается как на защиту: мёртвый флаг тише удалённого. ⚠ Движок — это КАЖДЫЙ пакет, которому передаётся конфиг, а не первый найденный.
onlyUpdatePeerDependentsWhenOutOfRangeудалили из.changeset/config.jsonкак мёртвый, потому что его не читаетassemble-release-plan@7. Но его читаетapply-release-plan@8: без флагаchangeset versionстал переписывать пир-диапазоны, и релизный PR #2410 упал на устаревшем lockfile. Грепай по всем@changesets+*в.pnpmи сверяй, у кого нашлось вхождение. ⚠ И убедись, что грепаешь артефакт ЦЕЛЕВОЙ версии: после бампа вnode_modules/.pnpmостаются каталоги старых версий, аrequire.resolve/find … | head -1спокойно отдаёт прошлый бинарник. У turbo я так получил «ни одного из пяти futureFlags не найдено» — из@turbo/darwin-arm64@2.10.5, лежавшего рядом с новым. Проверяй версию в пути того файла, который открыл. ⚠ И отдельно от «тот ли артефакт» — тот ли МАТЧЕР. Отсутствие, найденное грепом, читается как находка, а чаще это якорь или экранирование:grep -qxтребует совпадения ВСЕЙ строки, тогда как имена опций сидят внутри длинных строк и JSON-фрагментов. Негативный контроль («выдуманного имени нет») эту ошибку пропускает — он проверяет, что инструмент не переспрашивает, а не что он умеет находить. Нужен ПОЗИТИВНЫЙ: заведомо присутствующая строка (turbo.json, имя самой секции конфига) обязана дать ненулевой счёт ТЕМ ЖЕ выражением. Ноль при нуле у контроля — измерения не было. (Прогон turbo 2.10.13:strings … | grep -qxдал «все пять futureFlags ОТСУТСТВУЮТ»; не-якорный греп нашёл все пять.) ⚠ Тот же капкан, но про РАНТАЙМ:.pnpmкопит каталоги прошлых установок, и у одного пакета их бывает шесть —find+headотдаст осиротевший, а вывод «потребитель на старой версии» будет ложным (у меня так вышло с tsdown: шесть каталогов, выбранный линковалpublint@0.3.21, тогда как lockfile и рантайм несли 0.3.24, и на ложной посылке ушёлpnpm install --force). Не резолви ссылки: иди поnode_modules/<pkg>— единственному, на который смотрит рантайм, — или, лучше, запусти тул и прочитай, что он печатает про себя; прогон отвечает на вопрос, на который резолв ссылок только намекает. ⚠ Пробу-скрипт запускай ИЗ каталога воркспейса, а не файлом в/tmp/scratchpad: вне дерева репозитория он не резолвит воркспейсные пакеты, иERR_MODULE_NOT_FOUNDчитается как «пакета нет» или «бамп сломал резолюцию», а не как ошибка размещения пробы. ⚠ И отдельно от «читается ли опция» — сменился ли её ДЕФОЛТ: тул продолжает её читать, но подставляет другое значение, когда ты её не задал, и доки типов при этом врут. Уtsdown0.23AttwOptions.profileнесёт@default 'strict'в типах ОБЕИХ версий, а реализация читается какprofile = "esm-only"— то естьattw: trueмолча перестал проверять CJS-резолюцию. Грепай присваивание дефолта в dist (= "…"рядом с именем опции) и сравнивай старую реализацию с новой; дефолт, сдвинувшийся в сторону меньшей строгости, фиксируй значением явно. ⚠ Собираешься написать, что депрекацию поймает ГЕЙТ, — сначала сделай её красной. Включённость правила проверяется--print-config, а покрытие — только позитивным контролем, внедрённым в той форме, которой пользуется проект:@typescript-eslint/no-deprecated(он же SonarJS S1874) ловит ССЫЛКУ (prop.beforeEach(...)) и МОЛЧИТ на ключ объектного литерала ({ unbiased: true, numRuns }) — а у fast-check все восемь депрекаций 4.10.0 живут именно в мешкеParameters, которым в репозитории 1346 вызовов.tscтоже не гейтит: депрекация у него suggestion, не ошибка. И сам контрольный символ проверь на существование — несуществующий даёт находку ДРУГОГО правила (import-x/namespace), которую легко прочесть как успех. - Breaking change: что конкретно ломается у нас → переходит в план миграции (Фаза 3).
Фаза 3 — План миграции (если в диапазоне есть breaking changes)
Триггер — фактический breaking change в notes, а не номер версии. Из-за нарушений semver (см. Фазу 1) breaking может прийти и в minor/patch — план миграции нужен в любом таком случае, не только при смене мажора.
- Разработай конкретный, упорядоченный путь миграции: какие файлы/конфиги и как править, в каком порядке, что проверять после каждого шага.
- Покажи before/after для ключевых правок.
- Раздели объём на «механически/autofix» vs «требует ручного решения»; оцени риск.
- Если миграция объёмная или рискованная — не применяй сразу: предложи план и спроси подтверждение.
Фаза 4 — Применение (после согласования)
Гейт ветки — отдельная задача не нужна, ветка и PR нужны (pull_request-правило на master, CLAUDE.md). Только если сработал предохранитель из Фазы 0.7 (бампаемый пакет оказался в dependencies/peerDependencies публичного пакета — пока не случалось) — это доставляемое изменение: СТОП, заведи задачу (gh issue create --title "chore(deps): bump <pkg> <current> → <target>" --body "<что меняется для потребителей, миграция>", покажи перед созданием), переключись на ветку (gh issue develop <N> --checkout) и делай всё там.
Пре-флайт: git status ПЕРЕД применением. Скил дальше предполагает чистое дерево — не полагайся на это. Если дерево уже грязное (незакоммиченный прошлый бамп ИЛИ параллельный WIP пользователя — оба нормальны, если бампы идут подряд или пользователь работает в фоне): (a) стейджи ТОЛЬКО файлы этого бампа явно (package.json pnpm-lock.yaml + свой doc-sync вроде CLAUDE.md), никогда git add -A; (b) если прошлый незакоммиченный dep-бамп делит pnpm-lock.yaml с текущим — по рукам их не разделить (транзитивы интерливятся): не пытайся, вынеси факт пользователю и предложи либо объединённый build(deps)/chore(deps)-коммит, либо отложить; (c) перед коммитом сверь git diff --cached по общим файлам (package.json/CLAUDE.md/lockfile) — чужие правки, попавшие в тот же файл, стейджить нельзя. Не запускай pnpm install/pnpm up вслепую на грязном дереве, ожидая «только мой пакет в diff» — сначала зафиксируй, что уже изменено. ⚠ И восстановление после ПРОБЫ проверяй полным git status, а не тем файлом, который правил. Правка pnpm-workspace.yaml, package.json или любого другого входа резолвера распространяется в pnpm-lock.yaml при первом же запуске pnpm, поэтому git diff --quiet <твой файл> покажет чистоту, пока рядом лежит незамеченный след. Проверено дорого: ослабленный axios: '>=1.0.0' вместо security-пола '>=1.16.0' пережил моё «восстановлено» и был пойман только пре-флайтом СЛЕДУЮЩЕГО бампа — иначе уехал бы в его коммит. ⚠ Чужая правка в ТОМ ЖЕ файле — не выбор между «взять всё» и «не коммитить». git add -p неинтерактивен, но индекс можно собрать напрямую: возьми git show HEAD:<file>, наложи на него ТОЛЬКО свою правку, затем BLOB=$(git hash-object -w <твой-файл>) и git update-index --cacheinfo 100644,$BLOB,<file>. Индекс получит твой хунк, рабочее дерево сохранит оба. Проверяй результат ДВУМЯ диффами: git diff --cached <file> (только твоё) и git diff <file> (только чужое). ⚠ И правь файл, ПРОЧИТАВ его в переменную отдельным оператором: open(p,"w").write(f(open(p).read())) усекает файл до нуля ДО чтения, а assert-якорь внутри f() падает уже по пустой строке и выглядит как «ничего не записано».
-
Обнови точную версию во всех пакетах монорепо (консистентность обязательна):
pnpm up -r <pkg>@<target>→ затемpnpm dedupe. ⚠ Для НЕкорневого пакета эта команда — та же, а охват другой на два порядка. У корневого одно объявление разворачивается install'ом; у некорневого syncpack держит одну точную версию во ВСЕХ манифестах, где он объявлен, и это бывают десятки (замерено:@playwright/test— 109 манифестов,@sveltejs/vite-plugin-svelte— 26,@vitejs/plugin-vue— 24,vue-tsc— 23,@preact/preset-vite— 20). Посчитай число ДОpnpm up, сверь сgit diff --name-only | wc -lПОСЛЕ, и расхождение объясни явно: лишнее — это$<pkg>-подтяжки чужих отставших манифестов (Фаза 0.6), а не второй бамп. Неназванное расхождение в 100-файловом диффе не прочитает никто. ⚠ И сверь ХУНКИ, а не только файлы:git diff -U0 -- '*package.json' | grep '^[+-] ' | sort | uniq -cдолжен показать только строки бампаемого пакета.pnpm upпересериализует манифест целиком, и посторонняя правка приходит в том же файле, который число уже засчитало (прогон jsdom 30.1.0: 39 файлов из 39 ожидаемых, а вdescriptionуpreload-plugin\u2014стал—; экранированный юникод есть ещё вmemory-pluginиrx). -
Проверь консистентность и дубли:
pnpm lint:deps(syncpack) +pnpm lint:dedupe. При расхождении —pnpm lint:deps:fix. Настоящий индикатор чистоты lockfile — прошедшийpnpm lint:dedupe, а НЕ счётчик grep. Если проверяешь остаток старой версии grep'ом — якори имя на границе:grep -E "(^|[ /'])<pkg>@<oldver>" pnpm-lock.yaml, неgrep -c '<pkg>@<oldver>'. Наивный grep даёт ложные срабатывания: ловит пакеты с бампаемым именем в суффиксе (turbo→eslint-config-turbo/eslint-plugin-turbo,core→core-js) и промахивается по scoped-пакетам (в lockfile они в одинарных кавычках —'@scope/pkg@x'). ⚠ Красныйlint:dedupeпосле бампа — не рутина, а вопрос «кого ещё подвинет dedupe». Читай выводpnpm dedupe --checkкак список пакетов, чьи транзитивы съедут, и сверяй его с перечнем сборщиков ПУБЛИКУЕМЫХ артефактов из CLAUDE.md § «Grouped Dependabot PRs». Общий движок (rolldown, esbuild, babel) означает, что dev-тул тянет за собой toolchain, доезжающий до потребителя, и тогдаpnpm dedupe— не «починка гейта», а решение: либо валидируй пересборкой dist с доказанно РАЗЛИЧАЮЩИМ сравнением, либо снимай проблему другим набором плагинов. (Прогон size-limit 14:@size-limit/rolldownпотребовалrolldown ^1.2.8, и dedupe увёл на негоtsdownиrolldown-plugin-dts— сборщики публикуемыхdistи.d.ts; пересборка дала 135 из 135 файлов побайтово идентичными, но узнать это можно было только замером.) -
Примени согласованные адаптации из Фаз 2–3: удали снятые воркэраунды, внедри полезные фичи, выполни шаги миграции.
-
Валидация: точечный
bundle/test2–3 ключевых пакетов, затронутых бампом (pnpm -F <pkg> bundle), — полныйpnpm buildНЕ гоняй сам, предложи пользователю (заведено: полный build у него). Если тул имеет ci-only строгость (напр.failOnWarn: "ci-only"в tsdown-конфиге, или иной гейт, активный лишь приCI) — гоняй точечную валидацию сCI=1 pnpm -F <pkg> bundle, иначе локальный прогон слеп к новым варнингам и CI покраснеет уже ПОСЛЕ пуша.Класс «влияние через результат прогона». Для тулов, чей эффект — не «трогает ли код», а НАБОР находок / результат прогона, версия валидируется прогоном, а не рассуждением:
⚠ Если тул хеширует ВХОДЫ репозитория (lockfile, манифесты), твой бамп сам портит вход, и «сдвинулось всё» — это ожидаемый ноль, а не находка. Прежде чем писать «новая версия инвалидировала кеш», разведи БИНАРЬ и ВХОД: поставь старую версию в отдельный каталог (
npm i <pkg>@<current>в scratch) и прогони её бинарь по УЖЕ обновлённому дереву. Совпал с новой — двигал вход, тул ни при чём; разошёлся — вот это изменение хеширования, и его цена в холодном кеше идёт в отчёт. (Прогон turbo 2.10.13: сдвинулись ВСЕ 1020 хешей задач, и прямое чтение было «мажор выбросил весь remote cache»; бинарь 2.10.12 по новому lockfile дал тот жеhashOfExternalDependenciesи те же 1020 задач — двигал мой собственный бамп.)- Сканеры-линтеры, гейтящие CI/pre-push (knip, jscpd, publint, attw, syncpack): влияние = дельта находок. Прогони на ТЕКУЩЕЙ версии (baseline) → забампь → прогони снова → сравни. Ужесточение детекции в patch/minor = новые находки = красный gate уже ПОСЛЕ пуша. Новые находки классифицируй: истинный latent gap (чинишь причину) vs новый false-positive (гасишь ignore-конфигом тула). ⚠ Если находки истинные, а их причина лежит в
packages/<public>/src/илиshared/*.ts— чинить их этим же коммитом НЕЛЬЗЯ:changeset-check.ymlтребует changeset, то есть нужна ветка + PR, а бамп по определению инфраструктурный. Тогда бамп откатывается (pnpm up -r <pkg>@<current>+git checkout pnpm-lock.yaml, иначе оставишь дерево с красным гейтом) и разбивается на два шага: задача на расчистку со списком находок из прогона НОВОЙ версии, потом бамп отдельно. Подавить их в конфиге тула — не вариант: это ровно то заглушение истинного gap'а, ради которого дельта и меряется. Ненулевую дельту доводи до версии-виновника бисекцией (pnpm up -r <pkg>@<ver>по промежуточным релизам, сравнивая счётчик находок): notes дают гипотезу, подтверждает бисекция. У knip 58 находок пришли в 6.28.0 от одной строки «Report unused re-exports whenignoreExportsUsedInFileis set», а заметный симлинк-фикс 6.29.0, на который указывала первая гипотеза, состав находок не изменил вовсе. ⚠ Exit code тула ≠ состояние гейта. Сверь его с тем, КАК тул вызывается в CI:|| true,continue-on-error,set +eозначают, что ненулевой код ничего не красит (у насsize-limitвозвращает 1 при превышении лимита, а job лишь печатает комментарий — гейта нет). И наоборот: baseline бывает уже красным ДО бампа, поэтому снимай не только код возврата, но и состав находок — иначе припишешь бампу чужой провал. ⚠ И зеркально: лгать может сам тул, а не обёртка. Ненулевой код — не единственная форма провала: тул умеет напечататьERROR: …в stderr и выйти с 0, а хук и CI читают только код. Если в notes есть строка про exit-код — померь её обоими армами на одном и том же искусственном сбое, а не поверь заголовку (knip 6.34 → 6.35.1: один и тот же падающийvitest.config.mtsдаёт идентичный stderr иexit=0противexit=2; до бампа сломанный конфиг плагина означал анализ БЕЗ этого плагина при зелёном гейте). И убедись, что сбой подложен на путь, который тул РЕАЛЬНО исполняет: у knipvitest.config.mtsзагружается и даёт exit 2, аsyncpack.config.mjsберётся как entry-файл (entry:…в--debug) — парсится, не исполняется, и на синтаксической ошибке по-прежнему выходит 0. Контроль не на том пути читается как «нас не касается». ⚠ И у машинного вывода смотри не только успешную форму, но и аварийную: тул печатает в тот же stdout объект ошибки вместо ожидаемого массива, и страховки потребителя вида{{out}}/|| '[]'не срабатывают — они подставляются на ПУСТОТЕ, а не на другом ТИПЕ. Урони конфиг намеренно и прогони на этом выводе тот самый парс, что стоит у потребителя. (У size-limit это{"error": …}в stdout приexit=1:{{output}}пропускал объект насквозь, и компаратор вci.ymlупал бы на.filter.) ⚠ У сканера бывает НЕ один потребитель, и нулевая дельта гейта не значит «влияния нет». Сверь КАЖДЫЙ канал, который проект реально читает — второй репортер, SARIF/JSON-выгрузку, аплоад во внешнюю систему, — и проверяй не факт загрузки, а её ПОСЛЕДСТВИЕ. У jscpdlint:duplicatesдал 8↔8 клонов с посимвольно одинаковым списком, а параллельный SARIF-канал грузилresults=8и создавал 0 алертов в code scanning: пути были относительны корню СКАНА (packages/<pkg>/src/), а не репозитория, и ни один не существовал в корне. Смотриcode-scanning/alerts?tool_name=…противcode-scanning/analyses, а заодно версию, которой тул представился (там стоял захардкоженныйjscpd/5.0.3при установленном 5.0.12). ⚠ И зеркальный вопрос: до КОГО тул не дотягивается вовсе. Дельта находок меряется по вызываемым каналам и слепа к пакетам, которых в них нет. Построй карту покрытия — какой член воркспейса получает тул и КАКИМ каналом (свой скрипт, встроенный вызов из бандлера, шаг CI), — и сверь её с тем, что конфиг утверждает о покрытии. У нас 20 пакетов получают publint встроенно из tsdown, один — своимlint:package, аangularиsvelteне получают ни publint, ни attw ничем, при том что комментарий вtsdown.base.tsобещает им «their own validation»; у angular при ручном прогоне лежал невидимый warning. - Форматтеры и нормализаторы (prettier, dprint, sort-package-json): влияние — это множество файлов, чей ВЫВОД различается между версиями, а не «падает ли гейт». Прогоняй обе версии через API по всем git-отслеживаемым файлам и сравнивай выход СТАРОЙ с выходом НОВОЙ, а не с текущим содержимым: сравнение с содержимым подмешивает унаследованный форматный долг. Отдельно установи, какие файлы вообще гейтятся: у нас prettier запускается только правилом
prettier/prettierи только для**/*.ts/**/*.tsx, так что расхождение в.mdгейт не краснит. ⚠ Контроль строй из РЕАЛЬНОГО расхождения, найденного замером: пример из changelog — минимальный вход, он теряет контекст и часто не воспроизводится (прогон prettier 3.9.8: две пробы по примерам из changelog дали «одинаково», и только настоящая строка репозитория разделила версии; всего разошёлся 1 файл из 5683). - Тест-фреймворки / property-либы (vitest, fast-check, @fast-check/vitest): влияние = «не вскрыл ли бамп падений». Для property-либ (стохастика) «before» на старой версии невоспроизводим (рандомный seed) — важен прогон after на 2–3 пакетах с разными профилями генераторов: perf-рефакторинг генератора может сдвинуть выдаваемые при seed значения и вскрыть латентный баг; зелёный прогон это исключает. Mutation-раннеры (Stryker) меряй на самом МЕЛКОМ пакете полным прогоном с
--force(иначе incremental переиспользует старые результаты), сравнивая мутантов / статусы / score. ⚠ Отчёт-файл — не доказательство свежести: упавший или частичный--mutate-прогон оставляет ПРЕДЫДУЩИЙ json, и сравнение покажет «изменений нет» (у нас так и вышло: 391 → 391 при том, что прогон вообще не стартовал). Сверяй по времени прогона в логе и exit code, а не по одному файлу. - Side-effect-тулы (sonar-сканеры с upload, publish/deploy-тулы — всё, что грузит в облако / ходит в сеть / блокируется до вердикта): НЕ гоняй реальный запуск ради проверки версии — замусоришь внешнюю систему или упрёшься в токен. Валидируй инспекцией артефактов:
binв package.json (сохранён ли наш алиас),--version,--help, dry-run-флаг. - Генераторы артефактов и внешних транзакций (changelog-генераторы, кодогены по API, публикаторы): эффект — не набор находок, а произведённый артефакт, и сеть в нём обязательна, так что инспекции мало, а боевой прогон запрещён пунктом выше. Третий путь: изолированная песочница + локальный дублёр эндпоинта — HTTP-стаб вместо API (у changesets переопределяется
GITHUB_GRAPHQL_URL), verdaccio вместо реестра. Собери минимальный воркспейс, прогони тул на СТАРОЙ и НОВОЙ версии, сравни артефакт побайтово, нормализовав нестабильные поля (SHA, даты). ⚠ Сравнивай ровно ТОТ поток, который читает потребитель, и не смешивай потоки: машинный вывод идёт в stdout, а баннеры и прогресс — в stderr, так что диагностический2>&1ломает парс и выглядит как поломка формата. У turbo--dry=jsonпечатает• turbo 2.10.12в stderr: под2>&1парс упал, и это едва не стало выводом «стриминг сломал JSON», хотяbuild-matrix.mjsчитает только stdout и ничего не заметил. Сначала посмотри, КАК потребитель читает вывод, потом воспроизводи это. ⚠ Для артефактов с ФОРМАТОМ (.d.ts, бандлы с хеш-именами чанков) побайтовое сравнение даёт ложную тревогу: генератор вправе переписать всё, не меняя контракта —rolldown-plugin-dts0.28 переписал все 182 файла core, перейдя с финальногоexport { … }на инлайновыеexport interface. Сравнивай СЕМАНТИКУ поверх байтов: набор экспортируемых имён для.d.ts, набор внешних спецификаторов для бандлов, размеры черезsize-limit. Байтовая идентичность — приятный, но не необходимый исход. ⚠ Репетиция на упрощённом входе бывает вакуумной: уchangeset publishнабор из одних приватных пакетов печатал в stdout все теги — а при реальной публикации теги опубликованных пакетов в stdout не попадают вовсе, и парсер, проверенный такой репетицией, потерял бы весь релиз. Воспроизводи ту форму входа, которая есть в проде. ⚠ И «форма из прода» — это НЕ модальный экземпляр, когда их в репозитории несколько. Пересчитай структурно различные экземпляры предмета проверки и посади в песочницу МИНОРИТАРНУЮ форму тоже: исключение живёт именно в ней, а в манифесте она часто неотличима от модальной. Дальше меняй по одной оси за раз — совпадение предсказания песочницы с реальным репо на ВСЕХ экземплярах и есть доказательство, что разделитель найден верный, а не совпал. (Прогон @changesets/cli: 21 пакет из 22 вёл себя одинаково, а ответ про механизм дал единственный двадцать второй — разделителем оказалось реброdevDependencies, а не значение диапазона, которое я проверял первым.) - Пакеты ВНЕ зоны гейтов (
examples/**,benchmarks/): дельта находок здесь структурно равна нулю, и не потому, что влияния нет, а потому, что туда не ходит ни одна turbo-задача — knip ихignoreWorkspaces, уbenchmarks/нетtype-checkпо решению, а примеры не в графе. Прогон гейтов после такого бампа ничего не измеряет. Валидация — ручная сборка затронутого артефакта и его дифф: собери до и после, сравни хеш и размер (приём, которым закрылся #2277: одна и та же сумма до/после доказала, что опция не читалась). ⚠ И там же докажи, что сравнение РАЗЛИЧАЕТ — правка исходника обязана сдвинуть хеш; без этого «идентично» значит «я сравнил кеш». Плюс out-of-band шаги, которых не назовёт ни PR, ни CI: playwright — пере-сидинг пиннутого Chromium на self-hosted раннере; двигает ли бамп числа CodSpeed, отвечаетscripts/codspeed-gate.mjs, а не ручной список: прогони его на коммите-пробе без ветки (GIT_INDEX_FILE=<tmp> git read-tree HEAD, тем же индексомgit add -u, затемgit commit-tree $(GIT_INDEX_FILE=<tmp> git write-tree) -p HEADи--base HEAD --head <sha>) вместе с ПОЗИТИВНЫМ контролем — диапазоном, который обязан датьRUN(коммит бампа, двигавшего измеряемые зависимости:git log --format="%H %s" | grep "bump jsdom"). Параskip/skipне различает ничего: тот же результат дал бы сломанный гейт (прогон rollup-plugin-dts 6.5.1 — там контроль по этой формулировке и оказался вакуумным) (прогон jsdom 30.1.0:RUN, «measured dependencies moved», через импорт вbenchmarks/adapter-bench— пакет, которого не было ни в одном списке); четыре сборщика (ng-packagr,rollup,rollup-plugin-dts,@sveltejs/vite-plugin-svelte) производят ПУБЛИКУЕМЫЕ артефакты — дефект там доезжает до потребителя (#2155 измерил, сколько он живёт незамеченным). Полный список ловушек — CLAUDE.md § «Grouped Dependabot PRs». - Загрузчики и рантайм-хуки (
tsx,--import-хуки,ts-node, кастомные resolver'ы): эффект — не набор находок и не артефакт, а «попадает ли резолюция туда же, куда попадала». Прогона мало: одинаковое число задач/тестов получится и при МОЛЧАЛИВОМ откате на другой источник (distвместоsrc), потому что оба варианта рабочие. Валидация = тот же набор задач плюс проба резолюции с КОНТРОЛЕМ: напечатайrequire.resolve/import.meta.resolveцелевого пакета под точной формой запуска из CI и ещё раз БЕЗ ключевого флага — пути обязаны разойтись. Совпали — проба ничего не измерила. (Прогон tsx 4.23.13: четыре релиза диапазона правили резолюцию; 78 задач до и 78 после ничего не доказывали, а проба далаsrc/index.tsпод--conditionsпротивdist/cjs/index.jsбез него.)
- Сканеры-линтеры, гейтящие CI/pre-push (knip, jscpd, publint, attw, syncpack): влияние = дельта находок. Прогони на ТЕКУЩЕЙ версии (baseline) → забампь → прогони снова → сравни. Ужесточение детекции в patch/minor = новые находки = красный gate уже ПОСЛЕ пуша. Новые находки классифицируй: истинный latent gap (чинишь причину) vs новый false-positive (гасишь ignore-конфигом тула). ⚠ Если находки истинные, а их причина лежит в
Фаза 5 — Доставка (инфраструктурная)
- Changeset не нужен —
changeset-check.ymlтребует его только при измененииsrc/публичного пакета (/changesetсам это распознает). - Если бамп потребовал doc-sync (
IMPLEMENTATION_NOTES.md/CLAUDE.md— по CLAUDE.md он обязателен для любого инфра-изменения), прогониnpx prettier --checkна изменённых.md. Форматируй только те файлы, что были чистыми ДО правки — сверь с базовой версией (git show HEAD:<file> > /tmp/base.md && npx prettier --check /tmp/base.md), иначе притащишь в свой коммит чужой форматный долг. - Вызови
/commit-msg(тип обычноchore(deps): ..., для тулинга —ci(deps): .../build(deps): ...). Доставка — через PR, без changeset. НЕ коммить и НЕ пуш без явной просьбы — только предложи сообщение.
Если сработал предохранитель из Фазы 0.7 (доставляемое изменение — пока не случалось): changeset обязателен (
/changeset, обычноpatch; в pre-1.0 breaking →minor, сослаться на задачу из Фазы 4) →/commit-msgв ветку задачи → доставка только через/create-pr(Closes #N). Прямой коммит вmasterзапрещён.
Итоговый отчёт
- Зависимость:
current → target(сколько релизов в диапазоне). - Таблица по релизам: версия → ключевые изменения → тип → влияние на проект.
- Воркэраунды, которые можно снять (файл + что удалить).
- Фичи, достойные внедрения (где и зачем) — и явно: что внедрять НЕ стоит.
- План миграции (если мажор/breaking).
- Итоговая рекомендация: бампать сейчас / поэтапно / отложить — почему.
- Результат валидации (точечные
bundle/test, если применяли; полныйpnpm build— за пользователем). - Доставка: инфра → PR в
master, без changeset (если сработал предохранитель — номер задачи, имя ветки, статус changeset, URL PR). - Предложенный commit message.
Важно:
- Гейт корневого
package.jsonрешает ДОСТАВКУ, не анализ: нет в корне → разбор всё равно делается (Фазы 1–3), а применять можно, если пакет не лежит вpeerDependenciesпубличного пакета (Фаза 0.2). - Eslint-экосистема →
/bump-eslint, не этот скилл (см. диспетчер вверху). - Бамп всегда инфраструктурный → PR без changeset (гейт корня это гарантирует: корень devDeps-only, публичные пакеты без внешних runtime-deps). Предохранитель: если бампаемый пакет окажется в
dependencies/peerDependenciesпубличного пакета — тогда никогда не в master напрямую, а задача + ветка + changeset + PR. - Не внедряй фичи без доказанного ROI для текущего кода.
- Не применяй объёмную миграцию без согласования.
- НЕ коммить и НЕ пуш без явной просьбы пользователя.
Примеры:
/bump-dep knip
/bump-dep tsdown@0.23.0
/bump-dep turbo
Самокоррекция скила (ОБЯЗАТЕЛЬНО, в конце КАЖДОГО прогона)
Этот файл — живой; каждый прогон обязан его затачивать. После отчёта (анализ релизов + решение о внедрении/миграции) вынеси короткую секцию «Самокоррекция» — критику не своей работы, а САМОГО ЭТОГО СКИЛА: где его текст промолчал, соврал, был двусмыслен или избыточен — и это стоило времени, лишнего шага или риска ошибки.
- Привязка к ИНЦИДЕНТУ, не общие советы. Каждая правка — из конкретного момента ЭТОГО прогона: «скил сказал X / промолчал про X → реальность Y → я потерял/чуть не ошибся на Z». Нет инцидента — нет правки (не выдумывай улучшения впрок).
- Высокая планка: изменило бы исход? Предлагай, только если правка реально предотвратила бы ошибку, сократила перебор или сняла двусмысленность, на которой ты споткнулся. Косметику и «было бы неплохо» — отбрасывай.
- Разовое ≠ паттерн. Специфику конкретной зависимости (разовую) → в задачу/память, НЕ в скил. В скил идёт только повторяющийся класс, полезный СЛЕДУЮЩЕМУ бампу ДРУГОЙ зависимости.
- Сначала заточи, потом дописывай (анти-раздувание). Предпочитай усиление существующего абзаца новой секции; если правка делает прежний текст избыточным — консолидируй, не дублируй.
- Предлагай, не применяй молча. Выведи кандидатов таблицей (раздел | инцидент | предлагаемый текст), спроси разрешения. Применил по согласию →
.claude/commands/это infra: changeset не нужен, PR нужен (апрувов ноль, CLAUDE.md). - Честность про «нечего править». Прогон прошёл чисто и скил нигде не подвёл → так и скажи: «правок нет». Пустая самокоррекция честнее придуманной — пункты 1–2 это допускают.