Skip to main content

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.

CI License: Apache 2.0 pre-commit

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.

Running sagrada-linter scan-history on a repo: it reports a rule (test_runner) that was retracted in one commit and re-added in a later one, with the file:line and both commit hashes.

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:allow to 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: value shape (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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sagrada_linter-0.1.0.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sagrada_linter-0.1.0-py3-none-any.whl (49.3 kB view details)

Uploaded Python 3

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

Hashes for sagrada_linter-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1f8e5a0aad2c33ceee1606f7dbd371dedd3e1ae832308b1afd2c2aaf18a5d7ad
MD5 36449d5af7da49701c47becae27c7ac0
BLAKE2b-256 e15c96013a6b06eba2907ba3b69c15f4175f5547d4ce9cda53936dd078d74f7e

See more details on using hashes here.

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

Hashes for sagrada_linter-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5329d262358455ab2832f94b59aa4afa40a79d42e5a65482c2b1b8916e788762
MD5 f3995001c8a22771cae03c12b1ef97ae
BLAKE2b-256 cb50f27e36e198e6f7971ef4130aacc350dfc529927b17d9c8c7cdd2f855c250

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page