From BlockWatch
Annotates co-dependent code with `<block affects>`/`<block name>` tags so BlockWatch catches drift, and enforces sorted/unique lists, line patterns, and format constraints. Invoke when writing or editing files that use BlockWatch tags.
How this skill is triggered — by the user, by Claude, or both
Slash command
/blockwatch:blockwatchThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
BlockWatch is a language-agnostic linter that enforces rules declared inside HTML-like `<block ...>` tags placed in
BlockWatch is a language-agnostic linter that enforces rules declared inside HTML-like <block ...> tags placed in
source-file comments. It works across Rust, Python, JS/TS, Go, Java, Markdown, YAML, TOML, HTML, and more, and can run
on the whole tree or only the changed lines of a git diff.
Use this skill in three situations:
This is the primary way blocks should get added: incrementally, as part of normal coding. Whenever you write or change
code that matches a pattern in Where blocks add value (below), add the matching <block> tag right then, using the
Validator reference for the exact syntax.
keep-sorted immediately. Add a fact
that also lives in the docs or config → add affects immediately. Retrofitting later is exactly the cost this avoids.blockwatch config / existing <block>
tags in the tree) means the project opted in. Don't add blocks to a project that doesn't use the tool.git diff --patch | blockwatch to confirm they pass (see Running and verifying).Goal: add a small number of high-value blocks, not annotate everything. A block earns its place only when it would catch a real mistake a human might otherwise miss in review. Too many blocks create noise and get ignored.
Workflow:
rg/grep to find
lists, enums, match arms, tables, and constants.blockwatch list to confirm every new tag parses and is recognized, then run blockwatch to confirm all blocks
pass on the current (clean) tree. Fix any tag you placed on already-inconsistent content.| You see... | Add | Why |
|---|---|---|
| A hand-maintained list/enum/match that should stay ordered (dependencies, CLI flags, feature lists, route tables) | keep-sorted | Eliminates "please sort this" review nits |
| A list that must not repeat (allowlists, IDs, registered names) | keep-unique | Prevents accidental duplicates |
| The same fact in two places — an enum and its docs, a version constant and a changelog row, a config key and its README table | affects + name | Forces docs/config to be updated alongside code |
| A list whose items have a strict format (slugs, semver, env-var names) | line-pattern="<regex>" | Catches typos at the source |
| A block that must not grow past N lines (public API surface, a switch mapped to a fixed enum) | line-count="<=N" | Flags unbounded growth |
| Prose or config with a natural-language rule ("must mention X", "no TODOs left") | check-ai="..." | Rules regex can't express |
| Domain logic too complex for regex | check-lua="script.lua" | Custom programmable checks |
Prefer the deterministic validators (keep-sorted, keep-unique, affects, line-pattern, line-count) first — they
are free, fast, and need no API keys. Reserve check-ai for rules the cheaper validators genuinely can't express.
<block ...>, close with
</block>.DEPENDENCIES = [
# <block keep-sorted keep-unique>
"anyhow",
"clap",
"serde",
# </block>
]
// <block affects="README.md:supported-langs">
pub enum Language { Rust, Python }
// </block>
<!-- <block name="supported-langs"> -->
- Rust
- Python
<!-- </block> -->
(Editing the enum now forces you to touch the supported-langs block in README.md.)
| Attribute | Syntax | Notes |
|---|---|---|
name | name="foo" | Names a block; the target of affects; shown by blockwatch list. |
affects | affects="file:foo" or affects=":foo" (same file); comma-separate multiple | If this block's content changes in a diff, the referenced name="foo" block's content must change too, else a violation. One-way by default; put affects on both blocks (each named) for two-way drift detection. Only fires in diff mode. |
keep-sorted | keep-sorted / keep-sorted="asc" / keep-sorted="desc" | Default asc, compared lexicographically. |
keep-sorted-pattern | keep-sorted-pattern="id: (?P<value>\d+)" | Sort by the regex capture group named value instead of the whole line. |
keep-sorted-format | keep-sorted-format="numeric" | Compare the value numerically rather than as text ("10" after "2"). |
keep-unique | keep-unique / keep-unique="^ID:(?P<value>\d+)" | Uniqueness on the whole line, or on the value capture group. |
line-pattern | line-pattern="^[a-z0-9-]+$" | Every line in the block must match. |
line-count | line-count="<=5" | Operators: <, >, <=, >=, ==. |
check-ai | check-ai="Must mention 'Acme'" + optional check-ai-pattern="\$(?P<value>\d+)" | LLM validation. Requires BLOCKWATCH_AI_API_KEY (plus optional BLOCKWATCH_AI_MODEL, BLOCKWATCH_AI_API_URL). |
check-lua | check-lua="scripts/x.lua" | Script defines validate(ctx, content) returning nil (pass) or an error string. |
severity | severity="error" (default) / warning / info / hint | Only error fails the run (exit 1); the others are reported but exit 0. |
When you change code in a file that contains blocks, you MUST:
<block> / </block> tags unless explicitly told to. Place new content inside the appropriate
block boundaries.keep-sorted lists ordered, never introduce a keep-unique
duplicate, make every new line match line-pattern, stay within line-count, and satisfy check-ai / check-lua
rules.affects: if you change a block carrying affects="file:name", you must also update the referenced
<block name="name"> in file — they are meant to move together.You can run the blockwatch command directly in the shell:
blockwatch # validate every block in the tree
git diff --patch | blockwatch # validate only blocks your changes touched (fast)
git diff --cached --patch | blockwatch # staged changes only
blockwatch list # JSON dump of every block found (audit / debug)
blockwatch "src/**/*.rs" "**/*.md" # restrict to globs (quote them)
blockwatch --ignore "**/generated/**" # exclude paths
After editing annotated files, run git diff --patch | blockwatch. If it fails, read the message, fix the
sorting/duplication/pattern/sync issue, and re-run until it passes. Use blockwatch list to confirm a tag you just
added is parsed and seen.
If blockwatch is not on PATH, install it with cargo install blockwatch or
brew install mennanov/blockwatch/blockwatch.
Validating only the diff keeps these near-instant.
pre-commit (.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
Plain git hook (.git/hooks/pre-commit, then chmod +x):
#!/bin/sh
git diff --patch --cached --unified=0 | blockwatch
GitHub Actions (.github/workflows/blockwatch.yml):
name: blockwatch
on:
pull_request: { branches: [ main ] }
push: { branches: [ main ] }
permissions: { contents: read }
jobs:
blockwatch:
runs-on: ubuntu-latest
steps:
- uses: mennanov/blockwatch-action@v1
# Only needed if you use check-ai:
# env: { BLOCKWATCH_AI_API_KEY: ${{ secrets.BLOCKWATCH_AI_API_KEY }} }
Validating the PR diff is enough for affects/drift checks. A periodic full-tree blockwatch run (no diff) on main
is a good extra safety net for the deterministic validators.
npx claudepluginhub mennanov/blockwatch --plugin blockwatchReviews and verifies code before merge via triage-first checks (up to 16 parallel agents). Pipeline mode verifies vs plans; general mode for PRs/branches/staged changes. Flags findings only.
Guides creation of markdown-based Hookify rules to block dangerous bash commands, warn on risky file edits, and enforce behavioral guardrails in Claude Code.
Enforces validation rules for Claude Code tools (Edit, Write, Bash, NotebookEdit) to prevent errors like unread files before edits, non-unique old_strings, and missing directories. Useful before file changes or after failures.