CLAUDE.md Doctor — exam procedure
SkillAI & modelsGive this repo's CLAUDE.md / AGENTS.md a checkup — size vitals vs official guidance, dead references, dead commands, stale claims — then backtest every rule against the repo's own Claude Code session history to see which rules were actually followed, ignored, or never used, and produce a doctor-style HTML report with evidence-cited prescriptions. Use when asked to check, diagnose, audit, review, improve, optimize, lint, grade, fix, clean up, shorten, or "doctor" CLAUDE.md, AGENTS.md, or agent instruction/memory files, or to find out whether CLAUDE.md rules actually work. Also use when a repo has NO memory file and the user wants one — "write/generate/suggest a CLAUDE.md (or hooks) from my sessions" — the skill mines the repo's real session history and drafts a proposed file with receipts.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the CLAUDE.md Doctor — exam procedure skill
What this skill tells your AI
The instructions your AI receives, as published by agent-clinic/claude-md-doctor in skills/claude-md-doctor/SKILL.md and read by ahel’s review.
You are running a checkup on this repository's agent-instruction files. The deterministic work lives in scripts; your job is the judgment between them. Do not re-derive what a script already measured, and do not skip a stage — the report verifies the work-state manifest and will disclose skipped stages.
Definitions used below:
SCRIPTS=${CLAUDE_SKILL_DIR}/scripts— Claude Code substitutes${CLAUDE_SKILL_DIR}with this skill's own directory at run time, so the scripts resolve no matter the working directory or how the skill was installed. Use it literally; do not try to locate the skill yourself.REPO= the repository to examine (the argument if one was given, else the current working directory).WORK=REPO/.claude-md-doctor/work(scripts default to this; pass--workto relocate, e.g. into a scratch directory to avoid writing in the user's repo — prefer that when the repo is not yours to dirty).
Stage 0 — preflight
Run python3 --version. If python3 is missing, stop and tell the user this
skill needs Python 3.9+ (stdlib only, nothing to install).
Stage 1 — intake
python3 SCRIPTS/intake.py --repo REPO --work WORK
Then read WORK/intake.json (it is small). Note for later judgment:
- Is the project CLAUDE.md a pointer (
is_pointer) to AGENTS.md? That is the healthy, officially-recommended pattern — the target is the patient. Never diagnose a pointer file as "too short." Checkpointer_style:symlinkandimportwork;bare-textis a broken pointer — a regular file containing justAGENTS.mdwithout@means Claude Code never loads the target. That is a critical diagnosis with a one-character fix (@), unless you are examining a raw-fetched copy where symlinks flatten to text. - Ancestor and user-scope files are context the session loads but the repo can't fix — mention them, don't prescribe changes to them unless asked.
- A file with scope
orphan-agentsmeans the repo has an AGENTS.md but no CLAUDE.md pointing at it — Claude Code loads nothing. That is a critical diagnosis with the official one-line fix (create a CLAUDE.md containing@AGENTS.md), and you should still run the full static exam on the AGENTS.md itself, since it becomes the patient the moment the pointer exists. - If NO memory files exist at all, do not stop with an empty report — switch
to Mode B (below): mine the session history and write the initial
chart. Only when there is no session history either does the exam end,
with the
/init-plus-aggressive-pruning prescription and a note saying why nothing else could run.
Stage 2 — vitals
python3 SCRIPTS/vitals.py --work WORK
Read WORK/vitals.json. The script measured; you interpret. Detector notes:
init_boilerplatemeans the file still opens with stock/initoutput — a generated-and-never-pruned marker.emphasis_per_100_linesmatters as density, not presence (sparse emphasis is officially endorsed).- High
dated_per_100_linessuggests session-log/changelog accretion. - Very low
imperative_ratioon a large file suggests narrative documentation rather than instructions — read a sample and judge; the arcan case (a CLAUDE.md containing a sabotage manual) is why this check exists. - Judge the aggregate surface, not only each file (
launch_loaded_combinedplus the file count): many individually-healthy files can still sum to a heavy standing context, and cross-file duplication or contradiction is invisible per-file. When the combined surface is the problem, prescribe the escalating ladder — consolidate duplicates, then a thin router/index over on-demand files, then a one-screen always-on invariants file with procedures moved to skills (citation idsurface-bloat).
Stage 3 — records check
python3 SCRIPTS/refcheck.py --work WORK
Read WORK/refcheck.json. Your judgment passes:
- Review the failures, don't parrot them. For each
missing/machine_specific/glob_emptyreference and eachmissingcommand, open the cited file:line and confirm it is a real reference (not prose that merely looks like a path — API endpoints, MIME types, git refs, and files the text describes as deleted are the common false positives). Record each false positive indismissed_refswith its reason: the report shows only confirmed findings and discloses dismissals in a collapsed note. - Extract checkable claims the scripts cannot: countable assertions in
the memory files ("3,540 tests across 374 files", "12 UI components",
"there is no ESLint config"). Verify the cheap ones with quick commands
(file counts, grep for configs). Do NOT run test suites or builds unless
the user asked. Record each as
verified/drifted/unverifiedwith a one-line detail —unverifiedis an honest answer for anything expensive.
Stage 4 — history backtest
Skip this stage only if intake found no session directory (sessions.dir
null) — and then say so in chat; the report's History section will state it.
4a — condense the transcripts
python3 SCRIPTS/sessions.py --work WORK
4b — decompose the memory files into a rulebook (your judgment)
Write WORK/rulebook.json (schema documented at the top of backtest.py).
Guidance:
- Decompose EVERY directive in the file — the rulebook is the complete
directive inventory, and the enforcement ladder's "N of M" is only honest
if M is the whole file. Only mechanically checkable rules get matchers:
bans and requirements visible in Bash commands or Edit/Write content, and
finish-ordering rules via
ordering. Judge-class and not-yet-mechanizable rules go in as classification-only entries (enforcement block, no matchers) — never force a regex onto a semantic rule. Informational content (facts, architecture, API semantics) stays OUT of the rulebook. - For edit/write events the matchable text is
PATH: <file_path>on the first line followed by the (truncated) new content — anchor path-based rules on^PATH: .*…and content rules on the body. - Write conservative regexes (prefer false negatives over false
positives), use
scope.paths/scope.exclude_pathsto confine file-scoped rules, and date each rule withintroducedfromgit log --follow --format=%aI -- <file>when the file's history makes that cheap — sessions that ended before a rule existed must not count against adherence. - Classify every rule's enforcement (the
enforcementblock — schema at the top ofbacktest.py). Split compound rules into clauses first; each clause classifies independently. The class is the cheapest reliable detector:hook(event-stream regex: bash/edit/path/tool-input/output gates, ordering, cadence — try the event-ordering and standing-invariant reframings BEFORE surrendering a rule to judge),linter/test(static analysis over artifacts: lint rules, discipline tests, import-graph boundaries — recordscope_kind: file|project), orjudge(only an LLM can score it). A rule even a judge couldn't score is not a rule — diagnose itvague. Detect existing enforcement: if the repo already has the test/lint/hook the prose describes, setcurrent_layerto it — that rule is a healthy pointer, never a prescription target.current_layermay also be an org-level rule platform (team-wide rulebooks with centralized detectors/telemetry) — the right home for cross-repo rules, judge-class auditing at scale, and staged warn→block rollouts that per-repo configs can't govern. Give every classified rule anecho_regexof its distinctive tokens (for proven-defiance detection) and anorigin(root/nested/rules — only non-root rules can be truly absent after compaction). Also judgeagainst_prior: true|falsein the enforcement block: would a frontier model do this by default WITHOUT the rule? A with-prior rule showing high compliance may be coincidence, not obedience (citation idharness-if) — flag it as a redundancy candidate in diagnosis rather than celebrating it as healthy. And when prescribing move-to-skill: that move is for procedures only — a constraint demoted into a skill description measurably loses precedence (project files outrank tool/skill descriptions).
Engine semantics you need (so you don't reverse-engineer them):
- "Opportunities" = matcher fires (violation+compliance+context hits) for regex rules, and mutated-session count for ordering rules. Zero can mean "rule never applied" OR "your scope is wrong" — for any zero-fire path-scoped rule, run one negative control (confirm the sessions contain no events under that scope at all) before calling it inert.
scope.pathsfilters only events that carry a file path; bash events pass a paths filter (they have no path) — for mixed bash+edit rules put path constraints into the regex (^PATH: …) if bash must be excluded.exclude_pathsandrepo_onlyDO apply to ordering-rule mutation counting.- Edit/Write matchable content is truncated to ~1200 chars of new content (bash commands ~600) — first-line rules are fine; end-of-file or size rules are not expressible as content regexes.
- Condensed sessions are a top-level JSON array of event objects.
- Read-before-edit ordering is NOT yet expressible (
ordering.requirematches bash commands only) — classify such rules as unmechanized hooks; don't torture a regex.
4c — run the engine
python3 SCRIPTS/backtest.py --work WORK
4d — sample-verify (MANDATORY — matchers have bugs)
Read WORK/backtest.json. For EVERY rule with fires — violation AND
compliance samples both — read the sample excerpts and confirm each is a
true positive. A matcher with any false
positive gets fixed in rulebook.json and the engine re-run — this loop is
cheap and it is the whole reason the results can be trusted. Only when every
sampled fire is confirmed, set "verified": true in backtest.json
(edit the file) — the report shows a "provisional" banner otherwise.
Mention is not use. The most common false positive is a session that
talks about a rule rather than breaking it: documenting the hazard,
grepping for offenders, writing the rule itself, quoting it in a commit
message or a retraction. A regex cannot tell discussion from violation, and
these land as defiance-proven — the most severe cause — because the rule
text is echoed right there. When a repo's own docs quote its rules, expect
this and check the excerpt for whether the event performed the banned
action or merely referred to it. Repos that document their own conventions
generate this heavily; drop those fires and tighten the matcher (anchor on
the action, exclude edits to the memory files and docs via
scope.exclude_paths).
Then record per-rule verdicts in diagnosis.json under rule_verdicts:
"rule_verdicts": {
"R1": {"verdict": "healthy|ignored|mixed|inert", "note": "one line of judgment"}
}
(That is the common subset — Stage 5's schema is canonical and adds
unmeasured|abandoned|undocumented; mined rules take undocumented.)
inert (zero opportunities in the window) is a finding, not a failure —
say what it means: the rule cost context in every session and never came up.
The engine also triages every violation by cause: defiance-proven (the
agent echoed the rule in its own text, then violated it — the reminder
already happened and lost), defiance (fresh context), dilution (late
turn / heavy context), absence-risk (non-root rule after a compaction
boundary). Read the causes before judging: they pick the medicine — proven
defiance justifies block-mode; dilution calls for slimming/path-scoping, not
cages; absence calls for re-injection hooks. Sanity-check the buckets while
sample-verifying (a "dilution" tag on a turn-2 violation means the occupancy
proxy misfired — say so). Ordering-rule caveat: verdicts are per-transcript —
in subagent/worktree workflows the required command may have run in a sibling
transcript. A conversation message claiming it ran ("verify green") is not
proof; note the claim in your verdict and check whether repo edits happened
after it (the obligation re-ripens).
4e — compile enforcement proposals
python3 SCRIPTS/compile.py --work WORK
This writes WORK/enforcement/ — a PROPOSALS.md dossier per rule, a generic
guard script, its per-rule config (warn-mode by default; defiance-proven
rules start at block), and a settings snippet. Never install any of it
yourself; never edit the user's .claude/settings.json. Goodhart caution
(citation id specbench): a visible pattern-gate can be satisfied without
honoring the rule — where a rule has a real outcome (tests pass, build
green), prefer a gate that runs the outcome over one that greps a pattern. Tell the user
where the proposals live and that they are review-then-arm.
4f — gap analysis: what the sessions dictate that the file never says
python3 SCRIPTS/mine.py --work WORK
Read WORK/candidates.json and compare the surviving groups against the
rulebook: a recurrent signal (correction cluster, failed→fixed command
pair, recurring user denial) that matches NO existing rule is a rule the
user keeps dictating by hand, session after session. Judge as in Mode B2
below — decline one-offs and stale groups (automode blocks are already
excluded by the miner). For each
accepted miss, add a diagnosis with state undocumented (severity warn,
evidence = 1–2 excerpts), and when there are enough to matter, write
WORK/chart.json with "mode": "gap" (schema at the top of generate.py)
and run python3 SCRIPTS/generate.py --work WORK to emit
PROPOSED-ADDITIONS.md. Mind the combined budget: proposed additions must
not push the surface past the size target this same exam just graded.
Stage 5 — diagnosis (your judgment, written to a file)
Write WORK/diagnosis.json:
{
"grade": "B",
"chief_complaint": "One sentence, doctor-voice, the single biggest issue.",
"history_note": "optional override for the History section",
"stale_claims": [
{"claim": "…", "file": "/abs/path", "line": 12,
"status": "verified|drifted|unverified", "detail": "…"}
],
"dismissed_refs": [
{"ref": "the exact ref string from refcheck.json", "line": 46,
"reason": "why it is a false positive (route not file, MIME type, described as deleted, …)"}
],
"rule_verdicts": {
"R1": {"verdict": "healthy|ignored|mixed|inert|unmeasured|abandoned|undocumented",
"note": "one line of judgment; 'abandoned' = the repo's own history contradicts the rule (e.g. git shows the team doing the banned thing routinely) even if sessions were inert"}
},
"diagnoses": [
{"state": "dead-ref|stale|vague|ignored|inert|redundant|contradictory|oversized|accretion|generated-unpruned|undocumented",
"severity": "critical|warn|info",
"title": "short name", "detail": "1–3 sentences, plain language",
"file": "/abs/path", "line": 46,
"evidence": ["short quoted lines or metric readouts"],
"citations": ["official-200"],
"prescription": "the concrete fix, imperative voice"}
],
"prescriptions": [
{"action": "repo-wide action", "rationale": "why", "citations": ["eth"]}
],
"followup": ["re-run cadence; transcript-retention advice; what to fix first"],
"share_note": "one quotable line for the public share card — dry doctor's wit backed by the findings. STRICT safety: no file paths, no rule text, no quotes from the repo, no session ids, nothing repo-identifying; aggregate truths only (e.g. 'The loudest rule was the broken one.'). Omit the field to use a deterministic fallback."
}
Rules for this stage:
- Every diagnosis needs evidence (a quoted line, a metric, a failed check)
and, where one exists, a citation id. List the valid ids and what each
source claims with
python3 SCRIPTS/report.py --list-citations— use only those ids, and only where the source actually supports the point. A check with no official or research backing is stated as a heuristic in itsdetail. - Severity honestly:
critical= the file lies to the agent (dead refs, drifted claims, contradictions) or content is being skipped (4 MiB);warn= costs context or reduces adherence (oversized, emphasis saturation, accretion);info= worth knowing. - Structure-only findings carry a caution: the one factorial study found
no structural effect in its tested range (citation id
mcmillan) — do not present size/position folklore as causal fact. Content findings (dead refs, drift) need no such hedge. - Grade rubric: A = no criticals and at most 2 warns; B = no criticals and 3 or more warns; C = 1–2 criticals; D = 3+ criticals; F = the file is actively misleading (mostly dead/drifted) or unloadable. A pointer-style CLAUDE.md with a healthy target grades on the target.
- Cannot-fix scopes (ancestor/user/managed files) may generate
infodiagnoses only. - Never mention this tool's version numbers in report content (diagnoses, notes, follow-ups, chief complaint). The renderer stamps the version in the report footer; content reads timelessly — a reader doesn't know or care what "v0.2" means.
- Pointer repos are usually cross-agent repos. When the patient is an
AGENTS.md reached via a pointer, it likely serves Cursor/Codex/Copilot too —
prescriptions that relocate content into Claude-only surfaces
(
.claude/rules/, skills, hooks) hide it from those agents. Still prescribe them when right, but state the trade-off in the prescription ("Claude-only; other agents reading AGENTS.md will lose this") and prefer in-file fixes for content every agent needs.
Stage 6 — report
python3 SCRIPTS/report.py --work WORK
Then generate the share-safe card and badge:
python3 SCRIPTS/card.py --work WORK
card.svg (postable checkup card) and claude-md-health.svg (README badge)
land next to report.html. Both are aggregate-only by construction — but eye
the card once anyway before telling the user it is safe to post. Offer the
badge snippet the script prints for their README.
Open or send the resulting report.html to the user, and summarize in chat:
grade, chief complaint, the top 3 findings, and the single highest-value
prescription. Tell the user where the report lives. If report.py printed an
INCOMPLETE warning, say which stage was missing and why.
Mode B — the chart-less patient (no memory file? mine one)
Route here when intake found NO memory files at all, or when the user asked
to generate a CLAUDE.md / hooks / lint suggestions from their history —
but if the repo already HAS a memory file, never run Mode B: run the normal
exam and satisfy the generate request through Stage 4f (gap analysis,
"mode": "gap" → PROPOSED-ADDITIONS.md), so the existing file stays the
patient and the intake framing ("no memory file exists") stays true.
The transcripts already contain the unwritten rulebook: corrections the
user keeps typing, commands that fail until the right one runs, facts
re-derived at every session start, tool calls the user rejects. Mode B
takes a history and writes the initial chart.
Run Stages 2 and 3 first anyway — on an empty surface they finish instantly, keep the manifest honest, and "0 tokens loaded every session" is the patient's baseline vital.
B1 — condense and mine
python3 SCRIPTS/sessions.py --work WORK
python3 SCRIPTS/mine.py --work WORK
candidates.json holds mechanically pre-filtered signals in five families
(corrections, failure_recovery, rediscovery, denials, preambles) plus a
startup-tax estimate. The lexical markers are calibrated to ~65–75%
precision — YOU are the judge pass; nothing in this file is a rule yet.
B2 — judge the candidates (your judgment)
Triage every entry:
- A rule states something durable the user would still endorse: repeated
corrections that converge ("use pnpm", "never push directly"),
failed→fixed pairs whose fix is systematic (wrong runner, wrong dir,
missing env var), recurring
user-rejected/permission-ruledenials. Write it as one imperative line. - A fact is repo knowledge the agent keeps re-deriving: build/test commands from rediscovery groups, layout/context from preamble clusters.
- Decline one-off taste, task-specific instructions, anything flagged
stale(the repo may have moved past it), and excerpts you cannot confidently generalize. (Auto-mode classifier blocks are already excluded by the miner — they appear only asmeta.automode_blocked; do not resurrect them.) Record notable declines with reasons — the report discloses them. - The recurrence gates already ran for the GROUPED families
(failure_recovery, rediscovery, denials, preambles). Corrections are
ungated — any single flagged message reaches you — so judge them hardest;
a one-off correction is only a rule if its content is plainly durable.
Then apply the meaning test to everything: would the user bet on this
line? When unsure, decline — a mined draft earns trust by being small.
And spot-check recall: the pre-filter misses bare factual corrections
without marker words (
meta.known_gaps); skim one or two condensed sessions' user texts if the yield looks thin.
B3 — validate mined rules through the backtest (receipts)
For each accepted rule that is mechanically checkable, write a standard
WORK/rulebook.json entry (schema at the top of backtest.py; set
"source": {"file": "mined-from-history", "line": 0}, classify its
enforcement, give it an echo_regex), then:
python3 SCRIPTS/backtest.py --work WORK
python3 SCRIPTS/compile.py --work WORK
The backtest counts are the rule's receipts — a mined rule whose matcher finds nothing in the very history that suggested it is a mining false positive: drop it. Sample-verify fires exactly as in Stage 4d. compile writes review-then-arm hook proposals for hook-class mined rules — born mechanized: the best CLAUDE.md line is the one a guard enforces.
B4 — write the chart and generate the draft
Write WORK/chart.json (schema at the top of generate.py): accepted facts
and rules with occurrences/sessions from mining or backtest, per-item
evidence excerpts, startup_tax (copy est_tokens/sessions from
candidates.json — the estimate is attributed to the recurring discovery
commands' own records and results, so quote it as exactly that, an
estimate), and your declined list. Then:
python3 SCRIPTS/generate.py --work WORK
This assembles PROPOSED-CLAUDE.md next to the report — receipts ride in
HTML comments, which Claude Code strips at load, so they cost the adopter
nothing. It exits nonzero if the draft breaks the official 200-line target:
the doctor does not prescribe the disease it diagnoses. Never copy the
draft into the repo yourself — adoption is the user's move.
B5 — diagnosis, report, card
Proceed to Stages 5 and 6 as usual. Mode B specifics:
chief_complaint: the absence plus its cost, with evidence ("No memory file exists; N sessions show M recurring rules dictated by hand").- Grade the gap, not the void: D when the history shows recurring unwritten rules being re-dictated or violated; C when the mined chart is thin. F stays reserved for actively misleading files — absence is not deception.
rule_verdictsfor backtested mined rules use verdictundocumented(they cannot be "ignored" — there was no file to ignore).- The report renders the Initial chart section from
chart.jsonand the card switches to intake stats automatically. Tell the user wherePROPOSED-CLAUDE.mdlives, that every line carries its receipt, and that hook proposals (if any) are review-then-arm.
Conduct
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 35
- Forks
- 2
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
claude-md-doctor- Source
- github.com/agent-clinic/claude-md-doctor