blockwatch 0.2.17

Language agnostic linter that keeps your code and documentation in sync and valid
Documentation

BlockWatch

Build Status codecov Crates.io Downloads

BlockWatch is a language-agnostic linter that keeps your code, documentation, and configuration in sync.

It allows you to:

  • Link code to documentation to ensure updates in one place are reflected in another.
  • Enforce formatting rules like sorted lists or unique lines.
  • Validate content using Regex or even AI (LLMs).

BlockWatch can run on your entire codebase or check only changed files in a git diff.

Features

  • ๐Ÿ”— Drift Detection: Explicitly link blocks of code. If one changes, the other must be updated.
  • ๐Ÿงน Content Enforcement:
    • keep-sorted: Keep lists sorted.
    • keep-unique: Ensure no duplicates.
    • line-pattern: Validate lines against Regex.
    • line-count: Enforce block size limits.
  • ๐Ÿค– AI Validation: Use natural language rules to validate code or docs (e.g., "Must mention 'banana'").
  • ๐ŸŒ Language Agnostic: Works with almost any language (Rust, Python, JS, Go, Markdown, YAML, etc.).
  • ๐Ÿš€ Flexible Execution: Run on specific files, glob patterns, or git diffs.

Installation

Homebrew (macOS/Linux)

brew tap mennanov/tap
brew install blockwatch

From Source (Rust)

cargo install blockwatch

Prebuilt Binaries

Download from Releases.

Usage

1. Scan Your Project

Validate all blocks in your project:

# Check all files (defaults to "**")
blockwatch

# Check specific file types
blockwatch "src/**/*.rs" "**/*.md"

# Ignore specific files
blockwatch "**/*.rs" --ignore "**/generated/**"

Note: Always quote glob patterns to prevent shell expansion.

2. Check Modified Files (CI / Hooks)

Pipe a git diff to validate only changed blocks or blocks affected by changes:

# Check unstaged changes
git diff --patch | blockwatch

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

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

# Check changes and some other (possibly unchanged) files
git diff --patch | blockwatch "src/always_checked.rs" "**/*.md"

3. CI Integration

Pre-commit Hook: Add to .pre-commit-config.yaml:

- repo: local
  hooks:
    - id: blockwatch
      name: blockwatch
      entry: bash -c 'git diff --patch --cached --unified=0 | blockwatch'
      language: system
      stages: [ pre-commit ]
      pass_filenames: false

GitHub Action: Add to .github/workflows/your_workflow.yml:

- uses: mennanov/blockwatch-action@v1

Validators

Blocks are defined using XML-like tags in comments.

Linking Code & Docs (affects)

Ensure that when code changes, the documentation is updated.

src/lib.rs:

// <block affects="README.md:supported-langs">
pub enum Language {
    Rust,
    Python,
}
// </block>

README.html:

<!-- <block name="supported-langs"> -->

- Rust
- Python

<!-- </block> -->

If you change src/lib.rs, BlockWatch will complain if you don't also update README.md.

Enforcing Sort Order (keep-sorted)

Sort lines alphabetically (asc or desc).

# <block keep-sorted="asc">
"apple",
"banana",
"cherry",
# </block>

Advanced: Sort by Regex Match Use keep-sorted-pattern to sort by a specific part of the line.

  • If a named capture group value exists, it is used for sorting.
  • Otherwise, the entire match is used.
items = [
    # <block keep-sorted="asc" keep-sorted-pattern="id: (?P<value>\d+)">
    "id: 1  apple",
    "id: 2  banana",
    "id: 10 orange",
    # </block>
]

Enforcing Uniqueness (keep-unique)

Ensure no duplicate lines.

# <block keep-unique>
"user_1",
"user_2",
"user_3",
# </block>

Advanced: Uniqueness by Regex Use the attribute value as a regex to determine uniqueness.

  • If a named capture group value exists, it is used for comparison.
  • Otherwise, the entire match is used.
ids = [
    # <block keep-unique="^ID:(?P<value>\d+)">
    "ID:1 Alice",
    "ID:2 Bob",
    "ID:1 Carol",  # Violation: ID:1 is already used
    # </block>
]

Regex Validation (line-pattern)

Ensure every line matches a specific regex pattern.

slugs = [
    # <block line-pattern="^[a-z0-9-]+$">
    "valid-slug",
    "another-one",
    # </block>
]

Line Count (line-count)

Enforce the number of lines in a block. Supported operators: <, >, <=, >=, ==.

# <block line-count="<=5">
"a",
"b",
"c"
# </block>

AI Validation (check-ai)

Validate logic or style using an LLM.

<!-- <block check-ai="Must mention the company name 'Acme Corp'"> -->
<p>Welcome to Acme Corp!</p>
<!-- </block> -->

Advanced: Extract Content for AI

Use check-ai-pattern to send only relevant parts of the text to the LLM.

prices = [
    # <block check-ai="Prices must be under $100" check-ai-pattern="\$(?P<value>\d+)">
    "Item A: $50",
    "Item B: $150",  # Violation
    # </block>
]

check-ai configuration

  • BLOCKWATCH_AI_API_KEY: API Key (OpenAI compatible).
  • BLOCKWATCH_AI_MODEL: Model name (default: gpt-5-nano).
  • BLOCKWATCH_AI_API_URL: Custom API URL (optional).

Supported Languages

BlockWatch supports comments in:

  • Bash
  • C#
  • C/C++
  • CSS
  • Go (with go.mod, go.sum and go.work support)
  • HTML
  • Java
  • JavaScript
  • Kotlin
  • Makefile
  • Markdown
  • PHP
  • Python
  • Ruby
  • Rust
  • SQL
  • Swift
  • TOML
  • TypeScript
  • XML
  • YAML

Configuration

  • Extensions: Map custom extensions: blockwatch -E cxx=cpp
  • Disable Validators: blockwatch -d check-ai
  • Enable Validators: blockwatch -e keep-sorted
  • Ignore Files: Ignore files matching glob patterns: blockwatch --ignore "**/generated/**"

Known Limitations

  • Deleted blocks are ignored.
  • Files with unsupported grammar are ignored.

Contributing

Contributions are welcome! A good place to start is by adding support for a new grammar.

Run Tests

cargo test