contextpruner
v0.4.0
Published
Lint your AI-context config files (AGENTS.md, CLAUDE.md, GEMINI.md, Cursor rules) against your repo — find missing, dead, drifting, and conflicting rules.
Downloads
28
Maintainers
Readme
contextpruner
Lint your AI-context config files against your repo. contextpruner lint checks
your AGENTS.md / CLAUDE.md / GEMINI.md / Copilot / Cursor rules — plus the
enforced ignore artifacts (.cursorignore / .geminiignore / .codeiumignore
and the Claude Code deny rules in .claude/settings.json) — against the files
actually in your tree and reports:
- Missing — junk the config doesn't ignore yet (with what it's costing you),
- Dead — rules that match nothing,
- Drift — rules in one config but not another,
- Conflict — rules that contradict each other, including an enforced rule
that hard-blocks a file you pinned Keep or Skim, or a path you declared
keep:in.contextpruner.
It exits non-zero when it finds issues, so it drops straight into CI or a pre-commit hook.
This CLI is the check side of a larger loop. At contextpruner.app you can generate all nine config files free in your browser — four of them enforced by the agent's own harness as a best-effort block — and the $9/month automation regenerates all nine on every push, with your account's usage dashboard showing what the last sync cut. Lint is how you prove, locally or in CI, that the configs still match your tree.
Usage
export CONTEXTPRUNER_API_KEY=cp_live_... # from https://contextpruner.app/account
npx contextpruner lintOne subscription, one key: the same cp_live_ key authorizes both this CLI and
the ContextPruner config automation (the GitHub Action / pre-commit hook), so
reuse the one you already have.
contextpruner lint [--config <path>]... [--fix] [--json]--config <path>— a config to lint (repeatable). Defaults to auto-detecting the standard files at the repo root: the advisory markdown configs plus the four enforced artifacts. An explicit path's format is inferred from its basename (settings.json→ Claude settings, the*ignorefiles → gitignore syntax, anything else → markdown).--fix— rewrite each config's managed block in place to fix the issues. Your own content outside the block (and the "Exceptions" section) is left alone. Enforced ignore files regenerate only the# contextpruner:begin/endblock;.claude/settings.jsonis merged via its sentinel-delimited deny span and is skipped — never clobbered — when it has no recognized span or can't be merged safely.--json— emit the full report as JSON.
If the repo root has a committed .contextpruner file, its
keep: <path-or-glob> lines (enforcement exceptions — paths that must stay
readable by agents, even untracked ones like docs inside node_modules/)
drop any enforced rule covering them from the expected rule set, exactly as
the generate automation does — so lint, --fix, and generation always agree.
A bare prefer-tools line (optionally prefer-tools: true|on|yes) is also
recognized — it turns on the runtime filter's "Prefer these tools" section
(written by serve, see below). Unrecognized lines are reported as a warning
and ignored.
Exit codes: 0 clean · 1 issues found · 2 usage/auth error.
Filter search junk (serve)
export CONTEXTPRUNER_API_KEY=cp_live_... # same key as lint
contextpruner serveserve (paid, same cp_live_ key) installs shell shims for the common search
tools — rg, grep, find, ls — into ~/.contextpruner/bin. Each shim runs
the real tool and pipes its output through ContextPruner, dropping junk paths
from broad fan-out results and printing a footer showing what it hid. When a
search actually pruned something, a savings note is also printed to stderr —
contextpruner: this search — ~120 → ~30 tokens (~90 of junk pruned) — so the
filtered results on stdout stay clean while you still see the tokens-per-turn
saving. The estimate is computed locally and never sent. cat gets
a passthrough shim (reading a named file is a pointed read, never filtered). It
verifies your key (no key or an invalid one exits 2), writes the shims, and
prints the line to prepend to your PATH:
export PATH="$HOME/.contextpruner/bin:$PATH"Once it's on the front of your PATH, broad rg/grep/find/ls searches
drop junk paths automatically. Set CONTEXTPRUNER_ALL=1 to run a search
unfiltered. The shims are POSIX sh (macOS/Linux); Windows is not yet
supported.
After the shims, serve also writes (or merges into) each supported agent's
MCP config so an MCP-speaking agent — whose bundled search bypasses the PATH
shims — spawns ContextPruner's filtered-search server (contextpruner __mcp)
and gets the same junk-pruning through the contextpruner_search /
contextpruner_glob / contextpruner_list tools (pass all: true to see
everything). Claude Code's project .mcp.json is written unconditionally (the
proven target, and the generic project MCP config). The other three are
detection-gated — written only when that agent is in use here, so serve never
litters a config for an agent you don't have:
- Cursor —
.cursor/mcp.json(project), when.cursor/exists. - Gemini CLI —
.gemini/settings.json(project), when.gemini/exists. - Windsurf —
~/.codeium/mcp_config.json(home; Windsurf has no project-local MCP config), when~/.codeium/exists.
An undetected agent prints a one-line "not detected — re-run serve once it's set
up" hint. A fresh config file is created; an existing one is left untouched
except for splicing in the contextpruner server, preserving every other server
and key — and never overwritten if it can't be parsed safely (the note tells you
to add the server by hand). Restart the agent afterward, then prefer those tools
for broad searches.
Finally, serve marks the repo's .contextpruner file with a prefer-tools
directive (creating the file with a header if it doesn't exist, appending the
line if it does, or leaving it untouched if it's already there). Commit it:
the committed directive is what tells the config automation to add the "Prefer
these tools" section to your generated AGENTS.md / CLAUDE.md / etc., steering
agents toward the filtered tools. It's a committed marker — not local detection —
so the automation reads the same value locally and in CI and the section never
flip-flops.
Optional aggregate stats
The first time you run serve in an interactive terminal, it asks once whether
you'd share two aggregate numbers — how many files were assessed and how many
bytes were pruned — so we can show running totals on the website. Only those
two integers are ever sent — never a path, never file contents. A leading
y/yes opts in; anything else opts out, and a non-interactive or CI run is
never prompted and never sends. The answer is saved in
~/.contextpruner/config.json, so you're only asked once. If you opt in, each
filtered search adds to a local tally in ~/.contextpruner/stats.json, and
serve flushes it best-effort — at most once a day — to /api/stats/contribute
with your cp_live_ key. Opting out (or never being prompted) writes and sends
nothing.
In CI
Fail the build when a config drifts. Save this as
.github/workflows/contextpruner-lint.yml and add your key as the repo secret
CONTEXTPRUNER_API_KEY:
name: ContextPruner Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx --yes contextpruner lint
env:
CONTEXTPRUNER_API_KEY: ${{ secrets.CONTEXTPRUNER_API_KEY }}Privacy
The pruning engine runs entirely on your machine — your file tree never
leaves it. lint makes a single network call, to /api/license/verify, which
sends just your API key to confirm an active subscription. The only other thing
that can ever leave your machine is the opt-in aggregate stats above: if — and
only if — you say yes at serve, two integers (files assessed, bytes pruned)
are POSTed to /api/stats/contribute. Never a path, never file contents.
Development
The CLI bundles the app's pure engine at build time:
npm install # in packages/cli
npm run build # → dist/index.jsThe published package name is contextpruner; the repository's root
package.json is a separate private app that happens to share the name.
