becwright

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

ModuleResponsibility
cli.pyArgparse CLI: init / list / check / install / uninstall / export / import
rules.pyThe Rule model and loading of .bec/rules.yaml
engine.pyGlob path matching, running checks, deciding pass/fail
git.pyRepo root, staged files, the native pre-commit hook
checks/Built-in checks (one module each)
bundle.pyExport/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

  1. A commit triggers the pre-commit hook, which runs becwright check.
  2. becwright loads the rules from .bec/rules.yaml.
  3. It asks git for the staged files.
  4. For each rule, it filters the files by the rule’s paths globs and runs the rule’s check command, passing the matching files on stdin.
  5. The check’s exit code decides the result: 0 passes; non-zero fails.
  6. 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.