This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
make install # link dotfiles + install packages
make link # symlink dotfiles only (no package installation)
make install_packages # install Homebrew packages onlyRequires Git and Make. make copy copies a few files (.bashrc, /etc/paths) that cannot be symlinked.
This repo manages dotfiles via symlinks on macOS and Linux. The authoritative mapping lives in scripts/link.sh (DOTFILE_PAIRS), an indexed array of "src:dst" pairs (bash 3.2 compatible — macOS ships with bash 3.2 so associative arrays are avoided) that maps repo-relative paths to their home directory destinations (e.g. .gitconfig → ~/.gitconfig).
scripts/lib.sh— shared utilities:detect_platform(sets$PLATFORMtoosx/linux) andlog <level> <text>scripts/link.sh— creates all symlinks; also handles anOBSOLETED_PAIRSlist for cleanup of removed mappings. Each directory under.claude/skills/is auto-linked into~/.claude/skills/<name>so machine-local skills placed directly in~/.claude/skills/are left untouchedscripts/install_packages.sh— installs Homebrew, then brew packages and casks; handles macOS/Linux branchesgit-hooks/pre-commit— Ruby script that blocks commits containingbinding.pry,debugger,focus: true,save_and_open_page, or merge conflict markers
Self-authored scripts under .claude/ (hook helpers, skill helpers) and repo automation are written in Ruby by default, to keep a single runtime across the repo. Ruby is always available (managed via mise). git-hooks/pre-commit is Ruby; the voicevox-speak hook and the report-session skill scripts were ported from Python to Ruby for this reason — prefer Ruby for new scripts and don't introduce Python.
Breaking the Ruby default is fine in these cases:
- Thin shell glue / triggers where shell is the natural fit and there's little logic (e.g. the existing
.claude/hooks/report-session-*.shtrigger wrappers). - Integration constraints — the target tool only ships an SDK/library in another language, so that language is required.
- Vendored / third-party scripts — keep them in their original language; don't port for uniformity's sake.
When breaking the default, keep the logic minimal; if a script grows real logic, move it to Ruby.
.claude/settings.json (symlinked to ~/.claude/settings.json) — Claude Code configuration including:
- Permission allow/deny/ask lists for Bash commands and file reads (POST-style
curl,git checkout/switch/push/reset/rebaseare gated byask) - PostToolUse hooks: auto-formats
.rb/.rakefiles via.claude/hooks/ruby-format.rb(usesstandardrb --fixwhenstandardis in the bundle, falls back torubocop -A), and.mdfiles via.claude/hooks/md-format.rb(prettier). Format skipping is controlled by.claude/hooks/format-skip.rb— see Formatter skip below - macOS notification hooks on Stop and Notification events via
osascript report-sessionauto-suggest hooks (scripts in.claude/hooks/, referenced via$HOME/.claude/hooks/...): aUserPromptSubmithook fires on user satisfaction/completion phrases (gated by transcript length), and aPostToolUseBash hook fires ongit commit/push; both injectadditionalContextnudging Claude to offer running/report-session, with the final go/no-go left to the modellanguage: "日本語",alwaysThinkingEnabled, and enabled plugins (ruby-lsp,skill-creator,frontend-design,security-guidance)
PC-local settings in settings.json: settings.local.json does not exist at the user layer (~/.claude/) — only at the project layer. To keep machine-specific values (model, extra plugins, etc.) out of git while keeping the symlink intact, this repo uses git update-index --skip-worktree .claude/settings.json. When committing a change to the shared portions, temporarily lift the flag:
git update-index --no-skip-worktree .claude/settings.json
git add -p .claude/settings.json # stage only the shared hunks
git commit
git update-index --skip-worktree .claude/settings.json.claude/hooks/ (symlinked to ~/.claude/hooks) — shell scripts invoked by the hooks in settings.json. Referenced as $HOME/.claude/hooks/... so settings.json stays independent of the repo location.
Formatter skip — .claude/hooks/format-skip.rb is a shared module that ruby-format.rb and md-format.rb both require_relative. Skip rules are evaluated in order:
| Flag | Scope |
|---|---|
/tmp/.claude-skip-format |
all formatters, session-wide |
/tmp/.claude-skip-format.<ext> |
extension only, session-wide |
<repo>/.claude/skip-format |
all formatters, project-wide (commit to share with team) |
<repo>/.claude/skip-format.local |
all formatters, project-wide personal (gitignored) |
<repo>/.claude/skip-format.<ext> |
extension only, project-wide |
<repo>/.claude/skip-format.local.<ext> |
extension only, project-wide personal (gitignored) |
.claude/skills/<name>/SKILL.md — per-skill definitions auto-linked by scripts/link.sh. Add a new skill by creating a directory under .claude/skills/ with a SKILL.md; running make link symlinks it into ~/.claude/skills/<name> (no manual DOTFILE_PAIRS edit needed).
Gemfile — declares gem "standard" (standardrb). Dotfiles scripts are few and don't warrant maintaining a custom rubocop rule set; standardrb provides zero-config formatting. The .vscode/settings.json sets rubyLsp.formatter: "standard" so VSCode format-on-save also uses standardrb.
.config/mise/.config.toml — manages runtime versions for node, ruby, python, terraform, and claude via mise
.gitconfig — notable aliases:
git aicommit— generates a commit message via Claude CLI, then opens editorgit aa/git coo/git rr/git b-delete— fzf-powered interactive git operationsgit fmr— fetch + rebase onto origin/main in one step
.zsh/.aliases.zsh — defines cop (runs rubocop on modified .rb files or diff from main), gwt (creates git worktree under tmp/worktree/), rgt (ripgrep TSV output for spreadsheets)
- Add the file to the repo
- Add its
"src:dst"mapping toDOTFILE_PAIRSinscripts/link.sh(skip this step for skills — directories under.claude/skills/are picked up automatically) - Run
make linkto apply