Deterministic, portable constraints (BECs) that block commits violating your rules — a safety net for AI-written and human-written code.
Project description
English · Español
becwright
Rules that run, not notes that get ignored.
See it in 5 seconds — no setup, no git, nothing on your machine is touched:
becwright demo # after installing (see below) # zero-install: npx becwright demo · or: pipx run becwright demo
becwright enforces constraints on your code deterministically: instead of
asking an AI agent to respect a rule (the way CLAUDE.md, .cursorrules,
etc. do — which the agent can read and ignore), becwright verifies the
result and blocks the commit if the rule is broken.
In plain words
New to this kind of tool? Here is the whole idea in three lines:
A commit is the moment you save your work in git. becwright is a guard standing at that door. Right before the work is saved, it runs your rules against the code:
- ✅ everything passes → the commit goes through;
- ❌ a rule is broken → it stops you, names the rule and why it exists, and waits until you fix it.
A note in CLAUDE.md is a sign asking people (and AI agents) to behave.
becwright is the guard that checks. A sign can be ignored; the guard cannot.
Two words you'll see a lot: a commit is a saved snapshot of your code in git. A hook is a small script git runs automatically at a set moment — becwright uses the pre-commit hook, which fires just before a commit is saved. You never run it by hand; git does.
The rest of this README goes from "just get me started" to the full technical detail — read as far as you need.
The problem
An AI agent writes code and leaves a note: "this must never log session tokens". That note is text. Three months later, another agent regenerates the module, doesn't read it, and drops the token into the logs. Nobody notices until it blows up in production.
Notes are probabilistic (they depend on the agent reading, understanding and obeying). becwright is deterministic: the rule runs against the real code and returns pass/fail, no matter which agent or model made the change.
| Note in CLAUDE.md | becwright rule | |
|---|---|---|
| What it does | Asks to be respected | Verifies it was respected |
| Depends on | The agent reading and obeying | Nothing — it runs against the code |
| Result | Likely | Guaranteed |
| Analogy | A "speed limit" sign | A physical bump in the road |
The two layers are complementary: CLAUDE.md prevents (so 95% comes out right the first time), becwright is the safety net for the 5% that slips through.
Core concept: BEC (Bound Executable Constraint)
A BEC is a constraint with three properties that no current artifact has together:
- Bound — the rule is born tied to the intent and the decision that created it (the why); it is not a loose rule without context.
- Executable — it carries a check that runs and returns pass/fail; it is not prose someone promises to respect.
- Portable — it can be exported from one repo and imported into another, like a package (this is what creates the network effect over time).
Features
- Deterministic enforcement — a rule is a real check that runs against your code and returns pass/fail, not a note an agent may ignore.
- Blocks the commit, not just warns — blocking rules stop
git commit; warnings inform without blocking. - Any language — the engine matches file globs and runs a command; use the
no-code
forbidregex for Python, JS/TS, Go, Rust, or anything else. - Derive rules from your
CLAUDE.md—becwright init --from-claude-mdturns the prohibitions it recognizes (secrets,eval,debugger,console.log, breakpoints, a file line cap, …) into enforceable rules; an AI agent can extend that over MCP. Judgment-based guidance stays inCLAUDE.md. - Adopt on any codebase —
--baselinestarts rules that already have violations as warnings, so a legacy repo isn't blocked on day one; graduate each to blocking as you clean it. - Guaranteed and assisted rules — deterministic rules
blockwith a 100% guarantee; judgment rules (readability, design) live asadvisory— they report via your own reviewer check (e.g. an LLM) but never block, and are labelled so you always know which findings are guaranteed vs best-effort. - Bound to the why — every rule carries its intent and reason, shown when it fires.
- Batteries-included checks —
forbid/require(a pattern that must be present) /max_lines/filename, plus secret,eval, debug and import checks — with per-ruleexclude:to silence false positives. - Portable BECs —
exporta rule to a single.bec.yamlandimportit into another repo; custom checks travel with their code. - Offline catalog —
becwright search/addinstall ready-made rules with no URL, shipped inside the package. - No Python required — install via npm/pnpm as a self-contained binary, or via pip/pipx.
- Fits your setup — native git hook, or plug into the pre-commit framework or Husky.
- AI-agent ready — Claude Code plugin,
check --json, and an MCP server whose tools let an agent propose, preview and add rules from yourCLAUDE.md. - Tiny & trustworthy — small, dependency-light (
pyyaml), noeval/exec, dogfooded in CI.
Use cases
- Turn your
CLAUDE.mdinto guardrails — the deterministic parts become BECs that can't be ignored; the judgment calls stay as prose. - Adopt gradually on a legacy repo —
--baselinewarns on existing debt without blocking commits, then tighten to blocking rule by rule. - Stop secrets before they land — API keys, tokens, private keys, hardcoded passwords.
- Keep debug leftovers out —
breakpoint(),pdb,debugger;,console.log,dbg!, straypanic(). - Ban risky APIs / enforce conventions —
eval/exec, a file-length cap, file-name rules, or any pattern you forbid with a one-line regex rule. - Enforce commit-message rules — Conventional Commits, or block AI attribution
trailers, via a
target: commit-msgrule and thecommit-msghook. - Guard AI-written code — the deterministic net for what an agent regenerates and forgets.
- Enforce team conventions — encode a decision once as a BEC and share it across every repo.
How to use it
You install becwright once; each project only adds a small .bec/rules.yaml.
Two steps and you're done.
1. Install — one line:
npm install -g becwright
Prefer pnpm, pip, or a project-local install? →
pnpm add -g becwright
pipx install becwright # or: pip install becwright
npm install --save-dev becwright # project-local; the hook finds it in node_modules/.bin
Via npm/pnpm there's no Python required — a self-contained binary ships per
platform (linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64).
On any other platform, use pipx install becwright.
2. Set it up — inside your project:
becwright init # detects your language, writes .bec/rules.yaml, installs the pre-commit hook
That's it. From now on every git commit runs the checks by itself, and stops a
commit that breaks a blocking rule. You never call becwright by hand again.
Adopting on an existing codebase? Use becwright init --baseline: rules that
already have violations start as warning (nothing legitimate is blocked)
while clean rules start as blocking. Fix the debt over time, then graduate each
rule to blocking.
Already have a CLAUDE.md? becwright init --from-claude-md reads it and
turns the prohibitions it recognizes (secrets, eval, debugger, console.log,
breakpoints, …) into enforceable rules — the deterministic safety net under the
prose. Judgment-based guidance stays in CLAUDE.md. Review the result; combine
with --baseline to adopt on a dirty repo in one step.
Available commands:
| Command | What it does |
|---|---|
becwright demo |
Show becwright block a sample bad commit (no setup, no git needed) |
becwright init |
Scaffold a starter .bec/rules.yaml and install the hook |
becwright init --baseline |
Same, but start already-violated rules as warning (adopt without blocking) |
becwright init --from-claude-md |
Derive rules from the repo's CLAUDE.md (best-effort) |
becwright list |
List the built-in checks |
becwright check |
Runs the rules over the staged files |
becwright search [query] |
Lists ready-made BECs from the built-in catalog |
becwright add <name> |
Installs a catalog BEC into .bec/rules.yaml (offline) |
becwright install |
Installs the native pre-commit hook |
becwright uninstall |
Removes the hook |
becwright export <id> |
Exports a BEC to a .bec.yaml file |
becwright import <file|URL> |
Imports a BEC from another repo |
Already using pre-commit or Husky?
If your repo already manages git hooks, becwright plugs in without becwright install.
pre-commit — add this to .pre-commit-config.yaml:
repos:
- repo: https://github.com/DataDave-Dev/becwright
rev: v0.3.0
hooks:
- id: becwright
Husky (JS/TS repos) — in .husky/pre-commit:
npx becwright check
Either way becwright still reads .bec/rules.yaml and blocks the commit on a
broken blocking rule. You only need becwright init once to scaffold the rules
(skip its hook install if another tool owns the hook).
Use with AI agents (Claude Code)
becwright is the deterministic net for what an AI agent lets slip. There is a Claude Code plugin so an agent can install and drive it for you:
/plugin marketplace add DataDave-Dev/becwright
/plugin install becwright@becwright
It adds a becwright skill and a /becwright command. See
integrations/claude-code/.
For structured results, becwright check --json prints a machine-readable
summary, and becwright mcp (install the mcp extra: pipx install "becwright[mcp]") runs an MCP server — MCP is a standard way for AI tools to
plug in extra abilities — exposing check and list_checks to any agent. See
documentation/mcp.md.
A rule in .bec/rules.yaml:
rules:
- id: no-token-in-logs
intent: >
Session tokens and credentials must never reach any log.
why_it_matters: >
If a token shows up in the logs, anyone with access to them can steal a
user's session.
paths: ["src/**/*.py"]
exclude: ["src/logging_setup.py"] # optional: globs carved out of paths
check: "becwright run no_token_in_logs"
severity: blocking # blocking = stops the commit | warning = only warns
exclude subtracts globs from paths, so one rule can cover a whole language
while skipping the files that would only produce false positives — vendored or
generated code, or the check's own implementation. It travels with the rule
through export / import. Full field reference:
documentation/usage.md.
How becwright compares
becwright is not a linter and not just a hook runner — it is the layer that makes a rule portable and bound to its reason, and blocks the commit on it.
| becwright | pre-commit / Husky | gitleaks / linters | CLAUDE.md / .cursorrules | |
|---|---|---|---|---|
| Runs a real check | ✅ | ✅ (runs other tools) | ✅ | ❌ prose |
| Blocks the commit | ✅ | ✅ | ✅ | ❌ |
| Carries the why (intent) | ✅ | ❌ | ❌ | ⚠️ not enforced |
| Portable rule between repos | ✅ export/import |
⚠️ copy config | ⚠️ | ⚠️ |
| Any language, no per-tool plugin | ✅ forbid regex |
⚠️ | ❌ tool-specific | n/a |
becwright complements these rather than replacing them: run gitleaks or a linter as a becwright check, or add becwright inside pre-commit / Husky. The difference is that a BEC binds the rule to its intent and travels between repos.
Included checks
becwright ships ready-to-use checks. Each one is a module invoked from the
check field. They work by searching the text of your files for a pattern
(a regex — a search pattern for text, like "find this exact word"), rather
than truly understanding the code. That keeps them simple and predictable: they
may miss exotic cases, and the real value is in tying each rule to its why.
| Check | What it detects | Language | Suggested severity |
|---|---|---|---|
forbid |
Any regex you pass (--pattern) |
any | depends on the case |
require |
A regex (--pattern) that must appear (e.g. a license header) |
any | depends on the case |
max_lines |
Files longer than --max lines |
any | warning |
filename |
File names matching --forbid or not matching --require |
any | depends on the case |
no_token_in_logs |
Tokens/credentials in log calls | Python | blocking |
hardcoded_secrets |
AWS keys, private keys, password = "..." literals |
any | blocking |
debug_remnants |
Forgotten breakpoint(), pdb.set_trace(), import pdb |
Python | blocking |
dangerous_eval |
eval() / exec() calls |
any | blocking |
conflict_markers |
Leftover git merge conflict markers (<<<<<<<) |
any | blocking |
wildcard_imports |
from x import * |
Python | warning |
Example rules to copy into your .bec/rules.yaml:
rules:
- id: no-hardcoded-secrets
intent: >
No secret (key, token, password) should be hardcoded in the code.
why_it_matters: >
A secret in the repo stays in git history forever and is visible to
anyone with access to the code.
paths: ["src/**/*.py"]
check: "becwright run hardcoded_secrets"
severity: blocking
- id: no-debug-remnants
intent: >
Debug code (breakpoints, pdb) must not be committed.
why_it_matters: >
A forgotten breakpoint hangs the process in production or CI.
paths: ["src/**/*.py"]
check: "becwright run debug_remnants"
severity: blocking
- id: no-dangerous-eval
intent: >
Do not use eval()/exec(), which execute arbitrary code.
why_it_matters: >
eval/exec on untrusted input is remote code execution.
paths: ["src/**/*.py"]
check: "becwright run dangerous_eval"
severity: blocking
- id: no-wildcard-imports
intent: >
Avoid 'from x import *', which pollutes the namespace.
why_it_matters: >
Wildcard imports hide where each name comes from and break static
analysis.
paths: ["src/**/*.py"]
check: "becwright run wildcard_imports"
severity: warning
Ready-made rules (no writing required)
Don't want to write rules yourself? The catalog ships inside becwright, so
you can install a rule with one command — no URL, works offline. becwright shows
you the rule, then drops it into your .bec/rules.yaml, ready to go:
becwright search # list every BEC in the catalog
becwright search secret # filter by a word
becwright add no-token-in-logs # install one (Python)
becwright add no-debugger-js # JavaScript / TypeScript
becwright add no-hardcoded-secrets # any language
The full list (Python, JS/TS, Go, Rust) lives in
src/becwright/becs/.
Any language
becwright is language-agnostic: the engine only filters files by their
paths (written as globs — file patterns like src/**/*.js, where * means
"any name" and ** means "any folder, however deep") and runs the check as a
command; it never assumes Python. You can watch JavaScript, Go, Rust, or
anything else.
The fastest way to write a rule for another language —without writing code— is
the forbid check, which fails if a regex appears in the files:
rules:
- id: no-debugger-js
intent: >
Do not leave 'debugger;' in JavaScript/TypeScript code.
why_it_matters: >
A forgotten 'debugger' halts execution and should not reach production.
paths: ["**/*.js", "**/*.ts"]
check: "becwright run forbid --pattern '\\bdebugger\\b'"
severity: blocking
forbid accepts --pattern REGEX, --ignore-case and --message TEXT. For
finer checks, write your own script in whatever language you want (an executable
that reads the file list from stdin and exits with code 0/1) and point check
at it.
Sharing BECs between repos
A BEC is portable: you can take it out of one repo and install it in
another. A bundle is a single self-contained .bec.yaml file (the rule + the
check's code if it is custom).
# In the source repo: export a rule to a file
becwright export no-token-in-logs -o no-token-in-logs.bec.yaml
# In another repo: import it (from a file or an http/https URL)
becwright import no-token-in-logs.bec.yaml
becwright import https://example.com/no-token-in-logs.bec.yaml
On import, becwright shows the check's code and asks for confirmation before
installing it: importing a BEC is importing code that will run on every commit.
Use --yes to skip the confirmation in automated environments.
There is a catalog of ready-to-use BECs shipped inside becwright: run
becwright search to list them and becwright add <name> to install one (they
also live in src/becwright/becs/ for browsing).
Built-in checks (becwright run *) travel with the package, so
the bundle only stores their name. A custom check (.bec/checks/foo.py)
travels with its code embedded and lands in .bec/checks/ of the target repo.
Documentation
Full docs live in documentation/. Each page opens with a
plain-language summary and then goes deeper, so start wherever you are:
- Just getting started: usage — install, the commands, and how to write a rule.
- Want to add your own rule: writing checks
— from the no-code
forbidshortcut to a custom check in any language. - Sharing rules between projects: portability.
- Curious how it works inside: architecture & flow.
- Wiring it to an AI agent: MCP & JSON output.
Current status
becwright is published and installable on every platform: via npm/pnpm as a
self-contained binary (no Python) and via pip/pipx. The packaged engine
(src/becwright/) ships a CLI (demo / init / list / check (with
--json) / run / install / uninstall / export / import / mcp), a native git
hook, built-in checks (Python + the generic forbid for any language), BEC
portability between repos, and a catalog with Python, JS/TS, Go and Rust BECs.
For AI agents there is a Claude Code plugin and an MCP server
(becwright mcp) alongside structured check --json output. The original
prototype is archived under prototype/ as a reference, and the test suite
is green.
Future work (AST analysis, deep per-language tooling, cryptographic signing of verifications) is documented in the project plan.
Roadmap
becwright is intentionally small. On the horizon:
- Grow the
becwright addcatalog with more languages and common rules. - A landing page and a richer
examples/set. - More built-in checks, driven by real usage.
Deliberately out of scope to stay simple and deterministic: AST-based analysis, deep per-language tool suites, and cryptographic signing of BECs.
FAQ
Doesn't pre-commit already do this? pre-commit runs tools; it doesn't
give you a rule that carries its why and travels between repos. You can even run
becwright inside pre-commit — see above.
Do I need Python? No. npm i -g becwright installs a self-contained binary;
pipx install becwright also works.
Does it work on Windows? Yes, via Git Bash (the git hook is a sh script,
which Git for Windows provides). The becwright CLI itself is cross-platform.
How do I ignore a single line? Add a becwright: ignore comment on it.
How is "becwright" pronounced / what does it mean? bec-wright — a "wright" is a maker (as in playwright), so becwright is "the one who makes BECs".
Is it safe to import a BEC? becwright shows the check's code and asks for confirmation before installing. Treat an untrusted bundle like any untrusted script.
Contributing
Contributions are welcome — see CONTRIBUTING.md and the Code of Conduct. Found a security issue? Please follow the security policy. The changelog tracks every release.
License
MIT © Alonso David De Leon Rodarte
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file becwright-0.4.0.tar.gz.
File metadata
- Download URL: becwright-0.4.0.tar.gz
- Upload date:
- Size: 59.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86eb08056e0ae81df43bad76963fb7e988c8ce6cc367be5cfdcc4c3fd092323a
|
|
| MD5 |
90515cd49353ef54f525010044d6acfb
|
|
| BLAKE2b-256 |
05acd00ea52c9cfb0160a3080898a48ecd42c53b23ff8208260b0ff3256565ba
|
Provenance
The following attestation bundles were made for becwright-0.4.0.tar.gz:
Publisher:
release.yml on DataDave-Dev/becwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
becwright-0.4.0.tar.gz -
Subject digest:
86eb08056e0ae81df43bad76963fb7e988c8ce6cc367be5cfdcc4c3fd092323a - Sigstore transparency entry: 2040584601
- Sigstore integration time:
-
Permalink:
DataDave-Dev/becwright@02a97da78b95cbf270083a4508e31b9e1631d83a -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/DataDave-Dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@02a97da78b95cbf270083a4508e31b9e1631d83a -
Trigger Event:
release
-
Statement type:
File details
Details for the file becwright-0.4.0-py3-none-any.whl.
File metadata
- Download URL: becwright-0.4.0-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2968b716e11a82e376d992fe0271bc4601b40a9b5693ca2d47b0761a732ade3c
|
|
| MD5 |
3c5471f0be925ef2da34b31f12d7beca
|
|
| BLAKE2b-256 |
366221862ca28d86817f56e3dd38dd71c2e0dd78a66d62139b9c6eb6a2b3747f
|
Provenance
The following attestation bundles were made for becwright-0.4.0-py3-none-any.whl:
Publisher:
release.yml on DataDave-Dev/becwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
becwright-0.4.0-py3-none-any.whl -
Subject digest:
2968b716e11a82e376d992fe0271bc4601b40a9b5693ca2d47b0761a732ade3c - Sigstore transparency entry: 2040584695
- Sigstore integration time:
-
Permalink:
DataDave-Dev/becwright@02a97da78b95cbf270083a4508e31b9e1631d83a -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/DataDave-Dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@02a97da78b95cbf270083a4508e31b9e1631d83a -
Trigger Event:
release
-
Statement type: