Skip to content

Comparison

becwright vs husky

husky runs git hooks in JS projects but ships zero checks; becwright brings the checks themselves — secrets, debug leftovers, custom rules. Use both.

Last updated

husky and becwright sit on the same git hook but answer different questions. husky answers “how do I run a command when someone commits?” — it is the de-facto hook runner of the JavaScript ecosystem and ships exactly zero checks. becwright answers “which changes must never be committed?” — it is a constraint engine with deterministic checks built in and every rule bound to its intent. If husky is only bolting guardrails onto commits for you, becwright replaces it; if husky is running lint-staged, tests or commitlint, keep it and add becwright as one more line.

TL;DR — husky is plumbing: it wires shell commands to git hooks in JS projects and does that one job well. becwright is the water: actual checks (hardcoded secrets, debug leftovers, any regex via forbid), each bound to the why behind it, with check --json and an MCP server for AI agents. They run together in one line of .husky/pre-commit.

What is husky?

husky is one of the most-installed developer tools in the JavaScript ecosystem. On install it points git’s core.hooksPath at a .husky/ directory in your repo; whatever shell lines you put in .husky/pre-commit run on every commit. Typical contents: lint-staged, a test command, or commitlint on the commit message.

That is the whole product, deliberately: husky provides the trigger and stays out of what you run. Every check is your responsibility to choose, install and configure — husky itself will happily let any commit through until you write something into the hook.

What is becwright?

becwright is a deterministic pre-commit engine built around the BEC — a Bound Executable Constraint. Each rule in .bec/rules.yaml carries its intent and why_it_matters (the bound part), a check command that returns pass/fail (the executable part), and can be exported to a single .bec.yaml bundle and imported into another repo (the portable part).

It ships built-in checks — hardcoded secrets, tokens in log calls, eval()/exec(), forgotten breakpoints, and a generic forbid regex that works in any language — plus a contract for writing your own checks in whatever language you like. For AI-driven workflows, becwright check --json and an MCP server give agents structured pass/fail with the reason attached.

husky vs becwright: side by side

Aspecthuskybecwright
What it isGit-hook runner for JS projectsConstraint engine with its own checks
Runtime neededNode.jsNone — self-contained binary via npm/pnpm (pipx optional)
Ships built-in checks?No — you supply every commandYes — secrets, debug leftovers, eval, generic forbid
Rule ↔ intent bindingNo — hooks are shell linesYes — intent and why_it_matters on every rule
Portable rule bundlesNo — copy hook files by handSingle-file .bec.yaml via export / import
Output for AI agentsWhatever your commands printcheck --json + MCP server + Claude Code plugin
EcosystemUbiquitous in JS; pairs with lint-stagedSmall, growing becs/ catalog
Bypass storygit commit --no-verify skips itgit commit --no-verify skips it too

The honest concessions: husky’s ubiquity means every JS developer already knows it, and lint-staged integration is mature and battle-tested. And neither tool survives --no-verify — both are local hooks, so re-run your guard in CI (becwright check --all) for the full guarantee.

Can becwright and husky run together?

Yes, with one thing worth knowing about hook ownership. husky works by pointing core.hooksPath at .husky/, which means the native hook that becwright install writes into .git/hooks is not the one git runs in a husky repo. That’s not a conflict — it just means that in husky projects, you invoke becwright from husky’s hook rather than relying on becwright’s own.

Using becwright with husky

Install becwright as a dev dependency — the npm package is a self-contained binary, no Python needed — and scaffold your rules:

npm install --save-dev becwright
npx becwright init        # writes a language-aware .bec/rules.yaml

Then add one line to .husky/pre-commit, before or after whatever else you run:

npx becwright check
npx lint-staged

becwright check reads the staged files from git, runs every rule in .bec/rules.yaml, and exits with code 1 if a blocking rule fails — husky sees the non-zero exit and aborts the commit. Warnings print but never block.

When does becwright replace husky?

If the only reason husky exists in your repo is to guard commits, you can drop it: becwright install sets up a native pre-commit hook directly, with no Node hook-runner in between, and it works identically in Python, Go, Rust or mixed repos where husky is an awkward fit. That is the “replaces” half of this comparison.

The “complements” half is just as real: husky remains the right tool for everything that is not a constraint — running your test suite, formatting staged files with lint-staged, validating commit messages. becwright doesn’t do any of that, on purpose.

What do you actually get out of the box?

The part husky can never give you, because it ships no checks. After becwright init, a rule like this blocks a forgotten debugger in one YAML block, no code required:

- id: no-debugger-js
  paths: ["**/*.js", "**/*.ts"]
  check: "becwright run forbid --pattern '\\bdebugger\\b'"
  severity: blocking

Or import a ready-made BEC from the catalog — becwright import shows you the rule and its check before installing anything:

npx becwright import https://raw.githubusercontent.com/DataDave-Dev/becwright/main/becs/no-console-log-js.bec.yaml

Every rule carries its intent, so when a commit is blocked, the message explains why the rule exists — which is what lets an AI agent fix the code instead of second-guessing the rule. If your agent is Claude Code, a dedicated plugin can install and drive all of this for you — see the Claude Code integration.

Which one should you pick?

In a JavaScript repo that already runs husky: keep husky, add npx becwright check to the hook, and you have deterministic guardrails in under five minutes — the quickstart covers it. In any other repo, becwright alone is enough. If your team debates pre-commit instead of husky, the same complement logic applies — see becwright vs pre-commit; and for the secrets-specific corner of this space, see becwright vs gitleaks.

FAQ

What is the difference between husky and becwright?

husky is a git-hook runner for JavaScript projects: it wires shell commands to git hooks but ships no checks of its own. becwright is a constraint engine: it ships deterministic checks — hardcoded secrets, debug leftovers, a generic forbid regex — and binds each rule to its intent in .bec/rules.yaml. One triggers commands; the other decides pass or fail.

Do I still need husky if I use becwright?

Not for guardrails alone: becwright install sets up its own native pre-commit hook with no extra dependency, in any language ecosystem. Keep husky when you also run lint-staged, tests or commitlint from your hooks. In that case husky owns core.hooksPath, so add npx becwright check as a line in .husky/pre-commit instead of relying on the native hook.

Can becwright block console.log or debugger statements in commits?

Yes. Add a rule with the built-in forbid check — for example check: "becwright run forbid --pattern console\.log" over your JS/TS globs — or import a ready-made BEC such as no-console-log-js or no-debugger-js from the catalog with becwright import. The rule runs on staged files at every commit and blocks with exit code 1 when it matches.