diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-27 13:13:09 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-27 13:15:14 -0500 |
| commit | 7ea1d7b1402eb68a13479f8073b84819c1d59ec8 (patch) | |
| tree | aef0ae30760f04db3d99bc2305b46f52d3c295a5 /scripts/sync-language-bundle.sh | |
| parent | 0adcb1afcf64a4638f110a3ab0c616c48d86ba4f (diff) | |
| download | rulesets-7ea1d7b1402eb68a13479f8073b84819c1d59ec8.tar.gz rulesets-7ea1d7b1402eb68a13479f8073b84819c1d59ec8.zip | |
fix(rules): stop shipping generic rules into every project
Generic rules install once at ~/.claude/rules/ and load in every session on the machine. The language bundles copied all twenty into each project as well, so work and .emacs.d loaded them twice. Project rules outrank user-level ones, which makes it worse than waste. A stale project copy overrides the fresh global rule until the next startup heals it, and that is what happened to work's interaction.md this morning for the hours between the commit and its next session.
I stopped install-lang copying them. sync-language-bundle now sweeps the copies earlier installs left, but only where the global rule exists to take over. A machine that hasn't run make install keeps the only copy it has. I swept 20 files each from work and .emacs.d, leaving their language rules and work's publishing overlay untouched.
I updated three existing tests that encoded the old contract. The generic-rule drift test is the interesting one: I made a drifted copy get swept rather than repaired, which is the stronger fix, because that copy outranked the global rule for as long as it existed.
I also gitignored the live session anchor. This repo tracks .ai/, so the anchor read as untracked for a whole session. git-worktree-gate reported rulesets sync-blocked, and every other project skipped its rulesets pull until wrap. The anchor is ephemeral and gets archived under a different name, so nothing needs it tracked.
Diffstat (limited to 'scripts/sync-language-bundle.sh')
| -rwxr-xr-x | scripts/sync-language-bundle.sh | 23 |
1 files changed, 19 insertions, 4 deletions
diff --git a/scripts/sync-language-bundle.sh b/scripts/sync-language-bundle.sh index 45f8259..fe922af 100755 --- a/scripts/sync-language-bundle.sh +++ b/scripts/sync-language-bundle.sh @@ -105,7 +105,7 @@ inbox_drop() { # shared generic rules, its hooks/githooks, and surfaces settings.json. process_bundle() { local kind="$1" src="${2%/}" - local name rules rf f rel manual_before + local name rules rf f rel manual_before base name="$(basename "$src")" rules="$src/claude/rules" [ -d "$rules" ] || return 0 @@ -126,12 +126,27 @@ process_bundle() { HEADER_DONE=0 manual_before=$MANUAL - # AUTO-FIX: language bundles carry the shared generic rules; team overlays - # carry only their own rule(s). + # SWEEP: generic rules are installed once at ~/.claude/rules/ by `make + # install` and load in every session. Language bundles used to copy them into + # each project too, which made Claude Code load them twice — and project rules + # take priority over user-level ones, so a stale project copy silently + # overrode the fresh global rule until the next startup healed it. The bundle + # now owns only its own language rules, and sweeps the duplicates it shipped + # before. + # + # The sweep only fires when the global rule is actually present to take over. + # On a machine mid-bootstrap, or one where `make install` has not run, the + # project copy is the only copy and removing it would leave no rule at all. if [ "$kind" = language ]; then for f in "$GENERIC_RULES"/*.md; do [ -f "$f" ] || continue - fix "$f" "$PROJECT/.claude/rules/$(basename "$f")" + base="$(basename "$f")" + [ -f "$PROJECT/.claude/rules/$base" ] || continue + [ -f "$HOME/.claude/rules/$base" ] || continue + rm -f "$PROJECT/.claude/rules/$base" + ensure_header + OUT+=" swept .claude/rules/$base (duplicate of ~/.claude/rules/)"$'\n' + FIXED=$((FIXED + 1)) done fi for f in "$rules"/*.md; do |
