CLI Reference

August 4, 2026 · View on GitHub

For command-line flag documentation directly in your terminal, run blockwatch --help.

Quick Options Reference

  • List Blocks: blockwatch list outputs a JSON report of all discovered blocks.
  • Custom Extensions: Map custom file extensions: blockwatch -E cxx=cpp
  • Disable Validators: blockwatch -d check-ai
  • Enable Validators: blockwatch -e keep-sorted
  • Ignore Files: blockwatch --ignore "**/generated/**"
  • Report What Ran: blockwatch --verbosity summary (or full for JSON on stdout)

Selecting Files

By default, blockwatch scans all files in the current working directory, respecting .gitignore.

# Check everything in the repository
blockwatch

# Restrict checks to specific glob patterns
blockwatch "src/**/*.rs" "**/*.md"

# Exclude specific paths
blockwatch "**/*.rs" --ignore "**/generated/**"

Note: Quote glob patterns to prevent shell expansion before passing arguments to blockwatch.

Diff Validation

When given a unified diff via stdin, blockwatch limits validation to blocks modified by the diff. This keeps execution fast during pre-commit hooks and CI runs (see CI Integration).

# Validate unstaged changes
git diff --patch | blockwatch

# Validate staged changes
git diff --cached --patch | blockwatch

# Validate changes in a specific file
git diff --patch path/to/file | blockwatch

# Validate diff changes alongside explicit globs
git diff --patch | blockwatch "src/always_checked.rs" "**/*.md"

A block is validated if the diff overlaps its line range or start tag. To inspect which blocks a diff touches, use blockwatch list --diff.

Supported Diff Input

Each diff header names a file, and blockwatch resolves that name against the repository:

  • Path prefixes are required. Git writes each diff path behind a one-component prefix — a/ and b/ by default, or i/, w/, c/ and o/ under diff.mnemonicPrefix. Exactly one is removed, so a repository directory that happens to share a prefix's name (a top-level b/, for example) is preserved.
  • Quoted paths are decoded. Git's default core.quotePath writes a name containing non-ASCII characters, tabs, quotes or backslashes in an escaped form such as "b/caf\303\251.py"; the real name is recovered from it.

Diffs written without prefixes — git diff --no-prefix, diff.noprefix=true — or with a custom diff.srcPrefix / diff.dstPrefix are rejected. They cannot be distinguished from prefixed paths whose repository directory shares the prefix's name, and guessing would risk validating a file the diff never mentioned while the changed one went unchecked. diff.relative=true is likewise unsupported: it writes paths relative to the current directory, and nothing in the diff records that it did.

Two details make the detection reliable. Git draws the two prefixes from opposite sides of the comparison — a/ against b/, or i/, c/ and o/ against w/ — so a header repeating one prefix on both sides is recognised as unprefixed rather than stripped. An added file is the exception: its source is /dev/null, which says nothing either way, so both readings are checked against the working tree and a target where both name a real file is reported as ambiguous.

In each case blockwatch stops with the flag that fixes it rather than checking the wrong file:

$ git diff --no-prefix | blockwatch
Error: diff target "rules.py" has no recognized Git path prefix.
BlockWatch reads diffs written with the prefixes Git produces by default. This one looks like the
output of --no-prefix, diff.noprefix, or a custom diff.srcPrefix/diff.dstPrefix. Re-run with:
    git diff --default-prefix

A diff naming a file that does not exist in the repository is an error too — but only for files BlockWatch would parse. Entries whose extension maps to no language, such as binary assets or lockfiles, contribute no blocks and are passed over, so a diff carrying them alongside source changes still validates normally.

To produce configuration-independent output in a repository that sets these options globally:

git diff --patch --default-prefix --no-relative | blockwatch

--default-prefix requires Git 2.41 or newer. On older versions, use git -c diff.mnemonicPrefix=false -c diff.noprefix=false diff --patch.

Custom File Extension Mappings

Language detection relies on file extensions. Use -E to map unrecognized or custom extensions to a supported grammar:

blockwatch -E cxx=cpp -E c++=cpp

Files with extensions that do not map to any supported grammar are ignored.

Enabling and Disabling Validators

Control which validators run using -e (enable only) or -d (disable):

# Run all validators except check-ai
blockwatch -d check-ai

# Run only keep-sorted and keep-unique
blockwatch -e keep-sorted -e keep-unique

Note: -e and -d cannot be combined in a single invocation.

The list Command

The list command outputs details on all discovered blocks in JSON format without running validation.

# List all blocks under the current directory
blockwatch list

# Restrict block listing to specific globs
blockwatch list "src/**/*.rs" "**/*.md"

# Annotate output with diff status
git diff | blockwatch list --diff

blockwatch list reads stdin only when --diff is explicitly provided, preventing blocking during non-interactive scripts or pipeline commands (e.g. blockwatch list "src/**/*.ts" | jq).

With --diff, each block entry includes the is_content_modified boolean field.

Output Example

{
  "README.md": [
    {
      "name": "available-validators",
      "line": 18,
      "column": 10,
      "is_content_modified": false,
      "attributes": {
        "name": "available-validators"
      }
    }
  ]
}

Run Reports

By default blockwatch prints nothing when a run succeeds. That makes a check that passed look exactly like a check that never ran. Use --verbosity to see what was actually checked.

LevelOutput
none (default)Nothing.
summaryOne line of counts.
fullA JSON report of every block and the validators that checked it.

The report goes to stdout. Violations go to stderr. A run can print both, and each one can be piped and parsed on its own.

blockwatch --verbosity summary
blockwatch: 34/240 files, 61 blocks (3 unchecked), 73 checks, 0 violations

Reading that line:

  • 34/240 files — 240 files were read, and 34 of them contain blocks.
  • 61 blocks (3 unchecked) — 61 blocks were in scope, and no validator checked 3 of them.
  • 73 checks — validators ran 73 times in total, once per block they applied to.
  • 0 violations — nothing failed.

A block goes unchecked for one of three reasons:

  • It is only a reference target. A block that carries nothing but a name exists so that other blocks can point at it with affects or same-as. It declares no rule of its own, so nothing checks it. This is normal and needs no fixing.
  • An attribute name is misspelled. keep-sortd matches no validator, so the block is skipped without complaint. A full report shows each attribute as it was written, which is usually enough to spot the typo.
  • The validator does not apply to this run. affects only compares blocks that a diff has touched, so it checks nothing during a full-tree scan.

Reports Under a Diff

A diff scopes the report exactly as it scopes the run: only the blocks the diff touched are described. A block the diff never reached is absent from the report rather than listed with an empty checks array, so under a diff blocks_unchecked counts only blocks that were in scope and that nothing checked. This is the same rule blockwatch list --diff follows, so the two commands always agree on which blocks exist.

Reference targets follow it too. When a block declares affects or same-as, its target is read from disk and compared — but the target appears in the report only if the diff touched it as well. A diff that changes the source alone therefore reports a single file, even though two were involved:

git diff --patch | blockwatch --verbosity summary
blockwatch: 1/1 files, 1 blocks (0 unchecked), 1 checks, 1 violations

Nothing about the failure is hidden by this. The violation on stderr names both sides:

Block fileA.py:a at line 1 is modified, but fileB.py:b is not

The division of labour is deliberate — the report describes what the run examined, and the violation explains what went wrong. Once the diff touches the target as well, it appears like any other block, with an empty checks array because a block that carries nothing but a name declares no rule of its own:

git diff --patch | blockwatch --verbosity summary
blockwatch: 2/2 files, 2 blocks (1 unchecked), 1 checks, 0 violations

Full Reports

--verbosity full describes every block the same way blockwatch list does, and adds a checks array naming the validators that ran on it. A block checked by several validators lists all of them.

{
  "summary": {
    "files_scanned": 240,
    "files_with_blocks": 34,
    "files_skipped": 179,
    "blocks": 61,
    "blocks_unchecked": 3,
    "checks": 73,
    "violations": 0,
    "validators": {
      "affects": 41,
      "check-lua": 12,
      "keep-sorted": 20
    }
  },
  "files": {
    "src/validators/check_ai.rs": [
      {
        "attributes": {
          "affects": "docs/validators/check-ai.md:check-ai-env-vars",
          "name": "check-ai-env-vars",
          "same-as": "docs/validators/check-ai.md:check-ai-env-vars",
          "same-as-pattern": "BLOCKWATCH_AI_[A-Z_]+"
        },
        "checks": [
          "affects",
          "same-as"
        ],
        "column": 4,
        "is_content_modified": true,
        "line": 31,
        "name": "check-ai-env-vars"
      }
    ]
  }
}

Files are sorted by path, and each block's checks by validator name, so two runs over an unchanged tree print the same bytes.

The report says which validators looked at a block, not what each one concluded. Violations are not repeated here; they stay on stderr, under the same file paths and line numbers.

--verbosity cannot be combined with the list subcommand, because list already prints its own JSON to stdout.

Exit Codes

CodeDescription
0Success. No violations found, or all reported violations have a non-error severity level.
1Failure. At least one error-severity violation was detected.

← Return to README