Commands
Every lawbook command and flag.
Running lawbook with no command prints the usage and exits 2.
lawbook --help and lawbook --version exit 0.
lawbook init [root]
Writes a starter lawbook.yaml into root (default: the current directory)
and prints its path. Fails with exit code 2 when lawbook.yaml,
lawbook.yml, or lawbook.json already exists there, or when root does not
exist.
lawbook check [root]
Runs the rules against root (default: the current directory) and prints one
line per rule followed by a summary.
| Flag | Meaning |
|---|---|
-c, --config <file> |
Read this config instead of looking for lawbook.yaml, lawbook.yml, or lawbook.json in root. Paths in the config stay relative to root. |
--format <format> |
text (default), json, github (workflow commands for GitHub Actions), sarif (SARIF 2.1.0 for code scanning), or gitlab (a GitLab code quality report). |
--only <ids...> |
Run only the rules with these ids, in config order. An unknown id is an error. |
--no-llm |
Report standard rules as skipped instead of asking a model. No provider client is built. |
--files <paths...> |
Check only these files. Paths are relative to the current directory, or absolute, like root itself. |
--changed |
Check only files changed in the working tree against HEAD: staged, unstaged, and untracked. |
--since <ref> |
Check only files committed since the merge base with ref, as git diff <ref>...HEAD lists them. |
--changed-lines |
Judge standard rules on the lines --changed or --since touched, with the rest of each file as reference, and drop findings on untouched lines. Needs one of those selectors. See LLM rules. |
--baseline <file> |
Hide the findings this file records; see Baseline. Relative to the current directory. |
--update-baseline |
Write this run’s findings to the --baseline file, then report with them hidden. |
--dry-run |
List the files each rule would check and how many model requests the run would make, then exit 0 without reading a file or building a client. Text or JSON only. |
--explain |
Print the model’s reason for files that pass a standard rule, not only for findings. JSON carries them as reasons by path, or reason for a set. |
--max-requests <n> |
Stop with exit code 2 before asking the model when the run would make more than n requests. Overrides llm.maxRequests. See LLM rules. |
--no-cache |
Ask the model for every file even when a cached verdict exists. See LLM rules. |
--cache-dir <dir> |
Where verdicts are cached. Relative to the current directory; default node_modules/.cache/lawbook under root. |
Checking a subset of files
--files, --changed, and --since each name candidate files; given
together, their union is used. A files rule then selects only the
candidates its globs match, so a candidate no rule covers, such as a lockfile
or an image, is simply not checked. exists and absent rules ignore the
candidates, though .gitignore still applies to them. A rule whose globs
select no candidate passes.
--changed and --since ask git, so root must lie inside a repository
and ref must resolve, else check exits 2 with git’s message. Paths git
reports are rebased onto root, so a root below the repository’s top
level works and changes outside it are dropped.
--changed-lines narrows what a standard rule judges within each selected
file, not only which files it sees: the model judges the lines the change
touched and reads the rest as reference, and a finding on an untouched line
is dropped and counted. It needs --changed or --since to say what
changed, else check exits 2. See Changed lines.
--files and --only take lists, so put root before them. The repository
defines pre-commit hooks that append the staged
file names; prek reads the same file. lawbook skips standard rules, and
lawbook-llm runs them too:
repos:
- repo: https://github.com/drew-simmons/lawbook
rev: v0.1.0
hooks:
- id: lawbook
pre-commit installs the hook with the Node.js it finds on the machine, so
add default_language_version: { node: system } or point language_version
at a Node.js 22.12 or newer. With lawbook already installed globally, a
local hook works too:
- repo: local
hooks:
- id: lawbook
name: lawbook
entry: lawbook check . --no-llm --files
language: system
A CI job on a pull request checks only what the branch changed:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- run: npx lawbook check --since origin/${{ github.base_ref }} --changed-lines
github.base_ref is set on pull_request events only; a job that also
runs on push needs another ref there. --changed-lines keeps a finding on
a line the pull request never touched from failing it.
Dry run
--dry-run shows what a run would look at before it looks. Each rule lists
the files its globs select, after ignore, exclude, .gitignore, and any
--files, --changed, or --since selector; exists and absent list
their paths and globs as written, since nothing is read. A standard rule
leaves out empty files, which it tells by size alone, since check never
sends them, and under --changed-lines the files with no changed lines. The last line counts the distinct files and the model
requests: one per listed file of a standard rule, one for a scope: set
rule with any files, or none under --no-llm. Nothing is read, so binary,
oversize, and whitespace-only files still appear, and no provider client is
built.
PLAN no-console (forbid, 2 files)
src/cli.ts
src/init.ts
PLAN has-readme (exists, 1 file)
README.md
PLAN errors-are-actionable (standard, 2 files)
src/cli.ts
src/init.ts
3 files, 2 model requests
--format json prints { "rules": [{ "id", "kind", "level", "files", "requests" }], "requests" }, with each rule’s own request count. The github, sarif, and gitlab formats have nothing to annotate
and are rejected with exit code 2.
Text output
PASS no-console
WARN spdx-header
src/cli.ts: does not match /^// SPDX-License-Identifier: /
FAIL has-readme
README.md: missing
1 passed, 1 failed, 1 warned, 0 errored, 0 skipped
Findings under a forbid rule read path:line: <the line>; under require,
path: does not match /pattern/, or the rule’s message in place of the
line or the pattern when it sets one; under exists and absent, path: missing
or path: exists; under standard, path: <the model's reason> (noul <probability>). A rule with level: warn prints WARN <id> instead of
FAIL and does not affect the exit code. Findings a baseline
hides are counted on one line, 2 findings in baseline, after the rule’s
findings. A standard rule the provider
could not finish prints ERROR <id>, with the model’s findings for the files
it did judge and a path: <error> line for each file it did not, and the run
exits 2 after the report. With --explain, a standard rule also prints
path: passed, <the model's reason> (noul <probability>) for each file that
passed, or one passed, <reason> line for a passing set, so a standard’s
wording can be tuned against what the model actually saw. A standard rule lists files it left out, such
as one over llm.maxBytes, as path: skipped, <why> after its findings;
those lines do not change the rule’s status. A skipped rule prints
SKIP <id> with no findings. The summary line always lists all five counts
in this order. When a standard rule ran, one more line says what the run
cost: 3 requests (1 cached), 4210 input tokens, 180 output tokens, where
requests went to the provider, cached verdicts came from the
verdict cache, and input tokens count everything
the model read, including what the provider served from its own cache.
JSON output
{
"results": [
{
"id": "spdx-header",
"kind": "require",
"level": "warn",
"status": "warn",
"findings": [{ "path": "src/cli.ts", "message": "does not match /^// SPDX-License-Identifier: /" }]
},
{
"id": "errors-are-actionable",
"kind": "standard",
"level": "error",
"status": "fail",
"findings": [
{
"path": "src/cli.ts",
"message": "\"unexpected state\" on line 41 says what broke but not what to do.",
"decision": { "type": "noul", "noul": 0.12 }
}
],
"decisions": {
"src/cli.ts": { "type": "noul", "noul": 0.12 },
"src/init.ts": { "type": "noul", "noul": 0.97 }
},
"usage": {
"inputTokens": 310,
"outputTokens": 48,
"cacheReadInputTokens": 1200,
"cacheCreationInputTokens": 400,
"requests": 2,
"cached": 0
}
}
],
"summary": {
"passed": 0,
"failed": 1,
"warned": 1,
"errored": 0,
"skipped": 0,
"usage": {
"inputTokens": 310,
"outputTokens": 48,
"cacheReadInputTokens": 1200,
"cacheCreationInputTokens": 400,
"requests": 2,
"cached": 0
}
}
}
level is the rule’s configured error or warn. baselined counts the
findings a baseline hid, and appears only when there were any.
status is pass,
fail, warn (findings under a warn rule), error (a standard rule the
provider could not finish; its findings without a decision name the files
and reasons), or skip (a standard rule under --no-llm). findings
carry path and, for forbid, a 1-based line.
A judged standard rule adds decisions, every judged file’s decision keyed
by path, and each of its findings carries its own decision. Under
--explain it also adds reasons, the model’s reason for each passing file
keyed by path; failing files already carry theirs in findings[].message. A scope: set rule carries
one decision for the set instead, and its finding has no path; under
--explain a passing set carries one reason. When it
left files out, skipped lists each as { "path", "message" }. Its usage, and
the summary’s, count requests sent to the provider, verdicts served
cached from lawbook’s cache, and the provider’s token counts, with
cacheReadInputTokens and cacheCreationInputTokens for its prompt cache. A decision
follows the Jev decision schema: { "type": "noul", "noul": <0..1> }, the
probability that the file meets the standard. See
LLM rules.
GitHub output
--format github prints one
workflow command
per finding, so a GitHub Actions job annotates the pull request inline, then
the summary line as a notice. Findings under a warn rule annotate as
warnings; every other finding, including a provider error, annotates as an
error. file= and line= appear when the finding has them, and title=
carries the rule id.
::error file=src/cli.ts,line=12,title=no-console::console.log("starting");
::warning file=src/cli.ts,title=spdx-header::does not match /^// SPDX-License-Identifier: /
::error file=README.md,title=has-readme::missing
::notice title=lawbook::0 passed, 2 failed, 1 warned, 0 errored, 0 skipped
- run: npx lawbook check --format github
SARIF output
--format sarif prints a SARIF 2.1.0
log with one run: the tool, one rule per lawbook rule (its description
as the short description, or the id when it has none), and one result per
finding. Paths are relative to the ROOT base, which is the checked
directory as a file URL. Upload it to GitHub code scanning:
- run: npx lawbook check --format sarif > lawbook.sarif
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: lawbook.sarif
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "lawbook",
"version": "0.1.0",
"informationUri": "https://drew-simmons.github.io/lawbook/",
"rules": [{ "id": "no-console", "shortDescription": { "text": "Production code logs through the logger" } }]
}
},
"originalUriBaseIds": { "ROOT": { "uri": "file:///home/me/project/" } },
"results": [
{
"ruleId": "no-console",
"level": "error",
"message": { "text": "console.log(\"starting\");" },
"locations": [
{
"physicalLocation": {
"artifactLocation": { "uri": "src/cli.ts", "uriBaseId": "ROOT" },
"region": { "startLine": 12 }
}
}
]
}
]
}
]
}
level is warning for findings under a warn rule and error otherwise.
A finding without a path, such as an exists rule with several patterns
or a scope: set finding, has no locations.
GitLab output
--format gitlab prints a
code quality report:
a JSON array with one issue per finding. GitLab shows the issues inline on a
merge request when the job saves the file as a codequality artifact:
lawbook:
script: npx lawbook check --format gitlab > gl-code-quality-report.json
artifacts:
when: always
reports:
codequality: gl-code-quality-report.json
[
{
"description": "console.log(\"starting\");",
"check_name": "no-console",
"fingerprint": "3f786850e387550fdab836ed7e6dc881de23001b4a0f3c7bd2e6a1c95f0e8a4d",
"severity": "major",
"location": { "path": "src/cli.ts", "lines": { "begin": 12 } }
}
]
severity is major for findings under a failing rule, minor under a
warn rule, and critical for a provider error. The fingerprint hashes
the rule id, the path, and the message but not the line, so a finding keeps
its identity when the code above it moves. A finding without a path, such as
an exists rule with several patterns or a scope: set finding, is
located at the config file, line 1.
lawbook test [root]
Judges each standard rule’s fixtures,
example files the standard must pass or must fail, and reports the ones the
model put on the wrong side of the rule’s threshold. Rules without
fixtures are left out.
| Flag | Meaning |
|---|---|
-c, --config <file> |
Read this config instead of looking for one in root. |
--format <format> |
text (default) or json. |
--only <ids...> |
Test only the rules with these ids. An unknown id is an error. |
--no-cache |
Ask the model for every fixture even when a cached verdict exists. |
--cache-dir <dir> |
Where verdicts are cached; the same default as check. |
Each fixture is judged alone, as a scope: file request, with the rule’s
context and llm settings, through the same verdict cache check uses.
Text output prints PASS <id> or FAIL <id> per rule, one line per
misclassified fixture, and a total:
FAIL errors-are-actionable
fixtures/errors/vague.ts: expected fail, judged pass (noul 0.72): The messages name the failing step.
4 fixtures, 1 misclassified
--format json prints { "rules": [{ "id", "cases": [{ "path", "expected", "actual", "decision", "reason" }] }], "summary": { "cases", "misclassified" } }
with every fixture, not only the misclassified ones.
The exit code is 1 when any fixture is misclassified, 2 when a fixture
is missing or binary or the provider fails, and 0 otherwise.
lawbook schema
Prints the JSON Schema for lawbook.yaml, drawn from the same definitions
check validates with, so it always matches the version installed. The
schema is also published at
https://raw.githubusercontent.com/drew-simmons/lawbook/main/lawbook.schema.json
for editors; see Configuration.