月曜の朝にリポジトリ群を開くと、依存ライブラリの更新通知が合計47件たまっていました。複数のアプリとサイトを並行で運用していると、これは珍しい数字ではありません。1件ずつリリースノートを確認すれば半日が消えます。かといって放置すれば、セキュリティ修正を取り逃したまま差分だけが膨らみ、いざ更新するときの危険度が静かに上がっていきます。
私自身、Firebase SDK のマイナー更新を「いま動いているから」と3ヶ月寝かせた結果、依存の連鎖で差分が膨らみ、週末をまるごと修復に費やしたことがあります。以来、依存更新は「ためてから一気に片づける仕事」ではなく「小さく流し続ける仕事」だと考えるようになりました。そして小さく流し続ける定型作業は、エージェントに移管する対象として最も筋の良い部類です。
Antigravity 2.0 で worktree サポートとスケジュール実行が整理され、この移管は一段と現実的になりました。共有したいのは、リスク階層・ロット分割・検証・巻き戻しという4つの部品で組んだ運用設計です。設定ファイルとスクリプトは、そのまま流用できる形で載せています。
依存更新は任せやすい。それでも全部は任せられない
依存更新がエージェント向きである理由は、3つに整理できます。第一に、手順の定型性が高いこと。第二に、成功判定を機械化しやすいこと。ビルド・型検査・テスト・スモークという判定器が最初から揃っています。第三に、頻度が高いこと。週に1度は必ずやってくる仕事は、自動化への投資を回収しやすい仕事でもあります。
一方で、失敗の重さは非対称です。50件の更新を無事に通しても誰も気に留めませんが、1件の事故はリリースを止めます。さらに「上げるかどうか」の判断には、コードの外にある事情が混ざります。年内に作り直す予定のモジュールなら更新を見送る、広告 SDK はポリシー改定と重なる時期を避ける、といった判断は、リポジトリの中の情報だけでは下せません。
そこで私はこの仕事を「提案・検証・記録」と「採否」に分け、前者をエージェントへ、後者をリスク階層に応じて自動または人間へ、と割り当てています。裁量を一律に与えるのではなく、変更の危険度に応じて段階を変える。以降の設計はすべて、この段階制を実装するための部品です。
semver を信頼しすぎない — リスク4階層の分類
semver は便利な約束ですが、額面通りには信じられません。patch を名乗る破壊的変更は実在しますし、0.x 系では minor が事実上の major です。ネイティブ SDK に至っては、バージョン表記が同じ規約に従っている保証すらありません。そこで、形式上のバージョン種別を出発点にしつつ、実績で補正する4階層を使っています。
Tier 0 — 自動マージ可 : 間接依存の patch 更新。lockfile の更新だけで完結するもの。セキュリティアドバイザリ起点の修正もここに含めます
Tier 1 — 自動マージ可(夜間検証つき) : 直接依存の patch。公開 API・型定義・ビルド設定に触れないもの
Tier 2 — エージェントが提案し、人間が承認 : 直接依存の minor。peer dependency の要求が動くものは必ずここへ
Tier 3 — 人間主導、エージェントは調査のみ : major、ネイティブ SDK、ビルドツールチェーン(Gradle・Xcode・バンドラ)、および 0.x 系のすべて
このうえで2つの補正を入れます。過去12ヶ月のあいだに patch / minor で破壊的変更を出した実績のあるパッケージは、無条件に1階層上げる。リリースノートを書かないパッケージも1階層上げる。「何が変わったかを説明しない変更」は、それだけで危険度が高いと見なします。
分類はリポジトリ直下の JSON に固定し、エージェントにはこのファイルを唯一の判断根拠として渡します。
{
"$comment" : "deps-policy.json — 依存更新のリスク階層定義" ,
"tiers" : {
"0" : { "scope" : "indirect-patch" , "action" : "auto-merge" },
"1" : { "scope" : "direct-patch" , "action" : "auto-merge-after-verify" },
"2" : { "scope" : "direct-minor" , "action" : "propose" },
"3" : { "scope" : "major | native-sdk | toolchain | 0.x" , "action" : "research-only" }
},
"escalations" : [
{ "rule" : "past-breaking-in-patch" , "window" : "12m" , "raise" : 1 },
{ "rule" : "empty-release-notes" , "raise" : 1 },
{ "rule" : "package-in" , "list" : [ "firebase" , "react-native" , "next" ], "minTier" : 2 }
],
"limits" : { "lotSize" : 5 , "maxParallelLots" : 3 }
}
要になるのは escalations です。世間的には安全とされるパッケージでも、自分の環境で一度でも痛い目を見たものは minTier で底上げしておきます。この分類表は運用の記憶そのものなので、事故のたびに育てていく前提で持ちます。
ロット分割と worktree 隔離 — まとめ方が事故率を決める
1更新につき1ブランチが理想ですが、週に数十件の規模になると現実的ではありません。かといって「たまった47件を1ブランチで」は最悪手です。どれが原因で落ちたのか切り分けられず、巻き戻しの単位も壊れます。
私はこの中間に「ロット」という単位を置いています。編成ルールは3つだけです。
同一 Tier の更新だけでロットを組む(巻き戻しの単位を揃えるため)
同一エコシステムで揃える(npm と CocoaPods を混ぜない)
1ロットの上限は5件。ただし同じ peer dependency を共有するパッケージは、あえて同じロットに同居させる
3つ目は経験から入れたルールです。同じ peer を持つ2つのパッケージを別々のロットで上げると、依存解決が行き来して lockfile が振動します。一緒に上げれば1回で収束します。
ロットごとの作業場所は、Antigravity 2.0 の worktree サポートで隔離します。エージェントごとに独立した worktree を割り当てれば、並列に走らせても作業ツリーが衝突しません。
# ロットごとに worktree を切り、エージェントを割り当てる
git worktree add ../app-deps-lot-01 -b deps/lot-01-tier0-npm
git worktree add ../app-deps-lot-02 -b deps/lot-02-tier1-npm
# Antigravity 側は各 worktree をプロジェクトとして開き、
# 「lot-01 を deps-policy.json に従って処理」とだけ指示する
worktree 運用の副次的な利点は、失敗したロットの破棄が「ディレクトリごと消す」だけで済むことです。途中状態が main の作業ツリーに残らないため、エージェントの試行錯誤を許容しやすくなります。
AGENTS.md に書く更新プレイブック — 指示の実物
エージェントへの指示は、毎回プロンプトに書くのではなく AGENTS.md に固定しています。依存更新セクションの実物がこちらです。
# 依存更新プレイブック
### 手順
1. deps-policy.json を読み、対象ロットの Tier と許可アクションを確認する
2. 更新前に、各パッケージの CHANGELOG / リリースノートを取得して読む
3. 更新を適用する。変更してよいのは package.json と lockfile のみ
4. scripts/verify-deps.sh を実行し、全ステップの結果を記録する
5. 結果に関わらず、更新サマリを reports/deps/ に Markdown で出力する
6. Tier 0-1 かつ検証全通過の場合のみコミットする(1ロット=1コミット)
7. それ以外は変更を保持したまま停止し、人間の判断を待つ
### 禁止事項
- メジャー更新・Tier 3 の適用(調査と報告のみ行う)
- package.json / lockfile 以外のファイル変更(コード修正が必要なら停止して報告)
- 検証ステップの省略・閾値の緩和
- CHANGELOG が取得できないパッケージの適用(停止して報告)
### 更新サマリの必須項目
- パッケージ名と旧→新バージョン
- 変更点の要約(3行以内)
- 自分のコードベースへの影響有無と、その根拠
- 検証結果(各ステップの所要時間つき)
効いているのは「CHANGELOG の要約を成果物に義務づける」の一点です。人間のレビューが「差分を読む時間」から「要約を確かめる時間」に変わり、1ロットあたり数分で判断できるようになります。逆に、ここを省くとレビューが結局ゼロからのやり直しになり、移管の意味が薄れます。
もう1つの急所は「コード修正が必要なら停止」です。更新に追従するためのコード変更まで裁量で許すと、変更の影響範囲が読めなくなります。コード修正を伴う更新は、それ自体が Tier 2 以上の仕事だと考えています。
検証パイプライン — ビルドが通っただけでは半分
検証はエージェントの裁量に任せず、固定のシェルスクリプトにしています。「テストを実行して確認してください」と頼むのと、決まったスクリプトの終了コードで判定するのとでは、結果の信頼性がまったく違います。
#!/usr/bin/env bash
# scripts/verify-deps.sh — 依存更新ロットの検証
set -euo pipefail
echo "[1/5] install"
pnpm install --no-frozen-lockfile
echo "[2/5] typecheck"
pnpm tsc --noEmit
echo "[3/5] unit tests"
pnpm vitest run --reporter=dot
echo "[4/5] build + bundle size"
pnpm next build
NEW_KB = $( du -sk .next/static | cut -f1 )
BASE_KB = $( cat .deps-baseline/bundle-kb 2> /dev/null || echo " $NEW_KB " )
LIMIT = $(( BASE_KB * 103 / 100 )) # 前回比 +3% で停止
if [ " $NEW_KB " -gt " $LIMIT " ]; then
echo "NG: bundle size ${ BASE_KB }KB -> ${ NEW_KB }KB (+3% 超過)"
exit 1
fi
echo "[5/5] smoke"
pnpm playwright test e2e/smoke --reporter=line
mkdir -p .deps-baseline && echo " $NEW_KB " > .deps-baseline/bundle-kb
echo "OK: all checks passed"
バンドルサイズの比較を入れているのは、テストでは捕まらない劣化の代表だからです。あるユーティリティライブラリの patch 更新が内部で polyfill を増やし、ビルドサイズが7%ほど膨らんだことがありました。テストは全通過でしたから、この閾値がなければ気づかないまま本番に出ていたはずです。
同じ理由で、スモークには「機能が動くか」だけでなく「出力が変わっていないか」の検証を1本入れています。日付ライブラリの patch がタイムゾーンの既定挙動を変え、テストは通るのに画面の日付だけが1日ずれた、という経験をしてからの追加です。私の環境では、この5ステップで1ロットあたりおよそ7分。3ロット並列でも、夜間の30分枠に収まります。
検証が落ちたときの動きも決めてあります。ロットを二分割して再実行し、原因のパッケージを特定したうえで、そのパッケージだけを「見送り」として記録する。bisect の発想ですが、上限5件のロットなら高々3回の分割で特定が終わります。
巻き戻しと更新台帳 — 戻せる単位で進める
更新の運用で最後に効くのは、巻き戻しの容易さです。1ロット=1コミットを守っていれば、問題が出たときの対応は git revert 1回で終わります。逆に、複数ロットや手動修正が1つのコミットに混ざった瞬間、巻き戻しは「考える仕事」に戻ってしまいます。
もう1つ、地味に効くのが更新台帳です。採用した更新だけでなく、見送った更新と巻き戻した更新も NDJSON で記録しています。
# reports/deps/ledger.ndjson への追記(エージェントの成果物の一部)
echo '{"date":"2026-06-12","lot":"lot-01","pkg":"date-fns","from":"4.1.0","to":"4.1.2","tier":1,"decision":"adopted","verify":"pass","note":"TZ既定値の変更なしを確認"}' >> reports/deps/ledger.ndjson
# 月次の集計は jq で1行
jq -s 'group_by(.decision) | map({decision: .[0].decision, count: length})' reports/deps/ledger.ndjson
見送りを記録する理由は単純で、記録がないと同じ更新を毎週調べ直すことになるからです。「この minor は peer の要求が動くので、次の Tier 2 枠で扱う」と一度書いておけば、次回のロット編成はその行を読むだけで済みます。台帳は人間のためのメモであると同時に、次回実行時のエージェントへの入力でもあります。
1ヶ月運用した実測と、まだ任せていないこと
この体制で1ヶ月運用した結果を、私の環境の数字として置いておきます。月間の更新通知はおよそ60件。うち Tier 0-1 が7割弱で、これらは夜間に自動処理されました。人間の作業は週1回のレビューで40分前後。以前は同じ仕事に半日を取られていたので、数字以上に「更新疲れ」が消えたことが一番の変化でした。巻き戻しは月に2件発生しましたが、どちらも revert 1コマンドで復旧しています。
コスト面では、Tier 0-1 の定型処理は既定の Flash 系モデルで足り、上位モデルは Tier 3 の影響調査だけに使っています。Antigravity 2.0 はクォータの見通しが立てにくいという声もありますが、低リスク帯に軽いモデル・調査に上位モデルという振り分けにしてからは、並列実行で上限に当たることはなくなりました。
一方で、いまも任せていない領域があります。major 更新の採否、ライセンス条項の変更を含む更新の最終判断、そしてネイティブアプリの権限やプライバシーマニフェストに触れる更新です。個人開発の現場では、この種の判断を誤ったときのリカバリーコストが事業に直接響きます。エージェントの調査レポートは判断を速くしてくれますが、判断そのものを渡す予定は当面ありません。
最初の一歩としては、deps-policy.json を自分のリポジトリ向けに書き、Tier 0 だけを2週間任せてみることをおすすめします。階層を広げるのは、台帳に無事故の実績が並んでからで遅くありません。同じように更新通知の山と向き合っている方の参考になれば幸いです。