Personal macOS development environment, installed by symlinking files from this repo into $HOME. It covers the shell (fish), editors (Vim, plus a Neovim setup being trialed), tmux, Hammerspoon automation, git, a set of Homebrew packages, and Claude Code customization (skills, agents, review workflow).
There is no build, and most of what is here is config: "testing" a change means sourcing the file or reloading the affected tool (fish, tmux, nvim, Hammerspoon). Of the few first-party scripts, only claude/skills/review-diff.sh — which the /review, /polish and /ship skills and the code-reviewer agent depend on — has a test suite; make check runs it.
- macOS. Config and automation assume a Mac (Homebrew under
/opt/homebrew, Hammerspoon, iTerm2). - Homebrew is bootstrapped automatically by the
Makefileif missing. - After the first install, run
tfenv install latestonce — theBrewfileshipstfenv, notterraform, so there's noterraformbinary until you do.
Clone the repo, then from its root run:
make # full setup: files, brews, fish, vimIndividual targets:
| Target | What it does |
|---|---|
make |
Full setup — runs files, brews, fish, vim. |
make files |
(Re)create the $HOME symlinks (runs claude first to link the Claude subset). Run after adding a new config file. |
make brews |
Install/update Homebrew packages from Brewfile (bootstraps Homebrew first). |
make fish |
Register fish in /etc/shells and set it as the login shell. |
make vim |
Install Vim plugins (via vim-plug) and vim-go's Go binaries. |
make claude |
Link only the Claude Code config subset into ~/.claude/ — the portable part, usable on a bare machine (e.g. a dev VM) without the rest of the toolchain. |
make check |
Syntax-check claude/skills/review-diff.sh and run the repo's only test suite, claude/skills/review-diff.test.sh, which drives it through every mode in throwaway repos under $TMPDIR. Not part of make. |
Symlinks use ln -fs for files and a LINK_DIR macro for directories. The macro guards against a footgun: ln -fns only replaces an existing symlink, but if the target is already a real directory it silently creates the link inside it instead. When you add a new config file, add a matching ln line to the files target in the Makefile — otherwise it never reaches $HOME.
config/fish/config.fish(→~/.config/fish/config.fish) — the login shell: PATH, aliases, functions, prompt. Includes__gcloud_config_env, a--on-variable PWDhook that scopesCLOUDSDK_CONFIGper git repo so each project's gcloud account/project/ADC stays isolated.zprofile(→~/.zprofile) — minimal fallback for macOS's default zsh (puts Homebrew onPATH, loads nvm). Everything else belongs in the fish config.
vimrc+vim/(→~/.vimrc,~/.vim) — the current Vim setup, using vim-plug.make viminstalls its plugins. Custom snippets live undervim/ultisnips/.config/nvim/(→~/.config/nvim) — a Neovim setup being trialed (not yet the daily editor), lazy.nvim-based. Plugins are one spec file each underlua/plugins/, auto-imported bylua/config/lazy.lua; versions pinned inlazy-lock.json.
config/herdr/config.toml(→~/.config/herdr/config.toml) — herdr, the agent-aware multiplexer replacing tmux. Onlyconfig.tomlis linked, since herdr keeps sockets, logs and session state in the same directory. It lists only non-default settings, carrying over the tmux habits that translate (prefixctrl+space, copy mode onprefix space,"/%splits,hjklfocus andHJKL/^H^J^K^Lresize, tmux-style dividers) and adding agent navigation onprefix [/]and workspaces onprefix {/}.herdr config checkvalidates it, including keybinding syntax.tmux.conf+tmux/(→~/.tmux.conf,~/.tmux) — plugins are vendored undertmux/plugins/(tpm, extrakto, tmux-cowboy); helper scripts intmux/scripts/.- Inactive panes are dimmed via
window-style/window-active-style, which only recolour cells left on the terminal's default background. Vim takes part in this becausevimrcsets the jellybeansbackgroundoverride tonone, so it paints no background of its own; anything that legitimately has one (the airline statusline,CursorLine,Visual) keeps it and does not dim. Changingwindow-active-style's background therefore changes the background of an active Vim too.
hammerspoon/Spoons/— Lua Spoons (window-sizer,pull-requests,minecraft-fkeys). Only the Spoons are symlinked, not the whole Hammerspoon directory.
claude/(→~/.claude/) — user config:CLAUDE.md,RTK.md,settings.json,statusline-command.sh(linked individually), plusskills/andagents/(each linked as a whole directory).agents/code-reviewer.mdis a read-only reviewer that is the single generator of review findings. It plays one role at a time, and a review fans out: several of them in parallel, each owning an area (a slice of the changed files) or a lens (claims, tests, contracts, ux, security, delta), coverage-checked and merged into one list. The finding bar lives inskills/review-core.md, the orchestration inskills/review-fanout.md, the per-role procedures inskills/review-lenses/, the fix policy inskills/review-fix.md(used by/reviewand/polish;/shipnever fixes), and the diff preparation inskills/review-diff.sh(tested bymake check);/review,/polishand/shipare built from these and differ only in disposition:/reviewfixes the findings you pick,/polishfixes every defect and only reports(preference)findings,/shipgates. The bar also screens out rephrasing prose that is already accurate, so a review reports claims that are wrong rather than sentences that could be worded differently — together these are what let/polishconverge instead of trading one phrasing for another every round. A per-worktree ledger inside.gitcarries settled, rejected and decided findings — and/polish's convergence record, which lets/shipskip a re-review when the diff is unchanged — across rounds and sessions;/shipdeletes it after pushing, and any other ending needs$(git rev-parse --absolute-git-dir)/review/removed by hand./shipalso takes an optional free-text argument setting two dials:reviewforces a review past a matching convergence record,now/right awayskips the review outright, andignore nitpickslowers the blocking bar to Issues only — verification runs on every path, and an argument it cannot read stops the run. The prepared diff comes from agit add -Asnapshot, so keep secrets gitignored rather than merely untracked. Two skills compose this machinery into a workflow:/forgetakes one work item from selection through/next's plan-mode gate to a/polish-hardened change, left In Review for the user to/ship;/milltakes a series of work-item identifiers — resolved and confirmed up front — and runs each one from that same plan gate straight to a pushed commit via/ship now, with no review at all, on the strength of the approved plan and a green verification set./forgeis the default and/millthe exception, for items whose description already carries enough detail that the plan is the review;/millfixes a check its own change turned red, stops and asks on anything needing judgment beyond the approved plan, and records on each item that it shipped unreviewed. The rest of~/.claude/(session transcripts, history, caches) is machine state and is never tracked or symlinked.skills/synced/is the mirror image: Claude Code's plugin-sync state, generated inside the repo becauseskills/is symlinked wholesale, and gitignored for that reason.claude/statusline-command.shrenders two lines: working directory, git state and the work item this workspace is bound to, then model, rate-limit usage against elapsed window time, context usage and cost. A session created by/branchis marked on the second line with⑂and the short id of the conversation it branched from (claude -r <id>returns to it), read off theforkedFromstamp Claude Code writes onto every record it copies into the branch; a/rewindor a--resume --fork-sessionfork leaves no such stamp and so is not marked. The work item is the one/nextclaimed and/shipclears after pushing, read back throughskills/task-marker.shso the marker's location stays defined in one place:◆ <id>in white with the title dimmed after it, clipped toTASK_TITLE_MAX(40) characters. Because that marker is advisory and/shipacting on a stale one would close the wrong item, the two states checkable locally are shown in red instead: a marker naming a branch other than the one checked out (◆ <id> @<branch>), and a marker that has lost its id (◆ ?). A third line is added whenever the instruction currently driving the session can be identified, so it is always clear what stage of the work Claude is at — slash commands in bold, prose dimmed, read back out of the session transcript. It skips turns that don't drive the work (read-only commands like/tasksand/cost, session plumbing like/resume, asides like/btw, and bare nudges like "continue") and keeps walking back to the instruction that does./clearends the search, since nothing before it still applies; when the search finds nothing the line is omitted. A long instruction is clipped toPROMPT_MAX(100) characters with an ellipsis. The search covers the lastPROMPT_SCAN_LINES(2000) lines of the transcript and rereads the whole file only if that window yields nothing. The tunables —PROMPT_MAX,PROMPT_SCAN_LINES,PROMPT_SKIP_COMMANDSandPROMPT_SKIP_PROSE— are all at the top of the script.- Notifications come from Claude Code itself rather than a hook:
preferredNotifChannel: iterm2_with_bellrings the pane's bell and raises an iTerm2 banner, andtmux.conf'smonitor-bellwithbell-action otherhighlights whichever window rang.allow-passthrough onis there so Claude Code's tab progress indicator — which stays lit while background subagents or workflows are still running, and clears once the session is idle — can reach iTerm2 through tmux.
gitconfig(→~/.gitconfig) — conditionally includesgitconfig_stim(work identity) for repos under~/projects/stim/.gitignore(→~/.gitignore) — global ignore rules.ctags(→~/.ctags) — universal-ctags configuration.
htoprc,myclirc— configs for htop and mycli.dlv/(→~/.dlv) — Delve (Go debugger) config.default.itermkeymap— iTerm2 key mappings (import via iTerm2 > Preferences > Profiles (Default) > Keys > Key Mappings).
Brewfile— the Homebrew manifest (~50 packages and casks): fish, tmux, vim, ripgrep, bat, tree, gh, jq, go, rust, node, pyenv, tfenv, database CLIs (mycli, pgcli, mysql-client),rtk, and more. Casks: ngrok, tailscale. Installed and updated bymake brews.
Keep this README.md in sync with the repo. Whenever a config area, make target, or notable tool is added, removed, or restructured, update the relevant section here in the same change so the README never drifts from what the repo actually contains.