Offline · read-only · CI-ready

Catch toolchain drift before you push.

lockstep cross-checks every place a Node, Bun, or package-manager version is declared — package.json, .nvmrc, asdf and mise files, the committed lockfile, and every GitHub Actions workflow — then reports the conflicts in one pass.

bunx github:srivtx/lockstep#main

Runs with Bun, with no install step. It is not on npm; bun add -g github:srivtx/lockstep installs the lockstep binary instead.

lockstep fixtures/drifted
$ lockstep fixtures/driftedfixtures/drifted  errors:2  warnings:2  info:1node  .github/workflows/ci.yml:setup-node  20node  .node-version  18.19.0node  .nvmrc  v20.11.0node  engines.node  >=18packageManager  packageManager  pnpm@9.12.0ERROR  LOCK-001  package.json:engines.node  Node version sources disagree across majors: Node 18 (.node-version=18.19.0, engines.node=>=18); Node 20 (.github/workflows/ci.yml:setup-node=20, .nvmrc=v20.11.0).ERROR  LOCK-002  package.json:packageManager  packageManager declares "pnpm" but the committed lockfile(s) belong to npm (package-lock.json).WARNING  LOCK-004  package-lock.json  A lockfile is committed but no CI install step is frozen (use npm ci, --frozen-lockfile, or --immutable).WARNING  LOCK-006  package.json:packageManager  packageManager declares "pnpm" but no workflow enables corepack or pins the package-manager version.INFO  LOCK-007  package.json:engines.node  engines.node (>=18) does not match the pinned CI Node version(s) 20.

What it checks

Every source that pins a runtime or a package manager, read directly from the project. Missing files are simply skipped, and nothing is ever written back.

engines
The engines.node and engines.bun ranges in package.json — the versions the project claims to support.
packageManager
The manager and exact version Corepack reads from package.json, such as pnpm@9.12.0.
volta
The volta.node and volta.bun pins used by the Volta toolchain.
.nvmrc
The Node version nvm installs for the directory.
.node-version
The Node version read by nodenv and asdf-compatible tools.
.tool-versions
Per-tool version pins for asdf.
mise.toml
Node and Bun versions declared for mise.
Lockfiles
package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock, and bun.lockb, which reveal the manager actually in use.
GitHub Actions
Every .github/workflows/*.yml, including setup-node, setup-bun, and pnpm/action-setup pins and whether installs are frozen.

Quick start

Point it at one project. The summary comes first, then each declared source, then the findings. The exit code is the gate.

quick start
# run it without installing$ bunx github:srivtx/lockstep#main# audit another project$ lockstep --dir path/to/project# machine-readable output, and fail CI on warnings$ lockstep --json --fail-on warning

Every flag, the exit-code scheme, and JSON output are in Usage.

Rule explorer

Ten stable codes. Each finding carries one, a severity, and the file it points at.

LOCK-001
error Conflicting Node versions across sources.
LOCK-002
error packageManager conflicts with the committed lockfile.
LOCK-003
error Multiple lockfiles from different managers.
LOCK-004
warning Lockfile committed but CI does not install frozen.
LOCK-005
warning No lockfile committed.
LOCK-006
warning packageManager declared but CI never enables corepack.
LOCK-007
info engines.node range does not include the CI-pinned Node.
LOCK-008
info .nvmrc and .node-version differ only cosmetically.
LOCK-009
warning CI bun-version major disagrees with engines.bun.
LOCK-010
info No GitHub Actions workflow found so CI cannot be verified.

How each rule works, including the exact conditions that trigger it.

Documentation

The landing page is the pitch. The details live on four short pages.

Rules
The ten checks, the sources they read, and how a run is assembled.
Usage
Every CLI flag, the exit-code scheme, JSON output, and the library API.
CI
SARIF 2.1.0 and GitHub code scanning, with a workflow you can paste.
FAQ
Scope, limitations, and how lockstep compares to corepack, Volta, and Renovate.