Back to Discover

claude-adapt-rules

plugin

Patrick-DE

View on GitHub
1 starsMITSynced Aug 15, 2026

Install to Claude Code

/plugin marketplace add Patrick-DE/claude-adapt-rules

README

claude-adapt-rules

Mine your own Claude Code sessions for the moments you corrected the agent, distil those into rules, and land them where a future session will actually read them.

Two tiers, because the cost of a rule is not the same everywhere:

TierTargetPolicy
repo~/.claude-adapt-rules/rules/repos/<project>/rules.mdauto-written; blast radius is one project, and it's a git diff away from gone
global~/.claude-adapt-rules/rules/global/PROPOSED.md~/.claude/CLAUDE.mdproposed only, you approve; every line is loaded in every session of every project

Where the human text actually is

The interesting finding from building this. In a Claude Code desktop install, ~/.claude/projects/<slug>/<session>.jsonl records your prompts as:

{"type": "queue-operation", "operation": "enqueue", "content": "<what you typed>"}

The type: "user" records are almost entirely tool results and machine continuations. On this machine: 17k type:user records, of which 64 survive noise filtering — and all 64 are machine-generated (Continue from where you left off., compaction summaries, skill payloads). The 1325 real prompts are all in queue-operation records.

A miner that reads type: "user" learns nothing about the user. Both channels are read here, queue preferred, overlaps de-duplicated.

Install

As a Claude Code plugin:

claude plugin marketplace add https://github.com/Patrick-DE/claude-adapt-rules.git

Or from a local checkout, which is what you want while iterating on the tool itself:

claude plugin marketplace add /path/to/claude-adapt-rules

Then enable claude-adapt-rules. That registers two hooks — SessionStart (inject this project's rules) and SessionEnd (capture corrections) — plus the /claude-adapt-rules skill. A third, opt-in PreToolUse hook is described under Guards. Requires Python ≥ 3.12 on PATH as python.

PlatformWhat loadsNotes
Claude Code (Windows)skill + both hooksprimary target; hooks exec python directly, no shell needed
Claude Code (macOS/Linux)skill + both hookschange command to python3 in .claude-plugin/plugin.json if python is absent
Antigravity / Geminiskill + GEMINI.md contextno session hooks — run extract on a schedule and read rules from ~/.claude-adapt-rules/
Codexskill + AGENTS.md contextsame

State lives in ~/.claude-adapt-rules/ (CLAUDE_ADAPT_RULES_HOME overrides), never inside the plugin directory — installed plugins live under a versioned cache path, so an update would orphan your ledger, queue and archive. CLAUDE_ADAPT_RULES_USER_HOME separately overrides the OS home directory itself — only used for the pre-rename ~/.claude-learn lookup below.

~/.claude-adapt-rules/
  rules/ledger.json          rule identity, evidence, adoption dates, violations
  rules/global/PROPOSED.md   awaiting your approval
  rules/repos/<project>/     auto-written per-project rules
  rules/candidates/          distilled candidate batches
  data/corpus, queue, archive, reports

Run the CLI from anywhere without installing the package:

bin/claude-adapt-rules.sh status      # or bin\claude-adapt-rules.ps1 status on Windows

Upgrading from claude-learn

The state root is derived from the tool's own name, so the rename would otherwise orphan everything you had: the ledger, adopted globals, repo rule files, the archive of cited transcripts, and the consumed-event markers. A fresh ingest would then restart ids at R-0001 against a CLAUDE.md that already cited them.

Nothing to run. On first use, ~/.claude-learn/ is adopted automatically — copied, never moved, so the old root survives as a rollback and is marked as read. Files the new root already has are left alone, and the two queues are merged by (session, record) because neither side is authoritative: one holds everything captured before the rename, the other everything after. The old <!-- claude-learn --> block in ~/.claude/CLAUDE.md is replaced rather than appended to, so pre-rename rules stop being loaded twice.

Using the skill without installing the plugin

Plugin skills only load once the plugin is installed, and .claude/skills/ only loads inside its own project. To get /claude-adapt-rules in every project from a plain checkout, link it into your user skills directory — no admin needed on Windows, and it stays a single source of truth:

New-Item -ItemType Junction -Path "$env:USERPROFILE\.claude\skills\claude-adapt-rules" -Target "C:\path\to\claude-adapt-rules\skills\claude-adapt-rules"
ln -s /path/to/claude-adapt-rules/skills/claude-adapt-rules ~/.claude/skills/claude-adapt-rules

Skills are enumerated at session start, so it appears in the next session. Remove the link if you later install the plugin, or the same skill loads twice.

How rules reach a session

Distilling rules is worthless if nothing reads them. Both tiers have a delivery path:

TierDelivery
repoa SessionStart hook injects the current project's rules as session context — nothing is written into your other repositories, so teammates see no diff and a reworded rule takes effect next session
globaladopt --apply-global splices a marked block into ~/.claude/CLAUDE.md after you name the ids

Sessions started inside a git worktree receive the parent repository's rules. Projects with no rules get nothing — the hook prints nothing and exits 0.

claude-adapt-rules doctor      # is any of this actually working?

doctor exists because hooks fail open: a broken capture is silent by design. It reports captured/pending events, recent hook failures, archive coverage, transcripts approaching the cleanup age, and how many rules the current project would receive.

Pipeline

transcripts → signals → extract → /claude-adapt-rules → ledger → render
 (parse)      (score)   (bundles)  (the only             (identity,  (two tiers)
                                    model step)           rot tracking)

Everything except /claude-adapt-rules is deterministic and stdlib-only — the SessionEnd hook imports this package on every session exit, so a dependency here would break unrelated work in other projects.

python -m claude_adapt_rules.cli status                 # parse and report, write nothing
python -m claude_adapt_rules.cli extract                # corpus + per-project bundles
python -m claude_adapt_rules.cli ingest ~/.claude-adapt-rules/rules/candidates/<date>.json
python -m claude_adapt_rules.cli verify                 # every quote must be verbatim
python -m claude_adapt_rules.cli adopt R-0001 --apply-global
python -m claude_adapt_rules.cli rot                    # which rules aren't working
python -m claude_adapt_rules.cli guards                 # which ones a hook could enforce
python -m claude_adapt_rules.cli workflows              # work repeated by hand
python -m claude_adapt_rules.cli constraints            # rules for what you write next
python -m claude_adapt_rules.cli doctor                 # is any of it reaching a session
python -m claude_adapt_rules.cli register --system      # OS-level weekly schedule (see below)

Then in Claude Code: /claude-adapt-rules reads the bundles and writes the candidates file.

What makes an event worth reading

Lexical signals (don't, wrong, always, nicht, warum hast du) are cheap and noisy. Structural signals are weighted higher because they're harder to fake:

  • repeated_instruction — you said the same thing twice in one session (strongest)
  • user_denied — you rejected a tool call outright
  • interrupted — you hit escape

after_edit is deliberately worth zero. 407 of 778 prompts follow an edit; scoring it ranks "commit and push" alongside a real correction. It only adds a point when the words are corrective too.

Work you repeat by hand

Every signal above is corrective. That structurally cannot find work you drive by hand five times without ever complaining — there is no signal to find. Idea credited to Task-Observer.

claude-adapt-rules workflows

Counts recurring tool sequences across sessions, excluding any span the user corrected, denied or interrupted — those are already covered above, and proposing a skill for work that went wrong is backwards.

A candidate must reach beyond the ordinary edit loop. Measured here before that filter existed, the top result was Read → Edit → Bash, seven times across four projects: that is what coding is, and it buried everything distinctive. A sequence made only of Read/Edit/Write/Bash/Grep/Glob is therefore dropped. With the filter, the same corpus reports two candidates instead of eleven.

Output is candidates, not conclusions — a repeated shape of work is worth looking at, not proof a skill is warranted.

Rules for the next thing you write

Rules reach sessions. Nothing reached authoring, so a new skill or agent file gets written without the constraints its author already established, and the same correction gets learned again through the new artifact. Idea credited to Task-Observer.

claude-adapt-rules constraints                 # current project + globals
claude-adapt-rules constraints --project app --out CONSTRAINTS.md

Adopted rules only, globals first, as a block to paste into whatever you are writing. It prints rather than editing your files: writing into someone's skill file uninvited is the behaviour this project exists to correct.

Scope comes from generality, not from frequency

Every rule is classified applies: universal | project. Universal means it would hold in a repo you have never seen — "never commit code that does not build" qualifies after being said once, which no evidence-count gate would ever promote. Project means it is tied to this codebase's tooling, architecture or vocabulary.

A universal claim is vetoed when the rule text names a path, filename, identifier or known project name, and the reason is reported:

? R-0027 is universal but names a path (releases/canvas-debug.log) — scoped to repo

project is never widened. Unclassified rules fall back to the old count gate (≥2 projects or ≥3 sessions), which is only a proxy for generality.

claude-adapt-rules reclassify R-0024=universal R-0026=project --apply

Promotion out of repo scope drops the rule back to proposed: repo rules auto-apply, global rules never do.

Worktree slugs (...-app--claude-worktrees-brave-newton-a1b2c3) collapse onto their repository — otherwise one repo's quirk looks like cross-project evidence and gets promoted.

Why it compounds

ingest treats a candidate matching an already-adopted rule as a violation, not a new rule. That is the signal worth having: the rule existed and did not work. Reword it, hoist it earlier, or convert it to a hook.

rot then splits adopted rules into still being broken (escalate) and quiet for 30 days (stop paying its token cost).

Three delivery tiers, not two

A rule used to be always-on or nothing, so ~/.claude/CLAUDE.md could only grow — guards were the sole exit, and they only take the subset a regex can decide.

claude-adapt-rules defer R-0008 --trigger "building or restyling a user interface"
claude-adapt-rules defer R-0008 --promote        # bring it back

The rule stays adopted and leaves the always-on block. What remains there is one line naming the triggers and pointing at rules/global/ON-DEMAND.md, which holds the rules in full, grouped by trigger.

The trigger is mandatory. A deferred rule with no stated condition is one nothing will ever read, which is strictly worse than retiring it — it still looks live in the ledger.

delivery is orthogonal to scope: scope says where a rule applies, delivery says how it arrives. Both a global and a repo rule can be always-on or on-demand.

Guards: rules the machine can check

A rule in CLAUDE.md is a suggestion the model weighs against everything else in context. For the subset a regex can decide — --no-verify, a banned import, a forbidden command — weighing is the wrong mechanism: a PreToolUse hook simply refuses the call.

Those rules are already flagged enforceable. A guard is the check itself:

claude-adapt-rules guards        # enforced by a hook, vs still only prose
claude-adapt-rules guards --set R-0024 --tool Bash \
  --pattern=--no-verify --message='run the build and suite instead'

Use --pattern= with an =, not a space — the patterns worth guarding are usually flags, and argparse would read a leading - as an option.

Enable it by adding the hook. It is opt-in and scoped to one tool on purpose: the script costs ~209 ms per call, and gating every Read and Grep to catch one flag is a bad trade.

"PreToolUse": [
  { "matcher": "Bash",
    "hooks": [ { "type": "command", "command": "python",
      "args": ["/path/to/claude-adapt-rules/bin/guard.py"], "timeout": 10 } ] }
]

Guards are read from the ledger at hook time rather than compiled into a generated script. A generated script goes stale the moment a rule is reworded or retired, and a stale gate that refuses a legitimate command is worse than no gate.

Only adopted rules enforce, and each tool declares which input field a guard reads, so a pattern cannot fire on an unrelated path in the same call. guard.py sits in front of every matched tool call, so it fails open and logs — the one place here where that is correct. The loud path is --set, which refuses to store a pattern it cannot compile.

Known limitation: a guard matches command text and cannot tell running a flag from mentioning it, so a command quoting the guarded string is refused. Inherent to the mechanism. guards --clear R-0024 disarms without touching settings.

The escalation ladder this completes: prose → still violated after adoption (rot) → reword or hoist it earlier → if a regex can decide it, make it a guard and drop it from CLAUDE.md. That last step is the only thing that stops the always-on block growing forever.

Evidence integrity

verify re-checks every quote against the decoded transcript text and fails on paraphrase, changed capitalisation, or attribution to the wrong session. Raw JSONL escapes inner quotes, so grepping file bytes gives false failures — hence decoded comparison.

The first real run produced two bad quotes out of 48, both mine, both caught this way.

Transcripts expire — archive or the audit trail rots

Claude Code deletes transcripts after cleanupPeriodDays (default 30). Measured 2026-07-26: the oldest file in ~/.claude/projects was exactly 30 days old, and four evidence quotes from rules distilled that same morning already cited deleted sessions.

python -m claude_adapt_rules.cli archive        # cited sessions only
python -m claude_adapt_rules.cli archive --all  # every session, before it ages out

The weekly job archives after every extract. verify reads the archive too, and reports a vanished transcript as expired rather than as bad evidence — decay must not look like fabrication.

To keep raw history longer, raise retention in ~/.claude/settings.json:

{ "cleanupPeriodDays": 365 }

Automation

  • SessionStart hook (bin/inject.py) — puts the current project's rules into context.
  • SessionEnd hook (bin/capture.py, or bin/capture.sh / bin/capture.ps1 as shims) appends each finished session's candidates to ~/.claude-adapt-rules/data/queue/queue.jsonl. No model, no network, always exits 0.

Both are declared by the plugin and exec python directly, so neither needs a shell — on Windows that removes the Git Bash dependency. Where only python3 exists, change the command in .claude-plugin/plugin.json.

  • PreToolUse hook (bin/guard.py) — refuses a call that breaks a guarded rule. Not declared by the plugin: a hook that blocks tool calls is opt-in, and you add it yourself. See Guards.
  • Weekly refreshhooks/weekly_extract.ps1 (Windows Task Scheduler) or hooks/weekly_extract.sh (cron). Both re-extract full history and then archive.
schtasks /Create /TN "claude-adapt-rules weekly" /SC WEEKLY /D MON /ST 09:00 /TR "powershell -NoProfile -ExecutionPolicy Bypass -File C:\path\to\claude-adapt-rules\hooks\weekly_extract.ps1"
0 9 * * 1 /path/to/claude-adapt-rules/hooks/weekly_extract.sh

The distil step stays manual: it needs a model. Run /claude-adapt-rules when the bundles look worth reading.

Cadence

Capture is automatic and distillation is not, so the queue grows quietly until someone remembers it. Pick a rhythm and let doctor police it — it reports the age of the oldest undistilled event, not just the count, and flags anything left longer than --stale-days (default 7):

  pending distillation ....... 6
  oldest pending ............. 2d (2026-08-04)

Weekly suits a single developer. Task-Observer's author runs reviews three mornings a week and reports it scales better as the library grows.

Scheduling the weekly pass

extractarchive → distil → ingest is easy to run once and then forget. Two ways to put it on a schedule instead of relying on memory:

Native routine (recommended)

/claude-adapt-rules:register

Registers a Claude Code scheduled routine (claude-adapt-rules-weekly) that re-extracts, archives, distils the pending slice, checks candidates, and ingests — unattended, once a week. It only runs while Claude Code is open (or catches up at the next launch), so click Run now once after registering to pre-approve the tool calls it would otherwise have to ask for unattended. It never runs adopt --apply-global: global rules always wait for you.

/claude-adapt-rules:unregister

removes it. Both commands are idempotent — running register twice reports the existing routine instead of creating a second one.

OS-level fallback

No Claude Code desktop app, or want the job to run even while it is closed?

claude-adapt-rules register --system                              # Mondays 09:00, distil on
claude-adapt-rules register --system --day TUE --time 14:00 --no-distil
claude-adapt-rules unregister --system

Installs a Windows Task Scheduler entry or a crontab line (marker-commented, so re-registering replaces it cleanly without touching anything else in your crontab) that resolves this plugin's current install path from installed_plugins.json on every run — a later plugin update cannot break it. This path only runs extract + archive, plus a headless distil-and-gate step unless --no-distil is passed; it still never ingests or adopts anything unattended.

Without --system, register/unregister cannot reach the native scheduler — this CLI has no way to call it — and just point back at the slash commands above.

Layout

src/claude_adapt_rules/
  transcripts.py    locate and parse Claude Code session transcripts (stdlib only)
  signals.py        score human prompts by correction signal, lexical + structural
  extract.py        turn transcripts into a scored corpus + per-project evidence bundles
  candidates.py     validate a distilled candidates file before it nears the ledger
  classify.py       judge a rule universal vs project; veto misclassified paths
  ledger.py         rule identity, provenance, scope promotion, rot tracking
  render.py         render the ledger into the two delivery tiers (repo + global)
  verify.py         check every rule's evidence is verbatim in its transcript
  archive.py        copy cited transcripts out of the 30-day cleanup path
  inject.py         put a project's rules into context at SessionStart
  guards.py         PreToolUse enforcement for the subset a regex can decide
  authoring.py      put adopted rules in front of the next skill/agent file written
  workflows.py      find work repeated by hand that no correction signal reveals
  harness.py        inventory which skills, agents and tools ever actually fire
  impact.py         correction-rate density before vs after a rule's adoption
  migrate.py        one-time adoption of state written under the tool's earlier name
  doctor.py         health-check logic: is capture -> distil -> delivery working?
  paths.py          where this tool's state and Claude Code's own config each live
  jsonl.py          tolerant JSONL line reading, shared by every transcript consumer
  atomic.py         whole-file writes that can't leave a half-written file behind
  cli/              the CLI: argparse wiring plus the pipeline/ledger/report command modules
skills/claude-adapt-rules/    the model-facing distillation instructions
bin/                          hook entry points (capture, inject, guard) + CLI wrappers
hooks/                        weekly extract for Task Scheduler (.ps1) and cron (.sh)
.claude-plugin/               Claude Code plugin + marketplace manifests
.codex-plugin/                Codex manifest; AGENTS.md is its context file
gemini-extension.json         Antigravity / Gemini manifest; GEMINI.md is its context file
tests/                        suite run with `python -m pytest`

No rules ship with the plugin — the ledger starts empty and everything you distil stays in ~/.claude-adapt-rules/.

Closing the loop

Capture was automatic; everything after it was not. The weekly job can now draft candidates unattended:

CLAUDE_ADAPT_RULES_DISTIL=1   # opt-in, needs the `claude` CLI on PATH

It drafts and stops. Ingest stays manual: a bad rule reaches every session of every project, and global text waits for a human yes. What replaces the human reader is a mechanical gate:

claude-adapt-rules check-candidates <file> --write-accepted <file>

Every quote must be verbatim in the session it cites, or the candidate is dropped. This is stricter than verify: an expired transcript rejects a candidate rather than passing, because there is no rule to protect yet and admitting an uncheckable quote is how unverifiable rules are born.

Is any of it working?

claude-adapt-rules impact      # correction rate before vs after adoption, per project
claude-adapt-rules rot         # broken-and-caught vs broken-and-shipped
claude-adapt-rules harness     # which skills, agents and tools ever fire

impact is built to refuse to conclude. It reports per project rather than pooling, always prints the sample size, and says "no conclusion" under 50 prompts a side. On the real corpus every window currently refuses — including a 100% → 20% swing on n=2, which is precisely the reading the refusal exists to prevent. A number that looks like a verdict gets read as one.

rot now leads with guard fires, because a block is the one signal available without a distillation run: it separates broken and caught from broken and shipped.

Where this is going

docs/vision.md states the goal — every correction costs the user once — the principles each defect in this repo paid for, and the five places the system still falls short. docs/roadmap.md turns those into a checklist with acceptance tests.

The shortest summary of the gap: capture is automatic, everything after it is not.

Credits

Three features here came from reading Task-Observer — One Skill to Rule Them All by Eoghan Henn (rebelytics), licensed CC BY 4.0:

borrowedwhere it lives here
coverage gaps as a first-class category, not just correctionsworkflows
cross-cutting principles applied when artifacts are writtenconstraints
a standing review cadence rather than ad-hoc distillationdoctor --stale-days

The two projects solve adjacent problems and are worth reading together. Task-Observer improves skills — the procedures — by observing live in every session, and works anywhere Claude runs, including web and mobile. This project distils rules — the constraints — by mining stored transcripts after the fact, which buys a verbatim evidence chain and rule identity at the cost of needing transcripts on disk. Only the ideas above were taken; no text or code was copied.

Author

Built by Patrick Eisenschmidthttps://github.com/Patrick-DE/claude-adapt-rules.

License

MIT. See LICENSE.

Rendered live from Patrick-DE/claude-adapt-rules's GitHub README — not stored, always reflects the source repo.

1 Plugin

NameDescriptionCategorySource
claude-adapt-rulesMine past sessions for corrections; distil them into per-repo and global rules with verifiable evidence.productivity./

0 Comments

Login required
Log in to post a comment or update on this repo.

No comments yet — be the first to share an update.