dependency-upgrade · git:20260730.0bf7183 · 2026-07-30 · sha256 0deeba48da8560b1
dependency-upgrade git:20260730.0bf7183A
Immutable. This exact content is served forever at /api/v1/blob/0deeba48da8560b1.
---
name: dependency-upgrade
description: |
Bring dependencies current without breaking the build: find what is vulnerable, deprecated or behind,
classify each target version by risk, apply what is safe, verify, and roll back what is not.
---
# Dependency Upgrade
<!-- routing-eval reads this line; it lives in the BODY so the always-on skill LISTING stays inside
Claude Code's budget (1% of the context window) — an overflowing listing gets descriptions
truncated or dropped, which strips the very keywords a match depends on. -->
Trigger phrases: "upgrade dependencies", "outdated packages", "bump versions", "update packages", "keep dependencies current"
## When
Staying on current versions as an ongoing practice, or reacting to a CVE. [[dependency-audit]] tells you what is
wrong; this decides what to do about it and does it. It **mutates the manifest and the lockfile**, so it carries
the same shape as [[db-migration]]: detect the tool, classify by risk, gate the dangerous class behind approval,
apply, verify, roll back on red.
## 1 — Ask the three separate questions
They are three different problems with three different answers, and the tooling keeps them separate too.
| Question | Node | .NET | Python |
|---|---|---|---|
| What has a **known CVE**? | `npm audit --json` | `dotnet list package --vulnerable` | `pip-audit` |
| What is **deprecated**? | `npm view <pkg> deprecated` | `dotnet list package --deprecated` | registry / project notes |
| What is simply **behind**? | `npm outdated --json` | `dotnet list package --outdated` | `pip list --outdated` |
> The three `dotnet list package` flags **cannot be combined** — it is three separate runs, not one.
**Deprecated is not an upgrade.** No version bump fixes it; the package needs a replacement, which is a design
change with its own review — never fold it into a routine bump.
## 2 — Classify every target version before touching anything
The risk is not "how old is it", it is "how far does the jump go".
| Class | Rule | Approval |
|---|---|---|
| **Security patch** | fixes a known CVE, within the current major | Do it first, on its own commit — it is the one upgrade whose delay has a cost |
| **Patch** (`x.y.Z`) | bugfix only | Apply, batched |
| **Minor** (`x.Y.z`) | additive, backward compatible **by promise** | Apply, batched, only if the suite is green afterwards |
| **Major** (`X.y.z`) | breaking by definition | **Never automatic.** Read the changelog/migration guide, one package per commit, and say what breaks |
| **Pinned / transitive-only** | pinned deliberately, or not a direct dependency | Leave it. Find out *why* it is pinned before unpinning |
Semver is a promise, not a guarantee: a minor that breaks you is a bug in the package, and your suite is the
only thing that will tell you. That is why the verify step below is not optional.
## 3 — Apply
- **Group by class, one commit per group** — a patch batch and a major are not the same change and must not
share a commit. A single commit mixing forty bumps is unreviewable and unrevertable.
- **Preview first.** `npm audit fix --dry-run` before `npm audit fix`. Never reach for `npm audit fix --force`
without saying so out loud: it installs **semver-major** upgrades, which is precisely how this task breaks a
build while claiming to be a security fix.
- **The lockfile is part of the change.** Commit it with the manifest; a manifest bump without its lockfile is
a change nobody else reproduces ([[dependency-audit]] axis 5).
- Never hand-edit a lockfile. Let the tool regenerate it.
## 4 — Verify, then decide
Nothing counts as upgraded until it is observed working — install, build, full test suite, and the project's
quality gate. Run the app's real entry path if the change touches runtime, not just unit tests ([[testing]]).
- Green → keep it.
- Red → **roll back this group** (`git checkout -- <manifest> <lockfile>` then reinstall), then either pin, split
the batch to find the culprit, or move that package to its own major-upgrade task. Do not "fix forward" a
routine bump; that turns maintenance into an unplanned feature.
- Re-run [[dependency-audit]] afterwards: an upgrade can introduce a new transitive CVE.
## 5 — Continuous, without an autonomous loop
"Always current" is a **cadence**, not an agent that runs by itself — the kit has no timed or self-triggering
loops, because every mutation here needs an approval a loop cannot give. Continuity comes from somewhere that
already has a human gate at the end:
- A scheduled CI job running the step-1 commands and failing on HIGH/CRITICAL ([[ci-pipeline]]).
- Or a bot (Renovate / Dependabot) opening one PR per group — the kit does not reimplement it; the value added
is the policy above deciding which PRs merge on green and which need a human to read a changelog.
- Either way the PR passes through the ordinary gates: build, tests, [[security-scan]], review, approval.
## DoD
- Vulnerable, deprecated and outdated were asked as three separate questions, with the command output shown.
- Every applied upgrade is classed patch / minor / major, and no major went in automatically or unexplained.
- Security fixes landed first and separately.
- Manifest and lockfile moved together; no lockfile was hand-edited.
- Build + full suite were observed green after the change, or the group was rolled back and named.
- Deprecated packages are reported as replacement work, not silently bumped.