docs: refactor dotfiles skill into CLAUDE.md

The skill wasn't firing reliably, so instead of polluting the global
config with a skill that only helped *sometimes*, convert it into
project-specific instructions in .config/dot

Future work on this project (dotfiles) should always start from this
directory, even if changes are made outside. This helps Claude keep
the scope narrow.
This commit is contained in:
2026-07-04 10:54:45 -04:00
parent d41e1c46f4
commit d5aacad86e
3 changed files with 38 additions and 37 deletions

View File

@@ -1,75 +0,0 @@
# The dot CLI
## Architecture
`dot` is defined in one file: `~/.config/fish/functions/dot.fish`. It holds
three functions:
- `dot` (`--wraps=git`) — dispatches `init`, `help`, and any file found under
`~/.config/dot/commands/`, otherwise forwards everything to
`git --git-dir=~/.dotfiles --work-tree=$HOME $argv` (full passthrough).
- `__dot_init` — the bootstrap logic, inlined in the same file rather than
autoloaded separately, because it's the one subcommand that must work
before the dotfiles repo has ever been cloned onto a machine.
- `__dot_help` — prints usage: the built-in commands plus whatever is
currently found under `~/.config/dot/commands/`, generated by globbing that
directory rather than a hardcoded list, so it can't drift from reality.
`__dot_help`'s glob over `~/.config/dot/commands/*.fish` is duplicated in
`~/.config/fish/completions/dot.fish`'s `__dot_custom_subcommands` rather than
shared: fish only autoloads a function from a file named after that function,
so a helper defined inside `dot.fish` would be undefined if tab-completion
ran before `dot` had ever been sourced in the session. Keep both copies in
sync when the listing logic changes.
`dot init`:
- refuses to run if `~/.dotfiles` already exists (no re-init support)
- clones the bare repo from `--url` (default: the hardcoded Gitea remote) —
if the clone fails, it errors out; it never falls back to `git init`
- backs up any pre-existing file that checkout would clobber into
`~/.dotfiles-backup/<timestamp>/`, then retries the checkout
- explicitly sets `status.showUntrackedFiles=no` after cloning — this is a
local-only git setting, so a fresh `git clone` never carries it over
## Adding a subcommand
Beyond `init`, `dot` looks for `~/.config/dot/commands/<name>.fish`, sources
it, and calls `_dot_<name>`. These files are deliberately kept out of
`~/.config/fish/functions/` (fish's autoload path) so they never become
independently invokable top-level commands or clutter tab-completion outside
of `dot` itself.
1. Create `~/.config/dot/commands/<name>.fish` defining a `_dot_<name>`
function.
2. Confirm `dot <name>` dispatches to it. No other wiring is needed —
`~/.config/fish/completions/dot.fish` and `__dot_help` both discover new
command files by globbing that directory, and `--wraps=git` still covers
raw git subcommands.
3. Add a case to `~/.config/dot/tests/dot.fish` covering it and run
`fishtape ~/.config/dot/tests/dot.fish` until it passes.
## Testing
Tests live at `~/.config/dot/tests/dot.fish`, run with
`fishtape ~/.config/dot/tests/dot.fish`. Fishtape is installed via Fisher
(`fisher install jorgebucaran/fishtape`) and tracked in
`~/.config/fish/fish_plugins` — a real, restorable dependency for developing
`dot`, but never required just to use it.
- Each scenario overrides `$HOME` (`set -gx HOME (mktemp -d)`) before calling
`dot`, so tests never touch the real `~/.dotfiles`.
- Build a throwaway bare "remote" fixture with `git init --bare` plus a
seeded commit, and explicitly set its `HEAD`
(`git --git-dir=$remote symbolic-ref HEAD refs/heads/main`). Pushing with
`git push origin HEAD:main` does **not** update the bare repo's `HEAD`
symref — skip this and a clone of the fixture can end up "on a branch yet
to be born."
- Don't use `.gitconfig` as a fake pre-existing "conflict" file in a
fixture — git parses `$HOME/.gitconfig` as its own global config on every
invocation, and garbage content there spams "key does not contain a
section" errors that drown out the real assertion. Use a harmless file
like `.bashrc` instead.
- `@test "description" <expr> <op> <expected>` mirrors fish's `test` builtin
(`-eq`, `-ne`, `=`, `-e`, `-f`, `-d`, `-n`, `-z`); `-a`/`-o` combinators
aren't supported.

View File

@@ -1,26 +0,0 @@
---
name: dotfiles
description: Conventions for this machine's dotfiles bare-repo setup. Use when editing any file under $HOME managed by this repo, or when extending the dot CLI (subcommands, completions, bootstrap, tests).
---
# Dotfiles
This machine's dotfiles are a bare git repo at `~/.dotfiles`, checked out with
`$HOME` as its work-tree. The `dot` fish function wraps that invocation
(`git --git-dir=~/.dotfiles --work-tree=$HOME $argv`, declared with
`--wraps=git`), so every git subcommand works through it: `dot status`,
`dot add`, `dot commit`, `dot push`, etc.
## Always add by explicit path
`status.showUntrackedFiles=no` is set locally, and `.gitignore` only excludes
`.dotfiles` itself plus OS/editor cruft — it is **not** a whitelist. That
means virtually everything under `$HOME` reads as untracked, and `git status`
deliberately hides all of it.
**Always run `dot add <specific-path>`.** Never `dot add -A`, `dot add .`, or
any wildcard add — that would try to stage the entire home directory (caches,
secrets, everything).
See [DOT-CLI.md](DOT-CLI.md) for the `dot` command's own architecture,
bootstrap logic, subcommand dispatch, and test suite.