150 lines
11 KiB
Markdown
150 lines
11 KiB
Markdown
# Claude Code config
|
|
|
|
Portable [Claude Code](https://claude.com/claude-code) customizations, synced via these
|
|
dots. Claude Code still uses its default `~/.claude/` directory; we surface the tracked
|
|
files below into it via per-file/dir symlinks, so edits made through Claude write straight
|
|
back into this repo.
|
|
|
|
## What's tracked here
|
|
|
|
- `settings.json` — global settings: permissions (deny git commit/push, `gh pr` write ops, and
|
|
reads of secret files), default `model`, `enabledPlugins`, vim mode, dark theme, `effortLevel`,
|
|
fullscreen TUI, `statusLine`, `hooks`, `worktree` defaults, a generic `autoMode.environment`, …
|
|
Nothing in here may name a specific project, path or remote: this file is published by the
|
|
public mirror, and the auto-mode classifier only reads `autoMode` from user/managed settings
|
|
anyway (never from a project's `.claude/settings*.json`).
|
|
- `statusline.py` — rich status line (dir, git, model, context %, cost, disk — system metrics live in the tmux bar).
|
|
See [StatusBar.md](StatusBar.md) for what each segment and colour means.
|
|
- `keybindings.json` — custom keybindings: vim-style scroll/navigation, plus `ctrl+x v` for the
|
|
verbose transcript view (`app:toggleTranscript`, same keystroke as opencode's `<leader>v`; the
|
|
default `ctrl+o` still works, and `ctrl+e` inside the view expands everything).
|
|
- `CLAUDE.md` — global user preferences applied to every project.
|
|
- `hooks/format.py` — PostToolUse hook: formats edited files by extension (stylua / ruff /
|
|
prettier / gofmt / rustfmt / fish_indent / shfmt / taplo). Every formatter is opt-in: stylua,
|
|
ruff, prettier and taplo run only where the project has their config file (found walking up);
|
|
gofmt and rustfmt gate on a project marker (`go.mod` / `Cargo.toml`); fish_indent and shfmt gate
|
|
on an `.editorconfig` *section* that matches the file. Wired to `Edit|Write` **and** `Bash` —
|
|
under auto mode Claude edits through the shell, so the Bash branch scans the command for
|
|
path-like tokens and formats the ones modified in the last 30 s. No-ops if the formatter isn't
|
|
installed, and always exits 0.
|
|
- `hooks/git-guard.py` — PreToolUse hook on `Bash`: structurally blocks anything that commits,
|
|
rewrites history or discards working-tree changes — `git commit` / `push` / `revert` /
|
|
`cherry-pick` / `rebase` / `merge` (not `--abort`), `clean` / `reset` in any form that moves HEAD
|
|
or unstages everything (`reset -- <paths>`, `reset HEAD <path>` stay allowed) / `restore <paths>`
|
|
(`--staged <path>` is fine, `--staged .` is not) / `checkout --` / `checkout .` / `stash drop|pop|clear` /
|
|
`branch -D|-f|-M`, and `gh pr merge` / `create` / `close` / `edit` — wherever the flags sit
|
|
(`git -C . push`, `/usr/bin/git push`, `sh -c 'git push'`), which the prefix-matched deny rules
|
|
can't. The discard group is what protects parallel sessions sharing one checkout. Exit 2 + a
|
|
one-line reason. Heredoc bodies are skipped on purpose (file contents). Regression matrix:
|
|
`bash ~/.claude/hooks/git-guard-test.sh` (a file on purpose — typed inline, the commands under
|
|
test would trip the live hook).
|
|
- `hooks/notify.py` — Notification hook (`idle_prompt|permission_prompt`): `notify-send` desktop
|
|
notification when Claude is waiting on you; no-op on a headless host.
|
|
- `hooks/drift-check.sh` — SessionStart + UserPromptSubmit hook, read-only: fails loudly when a
|
|
`~/.config/claude/*` file has broken out of the repo into a plain file, or `~/.claude/*` no longer
|
|
resolves into the dots repo. That is what Claude Code's atomic settings rewrite does through the
|
|
stow layer — on 2026-09-15 it silently reverted the live config to a stale snapshot for an hour,
|
|
dropping four hook events. At session start the warning is shown to you (exit 2, session goes
|
|
on); on each prompt any drift is fed to Claude as context so it reports it. Fix is still
|
|
`link.sh` (heals identical copies, warns otherwise).
|
|
- `hooks/local-commands.py` — UserPromptExpansion hook: **TUI-side slash commands**. When you type
|
|
`/x` and `skills/x/local.py` exists, the hook runs it with the arguments, shows its output to you
|
|
and blocks the expansion (exit 2), so no prompt reaches the model and it costs nothing. Skills
|
|
without a `local.py` expand as usual. Caveat baked into the design: the hook fires *after* the
|
|
skill body expanded, so a skill with a `local.py` must not also act via a `!` inline command.
|
|
- `skills/` — custom [Agent Skills](https://code.claude.com/docs/en/skills). See **Skills** below.
|
|
- `link.sh` — idempotent bootstrap that creates the `~/.claude` symlinks below.
|
|
- `../git/claude-commit-msg` + `../git/hooks/prepare-commit-msg` — the commit-message handoff.
|
|
A session pipes its suggestion into `claude-commit-msg save`, which stores it as its own file under
|
|
`$(git rev-parse --git-path CLAUDE_COMMIT_MSG.d)/` **with the paths it staged**; on the next
|
|
`git commit` / lazygit `C` the hook prefills the editor with the message(s) whose paths are part of
|
|
that commit and consumes them (`used/`). Parallel sessions therefore never overwrite each other,
|
|
and committing one session's files gets that session's message. `claude-commit-msg list` shows
|
|
what is pending. The old single `CLAUDE_COMMIT_MSG` file is still honoured. Both tracked here
|
|
(deployed to `~/.config/git/`); the hook is installed **per repo** — see setup step 4 — and
|
|
deliberately not via a global `core.hooksPath`, which would override a project's own hooks.
|
|
|
|
The `settings.json` hook commands for the newer scripts are wrapped in `[ -f … ]`, so a host that
|
|
has pulled the repo but not yet re-stowed just skips them instead of blocking every Bash call.
|
|
|
|
Paths inside `settings.json` reference `~/.claude/...` via `$HOME`/`PATH` (`/usr/bin/env
|
|
python3 ~/.claude/...`), so they work regardless of the username, python location, or where the
|
|
dots repo is cloned (`~/.dots`, …).
|
|
|
|
## What is **not** tracked (stays local, per device)
|
|
|
|
Everything else under `~/.claude/` is machine-specific or secret and must never be committed:
|
|
credentials (`.credentials.json`), session history, `projects/` (transcripts + memory),
|
|
caches, and the `plugins/` cache/binaries.
|
|
|
|
## Setup on a new device
|
|
|
|
```sh
|
|
# 1. Deploy the dotfiles repo — install.sh stows ~/.config/claude AND runs link.sh for you
|
|
cd ~/.dots && ./install.sh
|
|
|
|
# 2. Launch Claude and log in once (credentials are NOT synced)
|
|
claude
|
|
|
|
# 3. Reinstall the plugins from settings.json -> enabledPlugins
|
|
# (typescript-lsp, frontend-design — anthropics/claude-plugins-official) via /plugin.
|
|
# Optional: install any formatters you want the hook to use (stylua, ruff, prettier, …),
|
|
# and libnotify (notify-send) on GUI hosts for hooks/notify.py.
|
|
```
|
|
|
|
```fish
|
|
# 4. In every repo you commit in: install the one-shot commit-message prefill hook
|
|
ln -sf ~/.config/git/hooks/prepare-commit-msg (git rev-parse --git-path hooks)/prepare-commit-msg
|
|
```
|
|
|
|
`install.sh` deploys `~/.config/claude` (via stow) and then runs `link.sh` automatically. You only
|
|
need to run `link.sh` by hand (`bash ~/.config/claude/link.sh`) if a link later gets clobbered
|
|
(e.g. Claude's `/config` replaces one with a plain file) — it's idempotent and self-healing.
|
|
|
|
A **new skill/hook** added to the repo shows up after a re-stow — `./install.sh` (or `dotsync`) links
|
|
the new file into `~/.config/claude`, which `link.sh` then surfaces into `~/.claude`.
|
|
|
|
## Skills
|
|
|
|
Custom skills live in `skills/<name>/SKILL.md` (the directory name is the `/command`) and are
|
|
linked in as `~/.claude/skills`. All are `disable-model-invocation: true` — explicit `/` only,
|
|
so Claude never auto-triggers them. Run them inside the repo whose branch you're working on.
|
|
|
|
- **`/review-branch [base]`** — reviews the current branch's diff (vs its merge-base with the
|
|
base branch) for 🐞 bugs / 🔒 security / ⚡ optimizations / 📖 readability, reports grouped
|
|
findings with `file:line`, then applies the readability + safe fixes *after you approve*. Never
|
|
commits.
|
|
Borrows `/code-review`'s discipline: CLAUDE.md-aware, changed-lines-only, verified bugs with a
|
|
false-positive filter (skips what linters/CI catch and pre-existing issues). Ends by pointing
|
|
you at the relevant built-in follow-ups (`/security-review`, `/code-review`, `/verify`, …).
|
|
- **`/pr-description [base]`** — auto-detects this repo's GitHub PR template
|
|
(`.github/pull_request_template.md`, …), fills it from the branch diff + commits, prints a
|
|
copy-paste markdown block, and copies it to the clipboard with `wl-copy`. Leaves verification
|
|
checkboxes for you; never creates/pushes the PR.
|
|
- **`/commit-msg [hint]`** — reads the **staged** diff, infers the repo's commit style from recent
|
|
`git log` (e.g. this repo's `[Scope] summary`), drafts a matching message, prints it, and copies
|
|
it to the clipboard with `wl-copy`. Never stages or commits — draft only.
|
|
- **`/verbose [on|off|status]`** — flips the *persisted* verbose view for the current project:
|
|
`viewMode` (`verbose` / `default` — the key that actually decides the view and beats a sticky
|
|
`/focus` choice for new sessions) plus `verbose` and `showThinkingSummaries`, written to the
|
|
project's `.claude/settings.local.json` (machine-local, never committed) rather than the tracked
|
|
`settings.json`, so a toggle never shows up as a dots diff. Runs **TUI-side** — `local.py` via
|
|
`hooks/local-commands.py`, no model turn — with the SKILL.md body as the fallback on a host
|
|
where the hook isn't deployed. The *live* toggle is a keypress: `ctrl+x v` / `ctrl+o`.
|
|
- **`/pr-loop [pr]`** — babysits the current branch's PR in a self-paced loop: fixes review
|
|
comments that don't need your input — from **any** reviewer (Codex, Claude, humans) plus any
|
|
`@claude` request — `git add`s them, drafts the commit message (`/commit-msg` style) and marks
|
|
each thread with a reaction instead of an "Addressed — …" reply: **👍** fixed, **👀** parked for
|
|
your decision, **👎** a bot false positive (bots only, always with a one-line why). Then pings you
|
|
to commit & push and re-checks every ~10 min (via `ScheduleWakeup`). Resolves threads once their
|
|
fix is pushed — dismissals right away, since there's nothing to land. Stops when no unresolved comments
|
|
remain or the automated reviewers (Codex/Claude) hit their limit. Prep-only — never
|
|
commits/pushes/merges; needs `gh` authed and the session left open. Each cycle it prints when it
|
|
will re-check and that `Esc` (between cycles) stops it; on the last cycle it prints a clear
|
|
**"Finished — no more active issues"** and does not reschedule.
|
|
|
|
## Adding more config later
|
|
|
|
Drop the file/dir under `.config/claude/` here, add its name to the `items` list in `link.sh`,
|
|
and re-run `bash ~/.config/claude/link.sh`. For example `commands/` or `agents/`.
|