DEV Community

vamshidhar reddy
vamshidhar reddy

Posted on

I built a linter that proves 74% of your AGENTS.md is wasting your AI agent's time

Ctxlint command line

If you use Claude Code, Cursor, Codex, or Gemini CLI, you probably have an AGENTS.md or CLAUDE.md sitting in your project root.

It gets loaded into every single session. Every token in it competes for attention with your actual task.

And most of them are full of junk.

The problem I kept seeing

I was reviewing context files across open-source repos and noticed the same patterns everywhere:

  • Directory trees that agents discover with ls in 200ms
  • "Built with React 18, TypeScript, and Tailwind CSS" — readable from package.json
  • References to src/middleware/auth.ts that was renamed to src/auth/middleware.ts three months ago
  • "Use 2-space indentation" when .prettierrc already enforces it
  • Entire sections copy-pasted from the README

None of this helps the agent. All of it costs tokens on every session.

What I built

ctxlint — a CLI linter for AI agent context files.

npx @ctxlint/ctxlint check
Enter fullscreen mode Exit fullscreen mode

8 deterministic rules. Zero LLM dependency. Pure filesystem analysis. Runs in milliseconds.

It supports AGENTS.md, CLAUDE.md, GEMINI.md, .cursorrules, and copilot-instructions.md.

The 8 rules

Rule Severity What it catches
stale-file-ref error References to files or directories that no longer exist
stale-command error Build/test scripts that don't match your actual package.json
no-directory-tree error Embedded directory structures agents discover on their own
no-inferable-stack warn Tech stack descriptions already in your config files
redundant-readme warn Sections that duplicate your README.md
no-style-guide info Coding style rules that belong in eslint/prettier/ruff
max-lines warn Files over 200 lines (production teams keep theirs under 60)
token-budget warn Token cost estimate with signal-to-noise breakdown

Every rule runs against your actual codebase — it checks whether referenced files exist, whether npm scripts are real, whether your linter config already covers a style rule.

What it looks like

Running ctxlint check on a typical bloated context file:

AGENTS.md

  ✗ no-directory-tree  Lines 4-7 contain a directory tree (~14 tokens)
     Agents discover file structure via ls/find — this just adds noise.

  ✗ stale-command  `npm run test:e2e` — script does not exist
     Available: dev, build, test, lint, typecheck

  ✗ stale-file-ref  `src/config/auth.ts` does not exist
     Stale refs mislead agents into searching for ghost files.

  ⚠ token-budget  24 lines, ~104 tokens
     Signal: 40 tokens (38%) ✓  Noise: 64 tokens (62%) ✗
     Ratio: 0.38 (poor)  Monthly: $0.09 → $0.03 (67% saved)

  Summary: 3 errors, 1 warning, 0 info
Enter fullscreen mode Exit fullscreen mode

I tested it on real repos

I didn't just test against toy fixtures. I cloned 8 popular open-source repos that have real context files maintained by their teams, and ran ctxlint against each one.

Repo Context file Key findings
next.js AGENTS.md Directory tree present. Multiple stale file refs — paths are relative to packages/next/ but written as root-relative
langchain CLAUDE.md Directory tree. Stale monorepo paths. Parent-relative refs (../) that don't resolve from root
codex AGENTS.md 5 stale file refs — files live under codex-rs/ subdirectory but refs assume root
ruff CLAUDE.md Borderline redundant-readme overlap. Architectural naming convention flagged (false positive — we fixed it)
anthropic-cookbook CLAUDE.md Directory tree flagged correctly

Overall precision: 91%. Zero crashes across all repos.

The most common issue by far: stale file references in monorepos. People write paths assuming root, but the files actually live two directories deep in a workspace package.

What surprised me

Directory trees are universal and universally useless. Almost every auto-generated context file has one. Agents don't use them — they run ls and find themselves. It's the single biggest source of token waste.

Stale commands are dangerous, not just wasteful. When your context file says npm run test:e2e but that script was renamed to test:integration last month, the agent runs the stale command, gets an error, spends tokens debugging a non-existent script, and then discovers the right one on its own. You paid triple.

The hardest rule to get right was redundant-readme. I use trigram overlap to detect similarity between context file sections and README sections. At 40% threshold, it catches real duplication but occasionally flags sections that share vocabulary without actually saying the same thing. Still tuning this one.

Style guide rules are always inferable. If you have .prettierrc or .eslintrc in your repo, every style rule in your context file is redundant. The agent reads the formatter output, not your prose.

The technical decisions

Why rule-based instead of LLM-powered? Because the problem is deterministic. Checking if src/auth.ts exists is fs.existsSync(). Checking if npm run test:e2e is a real script is JSON parsing. Using an LLM to do filesystem checks would be slower, more expensive, and less reliable.

Why zero dependencies beyond commander? Keeps the install fast, the attack surface small, and npx @ctxlint/ctxlint check works without polluting node_modules. No chalk (ANSI codes directly), no glob (recursive readdir), no YAML parser (regex for key fields in pyproject.toml).

Why synchronous I/O? A linter runs once and exits. fs.readFileSync is simpler than async/await chains and fast enough — the entire analysis takes under 100ms on repos with 10,000+ files.

Try it

# Check your context file
npx @ctxlint/ctxlint check

# Generate a minimal one from scratch
npx @ctxlint/ctxlint init --dry-run

# Strip the bloat from an existing file
npx @ctxlint/ctxlint slim --dry-run AGENTS.md

# Check for drift since last update
npx @ctxlint/ctxlint diff
Enter fullscreen mode Exit fullscreen mode

What's next — and where I need help

This is early stage. 116 npm downloads on day 1 tells me the problem is real, but the tool needs work:

  • Monorepo awareness — the biggest source of false positives. Paths in context files often assume a workspace root, not the repo root
  • Python/Rust/Go ecosystems — currently strongest on Node.js projects. Need pyproject.toml, Cargo.toml, and go.mod command extraction
  • VS Code extension — inline diagnostics instead of CLI output
  • GitHub Action — run ctxlint in CI and fail on stale refs

Good first issues are tagged in the repo. Whether it's a one-line regex fix or a full new rule, contributions are welcome.

ctxlint npm package

GitHub: github.com/vamshidhar199/Ctxlint
npm: npmjs.com/package/@ctxlint/ctxlint


What's in your context file that you're not sure belongs there? Run npx @ctxlint/ctxlint check and share what it finds — I'm curious how it performs on projects I haven't tested yet.

Top comments (1)

Collapse
 
apex_stack profile image
Apex Stack

This is exactly the kind of tooling the agent ecosystem needs right now. I maintain a pretty large CLAUDE.md for a project with 15+ scheduled agents, and the stale-file-ref rule alone would catch so many phantom references that accumulate as the codebase evolves.

The token-budget analysis is the real killer feature though. Most people have no idea how much of their context file is signal vs. noise — and when every token competes for attention in the context window, the noise isn't just wasteful, it actively degrades the agent's output quality.

Curious about one thing: does ctxlint handle nested includes or references between context files? E.g., when your CLAUDE.md references a glossary.md or other project files via @-mentions — does it validate those transitive references too?