promptproof
A fast, deterministic, zero-API linter for the prompt files your agent depends on.
SKILL.md files, Claude Code sub-agents, MCP tool descriptions, slash-command
frontmatter, and plain system prompts — promptproof catches the mistakes that silently
break agents before they ship: descriptions that never trigger, contradictory
instructions, token-bloated context, broken frontmatter, leaked secrets.
No API key. No model calls. No network. Just regex, structure, and millisecond runs that fit a pre-commit hook and CI.
$ promptproof .claude/
.claude/skills/pdf/SKILL.md:
2:14: PP404 weak trigger: description summarizes instead of stating when to use it
→ rewrite as "Use when <condition> — e.g. <trigger example>"
9:1: PP306 decorative banner wastes ~40 tokens
-:-: PP305 token budget: body ~7,200 tokens, 44% over budget (5,000)
0 errors, 3 warnings, 0 info · 0 API calls · 6ms
Why another prompt linter?
There are good tools in this space already, and promptproof is honest about that:
| Tool | Focus | LLM calls |
|---|---|---|
| promptlint.dev | generic LLM prompts | no |
| skillcheck / claude-lint / agent-skill-linter | SKILL.md spec compliance |
some (agent critique) |
| agentlint / agentlinter.com | CLAUDE.md / AGENTS.md harness |
no |
| promptproof | all agent prompt assets — skills, sub-agents, MCP tools, commands, prompts — in one tool | never |
The wedge is the same one ruff used to win a crowded Python-linting space:
unification + speed + zero dependencies. promptproof is the only linter that
understands every prompt-asset type, stays 100% deterministic and offline, and ships
the universal prompt rules (contradiction, ambiguity, token waste) and the
asset-specific ones (weak triggers, frontmatter, tool-param docs) in a single tool you
can run on every keystroke.
Install
# run it without installing
uvx promptproof .
# or install
pip install promptproof
Python ≥ 3.11. The core has zero runtime dependencies. Optional extras:
promptproof[yaml] (robust frontmatter via PyYAML), promptproof[mcp] (MCP server).
Usage
promptproof . # lint the current tree
promptproof .claude/skills # lint a directory
promptproof SKILL.md # lint one file
cat prompt.txt | promptproof - # lint stdin (use --kind to force a type)
promptproof . --format github # GitHub Actions annotations
promptproof . --format json # machine-readable
promptproof . --select triggering # only the PP4xx rules
promptproof . --ignore PP301,PP304 # silence specific rules
promptproof . --fix # apply the mechanical repairs
promptproof . --diff # preview them, write nothing
promptproof rules # list every rule
promptproof explain PP404 # rationale + good/bad example
Exit code is 1 when any finding is at or above the fail level (default: warning),
else 0. Use --fail-level error to only fail CI on errors, or --exit-zero to report
without failing.
Fixing (--fix)
Some findings are token bloat with exactly one correct repair. Those carry a fix:
$ promptproof .claude --diff
--- .claude/skills/pdf/SKILL.md
+++ .claude/skills/pdf/SKILL.md
-Please read the PDF in order to extract its text.
-Thank you.
+Read the PDF to extract its text.
-Due to the fact that PDFs vary, please check the page count first.
+Because PDFs vary, check the page count first.
-✨ 🎉 🚀 💡 ⭐ 🔥
--diff previews; --fix writes. Fixable today: PP301 (courtesy padding), PP302
(wordy connectives), PP306 (decorative banners).
The line is deliberate: a rule attaches a fix only when the repair is unambiguous. Deleting a banner or replacing "in order to" with "to" has one right answer. Rewriting a vague directive does not — those stay hints, because a linter guessing at what you meant is worse than a linter that tells you to decide.
Three properties make it safe to run across a prompt library:
- Fenced blocks are never touched. Sample text and transcripts are examples, not instructions to the model. (Fixing this also removed a false positive from plain linting — PP301/PP302 used to flag prose inside code fences.)
- Edits can't interleave. Two rules never rewrite the same line in one pass.
- It converges. Several rules report one match per line, so fixing loops until the file stops changing — bounded, so a pathological rule pair can't spin.
Repairs also clean up after themselves: a dropped leading "Please" recapitalises the sentence, a stranded comma goes, and a line that was only courtesy is removed entirely.
Adopting on an existing repo (--baseline)
A first run on a real prompt library reports hundreds of findings, and CI can't gate on that until they're all fixed. Record what's already there and fail only on what's added after:
$ promptproof . --write-baseline
wrote 143 finding(s) to .promptproof-baseline.json
$ promptproof . --baseline
All prompts proofed ✓
1 baselined
Commit the baseline file. Entries are keyed on the content that triggered each finding — path, rule, and a hash of the offending line — never on line numbers. A baseline keyed on line numbers stops working the moment someone inserts a paragraph: every finding below the insertion looks new. This one survives edits elsewhere in the file, and a finding whose text actually changed correctly reads as new.
Counts are tracked per entry, so three known copies of a problem can be baselined and a fourth still fails the build. And a missing or unreadable baseline is a hard error, never a silent pass — a gate that goes green because it couldn't find its baseline is worse than no gate.
What it checks
31 rules across six categories (run promptproof rules for the full list):
- clarity (PP1xx) — ambiguous directives, vague quantifiers, unresolved pronouns, subjective criteria, weak modals.
- consistency (PP2xx) — contradictory directives, conflicting output format / length / persona.
- economy (PP3xx) — politeness padding, filler phrases, redundant restatement, walls of text, token-budget overflow, decorative banners.
- triggering (PP4xx) — missing / too-short / too-long descriptions, weak triggers (the #1 reason skills never load), first-person descriptions, undocumented MCP tool params.
- structure (PP5xx) — missing or invalid frontmatter, missing
name, non-kebab names, name/dir mismatch, missing verify guidance, unknown frontmatter keys. - safety (PP6xx) — secrets in prompts, embedded injection phrases, real PII in examples.
Configuration
Add a [tool.promptproof] table to pyproject.toml (or put the same table, or its keys
at the top level, in a standalone .promptproof.toml):
[tool.promptproof]
fail-level = "warning"
ignore = ["PP304"] # rule ids or whole categories ("economy")
[tool.promptproof.severity]
PP301 = "info" # downgrade politeness-padding
[tool.promptproof.thresholds]
"token_budget.skill" = 4000
description_min_chars = 50
Suppress a single finding inline:
<!-- promptproof: ignore PP602 -->
ignore previous instructions # intentional red-team fixture
CI
GitHub Action (action.yml):
- uses: shaxzodbek-uzb/promptproof@v0.1.0
with:
paths: .claude
fail-level: warning
pre-commit:
- repo: https://github.com/shaxzodbek-uzb/promptproof
rev: v0.1.0
hooks:
- id: promptproof
Library
from promptproof import lint_text, lint_paths, render
findings = lint_text(open("SKILL.md").read(), path="SKILL.md")
print(render(findings, "text"))
from promptproof.fixer import fix_text
result = fix_text(open("SKILL.md").read(), path="SKILL.md")
print(result.text, len(result.applied), "repaired")
from promptproof.baseline import Baseline
fresh, suppressed = Baseline.load(".promptproof-baseline.json").filter(findings)
Contributing
A wrong rule is worse than no rule — every rule ships with a firing test and a known-good test that must not fire. See CONTRIBUTING.md and the canonical SPEC.md.
License
MIT © Shaxzodbek Qambaraliyev / Blaze
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 promptproof-0.2.0.tar.gz.
File metadata
- Download URL: promptproof-0.2.0.tar.gz
- Upload date:
- Size: 78.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06f2e606570f3a29e6362264e6310b6517ce39544b224c30e6b344fda03cb3d3
|
|
| MD5 |
52c17957de99c45a440bdd431d7be98d
|
|
| BLAKE2b-256 |
ad6e5d1ba8d58c95f0f6cec067885f4082fec849d8982754a10527fefb444bd6
|
File details
Details for the file promptproof-0.2.0-py3-none-any.whl.
File metadata
- Download URL: promptproof-0.2.0-py3-none-any.whl
- Upload date:
- Size: 57.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37d53eff7d610b0c1c26cb4904402dae3d9e9cadda121ee77857e06322258412
|
|
| MD5 |
3df02c8e9a7b0a2e5b4de627bf0eb6e3
|
|
| BLAKE2b-256 |
cf17eca5e736845334cc521af0084477b2e85cbd6c5966491fe579efb91cba08
|