Files
dotfiles/AGENTS.md

95 lines
12 KiB
Markdown

# dotfiles-nixos
One flake that builds every machine the user owns.
The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `~/Documents/ai-artifacts/projects/dotfiles/003-dotfiles-context.md`.
## Conventions
- Comments posted to Gitea (pull requests, issues, reviews) go out under the operator's account, so sign every one to make clear the author is the agent, not the operator.
End the comment with a `— Claude` sign-off.
(A dedicated bot account may replace this later.
Until then, the sign-off is the only marker.)
- Commit messages follow Conventional Commits, specified in `docs/conventional-commits.md`.
Scope is the module or host the change belongs to (`fish`, `nvim`, `neogaia`), omitted for repo-wide changes.
Keep messages free of Gitea-specific references: this repository is mirrored to GitHub, where issue and pull-request numbers resolve to unrelated things.
- When a graphical application is added, give it a `window-rewrite` icon mapping in `modules/desktop/waybar.nix`.
Without one its windows fall back to the generic default glyph on the workspace indicator instead of showing a recognisable per-application icon.
Match on the window class, which `hyprctl clients -j | jq -r '.[].class' | sort -u` lists for the running session.
## Gotchas
- Subagent completion delivery is non-blocking through immediate spawn, milestone notifications, retained terminal entries, and `subagent_list` or `subagent_result` retrieval.
`subagent_wait` intentionally blocks the parent tool call until its condition or timeout, so do not use it merely to keep background work alive during an interactive workflow.
- Nixvim's flake input following the root nixpkgs source does not make its Home Manager module reuse the host's `pkgs` instance.
Keep `programs.nixvim.nixpkgs.useGlobalPackages = true` so Nixvim uses the shared package set without warning that its source default was affected.
- This host has no `python` or `python3` command on its ordinary `PATH`.
For ad hoc Python, use Nix explicitly, such as `nix shell nixpkgs#python3 -c python3 <script>`.
- ADR bodies are immutable records of decisions as they were made, while frontmatter is mutable.
When a decision changes or its premise proves wrong, preserve the original body, update its status, and add a new ADR that supersedes it.
Filename migrations preserve references in immutable bodies through frontmatter aliases rather than rewriting those bodies.
- This repo pins no Nix formatter, and its committed `.nix` files are not clean under current `nixfmt-rfc-style`.
Running `nixfmt` across a file reflows untouched code (for example `lib.nix`'s `deriveMac` list and multi-line assertion messages) and injects churn unrelated to the change.
Format only the lines being written or changed, matching the surrounding style by hand.
- This repo is developed on `neogaia`, which now runs the NixOS it builds.
Flakes and the chaotic substituter come from this flake's own `nix.settings`, so no `NIX_CONFIG` export or per-command `--extra-experimental-features` is needed, and building a toplevel with `boot.kernelPackages = linuxPackages_cachyos` fetches the kernel from `nyx-cache` rather than compiling it.
Both were true only while the machine still ran CachyOS against a distro Nix daemon.
- Git identity is declared in the flake by `modules/git.nix`, which writes `alexion <contact@alexion.dev>` — the identity all history uses — on any host enabling `modules.git`.
Every new host has to enable it, so that a host reads as a full checklist of what it carries.
It is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion <contact@alexion.dev>` with no override.
Verify it that way rather than from this checkout, whose `.git/config` carries the same identity and would mask a broken module.
`~/.gitconfig` (a second global file that outranks the flake-managed `~/.config/git/config`) currently holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, but it is undeclared and will not survive a reimage.
- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.<host>` (the system toplevel).
Cheap targeted checks use `nix eval .#nixosConfigurations.<host>.config...`.
- chaotic-nyx must **not** follow our `nixpkgs`, and its packages are built against chaotic's own pinned nixpkgs (its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path).
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source.
The tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.
- The remote is self-hosted Gitea (`git.alexion.dev`), and the forge CLI is `gitea-axi` rather than `tea`.
`gitea-axi` resolves the repository from the `origin` remote and discovers credentials from a `tea` login whose host matches the remote, so both are implicit inside a checkout.
It is installed on `neogaia` by `modules.agents.tools.gitea-axi`, and verified: `gitea-axi` run from this checkout renders the `alexion/dotfiles` dashboard authenticated, so the claude-code `SessionStart` hook that runs it now resolves to a real binary rather than a missing one.
The package wraps the binary so `git` and `tea` are reachable without being on `PATH`, while still preferring the operator's own where present.
Credentials: `~/.config/tea/config.yml` holds a token-bearing login named `alexion`, which `gitea-axi` uses and which also opens pull requests directly with `nix run nixpkgs#tea -- pr create --login alexion --repo alexion/dotfiles --base main --head <branch> ...`.
The `--repo` flag is required on that path, since `tea` resolves `origin` only for a login whose SSH host matches.
The same token reads PR discussion, which `tea` itself does poorly: `tea pr <n> --comments` prints only the body, and `-f comments` returns no comments field at all.
Use the API instead, taking the token from `.logins[] | select(.name=="alexion") | .token`.
Review comments are **not** at `/issues/<n>/comments` — that endpoint holds only top-level discussion and is usually empty.
Inline comments need two calls: `/pulls/<n>/reviews` for the review ids, then `/pulls/<n>/reviews/<id>/comments` for the bodies, whose `path` and `diff_hunk` fields say what each one is attached to.
A review row with an empty `body` is the normal shape when the operator left only inline comments.
- `~/.claude/skills` and `~/.pi/agent/skills` are home-manager-generated (`recursive = true`), so editing a skill in place fails and a new file created there silently escapes the repo.
Shared global skills come from the `skills` flake through `modules/agents/skills.nix`, applied by a rebuild.
Claude-specific legacy skills, when kept, live under `modules/agents/claude-code/skills/<name>/`.
- Pi skill discovery honors `.gitignore`, `.ignore`, and `.fdignore` inside scanned skill directories.
A generated `.agents/skills/.gitignore` entry that ignores a symlinked skill also prevents Pi from loading that skill, even when `.agents/skills/<name>/SKILL.md` exists and the symlink target is valid.
- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`).
The config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`.
- `nixos-generate-config --show-hardware-config` needs root on this machine even just to print: unprivileged it dies at `Failed to retrieve subvolume info for /`, because the root filesystem is btrfs.
- This repo's claude-code module sets sudo's credential cache to per-user (`timestamp_type=global`, 60-minute window), so an authentication made in one real terminal counts for the agent's commands.
A `PreToolUse` hook refuses privileged commands while the cache is cold, so a cold cache announces itself instead of stalling.
A privileged-command failure *without* that message is the sandbox, not the cache.
- Host GPUs: `neogaia` is Intel, `zeus` (the desktop) is **AMD**, and `raichu` (a headless server) is the only Nvidia machine.
The corrected fact also lives in artifact `006-dotfiles-hyprland-compositor-adr.md`.
- This repo's `programs.firefox` `search` (with `force = true`) writes `search.json.mozlz4`.
Omission alone does not prune a built-in engine, since Firefox reconciles its app-provided engines back in, so remove one by listing it with `<engine>.metaData.hidden = true`.
Engines are referenced by their current id, so the default is `default = "ddg"`, not `"DuckDuckGo"`.
Decode the built file with `mozlz4a -d <search.json.mozlz4>` to check the result.
- Any non-empty Home Manager Firefox `profiles.<name>.extensions.settings.<id>.settings` causes Home Manager to set `extensions.webextensions.ExtensionStorageIDB.enabled = false` globally for that profile.
This repo's Stylix Firefox `colorTheme` settings trigger it, so every extension in the profile uses the legacy extension-storage backend regardless of how it is installed.
- `home.sessionVariables` do **not** reach the Hyprland session, since UWSM does not source `hm-session-vars.sh`.
The cursor is therefore set through Hyprland's own `env = KEY,VALUE` in `modules/desktop/hyprland/hyprland.nix`, sourced from `config.stylix.cursor`.
Bibata ships XCursor format only (no `hyprcursor/` dir), rendered through Hyprland's XCursor fallback, so `XCURSOR_*` and `HYPRCURSOR_*` naming the same theme are both safe.
- `neogaia`, the repo's only host, is a wifi laptop with a btrfs root and no ZFS pools, so it cannot honestly carry `modules.network`, `modules.zfs`, or a networked/pool-mounted guest.
Enabling networkd takes over its DNS, its CachyOS `zfs-kernel` build is marked broken, and it has no bridge or pool to attach to.
Verify these against it ad hoc through `nixosConfigurations.neogaia.extendModules` (forcing a ZFS-capable `boot.kernelPackages` for the zfs case) plus `nix eval` of the derived values, never by committing the enablement.
A committed guest therefore leaves `vlan`, `mounts`, and `secrets` unset, and the standing enablement waits for the first wired server host with real storage.
- Herdr key names for shifted punctuation are not interchangeable with the physical base key plus `shift`.
The tab rename binding must use the produced literal, such as `prefix+<`, rather than `prefix+shift+comma`.
- Flake-managed Pi extension, prompt, and skill directories may still be written directly for throwaway development or local experiments.
The risk is that a later Home Manager activation can overwrite or hide those unmanaged files, so finished work must be promoted into the dotfiles module before it counts as deployed.
- Pi's tool discovery checks `~/.pi/agent/bin` before `PATH`, and downloaded generic Linux binaries there can be unusable on NixOS with the stub-ld error.
This flake patches Pi to validate local tool binaries before selecting them, so it falls back to usable `fd`/`rg` from `PATH` instead.
Stale unpatched launchers are the remaining failure mode for broken `@` autocomplete.
- Nix flake evaluation ignores untracked files in this checkout.
Keep a new auto-loaded module staged or committed until it is removed, otherwise `nix flake check` and `nixos-rebuild --flake` evaluate without it and report its options as missing.
- The current Steam desktop client is an XWayland application.
Its CEF windows do not support Ozone and Steam composites them into an SDL surface with X11 extensions, so SDL Wayland selectors do not make the visible client native Wayland.
Keep fractional scaling sharp with Hyprland's `xwayland.force_zero_scaling` and Steam's own `STEAM_FORCE_DESKTOPUI_SCALING` instead.