Catch zombie prompts — retracted AI rules that crept back into your .cursorrules / CLAUDE.md / AGENTS.md — with a verifiable receipt.
Project description
Sagrada Linter
Catch the AI rules you already changed before they break your build — a local linter for your .cursorrules, CLAUDE.md, and AGENTS.md.
Your agent keeps acting on rules you already changed. You retract a guideline in
.cursorrules / CLAUDE.md / AGENTS.md, and a few edits later it creeps back in — the
build breaks, the agent does the thing you told it to stop doing, and you can't see why.
These are zombie prompts, and they're invisible to a snapshot — they only exist in the
history of your rule files.
Sagrada Linter reads that history and catches them.
Try it on your own repo (30 seconds, nothing installed)
uvx sagrada-linter scan-history .
It runs over your git history and tells you how many zombie prompts already happened
in your repo — with the exact file:line and the commit where each rule was retracted.
No install, no signup, no API key, nothing leaves your machine. If your rule files are
already clean, it says so:
$ uvx sagrada-linter scan-history .
0 zombie-prompt events found. Your rule files are coherent over time. ✓
Clean history? See it fire on your own files anyway:
uvx sagrada-linter scan-history --inject-demo CLAUDE.md
What it actually does (and what it doesn't)
The thing a normal linter can't see: coherence over time. A snapshot checker reads your rules as they are right now. Sagrada reads how they changed — and flags the one pattern that quietly breaks agents: a rule you retracted that came back.
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. When it flags something, it's because the bytes say so.
What it will not catch (so you know the edges):
- A rule re-added with completely different wording in the same commit it was removed — that reads as a rewrite, not a zombie.
- An intentional reversal you actually meant. (Mark it with
sagrada:allowto silence it.) - Semantic contradictions between two different rules ("always X" vs "never X"). That's a fuzzier problem; it is not part of the deterministic check and never fails your build.
- Imperative free-prose rules with no
key: valueshape (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.)
This honesty is the point: the deterministic catch is small and sharp, and you can trust every result because the tool never pretends to know more than the diff does.
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.
Install
uvx sagrada-linter scan-history . # 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.
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.1.0
hooks:
- id: sagrada-linter
GitHub Action
Catch zombies 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 of exactly what was checked and what the verdict was. It's offline-verifiable:
a stranger recomputes it byte-for-byte, in two languages, with no install and no trust in us.
sagrada-linter scan-history . --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.1.0/sagrada_linter/er1_verify.mjs
node er1_verify.mjs .sagrada/receipts/*.er1.json
The receipt format is ER1 — open, and built so the verifier is the simple part: see SCOPE_OF_CERTIFICATION.md for exactly what is certified and what is not.
In your agent — decision-time receipts
scan-history 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 in Python or zero-dep JS,
so a relying party never has to trust the agent that produced it.
License
Apache-2.0 © 2026 Cruxia (including the patent grant). Contributions welcome — see CONTRIBUTING.md.
A zombie prompt is the smallest, most checkable case of a general problem: systems that re-assert beliefs they were told to drop. The linter catches the deterministic version of that — and nothing fuzzier. It emits an ER1 receipt so the catch is something a stranger can re-verify, not something you take on trust. It's the first verb in a family. → Cruxia-Labs
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sagrada_linter-0.1.0.tar.gz.
File metadata
- Download URL: sagrada_linter-0.1.0.tar.gz
- Upload date:
- Size: 45.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f8e5a0aad2c33ceee1606f7dbd371dedd3e1ae832308b1afd2c2aaf18a5d7ad
|
|
| MD5 |
36449d5af7da49701c47becae27c7ac0
|
|
| BLAKE2b-256 |
e15c96013a6b06eba2907ba3b69c15f4175f5547d4ce9cda53936dd078d74f7e
|
File details
Details for the file sagrada_linter-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sagrada_linter-0.1.0-py3-none-any.whl
- Upload date:
- Size: 49.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5329d262358455ab2832f94b59aa4afa40a79d42e5a65482c2b1b8916e788762
|
|
| MD5 |
f3995001c8a22771cae03c12b1ef97ae
|
|
| BLAKE2b-256 |
cb50f27e36e198e6f7971ef4130aacc350dfc529927b17d9c8c7cdd2f855c250
|