Some parts of your codebase must change together: a function and its docs, a value across config files, etc. Blockwatch makes these relationships explicit and fails a CI run or a pre-commit hook if they drift.
Supports 33 languages. No config files are needed.
Wrap the code in a block and point it at the docs.
src/lib.rs:
// <block name="languages" affects="README.md:supported-languages">
pub enum Language {
Rust,
Python,
}
// </block>README.md:
<!-- <block name="supported-languages"> -->
- Rust
- Python
<!-- </block> -->Now add a Go variant to the enum in the Rust code and pass the diff to blockwatch --diff:
$ git diff --patch | blockwatch --diff
{
"src/lib.rs": [
{
"code": "affects",
"data": {
"affected_block_file_path": "README.md",
"affected_block_name": "supported-languages"
},
"message": "Block src/lib.rs:languages at line 1 is modified, but README.md:supported-languages is not",
"range": {
"end": {"character": 63, "line": 1},
"start": {"character": 4, "line": 1}
},
"severity": 1
}
]
}Update the contents of the block in README.md and it will pass the check.
affects and same-as are the two that work across files: one forces a co-edit, the other compares the actual values
and needs no diff to do it. The rest check a single block on its own, and are the things you'd otherwise nitpick in code
review.
| Validator | Description | Attributes |
|---|---|---|
affects |
Forces linked blocks to be edited together (e.g. code and its docs); needs a diff | affects |
same-as |
Asserts two or more blocks hold the same value, across languages and formats; no diff needed | same-as, same-as-pattern, same-as-mode, same-as-format |
keep-sorted |
Enforces alphabetical or numerical ordering on list items | keep-sorted, keep-sorted-pattern, keep-sorted-format |
keep-unique |
Prevents duplicate lines within a block | keep-unique |
line-pattern |
Enforces that every line matches a specified regex | line-pattern |
line-count |
Enforces lower or upper bounds on the number of lines in a block | line-count |
check-ai |
Validates content against natural language rules using an LLM | check-ai, check-ai-pattern |
check-lua |
Runs custom validation logic written in Lua | check-lua, check-lua-pattern, check-lua-timeout |
Blocks can have a name, so other blocks can point at it, and severity. The
error severity fails with a non-zero exit code.
Violations can be kept out of the exit code without editing the source:
blockwatch --suppress FILE[:BLOCK[:VALIDATOR[:HASH]]] keeps them in the output and stops them failing the run. The
shorter the address, the more it covers: from a single violation up to every violation in a file. Only named blocks can
be addressed below the file level — see Suppressing a Violation.
See the Validators Reference for full details.
brew install mennanov/blockwatch/blockwatch # macOS / Linux
cargo install blockwatch # from sourcePrebuilt binaries are on the Releases page.
This repository ships a skill that tells an agent which blocks are worth linking and how to verify its own edits.
For Claude Code:
/plugin marketplace add mennanov/blockwatch
/plugin install blockwatch@blockwatch
For Cursor, Copilot, Codex, and other setup options, see docs/agents.md.
A bare run checks every block in the repository. Pass --diff to read a unified diff from stdin, which marks the blocks
the diff changed, and add --only-changed to narrow the run down to those blocks:
# Check every block in the repository
blockwatch
# Check specific globs
blockwatch "src/**/*.rs" "**/*.md"
# Check every block, and enforce the rules that need a diff, such as `affects`
git diff --patch | blockwatch --diff
# Check only the blocks the diff changed
git diff --patch | blockwatch --diff --only-changed
# The same, for staged changes
git diff --cached --patch | blockwatch --diff --only-changed
# Dump all discovered blocks as JSON
blockwatch listEverything is flags and comments. There is no config file, deliberately: a central config is one more thing that drifts away from the code it describes, which is the problem this tool exists to solve.
See docs/cli.md for the run modes in full, CLI flags, path exclusions, and custom extension mappings.
pre-commit (.pre-commit-config.yaml):
- repo: https://github.com/mennanov/blockwatch
rev: v0.5.1 # Use latest release
hooks:
- id: blockwatchGitHub Actions:
- uses: mennanov/blockwatch-action@v1BlockWatch exits 1 when it finds at least one error severity violation, and 0 otherwise. Warnings, info, and hints
are printed but don't fail the run.
blockwatch --format sarif writes the violations as a SARIF log instead of the JSON diagnostics, for GitHub code
scanning and anything else that reads the format — see SARIF Output.
For plain git hooks, local pre-commit setups, and sandboxing untrusted Lua scripts in fork pull requests, see docs/ci.md.
- Bash
- C#
- C/C++ (
.c,.cc,.cpp,.h) - CMake (
CMakeLists.txt,.cmake) - CSS
- Dart
- Dockerfile (with
Containerfileand.dockerfilesupport) - Elixir (
.ex,.exs) - Go (with
go.mod,go.sumandgo.worksupport) - GraphQL (
.graphql,.gql) - Groovy (with
.gradleandJenkinsfilesupport) - HCL (Terraform:
.tf,.tfvars,.hcl) - HTML
- Java
- JavaScript
- Kotlin
- Lua
- Makefile
- Markdown
- Nix
- PHP
- Protocol Buffers (
.proto) - Python
- Ruby
- Rust
- SQL
- Scala (with
.sbtsupport) - Starlark (Bazel:
BUILD,WORKSPACE,MODULE.bazel,.bzl,.bzlmod,.star) - Swift
- TOML
- TypeScript
- XML
- YAML
Only the extensions listed above are recognized. Anything else is ignored, including spellings a grammar would otherwise
handle, .hpp, .hxx and .cxx among them. Map those to a supported syntax with -E:
blockwatch -E cxx=cpp -E hpp=cpp- Deleting a block deletes its rule, quietly. Remove a file, or just strip the tags out of it, and the links it declared are gone. The run passes and nothing tells you a rule disappeared. Blocks still pointing at the deleted one do fail, as a missing reference.
- A file needs comments to hold a block. JSON, CSV, and
.envfiles have nowhere to put a tag. Link to them with a whole-fileaffectsinstead. - Unsupported extensions are skipped silently. A run that read nothing looks exactly like a run that found no
problems.
blockwatch --verbosity summaryprints how many files were actually read.
Contributions are welcome. A great first issue is adding support for a new grammar.
To run tests locally:
cargo test