Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.py only 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, the claims:judgment-agent subagent, that only the check-claims skill 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.toml review doesn’t run at all. Whether a project’s own config values (a stale exclude glob matching nothing, say) are actually doing anything is reasoned prose the check-claims skill produces on explicit ask — not a Finding, so it was never part of run()’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.