Skip to main content

ESLint for your LLM prompts — catch context bugs before they silently kill your output quality.

Project description

demo

context-lint

ESLint for your LLM prompts. Catch the context bugs that silently wreck your output quality — buried instructions, token bloat, contradictions, ALL-CAPS shouting — and optionally auto-fix them with Claude.

context-lint v0.1.0 — scanning: prompt.txt

  WARN   AGGRESSIVE_FORMATTING  ALL-CAPS commands found: "ALWAYS" (2 occurrences); excessive exclamation marks (1 run(s)); forceful phrases: "YOU MUST"
         → Use normal sentence case and plain phrasing. Models respond worse to aggressive formatting and shouting.

  WARN   CONTRADICTION          Conflicting instructions (length: concise vs thorough)
         → Keep the more specific, actionable instruction and remove the conflicting one.
         └ "concise" vs "thorough"

  WARN   ROLE_BLEED             User turn opens with a system-style instruction ("you are")
         → Move persistent role/behavior instructions into the system prompt; keep the user turn as the actual input.
         └ "You are an expert assistant. ALWAYS be concise in your answ…"

  INFO   REDUNDANT_COT          Explicit chain-of-thought instruction detected
         → Remove the step-by-step phrase. Modern reasoning models reason internally without being told to.
         └ "Think step by step"

────────────────────────────────────────────────────────
  4 issues found  (3 warnings, 1 info)  →  Score: 61/100

  Tip: Run with --fix to get a rewritten prompt with all issues resolved.

The problem

Prompts fail in ways that never throw an error. The model just quietly does worse, and you have no idea why. The usual suspects are well-documented but easy to miss by eye:

  • Critical instructions buried in the middle. LLM attention is U-shaped — content in the middle of a long context is the most likely to be ignored.
  • Token bloat. Past a few thousand tokens, instruction-following measurably degrades.
  • ALL-CAPS and "!!!". Shouting at the model tends to make outputs more erratic, not more obedient.
  • Contradictions. "Be concise" and "be comprehensive" in the same prompt force the model to guess.
  • Role bleed. Stuffing persistent role instructions into a user turn (or a question into the system prompt) blurs a boundary the model relies on.

context-lint is a static linter for all of this. No prompt actually runs — it reads your prompt, flags the issues with a fix and a reason for each, and gives you a 0–100 quality score. With --fix, it sends the prompt and the findings to Claude and streams back a rewritten version, then re-lints to prove the issues are gone.

Install

pip install context-lint

For nicer colored output, install with the optional rich extra (the tool falls back to plain ANSI without it):

pip install "context-lint[rich]"

Quickstart

# Lint a file
context-lint prompt.txt

# Lint from stdin
echo "You are a bot. Be concise but thorough." | context-lint -

# Lint several files at once
context-lint prompts/*.txt

# Auto-fix with Claude (requires an API key)
export ANTHROPIC_API_KEY=sk-...
context-lint prompt.txt --fix

# Machine-readable output for CI
context-lint prompt.txt --json

The rules

Every finding carries a rule_id, a severity, a one-line fix, and a why (shown with --verbose, and always handed to the --fix rewriter).

Rule Severity Catches
LOST_IN_MIDDLE WARN Instruction-like sentences sitting in the middle 40% of the context, where models attend to them least reliably.
TOKEN_BUDGET WARN / ERROR Prompts over ~3,000 estimated tokens (WARN) or ~6,000 (ERROR), where reasoning quality drops.
AGGRESSIVE_FORMATTING WARN Repeated ALL-CAPS commands, runs of !!!, and shouty phrases like "YOU MUST". Technical acronyms (JSON, API, …) are allow-listed.
CONTRADICTION WARN Opposing directives — concise vs thorough, formal vs casual, or "always X" alongside "never X".
ROLE_BLEED WARN A user turn that opens with role-setting ("You are…", "Act as…"), or a system prompt that contains an actual user question.
REDUNDANT_COT INFO Explicit "think step by step" phrasing, which is redundant on models that already reason internally.
VAGUE_OUTPUT_FORMAT INFO A named output format (JSON, table, list, …) with no concrete example to anchor it.
CONTEXT_ROT_RISK WARN Long context (>1,500 tokens) with no summarize / filter / extract strategy to keep it manageable.

Run a single rule with --rule, or raise the floor with --min-severity:

context-lint prompt.txt --rule TOKEN_BUDGET
context-lint prompt.txt --min-severity warn   # hide INFO findings

--fix: rewrite with Claude

--fix runs the linter, shows the findings, then asks Claude to rewrite the prompt fixing every one — preserving your original intent — and streams the result back. It finishes by re-linting the rewrite so you can see the issues are actually resolved.

BeforeScore: 61/100

You are an expert assistant. ALWAYS be concise in your answers, but ALWAYS be
thorough and detailed too. YOU MUST get this right!!! Think step by step before
responding to the user's question about their account.

After context-lint prompt.txt --fixScore: 100/100

Answer the user's question about their account. Keep the response clear and concise.

CHANGES MADE:
• [ROLE_BLEED] — removed the "You are…" role-setting from the user turn; this is a request, not a system prompt.
• [AGGRESSIVE_FORMATTING] — converted ALL-CAPS to sentence case and dropped the "!!!".
• [CONTRADICTION] — kept "concise" and removed the conflicting "thorough and detailed".
• [REDUNDANT_COT] — removed "Think step by step"; the model reasons internally.

Write only the rewritten prompt to a file (the report still prints to the terminal):

context-lint prompt.txt --fix --output prompt.fixed.txt

--fix needs ANTHROPIC_API_KEY in the environment. If it's missing, context-lint prints a clear message and exits 3 without ever prompting you for it. The rewrite uses Claude Sonnet.

Input formats

Formats are auto-detected — no flags needed. Content sniffing wins over the file extension.

  • Plain text (.txt, .md, .prompt) — treated as a single user message.
  • JSON (.json) — either a bare messages array [{"role": ..., "content": ...}] or an object {"system": "...", "messages": [...]}. Block-style content ([{"type": "text", "text": "…"}]) is flattened automatically.
  • XML-style<system>…</system><user>…</user> tags (also <assistant> / <developer>).

CI integration

GitHub Actions

name: prompt-lint
on: [push, pull_request]

jobs:
  context-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install context-lint
      - run: context-lint prompts/*.txt

The job fails on any warning (exit 1) or error (exit 2). To gate only on errors, add --min-severity error.

pre-commit

Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/your-org/context-lint
    rev: v0.1.0
    hooks:
      - id: context-lint

The hook lints every staged .txt, .md, .json, and .prompt file and blocks the commit if any fails.

Exit codes

Code Meaning
0 Clean, or info-only findings
1 One or more warnings
2 One or more errors (or a usage / parse error)
3 --fix requested but ANTHROPIC_API_KEY is not set

JSON output

--json emits a single object for one file, or a results array for several. Each issue includes its rule_id, severity, message, fix, why, and snippet:

{
  "version": "0.1.0",
  "source": "prompt.txt",
  "score": 61,
  "issues": [
    {
      "rule_id": "ROLE_BLEED",
      "severity": "WARN",
      "message": "User turn opens with a system-style instruction (\"you are\")",
      "fix": "Move persistent role/behavior instructions into the system prompt; keep the user turn as the actual input.",
      "why": "Mixing system-level instructions into user turns confuses model behavior...",
      "snippet": "You are an expert assistant. ALWAYS be concise..."
    }
  ],
  "summary": { "errors": 0, "warnings": 3, "info": 1, "total": 4 },
  "exit_code": 1
}

How the score works

The score starts at 100 and deducts a penalty per finding: ERROR −15, WARN −12, INFO −3, floored at 0. A clean prompt scores 100. So three warnings plus one info is 100 − (12 + 12 + 12 + 3) = 61. Filters like --min-severity change the score, because the score always reflects exactly what's shown.

Extending it

Rules are self-registering. To add one: create a module under context_lint/rules/, subclass BaseRule, implement check(prompt) -> list[LintIssue], and append the class to the RULES list in context_lint/rules/__init__.py. The runner has zero per-rule hardcoding, so that's the whole change.

Roadmap

  • More rules: few-shot imbalance, delimiter consistency, injected-instruction detection.
  • Token counting via real tokenizers (currently a fast words × 1.33 estimate).
  • Config file for per-project thresholds and rule toggles.
  • Editor integrations (LSP) for inline linting as you write prompts.

Development

git clone https://github.com/your-org/context-lint
cd context-lint
pip install -e ".[dev]"
pytest

License

MIT.

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

context_lint-0.1.0.tar.gz (33.6 kB view details)

Uploaded Source

Built Distribution

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

context_lint-0.1.0-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

Details for the file context_lint-0.1.0.tar.gz.

File metadata

  • Download URL: context_lint-0.1.0.tar.gz
  • Upload date:
  • Size: 33.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for context_lint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6dd6458488c5fc11d744adc5ec71d674778e7a2d3e9146d800c5c4e0141161ad
MD5 5d1657d55f2acdf732ce28aa9ed184e9
BLAKE2b-256 d095d97db81d84aa84b3e2a440b221ab95a6fb2b7361af60c7792cd109c49f2d

See more details on using hashes here.

File details

Details for the file context_lint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: context_lint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 32.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for context_lint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ca0622eead4bce9802a185139196611a61cb0c78727e52a6bb4ea6baaa6c0c9c
MD5 f3e5f38024cdb70a2ceb935cb7e5882a
BLAKE2b-256 fed5e6b892f9b7677552b5c6cb8e16f3b46920e7e62b3cd2f6c6af6df83da795

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