English · Español
becwright is a small engine that runs checks against your files and decides whether a commit may proceed. It is language-agnostic: it never parses your code itself — it matches files by path and runs a command.
The one-sentence version: for every rule, becwright picks the files it applies to and runs a small program (the "check"); if that program reports a problem, a blocking rule stops the commit. Everything below is just that idea in detail — the components, the exact flow, and the contract a check must follow. The rest of this page is for the curious and for people writing their own checks.
| Module | Responsibility |
|---|---|
cli.py |
Argparse CLI: init / list / check / check-msg / install / uninstall / export / import / add / search |
rules.py |
The Rule model (incl. severity, target) and loading of .bec/rules.yaml |
engine.py |
Glob path matching, running checks (files or commit message), deciding pass/fail |
git.py |
Repo root, staged files, the native pre-commit and commit-msg hooks |
checks/ |
Built-in checks (one module each) |
bundle.py |
Export/import of BECs (the portable .bec.yaml) |
The engine ships as an installed package; the repo being watched only contributes
its own .bec/rules.yaml. That decoupling is why becwright can be installed once
and used across many repos.
flowchart TD
A[git commit] --> B["pre-commit hook runs 'becwright check'"]
B --> C[load .bec/rules.yaml into Rules]
C --> D[get staged files from git]
D --> E{for each rule}
E --> F[match files against rule.paths globs]
F --> G["run rule.check as a shell command, files on stdin"]
G --> H{exit code}
H -->|0| I[PASS]
H -->|non-zero| J{severity}
J -->|blocking| K[BLOCK: commit rejected, exit 1]
J -->|warning| L[WARN: commit allowed]
J -->|advisory| M[ADVISORY: best-effort, commit allowed]
- A commit triggers the
pre-commithook, which runsbecwright check. - becwright loads the rules from
.bec/rules.yaml. - It asks git for the staged files.
- For each rule with
target: files, it filters the files by the rule'spathsglobs and runs the rule'scheckcommand, passing the matching files on stdin. - The check's exit code decides the result:
0passes; non-zero fails. - If any blocking rule failed, the commit is rejected (exit 1).
warningandadvisoryfindings are printed but never block (advisoryis labelled best-effort, for non-deterministic checks like an LLM reviewer).
A separate commit-msg hook runs becwright check-msg, which applies the
target: commit-msg rules to the commit message the same way (the message is fed
to the check on stdin instead of file paths).
The engine runs rule.check as a shell command from the repo root and feeds it
the relevant file paths (one per line) on stdin — stdin is just the
program's standard input stream, the channel a command reads from. A check:
- reads the file list from stdin,
- prints any violations to stdout (shown under "Found in:"),
- exits 0 if everything is fine, non-zero if it found a violation.
Because the contract is just "files on stdin, exit code out", a check can be written in any language. See writing-checks.md.
Unlike a note in CLAUDE.md that asks an agent to behave, a BEC's check runs
against the real code on every commit and returns pass/fail regardless of who or
what produced the change. The rule carries its intent and why (the "bound"
part), the check makes it executable, and a bundle makes it portable — see
portability.md.