Skip to content

Priorities in uptide list

uptide list opens with PRIORITIES: what to upgrade first, why, and the command to start. Every rule is deterministic, runs in the same pass as discovery and calls no LLM. The rules live in one module, packages/core/src/list/priorities.ts, with a unit test per signal.

Each outdated direct dependency gets these signals (JSON: packages[].signals).

SignalWhenSource
securitya known advisory covers the installed versionone POST to npm’s /-/npm/v1/security/advisories/bulk for every public package; packages from a private registry or scope are never sent; off with --no-advisories or "advisories": false in uptide.config.json
deprecatedthe registry marks the installed version deprecatedthe abbreviated packument discovery already fetches
unsupporteda newer major exists and the installed major line’s last release is 12 months old or morethe registry’s time map, fetched only for packages with a newer major
driftworkspaces declare different majors of it (5.x and 7.x): aligning them is a cheap consolidation winlockfile versions
blockingits installed peer range holds back another outdated package (blocks react 19)installed manifests
behindtwo or more majors behind (one is the normal state of an outdated package)versions
effortfiles to touch + call sites / 10, halved by a verified migration packthe usage scan

The advisory request and the publish dates each have a 5-second deadline and never retry. If the advisory request fails, times out, is turned off or every package is private, the PRIORITIES heading says “advisories not checked” with the reason, and the run goes on; nothing is guessed.

Each package is runtime when a workspace declares it in dependencies or optionalDependencies (or it is the peer of a package that is), and it is not tooling; it is dev when it is only in devDependencies, or is tooling (a test runner, a linter, a build tool). JSON: packages[].kind.

A dev package’s advisory drops one severity step, and at equal urgency the runtime package comes first. So:

runtime critical > runtime high ≥ dev critical > runtime moderate ≥ dev high > …

A high advisory in a runtime dependency outranks a critical one in a dev-only test runner: both rank 5.3, and runtime wins the tie. A dev critical still outranks a runtime moderate. The reason of a dev row starts with dev · , for every signal: dev · 2 advisories (1 critical), fixed in 1.2.6 (patch, same major).

For a security row, the fix is the lowest stable version above the installed one that none of its advisories covers, and the reason says what moving there is:

  • fixed in 3.2.5 (patch, same major) or fixed in 4.18.0 (minor, same major);
  • needs 4.1.11 (major) when every version of the installed major is affected;
  • no fixed version yet when no published version is clean.

At equal urgency (and the same runtime/dev side), a same-major fix ranks above a major-only one: it is the cheaper way out. The row’s command checks that version, not the latest: uptide check moment --target moment@2.29.4.

A package is ranked by its most urgent signal:

UrgencySignalReason, as printed
5 (+0.4 critical, +0.3 high, +0.2 moderate, +0.1 low; −0.1 for dev)security2 advisories (1 high), fixed in 3.1.2 (patch, same major)
4deprecateddeprecated: <registry message, truncated to 60 characters>
3unsupported4.x line unsupported since 2025-03, 37 files to touch
2.5driftversion drift: 5.x and 7.x across 3 workspaces
2blockingblocks ai 7
1behind3 majors behind, 2 files to touch

PRIORITIES shows two tiers (JSON: priorities[].tier). Urgent is security and deprecated, with a count and up to five rows (all with --all). Worth planning is everything else, collapsed to its count until --all.

A package installed at several versions across workspaces is one row: its advisories are those of any installed version, and its smallest fix is the first clean version above all of them.

The dev step is applied before ranking (a dev critical ranks 5.3). Among equal urgencies: runtime before dev, then a same-major fix before a major-only one, then the cheaper upgrade (lower effort).

A group is one row, named after the group, with the command uptide check --group <id>. It is ranked by its most urgent member (then the one furthest behind, then the cheapest), and costed as the sum of its members’ effort. Moving with, or holding back, another member of the same group does not make the group urgent: upgrading the group together is what it already says.

When nothing is urgent, list says so and suggests one PR of minor and patch upgrades with no urgent signal that touch at most 3 files each (possibly-unused packages excluded). A group joins the batch only when all its members qualify. Next points to the top priority, or to the cheap batch when nothing is urgent.

The terminal shows at most 5 rows (--all shows every one). The HTML report shows all of them at the top with the same reasons, and its Priority tile filters the table to their packages. JSON has priorities, cheapBatch and advisories (checked with the package count, or not checked with the reason).