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.
$ 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 asagrada:allowmarker on the line — and the reading shows the decision instead of an accusation (that's theRESTORED WITH INTENTentry 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)
| File | Size | Uploaded | |
|---|---|---|---|
| sagrada_linter-0.2.1.tar.gz | 87.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|