Introduction
claims checks whether what your documentation says about your code is
actually true. It decides by executing against the artifact the prose
describes — running the command, resolving the symbol, walking the git
history — never by matching words that happen to sit near a claim.
The failure it is built for
Most doc-integrity tooling watches for drift: a document and the code it describes agreed once, then diverged, and the tool’s job is to notice the divergence. That model has a blind spot, and it is the more common failure. Prose is very often wrong the day it is written — a flag that was renamed before the sentence naming it was typed, a default copied from a sibling project, a “runs in under a second” nobody ever timed. Nothing diverged, because the two were never together. A drift detector has nothing to compare.
So claims does not ask whether the code changed since the prose was
written. It asks whether the prose is true now, and it finds out the only
way that answer is available: by going and looking.
What it does with an answer
Twelve checks run over a commit’s own diff. Most of what they report is advisory: surfaced alongside the commit and never failing the run, because the check ranks, samples or suspects rather than decides. A finding that names a claim the tool has actually disproved — a link with no target, a cited symbol the repository no longer declares, a command whose real output differs from the block beneath it — is a gate, and blocks the commit it is attached to, because it is both certain and cheapest to fix while you are still holding the context.
One advisory check hands off rather than deciding at all: it works out which architecture-or-intent claims a diff has put in doubt and surfaces each as a candidate for an agent to judge against the cited code.
Where it runs
claims installs into a project as a Claude Code plugin: a hook that fires
on git commit, and a skill that runs the same checks on demand mid-task.
The checks are also a plain CLI, so the same run works as a git pre-commit
hook or a CI step in a project that has never seen Claude Code.
Next
Installation has the commands, for a plugin install and for a standalone checkout alike.
Installing claims in a consuming project
Direct git-URL plugin, no marketplace listing — see
.scratch/claims-consolidation/issues/03-distribution-mechanism.md’s Answer
for why. .claude-plugin/marketplace.json in this repo is a
self-referencing, single-plugin listing ("source": "./"); a consuming
project points claude’s marketplace registry straight at this repo’s git
URL rather than a central listing.
Install
From inside the consuming project:
claude plugin marketplace add https://github.com/or1can/claims.git --scope project
claude plugin install claims@claims --scope project
The first command registers this repo as a marketplace named claims
(marketplace.json’s own name); the second installs the one plugin it
lists, also named claims, hence claims@claims. Both the skill and the
PreToolUse hook come live from this one install — nothing else to wire up.
--scope project writes both declarations into that project’s own
.claude/settings.json, so the dependency is explicit and travels with the
repo rather than living only in your machine’s global config — the same
thing a second developer cloning the project would need. Both flags default
to user (global, this machine only) if omitted; that’s a reasonable choice
for a quick personal try, but avoid it if this repo (or any other project on
the same machine) ever registers its own marketplace also named claims —
the CLI has no way to alias a marketplace to a different local name than the
one its own marketplace.json declares, so a second claims source at the
same scope silently replaces the first rather than coexisting. This repo’s
own dogfooding setup (a project-scoped claims marketplace pointing at
directory: ".", not this git URL) hit exactly that collision when a
global-scope entry was added alongside it.
Verify:
claude plugin details claims@claims
Component inventory
Skills (1) check-claims
Hooks (1) PreToolUse (harness-only — no model context cost)
Confirmed by installing into a disposable project this way and checking
claims:check-claims and the PreToolUse gate were both live — not just
read off the manifest.
Pinned at install, explicit update. claude plugin marketplace add
resolves and pins the source at add time — an unreviewed upstream change to
this repo doesn’t silently start gating a commit differently mid-project.
Pull in a newer version explicitly, when you choose to — same name and
scope as the install commands above, both required:
claude plugin update claims@claims --scope project
claude plugin update claims alone fails (Plugin "claims" not found —
it defaults to user scope, and the plugin above was installed at
project); claude plugin update claims@claims without --scope project
fails the same way (... is not installed at scope user). Confirmed by
running all three against a real --scope project install — only the
full form above succeeds.
Disabling the automatic hook
Installing always gets you the on-demand check-claims skill; if a project
wants the automatic commit gate off without uninstalling the plugin
entirely, add to that project’s claims.toml:
[hook]
enabled = false
claims/hook.py checks this key (config.get("hook", {}).get("enabled", True)) before running anything — with it false, git commit is never
gated by this plugin, though the skill still works on demand.
To silence just one check at commit time instead of every check, add
enabled = false to that check’s own section instead:
[stale-claims]
enabled = false
Only this commit-time decision is affected, same as the plugin-wide flag
above — python3 -m claims.cli and the skill still run and report that
check’s own findings on demand.
Using it outside Claude Code
The automatic gate above only fires through Claude Code’s own PreToolUse
hook — a human running git commit from a plain terminal, or a CI job,
never triggers it at all; nothing here is a push-time or CI check by
itself (see the top-level README.md’s own “What it isn’t”). claims.cli
is the same underlying runner (claims/hook.py and claims/cli.py both
just call claims/runner.py’s run()), so it works equally well as a
plain git pre-commit hook or a CI step — the same exit code (0/1) a
shell script or CI job already expects.
claims isn’t pip-installable (pyproject.toml’s own package = false)
and the Claude Code plugin cache it normally runs from
(CLAUDE_PLUGIN_ROOT, set only by Claude Code itself when invoking the
hook — see claims/hooks.json) is a per-user, undocumented install
location that won’t exist at all on a CI runner. Outside Claude Code, get
a checkout of this repo some other way instead — a pinned clone step in
CI, a git submodule, or a sibling checkout for a local pre-commit hook —
and point PYTHONPATH at it directly:
# .git/hooks/pre-commit (executable)
#!/bin/sh
PYTHONPATH=/path/to/claims python3 -m claims.cli --repo-root .
# a CI job step, e.g. GitHub Actions
- uses: actions/checkout@v4
with:
repository: or1can/claims
ref: <pinned tag or commit>
path: claims
- run: PYTHONPATH=claims python3 -m claims.cli --repo-root .
What’s different from the full Claude Code experience: every
mechanical check still runs exactly as it does at commit time — the same
run() seam, the same gate/advisory split, the same exit code. Two
things don’t, because both need an LLM to actually interpret what the
mechanical half only sets up:
judgment-agent’s own candidates never become verdicts.claims/checks/judgment_agent.pyonly ever computes the candidate list — a diff-touched subject with a claim about it — and reports each one as an advisory finding; the actual true/false judgment is a separate step, theclaims:judgment-agentsubagent, that only thecheck-claimsskill invokes (claims/skill/SKILL.md). Run this way, those candidates still show up in the output, but as raw, un-judged data points to read yourself — nothing resolves them into an answer.- The skill’s own on-demand
claims.tomlreview doesn’t run at all. Whether a project’s own config values (a staleexcludeglob matching nothing, say) are actually doing anything is reasoned prose thecheck-claimsskill produces on explicit ask — not aFinding, so it was never part ofrun()’s own output to begin with, and nothing here replaces it.
claims.toml
Optional, at the consuming project’s repo root. No file at all means
load_config (claims/config.py) returns {} and every check runs with
its own defaults, defined in its own module under claims/checks/ —
nothing to configure to get started. Where present, each top-level table is
one check’s own config section, read by that check’s name.
See docs/configuration.md for every check’s config keys, their defaults,
and when to reach for each one — or that check’s own module docstring
under claims/checks/ directly, if this page and the code ever disagree.
executable-claims also reads a second, git-ignored, per-machine file,
claims.local.toml — committed claims.toml can’t be the trust boundary
for what commands are allowed to actually run; see
decisions/0001-executable-claims-deny-by-default.md.