Banh Mi Guard (guard)
Dual-Gate Impact Analysis & Regression Guard for AI-Assisted Development
Enforcing the Sandwich Pattern with scope control, deterministic diff auditing, project invariants and LLM gatekeeping.
guard wraps coding workflows in an automated safety harness that verifies changes before and after execution. It runs two gates around each change: a pre-task scope check and a post-task verification gate combining deterministic diff checks, static rules, project invariants, and an LLM review.
Documentation: https://okrath.github.io/banh-mi-guard/
It automates the impact and regression protocol from oh-my-ainovel across three layers:
- Deterministic gates (0 token): declared scope, base-commit diff audit, build/test command, project invariants (
guard.invariants.json), and removed-symbol reference checks. - Alibaba Open Code Review - OCR (optional full review:
guard post --fulladds an LLM review of the task's changes that reads the repository; it uses your configured LLM, costs tokens, and takes minutes). - Configured LLM (Claude, GPT, DeepSeek, Ollama, or local models): reviews the diff, verified evidence, and blast radius for a final approval or revision decision (
APPROVEDorREVISE).
Cross-Platform Installation (Windows, Linux, macOS)
Install the guard CLI globally on any platform using one of the following methods:
Method 1: Install directly from GitHub (Recommended)
# On Linux / macOS (Recommended with pipx for isolated global binary):
pipx install git+https://github.com/okrath/banh-mi-guard.git
# Or with standard pip across all platforms (Windows / Linux / macOS):
pip install git+https://github.com/okrath/banh-mi-guard.git
Method 2: Clone repository & install in editable mode
git clone https://github.com/okrath/banh-mi-guard.git
cd banh-mi-guard
pip install -e .
Environment $PATH Setup
- Windows:
guard.exeis automatically installed intoPython3xx\Scripts\guard.exe. - Linux / macOS: The
guardexecutable lives in~/.local/bin/guardor/usr/local/bin/guard.
If your terminal displayscommand not found: guard, add it to your shell configuration:# For bash: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc # For zsh (default on macOS): echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
No model download. Earlier versions shipped the "Laya" neural triage (a 554 MB ONNX model plus numpy, onnxruntime and tokenizers). It only produced informational domain / intent / risk guesses, never changed a gate decision, and scored at chance level, so it was removed in 0.11. If ~/.guard/models exists from an older version, it can be deleted.
Optional: Install Alibaba OCR CLI (for full reviews)
guard post --full adds an Alibaba OCR review and cannot approve without it; plain guard post does not need it:
npm install -g @alibaba-group/open-code-review
Check system environment health:
guard doctor
CLI Usage Workflows
1. Pre-Task Phase (guard pre)
Execute before modifying source code:
guard pre "Refactor checkout button to sticky bottom on mobile, update CSS and responsive modal"
# Declare scope explicitly (repeatable, globs allowed) when the prompt names no files:
guard pre "Review and fix bugs in the chat UI" --scope src/ui --scope src/ai/service.ts
A directory scope (src/ui) covers everything below it. Prefer it over "src/ui/**" on Windows, where the guard.exe launcher expands glob arguments even when they are quoted.
Output: Records the scope, the base commit (and a baseline snapshot with --allow-dirty), detects the repository domain, locks the invariants and evaluates them once as a baseline, and writes ### 🔍 PRE-TASK IMPACT NOTE to .guard/PRE_TASK_NOTE.md.
Gate rules that keep the pre-task gate meaningful:
- Scope is only what the prompt names or
--scopedeclares. Files that are already dirty are never added to it. With no scope, the post report says scope was not audited instead of flagging every file. - A dirty working tree is refused.
--allow-dirtyrecords those files as a pre-existing baseline and snapshots them withgit stash create(pinned atrefs/guard/baseline; the working tree is not touched). Post then reviews only the edits made after pre (diff against the snapshot), marks untouched filesPRE-EXISTING, and raisesSCOPE-003as a MEDIUM notice. - An unfinished (pre without post) or rejected (
REVISE) session is refused.--forcerestarts it but keeps its baseline, snapshot, base commit, scope and locked invariants. The restart is listed in both reports, and files covered only by scope added in the restart fail asSCOPE-004. Stashing, restarting and popping, or committing mid-task, is still audited, because post diffs against the base commit recorded at the first pre. - Paths come from
git status --porcelain -z, so renamed files and names with spaces or Vietnamese characters are tracked correctly. Globs are accepted only via--scope: prose such as "do not edit *.css" never widens scope.
Project invariants (guard.invariants.json)
Invariants turn the rules an agent is told to respect (for example the invariant section of AGENT.md) into checks guard runs on every task, replacing the generic domain templates. They come from two files, which guard merges:
| File | Owner | In Git |
|---|---|---|
.guard/invariants.json |
guard (setup, guard invariants init, rules learned in review) |
never (Git-excluded) |
guard.invariants.json in the repository root |
the user or team | committed by the user, read-only for guard |
A rule in the repository file wins over a local rule with the same id.
guard invariants init # create the local file; imports numbered items under an "Invariants" / "Bất biến" heading of AGENT.md / AGENTS.md / CLAUDE.md
guard invariants init --shared # create guard.invariants.json in the repository root instead, to commit for the team
guard invariants check # evaluate every check on the current code, no session needed (exit 1 = a check fails, 2 = file missing/invalid)
Setup creates the local file automatically and never overwrites an existing one. Imported entries start without checks (UNVERIFIED) until you add them:
{"invariants": [
{"id": "CHAT-01", "description": "Chat requests never time out",
"checks": [{"files": "src/ai/**/*.ts", "forbid": "AbortSignal\\.timeout"}]},
{"id": "UX-01", "description": "Message renders within 1ms"}
]}
Every check runs on the current file contents. forbid fails when any matched file contains the regex, and require fails when none of them does. A check whose files glob matches nothing also fails, and so does an invalid regex. A malformed guard.invariants.json makes guard pre stop with the parse error. Invariants without checks are reported as UNVERIFIED (manual) and never counted as passed. A check that was already failing when pre ran is reported as BASELINE_FAILED (a warning), so an old defect does not block unrelated tasks; a check that starts failing during the task blocks approval. When a task adds or edits guard.invariants.json, post also self-checks the new file on the current tree (... (new guard.invariants.json, self-check)), so a rule that fails on the code it was written for blocks approval. This repository's own gate-integrity invariants live in guard.invariants.json.
Rules learned during review. The LLM gate may propose durable rules it notices in the diff (INVARIANTS: section of its answer). Guard writes a proposal into the local .guard/invariants.json (never into the repository's file) only when its id and description are new and its check passes on the current code; it is tagged "origin": "llm:<session>", listed under "Invariants learned in this review", and enforced from the next guard pre. To share a learned rule with the team, copy it into guard.invariants.json yourself. Rejected proposals are listed with the reason.
The rulebook cannot be weakened as a side effect. Adding invariants never counts as out of scope. Removing an invariant or changing its checks raises INV-WEAKENED: CRITICAL (blocks) unless the task declares guard.invariants.json in its scope. A declared edit is reported as MEDIUM for the reviewer, and the locked rules it removes are shown as RETIRED (a changed rule is re-evaluated with its new definition) instead of failing.
2. Post-Task Phase (guard post)
Execute after code modifications are complete:
# Standard verification:
guard post
# Deep focus on code hygiene & dead code:
guard post --focus dead-code
# Deep focus on KISS, YAGNI & over-engineering:
guard post --focus simplicity
Output: Inspects git diff, detects out-of-scope and deleted files, scans the built-in rules and code hygiene, runs the Alibaba OCR review (only with --full), executes the build command, runs invariant checks, and requests Final Gate Approval from your configured LLM (APPROVED or REVISE) in .guard/POST_TASK_REPORT.md.
The report names the gate that actually ran. It says "LLM Gate" only when the LLM answered. Otherwise it says "Heuristic Gate (no LLM review)" and records the reason (llm_error, review_mode in .guard/session.json), for example a timeout or a model that refused to review a part. A heuristic REVISE (failed build, violated invariant, CRITICAL rule, out-of-scope file, with --full an OCR review that did not run or a high/critical OCR finding, or a score below 7.5) is final; otherwise the LLM decides. Large diffs are reviewed in parts of up to 80k characters (one REVISE rejects the whole diff); deleted files are sent as a one-line note; an answer that ignores the SCORE/VERDICT format is retried once. A review request has no time limit: AI review takes as long as it takes, and it ends when the LLM answers or its provider returns an error (llm.timeout applies only to guard config test pings). Deleting code earns no score bonus.
Alibaba OCR review (guard post --full). OCR is optional: a plain guard post does not run it and the report says "Alibaba OCR: not run". When a plain guard post is approved, the report tells the agent to ask you before committing whether you want a full review with OCR: say yes and it runs guard post --full, say no and the gate approval is enough. Asking for a full review at any time also runs it; Git hooks never run it. guard config ocr always runs it on every post and guard config ocr optional switches that off; only you can run guard config ocr, in an interactive terminal. With --full, guard runs ocr review from the base commit recorded at pre to a snapshot of the working tree, so commits made mid-task, unstaged edits and new files are all reviewed (the snapshot is a Git object built in a throwaway index; your index, working tree and branches are not touched). The task prompt is passed as --background. It takes minutes, not seconds, and has no time limit: guard passes --timeout 0 and sets OCR's per-request limit, which OCR cannot switch off, to ten years (OCR_LLM_TIMEOUT, overriding a shorter value in the environment), so the review ends only when OCR finishes or reports the provider's error; Ctrl+C stops it. A high or critical OCR finding blocks (REVISE); medium and low findings are listed and passed to the LLM gate. OCR not running (not installed, a provider error, a partial review) is OCR-RUN (HIGH) and also blocks: the report says "did not run" with the reason, never a pass. Findings on files that were already dirty before pre and that the task left untouched are dropped, and the report counts them; a dirty file the task edits is reviewed like any other. A partial review (the provider failed on some files) is resumed once with --resume, so only the failed files run again. A gateway that drops parallel requests needs a lower ocr.concurrency in ~/.guard/config.json (0 keeps OCR's default of 8).
Commit messages. Who writes them is your choice, saved machine-wide: guard config commit auto lets the agent write them (a conventional message that never mentions guard), guard config commit ask makes the agent ask you for every one. The post report of approved work ends with a Commit line that tells the agent which mode is set; while none is set, it tells the agent to ask you which one you want. guard install and guard doctor show the choice as "Commit messages".
Removals that a compiler cannot see are checked over the whole repository: every string key (case 'edit':), export and CSS class deleted by the diff is searched for. One that is no longer defined but still referenced raises DEAD-REF (HIGH) with the locations. The summary line goes into every LLM review part as verified evidence, so a batched review does not have to guess about references in another part.
SEC-003 (innerHTML/outerHTML = and += sinks) checks every assignment on a line, and a comment that mentions "sanitize" does not silence it. Only an empty literal, a value that is exactly one DOMPurify.sanitize(...) call, or an explicit // guard-allow SEC-003: <reason> exempts a line, and that marker is still listed as a LOW finding.
Several agent sessions in one folder. Each hook event carries the agent's own session id (session_id, conversation_id, sessionId or thread_id in its payload). The agent session whose command ran guard pre owns that guard session; the pre note and the post report name it (claude-code session 1a2b3c4d), and pre records that session's own prompt, never the last one typed into any agent there. Another agent session in the same folder is judged on its own work only: its stop never waits for the owner's post or answers for the owner's edits, its edit of a tracked file and its commit are refused with the owner's task and the fix (git worktree add for parallel work, or wait until the task is committed), and files its commands change outside the owner's scope are reported to it and stop it until they are undone. A guard pre --force from another session is refused. A guard pre run by hand has no owner, nor has one started by two agent sessions at the same moment, and an agent that sends no session id keeps the shared behaviour; guard doctor names those agents. Run parallel agents in one git worktree each.
Git hooks call guard post --hook. It skips repositories that have no guard session, and it also skips when guard is not on PATH. An approved session covers only the exact file contents it approved: committing them passes, while any file changed after the approval (or unrelated to it) is blocked until a new guard pre/guard post round. guard reset closes the current session (for work that was committed or abandoned) and archives it to .guard/history/. With global hooks (core.hooksPath), each repository's own hook (and a .guard.bak original) in its common hooks/ directory still runs first, including from linked worktrees. guard run and the agent wrapper script accept the same pre-task flags (--scope, --allow-dirty, --force; the wrapper script reads them from GUARD_PRE_ARGS).
3. Install (guard install)
There are two ways to install guard. Neither needs a per-repository step.
# Global (whole machine):
# - Git hooks for every repository (git config --global core.hooksPath ~/.guard/hooks)
# - the guard directives in the global instruction file of each agent found on this machine:
# ~/.claude/CLAUDE.md (Claude Code), ~/.codex/AGENTS.md (Codex), ~/.gemini/GEMINI.md (Gemini CLI),
# ~/.config/opencode/AGENTS.md (opencode). Existing content is kept (backup: *.guard.bak).
guard install
# Workspace (one folder only):
# - the guard directives in CLAUDE.md and AGENT.md of that folder
# - Git hooks in the folder's repository, or in every Git repository below it
guard install --workspace path/to/workspace
# Remove what install added (marked blocks and guard's hooks; a foreign core.hooksPath is kept):
guard uninstall [--workspace path/to/workspace]
# Rewrite what guard installed earlier to the current version. Runs automatically after upgrades.
guard hook refresh
How it chains together. The agent reads the guard directives (global or workspace) and runs guard pre before editing. The first guard run inside a repository sets it up (below), and the Git hook checks every commit. Agents without a global instruction file (Cursor, omp) see the directives through a workspace install.
Agent hooks: the directives ask, the hooks enforce. guard agent add <agent> puts guard on the agent's own path, so an edit before guard pre or outside the scope, a stop with unapproved edits and a commit without an approval are refused with the reason instead of relying on the agent to obey. Guard ships checked adapters for Claude Code (CLI, Desktop, IDE extensions), Codex, Cursor, Grok Build, Gemini CLI, Google Antigravity, ZCode, omp, pi and opencode; guard agent list shows them and guard install offers the ones it finds. Some agents cannot refuse everything (pi and opencode have no stop that can be refused; Antigravity's is only reminded; Codex edits are checked right after they happen): guard agent add says so first, and the Git hook remains the backstop. For any other agent guard investigates it on this machine, has the LLM propose an adapter, tests it with you (guard agent test), and, when it cannot be set up, tells you why with a prefilled GitHub issue. Details: docs/cli-reference.md.
The older guard hook install still works: without options it runs guard install, and its options (--stealth, --mode, --all-repos, --select-repos, --global) keep their previous per-repository behavior. guard hook status shows the current hooks and directives.
Safe-append for agent docs written by guard.
guard install(your global agent docs),guard install --workspace(only docs Git does not track) and the legacyguard hook install --mode agent|allappend a marked block and keep your content, with a one-time.guard.bakbackup. Uninstall removes only the marked block.
Guard never creates a diff in your repository. Inside a repository it writes only where Git tracks nothing: the
.gitdirectory and the.guard/folder, which it keeps out of Git through.git/info/exclude(never through.gitignore). Files that belong to the repository (agent docs, hooks kept in the tree such as.husky/,guard.invariants.json) are only read. When one of them needs a change,guard doctortells you what to change; you decide.
Automatic repository setup. The first time guard runs inside a Git repository (guard pre, guard post, guard install), it:
- creates the local
.guard/invariants.jsonwhen the repository has no invariants yet, importing the agent docs' invariant section; - checks which hook directory Git really uses. With the global hooks nothing is added. When Git runs hooks from inside
.git, guard makes thatpre-commitcall guard. When the repository keeps its hooks in its own tree (for example husky's.husky), guard does not touch them; the setup check shows the one line to add; - records the repository in
~/.guard/repos.json.
Outside a Git repository only the agent directives apply.
Refresh after an upgrade. The first guard command after a version change rewrites what guard owns: the global hooks (when core.hooksPath points at ~/.guard/hooks), guard hooks inside .git of recorded repositories, and the directive block between the BANH-MI-GUARD DUAL-GATE HOOK: START/END markers in your global agent docs (~/.claude/CLAUDE.md, ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md, ~/.config/opencode/AGENTS.md). Agent docs inside repositories are never rewritten; an outdated or unmarked guard section there is reported by the setup check. guard hook refresh runs the same refresh on demand.
4. Process Harness Wrapper (guard run)
Wraps any developer or agent command in pre- and post-task gates:
guard run "Add shipping fee calculation endpoint" -- git status
5. On-Demand LLM Code Review (guard review)
Run an immediate architectural and safety review on the current Git diff:
# Standard on-demand review:
guard review
# Focused review on code hygiene & orphan files:
guard review --focus dead-code
# Focused review on KISS, YAGNI & over-engineering:
guard review --focus simplicity
6. Supply-Chain Security & Safe Updates (guard update)
Guard enforces a 3-day Quarantine Cooling Period on Alibaba OCR npm releases to defend against zero-day backdoors:
# Check for new releases without installing:
guard update --check
# Safely upgrade Alibaba OCR (automatically halts if release is < 3 days old):
guard update
# Bypass quarantine hold explicitly:
guard update --force
# Upgrade the Guard CLI itself directly from GitHub:
guard update self
# Check for Guard CLI updates on GitHub without installing:
guard update self --check
# Check system diagnostics & release audits:
guard doctor
Upgrading from an older version
Guard tells you what is still missing after an upgrade:
guard update selfrunsguard hook refreshwith the new version and then prints a setup check listing each missing item and the command that fixes it.- If guard was upgraded another way, the first guard command of the new version prints the same check once.
guard doctoralways shows it, in the "Installation & Repository Setup" table.
| Check says | Run |
|---|---|
| Git hooks missing / commits not checked | guard install (or guard install --workspace <dir>) |
| Agent directives missing (no agent is told to run guard) | guard install (or guard install --workspace <dir>) |
Repository hook does not call guard (hooks inside .git) |
guard hook refresh inside the repository |
Repository keeps its hooks in its tree (e.g. .husky/) |
add the line the check prints to that pre-commit (guard does not edit repository files) |
| Repository agent doc has an older or unmarked guard section | update or remove it yourself (guard does not edit repository files) |
| Guard directives without START/END markers | wrap the guard section as shown below, or delete it and run guard install |
No guard.invariants.json / invariants without checks |
guard invariants init, then add checks and run guard invariants check |
Alibaba OCR not on PATH (needed only by guard post --full) |
npm install -g @alibaba-group/open-code-review, then guard config sync |
| Commit messages: not chosen yet | guard config commit auto (or guard config commit ask) |
| Old Laya model files left by guard <= 0.10 | delete ~/.guard/models |
| Old laya-ocr-guard files (hooks, agent wrapper, exclude comment, directive blocks, the old package) | guard hook refresh; for repository files and the package, the change doctor shows |
Markers that let guard refresh a pasted directive section:
<!-- === BANH-MI-GUARD DUAL-GATE HOOK: START === -->
...guard directives...
<!-- === BANH-MI-GUARD DUAL-GATE HOOK: END === -->
After an upgrade, what guard owns is refreshed for you. guard update self starts the newly installed guard to run guard hook refresh, which rewrites the global hooks, guard hooks inside .git of recorded repositories and the marked directive block in your global agent docs. If guard was upgraded another way (for example pipx upgrade), the first guard command of the new version does the same once. Repository files (agent docs, hooks kept in the tree) are only reported, with the change to make.
Coming from laya-ocr-guard (0.10 or older). The first guard command of the new version cleans up what the old versions left wherever guard may write: their hooks inside .git (a copy is kept as <hook>.laya.bak, and the global hooks never run them), the LAYA-OCR-GUARD block in your global agent docs, the old agent wrapper in .guard/bin and the old comment in .git/info/exclude; guard hook refresh does it again on demand. guard doctor lists what is left, with the fix. Three things only you can do:
pip uninstall laya-ocr-guard(orpipx uninstall laya-ocr-guard): the old package owns the sameguardcommand.- Replace
LAYA-OCR-GUARDdirective sections in repository agent docs, and old guard lines in hooks kept in the repository tree; guard never edits repository files. - Delete
~/.guard/models(the removed Laya model, about 555 MB).
LLM Configuration & OCR Auto-Sync
Configure your LLM credentials once; guard writes them into the Alibaba OCR CLI as the custom provider guard (same URL, protocol, key and model), so OCR reviews with the same LLM. OCR keeps that key in plain text in its own configuration file (~/.opencodereview/config.json), as guard does in ~/.guard/config.json (readable by its owner only, mode 0600, on Linux and macOS). guard config llm --local does not touch OCR, because OCR's settings apply to the whole machine; run guard config sync --repo <repository> if that repository's LLM should serve OCR. guard config sync repeats that step, and ocr llm test checks it:
guard config llm
The interactive wizard offers three ways to reach an LLM:
- OpenAI / OpenAI-Compatible: OpenAI, Ollama (
http://localhost:11434/v1), DeepSeek (https://api.deepseek.com/v1), OpenRouter, vLLM, or Local Gateways (http://127.0.0.1:8090/v1). - Anthropic: Claude API (any current Claude model id).
- My agent CLI:
claude,codexorompon this machine answers the review gate through your subscription, with no API key or gateway: Claude with its tools and your MCP servers off; Codex without your config and without its shell tool, in its read-only sandbox; omp with no tools and no saved session (omp ignores a system prompt in print mode, so the rules lead the prompt, ahead of the marked repository text);guard agent add claude-code,codexorompoffers to make that agent guard's LLM.agyis not offered: headless it asks for tools that only--dangerously-skip-permissionswould allow; both in an empty folder outside any repository, with the review rules in the CLI's system prompt. Guard checks the CLI by its sign-in and model list (codex reads its catalog locally; claude answers/model, usually without a model call) and offers those models to choose from. With My agent CLI,guard post --fullruns Alibaba OCR through the same CLI: guard starts a local endpoint for that review only (127.0.0.1, a random port and token), runs OCR from a throwaway home whose OCR settings point at it (your own OCR settings are never changed), and answers each of OCR's requests with one CLI call that has OCR's tools written into the prompt and returns one tool call, which OCR runs. Two requests run at a time. The report saysanswered by the <cli> CLI (tool calls written as text), and a CLI that fails is an OCR failure with its reason. When that review does not run, guard warns and falls back to OCR's delegation mode:ocr delegatelists the files and OCR's rules for them, and the same CLI, tools still off, reviews each rule group's diff in one prompt (in parts of up to 150,000 characters when it is larger). The report then saysanswered by the <cli> CLI as the fallback after …with the first path's reason. A file the fallback did not review (the CLI failed, or its diff is over 150,000 characters) keeps it a failed review.
Verify connectivity with an instant, token-free latency ping:
guard config test
Three-Pillar Architecture
Every change is checked twice, once before and once after the developer or agent edits:
- Pre-Task Gate (
guard pre): Scope declaration, baseline snapshot, invariant locking, and contract hints (keyword scan of up to 5 scoped files). - Core Edits: Scoped code implementation within declared boundaries.
- Post-Task Gate (
guard post): Deterministic diff blast-radius audit, built-in static rules, Alibaba OCR review (with--full), project build/test command, invariant checks, and LLM gate approval.
[User Task / Issue Prompt]
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. PRE-TASK PHASE: `guard pre "<prompt>"` │
│ • Scope declaration & repository domain detection │
│ • Contract hints (keyword scan of up to 5 scoped files) │
│ • Lock Invariant Rules (Must NOT be broken) │
│ ➔ Emits: "### 🔍 PRE-TASK IMPACT NOTE" │
└─────────────────────────────────────────────────────────────┘
│
▼ (AI Coding Agent / Developer modifies code)
│
┌─────────────────────────────────────────────────────────────┐
│ 2. POST-TASK PHASE: `guard post` │
│ • Diff Inspector (0-cost): Diff audit & blast radius check │
│ • Static rules: secrets, injection, XSS, infra, UX │
│ • Alibaba OCR (LLM, --full): Reviews the task's changes │
│ • Hygiene Engine (0-cost): Detects orphan files & draft names│
│ • Project Health Check: Automated compile & test execution │
│ • Invariant checks (0-cost): guard.invariants.json rules │
│ ➔ Compiles: "### 🧪 POST-TASK VERIFICATION" │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. FINAL SAFETY GATE: YOUR CONFIGURED LLM │
│ (Claude / GPT / DeepSeek / Ollama / OpenAI-compatible) │
│ • Reviews the report, verified evidence & batched diff │
│ • Technical Audit (architecture, memory, nulls, scope) │
│ • Cross-Platform UX/UI & Ergonomics Assessment │
│ • Verdict: [APPROVED] or [REVISE] with Actionable Remediation│
└─────────────────────────────────────────────────────────────┘
Multi-Domain Coverage (FE, BE, Fullstack, Infra, MB)
guard detects the repository's domain from the repository itself, scoring several signals instead of taking the first marker file: Node dependencies of every package.json (monorepos included), web framework configs, index.html, UI component files, server//api/ directories, Python/Go/Rust/Java manifests, Terraform/Helm/Kubernetes and container files. A Node backend is not reported as frontend, an app with a Dockerfile is not infra, and a repository with both a web client and a server is fullstack.
The domain selects the template invariants used when the repository has no project invariants (most of them are diff heuristics and show as UNVERIFIED). The build command is chosen per ecosystem, not per domain:
| Domain | Typical stacks | Template invariants (only without project invariants) | Build / test command guard runs |
|---|---|---|---|
| FE (Frontend) | React, Next.js, Vue, Svelte, Vite | loading / disabled states, keyboard shortcuts (Escape, Enter), responsive layout |
<pm> run build (or check, else <pm> test) |
| BE (Backend) | Go, Python, Node APIs (Fastify, Express, NestJS), Rust | JSON schema compatibility, parameterized SQL, atomic DB transactions | go test ./..., cargo test, pytest, or the package script for Node |
| Fullstack | web client + server in one repository or monorepo | frontend templates | the package script |
| Infra (DevOps) | Terraform, Helm, Kubernetes (IaC-only repositories) | no hardcoded secrets, no 0.0.0.0 DB binding, health checks | terraform validate, docker compose config |
| MB (Mobile) | Flutter, React Native, iOS, Android | permission flows, SafeArea, offline fallback | flutter analyze, ./gradlew test |
<pm> is pnpm, yarn, bun or npm, from the lockfile. See docs/quality-pillars.md for what each rule actually checks.
Code Hygiene & Dead Code Gate
AI coding agents often leave behind code rot: scratchpad files, commented-out dead code blocks, and unreferenced helper functions. guard prevents codebase rot via deterministic checks on newly added files and removed references, while commented-out code, unused imports and private functions are checked by the LLM review in every language:
| Rule ID | Severity | Inspection Rule | Detection Target |
|---|---|---|---|
DEAD-001 |
HIGH / MEDIUM |
Orphan & Draft Files | Unreferenced newly added files or draft names (*.tmp, *backup*, temp_*, test_scratch*). |
DEAD-REF |
HIGH |
Removed References | Removed string keys, exports or CSS classes that are still referenced elsewhere in the repository. |
| — | — | Commented Code & Unused Symbols | Commented-out code blocks, unused imports and private functions are checked by the LLM review in every language. |
Two-Tier Execution Strategy
- Commit-Level (Diff-Level, <50ms): Automatically runs during
guard postand Git hooks. Checks newly added files for orphan status. - Focus-Level (Full-File Deep Scan): Triggered via
--focus dead-code. Scans touched files for orphan files and directs LLM scrutiny to dead code, unused imports and zombie code blocks.
Simplicity & Engineering Frugality (KISS & YAGNI)
Inspired by Larry Wall's virtue of Laziness and Dietrich Gebert's Ponytail philosophy: "The best code is the code you never wrote."
AI coding agents often add libraries for trivial tasks or abstractions for a few lines of logic. guard checks new code against this necessity ladder:
| Rule ID | Severity | Inspection Rule | Detection Target |
|---|---|---|---|
LAZY-001 |
HIGH |
Dependency Bloat | Redundant npm/pip packages (is-odd, uuid, mkdirp, rimraf, pathlib2, mock) when native APIs or stdlib suffice. |
| — | — | Over-Engineering & Reinvented Helpers | Premature abstractions, pass-through wrappers, and reinvented standard helpers are checked by the LLM review in every language. |
NET-LOC |
ℹ️ Info | Change Size | Reports net lines added or removed. It is informational only: deleting code earns no score bonus. |
Running the Test Suite
The project includes a comprehensive end-to-end integration and unit test suite (140+ tests):
pytest
Releasing (maintainers)
A version reaches PyPI through GitHub: bump the version in pyproject.toml, guard/__init__.py and docs/index.html, merge it, then publish a GitHub Release whose tag is the version with a leading v (for example v0.15.0). The Publish to PyPI workflow builds the sdist and wheel and uploads them with PyPI trusted publishing, so no token is stored; it refuses a tag that differs from the two version strings. One-time setup on pypi.org: add a (pending) trusted publisher for project banh-mi-guard, owner okrath, repository banh-mi-guard, workflow publish.yml, environment pypi, and create the pypi environment in the repository settings. A version on PyPI cannot be changed or reused: a mistake is fixed by the next version.
Metadata
Release files for banh-mi-guard 0.15.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| banh_mi_guard-0.15.0.tar.gz | 370.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| banh_mi_guard-0.15.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 627.3 kB
Release files / banh_mi_guard-0.15.0.tar.gz
| Download URL | banh_mi_guard-0.15.0.tar.gz |
|---|---|
| Size | 370.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
60048de864bae96a4258b88b3142c95ae4cf31fdccc6cf8ac872a4bbb277df29
|
|
BLAKE2b-256 checksum How to use checksums |
2cfc52694d77cd19b3b6e10827ec8e5a1c2d2518f67a1b7deb96d2a8a66b1c94
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / banh_mi_guard-0.15.0-py3-none-any.whl
| Download URL | banh_mi_guard-0.15.0-py3-none-any.whl |
|---|---|
| Size | 256.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df510dcfb5f07622816bf00a3d83fff9f130b642a9dc15143fcd445d4e001f34
|
|
BLAKE2b-256 checksum How to use checksums |
b88af7016f48e25341db47d9ffb53d8ff178f4349b4ac3be0aa830f769180fbd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log