---
name: chezmoi-management
description: "Meta-skill for mizchi's chezmoi dotfiles. Invoke ONLY when the user explicitly asks to manage / diff / apply chezmoi sources, add a skill to dotfiles, audit the APM vs chezmoi boundary, or initialize a new machine. Covers source location, diff/apply flow, skill addition, pre-push (pkfire + secretlint). Do NOT auto-invoke when the task only happens to touch a path under ~/.claude/, ~/.config/, or ~/.zshrc — consult only on explicit dotfile-management intent."
---

# chezmoi Management (mizchi personal)

Personal dotfiles operations notes. The official chezmoi docs are already sufficient, so this document focuses solely on **how things concretely work in my own environment**.

## Baseline environment

| Item | Value |
|---|---|
| Source directory | `~/.local/share/chezmoi/` |
| Remote | `https://github.com/mizchi/chezmoi-dotfiles.git` |
| Branch | `main` |
| Packages / `programs.*` | **nix home-manager** via `dot_config/home-manager/flake.nix` (= `home-manager` standalone or `nix-darwin` integrated mode) |
| pre-push | [pkfire](https://github.com/mizchi/pkfire) (`Taskfile.pkl` + `pkf hooks install`) + [secretlint](https://github.com/secretlint/secretlint) |
| Post-apply hook | `run_after_apm-install.sh` → `apm install --global --target claude` |

### Responsibility split with nix home-manager

This repo runs **alongside** home-manager — neither is a superset of the other:

- **home-manager owns**: CLI packages (`home.packages`), `programs.*` wrappers (git / direnv / zsh / etc), and tool installs that flow through Nix derivations (e.g. `pkfire`, `pkl`, `actionlint`, `awscli2`, `pkgs.go`). Edit `dot_config/home-manager/common.nix` then `darwin-rebuild switch --flake .#macos` (or `home-manager switch`).
- **chezmoi owns**: dotfile content that `programs.*` can't shape — `~/.claude/`, `~/.codex/`, `~/.apm/`, ad-hoc `~/.config/<editor-or-shell>/`, `~/.zshrc`, kept-templated paths.
- **APM owns**: public Claude Code skills under `~/.claude/skills/<name>/`. `.chezmoiignore` lists each APM-managed skill so chezmoi never re-deploys over `apm install -g`.

**Rule**: if a `programs.*` wrapper exists in home-manager for a tool, configure it there — do not also stage the dotfile via chezmoi. Mixing creates two source-of-truth conflicts that surface as "I edited the file but my change keeps reverting".

## Layout cheat sheet

```
~/.local/share/chezmoi/
├── dot_apm/          → ~/.apm/      (APM config)
├── dot_claude/       → ~/.claude/   (Claude Code)
│   ├── CLAUDE.md.tmpl
│   ├── settings.json.tmpl
│   ├── rules/
│   └── skills/       → ~/.claude/skills/   (self-authored skills)
├── dot_codex/        → ~/.codex/
├── dot_config/       → ~/.config/   (helix, mise, sheldon, starship, zellij, zsh, home-manager)
│   └── home-manager/ → ~/.config/home-manager/  (flake.nix / common.nix / darwin.nix — Nix-evaluated; chezmoi only stages the files, Nix does the actual install)
├── dot_zshrc         → ~/.zshrc
├── run_once_before_install-brew.sh   (bootstrap: clone Homebrew prefix to ~/brew before nix-darwin first activation)
└── run_after_apm-install.sh          (every apply: apm install --global --target claude)
```

### Meaning of filename prefixes

| prefix | Meaning | Example |
|---|---|---|
| `dot_` | Leading `.` | `dot_zshrc` → `.zshrc` |
| `executable_` | `+x` permission | `executable_setup.sh` → `setup.sh` (755) |
| `private_` | `0600` permission | `private_key` → `key` (600) |
| `.tmpl` | Go template | `CLAUDE.md.tmpl` → `CLAUDE.md` expanded with hostname etc. |
| `run_once_` | Run only the first time | `run_once_install_brew.sh` |
| `run_after_` | Run every time after apply | `run_after_apm-install.sh` |

## Daily flow

### (1) Check what differs

```bash
chezmoi diff                   # diff between source and dest
chezmoi status                 # rough status (MM/M /?? etc.)
```

**Direction sense for `chezmoi diff`**: the output is in `- dest` / `+ target` order (following git diff's old=current / new=after-apply convention).
- `-` lines = content in dest (the current real file) — will be **removed** by apply
- `+` lines = content in target (the expected state after source.tmpl expansion) — will be **added** by apply
- `chezmoi apply` moves in the direction of aligning dest to target (source → dest)

When in doubt, the fastest way to confirm is to look at both `cat <source>` and `cat <dest>`.

The columns for `chezmoi status` are `[source][dest]`:
- `M` = modified, `A` = added, `D` = deleted, `?` = untracked (not in source)
- `MM` → changes on both sides (need to absorb the manually-edited dest into source)
- ` M` → source unchanged but dest was modified (stray change)
- `DA` → marked as deleted in source but exists in dest (junk like `node_modules/`)

### (2) Edited dest (the real file) → absorb into source

```bash
chezmoi add ~/.zshrc                    # add a new file
chezmoi re-add                          # bulk-reflect changes from managed dest into source
chezmoi re-add ~/.claude/CLAUDE.md      # individual
```

### (3) Edited source (repo side) → apply to dest

```bash
chezmoi diff                            # look first
chezmoi apply                           # all
chezmoi apply ~/.claude/CLAUDE.md       # individual
chezmoi apply --verbose                 # show what it's doing
```

### (4) Open source in an editor

```bash
chezmoi edit ~/.zshrc                   # open the source side (whether to apply after closing the editor is controlled by the -a flag)
chezmoi edit -a ~/.zshrc                # also run apply after editing
chezmoi cd                              # cd to source dir
```

## New machine initialization

Install order is **nix → chezmoi → apm**. brew prefix is bootstrapped by chezmoi's `run_once_before_install-brew.sh` so `nix-darwin`'s `homebrew` module can reference `~/brew` on first activation.

```bash
# 1. Install Nix (Determinate Systems installer recommended).
curl -fsSL https://install.determinate.systems/nix | sh -s -- install

# 2. Bootstrap chezmoi (clones repo + applies; the brew script runs once here).
chezmoi init https://github.com/mizchi/chezmoi-dotfiles.git --apply

# 3. Switch home-manager / nix-darwin (installs pkfire, pkl, awscli2, …).
nix run nix-darwin -- switch --flake ~/.config/home-manager#macos
# (or for standalone HM without system-layer changes:
#  nix run home-manager/master -- switch --flake ~/.config/home-manager#macos)

# 4. Arm the pre-push gate on this repo's source.
cd $(chezmoi source-path)
pkf hooks install   # writes .git/hooks/pre-push (secretlint over outgoing diff)

# 5. apm install -g already fired via run_after_apm-install.sh during step 2.
#    To pick up upstream updates later: apm install -g --update
```

## Skill-addition flow (my personal routine)

1. Develop and verify in `~/.claude/skills/<name>/` (for nix-setup, get `nix build` passing)
2. `chezmoi add ~/.claude/skills/<name>` to reflect into source
   - Scripts with `+x` get the `executable_` prefix automatically
3. `cd ~/.local/share/chezmoi && git status` to confirm added files
4. `git add dot_claude/skills/<name>/` + commit + `git push origin main`

### If you edited an existing skill

Both `chezmoi add` and `chezmoi re-add` overwrite. In practice, `re-add` tends to produce fewer extraneous diffs:

```bash
chezmoi re-add ~/.claude/skills/nix-setup/SKILL.md
```

## APM vs chezmoi boundary

`~/.claude/skills/` is a **mix of two systems**:

| Kind | How it gets in | Managed at |
|---|---|---|
| APM-managed (external repo) | Fetched by `apm install --global` after apply | `~/.local/share/chezmoi/dot_apm/apm.yml` |
| chezmoi-managed (self-authored) | Copied into source by `chezmoi add` | `~/.local/share/chezmoi/dot_claude/skills/<name>/` |

**Currently installed via APM** (excerpt from `dot_apm/apm.yml`):

- `moonbitlang/moonbit-agent-guide/*` (moonbit-agent-guide, moonbit-refactoring, moonbit-c-binding)
- `mizchi/moonbit-practice/skills/moonbit-practice`
- `mizchi/flaker/skills/flaker-setup`
- `ast-grep/agent-skill/ast-grep`
- `mizchi/tui.mbt/skills/tuimbt-practice`

**Decision criteria**:
- Public / likely to be used from other repos → place in the upstream repo and register with APM
- Operational notes used only in my own environment / experimental skills → chezmoi-managed

If a directory of the same name exists in both locations, **APM may overwrite it at install time**, so watch out for name collisions.

**Check for APM name collisions before adding a new skill to chezmoi**:

```bash
# Check whether the same name exists on the APM side
grep "<skill-name>" ~/.local/share/chezmoi/dot_apm/apm.yml
# or
chezmoi cd && grep -r "<skill-name>" dot_apm/
```

## Editing tmpl files

`CLAUDE.md.tmpl` and `settings.json.tmpl` are Go templates:

```bash
chezmoi edit ~/.claude/CLAUDE.md     # opens the source-side .tmpl, not the real file
chezmoi execute-template < foo.tmpl  # manually check the expansion result
chezmoi data                         # list data available for expansion (hostname, OS, etc.)
```

Examples of template variables: `{{ .chezmoi.os }}`, `{{ .chezmoi.hostname }}`

**Caution**: Running `chezmoi re-add` against a `.tmpl`-managed file (`CLAUDE.md.tmpl`, `settings.json.tmpl`, etc.) will overwrite the `.tmpl` syntax in source with the expanded dest content. To update a `.tmpl` file, use `chezmoi edit` (which automatically opens the `.tmpl` side) or edit `~/.local/share/chezmoi/dot_claude/CLAUDE.md.tmpl` directly.

**Pre-check before re-add**: always check whether the target file is `.tmpl`-managed with `chezmoi source-path`:

```bash
chezmoi source-path ~/.claude/CLAUDE.md
# → /Users/mz/.local/share/chezmoi/dot_claude/CLAUDE.md.tmpl
# If it ends in .tmpl, don't re-add — edit manually. If not .tmpl, re-add is OK
```

### When you want to change the default value of a tmpl variable

Rather than rewriting the tmpl itself to a hard-coded value, put the variable in the `[data]` section of `~/.config/chezmoi/chezmoi.toml`:

```toml
[data]
  claude_default_mode = "auto"
  github_username = "mizchi"
```

It's more flexible to preserve tmpl structures like `{{ .claude_default_mode | default "acceptEdits" }}` as-is and switch only the values per-host via `[data]` (leaves room to vary settings across machines).

## pre-push (pkfire + secretlint)

`secretlint` runs on `git push` via pkfire (`Taskfile.pkl` → `pkf hooks install` → `.git/hooks/pre-push`), scoped to the diff range about to be pushed. Diffs containing API keys or tokens are rejected.

Re-arm the hook on a fresh checkout with `pkf hooks install`. Emergency bypass: `git push --no-verify`.

**Common false positives**:
- Example sha256 / hex strings (can trip when length resembles aws keys / github tokens)
- Sample values written in `.envrc` comments

Remediation:
```bash
# If it really is a false positive, exclude the rule in .secretlintrc.json
# If the detection is correct, fix the line and git add ... && git commit
```

Exclusion syntax in `.secretlintrc.json` (excerpt):

```json
{
  "rules": [
    {
      "id": "@secretlint/secretlint-rule-preset-recommend",
      "options": {
        "allows": [
          "/sha256-[a-f0-9]{64}/",
          "fake-token-for-example",
          "skill-examples/*"
        ]
      }
    }
  ]
}
```

`allows` takes either a regex (surrounded by `/.../`) or a literal string match. Per-file exclusion is possible via `disabledRules` + `includes`/`excludes`.

Don't use `--no-verify` (defeats the purpose).

## Troubleshooting

### `chezmoi status` shows a flood of `DA` (`node_modules/` etc.)

Append to `.chezmoiignore`:
```
node_modules
**/.DS_Store
```

### source and dest conflict

```bash
chezmoi merge ~/.zshrc          # 3-way merge (vimdiff-style)
chezmoi forget ~/.something     # remove from source (keep dest)
chezmoi destroy ~/.something    # remove both (destructive)
```

The `chezmoi merge` backend is configured in the `[merge]` section of `~/.config/chezmoi/chezmoi.toml`:

```toml
[merge]
  command = "nvim"
  args = ["-d", "{{ .Destination }}", "{{ .Source }}", "{{ .Target }}"]
```

If unset, chezmoi consults `git config merge.tool`. If both are unset, it falls back to vimdiff.

### Apply broke things → roll back to a previous revision

chezmoi itself has no undo. Roll back via git on the source side:
```bash
chezmoi cd
git log --oneline -5
git reset --hard <rev>
cd -
chezmoi apply
```

### Which files are managed

```bash
chezmoi managed                 # list of managed files
chezmoi unmanaged ~/            # unmanaged files
chezmoi managed ~/.claude       # filter by path
```

### apply is slow

`apm install` inside `run_after_apm-install.sh` runs every time. If no skill updates are needed, skip it via an env var at apply time:
```bash
SKIP_APM=1 chezmoi apply    # only if the script supports it (otherwise ignored)
```

If unsupported, ignore.

## Reference command cheat sheet

```bash
chezmoi diff                    # diff
chezmoi status                  # status
chezmoi apply [-v]              # apply
chezmoi add <path>              # take in a new file
chezmoi re-add [<path>]         # re-take an existing file
chezmoi edit [-a] <path>        # edit source (-a applies immediately after)
chezmoi merge <path>            # 3-way merge for conflicts
chezmoi forget <path>           # drop from source
chezmoi managed [<path>]        # list managed files
chezmoi unmanaged <path>        # unmanaged files
chezmoi cd                      # cd to source-path
chezmoi source-path             # show source directory
chezmoi execute-template <     # test template expansion
chezmoi data                    # list variables usable in templates
chezmoi doctor                  # setup diagnosis
```
