| name | dotfiles |
| description | Manage and develop dotfiles — installation, updates, adding configs, troubleshooting symlinks, and repo development guidelines. Use when asked about dotfiles, system config, shell setup, or when making changes to the dotfiles repo. Trigger on: dot sync, dot health, dot validate, managed files, drift, private overlay, Brewfile, build artifacts, symlink map, dot fix, dot clean. |
| user_invocable | true |
| argument-hint | [sync|health|validate|clean|private|setup|edit|status|fix|links] |
Dotfiles
How the System Works
Symlink-based: ~/.dotfiles/symlink_map.txt defines source → destination mappings.
CLI: dot <command> (symlinked to ~/.local/bin/dot):
dot sync - Apply dotfiles state (auto-detects first-time vs update)
dot pull - Pull latest, then sync
dot links - Symlinks only (no brew, no pull)
dot health / dot fix - Diagnostics / auto-repair
dot clean - System cleanup (--dry-run, --force)
dot private - Manage private overlay (init, status, sync, push, edit)
dot status - Show mode, git state
dot validate - Run validation checks (shellcheck, syntax, symlinks, skills)
dot edit - Open dotfiles in editor
Multi-mode: ~/.dotfiles/.machine contains aggressive or conservative
configs/base/ applies in all modes
configs/aggressive/ only in aggressive mode
- Conservative mode: minimal changes, stays local (work machines)
Private overlay (~/.dotfiles.private/):
- Separate git repo for machine-specific private configs
symlink_map.txt, Brewfile, skills/ — private symlinks, packages, Claude skills
dot private init to create, dot private sync to re-apply
Managed files (managed_map.txt):
- Some files are generated by merging a public base with a private overlay (e.g.
~/.claude/settings.json)
- Format:
base[+overlay]:destination — merged with jq deep recursive merge (objects merge recursively, arrays concatenate and deduplicate)
- Drift detection:
dot health --check managed compares compiled output to on-disk files; dot sync blocks if drift is found (override with --force)
Managed File Drift
Managed files drift when external tools modify the destination file after compilation. Common causes:
- Claude auto-adds permission patterns to
settings.json (e.g. Bash(bundle *) when Bash(bundle*) already exists)
/plugin installs add entries to installed_plugins.json and enabledPlugins in settings.json
- Plugin version bumps update
installed_plugins.json and known_marketplaces.json timestamps
Detecting drift: Run dot health --check managed. It computes the expected merge output and diffs against on-disk files, normalizing JSON key/array ordering.
Resolving drift — determine which source file to update:
| What drifted | Where to backfill |
|---|
| Permission patterns (MCP tools, Bash rules) | Private overlay (claude-settings.json) — these are machine-specific |
enabledPlugins entries | Private overlay (claude-settings.json) — plugin selection is per-machine |
| User-scoped plugin installs/version bumps | Private overlay (claude-plugins-installed.json) |
| Project-scoped plugin installs | Private overlay (claude-plugins-installed.json) — auto-installed per-project but lost on sync if not captured |
| New marketplace registrations | Private overlay (claude-plugins-marketplaces.json) |
| Marketplace timestamp bumps | Private overlay (claude-plugins-marketplaces.json) |
| Core settings (hooks, mcpServers, memory) | Base (configs/base/claude/settings.base.json) — shared across machines |
Workflow: Check drift → backfill to source → verify with dot health --check managed → commit both repos
Repository Structure
~/.dotfiles/
├── cmd/
│ ├── dendrik/ # Convention linter (Go CLI)
│ ├── dot/ # dot CLI + engine
│ │ ├── dot # entrypoint (shell dispatcher)
│ │ ├── sync.sh # dot sync/pull/links
│ │ ├── cleanup.sh # dot clean
│ │ ├── health.sh # dot health/fix
│ │ ├── lib/ # sourced shell functions
│ │ └── scripts/ # one-offs + helpers
│ ├── folio/ # Knowledge work CLI (Go)
│ └── jf/ # Jira Forest CLI (Go)
├── configs/
│ ├── base/ # always-applied configs
│ │ ├── <app>/ # One directory per app
│ │ └── claude/.claude/ # Claude Code config + skills
│ ├── aggressive/ # Aggressive mode only (Brewfile + shell.managed)
│ └── templates/ # Private overlay scaffold
├── symlink_map.txt # Defines all symlinks
└── Brewfile.shared # Core tools
Brewfiles
| File | Purpose | When Installed |
|---|
~/.dotfiles/Brewfile.shared | Core dev tools | All machines |
~/.dotfiles/configs/aggressive/Brewfile | Extra apps | Aggressive mode only |
~/.dotfiles.private/Brewfile | Private packages | When private overlay exists |
Development Guidelines
Adding a New Config
- Create directory:
configs/base/<app>/ mirroring target structure
- Add to
symlink_map.txt: configs/base/<app>/config:$HOME/.config/<app>/config
- If needs brew package: Add to
Brewfile.shared or configs/aggressive/Brewfile
- Test:
dot links
Modifying Scripts
Patterns to follow:
- Use
--dry-run flag for previewing changes
- Use colored output:
ok(), warn(), err(), info()
- Detect state before acting (idempotent)
- Support both interactive and non-interactive modes
When adding new checks to health.sh:
- Add check function:
check_<name>()
- Add to
SPECIFIC_CHECK case statement
- If manual action needed, add to
check_setup_status()
- If walkthrough needed, add step to
run_interactive_setup()
Adding/Modifying Skills
Location: configs/base/claude/.claude/skills/<name>/SKILL.md
Required frontmatter:
---
name: skill-name
description: When to use this skill (for Claude's context)
---
Optional frontmatter:
user_invocable: true - User-invocable slash command
user_invocable: false - Claude-only (background knowledge)
allowed-tools: Read, Grep - Restrict tools
Keep skills focused: One topic per skill, detailed docs in supporting files.
Build Artifacts
This repo is deployment-via-clone — dot sync does NOT run build steps. Build
artifacts (binaries, JS bundles) must be checked into git so they work immediately
after clone/pull. Only gitignore transient dev state like node_modules/.
Go CLIs (folio, jf, dendrik) each have a Makefile in cmd/<tool>/:
make check — fmt, vet, test (run before committing)
make build — compile binary in-tree (cmd/<tool>/<tool>)
make clean — remove binary
When changing Go source, always make check && make build and commit the binary
alongside the source changes. dot sync symlinks these binaries to ~/.local/bin/.
Never go build directly to ~/.local/bin/ — that bypasses the checked-in artifact
and dot sync will overwrite it.
Post-Edit Workflow
After making any change to dotfiles (public or private), complete the full cycle:
- Validate if touching scripts:
dot validate
- Test with
dot sync --dry-run before committing
- Commit using
/commit skill — conventional commits with scope (e.g. fix(shell): correct PATH ordering)
- Push to remote
dot sync if the change affects symlinks or managed files
Do not stop after editing a file — commit and push to complete the change.
When a change touches files in both ~/.dotfiles and ~/.dotfiles.private, commit each repo separately.
Testing Changes
- Validate:
dot validate — shellcheck, syntax, symlink sources, skill frontmatter
- Quick test:
dot health
- Full test:
~/.dotfiles/cmd/dot/scripts/uninstall.sh && dot sync --dry-run
- Interactive test:
dot setup
First-Time Setup
After first sync:
- Restart shell:
exec $SHELL -l
- Open nvim to trigger plugin installation
- Select iTerm2 "Dotfiles Default" profile
- Run
claude auth to authenticate
- Run
dot health to verify
Troubleshooting
- Git operations fail: Check GitHub SSH with
ssh -T git@github.com (must be set up before bootstrap)
- Symlink conflicts:
dot sync --dry-run shows state
- Broken links:
dot fix repairs them
- Changes not applied: Run
dot links
- First-time issues: Run
dot health for diagnostics
- Icons not rendering: Test with
echo -e "\uf015 \ue0a0 \uf121 \uf07b" — should show home, git-branch, code, folder icons. If broken, check terminal font is set to a Nerd Font.