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
# 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
| Flag | Description |
|---|---|
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. |
--json | Print one machine-readable JSON object to stdout with a stable field order. |
--quiet, -q | Print 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, --version | Print 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
# 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
| Code | Meaning |
|---|---|
0 | No findings at or above --fail-on. |
1 | At least one finding at or above --fail-on. |
2 | Invalid usage or unreadable input, including a malformed workflow. |
3 | I/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");