Skip to main content

Sagrada Linter

A linter for the belief state of agent instruction files (CLAUDE.md, AGENTS.md, .cursorrules, …): it reads their git history and reports rules that were retracted in one commit and are back in the file today.

CI License: Apache 2.0 pre-commit

$ git clone https://github.com/Cruxia-Labs/sagrada-specimen && cd sagrada-specimen
$ uvx sagrada-linter read .
EXAMEN — sagrada-specimen
reading 1 rule file across 16 commits. nothing leaves this machine.

o RESTORED WITH INTENT  tone
    "Keep error messages plain and unfunny"
    killed   376ce593  2026-03-02
    returned 2ba05a7d  2026-03-28  — with the decision on the books:
    in-file · "restored 2026-04-02: the style guide never shipped"
    not a zombie. an intentional restoration, recorded.

+ WALKING  deploy_gate
    "Always run migrations manually before deploy"
    killed   e48323d1  2026-02-14
    revived  0f6c89af  2026-05-19
    active again in CLAUDE.md:2 today · walking 73 days

sagrada-specimen: 1 walking · 1 restored with intent

(captured 2026-07-31 — the day count moves; the commits do not)

That is the whole idea. The specimen is a scripted repository with known findings; run the same command on a repository you own (use a full clone — a shallow --depth 1 clone has no history to read, and the tool says so rather than printing a clean result):

uvx sagrada-linter read .

Runs locally over your git history. No install, no signup, no API key, nothing leaves your machine. On a clean history it says so plainly — and a clean result is a result:

$ uvx sagrada-linter read .
EXAMEN — clean-repo
reading 1 rule file across 1 commits. nothing leaves this machine.

NO REVIVED RULES FOUND
every retraction in 1 rule file is still resting.

What it does (and what it doesn't)

A snapshot checker reads rules as they are right now. Sagrada reads how they changed — and flags one pattern that quietly breaks agents: a retracted rule that came back. A merge, a stale branch, a second file nobody updated; agents read whatever text survived.

It's deterministic. Every result is a real retract→re-add in your git history, located by diffing consecutive versions of the file — no fuzzy matching, no model, no guessing. "The same rule" means the same normalized structured line; a reworded rule does not count.

What it will not catch (the edges, stated up front):

  • A rule re-added with completely different wording — that reads as a rewrite, not a zombie.
  • An intentional reversal you actually meant. Declare it — sagrada-linter restore FILE LINE --reason "…" writes a sagrada:allow marker on the line — and the reading shows the decision instead of an accusation (that's the RESTORED WITH INTENT entry above).
  • Semantic contradictions between two different rules ("always X" vs "never X"). That is a fuzzier problem; it is not part of the deterministic check and never fails your build.
  • Imperative free-prose rules with no structure (e.g. - Use type annotations). The deterministic floor anchors on structured rules (key: value, - term — definition); it refuses to guess at prose rather than risk a false positive. (Measured: see BENCHMARKS.md.)

Not git log | grep. Grep finds a string; it can't tell that a rule was retracted and then re-asserted across commits, pair the before/after, or tell a rewrite from a revival.

The gate — guard

The reading reports; the gate enforces. sagrada-linter guard writes a lock of every dead rule; guard --check then fails (exit 2) if an undeclared resurrection is present — with the rule's kill history in the failure text. guard --workflow installs the GitHub Actions workflow so the check runs on every PR.

This repository's specimen runs the gate on itself: its PR tab has real pull requests that try to bring retired rules back and fail CI with the kill history attached — and one declared restoration that passed.

Removal with a record — forget

Deleting a rule line loses the reason it died. sagrada-linter forget FILE LINE --reason "…" makes the removal itself an artifact: the line is deleted, a dated tombstone row is appended to .sagrada/tombstones.jsonl (term, definition, reason, residue scan), and a signed receipt is written beside it. Commit all three and the retirement is on the record — guard then locks the grave.

Belief-integrity score — vitals

sagrada-linter vitals            # 0-100 score for the current repo
sagrada-linter vitals --json     # full inputs + active-zombie detail
sagrada-linter vitals --badge-out badge.json   # shields.io endpoint JSON

A deterministic 0-100 score of one repo's belief hygiene over the trailing year, computed under SAGRADA-VITALS-METHOD v0.2 — a frozen, hash-committed formula (active zombies dominate and saturate; historical revivals add a small memory penalty; retraction hygiene can only reduce penalties, never add points). Record-side only: no model, no network, no judgment call anywhere in the number. What it does not measure: whether your agent answers correctly, code quality, security, or anything an LLM said — 100 means "no zombie beliefs detectable in the record," nothing more. The GitHub Action publishes the score to the job summary and uploads the badge (vitals: true, the default).

Band names. A repo reads CLEAR or ROTTEN. The strings frozen with the method are an implementation detail of the receipt format: --json output and sealed records carry them forever so every historical artifact recomputes byte-for-byte, and the display maps over them. WALKING names a rule's state, never a repo's.

Install

uvx sagrada-linter read .           # zero-install run (recommended)
pipx install sagrada-linter         # persistent CLI
pip install sagrada-linter          # into the current environment

Python 3.9+. One dependency (cryptography). Runs fully offline. Verbs: read · guard · forget · restore · scan-history · vitals · check-action · verify.

Pre-commit

Block a zombie before it lands. Add to .pre-commit-config.yaml:

repos:
  - repo: https://github.com/Cruxia-Labs/sagrada-linter
    rev: v0.2.1
    hooks:
      - id: sagrada-linter

GitHub Action

The same check on every PR, with a comment on the offending line. See docs/GITHUB_ACTION.md:

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- uses: Cruxia-Labs/sagrada-linter@v0

Verify it yourself

Every check can drop a small receipt (--receipt) into .sagrada/receipts/ — a signed, chained record (.er1.json) of exactly what was checked and what the verdict was. It verifies offline: a stranger recomputes it byte-for-byte with no install and no trust in us, in any of three implementations on disjoint stacks (Python, Node, browser WebCrypto — spec and golden vectors at er1-spec).

sagrada-linter read . --receipt
sagrada-linter verify .sagrada/receipts/*.er1.json     # Python — works from any install
# Or the zero-dependency JS reference verifier (one file; grab it from the repo):
#   curl -O https://raw.githubusercontent.com/Cruxia-Labs/sagrada-linter/v0.2.1/sagrada_linter/er1_verify.mjs
node er1_verify.mjs .sagrada/receipts/*.er1.json

See SCOPE_OF_CERTIFICATION.md for exactly what is certified and what is not.

In your agent — decision-time receipts

read audits the past. To attest what an agent did as it acts, call check_action in your loop (or from an MCP tool) before it runs a step: you get an ALLOW / HALT verdict and a receipt of the exact constraint state the action was taken under — recomputable offline by anyone.

from sagrada_linter import check_action

# your agent's active, deterministic constraints (from your rules / policy)
beliefs = [
    {"entity": "env:DEPLOY_TARGET", "rule": "equals", "value": "staging"},
    {"entity": "lib:boto3", "rule": "excludes"},
]
receipt = check_action(
    beliefs,
    {"tool": "shell", "asserts": {"env:DEPLOY_TARGET": "production"}, "resource": "deploy.sh"},
    receipts_dir=".sagrada/receipts",
)
if receipt["decision"]["verdict"] == "HALT":
    raise RuntimeError(receipt["decision"]["reason_code"])   # -> SUPERSEDED_VALUE

Or from the shell: sagrada-linter check-action --beliefs beliefs.json --action action.json --receipt. Runs locally, no network — we never see your files. The receipt verifies offline, so a relying party never has to trust the agent that produced it.

The Index

beliefrotindex.com — a weekly census of zombie beliefs in tracked public repos, scored with this linter under a frozen method. Named rows are opt-in: run the reading, then self-list.

Status

One person built this and nobody else has used it yet. A flagged rule can be one you re-added on purpose — declare it with restore and the reading shows the decision instead of an accusation. If it is wrong about your repository, that is exactly what we want to hear: open an issue with the two commits.

License

Apache-2.0 © 2026 Cruxia (including the patent grant). Contributions welcome — see CONTRIBUTING.md.


A zombie belief is the smallest, most checkable case of a general problem: systems that re-assert rules they were told to drop. The linter catches the deterministic version of that — and nothing fuzzier — and emits a receipt so the catch is something a stranger can re-verify, not something you take on trust. → Cruxia-Labs

Metadata

Release files for sagrada-linter 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sagrada-linter 0.2.1
File Size Uploaded
sagrada_linter-0.2.1.tar.gz 87.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sagrada-linter 0.2.1
File Interpreter ABI Platform
sagrada_linter-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 168.5 kB

Release files / sagrada_linter-0.2.1.tar.gz

Download URL sagrada_linter-0.2.1.tar.gz
Size 87.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5f033fbecda41a9ff196c2250e3f65624ce4b51a248de2f4fe670e4fad3e347b
BLAKE2b-256 checksum
How to use checksums
44efc77a72ff41b43323ed4df9bfb3c3eb9e8415130a40affcee55e87c007210
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.15

Release files / sagrada_linter-0.2.1-py3-none-any.whl

Download URL sagrada_linter-0.2.1-py3-none-any.whl
Size 81.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d2a313e2cd77dc22f70f3c25fde8b5c472a9032731a7d0cbab74f736f26a357a
BLAKE2b-256 checksum
How to use checksums
acd502283ece6806d2c96ba66bfdad0ad28c464cd5f098382a4ac3bf69a21a3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.15

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page