Reference

Usage

One project per run. Pass it the directory to audit and lockstep reads the local files, prints a report, and exits with a code CI can gate on.

Install

install
# one-line install (installs the `lockstep` binary)$ curl -fsSL https://raw.githubusercontent.com/srivtx/lockstep/main/install.sh | sh# run once, without installing$ bunx github:srivtx/lockstep#main# add as a project dev dependency$ bun add -d github:srivtx/lockstep

Command-line flags

Every flag lockstep accepts.
FlagDescription
lockstep [dir]Audit the project in dir. Defaults to the current directory.
--dir <path>Audit the project rooted at path. Equivalent to the positional form.
--jsonPrint one machine-readable JSON object to stdout with a stable field order.
--quiet, -qPrint only the summary line.
--sarif <path>Write a SARIF 2.1.0 report for GitHub code scanning.
--fail-on <level>error (default), warning, info, or none — the exit-code threshold.
--End option parsing; treat the remaining argument as the target directory.
-h, --help / -v, --versionPrint help or the version and exit.

Every option that takes a value accepts both --flag value and --flag=value. lockstep audits one project at a time; passing more than one path is a usage error.

Examples

examples
# audit the current directory$ lockstep# audit another project$ lockstep path/to/project# machine-readable output$ lockstep --json# summary only$ lockstep --quiet# SARIF for GitHub code scanning, fail on warnings too$ lockstep --sarif lockstep.sarif --fail-on warning

Exit codes

The exit code is the CI gate.
CodeMeaning
0No findings at or above --fail-on.
1At least one finding at or above --fail-on.
2Invalid usage or unreadable input, including a malformed workflow.
3I/O error: a report could not be written.

--fail-on affects only the exit code. Every finding is still reported and included in the JSON and SARIF output.

Text output

Without flags, lockstep prints the summary, then each declared source, then the findings. The drifted fixture below is the exact shape of a failing run.

fixtures/drifted  errors:2  warnings:2  info:1
node  .github/workflows/ci.yml:setup-node  20
node  .node-version  18.19.0
node  .nvmrc  v20.11.0
node  engines.node  >=18
packageManager  packageManager  pnpm@9.12.0
ERROR  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).

JSON output

With --json, lockstep prints one object. Field order is stable, so a diff between two runs means a real change. The clean fixture produces no issues; this is a drifted project trimmed to one source and one finding.

{
  "file": "fixtures/drifted",
  "sources": [
    {
      "source": ".node-version",
      "file": ".node-version",
      "value": "18.19.0",
      "kind": "node"
    }
  ],
  "issues": [
    {
      "code": "LOCK-001",
      "severity": "error",
      "message": "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).",
      "location": "package.json:engines.node",
      "file": "package.json"
    }
  ],
  "counts": { "error": 2, "warning": 2, "info": 1 }
}

Library

The CLI is a thin wrapper over the same functions. Import them directly when you are already in TypeScript.

import { auditProject, runRules, toSarif } from "lockstep";

const result = auditProject("path/to/project");
// { file, sources, issues, counts }

const sarif = toSarif(result, "lockstep", "0.1.0");