This document describes how this chezmoi repo configures AI coding assistants and what a new system setup requires. AGENTS.md holds the maintenance rules; this is the map.
Rules, the git-commit command, and skills have one source each:
| Content | Source | Delivered by |
|---|---|---|
| Rules | .rulesync/rules/*.md |
rulesync, from run_onchange_rulesync-generate |
| Commands | .rulesync/commands/*.md |
rulesync, from run_onchange_rulesync-generate |
| Skills | home/dot_claude/skills/*/ |
chezmoi for Claude Code; rulesync for the others |
.rulesync/rules/AGENTS.md is the root rule (root: true). Each tool receives it under its own
root file name. .rulesync/skills/*/ are symlinks back to home/dot_claude/skills/*/.
The rulesync script reruns on any chezmoi apply where a file under .rulesync/ or
home/dot_claude/skills/ changed. Never edit generated files; edit the source and apply.
| Target | Source | Notes |
|---|---|---|
~/.claude/CLAUDE.md |
.rulesync/rules/AGENTS.md |
Generated root rule |
~/.claude/rules/*.md |
.rulesync/rules/*.md |
Generated |
~/.claude/rules/environment.md |
home/dot_claude/rules/encrypted_environment.md.age |
Age-encrypted; deployed only where the personal key exists |
~/.claude/commands/git-commit.md |
.rulesync/commands/git-commit.md |
Generated |
~/.claude/skills/*/ |
home/dot_claude/skills/*/ |
Plain chezmoi files |
~/.claude/settings.json |
home/dot_claude/modify_private_settings.json |
Merges ACTIVE_AGENT, status line, vim mode, rtk hook, md-format hook |
~/.claude/statusline-command.sh |
home/dot_claude/executable_statusline-command.sh |
Status line script |
The settings script only edits the keys it owns. The rtk PreToolUse hook is added when rtk is on
PATH and removed when it is not. The md-format PostToolUse hook (see
Markdown Formatting) is always present. Both are rebuilt idempotently, so
other hooks in the file survive every apply.
| Target | Source | Notes |
|---|---|---|
~/.copilot/copilot-instructions.md |
.rulesync/rules/AGENTS.md |
Generated root rule |
~/.copilot/instructions/*.instructions.md |
.rulesync/rules/*.md |
Generated |
~/.copilot/instructions/git-commit.prompt.md |
home/dot_copilot/instructions/git-commit.prompt.md |
Hand-maintained (see below) |
~/.copilot/skills/*/ |
home/dot_claude/skills/*/ |
Generated |
| VS Code terminal profile | home/run_onchange_configure-vscode-copilot-terminal.sh.tmpl |
Sets ACTIVE_AGENT=Copilot |
~/.copilot/hooks/md-format.json |
home/dot_copilot/hooks/md-format.json.tmpl |
md-format hook for Copilot CLI and VS Code |
rulesync has no global-scope command support for Copilot, so its git-commit prompt is maintained
by hand. It sets model: claude-haiku-4-5 to run commits on a cheaper model; Claude Code and
OpenCode command files have no per-command model setting.
| Target | Source | Notes |
|---|---|---|
~/.cursor/rules/*.mdc |
home/dot_cursor/rules/*.mdc |
Hand-maintained (see below) |
~/.cursor/skills/*/ |
home/dot_claude/skills/*/ |
Generated |
~/.cursor/hooks.json |
home/dot_cursor/hooks.json.tmpl |
md-format hook (afterFileEdit) |
rulesync has no global-scope rule support for Cursor, so home/dot_cursor/rules/*.mdc is kept in
sync with .rulesync/rules/ by hand. Cursor .mdc frontmatter uses globs and alwaysApply.
Cursor also reads these natively, with no copies needed:
- Commands:
~/.claude/commands/, so/git-commitworks without a Cursor copy. - Agents:
~/.claude/agents/. - Project instructions:
AGENTS.mdin a project root.
Cursor's global User Rules live in a SQLite database
(~/Library/Application Support/Cursor/User/globalStorage/state.vscdb, key
aicontext.personalContext), not a file chezmoi can manage. Set them once under Cursor Settings →
Rules by pasting ~/.claude/rules/general-behavior.md.
| Target | Source | Notes |
|---|---|---|
~/.config/opencode/AGENTS.md |
.rulesync/rules/AGENTS.md |
Generated root rule, with an index of the rules |
~/.config/opencode/memories/*.md |
.rulesync/rules/*.md |
Generated |
~/.config/opencode/opencode.jsonc |
.rulesync/rules/*.md |
rulesync writes instructions; other keys survive |
~/.config/opencode/commands/git-commit.md |
.rulesync/commands/git-commit.md |
Generated |
~/.config/opencode/skills/*/ |
home/dot_claude/skills/*/ |
Generated |
~/.config/opencode/plugins/active-agent.ts |
home/dot_config/opencode/plugins/active-agent.ts |
Sets ACTIVE_AGENT, PAGER, GIT_PAGER |
~/.config/opencode/plugins/rtk.ts |
home/dot_config/opencode/plugins/rtk.ts.tmpl |
Deployed only when rtk is on PATH |
~/.config/opencode/plugins/md-format.ts |
home/dot_config/opencode/plugins/md-format.ts |
md-format hook; appends lint problems to the tool result |
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1 |
available/opencode.sh |
Shell module, enabled when opencode is installed |
OpenCode is installed through devbox global on every host, pinned to a nixpkgs-unstable commit because herdr needs 1.18.29 or newer and devbox's package index stopped at 1.18.21. See herdr.
- Instructions list: rulesync replaces the whole
instructionsarray inopencode.jsoncon every run, so add instruction files through.rulesync/rules/, not by editing that array. - Pager variables:
opencode runshells do not read.zshrcand inherit the launching terminal's pager, so the plugin setsPAGERandGIT_PAGERitself instead of relying on the agent branch of.zshrc. - Skills: OpenCode also loads skills from
~/.claude/skills/, including claude.ai-synced skills that need Claude-only tools. The shell module turns that off. Skills in~/.agents/skills/still load, so install third-party skills there (or with--agent opencode) to keep them visible. - Encrypted rule: the environment rule reaches Claude Code only.
herdr is a terminal multiplexer for coding agents. It comes from devbox
global on every host, pinned to the same nixpkgs-unstable commit as OpenCode.
home/.chezmoiscripts/run_onchange_after_herdr-setup.sh.tmpl does the rest, and its hash inputs are
the herdr and OpenCode versions and whether claude and copilot are present:
| Target | Written by | Condition |
|---|---|---|
~/.claude/hooks/herdr-agent-state.sh |
herdr integration install claude |
claude on PATH |
SessionStart entries in ~/.claude/settings.json |
herdr integration install claude |
claude on PATH |
~/.config/opencode/plugins/herdr-agent-state.js |
herdr integration install opencode |
opencode 1.18.29 or newer |
~/.config/opencode/herdr-tui-session.js, herdr-opencode/tui.js |
herdr integration install opencode |
opencode 1.18.29 or newer |
plugin in tui.jsonc, plugins in cli.json |
herdr integration install opencode |
opencode 1.18.29 or newer |
~/.copilot/hooks/herdr-agent-state.sh |
herdr integration install copilot |
copilot on PATH |
SessionStart entry in ~/.copilot/settings.json |
herdr integration install copilot |
copilot on PATH |
~/.copilot/skills/herdr/SKILL.md |
herdr --skill |
copilot on PATH |
~/.claude/skills/herdr/SKILL.md |
herdr --skill |
claude on PATH |
~/.config/opencode/skills/herdr/SKILL.md |
herdr --skill |
opencode on PATH |
- Nothing here is in the chezmoi source. herdr owns these files, and none of them collide with
managed ones. Copilot's
settings.jsonis unmanaged, and its hook script sits beside the managedhooks/md-format.json. The settings script passes theSessionStartentries through, the plugins directory isn'texact_, and rulesync doesn't delete skills it didn't generate. - First install takes two applies. The apply that adds herdr to devbox.json runs the script
before devbox installs herdr, so it renders as "absent" and skips. Run
devbox global install, thenchezmoi applyagain. - Upgrades: moving the nixpkgs pin changes the herdr version, which re-runs the script and reinstalls the integrations, as herdr's docs require. Restart OpenCode TUIs and any shared OpenCode servers afterward, or an old reporter can overwrite pane state.
- Skill: written from the binary unedited, so it matches the installed release and keeps its
HERDR_ENV=1guard.npx skills addis not used. - Config:
home/dot_config/herdr/config.tomlis a plain file that sets thedraculatheme and keepsonboarding = false. herdr also writes this file (onboarding, theme selection in Settings,herdr config reset-keys), so keep wanted changes in the source or the next apply reverts them. Runherdr server reload-configto load a change into a running server. - tmux: no shell file here auto-attaches tmux, so no
HERDR_ENVguard is needed.
Each tool sets ACTIVE_AGENT so .zshrc/.bashrc take the minimal branch: no history, plugins,
completions, prompt, or pager.
| Tool | Set by |
|---|---|
| Claude Code | env in ~/.claude/settings.json |
| Copilot | VS Code terminal profile |
| OpenCode | plugins/active-agent.ts |
| Cursor | Not set by this repo |
Line filling and table alignment are enforced by tools, not left to the model. Two reasons:
markdownlint never flags a line that stops short of 100 (and, before stern was set on MD013 in
home/dot_markdownlint-cli2.jsonc, it also passed overlong lines whenever nothing after column 100
was whitespace), and Claude Code skips the path-scoped markdown.md rule when it creates a new file
with Write. markdown.md still covers what a tool can't decide: title-case headings, no unprompted
emoji, not pasting whole files.
Three scripts in home/dot_local/bin/ deploy to ~/.local/bin/:
md-format [--check] FILE...reflows prose to 100 columns, then runsalign-tables. It leaves frontmatter, code, tables, headings, blockquotes, and reference-link definitions alone, keeps hard line breaks, and keeps links and code spans whole when they fit on one line. It refuses to write if anything other than whitespace would change.--checkwrites nothing and exits 1 if a file would change.md-format-hookis what every tool calls after an edit. It finds the edited Markdown files in the tool's hook payload (includingapply_patchtext), runsmd-format, then lints withmarkdownlint-cli2 --config "${HOME}/.markdownlint-cli2.jsonc", and returns remaining problems to the model. It skips files not modified in the last 60 seconds and anything under~/.claude/projects/.md-precommitis a git pre-commit hook that refuses a commit whose staged Markdown isn't formatted or lint-clean. Git hooks aren't versioned, so install it per repo:ln -s ~/.local/bin/md-precommit .git/hooks/pre-commit.
| Tool | Hook | Lint problems reach the model through |
|---|---|---|
| Claude Code | PostToolUse, matcher Write|Edit|MultiEdit |
Exit 2 and stderr |
| OpenCode | plugins/md-format.ts |
Text appended to the edit tool's result |
| Cursor | afterFileEdit in ~/.cursor/hooks.json |
Nothing; Cursor's after-edit hook cannot reply |
| Copilot CLI | PostToolUse, matcher edit|create|apply_patch |
additionalContext JSON on stdout |
| VS Code | The same ~/.copilot/hooks file; matchers are ignored, so it runs after every tool |
Exit 2 and stderr |
The hooks are not delivered through rulesync's hooks feature: in global mode it replaces the entire
hooks block in ~/.claude/settings.json, which would remove the rtk and other hooks there.
| Script | Runs when | Effect |
|---|---|---|
home/.chezmoiscripts/run_onchange_rulesync-generate.sh.tmpl |
.rulesync/ or a skill changes |
Generates rules, commands, and skills for all targets |
home/.chezmoiscripts/run_onchange_after_herdr-setup.sh.tmpl |
herdr, OpenCode, claude, or copilot changes |
Installs herdr's agent integrations and skill |
home/.chezmoiscripts/run_onchange_mklinks.sh.tmpl |
enabled/mklinks.sh changes |
Rebuilds enabled/ symlinks for the shell module loader |
home/run_onchange_after_install-packages.sh.tmpl |
Brewfile changes | Installs Homebrew packages from the Brewfile |
home/run_onchange_configure-vscode-copilot-terminal.sh.tmpl |
The script changes | Writes the Copilot terminal profile into VS Code settings |
home/run_onchange_configure-macos-defaults.sh |
The script changes | Applies macOS system defaults |
home/run_once_init-devbox-local.sh.tmpl |
First apply only | Initializes the devbox local environment |
home/dot_local/bin/executable_align-tables deploys to ~/.local/bin/align-tables. It reformats
Markdown tables to the MD060 aligned style the markdown rule requires, padding cells to display
width, skipping fenced code blocks, and treating \| in a cell as a literal pipe. md-format runs
it automatically. It is stdlib-only Python invoked through a uv-run shebang.
- Install chezmoi and clone this repo as the source directory.
- Run
chezmoi apply. This deploys all managed files, installs packages, and runs the scripts above, including rulesync generation. - Cursor: paste
~/.claude/rules/general-behavior.mdinto Cursor Settings → Rules. - OpenCode: run
opencode auth loginand choose a provider, then set a defaultmodelin~/.config/opencode/opencode.jsoncif wanted. Restart OpenCode to load the plugins. - Restart Claude Code, Cursor, and VS Code so they load the md-format hooks.
- In each repo that should refuse unformatted Markdown at commit time, run
ln -s ~/.local/bin/md-precommit .git/hooks/pre-commit.