Load BEFORE making, committing, or reviewing ANY change in the dotfiles repo. Covers change classification (tracked asset vs installer logic vs machine-local template vs vendored-untracked vs project .claude config), the full gate chain (guard hooks, bin/dotfiles-tests, public-safety scan, CI), commit/branch/PR rules, and the non-negotiables with their incident history. Trigger phrases -- "commit this", "open a PR", "why did my commit get blocked", "can I edit settings.json.tmpl", "can I edit these rules files", "hook blocked me", "bypass the hook". NOT for diagnosing why something is broken (dotfiles-debugging-playbook), running the tests as evidence (dotfiles-validation-and-qa), or historical why-decisions (dotfiles-failure-archaeology).
Install
npx skillscat add taloncjones/dotfiles/dotfiles-change-control Install via the SkillsCat registry.
Dotfiles Change Control
How changes are classified, gated, tested, and reviewed in this repo, and which
rules are non-negotiable. This repo is PUBLIC (github.com/taloncjones/dotfiles)
and its assets are symlinked live into ~/.claude, ~/.claude-work, and~/.codex on every machine that runs it -- a bad commit here ships instantly to
every session on every machine. Read this before your first edit, not after
your first blocked commit.
Front-loaded warnings
- [WARNING] PUBLIC repo. No secrets, no employer names/URLs, no machine-local
paths (/Users/<name>-- writing the real one out would itself trip the
scan). The public-safety suite (git/hooks/public-safety.test.sh)
fails the build on violations; do not rely on it as your only check. - [WARNING] Plans and specs are private working artifacts. Keep
docs/superpowers/,docs/plans/,docs/specs/,claude/contracts/, and.planning/ignored and untracked; approval to create one is not approval
to publish it. - [WARNING] Never hand-edit vendored content: the retired-ECC language dirs
underclaude/rules/(everything exceptpersonal/) and native plugin payloads.
Change their lifecycle or owned adapters.codex/AGENTS.mdis now fully
repo-owned; the stale copied upstream instruction block is retired. - [WARNING]
claude/settings.json.tmplchanges reach existing machines only at
their nextupdate/link run (reconcile merge); until then live settings are
stale -- see the protocol below. - [WARNING] Hook bypass variables exist for emergencies only (broken hook,
never a policy disagreement). Every bypass must be mentioned in the PR
description. - [WARNING] Everything runs through
update->install/install.shrepeatedly.
Any installer change must be idempotent: safe to run twice, safe on a machine
that already has the state.
Step 0: classify the change
Every file in this repo belongs to exactly one class. The class determines what
your change needs.
| Class | Examples | What a change requires |
|---|---|---|
| Tracked asset (symlinked live) | zsh/*.zsh, claude/global-instructions.md, claude/skills/, claude/hooks/*.py, git/hooks/commit-msg, bin/* |
Normal commit + tests. Takes effect on machines at next symlink resolution (usually immediately -- symlinks point into the repo checkout). Hook changes: update claude/hooks/claude-hooks.test.sh drift checks. |
| Installer logic | bootstrap.sh, bootstrap-cloud.sh, install/**, zsh/functions.zsh install helpers |
Must stay idempotent (update re-runs install.sh). Update install/install.test.sh if flow shape changes. Platform-gate macOS/Linux paths. |
| Machine-local template | claude/settings.json.tmpl, git/work/.gitconfig-work.tmpl, ssh/configs/agent.toml |
Seed-once: template edits reach only FRESH machines automatically. Follow the settings.json.tmpl protocol below. |
| Private planning artifact | docs/superpowers/, docs/plans/, docs/specs/, claude/contracts/, .planning/ |
Keep local-only and ignored. Never force-add or publish these files in this public repository; planning_artifact_guard.py refuses the add or commit. |
| Vendored-untracked | claude/rules/{common,cpp,python,rust,typescript,web}/ (retired-ECC leftovers) |
NEVER edit by hand; delete leftovers (ecc-update/ecc-sync-rules are retired). Untracked by design (claude/rules/.gitignore whitelists only personal/). |
Project .claude/ config |
.claude/settings.json, .claude/hooks/session-start.sh, .claude/skills/ |
Tracked, load-bearing for CLOUD sessions (plugin declaration is the pre-launch install path -- see claude-code-platform-reference). Changes here alter what every fresh cloud container gets on session 1. |
Vendored-but-TRACKED special case: git/hooks/pre-commit and pre-push are
copies adapted from the retired ECC that ARE tracked (and NOTICE-listed) --
do not hand-patch them; fix them here directly (see "Special protocol:
vendored content").
Quick classification checks:
# Run from the repo checkout (canonical machine path ~/Git/personal/dotfiles;
# cloud containers use the platform checkout -- the repo is location-independent)
git ls-files --error-unmatch <path> # tracked? (fails = untracked/vendored)
readlink ~/.claude/CLAUDE.md # confirm the live symlink targetThe gate chain (what fires between edit and merge)
In order, for a change made inside a Claude Code session:
Claude guard hooks (session-time, registered in
claude/settings.json.tmpl, active via each machine'ssettings.json):Hook ( claude/hooks/)Event / matcher Blocks commit_guard.pyPreToolUse Bash git commitcommands whose message mentions Claude/Anthropic/Copilot/ChatGPT, AI co-author lines, AI-tool attribution footers, emojisno_ai_attribution_bash.pyPreToolUse Bash AI attribution in ANY outbound command payload ( gh pr comment,curl --data) -- closes the non-commit gapplanning_artifact_guard.pyPreToolUse Bash git add/git committhat would recorddocs/specs/,docs/plans/,docs/superpowers/,.planning/, orclaude/contracts/; overrideDOTFILES_ALLOW_PLAN_ARTIFACTS=1block_secrets.pyPreToolUse Read|Edit|Write Access to .env, key files (.pem,id_ed25519, ...), credential dirs. Segment/basename matching, not substring (substring matching blocked the hook's own file -- fixed 52f807d)protect_claude_md.pyPreToolUse Edit|Write Nothing -- WARNS on global CLAUDE.md edits emoji_guard.pyPostToolUse Edit|Write Emojis written into files (exit 2 = block) no_ai_comments.pyPostToolUse Edit|Write AI-tool-attribution comments in code (markdown exempt) format_files.pyPostToolUse Edit|Write Nothing -- runs prettier Hooks exit 2 to block and fail OPEN on internal exceptions (a crashed hook
never blocks work -- it also never protects it, so do not assume silence
means clean). This table covers only the BLOCKING gates relevant to a
change; the full registration table (includingcache_jira_url.pyandaccount_guard.py) lives in claude-code-platform-reference -- that skill
is the single home for it.Git hooks (
core.hooksPath = ~/.config/git/hooks-> symlink togit/hooks/; fire on EVERY commit, any tool, any editor -- LINKED MACHINES
ONLY: cloud containers never setcore.hooksPath(bootstrap-cloud.sh
does not link~/.gitconfig), so none of these hooks fire there and the
Claude guard hooks plus CI are the only gates):Hook Fires Blocks Bypass (EMERGENCY ONLY) commit-msgcommit attribution terms, AI co-author lines, emojis in the message DOTFILES_SKIP_COMMIT_MSG_GUARD=1pre-commit(derived from retired ECC)commit staged high-signal secrets ECC_SKIP_PRECOMMIT=1orECC_SKIP_GIT_HOOKS=1pre-push(derived from retired ECC)push failing lint/typecheck/test/build in Node projects (verification flow -- it does NOT scan secrets) ECC_SKIP_PREPUSH=1orECC_SKIP_GIT_HOOKS=1post-checkoutcheckout/worktree add nothing -- hydrates .todos/.planninginto worktreesn/a Bypass discipline: use only when the hook itself is broken (e.g.
rg
missing false-positive), never to land content the hook correctly rejects.
State the bypass and the reason in the PR description. Notecommit-msg
silently SKIPS its checks ifrgis not installed -- absence of a block is
not proof of a clean message.Tests, before every push -- the single runner:
bash bin/dotfiles-tests # from the repo checkout: all suites, one verdict bash bin/dotfiles-tests --list # enumerate suitesNon-zero exit = do not push. The public-safety suite
(git/hooks/public-safety.test.sh) is part of this run: no trackedtodo.md, no tracked planning artifacts, no/Users/<name>paths, and no high-confidence secret patterns in tracked
content.CI (
.github/workflows/tests.yml): runs on push tomainand on every
PR. zsh syntax gate, bash syntax gate, pythonpy_compileon all hooks,
thenbash bin/dotfiles-tests. CI green is necessary, not sufficient --
syntax gates and drift checks do not execute installers end-to-end.PR review: target
main, squash merge, description + test plan. Do not
push directly tomainfor anything non-trivial.
Commit, branch, PR format (from claude/global-instructions.md, hook-enforced)
- Commit:
<scope>: <summary>-- imperative mood, under 75 chars, scope =
affected area. Examples:zsh: Add geoip lookup function,install: Fix Brewfile path. - Branch:
<user>/<topic-slug>; with a Jira ticket<user>/<PROJ-###>/<topic-slug>. - PR: target
main, squash merge, include description and test plan; Jira link
at the bottom if one exists. - Author: the human's git identity only. No Claude author, no co-author, no
attribution anywhere in message or body.
Special protocol: claude/settings.json.tmpl
Why it is special: ~/.claude/settings.json and ~/.claude-work/settings.json
are machine-local because the settings reconcile and, historically, the now
retired Superpowers and ECC plugin installers WRITE into them at runtime.
The installer therefore seeds the file from the template only if it is absent
(seed_machine_local_file in install/common/claude-links.sh, called bylink_claude_config_dir) -- re-copying on every update would clobber the
installer-written plugin keys. Consequence: a template edit lands on fresh
machines only.
When you change the template:
- Edit
claude/settings.json.tmpl(e.g. register a new hook). - Run
bash bin/dotfiles-tests--claude/hooks/claude-hooks.test.sh
drift-checks livesettings.jsonagainst the template on each machine, andinstall/claude-links.test.shproves the reconcile merge semantics. - Existing machines pick the change up at their next
update(or anylink.sh/dotfiles-repairrun):reconcile_claude_settings_file
(install/common/claude-links.sh) reasserts template-owned keys per config
dir while plugin-installer keys survive (closed 2026-07-02; previously a
manual hand-merge). A machine that never runsupdatestays stale -- the
drift check in step 2 is what surfaces it. [WARNING] This works for
template-owned keys only:enabledPlugins/extraKnownMarketplacesmerge
with LIVE state winning, so flipping a plugin tofalsein the template
reaches fresh seeds but never disables it on an existing machine -- use the
claude.ai account toggle plus a one-time per-machine flip for that (worked
example:feature-dev@claude-code-plugins, see
dotfiles-cloud-first-campaign). - Cloud uses the same shared merge:
bootstrap-cloud.shreconcile_claude_settingsdelegates toreconcile_claude_settings_file
(template supplies defaults, live plugin keys win) -- history: 910f2bc.
Special protocol: vendored content
- ECC language rules: vendoring RETIRED (2026-07-02) and ECC itself is
retired (2026-09) --ecc-sync-rules,ECC_VENDOR_LANGS, and the ECC
plugin/marketplace itself are all gone; there is no longer an upstream
clone to point at. Claude Code natively auto-loads every.mdunder~/.claude/rules(paths:frontmatter scopes to matching files; none =
every session), so language dirs still underclaude/rules/are NOT inert
-- they auto-load and should be deleted (claude/rules/.gitignorekeeps
them uncommitted; the notice function that used to flag them,_ecc_legacy_rules_notice, was removed with the rest of the retired ECC). Your own
rules go inclaude/rules/personal/-- the only whitelisted, tracked
subdir. History: tracked vendored rules caused 39-file churn (untracked at
e140ab3); vendoring was retired after consumer enumeration found no
runtime reader of~/.claude/rules-- a finding later corrected (it ran in
fresh cloud clones where the untracked vendored dirs did not exist; the
harness loader is real). codex/AGENTS.mdis repo-owned global policy. Native plugins own upstream
guidance; do not reintroduce a copied instruction block or upstream global
sync. See.claude/skills/dotfiles-architecture-contract/references/install-layout.md, "Codex plugin integration".- The git hooks
git/hooks/pre-commitandpre-pushare copies that are
TRACKED in git (adapted from the retired ECC, listed inNOTICE): do not
patch them against an upstream that no longer exists -- fix them here
directly.
Non-negotiables, with rationale and incident
| Rule | Enforced by | Why (incident) |
|---|---|---|
| No AI attribution; commits look human-authored | commit_guard.py, no_ai_attribution_bash.py, no_ai_comments.py, commit-msg |
Cloud containers seed Claude <noreply@anthropic.com> as git identity; 2304015 reattributes cloud commits to the personal identity. Attribution that slips through is permanent public history. |
| No secrets, ever | block_secrets.py, ECC pre-commit secret scan, public-safety suite, CI |
PUBLIC repo. A leaked key is compromised the moment it is pushed. Related supply-chain lesson: the GSD npm package was compromised via token rug-pull (retired at 9ad4dc8) -- never reinstall it. |
| Machine-local files never tracked | seed-once design, public-safety "no tracked todo.md" check | Tracking installer-written files causes write-through corruption: the abandoned Codex mirror wrote through symlinks into the repo and orphaned core.hooksPath. Machine state and repo state must not share a writable file. |
| Planning artifacts never published | .gitignore, public-safety tracked-plan assertion |
Plans and specs contain working context and operational detail; they stay local even when their implementation is public. Approval to create an artifact never implies approval to publish it. |
| No emojis anywhere | emoji_guard.py, commit_guard.py, commit-msg |
House style (~/.claude/CLAUDE.md); use [OK] [X] [WARNING] [INFO]. Hook exit 2 blocks the write. |
| Idempotent installers | convention + install/install.test.sh |
update re-runs install.sh on every invocation; a non-idempotent step breaks every machine on its next update. |
| Pre-launch placement for cloud plugins | .claude/settings.json declaration |
Plugins load at CLI launch; a post-launch install is dark until next session (root cause c1c4500; final design 8d4507f). Do not "simplify" the declaration away into the SessionStart hook. |
Pre-push checklist
- Change classified (table above) and its class requirements met?
-
bash bin/dotfiles-testsgreen locally (all suites)? - Installer edits idempotent and platform-gated?
- No secrets, no employer strings, no
/Users/<name>paths in the diff? - No files under
docs/superpowers/,docs/plans/,docs/specs/,claude/contracts/, or.planning/tracked or staged? - Commit message
<scope>: <summary>, <75 chars, imperative, no attribution, no emojis? - Template edits: manual-merge plan for existing machines stated in the PR?
- Vendored files untouched (or re-vendored, not hand-edited)?
- Any hook bypass used? If yes: broken-hook justification in the PR description.
- PR targets
main, squash merge, description + test plan?
When NOT to use this skill
- Diagnosing WHY something broke (symlink dead, hook not firing, plugin
missing) ->dotfiles-debugging-playbook. - Test-suite inventory, what counts as evidence, adding a test ->
dotfiles-validation-and-qa. - Why a past decision was made, settled reverts, dead ends with hashes ->
dotfiles-failure-archaeology. - Design invariants and load-bearing architecture ->
dotfiles-architecture-contract. - Claude Code platform mechanics (config dirs, plugin/marketplace internals,
cloud container lifecycle) ->claude-code-platform-reference. - Public-repo posture in depth (what may never land, licensing, supply chain)
->dotfiles-external-positioning. - Day-to-day ops (update lifecycle, plugin repair, account routing) ->
dotfiles-run-and-operate.
Provenance and maintenance
Facts verified against the repo on 2026-07-02. Volatile items and how to
re-verify:
All commands run from the repo checkout root (the repo is
location-independent; canonical machine path ~/Git/personal/dotfiles, cloud
containers use the platform checkout):
| Fact | Re-verify |
|---|---|
| 10 test suites in the runner | bash bin/dotfiles-tests --list |
| Guard-hook registrations and matchers | grep -n -A4 'matcher' claude/settings.json.tmpl |
| Hook inventory | ls claude/hooks/ git/hooks/ |
DOTFILES_SKIP_COMMIT_MSG_GUARD still the commit-msg bypass |
grep -n 'SKIP' git/hooks/commit-msg |
| Bypass vars (named ECC_SKIP_*, retired ECC's naming) | grep -n 'ECC_SKIP' git/hooks/pre-commit git/hooks/pre-push |
| hooksPath indirection | git config --get core.hooksPath (expect ~/.config/git/hooks; EMPTY in cloud containers -- git hooks do not fire there) |
| Seed-once settings behavior | grep -n 'seed_machine_local_file' install/common/claude-links.sh |
| Cloud settings reconcile | grep -n 'reconcile_claude_settings' bootstrap-cloud.sh |
Rules whitelist (only personal/ tracked) |
cat claude/rules/.gitignore |
| CI trigger + steps | cat .github/workflows/tests.yml |
| Cited commits (2304015, 52f807d, e140ab3, 910f2bc, c1c4500, 8d4507f, 9ad4dc8, 465ee17) | git show -s --format='%h %s' <hash> |
Known-open as of 2026-07-02: all three original entries closed the same day --
machine-path plugin installs verify against installed_plugins.json
(_claude_ensure_plugin + zsh/functions.test.sh); machine-side auto-merge
for settings.json.tmpl (reconcile_claude_settings_file +install/claude-links.test.sh); pre-commit/pre-push (derived from the
retired ECC) behavioral suites and a post-checkout runtime suite
(git/hooks/{pre-commit,pre-push,post-checkout-runtime}.test.sh).