Skip to content
lawbook
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Last updated on October 10, 2026

Was this page helpful?