# Agent Instructions for Nomarchy You're an AI coding agent picking up the Nomarchy project. This document gives you everything you need to be useful from the first turn: what Nomarchy is, how it's organized, what the rules are, and how to actually pick up the next piece of work. If anything below conflicts with what the user just said, the user wins. If anything below conflicts with the existing code, read the code — these notes can rot. --- ## 1. What Nomarchy is Nomarchy is a NixOS-based distribution that ships a highly curated Hyprland desktop on a strictly declarative, flake-based foundation. It targets power users who want a polished, reproducible desktop environment. Concretely: - A flake at the repo root exposes `nixosModules.system` (foundational OS modules) and `nixosModules.home` (apps + desktop), plus three `nixosConfigurations` (`nomarchy-installer`, `nomarchy-live`, `default`) and standalone `homeConfigurations`. - Downstream users get the distro by importing `nomarchy.nixosModules.system` and `nomarchy.nixosModules.home` from their own `/etc/nixos/flake.nix`. The Nomarchy installer generates that flake for them; an existing-NixOS user can hand-write it (see `docs/MIGRATION.md`). - A bash/`gum` TUI installer lives in `installer/install.sh` (~1100 lines). It auto-detects hardware via `installer/hardware-db.sh` (DMI + `lspci` + `BAT*` sysfs), prompts for the rest, and generates `flake.nix`, `system.nix`, `home.nix`, and `hardware-selection.nix` into `/mnt/etc/nixos/`. - The desktop is Hyprland + waybar + walker + a curated theming engine (`themes/`) with 22 palettes wired through Stylix. Read in this order to come up to speed: 1. `README.md` — public face. 2. `docs/STRUCTURE.md` — directory layout and module logic. 3. `docs/OPTIONS.md` — every `nomarchy.*` option a downstream flake can set. 4. `docs/ROADMAP.md` — what's planned. **This is your work queue.** 5. `docs/SCRIPTS.md` — the script & menu audit table (Pillar 3 of the roadmap). 6. `docs/MIGRATION.md` — how an existing NixOS install becomes Nomarchy. 7. `docs/creating-themes.md` — when palette work comes up. --- ## 2. How the repo is organized ``` core/ Foundational OS + user defaults. Don't put apps here. system/ NixOS modules, all imported via core/default.nix. options.nix nomarchy.system.* options live here. hardware.nix nomarchy.hardware.* options + module wiring. network.nix NetworkManager, DNS, networkmanagerapplet. impermanence.nix Erase-Your-Darlings root wipe. scripts/ Low-level system scripts (battery, brightness, hardware). home/ Home Manager modules. options.nix Most home-side nomarchy.* options. overrides.nix nomarchy.overrides.* (reserved; currently no-op — see ROADMAP). config/ Plain dotfiles symlinked into ~/.config. features/ Apps and desktop components. Add new apps here. apps/ One subdir per app (alacritty, btop, kitty, vscode.nix, …). desktop/ hyprland, waybar, idle.nix, nightlight.nix, … scripts/ User-PATH scripts, battery-monitor user service. utils/ ~68 nomarchy-* user scripts. default.nix Packages them as nomarchy-system-scripts derivation. themes/ Theme engine + 22 palettes. engine/ Loader, Stylix glue, switcher, scripts (font, theme, wallpaper). palettes/ One subdir per palette (summer-night, tokyo-night, …). hosts/ ISO host configs (nomarchy-installer, nomarchy-live/live-iso). installer/ The bash/gum TUI + disko configs + hardware-db.sh. lib/ Shared Nix helpers (state schema, color resolution, paths). docs/ All long-form documentation. README.md stays at repo root. bin/ Convenience wrappers for testing (nomarchy-test-installer, …). ``` When you add a new feature: - A new app → `features/apps//default.nix` (+ optional `config/`), import it from `features/default.nix`. **All app modules must be opt-in** via a `nomarchy.apps..enable` option defined in `core/home/options.nix`. Enabling the module should also handle the `programs..enable` or `home.packages` installation where applicable. - A new system service → `core/system/.nix`, import from `core/default.nix`. - A new toggle → add to `core/system/options.nix` or `core/home/options.nix`, wire it into the relevant module, document it in `docs/OPTIONS.md`. Core user-facing apps (browsers, file managers, media players) are **not** installed by the `features/default.nix` module list. Instead, they are managed in the downstream `home.nix` (which the installer pre-fills with defaults like `firefox` and `thunar`). This keeps the Nomarchy core modules focused on the desktop environment while giving the user explicit control over their app set. --- ## 3. Guardrails (non-negotiable unless the user overrides) These are inherited from the established Nomarchy conventions. Violating them is a bug. 1. **Declarative-first.** No imperative state in `core/`. Mutable state goes in `~/.config/nomarchy/state.json` or in NixOS / home-manager options. 2. **Downstream-flake friendly.** Every behavior toggle is a `nomarchy.*` option documented in `docs/OPTIONS.md`. Adding a feature without a corresponding option is a bug. 3. **Opt-in by default.** New features default off (or default to existing behavior). The installer can flip defaults *for the user being installed*, but the option must read sensibly when set by hand. 4. **`lib.mkDefault` on *scalars* the user might override — never on lists/attrsets that merge.** If a downstream `system.nix` would reasonably want to change a scalar Nomarchy sets (a string, bool, int), set it with `lib.mkDefault`. If it must not be overridden, use `lib.mkForce` and explain why. **But do not wrap a list or attrset option (`home.packages`, `environment.systemPackages`, `boot.kernelParams`, …) in `lib.mkDefault`** when other modules also contribute to it: the module system's `filterOverrides` keeps only the highest-priority definitions, so a `mkDefault` list is *silently discarded the moment any module sets that option at normal priority* — it doesn't merge, it vanishes. This dropped the entire curated `home.packages` (firefox, mako, hyprlock, …) and broke notifications + the lock screen distro-wide (`1117dcf`, `f34f59c`). A plain list/attrset is the correct default — it merges, and downstream can still add to it (to *remove* a default, downstream uses `mkForce`/filtering, which is rare). 5. **Reuse before invent.** ~155 `nomarchy-*` scripts already exist across `core/system/scripts/`, `features/scripts/utils/`, `themes/engine/scripts/`. Grep before writing a new one. 6. **No comments that narrate.** Don't write comments explaining *what* the code does. Only write a comment when the *why* is non-obvious — a hidden constraint, a subtle invariant, a workaround. 7. **No backwards-compat shims.** If you remove a thing, remove it everywhere. No re-exports, no `// removed` markers. 8. **Docs ride with the change.** Every change ships in the same commit as its doc updates — no follow-up "docs catch-up" PRs. The mapping is in §5.4 below; if a change touches something in that table and the matching doc didn't move with it, the change is incomplete. --- ## 4. How to find work to do The roadmap (`docs/ROADMAP.md`) is the source of truth. It has three columns and seven pillars. **Default rule:** prefer items in the **Now** column. Pick whichever matches the user's current ask, or the smallest one if no ask is in flight. If the user asked for something not in the roadmap: - Do the work. - After it ships, propose a one-line addition to `docs/ROADMAP.md` so future you doesn't re-discover it. If the user asks "what's next?", reply with the Now column items in 2–3 sentences and let them pick. ### The script & menu audit (Pillar 3) This is the **largest single open work item**. Phase A (inventory) hasn't run yet; `docs/SCRIPTS.md` is just scaffolding. The user explicitly flagged this pillar as important. Phase A shipped. The inventory lives at [`docs/SCRIPTS.md`](SCRIPTS.md) and is regenerated by: ```bash ./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md ``` It tags every row with one of `kept` / `unused?` / `missing`. Phase B is the per-batch porting/removal: pick ~10 rows tagged `missing` or `unused?`, decide one of `port-from-omarchy` / `delete-dead` / `stub-with-notify`, and ship as one PR per batch on a `wave/audit-` branch. Each PR description should reference the rows it closes; reviewers spot-check that every caller of a removed/renamed script is updated. --- ## 5. Workflow per change Steps you should follow for any non-trivial change: 1. **Understand the current state first.** - `git status` and `git log -5` so you know what's just landed and what's in flight. - Read the relevant files. Don't infer. 2. **Plan if it's non-trivial.** Tasks with three or more steps benefit from a TaskCreate list. Tasks that touch the architecture benefit from plan mode. 3. **Reuse existing options and scripts.** Grep `core/system/options.nix`, `core/home/options.nix`, and the three script directories before adding anything. 4. **Touch the docs in the same change.** Use this table — if your change matches a row, the doc edit ships in the same commit. No exceptions, no follow-ups. | If you change… | Update in the same commit | | --- | --- | | A `nomarchy.*` option (added, removed, renamed, default flipped, type changed, description changed) | `docs/OPTIONS.md` (matching row) **and** any other row that mentions it (e.g. `formFactor` is referenced from `laptop.enable`'s row) | | A `nomarchy-*` script (added, removed, renamed) | `docs/SCRIPTS.md` — the pre-commit hook regenerates it; verify it's staged. If `core.hooksPath` isn't set, run `./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md` manually. | | A keybinding (`bindd =` / `bindeld =`) under `core/home/config/.../hypr/` | `docs/KEYBINDINGS.md` — regenerate via `./bin/utils/nomarchy-docs-keybindings --out docs/KEYBINDINGS.md`; spot-check the README highlights still hold | | A directory was added/removed/renamed under `core/`, `features/`, `themes/`, `hosts/`, `installer/`, `lib/`, `bin/`, or `docs/` | `docs/STRUCTURE.md` (tree + module logic prose) **and** §2 of this file (`docs/AGENT.md`) if the change is structural enough to affect new-agent onboarding | | A feature module's behavior changed in a way an existing user would notice (a default flipped, a service started/stopped, a key remapped) | `README.md` if it's user-visible at the marketing level; otherwise just `docs/OPTIONS.md` | | An installer prompt added/removed/reordered, or generated-file shape changed | `docs/MIGRATION.md` if it affects hand-written `flake.nix` setups; `installer/install.sh` heredoc comments if a generated file's shape changed | | A theme palette added/removed, or the theming engine's contract changed | `docs/creating-themes.md` and the palette count in `README.md` | | A roadmap item shipped | Move the entry to the **Shipped** section at the bottom of `docs/ROADMAP.md`. Don't delete — that's our changelog. | | A roadmap item discovered (new scope) | Add a one-line row to **Now**, **Next**, or **Later** in `docs/ROADMAP.md`. Don't tell the user verbally and forget. | | A workflow / convention / guardrail you'd want a future agent to follow | `docs/AGENT.md` (this file). | | A new flake input, output, or major architectural decision | `docs/STRUCTURE.md` and `docs/AGENT.md` §1. | Before declaring done, do one explicit pass: did the change cross any row above? If yes, is the matching doc edit staged? If you can't answer "yes" to both, you're not done. 5. **Verify the change evaluates** (cheap, do this before declaring done): ```bash nix --extra-experimental-features 'nix-command flakes' flake check --no-build bash -n installer/install.sh # if you touched it ``` `flake check` only evaluates the four *default* configs — it never flips a `nomarchy.*` toggle. If you touched anything that only fires when a toggle is enabled (impermanence, a preset, an opt-in feature, the per-palette system theme), also run the toggle matrix, which layers each opt-in scenario onto the default config and forces it: ```bash ./bin/utils/nomarchy-eval-matrix ``` When you **add or rename an opt-in `nomarchy.*` option**, fold it into the relevant combined scenario in the `scenarios` attrset in that script (the scenarios bundle many compatible toggles per eval to stay fast on the CI runner) — otherwise the new surface is unverified and the next eval-time bug in it ships silently (this is exactly how the impermanence assertion and the vscode option rename reached `main`). The matrix also runs in CI (`.gitea/workflows/check.yml`). First clone? Enable the repo's pre-commit hook so `docs/SCRIPTS.md` regenerates whenever you add/modify/remove a `nomarchy-*` script: ```bash git config core.hooksPath .githooks ``` For installer changes, also do a dry-run end-to-end: ```bash sudo /etc/install.sh --dry-run # in the live ISO or VM ``` For waybar / Hyprland visual changes, boot it and look. On a host with KVM you can do this headless: `nix build .#nixosConfigurations.default.config.system.build.vm`, run its `bin/run-*-vm` with `QEMU_OPTS="-display none -monitor unix:/tmp/mon.sock,server,nowait -vga none"`, then `echo "screendump /tmp/s.ppm" | socat - unix-connect:/tmp/mon.sock`, convert PPM→PNG, and inspect it (this is how the first Pillar 9 pass caught the boot-time Hyprland config-error overlay). The `nomarchy-test-live-iso` / `installerVm` flows are the ISO-side equivalents. If you genuinely can't boot anything, **say so** rather than claiming a visual change works. See ROADMAP §9 (Live VM runtime QA) for the full method. 6. **Commit narrowly.** One concept per commit. The commit subject is `: ` (`feat:`, `fix:`, `docs:`, `chore:`). The body explains the why. 7. **Push only when the user asks.** Local commits are free; pushing publishes. --- ## 6. Patterns worth knowing ### Adding a new option ```nix # core/system/options.nix (or core/home/options.nix for home-side) mything.enable = lib.mkEnableOption '' Concise description that reads well in `nix eval`. Mention any pre-conditions (groups, kernel modules, hardware needed). ''; ``` Then wire it inside a `config = lib.mkIf cfg.mything.enable { ... }` block and document it in `docs/OPTIONS.md`. ### Filtering modules conditionally We do this for waybar (drop `battery` on desktop) in `features/desktop/waybar/default.nix:25-39`. Pattern: ```nix let rawSettings = builtins.fromJSON (builtins.readFile configFile); laptopOnly = [ "battery" "custom/battery" ]; filterModules = mods: if config.nomarchy.formFactor == "laptop" then mods else builtins.filter (m: !(builtins.elem m laptopOnly)) mods; settings = rawSettings // { modules-right = filterModules (rawSettings.modules-right or []); modules-center = filterModules (rawSettings.modules-center or []); modules-left = filterModules (rawSettings.modules-left or []); }; in { ... } ``` ### Form-factor (laptop vs desktop) `nomarchy.system.formFactor` and `nomarchy.formFactor` are the two halves of the same flag (system + home). Default `"laptop"`. The installer auto-detects via `compgen -G "/sys/class/power_supply/BAT*"` and writes the explicit value into both generated files. Use this option to gate any laptop-only UI / service. ### Hybrid State (`state.json` + `nomarchy-state.nix`) Nomarchy uses a hybrid model to bridge the gap between runtime UI discovery and declarative Nix persistence. 1. **Runtime (`state.json`):** Located at `~/.config/nomarchy/state.json`. Consumed by scripts for instant session reloads (Waybar, Walker, etc.). 2. **Declarative (`nomarchy-state.nix`):** Located at `/etc/nixos/nomarchy-state.nix`. This file is the primary authority for Nix evaluation and is imported by `home.nix`. 3. **The Sync:** Whenever a script calls `nomarchy-state-write`, the `nomarchy-sync-nix-state` helper is triggered. It mirrors the current UI state into the `.nix` file automatically. 4. **Solidification:** To make a UI change permanent, the user (or script) runs `nomarchy-env-update`, which performs a fast `home-manager switch`. When adding new configuration options that should be script-manageable, ensure they are added to the sync logic in `features/scripts/utils/nomarchy-sync-nix-state`. ### Scripts derivation User-PATH scripts ship via `nomarchy-system-scripts` (`core/system/scripts-derivation.nix`) plus the per-category dependencies declared in `features/scripts/default.nix:categoryDeps`. When you add a script: 1. Drop the file in `features/scripts/utils/` or `core/system/scripts/`. 2. `chmod +x` it (it'll be wrapped with the right deps automatically). 3. Reference the right `categoryDeps` group in `features/scripts/default.nix` if it needs new tools. 4. Test that `which