This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Cross-platform Nix configuration for macOS (nix-darwin) and NixOS. Manages system and user configurations declaratively using Nix.
Hosts (config attr = hostname for every host):
burnedapple— personal Mac (aarch64-darwin),darwinConfigurations, nix-darwin + Home Managerpersonal-nixos— NixOS shell-only VM in UTM on the personal Mac (aarch64-linux),nixosConfigurationswork-nixos— NixOS shell-only VM in VMware Fusion on the work Mac (aarch64-linux),nixosConfigurations
Both NixOS hosts share system/nixos + hosts/common.nix; each pulls its own machine-specific system/nixos/hardware/<host>.nix (generated by nixos-generate-config inside that VM). just switch picks the NixOS config by hostname; pass one explicitly to bootstrap (just switch personal-nixos).
just switch # Apply configuration changes
just check # Run all linting checks
just fmt # Format all files
nix flake update # Update all flake inputs
sops secrets/secrets.yaml # Edit encrypted secrets
git meta <init|link|status> # Sync shared .meta/ config into worktrees (symlink .envrc/external; copy docs/specs+plans up)Tools: selene, stylua, alejandra, statix, deadnix, typos, taplo, shellcheck, actionlint, jaq, gitleaks
flake.nix # Entry point (inputs + module composition)
├── hosts/ # Host configs: common.nix, macbook.nix, personal-nixos.nix, work-nixos.nix
├── modules/ # Shared: options.nix, nix.nix, theme.nix
├── system/{darwin,nixos}/ # Platform system configs
├── home/{common,darwin,linux}/ # Home Manager configs
├── claude/ # Everything Claude: home/, security/, overrides/, package.nix, update.sh, statusline.sh, direnv-rules.sh, direnv-agents.sh
├── config/ # Dotfiles: nvim/, wezterm/, yazi/, karabiner/
├── llm/ # Shared assistant content (rules/skills/agents/commands consumed by claude + codex)
├── scripts/ # Helper scripts (setup, git-meta, git-bare-clone, keymaps, update-codex)
├── tests/ # Integration tests (claude-security.nix, claude-settings.nix, codex.nix)
├── overlays/ # Custom package overlays
├── patches/ # Custom patches for packages
└── pkgs/ # Custom package definitions
@configPath@placeholder (wezterm, yazi): Substituted viabuiltins.replaceStringsinhome/common/xdg.nixat build time.nix-infomodule (nvim): nix-wrapper-modules injectsconfig_directory.init.luareads_G.nixInfo.settings.config_directory.
config.custom.*: Typed NixOS options inmodules/options.nix. All modules consume these instead ofextraSpecialArgs. Options defined inhosts/common.nix, overridden per-host.- lze plugin loading: Uses
on_require,dep_of,on_plugin. Does NOT have adepfield. Library deps inconfig/nvim/lua/plugins/deps.lua. allowUnfreePredicate: Shared unfree allowlist inflake.nix, applied to both platforms.- Claude consolidation: All Claude code lives under
claude/—claude/home.nixis the HM entry,claude/security/is the security module generatingsettingsFragment(hooks + permissions),claude/home/settings.nixmerges it into~/.claude/settings.json,claude/package.nixis the vendored binary,claude/overrides/holds CLAUDE.md and agent overrides. Codex follows the conventional split (pkgs/codex.nix+home/common/codex/+scripts/update-codex.sh) since it lacks the custom surface area that justifies a top-level dir. - Git worktree tools:
git bclone(bare clone →.bare/+ sibling worktrees) andgit meta(syncs shared.meta/config into each worktree:.envrc/externalare symlinked,docs/specs+docs/plansare real per-worktree dirs copied up into.metawith no overwrite) installed viahome/packages.nix. - Sandbox wrappers on PATH:
config.custom.sandboxedPackages.{claude,codex}(set per platform inhome/{darwin,linux}/sandboxed.nix) install binaries namedclaude-sandboxed/codex-sandboxedviahome/common/packages.nix. Thea/arshell functions and theo/or/oxaliases invoke those names, neverlib.getExe— a store path baked into zshrc makes a long-lived shell keep launching the version that was current when it started. Plainclaude/codexon PATH remain the unsandboxed CLIs. - Claude rules (direnv): Language rules stored in
~/.config/claude-rules/(nix-managed). Useuse claude_rulesin.envrcto symlink relevant rules into project-local.claude/rules/. Auto-detects languages when called without args. Explicit:use claude_rules go nix. Seeclaude/direnv-rules.sh. - Claude agents (direnv): Agents from
llm/shared/agents/are stored in~/.config/claude-agents/, deliberately not~/.claude/agents/— a globally installed agent spends prompt tokens in every session, including projects it can never apply to.programs.direnv.stdlibcallsuse_claude_agentsfrom~/.config/direnv/direnvrc, so every direnv project links its relevant agents into project-local.claude/agents/with no.envrcline. Detection is by project shape (Cargo.toml →rust-engineer, abevydep →bevy-engineer,Anchor.toml/anchor-lang→solana-developer, Unity layout or any.asmdef→unity-csharp-engineer); probes are depth-bounded because this runs on entry to every project. A project that matches nothing links nothing and leaves no directory behind. Override explicitly withuse claude_agents rust-engineerin.envrc. Seeclaude/direnv-agents.sh. Codex is unaffected; it still installs all agents to~/.codex/agents/. - MCP tool-surface budget: Every MCP tool name is spent in each session's prompt, so servers are narrowed at the wrapper.
githubruns with--toolsets/--exclude-tools(44 default tools → 31, seehome/common/mcp.nix);gitlabis gated behindcustom.gitlab.enable(work host only) and runs read-only. - MR review (
<leader>cv):config/nvim/lua/plugins/review.luaholds both halves. The local ones (diff vs the base branch, commit-by-commit walk, difftastic,glabMR picker) work in any repo on any forge and always diffbase...HEAD, sincebase..HEADwould also show whatever landed on the base branch after the fork. The gitlab.nvim ones (discussion tree, line comments, approvals) only register whencustom.gitlab.enableis set, which also gates the Go server build (gitlab-nvim-serverinhome/common/neovim/module.nix, pinned with thegitlab-nvimflake input — bumping the tag needs a newvendorHash). The prebuilt binary is what makes the plugin skip its owngo buildat runtime. diffview carrieson_requirebecause gitlab.nvim pulls indiffview.asyncbefore anyDiffview*command runs. - glab aliases: written by a
home.activationcopy at 0600, notxdg.configFile— glab refuses to read a config file that is not mode 600 and then fails every command, and a store symlink is 444.
SOPS with age encryption. Key: ~/.config/sops/age/key.txt
sops secrets/secrets.yaml # Edit secrets
# Define in nix: sops.secrets.my-secret = { };
# Access at runtime: /run/secrets/<secret-name>