{"id":37514211,"url":"https://github.com/mennanov/blockwatch","last_synced_at":"2026-06-14T07:02:58.388Z","repository":{"id":286655675,"uuid":"948277352","full_name":"mennanov/blockwatch","owner":"mennanov","description":"Language agnostic linter that keeps your code and documentation in sync and valid","archived":false,"fork":false,"pushed_at":"2026-02-13T04:49:03.000Z","size":2797,"stargazers_count":22,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-13T12:39:59.198Z","etag":null,"topics":["cli-tools","documentation","linter","linters","rust","static-analysis"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mennanov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-14T03:31:30.000Z","updated_at":"2026-02-13T04:49:06.000Z","dependencies_parsed_at":"2025-06-03T05:05:51.175Z","dependency_job_id":"0cab8a0b-a396-4c6e-b869-89318cae8fe4","html_url":"https://github.com/mennanov/blockwatch","commit_stats":null,"previous_names":["mennanov/blockwatch"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/mennanov/blockwatch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Fblockwatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Fblockwatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Fblockwatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Fblockwatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mennanov","download_url":"https://codeload.github.com/mennanov/blockwatch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mennanov%2Fblockwatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29488011,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T19:29:10.908Z","status":"ssl_error","status_checked_at":"2026-02-15T19:29:10.419Z","response_time":118,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli-tools","documentation","linter","linters","rust","static-analysis"],"created_at":"2026-01-16T08:05:44.257Z","updated_at":"2026-06-14T07:02:58.380Z","avatar_url":"https://github.com/mennanov.png","language":"Rust","funding_links":[],"categories":["Multiple languages"],"sub_categories":[],"readme":"# BlockWatch\n\n[![Build Status](https://github.com/mennanov/blockwatch/actions/workflows/rust.yml/badge.svg)](https://github.com/mennanov/blockwatch/actions)\n[![codecov](https://codecov.io/gh/mennanov/blockwatch/graph/badge.svg?token=LwUfGTZ551)](https://codecov.io/gh/mennanov/blockwatch)\n[![Crates.io](https://img.shields.io/crates/v/blockwatch)](https://crates.io/crates/blockwatch)\n[![Downloads](https://img.shields.io/crates/d/blockwatch)](https://crates.io/crates/blockwatch)\n\nBlockWatch is a linter that keeps your code, documentation, and configuration in sync and enforces strict formatting and\nvalidation rules.\n\n\u003cp\u003e\n  \u003cimg src=\"demo.gif\" alt=\"BlockWatch Demo\"\u003e\n\u003c/p\u003e\n\nIt helps you avoid broken docs and messy config files by enforcing rules directly in your comments. You can link code to\ndocumentation, enforce sorted lists, ensure uniqueness, and even validate content with Regex, AI, or custom Lua scripts.\n\nIt works with almost any language (Rust, Python, JS, Go, Markdown, YAML, etc.) and can run on your entire repo or just\nyour VCS diffs.\n\n## Annotate your project with an AI agent (recommended)\n\nAdding the first `\u003cblock\u003e` tags by hand is the tedious part of picking up BlockWatch.\nAI coding agents are good at this. An agent can read through the repo, pick reasonable spots, add the tags in the\ncorrect comment syntax for each language, and run `blockwatch` to check its own work.\n\nThere's a skill in this repo for it: [`.agents/skills/blockwatch/SKILL.md`](.agents/skills/blockwatch/SKILL.md). It\ntells\nthe agent where blocks are worth adding, documents the tag syntax, and explains how to verify the result.\n\n**1. Install the binary** so the agent can run it:\n\n```shell\ncargo install blockwatch   # or: brew install mennanov/blockwatch/blockwatch\n```\n\n**2. Give the skill to your agent.**\n\n**Claude Code users:** install the plugin once and the skill is available in every project — no\nper-project setup:\n\n```text\n/plugin marketplace add mennanov/blockwatch\n/plugin install blockwatch@blockwatch\n```\n\nFor other agents (or if you prefer a project-local copy), place `SKILL.md` where your tool looks\nfor instructions:\n\n| Agent              | Where to put the skill                                                                                                  |\n|--------------------|-------------------------------------------------------------------------------------------------------------------------|\n| **Claude Code**    | Use the plugin above (recommended), or `.claude/skills/blockwatch/SKILL.md` (project) / `~/.claude/skills/...` (global) |\n| **Cursor**         | `.cursor/rules/blockwatch.mdc`                                                                                          |\n| **GitHub Copilot** | append to `.github/copilot-instructions.md`                                                                             |\n| **Codex / others** | append to `AGENTS.md`                                                                                                   |\n\nYou can pull the file straight from this repo:\n\n```shell\nmkdir -p .claude/skills/blockwatch\ncurl -sL https://raw.githubusercontent.com/mennanov/blockwatch/main/.agents/skills/blockwatch/SKILL.md \\\n  -o .claude/skills/blockwatch/SKILL.md\n```\n\n**3. Ask the agent to annotate the project**, for example:\n\n\u003e Using the BlockWatch skill, annotate this repository with `\u003cblock\u003e` tags. Focus on lists that should stay\n\u003e sorted/unique and on code that must stay in sync with docs or config. Add only high-value blocks, then run\n\u003e `blockwatch` to confirm they all pass.\n\nReview the diff before you commit it: the agent's choices are a starting point.\n\n**4. Turn on enforcement** so the rules stay in place: wire up the pre-commit hook and GitHub Action from\n[CI Integration](#ci-integration).\n\n## Features\n\n[//]: # (\u003cblock name=\"available-validators\"\u003e)\n\n- **Drift Detection**: Link a block of code to its documentation. If you change the code but forget the docs, BlockWatch\n  alerts you.\n- **Strict Formatting**: Enforce sorted lists (`keep-sorted`) and unique entries (`keep-unique`) so you don't have to\n  nitpick in code reviews.\n- **Content Validation**: Check lines against Regex patterns (`line-pattern`) or enforce block size limits (\n  `line-count`).\n- **AI Rules**: Use natural language to validate code or text (e.g., \"Must mention 'banana'\").\n- **Lua Scripting**: Write custom validation logic in Lua scripts (`check-lua`).\n- **Flexible**: Run it on specific files, glob patterns, or just your unstaged changes.\n\n[//]: # (\u003c/block\u003e)\n\n## Installation\n\n### Homebrew (macOS/Linux)\n\n```shell\nbrew install mennanov/blockwatch/blockwatch\n```\n\nThe fully-qualified name keeps working once Homebrew starts requiring\n[explicit trust](https://docs.brew.sh/Tap-Trust) for third-party taps: it trusts only the\n`blockwatch` formula, not the whole tap. If you install via a `Brewfile`, use:\n\n```ruby\nbrew \"mennanov/blockwatch/blockwatch\", trusted: true\n```\n\n### From Source (Rust)\n\n```shell\ncargo install blockwatch\n```\n\n### Prebuilt Binaries\n\nCheck the [Releases](https://github.com/mennanov/blockwatch/releases) page for prebuilt binaries.\n\n## Quick start example\n\n1. Add a special `block` tag in the comments in any supported file ([See *Supported Languages*](#supported-languages))\n   like this:\n\n   ```python\n   user_ids = [\n       # \u003cblock keep-sorted keep-unique\u003e\n       \"cherry\",\n       \"apple\",\n       \"apple\",\n       \"banana\",\n       # \u003c/block\u003e\n   ]\n   ```\n\n2. Run `blockwatch`:\n\n   ```shell\n   blockwatch\n   ```\n\n   BlockWatch will fail and tell you that the list is not sorted and has duplicate entries.\n\n3. Fix the order and uniqueness:\n\n   ```python\n   user_ids = [\n       # \u003cblock keep-sorted keep-unique\u003e\n       \"apple\",\n       \"banana\",\n       \"cherry\",\n       # \u003c/block\u003e\n   ]\n   ```\n\n4. Run `blockwatch` again:\n\n   ```shell\n   blockwatch\n   ```\n\n   Now it passes!\n\n## How It Works\n\nYou define rules using HTML-like tags inside your comments.\n\n### Linking Code Blocks (`affects`)\n\nThis ensures that if you change some block of code, you're forced to look at the other blocks too.\n\n**src/lib.rs**:\n\n```rust\n// \u003cblock affects=\"README.html:supported-langs\"\u003e\npub enum Language {\n    Rust,\n    Python,\n}\n// \u003c/block\u003e\n```\n\n**README.html**:\n\n```html\n\u003c!-- \u003cblock name=\"supported-langs\"\u003e --\u003e\n\u003cul\u003e\n    \u003cli\u003eRust\u003c/li\u003e\n    \u003cli\u003ePython\u003c/li\u003e\n\u003c/ul\u003e\n\u003c!-- \u003c/block\u003e --\u003e\n```\n\nIf you modify the enum in `src/lib.rs`, BlockWatch will fail until you touch the corresponding block `supported-langs`\nin `README.html` as well.\n\n### Enforce Sort Order (`keep-sorted`)\n\nKeep lists alphabetized. Default is `asc` (ascending).\n\n```python\n# \u003cblock keep-sorted\u003e\n\"apple\",\n\"banana\",\n\"cherry\",\n# \u003c/block\u003e\n```\n\nIf the list is not sorted alphabetically, BlockWatch will fail until you fix the order.\n\n#### Sort by Regex\n\nYou can sort by a specific part of the line using a regex capture group named `value`.\n\n```python\nitems = [\n    # \u003cblock keep-sorted=\"asc\" keep-sorted-pattern=\"id: (?P\u003cvalue\u003e\\d+)\"\u003e\n    \"id: 1  apple\",\n    \"id: 2  banana\",\n    \"id: 10 orange\",\n    # \u003c/block\u003e\n]\n```\n\n#### Numeric Sort (`keep-sorted-format`)\n\nBy default, values are compared lexicographically (as strings). This means `\"10\"` sorts before `\"2\"` because `\"1\" \u003c \"2\"`\ncharacter-by-character. Use `keep-sorted-format=\"numeric\"` to compare values as numbers instead.\n\n```python\nnumbers = [\n    # \u003cblock keep-sorted keep-sorted-format=\"numeric\"\u003e\n    2\n    10\n    20\n    # \u003c/block\u003e\n]\n```\n\nThis works with `keep-sorted-pattern` to extract numeric values from lines with mixed content:\n\n```python\nitems = [\n    # \u003cblock keep-sorted keep-sorted-format=\"numeric\" keep-sorted-pattern=\"id: (?P\u003cvalue\u003e\\d+)\"\u003e\n    \"id: 2  banana\",\n    \"id: 10 orange\",\n    \"id: 20 apple\",\n    # \u003c/block\u003e\n]\n```\n\nWithout `keep-sorted-format=\"numeric\"`, the example above would fail because `\"10\"` is lexicographically less than\n`\"2\"`.\n\n### Enforce Unique Lines (`keep-unique`)\n\nPrevent duplicates in a list.\n\n```python\n# \u003cblock keep-unique\u003e\n\"user_1\",\n\"user_2\",\n\"user_3\",\n# \u003c/block\u003e\n```\n\n#### Uniqueness by Regex\n\nJust like sorting, you can check uniqueness based on a specific regex match.\n\n```python\nids = [\n    # \u003cblock keep-unique=\"^ID:(?P\u003cvalue\u003e\\d+)\"\u003e\n    \"ID:1 Alice\",\n    \"ID:2 Bob\",\n    \"ID:1 Carol\",  # Violation: ID:1 is already used\n    # \u003c/block\u003e\n]\n```\n\n### Regex Validation (`line-pattern`)\n\nEnsure every line matches a specific regex pattern.\n\n```python\nslugs = [\n    # \u003cblock line-pattern=\"^[a-z0-9-]+$\"\u003e\n    \"valid-slug\",\n    \"another-one\",\n    # \u003c/block\u003e\n]\n```\n\n### Enforce Line Count (`line-count`)\n\nEnforce the number of lines in a block.\nSupported operators: `\u003c`, `\u003e`, `\u003c=`, `\u003e=`, `==`.\n\n```python\n# \u003cblock line-count=\"\u003c=5\"\u003e\n\"a\",\n\"b\",\n\"c\"\n# \u003c/block\u003e\n```\n\n### Validate with AI (`check-ai`)\n\nUse an LLM to validate logic or style.\n\n```html\n\u003c!-- \u003cblock check-ai=\"Must mention the company name 'Acme Corp'\"\u003e --\u003e\n\u003cp\u003eWelcome to Acme Corp!\u003c/p\u003e\n\u003c!-- \u003c/block\u003e --\u003e\n```\n\n#### Targeted AI Checks\n\nUse `check-ai-pattern` to send only specific parts of the text to the LLM.\n\n```python\nprices = [\n    # \u003cblock check-ai=\"Prices must be under $100\" check-ai-pattern=\"\\$(?P\u003cvalue\u003e\\d+)\"\u003e\n    \"Item A: $50\",\n    \"Item B: $150\",  # Violation\n    # \u003c/block\u003e\n]\n```\n\n#### Supported environment variables\n\n[//]: # (\u003cblock name=\"check-ai-env-vars\"\u003e)\n\n- `BLOCKWATCH_AI_API_KEY`: API Key.\n- `BLOCKWATCH_AI_MODEL`: Model name (default: `gpt-5-nano`).\n- `BLOCKWATCH_AI_API_URL`: Custom OpenAI compatible API URL (optional).\n\n[//]: # (\u003c/block\u003e)\n\n### Validate with Lua Scripts (`check-lua`)\n\nRun custom validation logic using a Lua script. The script must define a global `validate(ctx, content)` function that\nreturns `nil` if validation passes or a string error message if it fails.\n\n```python\ncolors = [\n    # \u003cblock check-lua=\"scripts/validate_colors.lua\"\u003e\n    'red',\n    'green',\n    'blue',\n    # \u003c/block\u003e\n]\n```\n\n**scripts/validate_colors.lua**:\n\n```lua\nfunction validate(ctx, content)\n    if content:find(\"purple\") then\n        return \"purple is not an allowed color\"\n    end\n    return nil\nend\n```\n\nThe `validate` function receives two arguments:\n\n- `ctx` — a table with the following fields:\n    - `ctx.file` — the source file path.\n    - `ctx.line` — the line number of the block's start tag.\n    - `ctx.attrs` — a table of all block attributes.\n- `content` — the trimmed text content of the block.\n\n\u003c!-- \u003cblock name=\"lua-safety-modes\"\u003e --\u003e\n\n#### Lua safety mode\n\nBy default, Lua scripts run in a **sandboxed** mode with only the `coroutine`, `table`, `string`, `utf8`, and `math`\nstandard libraries available. The `io`, `os`, and `package` libraries are **not** loaded, preventing file system access,\ncommand execution, and loading of external modules.\n\nYou can change the security level by setting the `BLOCKWATCH_LUA_MODE` environment variable:\n\n```shell\n# Allow IO and OS libraries (memory-safe, but with file/system access)\nBLOCKWATCH_LUA_MODE=safe blockwatch\n\n# Allow all libraries including C module loading (unsafe)\nBLOCKWATCH_LUA_MODE=unsafe blockwatch\n```\n\n| `BLOCKWATCH_LUA_MODE` | Libraries available                                                   | Security Level                      |\n|-----------------------|-----------------------------------------------------------------------|-------------------------------------|\n| `sandboxed` (default) | `coroutine`, `table`, `string`, `utf8`, `math`                        | Most secure - No file/OS access     |\n| `safe`                | All memory-safe libraries (including `io`, `os`, `package`)           | Memory-safe - Allows file/OS access |\n| `unsafe`              | All Lua standard libraries with no restrictions (including C modules) | Unsafe - Full system access         |\n\n\u003c!-- \u003c/block\u003e --\u003e\n\n## Usage\n\n### Run Locally\n\nValidate all blocks in your project:\n\n```shell\n# Check everything\nblockwatch\n\n# Check specific files\nblockwatch \"src/**/*.rs\" \"**/*.md\"\n\n# Ignore stuff\nblockwatch \"**/*.rs\" --ignore \"**/generated/**\"\n```\n\n\u003e **Tip:** Glob patterns should be quoted to avoid shell expanding them.\n\n### Check Only What Changed\n\nPipe a git diff to BlockWatch to validate only the blocks you touched. This is perfect for pre-commit hooks.\n\n```shell\n# Check unstaged changes\ngit diff --patch | blockwatch\n\n# Check staged changes\ngit diff --cached --patch | blockwatch\n\n# Check changes in a specific file only\ngit diff --patch path/to/file | blockwatch\n\n# Check changes and some other (possibly unchanged) files\ngit diff --patch | blockwatch \"src/always_checked.rs\" \"**/*.md\"\n```\n\n### Listing Blocks\n\nYou can list all blocks that BlockWatch finds without running any validation. This is useful for auditing your blocks or\ndebugging your configuration.\n\n```shell\n# List all blocks in the current directory\nblockwatch list\n\n# List blocks in specific files\nblockwatch list \"src/**/*.rs\" \"**/*.md\"\n\n# List only blocks affected by current changes (reads the diff from stdin)\ngit diff | blockwatch list --diff\n```\n\n`blockwatch list` does not read stdin by default, so it never blocks waiting for\ninput. This makes it safe to run non-interactively — in CI, or when invoked by\nanother program such as an AI agent — and to feed its JSON output into a pipe,\ne.g. `blockwatch list \"src/**/*.ts\" | jq`. Pass `--diff` to opt in to reading a\nunified diff from stdin; `list` then reports which blocks the diff touched via\nthe `is_content_modified` field.\n\nThe output is a JSON object.\n\n#### Example Output\n\n[//]: # (\u003cblock name=\"list-output-example\"\u003e)\n\n```json\n{\n  \"README.md\": [\n    {\n      \"name\": \"available-validators\",\n      \"line\": 18,\n      \"column\": 10,\n      \"is_content_modified\": false,\n      \"attributes\": {\n        \"name\": \"available-validators\"\n      }\n    }\n  ]\n}\n```\n\n[//]: # (\u003c/block\u003e)\n\n### CI Integration\n\n#### Pre-commit Hook\n\nAdd this to `.pre-commit-config.yaml` (pre-commit builds blockwatch from source with cargo on first run):\n\n```yaml\n- repo: https://github.com/mennanov/blockwatch\n  rev: v0.2.27  # use the latest release tag\n  hooks:\n    - id: blockwatch\n```\n\nIf you already have the `blockwatch` binary installed (e.g. via Homebrew), you can use the local form instead:\n\n```yaml\n- repo: local\n  hooks:\n    - id: blockwatch\n      name: blockwatch\n      entry: bash -c 'git diff --cached --patch --unified=0 | blockwatch'\n      language: system\n      stages: [ pre-commit ]\n      pass_filenames: false\n```\n\n#### GitHub Action\n\nAdd this to `.github/workflows/your_workflow.yml`:\n\n```yaml\n- uses: mennanov/blockwatch-action@v1\n```\n\n## Supported Languages\n\nBlockWatch supports comments in:\n\n[//]: # (\u003cblock name=\"supported-grammar\" keep-sorted=\"asc\"\u003e)\n\n- Bash\n- C#\n- C/C++\n- CSS\n- Go (with `go.mod`, `go.sum` and `go.work` support)\n- HTML\n- Java\n- JavaScript\n- Kotlin\n- Makefile\n- Markdown\n- PHP\n- Python\n- Ruby\n- Rust\n- SQL\n- Swift\n- TOML\n- TypeScript\n- XML\n- YAML\n\n[//]: # (\u003c/block\u003e)\n\n## CLI Options\n\n[//]: # (\u003cblock name=\"cli-docs\"\u003e)\n\n- **List Blocks**: `blockwatch list` outputs a JSON report of all found blocks.\n- **Extensions**: Map custom extensions: `blockwatch -E cxx=cpp`\n- **Disable Validators**: `blockwatch -d check-ai`\n- **Enable Validators**: `blockwatch -e keep-sorted`\n- **Ignore Files**: `blockwatch --ignore \"**/generated/**\"`\n\n[//]: # (\u003c/block\u003e)\n\n## Known Limitations\n\n- Deleted blocks are ignored.\n- Files with unsupported grammar are ignored.\n\n## Contributing\n\nContributions are welcome! A good place to start is\nby [adding support for a new grammar](https://github.com/mennanov/blockwatch/pull/2).\n\n### Run Tests\n\n```shell\ncargo test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmennanov%2Fblockwatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmennanov%2Fblockwatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmennanov%2Fblockwatch/lists"}