Semantic health check for AI agent instruction files. Finds the instructions that will silently break your agent — before your agent runs.
Structural linters check if your CLAUDE.md is well-formed.
agent-doctor checks if it will actually work.
There's a difference.
# These pass every structural linter. They will break your agent.
- Be helpful and concise ← no decision boundary
- Always ask before making changes ← conflicts with rule below
- Complete tasks autonomously without interruption ← decision loop on file edits
- Use the fetch_data tool to retrieve information ← tool also writes logs (agent won't know)
- TODO: add more constraints here ← agent follows this literallyNo regex catches these. No schema validates them. They require semantic reasoning — understanding what the agent will do with each instruction.
agent-doctor uses Claude (or OpenAI) to evaluate your instructions the way your agent will read them.
# Structural only — no API key needed
npx @chiragdarji/agent-doctor CLAUDE.md --structural-only
# Full analysis with Claude
ANTHROPIC_API_KEY=sk-ant-... npx @chiragdarji/agent-doctor CLAUDE.md
# Full analysis with OpenAI
OPENAI_API_KEY=sk-... npx @chiragdarji/agent-doctor CLAUDE.md --model gpt-4o
# Full analysis with Ollama (no cloud API key)
npx @chiragdarji/agent-doctor CLAUDE.md # set provider/baseURL in .agentdoctor.json
# Auto-fix structural issues in-place
npx @chiragdarji/agent-doctor CLAUDE.md --fix
# Preview fixes without writing
npx @chiragdarji/agent-doctor CLAUDE.md --fix --dry-run
# Analyse all instruction files in the project
npx @chiragdarji/agent-doctor --all
# Run as MCP server (Claude Code / Cursor)
npx @chiragdarji/agent-doctor --mcpagent-doctor 🩺 Analysing CLAUDE.md...
❌ CRITICAL Incomplete instruction marker "TODO" found
Line 14: "TODO: add tool constraints here"
Agent will follow this literally — replace before deploying.
→ Replace the placeholder with the actual instruction.
❌ CRITICAL Unclosed code fence (```) — everything after line 22 is treated as code
→ Add a closing ``` fence to end the code block.
⚠ WARNING Heading "Rules" appears more than once (lines 8 and 34)
→ Rename or merge — agents cannot determine which copy takes precedence.
⚠ WARNING Rule 8 × Rule 12 — Decision loop detected
Rule 8: "Always ask before making file changes"
Rule 12: "Complete tasks autonomously without interruption"
→ Add scope: "Ask only before destructive operations."
💡 SUGGEST Section "Output Format" has >60% negation-based instructions
→ Rewrite as positive: "Never write long responses" → "Keep responses under 100 words"
────────────────────────────────────────────
Health Score 48 / 100 (D)
Issues 2 critical · 2 warnings · 1 suggestion
Files CLAUDE.md
Model claude-sonnet-4-6
────────────────────────────────────────────
agent-doctor runs two layers of analysis:
| Rule | Severity | What it catches |
|---|---|---|
missing-frontmatter |
critical | .mdc file missing --- YAML block |
unclosed-code-block |
critical | Odd ``` fences — rest of file read as code |
todo-in-instructions |
critical | TODO/FIXME/PLACEHOLDER left in — agent follows literally |
missing-always-apply |
warning | .mdc where alwaysApply is not true |
missing-description |
warning | .mdc with no description — Cursor can't match it contextually |
conflicting-frontmatter |
warning | alwaysApply: true + globs set — globs silently ignored |
missing-file-glob |
warning | alwaysApply: false + no globs — rule may never activate |
duplicate-heading |
warning | Same heading twice — agent can't pick which wins |
legacy-format |
warning | .cursorrules file ignored by agent mode |
token-budget-exceeded |
warning | Section over configurable token threshold (default 500) |
empty-section |
suggestion | Heading with no content and no children |
heading-depth-skip |
suggestion | ## → #### jump — breaks hierarchy agents use for scoping |
negation-heavy |
suggestion | >60% "don't/never/avoid" bullets — rewrite as positive |
| Rule | Severity | What it catches |
|---|---|---|
decision-loop |
critical | Two rules that conflict on a common task |
contradiction |
critical | Rules that directly oppose each other |
vague-boundary |
warning | Instructions with no measurable success condition |
tool-mismatch |
warning | Tool description doesn't match schema behaviour |
missing-fallback |
warning | Conditional with no else/default branch |
scope-bleed |
warning | Rule intended for one context leaks into all contexts |
over-permissive |
warning | Tool granted with no usage constraint |
ambiguous-pronoun |
suggestion | "it", "they", "this" with no clear referent |
| File | Tool |
|---|---|
CLAUDE.md |
Anthropic Claude Code |
AGENTS.md |
OpenAI Codex CLI, universal |
.cursor/rules/*.mdc |
Cursor IDE |
GEMINI.md |
Google Gemini CLI |
.github/copilot-instructions.md |
GitHub Copilot |
.claude/agents/*.md |
Claude Code subagents |
.claude/commands/*.md |
Claude Code slash commands |
agent-doctor runs as an MCP server so you can call it from inside Claude Code or Cursor.
Pass your API key directly as a tool input — no environment variables needed.
// .claude/settings.json
{
"mcpServers": {
"agent-doctor": {
"command": "npx",
"args": ["@chiragdarji/agent-doctor", "--mcp"]
}
}
}Runs structural + semantic analysis on any instruction file.
{
"filePath": "CLAUDE.md",
"layers": ["structural", "semantic"],
"model": "claude-sonnet-4-6",
"anthropicApiKey": "sk-ant-...",
"openaiApiKey": "sk-..."
}| Input | Type | Required | Description |
|---|---|---|---|
filePath |
string | ✅ | Path to the instruction file |
layers |
array | — | ["structural","semantic"] (default: both) |
model |
string | — | e.g. claude-sonnet-4-6, gpt-4o |
anthropicApiKey |
string | — | Falls back to ANTHROPIC_API_KEY env var |
openaiApiKey |
string | — | Falls back to OPENAI_API_KEY env var |
No API key? Omit
anthropicApiKey/openaiApiKeyand setlayers: ["structural"]for free analysis.
Returns a built-in fix suggestion + LLM-generated before/after rewrite for a specific rule.
{
"filePath": "CLAUDE.md",
"issueRuleId": "todo-in-instructions",
"anthropicApiKey": "sk-ant-..."
}| Input | Type | Required | Description |
|---|---|---|---|
filePath |
string | ✅ | Path to the instruction file |
issueRuleId |
string | ✅ | Rule ID from analyse_agent_file output |
anthropicApiKey / openaiApiKey |
string | — | Enables LLM-generated rewrite |
// .agentdoctor.json
{
"model": "claude-sonnet-4-6",
"layers": ["structural", "semantic"],
"rules": {
"negation-heavy": "off",
"heading-depth-skip": "off",
"missing-always-apply": "off"
},
"tokenBudgetWarning": 500,
"ignore": [".claude/agents/legacy-*.md"],
"failOn": "critical"
}Using OpenAI:
{
"model": "gpt-4o",
"provider": "openai"
}Using Ollama or any local LLM (no cloud API key needed):
{
"provider": "openai-compatible",
"baseURL": "http://localhost:11434/v1",
"model": "llama3.1",
"layers": ["structural", "semantic"]
}Requires Ollama running locally: ollama pull llama3.1. See examples/ollama-config.json for a ready-to-use config.
Provider selection (priority order):
providerfield in config — explicit override- Model name prefix:
gpt-*,o1-*,o3-*,o4-*→ OpenAI; else → Anthropic - API key present:
ANTHROPIC_API_KEY→ Anthropic,OPENAI_API_KEY→ OpenAI
agent-doctor [file] [options]
Arguments:
file Path to instruction file (auto-detects CLAUDE.md if omitted)
Options:
--all Discover and analyse all instruction files in the project
--structural-only Skip semantic layer — no API key required
--model <id> Override LLM model (e.g. gpt-4o, claude-opus-4-5)
--fail-on <severity> Exit code 1 if issues at this level (default: critical)
--format <format> Output format: text | json (default: text)
--fix Auto-fix structural issues in-place
--dry-run Preview --fix changes without writing to disk
--mcp Start MCP server mode
-V, --version Show version number
-h, --help Show helpExit codes:
| Code | Meaning |
|---|---|
0 |
No issues at or above --fail-on threshold |
1 |
Issues found at or above threshold |
2 |
File not found / parse error |
# .github/workflows/agent-doctor.yml
name: Agent Instructions Health Check
on: [push, pull_request]
jobs:
agent-doctor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Structural check (no API key needed)
run: npx @chiragdarji/agent-doctor --all --structural-only --fail-on warning
- name: Semantic check (optional — needs API key)
if: env.ANTHROPIC_API_KEY != ''
run: npx @chiragdarji/agent-doctor --all --fail-on critical
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}npx @chiragdarji/agent-doctor CLAUDE.md --format json \
| jq '.issues[] | select(.severity == "critical")'npm install @chiragdarji/agent-doctorimport { analyse, loadConfig } from '@chiragdarji/agent-doctor';
const config = loadConfig(process.cwd());
const result = await analyse('./CLAUDE.md', config);
console.log(result.score); // 0–100
console.log(result.grade); // 'A' | 'B' | 'C' | 'D' | 'F'
console.log(result.issues); // Issue[]import { analyse, DEFAULT_CONFIG } from '@chiragdarji/agent-doctor';
const result = await analyse('./CLAUDE.md', {
...DEFAULT_CONFIG,
layers: ['structural'],
});import { analyseSemantics, createAnthropicClient, createOpenAIClient } from '@chiragdarji/agent-doctor';
// Anthropic
const client = createAnthropicClient(process.env.ANTHROPIC_API_KEY!);
// OpenAI
const client = createOpenAIClient(process.env.OPENAI_API_KEY!);
const issues = await analyseSemantics(content, filePath, config, client);const config = {
...loadConfig(process.cwd()),
rules: {
'negation-heavy': 'off',
'heading-depth-skip': 'off',
},
failOn: 'warning',
};interface AnalysisResult {
file: string;
score: number; // 0–100
grade: 'A' | 'B' | 'C' | 'D' | 'F';
issues: Issue[];
tokenCount: number;
analysedAt: string; // ISO timestamp
layers: ('structural' | 'semantic')[];
}
interface Issue {
ruleId: RuleId;
severity: 'critical' | 'warning' | 'suggestion';
message: string;
suggestion: string;
line?: number;
context?: string;
relatedLine?: number;
}- Create
src/rules/structural/<rule-id>.ts:
import type { Issue, StructuralRule } from '../../types.js';
/** Detects ... */
export const myRule: StructuralRule = (content, filePath) => {
return []; // return Issue[] to flag, [] to pass
};- Export from
src/rules/structural/index.ts - Add to
rulesarray insrc/analyser/structural.ts - Add
RuleIdtosrc/types.ts - Add tests in
tests/structural.test.ts
Your CLAUDE.md / AGENTS.md / .mdc
│
├─► Layer 1: Structural (13 rules, regex-based, zero API cost)
│ ├─► Frontmatter validation (missing, conflicting, incomplete)
│ ├─► Content quality (empty sections, duplicate headings, TODOs)
│ ├─► Code fence integrity (unclosed blocks)
│ └─► Token budget per section
│
└─► Layer 2: Semantic (8 rules, LLM-powered)
├─► Reads instructions as an agent would
├─► Detects conflicts, ambiguities, missing boundaries
└─► Returns structured JSON → Zod-validated → formatted output
The semantic layer sends only your instruction file (never your codebase) to the LLM API. All analysis runs locally. Nothing is stored or cached.
- CLI —
npx @chiragdarji/agent-doctor <file> - Structural layer — 13 rules, zero API cost
- Semantic layer — 8 rules, Claude + OpenAI
- OpenAI support (
gpt-4o,o1-*,o3-*,o4-*) - MCP server —
analyse_agent_file+suggest_fixtools with API key injection - Programmatic API (
analyse,analyseAll,discoverFiles) - CI/CD mode (exit codes 0/1/2)
-
--fix— auto-fix structural issues (todo-in-instructions,unclosed-code-block,empty-section) -
--dry-run— preview fixes without writing - Ollama / local LLM support via
provider: "openai-compatible"+baseURL - Cursor semantic skill (
skills/cursor-semantic-analysis/SKILL.md) - VS Code extension (inline diagnostics)
- Cross-file conflict detection (CLAUDE.md vs AGENTS.md)
- Rule packs:
cursor-pack,claude-code-pack,langgraph-pack - Watch mode (re-analyse on save)
agent-doctor can repair common structural issues automatically:
# Fix issues in-place
npx @chiragdarji/agent-doctor CLAUDE.md --fix
# Preview changes before writing
npx @chiragdarji/agent-doctor CLAUDE.md --fix --dry-run| Rule | Fix action |
|---|---|
todo-in-instructions |
Replaces TODO line with <!-- TODO removed by agent-doctor — replace with actual instruction --> |
unclosed-code-block |
Appends closing ``` fence at end of file |
empty-section |
Inserts _No content yet — add instructions here._ after the heading |
legacy-format |
Skipped — prints rename instruction; file rename must be manual |
agent-doctor works inside Cursor as an MCP server. For semantic analysis without a separate API key, use the included skill that lets Cursor's own LLM apply agent-doctor's semantic rules.
Copy examples/cursor-mcp.json to .cursor/mcp.json in your project:
{
"mcpServers": {
"agent-doctor": {
"command": "npx",
"args": ["@chiragdarji/agent-doctor", "--mcp"],
"env": {}
}
}
}When you don't have a separate Anthropic/OpenAI key, use the skill at skills/cursor-semantic-analysis/SKILL.md — Cursor's built-in LLM performs the semantic analysis guided by agent-doctor's 8 semantic rules.
1. Use agent-doctor MCP: structural analysis on CLAUDE.md
2. Read CLAUDE.md fully
3. Apply agent-doctor semantic rules (from skill)
4. Output findings with health score
Run full semantic analysis with no cloud API key using Ollama or any OpenAI-compatible endpoint:
# 1. Start Ollama and pull a model
ollama pull llama3.1
# 2. Copy the example config
cp examples/ollama-config.json .agentdoctor.json
# 3. Run analysis
npx @chiragdarji/agent-doctor CLAUDE.md.agentdoctor.json:
{
"provider": "openai-compatible",
"baseURL": "http://localhost:11434/v1",
"model": "llama3.1",
"layers": ["structural", "semantic"]
}Works with any OpenAI-compatible server: Ollama, LM Studio, vLLM, llama.cpp.
Those tools validate structure. agent-doctor validates semantics. They're complementary:
cclint / cursor-doctor → "Is this file valid?"
agent-doctor → "Will this file work?"
git clone https://github.com/chiragdarji/agent-doctor
cd agent-doctor
npm install
npm run test # 213 tests
npm run dev # watch modeSee CONTRIBUTING.md.
MIT © Chirag Darji
Built because every structural linter said my CLAUDE.md was valid. My agent disagreed.