Evolving CLAUDE.md

Wires CLAUDE.md to maintain a living Decisions & Learnings log that the assistant updates as the project evolves — without letting the file bloat. CLAUDE.md is read into context every turn, so the goal is a small, well-pruned set of durable decisions, with everything else linked out or archived. Three hooks keep the log healthy automatically. It complements the built-in init skill: init describes the codebase, this adds the mechanism that lets the file grow safely.

Install

/plugin install evolving-claude-md@alexmskills

Trigger it

/evolving-claude-md:evolving-claude-md

This skill is model-invokable — most often you just ask in natural language: "make CLAUDE.md evolve", "add a self-updating decisions log to CLAUDE.md", or "CLAUDE.md is getting too big, compact it".

When to use it

  • "add learning mechanism to CLAUDE.md", "make CLAUDE.md evolve", "self-updating CLAUDE.md"

  • "decisions log", "ADR-style notes in CLAUDE.md", "project memory in CLAUDE.md"

  • "compact CLAUDE.md" / "CLAUDE.md is getting too big"

  • A CLAUDE.md exists but has no mechanism to keep itself current

  • Not for plain codebase documentation — use init for that

What it does

Three automatic hooks

The plugin ships hooks/hooks.json (pathed via ${CLAUDE_PLUGIN_ROOT}), so the hooks register on enable with nothing to add to settings:

  • SessionStartaudit-claude-md.py checks the D&L section + whole-file size + staleness and injects a recommendation when something needs attention; silent when healthy. Since 1.4.0 staleness covers three rot classes, grounded against the tree rather than reasoned about: a cited artifact git grep can no longer find, a version pin every build file now contradicts ("Spring Boot 4.0.5" vs a pom that says 4.0.7 — prefix-compatible pins and examples stay silent), and a sequence fact the tree has moved past ("latest is V27`" once `V28 exists). The predicates live in a vendorable stdlib core, freshness.py, calibrated across 29 real repos to zero false positives.

  • PreToolUse on Write|Edit of CLAUDE.mdlint-claude-md.py rejects new entries that lack a date or topic-tag, or exceed 200 chars.

  • PostCompact — re-runs the audit so the assistant sees the current state without paying to re-read the whole file.

What’s new in 1.5.0 — the capture side

Every earlier mechanism was pruning pressure on entries that already exist; the blind spot was a log that was never written to (issue #37 — installed for weeks on a 127-commit repo, zero output, while sessions rediscovered the same gotchas).

New tree-grounded audit states, fleet-calibrated on 33 repos:

  • Unadopted — a hand-rolled gotchas/learnings heading with bullets but zero parseable D&L entries: the repo already tried to solve the problem by hand; the audit offers the migration.

  • Empty log — 20+ commits, docs/ markdown at least 5x CLAUDE.md, and no entries: the learning is going somewhere that doesn’t load every turn.

  • Docs recurrence — "the third time…​" self-counting language in docs/; a rule that proves itself in prose belongs in the file that loads every session.

  • Layout drift, both directions — a git-tracked top-level directory CLAUDE.md never mentions, and a mentioned dir/ gone from the tree (filtered by git history so foreign paths from skill docs never fire).

New opt-in capture triggers (both default off until real-use noise is measured): a session-end prompt when a session committed and edited files but never touched CLAUDE.md, and commit-message mining that offers to promote gotcha-shaped commit prose while the context is hot. Both route entries: repo-durable → the D&L log; machine-personal → the learn-on-failure skill.

New structure review: analyze the file’s internal shape against a shipped, researched rulebook (references/claude-md-best-practices.md — 96 cited findings from official docs, ~40 real repo files, and measured studies; adversarially verified). Recommendations must cite a rule or a tree-provable check, and the measured evidence hierarchy caps cosmetic rearrangement.

What’s new in 1.1.1

  • Merge as the fourth downward pressure — SKILL.md now documents merging same-session same-area clusters (phased rollouts e2a/e2b/e3/e4; multi-aspect bursts within ~48h) as the compaction lever that works pre-14-days. Graduation and quarterly archive both require old entries; merge does not. Discovered while compacting a sibling repo’s 37-entry log when the audit fired "RECOMMENDED" but every documented pressure was blocked.

What’s new in 1.1.0

  • Whole-file size check — audit now reports total CLAUDE.md KB independently (warn 25 KB, recommend 40 KB). Catches bloat that lives outside the # Decisions & Learnings section (Conventions / Architecture / Gotchas) which the entry/line counters never saw.

  • Self-report when D&L is missing — a repo without the # Decisions & Learnings section no longer exits silently; the audit reports the file size + total dated-bullet count and nudges to wire evolving-claude-md.

  • Staleness trigger — for each D&L entry, the audit extracts backticked artifact-looking tokens (paths, ClassName.method, --cli-flags, <xml-tags>, filenames with known extensions) and runs git grep -qF against the tree. Entries citing ≥2 missing tokens surface as "may be wrong now" candidates. Budget-capped (~2.5s) so the hook stays fast. Closes the only signal the bloat-only audit lacked: a delete lever, not just a compress lever.

Entry format contract

Every entry follows - YYYY-MM-DD — topic-tag — what. Why: reason. [see → docs/decisions/…​]. Absolute dates only, a mandatory kebab-case topic tag, and a hard 200-char body cap (bigger detail moves to docs/decisions/).

Downward pressure on size

A Recent (last 14 days) / Historic split keeps the actively-scanned section small. Reversals are struck through, stable patterns graduate into Conventions/Gotchas, and a quarterly archive-decisions.py run moves old entries into docs/decisions/{YYYY-Q}.md.

Tuning per repo

"Concise" is not a universal number — 40 KB is bloat in a library and reasonable in a monorepo. Every threshold is overridable, resolved defaults → ~/.claude/evolving-claude-md/config.json<repo>/.claude/evolving-claude-md/config.json.

{
  "file_warn_kb": 25,      "file_recommend_kb": 40,
  "lines_warn": 200,       "lines_recommend": 300,
  "entries_warn": 25,      "entries_recommend": 35,
  "mega_entry_chars": 800, "topic_cluster": 3,
  "layout_min_dirs": 5,
  "coverage": true,        "nested": true
}

Name only what you want changed. Unknown keys are ignored and a corrupt file falls back to defaults — this runs on SessionStart, so a bad config must never be why a session starts badly.

Companion context files

.claude.local.md and nested CLAUDE.md files load into context exactly like the root file, so bloat in them was previously the same problem measured nowhere. The audit now size-checks both, walking up to three levels deep and skipping node_modules, target, build and friends.

Only the root file gets the full treatment — Decisions & Learnings parsing, staleness, coverage — because that is where the log lives, and reporting on five files at every session start would be its own kind of noise.

The routing rule for which file a learning belongs in is one question: would this still be true on a teammate’s laptop, in CI, and in a fresh clone? No means .claude.local.md. An absolute path containing your username is the most common way a personal detail gets committed — ~/ is fine, /home/alex/… is not.

Coverage — the one upward check

Every other check pushes content down: bloat, staleness, clustering, archiving. A file can pass all of them and still be useless — well under every threshold, perfectly formatted, and never saying how to run the tests.

Two gaps, both grounded in the tree rather than in a generic checklist:

Gap Fires only when

no build/test command

a build file exists (pom.xml, package.json, Cargo.toml, go.mod, Makefile, … 12 supported) and CLAUDE.md never mentions its command

nothing on layout

the repo has 5+ meaningful top-level directories — generated ones like target/ and node_modules/ don’t count — and CLAUDE.md never says where anything lives

The grounding is the design. A docs repo has no build command and a three-directory repo needs no layout section; a generic checklist nags both. Measured across a 29-repo sample these fired zero times, because every one already covered what its tree justified.

Decline a gap by putting the decision in the file itself:

<!-- audit-skip: commands, layout -->

A gotchas check was built and then removed: on the same sample it fired on 17 of 29 repos, and those 17 were exactly the ones with no Decisions & Learnings log — so it was re-detecting "hasn’t adopted this skill", which the audit already reports. Gotchas arrive by graduation from the log, and the topic-cluster check is the grounded way to prompt for them.

Notes

  • Manual (non-plugin) install requires copying the three scripts into .claude/skills/evolving-claude-md/ and adding the hooks to .claude/settings.json, then one session restart.

  • The quarterly archive is the one operational step nothing automates — set a calendar reminder.

  • Disable a hook by removing its entry; disable all via disableAllHooks: true.