{
  "markdown": "# claude-adapt-rules\n\nMine your own Claude Code sessions for the moments you corrected the agent, distil\nthose into rules, and land them where a future session will actually read them.\n\nTwo tiers, because the cost of a rule is not the same everywhere:\n\n| Tier | Target | Policy |\n| --- | --- | --- |\n| **repo** | `~/.claude-adapt-rules/rules/repos/<project>/rules.md` | auto-written; blast radius is one project, and it's a git diff away from gone |\n| **global** | `~/.claude-adapt-rules/rules/global/PROPOSED.md` → `~/.claude/CLAUDE.md` | proposed only, you approve; every line is loaded in every session of every project |\n\n## Where the human text actually is\n\nThe interesting finding from building this. In a Claude Code desktop install,\n`~/.claude/projects/<slug>/<session>.jsonl` records your prompts as:\n\n```json\n{\"type\": \"queue-operation\", \"operation\": \"enqueue\", \"content\": \"<what you typed>\"}\n```\n\nThe `type: \"user\"` records are almost entirely tool results and machine continuations.\nOn this machine: 17k `type:user` records, of which **64** survive noise filtering — and\nall 64 are machine-generated (`Continue from where you left off.`, compaction summaries,\nskill payloads). The 1325 real prompts are all in `queue-operation` records.\n\nA miner that reads `type: \"user\"` learns nothing about the user. Both channels are read\nhere, queue preferred, overlaps de-duplicated.\n\n## Install\n\nAs a Claude Code plugin:\n\n```bash\nclaude plugin marketplace add https://github.com/Patrick-DE/claude-adapt-rules.git\n```\n\nOr from a local checkout, which is what you want while iterating on the tool itself:\n\n```bash\nclaude plugin marketplace add /path/to/claude-adapt-rules\n```\n\nThen enable `claude-adapt-rules`. That registers two hooks — `SessionStart` (inject this\nproject's rules) and `SessionEnd` (capture corrections) — plus the `/claude-adapt-rules` skill.\nA third, opt-in `PreToolUse` hook is described under [Guards](#guards-rules-the-machine-can-check).\nRequires Python ≥ 3.12 on PATH as `python`.\n\n| Platform | What loads | Notes |\n| --- | --- | --- |\n| Claude Code (Windows) | skill + both hooks | primary target; hooks exec `python` directly, no shell needed |\n| Claude Code (macOS/Linux) | skill + both hooks | change `command` to `python3` in `.claude-plugin/plugin.json` if `python` is absent |\n| Antigravity / Gemini | skill + `GEMINI.md` context | no session hooks — run `extract` on a schedule and read rules from `~/.claude-adapt-rules/` |\n| Codex | skill + `AGENTS.md` context | same |\n\n**State lives in `~/.claude-adapt-rules/`** (`CLAUDE_ADAPT_RULES_HOME` overrides), never inside the\nplugin directory — installed plugins live under a versioned cache path, so an update\nwould orphan your ledger, queue and archive. `CLAUDE_ADAPT_RULES_USER_HOME` separately overrides\nthe OS home directory itself — only used for the pre-rename `~/.claude-learn` lookup below.\n\n```\n~/.claude-adapt-rules/\n  rules/ledger.json          rule identity, evidence, adoption dates, violations\n  rules/global/PROPOSED.md   awaiting your approval\n  rules/repos/<project>/     auto-written per-project rules\n  rules/candidates/          distilled candidate batches\n  data/corpus, queue, archive, reports\n```\n\nRun the CLI from anywhere without installing the package:\n\n```bash\nbin/claude-adapt-rules.sh status      # or bin\\claude-adapt-rules.ps1 status on Windows\n```\n\n### Upgrading from `claude-learn`\n\nThe state root is derived from the tool's own name, so the rename would otherwise\norphan everything you had: the ledger, adopted globals, repo rule files, the archive of\ncited transcripts, and the consumed-event markers. A fresh `ingest` would then restart ids\nat `R-0001` against a `CLAUDE.md` that already cited them.\n\nNothing to run. On first use, `~/.claude-learn/` is adopted automatically — copied, never\nmoved, so the old root survives as a rollback and is marked as read. Files the new root\nalready has are left alone, and the two queues are merged by `(session, record)` because\nneither side is authoritative: one holds everything captured before the rename, the other\neverything after. The old `<!-- claude-learn -->` block in `~/.claude/CLAUDE.md` is\nreplaced rather than appended to, so pre-rename rules stop being loaded twice.\n\n### Using the skill without installing the plugin\n\nPlugin skills only load once the plugin is installed, and `.claude/skills/` only loads\ninside its own project. To get `/claude-adapt-rules` in every project from a plain checkout, link\nit into your user skills directory — no admin needed on Windows, and it stays a single\nsource of truth:\n\n```bash\nNew-Item -ItemType Junction -Path \"$env:USERPROFILE\\.claude\\skills\\claude-adapt-rules\" -Target \"C:\\path\\to\\claude-adapt-rules\\skills\\claude-adapt-rules\"\n```\n\n```bash\nln -s /path/to/claude-adapt-rules/skills/claude-adapt-rules ~/.claude/skills/claude-adapt-rules\n```\n\nSkills are enumerated at session start, so it appears in the next session. Remove the link\nif you later install the plugin, or the same skill loads twice.\n\n## How rules reach a session\n\nDistilling rules is worthless if nothing reads them. Both tiers have a delivery path:\n\n| Tier | Delivery |\n| --- | --- |\n| **repo** | a `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 |\n| **global** | `adopt --apply-global` splices a marked block into `~/.claude/CLAUDE.md` after you name the ids |\n\nSessions started inside a git worktree receive the parent repository's rules. Projects with\nno rules get nothing — the hook prints nothing and exits 0.\n\n```bash\nclaude-adapt-rules doctor      # is any of this actually working?\n```\n\n`doctor` exists because hooks fail open: a broken capture is silent by design. It reports\ncaptured/pending events, recent hook failures, archive coverage, transcripts approaching\nthe cleanup age, and how many rules the current project would receive.\n\n## Pipeline\n\n```\ntranscripts → signals → extract → /claude-adapt-rules → ledger → render\n (parse)      (score)   (bundles)  (the only             (identity,  (two tiers)\n                                    model step)           rot tracking)\n```\n\nEverything except `/claude-adapt-rules` is deterministic and **stdlib-only** — the SessionEnd\nhook imports this package on every session exit, so a dependency here would break\nunrelated work in other projects.\n\n```bash\npython -m claude_adapt_rules.cli status                 # parse and report, write nothing\npython -m claude_adapt_rules.cli extract                # corpus + per-project bundles\npython -m claude_adapt_rules.cli ingest ~/.claude-adapt-rules/rules/candidates/<date>.json\npython -m claude_adapt_rules.cli verify                 # every quote must be verbatim\npython -m claude_adapt_rules.cli adopt R-0001 --apply-global\npython -m claude_adapt_rules.cli rot                    # which rules aren't working\npython -m claude_adapt_rules.cli guards                 # which ones a hook could enforce\npython -m claude_adapt_rules.cli workflows              # work repeated by hand\npython -m claude_adapt_rules.cli constraints            # rules for what you write next\npython -m claude_adapt_rules.cli doctor                 # is any of it reaching a session\npython -m claude_adapt_rules.cli register --system      # OS-level weekly schedule (see below)\n```\n\nThen in Claude Code: `/claude-adapt-rules` reads the bundles and writes the candidates file.\n\n## What makes an event worth reading\n\nLexical signals (`don't`, `wrong`, `always`, `nicht`, `warum hast du`) are cheap and noisy.\nStructural signals are weighted higher because they're harder to fake:\n\n- **repeated_instruction** — you said the same thing twice in one session (strongest)\n- **user_denied** — you rejected a tool call outright\n- **interrupted** — you hit escape\n\n`after_edit` is deliberately worth **zero**. 407 of 778 prompts follow an edit; scoring it\nranks \"commit and push\" alongside a real correction. It only adds a point when the words\nare corrective too.\n\n## Work you repeat by hand\n\nEvery signal above is corrective. That structurally cannot find work you drive by hand\nfive times without ever complaining — there is no signal to find. Idea credited to\n[Task-Observer](#credits).\n\n```bash\nclaude-adapt-rules workflows\n```\n\nCounts recurring tool sequences across sessions, excluding any span the user corrected,\ndenied or interrupted — those are already covered above, and proposing a skill for work\nthat went wrong is backwards.\n\nA candidate must reach beyond the ordinary edit loop. Measured here before that filter\nexisted, the top result was `Read → Edit → Bash`, seven times across four projects: that\nis what coding *is*, and it buried everything distinctive. A sequence made only of\n`Read`/`Edit`/`Write`/`Bash`/`Grep`/`Glob` is therefore dropped. With the filter, the same\ncorpus reports two candidates instead of eleven.\n\nOutput is candidates, not conclusions — a repeated shape of work is worth looking at, not\nproof a skill is warranted.\n\n## Rules for the next thing you write\n\nRules reach *sessions*. Nothing reached *authoring*, so a new skill or agent file gets\nwritten without the constraints its author already established, and the same correction\ngets learned again through the new artifact. Idea credited to [Task-Observer](#credits).\n\n```bash\nclaude-adapt-rules constraints                 # current project + globals\nclaude-adapt-rules constraints --project app --out CONSTRAINTS.md\n```\n\nAdopted rules only, globals first, as a block to paste into whatever you are writing.\nIt prints rather than editing your files: writing into someone's skill file uninvited is\nthe behaviour this project exists to correct.\n\n## Scope comes from generality, not from frequency\n\nEvery rule is classified `applies: universal | project`. Universal means it would hold in\na repo you have never seen — \"never commit code that does not build\" qualifies after being\nsaid **once**, which no evidence-count gate would ever promote. Project means it is tied\nto this codebase's tooling, architecture or vocabulary.\n\nA `universal` claim is vetoed when the rule text names a path, filename, identifier or\nknown project name, and the reason is reported:\n\n```\n? R-0027 is universal but names a path (releases/canvas-debug.log) — scoped to repo\n```\n\n`project` is never widened. Unclassified rules fall back to the old count gate\n(≥2 projects or ≥3 sessions), which is only a proxy for generality.\n\n```bash\nclaude-adapt-rules reclassify R-0024=universal R-0026=project --apply\n```\n\nPromotion out of repo scope drops the rule back to *proposed*: repo rules auto-apply,\nglobal rules never do.\n\nWorktree slugs (`...-app--claude-worktrees-brave-newton-a1b2c3`) collapse onto their\nrepository — otherwise one repo's quirk looks like cross-project evidence and gets promoted.\n\n## Why it compounds\n\n`ingest` treats a candidate matching an already-adopted rule as a **violation**, not a new\nrule. That is the signal worth having: the rule existed and did not work. Reword it, hoist\nit earlier, or convert it to a hook.\n\n`rot` then splits adopted rules into *still being broken* (escalate) and *quiet for 30 days*\n(stop paying its token cost).\n\n## Three delivery tiers, not two\n\nA rule used to be always-on or nothing, so `~/.claude/CLAUDE.md` could only grow —\nguards were the sole exit, and they only take the subset a regex can decide.\n\n```bash\nclaude-adapt-rules defer R-0008 --trigger \"building or restyling a user interface\"\nclaude-adapt-rules defer R-0008 --promote        # bring it back\n```\n\nThe rule stays adopted and leaves the always-on block. What remains there is one line\nnaming the triggers and pointing at `rules/global/ON-DEMAND.md`, which holds the rules in\nfull, grouped by trigger.\n\nThe trigger is mandatory. A deferred rule with no stated condition is one nothing will\never read, which is strictly worse than retiring it — it still looks live in the ledger.\n\n`delivery` is orthogonal to `scope`: scope says *where* a rule applies, delivery says\n*how* it arrives. Both a global and a repo rule can be always-on or on-demand.\n\n## Guards: rules the machine can check\n\nA rule in `CLAUDE.md` is a suggestion the model weighs against everything else in context.\nFor the subset a regex can decide — `--no-verify`, a banned import, a forbidden command —\nweighing is the wrong mechanism: a `PreToolUse` hook simply refuses the call.\n\nThose rules are already flagged `enforceable`. A **guard** is the check itself:\n\n```bash\nclaude-adapt-rules guards        # enforced by a hook, vs still only prose\nclaude-adapt-rules guards --set R-0024 --tool Bash \\\n  --pattern=--no-verify --message='run the build and suite instead'\n```\n\nUse `--pattern=` with an `=`, not a space — the patterns worth guarding are usually flags,\nand argparse would read a leading `-` as an option.\n\nEnable it by adding the hook. It is opt-in and **scoped to one tool on purpose**: the\nscript costs ~209 ms per call, and gating every `Read` and `Grep` to catch one flag is a\nbad trade.\n\n```json\n\"PreToolUse\": [\n  { \"matcher\": \"Bash\",\n    \"hooks\": [ { \"type\": \"command\", \"command\": \"python\",\n      \"args\": [\"/path/to/claude-adapt-rules/bin/guard.py\"], \"timeout\": 10 } ] }\n]\n```\n\nGuards are read from the ledger at hook time rather than compiled into a generated script.\nA generated script goes stale the moment a rule is reworded or retired, and a stale gate\nthat refuses a legitimate command is worse than no gate.\n\nOnly **adopted** rules enforce, and each tool declares which input field a guard reads, so\na pattern cannot fire on an unrelated path in the same call. `guard.py` sits in front of\nevery matched tool call, so it fails open and logs — the one place here where that is\ncorrect. The loud path is `--set`, which refuses to store a pattern it cannot compile.\n\nKnown limitation: a guard matches command *text* and cannot tell running a flag from\nmentioning it, so a command quoting the guarded string is refused. Inherent to the\nmechanism. `guards --clear R-0024` disarms without touching settings.\n\nThe escalation ladder this completes: prose → still violated after adoption (`rot`) →\nreword or hoist it earlier → if a regex can decide it, make it a guard and drop it from\n`CLAUDE.md`. That last step is the only thing that stops the always-on block growing\nforever.\n\n## Evidence integrity\n\n`verify` re-checks every quote against the decoded transcript text and fails on\nparaphrase, changed capitalisation, or attribution to the wrong session. Raw JSONL escapes\ninner quotes, so grepping file bytes gives false failures — hence decoded comparison.\n\nThe first real run produced two bad quotes out of 48, both mine, both caught this way.\n\n## Transcripts expire — archive or the audit trail rots\n\nClaude Code deletes transcripts after `cleanupPeriodDays` (**default 30**). Measured\n2026-07-26: the oldest file in `~/.claude/projects` was exactly 30 days old, and four\nevidence quotes from rules distilled that same morning already cited deleted sessions.\n\n```bash\npython -m claude_adapt_rules.cli archive        # cited sessions only\npython -m claude_adapt_rules.cli archive --all  # every session, before it ages out\n```\n\nThe weekly job archives after every extract. `verify` reads the archive too, and reports a\nvanished transcript as **expired** rather than as bad evidence — decay must not look like\nfabrication.\n\nTo keep raw history longer, raise retention in `~/.claude/settings.json`:\n\n```json\n{ \"cleanupPeriodDays\": 365 }\n```\n\n## Automation\n\n- **SessionStart hook** (`bin/inject.py`) — puts the current project's rules into context.\n- **SessionEnd hook** (`bin/capture.py`, or `bin/capture.sh` / `bin/capture.ps1` as shims)\n  appends each finished session's candidates to\n  `~/.claude-adapt-rules/data/queue/queue.jsonl`. No model, no network, always exits 0.\n\nBoth are declared by the plugin and exec `python` directly, so neither needs a shell — on\nWindows that removes the Git Bash dependency. Where only `python3` exists, change the\n`command` in `.claude-plugin/plugin.json`.\n- **PreToolUse hook** (`bin/guard.py`) — refuses a call that breaks a guarded rule. Not\n  declared by the plugin: a hook that blocks tool calls is opt-in, and you add it yourself.\n  See [Guards](#guards-rules-the-machine-can-check).\n- **Weekly refresh** — `hooks/weekly_extract.ps1` (Windows Task Scheduler) or\n  `hooks/weekly_extract.sh` (cron). Both re-extract full history and then archive.\n\n```bash\nschtasks /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\"\n```\n\n```bash\n0 9 * * 1 /path/to/claude-adapt-rules/hooks/weekly_extract.sh\n```\n\nThe distil step stays manual: it needs a model. Run `/claude-adapt-rules` when the bundles look\nworth reading.\n\n### Cadence\n\nCapture is automatic and distillation is not, so the queue grows quietly until someone\nremembers it. Pick a rhythm and let `doctor` police it — it reports the *age* of the\noldest undistilled event, not just the count, and flags anything left longer than\n`--stale-days` (default 7):\n\n```\n  pending distillation ....... 6\n  oldest pending ............. 2d (2026-08-04)\n```\n\nWeekly suits a single developer. Task-Observer's author runs reviews three mornings a week\nand reports it scales better as the library grows.\n\n## Scheduling the weekly pass\n\n`extract` → `archive` → distil → `ingest` is easy to run once and then forget. Two ways to\nput it on a schedule instead of relying on memory:\n\n### Native routine (recommended)\n\n```\n/claude-adapt-rules:register\n```\n\nRegisters a Claude Code scheduled routine (`claude-adapt-rules-weekly`) that re-extracts,\narchives, distils the pending slice, checks candidates, and ingests — unattended, once a\nweek. It only runs while Claude Code is open (or catches up at the next launch), so click\n**Run now** once after registering to pre-approve the tool calls it would otherwise have to\nask for unattended. It never runs `adopt --apply-global`: global rules always wait for you.\n\n```\n/claude-adapt-rules:unregister\n```\n\nremoves it. Both commands are idempotent — running `register` twice reports the existing\nroutine instead of creating a second one.\n\n### OS-level fallback\n\nNo Claude Code desktop app, or want the job to run even while it is closed?\n\n```bash\nclaude-adapt-rules register --system                              # Mondays 09:00, distil on\nclaude-adapt-rules register --system --day TUE --time 14:00 --no-distil\nclaude-adapt-rules unregister --system\n```\n\nInstalls a Windows Task Scheduler entry or a crontab line (marker-commented, so\nre-registering replaces it cleanly without touching anything else in your crontab) that\nresolves this plugin's *current* install path from `installed_plugins.json` on every run —\na later plugin update cannot break it. This path only runs `extract` + `archive`, plus a\nheadless distil-and-gate step unless `--no-distil` is passed; it still never ingests or\nadopts anything unattended.\n\nWithout `--system`, `register`/`unregister` cannot reach the native scheduler — this CLI has\nno way to call it — and just point back at the slash commands above.\n\n## Layout\n\n```\nsrc/claude_adapt_rules/\n  transcripts.py    locate and parse Claude Code session transcripts (stdlib only)\n  signals.py        score human prompts by correction signal, lexical + structural\n  extract.py        turn transcripts into a scored corpus + per-project evidence bundles\n  candidates.py     validate a distilled candidates file before it nears the ledger\n  classify.py       judge a rule universal vs project; veto misclassified paths\n  ledger.py         rule identity, provenance, scope promotion, rot tracking\n  render.py         render the ledger into the two delivery tiers (repo + global)\n  verify.py         check every rule's evidence is verbatim in its transcript\n  archive.py        copy cited transcripts out of the 30-day cleanup path\n  inject.py         put a project's rules into context at SessionStart\n  guards.py         PreToolUse enforcement for the subset a regex can decide\n  authoring.py      put adopted rules in front of the next skill/agent file written\n  workflows.py      find work repeated by hand that no correction signal reveals\n  harness.py        inventory which skills, agents and tools ever actually fire\n  impact.py         correction-rate density before vs after a rule's adoption\n  migrate.py        one-time adoption of state written under the tool's earlier name\n  doctor.py         health-check logic: is capture -> distil -> delivery working?\n  paths.py          where this tool's state and Claude Code's own config each live\n  jsonl.py          tolerant JSONL line reading, shared by every transcript consumer\n  atomic.py         whole-file writes that can't leave a half-written file behind\n  cli/              the CLI: argparse wiring plus the pipeline/ledger/report command modules\nskills/claude-adapt-rules/    the model-facing distillation instructions\nbin/                          hook entry points (capture, inject, guard) + CLI wrappers\nhooks/                        weekly extract for Task Scheduler (.ps1) and cron (.sh)\n.claude-plugin/               Claude Code plugin + marketplace manifests\n.codex-plugin/                Codex manifest; AGENTS.md is its context file\ngemini-extension.json         Antigravity / Gemini manifest; GEMINI.md is its context file\ntests/                        suite run with `python -m pytest`\n```\n\nNo rules ship with the plugin — the ledger starts empty and everything you distil stays\nin `~/.claude-adapt-rules/`.\n\n## Closing the loop\n\nCapture was automatic; everything after it was not. The weekly job can now draft\ncandidates unattended:\n\n```bash\nCLAUDE_ADAPT_RULES_DISTIL=1   # opt-in, needs the `claude` CLI on PATH\n```\n\nIt **drafts and stops.** Ingest stays manual: a bad rule reaches every session of every\nproject, and global text waits for a human yes. What replaces the human *reader* is a\nmechanical gate:\n\n```bash\nclaude-adapt-rules check-candidates <file> --write-accepted <file>\n```\n\nEvery quote must be verbatim in the session it cites, or the candidate is dropped. This\nis stricter than `verify`: an expired transcript rejects a candidate rather than passing,\nbecause there is no rule to protect yet and admitting an uncheckable quote is how\nunverifiable rules are born.\n\n## Is any of it working?\n\n```bash\nclaude-adapt-rules impact      # correction rate before vs after adoption, per project\nclaude-adapt-rules rot         # broken-and-caught vs broken-and-shipped\nclaude-adapt-rules harness     # which skills, agents and tools ever fire\n```\n\n`impact` is built to **refuse to conclude**. It reports per project rather than pooling,\nalways prints the sample size, and says \"no conclusion\" under 50 prompts a side. On the\nreal corpus every window currently refuses — including a 100% → 20% swing on n=2, which\nis precisely the reading the refusal exists to prevent. A number that looks like a verdict\ngets read as one.\n\n`rot` now leads with guard fires, because a block is the one signal available without a\ndistillation run: it separates *broken and caught* from *broken and shipped*.\n\n## Where this is going\n\n[`docs/vision.md`](docs/vision.md) states the goal — every correction costs the user\nonce — the principles each defect in this repo paid for, and the five places the\nsystem still falls short. [`docs/roadmap.md`](docs/roadmap.md) turns those into a\nchecklist with acceptance tests.\n\nThe shortest summary of the gap: capture is automatic, everything after it is not.\n\n## Credits\n\nThree features here came from reading\n**[Task-Observer — One Skill to Rule Them All](https://github.com/rebelytics/one-skill-to-rule-them-all)**\nby **Eoghan Henn** ([rebelytics](https://rebelytics.com)), licensed\n**CC BY 4.0**:\n\n| borrowed | where it lives here |\n| --- | --- |\n| coverage gaps as a first-class category, not just corrections | `workflows` |\n| cross-cutting principles applied when artifacts are *written* | `constraints` |\n| a standing review cadence rather than ad-hoc distillation | `doctor --stale-days` |\n\nThe two projects solve adjacent problems and are worth reading together. Task-Observer\nimproves **skills** — the procedures — by observing live in every session, and works\nanywhere Claude runs, including web and mobile. This project distils **rules** — the\nconstraints — by mining stored transcripts after the fact, which buys a verbatim evidence\nchain and rule identity at the cost of needing transcripts on disk. Only the ideas above\nwere taken; no text or code was copied.\n\n## Author\n\nBuilt by **Patrick Eisenschmidt** — <https://github.com/Patrick-DE/claude-adapt-rules>.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n",
  "bytes": 24746,
  "sha": "3c13bc6a9ed940c940ad2b264b0b43ac671519dde18a77ca0e6920aa519c9abb",
  "repo_slug": "patrick-de/claude-adapt-rules",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_patrick_de_claude_adapt_rules_b13a71b6/readme"
}