A report-only, pre-merge policy gate that runs as a Claude Code skill. It reads a diff, evaluates it against versioned rulesets, and emits a structured PASS / WARN / FAIL verdict. It never edits your code.
Install in one command —
/plugin marketplace add beemann/claudeguardthen/plugin install claudeguard@claudeguard. (What's new in 0.2.0)
ClaudeGuard is a gate, not a fixer. It answers one question on every change — "does this diff violate our policy?" — and leaves the decision to a human.
It deliberately reuses the host agent's existing engine (git, diff reading, reasoning) instead of rebuilding an LLM client, a diff analyzer, or a patch applier. The only thing ClaudeGuard owns is policy as data: a folder of plain-Markdown rules, each with a small frontmatter contract and a human-readable body describing what a failure looks like and why.
That design has three consequences worth stating up front:
- Separation of concerns. A component that both finds and fixes violations has an incentive to over-flag to look useful. A pure gate doesn't.
- No unreviewed merges. Running on a PR moments before merge, an auto-fixer would mutate the very diff a human just reviewed. ClaudeGuard reports; you act.
- Determinism & auditability. The output is a verdict table plus a parseable JSON block — diffable, reviewable, and mappable to a CI exit code.
A stronger, more deterministic core. ClaudeGuard is a deterministic gate built on a non-deterministic engine; 0.2.0 pushes determinism down toward the rules:
- Deterministic
detect/exempt. A rule can carry regex lists in its frontmatter. Detection of candidates is then reproducible — a real hit can't be silently missed — and the model only adjudicates those candidates. Shipped forno-secrets,no-conflict-markers,no-any; other rules stay judgment-only. - Testable rules. Fixtures +
scripts/test-rules.{sh,ps1}assert everydetect/exemptpattern with no LLM and no API key, so the deterministic layer is regression-tested for free. - Robust verdict. The skill ends with a
claudeguard-verdict:line the runner reads directly — no more scraping the verdict out of prose.
0.1.0 made it installable in one step: Claude Code plugin + marketplace, a
claudeguard-init bootstrap skill, a standalone scripts/install.{sh,ps1} for any
repo or CI, and the engine/policy split (universal _core ships with the engine;
your house rules and config live in your repo).
Full notes in the CHANGELOG.
Two ways to adopt ClaudeGuard, sharing one design — a universal engine
(the gate skill, _core rules, runner scripts) plus per-project policy
(rulesets/house/, claudeguard.config.json, the CI workflow).
/plugin marketplace add beemann/claudeguard
/plugin install claudeguard@claudeguard
You immediately get two skills in every project: claudeguard (the gate) and
claudeguard-init (bootstrap). To scaffold the gate into the current repo, ask:
set up claudeguard · claudeguard init
The engine updates with /plugin update. When run as a plugin, the gate reads
its _core rules from the plugin and your house rules from the project.
Vendors the engine + a policy template into your repo, self-contained with no plugin dependency — ideal for CI:
# POSIX
scripts/install.sh /path/to/your/repo# PowerShell
./scripts/install.ps1 -Target C:\path\to\your\repoThe installer is idempotent: it refreshes engine files on every run and never
overwrites your policy (rulesets/house/, claudeguard.config.json, an existing
workflow). See Forking into your project for what
it lays down.
Static linters catch syntax; they don't catch policy. "We run everything
through Docker", "no destructive migration without sign-off", "every new route
declares its auth posture", "no optimization without a measurement" — these are
team contracts that normally live in a CLAUDE.md nobody re-reads at review
time. ClaudeGuard turns those contracts into executable rules and enforces them
at the last cheap moment: before the merge.
The procedure is defined in SKILL.md and runs in five steps:
- Resolve the diff. Default base is
origin/main(merge-base, so only the branch's own commits are judged); overridable per project or per run. - Resolve rulesets. Read every
*.mdfrom the engine's_core(${CLAUDE_PLUGIN_ROOT}/rulesets/_core/when installed as a plugin) and the project'srulesets/, dedupe byid(the project copy wins so a repo can override a shipped rule), honorclaudeguard.config.json(enable/disable, severity overrides), and keep only rules whoseapplies_toglobs match at least one changed file. - Smoke check (mandatory, anti-falsification). For each kept rule, state how many changed files it actually applies to. A rule that matches zero files is dropped — the gate never invents violations to justify a rule.
- Evaluate. Judge only added/modified (
+) lines. Rules that declaredetectpatterns get deterministic candidate detection (regex finds the hits; the model only adjudicates them against the rule's exemptions); rules withoutdetectare judged by the model directly. Every violation must cite concrete evidence: a file, a best-effort line, and the exact offending line. - Verdict.
FAILif any violation ishigh/critical;WARNformedium/low;PASSif clean. The threshold is configurable viablock_on(defaulthigh).
ClaudeGuard emits a human-readable table followed by a machine-readable JSON block:
ClaudeGuard verdict: FAIL
Base: origin/main · Files scanned: 7 · Rulesets active: 5
| Severity | Rule | File | Line | Why |
|----------|-------------|-----------------|------|------------------------------|
| high | docker-only | scripts/dev.ps1 | 12 | `npm run dev` invoked on host |
| medium | no-any | src/api/user.ts | 44 | `: any` on request body |
{
"verdict": "FAIL",
"base": "origin/main",
"files_scanned": 7,
"rulesets_active": ["docker-only", "no-any", "..."],
"violations": [
{
"rule": "docker-only",
"severity": "high",
"file": "scripts/dev.ps1",
"line": 12,
"evidence": "+ npm run dev",
"why": "Command invoked on host instead of `docker compose exec app …`.",
"suggestion": "docker compose exec app npm run dev"
}
],
"block_on": "high"
}The CI runner maps the verdict to an exit code: PASS/WARN → 0, FAIL → 1.
Rules ship in two tiers:
| Tier | Folder | Intent |
|---|---|---|
| Core | rulesets/_core/ |
Universal, provider-agnostic, uncontroversial — adopt as-is. |
| House | rulesets/house/ |
Your team's opinionated rules — swap when you fork. |
Bundled rules:
| Rule | Tier | Severity | Trips on |
|---|---|---|---|
no-secrets |
core | critical | Credential/key/token literals (provider prefixes, PEM blocks, secret assignments). |
no-conflict-markers |
core | high | Unresolved git conflict markers committed to a tracked file. |
docker-only |
house | high | Project tooling run on the host instead of in-container (shell scripts & task runners). |
no-destructive-db |
house | critical | Unguarded DROP/TRUNCATE/DELETE … without WHERE, etc. |
security-routes |
house | high | A new route/handler with no visible auth, validation, or injection posture. |
no-any |
house | medium | any, as any, @ts-ignore and other TypeScript escape hatches. |
measurement-first |
house | medium | A change claiming to optimize something with no benchmark/measurement. |
Place this repo's contents where Claude Code discovers skills (e.g.
.claude/skills/claudeguard/ in your project), then ask:
run claudeguard · gate this branch · check policy compliance
# PowerShell
./scripts/check.ps1 -BaseRef origin/main# POSIX
scripts/check.sh origin/mainRequires git, the claude CLI on PATH, and ANTHROPIC_API_KEY in the
environment. An empty diff is an immediate PASS.
.github/workflows/claudeguard.yml gates
pull requests, posts the verdict as a PR comment and job summary, and blocks the
merge on a high/critical violation. Add a repository secret
ANTHROPIC_API_KEY to activate it.
Security note: the workflow uses
on: pull_request(notpull_request_target). On public repos, GitHub does not pass secrets to workflows triggered by fork PRs, so an untrusted PR cannot exfiltrate the API key — by design.
Copy claudeguard.config.example.json to
claudeguard.config.json and edit per project:
{
"base": "origin/main",
"block_on": "high",
"rules": {
"no-secrets": { "enabled": true },
"docker-only": { "enabled": true, "severity": "critical" }
}
}base— default ref to diff against.block_on— minimum severity that produces aFAIL(low|medium|high|critical).rules.<id>.enabled— toggle a rule off without deleting it.rules.<id>.severity— override a rule's shipped severity.
Drop a *.md into rulesets/_core/ (universal) or rulesets/house/
(team-specific). No code change is required — the procedure picks it up
automatically.
---
id: no-todo-without-ticket
severity: low
applies_to:
- "**/*.ts"
- "**/*.py"
enabled: true
detect: # optional: deterministic candidate detection (ERE)
- 'TODO|FIXME'
exempt: # optional: suppress candidates that cite a ticket
- '[A-Z]+-[0-9]+|#[0-9]+'
---
# No TODO without a tracking ticket
**FAIL** when the diff adds a `TODO`/`FIXME` with no issue reference.
Trip on added lines matching `TODO`/`FIXME` not followed by a ticket id
(e.g. `JIRA-123`, `#456`).
**Why:** untracked TODOs are debt that never gets scheduled.
**Suggestion:** link a ticket, or do the work now.Deterministic detection (optional). A rule may carry detect (and exempt)
regex lists. Detection then runs deterministically: a changed line is a candidate
when it matches a detect pattern and no exempt pattern, and the model only
adjudicates those candidates against the rule body. This makes recall
reproducible — a real hit can't be silently missed — and is the right shape for
pattern rules (no-secrets, no-conflict-markers, no-any). Rules with no
detect are judged purely by the model, as before.
Test your patterns. Add fixtures/<rule-id>/should-fail.txt and
should-pass.txt, then run scripts/test-rules.sh (or .ps1). It asserts every
detect/exempt pattern against the fixtures with no LLM and no API key, so
the deterministic layer is regression-tested for free.
Keep _core rules uncontroversial; anything debatable belongs in house/.
Every rule body should state, in plain language, what a FAIL looks like,
why it matters, and a suggestion — and should rely on evidence visible
in the diff.
The fastest path is the standalone installer or the
claudeguard-init skill — both lay down exactly what's below. Under the hood it:
- Copies the engine into your repo: the gate skill →
.claude/skills/claudeguard/SKILL.md,rulesets/_core/,scripts/check.*, and (if absent) the workflow. - Seeds
rulesets/house/andclaudeguard.config.jsononly if they don't already exist — your policy is never clobbered.
Then make it yours:
- Replace
rulesets/house/with your team's rules; keeprulesets/_core/. - Tune
applies_toglobs and trip patterns to your stack (e.g. Bun vs npm, Drizzle vs Prisma, Hono vs Express). - Set
baseinclaudeguard.config.jsonto your integration branch.
- Never edits, stages, or commits. A gate, not a fixer.
- Never invents violations to make a rule look useful. Inert rule → dropped.
- Evidence-based. Every violation cites the exact added line.
- Precise over zealous. A false
FAILerodes trust faster than a missedLOW. - Deterministic output so report diffs stay reviewable.
MIT © 2026 Beeman