Problem
exact_dot_claude/rules/ holds 47 rules. Ten carry globs: frontmatter and load lazily; the other 37 load into every session of every project:
37 always-loaded, 102,868c ≈ 25,700 est. tokens
That is ~13% of a 200k context window spent before the first prompt is read. Measured 2026-08-05 alongside a /doctor pass; total memory-file load across all scopes was ~58.6k est. tokens (~29%).
The sibling pass on the portfolio rules has already landed as
laurigates/repos-claude-config#21 — six procedural rules promoted to skills, 36,320c → 3,296c, ~8.2k est. tokens saved. This issue is the same triage applied here.
Constraint: these files are chezmoi-managed. All edits go to ~/.local/share/chezmoi/exact_dot_claude/rules/, never the live ~/.claude/rules/ targets.
The triage
A rule is push — resident, shaping behaviour unasked. A skill is pull — loads once recognised as relevant. Converting the wrong rule breaks it: if a hazard rule only loads after I suspect the hazard, I already knew enough not to need it. So sort by whether the trigger is recognisable in advance, into three destinations. This is offload-to-deterministic-substrate.md's own routing table applied to the rules directory itself.
Bucket 1 — mechanically enforceable → PreToolUse hook
Deterministic, fires every time, costs zero context. Highest value where it applies, but it applies to less than it first appears.
| Rule |
Chars |
Hook shape |
repo-deletion-safety |
2,576 |
PreToolUse/Bash on rm -rf <path>: if <path> is a git repo, run git remote -v; block when empty (local-only checkout) |
git-hazards (trap #2 only) |
— |
PreToolUse/Bash on git switch -c/git checkout -b: warn when git log origin/main..main is non-empty |
Honest scope note: the rest of git-hazards and most of pr-merge-hazards are diagnostic (how to recognise a symptom), not checkable preconditions. They belong in bucket 2 or 3, not here. pr-merge-hazards #1 already has just -g branch-audit as its deterministic substrate.
Bucket 2 — procedural, recognisable trigger → skill + pointer stub
Each fires at a moment nameable in advance. Same treatment as the portfolio six: body moves verbatim into SKILL.md, rule becomes a stub, description written as a trigger rather than a topic.
| Rule |
Chars |
Trigger |
prefer-diy-over-heavy-dependency |
7,079 |
about to depend on a single-purpose tool / GUI app / bundled binary |
pr-merge-hazards |
7,607 |
merging a PR, deleting a branch, force-pushing — keep the gate inline (see below) |
verify-machine-facts-before-publishing |
4,654 |
writing org docs from scutil/route/ifconfig/local config readings |
read-issue-thread-before-contributing |
3,916 |
scoping a contribution from a GitHub issue |
documentation-authoring |
3,093 |
writing or restructuring docs |
taskwarrior-tracking |
2,978 |
end-of-session capture (already sibling to task-bulk-ops) |
scaffold-fix-backport |
2,960 |
fixing something a generator/template produced |
verify-upstream-before-patching |
2,710 |
patching vendored / tarball-installed / forked code |
repo-deletion-safety |
2,576 |
if not hooked per bucket 1 |
| Subtotal |
37,573 |
→ ~3,600c of stubs |
≈ 8,500 est. tokens/session.
pr-merge-hazards follows the worktree-stale-base-merge exception from repos-claude-config#21: the load-bearing checks stay inline in the stub (the gh pr list --head authority order for squash-merge detection; UNSTABLE ≠ BLOCKED), and the exposition — stacked-PR auto-close recovery, the push-by-SHA protocol, the negated-closing-keyword trap — moves to the skill.
Bucket 3 — genuinely ambient → stays resident
These have no recognisable trigger, or the trigger is the failure they prevent. Do not convert:
communication, decision-defaults, copy-paste-commands — shape every response
front-load-executive-decisions, plan-mode, agent-and-tool-selection, security — apply at every task boundary or dispatch
diagnose-at-the-failure-point, never-fabricate-test-identifiers — the trap is invisible; by the time you'd think to load the skill, you have already fabricated the identifier or believed the sentinel
offload-to-deterministic-substrate, code-quality — meta-principles that inform the others
skill-and-agent-catalog-check — load-bearing for this entire plan. Promoting rules to skills only pays off if the catalog is actually consulted at task boundaries. This one must stay resident, and is the reason the pattern works at all.
Bucket 4 — one rule worth splitting rather than moving
tool-use-patterns (10,778c) is the single largest always-loaded rule and is a grab-bag of two different kinds of content:
- Ambient (keep): Read-before-Edit, re-Read after formatter, the
rg -r fabrication trap, "a rejected flag looks like no results", parallel-batch cancellation, Bash denials are terminal
- Procedural / lookup (promote): the WebFetch failure→fallback table, the agent fan-out burst-limit guidance, the usage-limit mid-run recovery protocol
Splitting should roughly halve it — ≈ 1,300 est. tokens — without making any always-on trap lazy.
Expected total
| Bucket |
Saving |
| 2 — promote to skills |
~8,500 tok |
4 — split tool-use-patterns |
~1,300 tok |
| 1 — hooks |
~650 tok + deterministic enforcement |
| Total |
~10,400 est. tokens/session |
Roughly 40% off the user-rules load, in every project, on top of the ~8.2k already landed for the portfolio rules.
Suggested execution
- Bucket 2 first — highest saving, lowest risk, and the pattern is already proven by repos-claude-config#21 and by the five stubs that already exist here (
huggingface-downloads, taskwarrior-bulk-operations, multi-model-delegation, tui-keybinding-terminal-interception, bulk-syntax-sweep-classify-matches).
- Bucket 4 split as a separate commit — it is an editorial judgement per section, not a mechanical move.
- Bucket 1 last — two hooks, each needing its own test.
Move bodies mechanically (script, not retyping) and verify byte-identical against the pre-change source, as repos-claude-config#21 did. That is what makes "the skill cannot have drifted from the rule it replaces" a checkable claim rather than a hope.
Where do the new skills live? Options: exact_dot_claude/skills/ (user scope, mirrors the current rules location), or promote into the relevant laurigates/claude-plugins plugin (matches how hf-downloads and task-bulk-ops were handled). The existing stubs all point at plugin skills, so the plugin route is the established precedent — worth deciding before starting bucket 2.
Problem
exact_dot_claude/rules/holds 47 rules. Ten carryglobs:frontmatter and load lazily; the other 37 load into every session of every project:That is ~13% of a 200k context window spent before the first prompt is read. Measured 2026-08-05 alongside a
/doctorpass; total memory-file load across all scopes was ~58.6k est. tokens (~29%).The sibling pass on the portfolio rules has already landed as
laurigates/repos-claude-config#21 — six procedural rules promoted to skills, 36,320c → 3,296c, ~8.2k est. tokens saved. This issue is the same triage applied here.
Constraint: these files are chezmoi-managed. All edits go to
~/.local/share/chezmoi/exact_dot_claude/rules/, never the live~/.claude/rules/targets.The triage
A rule is push — resident, shaping behaviour unasked. A skill is pull — loads once recognised as relevant. Converting the wrong rule breaks it: if a hazard rule only loads after I suspect the hazard, I already knew enough not to need it. So sort by whether the trigger is recognisable in advance, into three destinations. This is
offload-to-deterministic-substrate.md's own routing table applied to the rules directory itself.Bucket 1 — mechanically enforceable → PreToolUse hook
Deterministic, fires every time, costs zero context. Highest value where it applies, but it applies to less than it first appears.
repo-deletion-safetyrm -rf <path>: if<path>is a git repo, rungit remote -v; block when empty (local-only checkout)git-hazards(trap #2 only)git switch -c/git checkout -b: warn whengit log origin/main..mainis non-emptyHonest scope note: the rest of
git-hazardsand most ofpr-merge-hazardsare diagnostic (how to recognise a symptom), not checkable preconditions. They belong in bucket 2 or 3, not here.pr-merge-hazards#1 already hasjust -g branch-auditas its deterministic substrate.Bucket 2 — procedural, recognisable trigger → skill + pointer stub
Each fires at a moment nameable in advance. Same treatment as the portfolio six: body moves verbatim into
SKILL.md, rule becomes a stub, description written as a trigger rather than a topic.prefer-diy-over-heavy-dependencypr-merge-hazardsverify-machine-facts-before-publishingscutil/route/ifconfig/local config readingsread-issue-thread-before-contributingdocumentation-authoringtaskwarrior-trackingtask-bulk-ops)scaffold-fix-backportverify-upstream-before-patchingrepo-deletion-safety≈ 8,500 est. tokens/session.
pr-merge-hazardsfollows theworktree-stale-base-mergeexception from repos-claude-config#21: the load-bearing checks stay inline in the stub (thegh pr list --headauthority order for squash-merge detection;UNSTABLE≠BLOCKED), and the exposition — stacked-PR auto-close recovery, the push-by-SHA protocol, the negated-closing-keyword trap — moves to the skill.Bucket 3 — genuinely ambient → stays resident
These have no recognisable trigger, or the trigger is the failure they prevent. Do not convert:
communication,decision-defaults,copy-paste-commands— shape every responsefront-load-executive-decisions,plan-mode,agent-and-tool-selection,security— apply at every task boundary or dispatchdiagnose-at-the-failure-point,never-fabricate-test-identifiers— the trap is invisible; by the time you'd think to load the skill, you have already fabricated the identifier or believed the sentineloffload-to-deterministic-substrate,code-quality— meta-principles that inform the othersskill-and-agent-catalog-check— load-bearing for this entire plan. Promoting rules to skills only pays off if the catalog is actually consulted at task boundaries. This one must stay resident, and is the reason the pattern works at all.Bucket 4 — one rule worth splitting rather than moving
tool-use-patterns(10,778c) is the single largest always-loaded rule and is a grab-bag of two different kinds of content:rg -rfabrication trap, "a rejected flag looks like no results", parallel-batch cancellation, Bash denials are terminalSplitting should roughly halve it — ≈ 1,300 est. tokens — without making any always-on trap lazy.
Expected total
tool-use-patternsRoughly 40% off the user-rules load, in every project, on top of the ~8.2k already landed for the portfolio rules.
Suggested execution
huggingface-downloads,taskwarrior-bulk-operations,multi-model-delegation,tui-keybinding-terminal-interception,bulk-syntax-sweep-classify-matches).Move bodies mechanically (script, not retyping) and verify byte-identical against the pre-change source, as repos-claude-config#21 did. That is what makes "the skill cannot have drifted from the rule it replaces" a checkable claim rather than a hope.
Where do the new skills live? Options:
exact_dot_claude/skills/(user scope, mirrors the current rules location), or promote into the relevantlaurigates/claude-pluginsplugin (matches howhf-downloadsandtask-bulk-opswere handled). The existing stubs all point at plugin skills, so the plugin route is the established precedent — worth deciding before starting bucket 2.