Validators
Every rule is declared as an attribute on a <block> tag inside a comment. One block can carry several attributes at
once.
A start or end tag that fails to parse — most commonly a missing closing > — fails the whole run with Malformed block tag at line N, column N, rather than being silently ignored. Text counts as a tag only when whitespace follows
<block or </block, or the comment ends there. So prose such as <block, filesystem> or <blockquote> is ignored.
Text inside backticks in a comment is ignored too, so a comment can quote a tag: // add a `<block keep-sorted>` tag.
As in Markdown, the closing backticks must be on the same line, and there must be as many of them as opening ones.
A quoted value ends at the next quote of the same kind. To put that quote inside the value, write a backslash before
it: keep-unique="\"(?P<value>[a-z]+)\"". The backslash stays in the value, so a regex reads \" as ". The other
kind of quote needs no backslash: keep-unique='"(?P<value>[a-z]+)"'.
A tag can go on over several line comments in a row, such as // lines that rustfmt wraps. Only whitespace can
come between them, so a comment after code on the next line does not continue the tag.
A pattern or a reference with a line break in it fails the run. A formatter that wraps the tag there may have dropped a space, so blockwatch can’t know what the value was. Keep each one on a single line. A line break after the comma between two references is fine.
Which validator do I want?
Section titled “Which validator do I want?”| I want to… | Use |
|---|---|
| Force docs or config to be updated whenever some code changes | affects |
| Assert two places still hold the same value | same-as |
| Keep a list alphabetized or numerically ordered | keep-sorted |
| Prevent duplicate entries in a list | keep-unique |
| Require every line to match a format, or forbid some text | line-pattern |
| Cap or fix the number of lines in a block | line-count |
| Enforce a rule stated in plain English | check-ai |
| Run arbitrary validation logic | check-lua |
Prefer the deterministic validators — affects, same-as, keep-sorted, keep-unique,
line-pattern, line-count. They are fast, offline, and need no API key. Reserve check-ai for rules the others
genuinely cannot express.
Universal attributes
Section titled “Universal attributes”These apply to any block regardless of which validator it uses.
Names the block so other blocks can point at it with affects or same-as. Names also show up in
blockwatch list.
# <block name="allowed-colors">"red","green",# </block>A name must be unique within its file — a second block reusing one is a hard error, since every
affects/same-as reference to it would be ambiguous.
Naming a block also makes its violations suppressible one at a time: only a named block’s violations carry the address
that --suppress points at. An unnamed block’s violations can only be suppressed
along with the rest of their file.
severity
Section titled “severity”Controls how a violation is reported. Mirrors LSP diagnostic severities.
| Value | Reported | Exit code |
|---|---|---|
error (default) |
yes | 1 |
warning |
yes | 0 |
info |
yes | 0 |
hint |
yes | 0 |
Only error fails the run. This makes severity the way to introduce a rule without breaking CI on day one — land the
block as a warning, clean up the existing violations, then promote it:
# <block keep-sorted severity="warning">"cherry","apple",# </block>severity is declared beside the code and applies to every violation the block will ever produce.
--suppress is passed on the command line and applies to violations that were
already reported. Lower the severity when the rule is genuinely advisory here; suppress when the rule is right in
general and wrong about this one case.
When a block is checked
Section titled “When a block is checked”With a diff on stdin, a block is validated only if the diff touched its content or its start tag. Without a diff, every block in scope is validated.
affects is the exception in the other direction: it is inert without a diff, since it asks “was this edited without
its counterpart?” — a question a full-tree run cannot answer. Every other validator runs in both modes.
← README