Concepts
Architecture
How the engine matches files, runs checks, and decides whether a commit may proceed.
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.
Components
| Module | Responsibility |
|---|---|
cli.py | Argparse CLI: init / list / check / install / uninstall / export / import |
rules.py | The Rule model and loading of .bec/rules.yaml |
engine.py | Glob path matching, running checks, deciding pass/fail |
git.py | Repo root, staged files, the native pre-commit hook |
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.
The check flow
- A commit triggers the pre-commit hook, which runs
becwright check. - becwright loads the rules from
.bec/rules.yaml. - It asks git for the staged files.
- For each rule, it filters the files by the rule’s
pathsglobs 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). Warnings are printed but never block.
git commit
└─▸ pre-commit hook → becwright check
├─ load .bec/rules.yaml into Rules
├─ get staged files from git
└─ for each rule:
├─ match files against rule.paths globs
├─ run rule.check (files on stdin)
└─ exit code 0 ........ PASS
exit code non-zero
├─ blocking ..... BLOCK (commit rejected, exit 1)
└─ warning ...... WARN (commit allowed)
The check contract
The engine runs rule.check as a shell command with cwd set to 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.
Why it is deterministic
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.