Apply or review multi-version support compliance for first-party Nx plugins. Primary entry point: a Linear task ID (NXC-XXXX) from the "Multi-version supported across plugins" milestone — the task carries the resolved support window, findings, and "Needs human decision" items. Falls back to self-discovery when no task exists. Use when asked to "fix multi-version compliance for @nx/X", "do NXC-XXXX", "review this compliance PR", or when working on a branch / PR titled "multi-version support compliance for @nx/X". Covers the canonical shape (assertSupportedPackageVersion, all-generators-enforce-floor.spec.ts, peer dep alignment, requires-gate auditing, user-pin preservation, executor / inferred-plugin feature gating).
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
The nx migrate --first-party-only flag lets users upgrade Nx without
dragging the managed third-party ecosystems (Angular, Cypress, Playwright,
Jest, Vitest, ESLint, etc.) along. For that to be safe, every first-party
plugin must keep working across its declared support window — not silently
fall through to the latest install constants on older workspaces, not
silently break on newer ones.
Source-of-truth split:
| Source | Owns |
|---|---|
| Linear milestone "Multi-version supported across plugins" (project NXC-4072) | What's wrong per plugin, the resolved support window, open human decisions. Per-plugin tasks NXC-4381..NXC-4410 (P1–P29). |
| This skill | How to implement the canonical shape, code-level anti-patterns, gotchas, findings doc shape (no-task case). |
The skill is the gap-closer: it accepts a Linear task, parses it, drives the fix. When no task exists for the plugin, fix mode runs discovery in Phase 1–2 and produces a findings doc that mirrors a Linear task body — so the user can file it as a new task before proceeding.
Reference PRs (the canonical shape):
#35587 — @nx/angular — merged. Set the precedent. Introduced
throwForUnsupportedVersion.#35642 — @nx/playwright — merged. Generalized the shared helpers
into @nx/devkit/internal. Established executor / runtime feature-
gating.#35670 — @nx/cypress — merged. Added excludeGenerators to the
parameterized test helper.#35671 — @nx/vitest — open at time of writing. Demonstrates
"drop phantom peer-range claim" and "declared floor < effective floor"
patterns.Before citing any PR by number, verify state — these go stale:
gh pr view <N> --repo nrwl/nx --json state. Verify any unmerged PR's
contents via gh pr diff <N> --repo nrwl/nx.
| Invocation | Mode | Behavior |
|---|---|---|
multi-version-compliance <NXC-XXXX> | Fix (primary) | Fetch task, surface findings + decisions in Phase 2, wait for user OK before Phase 3 edits. |
multi-version-compliance (no arg) | Ask for task ID | Prompt for NXC-XXXX. |
multi-version-compliance @nx/<plugin> (bare plugin) | Fix (task lookup) | Look up the per-plugin task in milestone NXC-4072. If found, confirm with user and enter fix mode. If not found, run discovery in Phase 1–2 (rubric against code), present findings, suggest filing as a new task before any edits. |
multi-version-compliance review #<N> | Review | Fetch PR, derive Linear task from branch name if possible, compare diff vs. task findings (or run pure code-level review if no task). |
Stop-after-Phase-2 (audit-equivalent): if you want findings without edits, decline to approve at the end of Phase 2. The skill stops, no branch, no commits.
On a branch matching nxc-NNNN with no explicit arg: before
asking the user, suggest "Use NXC-NNNN?" inferred from the branch name.
Before any code-level work in Linear-driven mode, the skill MUST:
mcp__linear-server__get_issue
isn't available (MCP server not installed / not connected), tell the
user and fall through to the no-task discovery path (fix mode Phase 1
step 2). Don't pretend to fetch.mcp__linear-server__get_issue id="NXC-XXXX".
If the call errors (invalid ID, network), halt and ask the user to
verify the ID.[multi-version][P##] \@nx/` — multi-version support compliance(per-plugin) or[multi-version][W#] ...` (cross-cutting). If the pattern doesn't match, halt and ask the user to confirm this is the right task.Done → ask whether re-audit or follow-up. Canceled → halt and ask.(high|medium|low) items with [file:line] and a suggested fix per item.mcp__linear-server__list_comments issueId="...".
Audits attached as files / linked uploads may carry additional
context.references/canonical-shape.md. The Linear task's
suggested fix is the authoritative scope; the skill verifies it
conforms to the canonical shape and flags any deviation.Default to the task's resolved support window. Don't re-derive it from code unless the user explicitly overrides. If the user overrides: restate the new window and confirm before applying.
Don't expand scope beyond the task's Findings without asking. If you spot a new issue mid-fix: stop, present it, ask whether to (a) add it to this PR, (b) defer as a follow-up, or (c) update the Linear task as a comment.
Phase 1 — Read.
nxc-NNNN, read recent commits to understand
prior scope decisions.references/canonical-shape.md and references/anti-patterns.md.Phase 2 — Align.
Phase 3 — Implement (per canonical-shape.md).
master if needed using the repo's nxc-NNNN convention.command: '<bin>') gets a peer entry. Default to
optional: true via peerDependenciesMeta for gated surfaces
(executor opt-in, inferred plugin gated on config file presence).
Non-optional peers are reserved for packages every workspace using
the plugin needs.keepExistingVersions,
fresh-install branch.requires gates per package per AND-semantics; split
mixed entries; retain intentional pre-floor entries. Default to
bilateral bounds (>=N <M) for cross-major packageJsonUpdates
windows. One-sided windows (<N with no lower, >=N with no
upper) need a justified reason (legacy cleanup, undefined source,
v0→v1 bridge) — record the reason in the findings doc or as a code
comment. Migration entries gate on the destination instead,
usually >=N alone (checklist below).astro-docs/, docs/, inline .md) that
describe the changed behavior. Docs that contradict the code are a
correctness bug, not a PR-body concern.Phase 4 — Tests (same commit as Phase 3 usually).
all-generators-enforce-floor.spec.ts — parameterized via
assertGeneratorsEnforceVersionFloor. This exercises every
generator's floor assert and is the high-value spec.assert-supported-<pkg>-version.spec.ts
with the 5 canonical cases. The shared assertSupportedPackageVersion
already has full coverage in devkit, so this is mostly symmetry
across the PR series — skip unless the user asks.Phase 5 — Verify locally.
npx nx test <plugin> --testPathPattern="all-generators-enforce-floor"
(add assert-supported- if you added the optional wrapper spec).npx nx test <plugin> --testPathPattern="<modified-generator>" per
touched generator.npx nx format.Phase 6 — Hand off. Code changes complete. The user drives
commit/push/PR per their own conventions (loaded globally from
~/.claude/memory/workflow/git/). This skill does not enforce PR title,
body, commit shape, or related-issues format.
gh pr view <N> --repo nrwl/nx and
gh pr diff <N> --repo nrwl/nx. For a local branch:
git diff master...HEAD.nxc-NNNN → NXC-NNNN. If
no match: ask the user.canonical-shape.md and anti-patterns.md.canonical-shape.md. Cross-reference
anti-patterns.md. For each finding, anchor at file:line and cite
which reference PR / file demonstrates the correct pattern.
Scope: code, configs, migrations, and in-codebase docs that claim
runtime behavior. NOT PR title / body / commit shape — those defer to
the user's PR conventions.[blocker] and [non-blocker]. No
"open question," "ask," or other inline tags. Questions for the
author surface in the closing "Open questions for author" block,
drawn from non-blocker findings — list each question once.version-utils.ts duplication), list them as sub-bullets under
the blocker with "(resolved when §X is fixed)" rather than as N
separate top-level non-blockers.file:line, followed by the structured verdict block from
canonical-shape.md §"Verdict template". The verdict block is the
skimmable index — produce it, don't substitute a free-form prose
summary. Do not post via gh pr review unless the user explicitly
asks.| Mode | Required | Optional |
|---|---|---|
| Fix | canonical-shape.md, anti-patterns.md | gotchas.md (effective floor, ecosystem lockstep, cypress inline tree), examples.md (when copying a pattern) |
| Review | anti-patterns.md, canonical-shape.md (especially the "Code-level verification" section) | gotchas.md (cross-plugin coordination, lockstep), examples.md (when citing) |
| "What is compliance?" answer | none | answer from SKILL.md alone |
References are ~100–500 lines each. Don't pull all of them just because you're invoked. Match the load to the mode.
@nx/devkit/internal (assertSupportedPackageVersion,
getInstalledPackageVersion, getDeclaredPackageVersion,
throwForUnsupportedVersion, normalizeSemver, isNonSemverDistTag)
and @nx/devkit/internal-testing-utils
(assertGeneratorsEnforceVersionFloor). Reject any local
re-implementation (cleanVersion, getInstalled<Pkg>VersionRuntime,
private throwBelowFloor, etc.). See canonical-shape.md.throwAboveWindow, warnAboveCeiling,
versions() with switch + throw default:. The only throw is below
the declared floor.keepExistingVersions: true is for generators only. Migration
generators (src/migrations/) are exempt — their job is to bump.
Do not flag missing flags in migration code.*Internal. Single-function generators (angular): in the function
itself. Not conditional, not inside an install branch, not after a
tree read.tmp/ (gitignored)
and stays uncommitted. No TRIAGE-REPORT.md / AUDIT.md at repo
root. Branch creation and edits start at Phase 3, after the user OK.pr-creation-shorthand.md,
push-conventions.md, explain-before-committing.md,
chore-not-fix-non-prod.md). Don't enforce or flag these from this
skill — defer to whatever the user's conventions resolve to at PR time.When fix mode hits the no-task case (Phase 1 step 2), produce
tmp/<plugin>-findings.md shaped to mirror a Linear task body so the
user can file it as a new task in milestone NXC-4072 before proceeding
to Phase 3.
For plugins managing multiple primary packages, repeat the install-map / decisions / findings bullets per primary.
# @nx/<plugin> — multi-version support compliance findings
> No Linear task in milestone NXC-4072. This doc is filing-ready —
> create the task with this body before proceeding to fix.
## Plugin
- Path: packages/<plugin>
- Upstream support: <official policy if any, else "no formal LTS">
- peerDep declarations: <list>
- Per-major install (`<file>` branches on installed `<package>` major):
- v<N-1>: <constants>
- v<N>: <constants> (default)
- Paired secondaries: <list of ecosystem-locked siblings>
## Needs human decision
1. <decision 1 — e.g., raise floor to vN.0.0 vs keep current>
2. <decision 2 — e.g., drop ^1.0.0 from peer (no v1 install lane)>
## Findings
- **(high) <one-line summary>** [file:line]
_Suggested fix_: <one-line>
- **(medium) ...**
- **(low) ...**
## Verification checklist (A–F)
### A. Support window declarations
- [ ] peerDep ranges match the support window
- [ ] Version map / runtime branching covers every supported major
- [ ] Every third-party package the plugin **invokes at runtime** has a peerDep entry. "Invokes" = TS import/`require` OR executor spawns its CLI binary OR inferred plugin emits a target whose `command` invokes its CLI (look for `externalDependencies: ['<pkg>']` in emitted target inputs). Packages the generator installs for the user to consume independently (ESLint plugins loaded by the user's eslintrc, `@types/*`) don't need peer-declaration.
- [ ] Peers needed only when a user opts into a specific surface (executor opt-in, inferred plugin gated on config file presence, opt-in preset) are declared **optional** via `peerDependenciesMeta: { "<pkg>": { "optional": true } }`. Required-non-optional peers are reserved for packages every workspace using the plugin needs.
### B. Generator inputs
- [ ] Generators don't overwrite installed third-party versions
- [ ] `addDependenciesToPackageJson` passes `keepExistingVersions=true` or branches on detected version
- [ ] Fresh-install path installs the latest supported version
### C. Generator outputs
- [ ] Templates compile and run on every supported version
- [ ] Generated `project.json` target shape valid on every major
- [ ] Default option values valid on every major
- [ ] Version map covers every managed third-party dep — no gaps
- [ ] Schema accepts union of options; runtime throws when inapplicable
### D. Migrations (migrations.json + packageJsonUpdates)
- [ ] Cross-major `packageJsonUpdates` declare `requires` per bumped package
- [ ] `packageJsonUpdates` `requires` windows are bilateral (`>=N <M`) by default. One-sided ranges (`<N` with no lower, `>=N` with no upper) are intentional (legacy cleanup, undefined source major, v0→v1 bridge) — flagged in "Needs human decision" or noted in the Findings.
- [ ] Migration entries gate on the destination: `requires` evaluates once at collection time against the version the package lands on in this run (installed only when the run does not bump it), so a bound meant as the source window (`>=9 <10` for "migrating from 9") skips whenever the run bumps past the cap (the storybook bug, #33613). Default is `>=N` alone; add an upper bound only when the migration is inapplicable at or above it (`next >=15.0.0 <16.0.0` on the next-15 instructions entry). Semantics: `.claude/skills/author-migration/SKILL.md`, `requires` section.
- [ ] A migration declares a gate only when its behavior depends on the touched package's version; conditions `requires` cannot express (an OR of alternative package names) get an in-body check instead
- [ ] Nx-only migrations have no third-party `requires`
- [ ] No silent gap in `packageJsonUpdates` across the support window
### E. Runtime
- [ ] Executors branch on installed version where behavior diverges
- [ ] Inferred plugin (createNodes/V2) parses configs across every major
### F. Out-of-window UX
- [ ] Below-floor: throws via shared util naming package + installed + floor; no silent fall-through
## Out-of-scope (deferred follow-ups)
- <e.g., consolidate ... across plugins — separate PR>See "Which references to load" near the top. Don't pull all of them.
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.